Fork of daniellemaywood.uk/gleam — Wasm codegen work
244 kB
7144 lines
1use std::{collections::HashSet, iter, sync::Arc};
2
3use crate::{
4 Error, STDLIB_PACKAGE_NAME, analyse,
5 ast::{
6 self, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType,
7 FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern,
8 PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind,
9 TypedArg, TypedAssignment, TypedExpr, TypedModuleConstant, TypedPattern,
10 TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse,
11 visit::Visit as _,
12 },
13 build::{Located, Module},
14 config::PackageConfig,
15 exhaustiveness::CompiledCase,
16 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter},
17 line_numbers::LineNumbers,
18 parse::{extra::ModuleExtra, lexer::str_to_keyword},
19 strings::to_snake_case,
20 type_::{
21 self, FieldMap, ModuleValueConstructor, Type, TypeVar, TypedCallArg, ValueConstructor,
22 error::{ModuleSuggestion, VariableOrigin},
23 printer::{Names, Printer},
24 },
25};
26use ecow::{EcoString, eco_format};
27use im::HashMap;
28use itertools::Itertools;
29use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
30use vec1::{Vec1, vec1};
31
32use super::{
33 TextEdits,
34 compiler::LspProjectCompiler,
35 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
36 engine::{overlaps, within},
37 files::FileSystemProxy,
38 reference::find_variable_references,
39 src_span_to_lsp_range, url_from_path,
40};
41
42#[derive(Debug)]
43pub struct CodeActionBuilder {
44 action: CodeAction,
45}
46
47impl CodeActionBuilder {
48 pub fn new(title: &str) -> Self {
49 Self {
50 action: CodeAction {
51 title: title.to_string(),
52 kind: None,
53 diagnostics: None,
54 edit: None,
55 command: None,
56 is_preferred: None,
57 disabled: None,
58 data: None,
59 },
60 }
61 }
62
63 pub fn kind(mut self, kind: CodeActionKind) -> Self {
64 self.action.kind = Some(kind);
65 self
66 }
67
68 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
69 let mut edit = self.action.edit.take().unwrap_or_default();
70 let mut changes = edit.changes.take().unwrap_or_default();
71 _ = changes.insert(uri, edits);
72
73 edit.changes = Some(changes);
74 self.action.edit = Some(edit);
75 self
76 }
77
78 pub fn preferred(mut self, is_preferred: bool) -> Self {
79 self.action.is_preferred = Some(is_preferred);
80 self
81 }
82
83 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
84 actions.push(self.action);
85 }
86}
87
88/// A small helper function to get the indentation at a given position.
89fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
90 let mut indent_size = 0;
91 let line_start = *line_numbers
92 .line_starts
93 .get(line as usize)
94 .expect("Line number should be valid");
95
96 let mut chars = code[line_start as usize..].chars();
97 while chars.next() == Some(' ') {
98 indent_size += 1;
99 }
100
101 indent_size
102}
103
104/// Code action to remove literal tuples in case subjects, essentially making
105/// the elements of the tuples into the case's subjects.
106///
107/// The code action is only available for the i'th subject if:
108/// - it is a non-empty tuple, and
109/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
110///
111/// # Basic example:
112///
113/// The following case expression:
114///
115/// ```gleam
116/// case #(1, 2) {
117/// #(a, b) -> 0
118/// }
119/// ```
120///
121/// Becomes:
122///
123/// ```gleam
124/// case 1, 2 {
125/// a, b -> 0
126/// }
127/// ```
128///
129/// # Another example:
130///
131/// The following case expression does not produce any code action
132///
133/// ```gleam
134/// case #(1, 2) {
135/// a -> 0 // <- the pattern is not a tuple
136/// }
137/// ```
138pub struct RedundantTupleInCaseSubject<'a> {
139 edits: TextEdits<'a>,
140 code: &'a EcoString,
141 extra: &'a ModuleExtra,
142 params: &'a CodeActionParams,
143 module: &'a ast::TypedModule,
144 hovered: bool,
145}
146
147impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
148 fn visit_typed_expr_case(
149 &mut self,
150 location: &'ast SrcSpan,
151 type_: &'ast Arc<Type>,
152 subjects: &'ast [TypedExpr],
153 clauses: &'ast [ast::TypedClause],
154 compiled_case: &'ast CompiledCase,
155 ) {
156 for (subject_idx, subject) in subjects.iter().enumerate() {
157 let TypedExpr::Tuple {
158 location, elements, ..
159 } = subject
160 else {
161 continue;
162 };
163
164 // Ignore empty tuple
165 if elements.is_empty() {
166 continue;
167 }
168
169 // We cannot rewrite clauses whose i-th pattern is not a discard or
170 // tuples.
171 let all_ith_patterns_are_tuples_or_discards = clauses
172 .iter()
173 .map(|clause| clause.pattern.get(subject_idx))
174 .all(|pattern| {
175 matches!(
176 pattern,
177 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
178 )
179 });
180
181 if !all_ith_patterns_are_tuples_or_discards {
182 continue;
183 }
184
185 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
186
187 for clause in clauses {
188 match clause.pattern.get(subject_idx) {
189 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
190 *location,
191 elements.last().map(|element| element.location()),
192 ),
193 Some(Pattern::Discard { location, .. }) => {
194 self.discard_tuple_items(*location, elements.len())
195 }
196 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
197 }
198 }
199 let range = self.edits.src_span_to_lsp_range(*location);
200 self.hovered = self.hovered || overlaps(self.params.range, range);
201 }
202
203 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case)
204 }
205}
206
207impl<'a> RedundantTupleInCaseSubject<'a> {
208 pub fn new(
209 module: &'a Module,
210 line_numbers: &'a LineNumbers,
211 params: &'a CodeActionParams,
212 ) -> Self {
213 Self {
214 edits: TextEdits::new(line_numbers),
215 code: &module.code,
216 extra: &module.extra,
217 params,
218 module: &module.ast,
219 hovered: false,
220 }
221 }
222
223 pub fn code_actions(mut self) -> Vec<CodeAction> {
224 self.visit_typed_module(self.module);
225 if !self.hovered {
226 return vec![];
227 }
228
229 self.edits.edits.sort_by_key(|edit| edit.range.start);
230
231 let mut actions = vec![];
232 CodeActionBuilder::new("Remove redundant tuples")
233 .kind(CodeActionKind::REFACTOR_REWRITE)
234 .changes(self.params.text_document.uri.clone(), self.edits.edits)
235 .preferred(true)
236 .push_to(&mut actions);
237
238 actions
239 }
240
241 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
242 let tuple_code = self
243 .code
244 .get(location.start as usize..location.end as usize)
245 .expect("valid span");
246
247 // Delete `#`
248 self.edits
249 .delete(SrcSpan::new(location.start, location.start + 1));
250
251 // Delete `(`
252 let (lparen_offset, _) = tuple_code
253 .match_indices('(')
254 // Ignore in comments
255 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
256 .expect("`(` not found in tuple");
257
258 self.edits.delete(SrcSpan::new(
259 location.start + lparen_offset as u32,
260 location.start + lparen_offset as u32 + 1,
261 ));
262
263 // Delete trailing `,` (if applicable)
264 if let Some(last_elem_location) = last_elem_location {
265 // Get the code after the last element until the tuple's `)`
266 let code_after_last_elem = self
267 .code
268 .get(last_elem_location.end as usize..location.end as usize)
269 .expect("valid span");
270
271 if let Some((trailing_comma_offset, _)) = code_after_last_elem
272 .rmatch_indices(',')
273 // Ignore in comments
274 .find(|(i, _)| {
275 !self
276 .extra
277 .is_within_comment(last_elem_location.end + *i as u32)
278 })
279 {
280 self.edits.delete(SrcSpan::new(
281 last_elem_location.end + trailing_comma_offset as u32,
282 last_elem_location.end + trailing_comma_offset as u32 + 1,
283 ));
284 }
285 }
286
287 // Delete )
288 self.edits
289 .delete(SrcSpan::new(location.end - 1, location.end));
290 }
291
292 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
293 // Replace the old discard with multiple discard, one for each of the
294 // tuple items.
295 self.edits.replace(
296 discard_location,
297 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
298 )
299 }
300}
301
302/// Builder for code action to convert `let assert` into a case expression.
303///
304pub struct LetAssertToCase<'a> {
305 module: &'a Module,
306 params: &'a CodeActionParams,
307 actions: Vec<CodeAction>,
308 edits: TextEdits<'a>,
309}
310
311impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
312 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
313 // To prevent weird behaviour when `let assert` statements are nested,
314 // we only check for the code action between the `let` and `=`.
315 let code_action_location =
316 SrcSpan::new(assignment.location.start, assignment.value.location().start);
317 let code_action_range =
318 src_span_to_lsp_range(code_action_location, self.edits.line_numbers);
319
320 self.visit_typed_expr(&assignment.value);
321
322 // Only offer the code action if the cursor is over the statement
323 if !overlaps(code_action_range, self.params.range) {
324 return;
325 }
326
327 // This pattern only applies to `let assert`
328 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
329 return;
330 };
331
332 // Get the source code for the tested expression
333 let location = assignment.value.location();
334 let expr = self
335 .module
336 .code
337 .get(location.start as usize..location.end as usize)
338 .expect("Location must be valid");
339
340 // Get the source code for the pattern
341 let pattern_location = assignment.pattern.location();
342 let pattern = self
343 .module
344 .code
345 .get(pattern_location.start as usize..pattern_location.end as usize)
346 .expect("Location must be valid");
347
348 let message = message.as_ref().map(|message| {
349 let location = message.location();
350 self.module
351 .code
352 .get(location.start as usize..location.end as usize)
353 .expect("Location must be valid")
354 });
355
356 let range = src_span_to_lsp_range(assignment.location, self.edits.line_numbers);
357
358 // Figure out which variables are assigned in the pattern
359 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
360
361 let assigned = match variables.len() {
362 0 => "_",
363 1 => variables.first().expect("Variables is length one"),
364 _ => &format!("#({})", variables.join(", ")),
365 };
366
367 let mut new_text = format!("let {assigned} = ");
368 let panic_message = if let Some(message) = message {
369 &format!("panic as {message}")
370 } else {
371 "panic"
372 };
373 let clauses = vec![
374 // The existing pattern
375 CaseClause {
376 pattern,
377 // `_` is not a valid expression, so if we are not
378 // binding any variables in the pattern, we simply return Nil.
379 expression: if assigned == "_" { "Nil" } else { assigned },
380 },
381 CaseClause {
382 pattern: "_",
383 expression: panic_message,
384 },
385 ];
386 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
387
388 let uri = &self.params.text_document.uri;
389
390 CodeActionBuilder::new("Convert to case")
391 .kind(CodeActionKind::REFACTOR_REWRITE)
392 .changes(uri.clone(), vec![TextEdit { range, new_text }])
393 .preferred(false)
394 .push_to(&mut self.actions);
395 }
396}
397
398impl<'a> LetAssertToCase<'a> {
399 pub fn new(
400 module: &'a Module,
401 line_numbers: &'a LineNumbers,
402 params: &'a CodeActionParams,
403 ) -> Self {
404 Self {
405 module,
406 params,
407 actions: Vec::new(),
408 edits: TextEdits::new(line_numbers),
409 }
410 }
411
412 pub fn code_actions(mut self) -> Vec<CodeAction> {
413 self.visit_typed_module(&self.module.ast);
414 self.actions
415 }
416}
417
418struct PatternVariableFinder {
419 pattern_variables: Vec<EcoString>,
420}
421
422impl PatternVariableFinder {
423 fn new() -> Self {
424 Self {
425 pattern_variables: Vec::new(),
426 }
427 }
428
429 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
430 let mut finder = Self::new();
431 finder.visit_typed_pattern(pattern);
432 finder.pattern_variables
433 }
434}
435
436impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
437 fn visit_typed_pattern_variable(
438 &mut self,
439 _location: &'ast SrcSpan,
440 name: &'ast EcoString,
441 _type: &'ast Arc<Type>,
442 _origin: &'ast VariableOrigin,
443 ) {
444 self.pattern_variables.push(name.clone());
445 }
446
447 fn visit_typed_pattern_assign(
448 &mut self,
449 location: &'ast SrcSpan,
450 name: &'ast EcoString,
451 pattern: &'ast TypedPattern,
452 ) {
453 self.pattern_variables.push(name.clone());
454 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
455 }
456
457 fn visit_typed_pattern_string_prefix(
458 &mut self,
459 _location: &'ast SrcSpan,
460 _left_location: &'ast SrcSpan,
461 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
462 _right_location: &'ast SrcSpan,
463 _left_side_string: &'ast EcoString,
464 right_side_assignment: &'ast AssignName,
465 ) {
466 if let Some((name, _)) = left_side_assignment {
467 self.pattern_variables.push(name.clone());
468 }
469 if let AssignName::Variable(name) = right_side_assignment {
470 self.pattern_variables.push(name.clone());
471 }
472 }
473}
474
475pub fn code_action_inexhaustive_let_to_case(
476 module: &Module,
477 line_numbers: &LineNumbers,
478 params: &CodeActionParams,
479 error: &Option<Error>,
480 actions: &mut Vec<CodeAction>,
481) {
482 let Some(Error::Type { errors, .. }) = error else {
483 return;
484 };
485 let inexhaustive_assignments = errors
486 .iter()
487 .filter_map(|error| match error {
488 type_::Error::InexhaustiveLetAssignment { location, missing } => {
489 Some((*location, missing))
490 }
491 _ => None,
492 })
493 .collect_vec();
494
495 if inexhaustive_assignments.is_empty() {
496 return;
497 }
498
499 for (location, missing) in inexhaustive_assignments {
500 let mut text_edits = TextEdits::new(line_numbers);
501
502 let range = text_edits.src_span_to_lsp_range(location);
503 if !overlaps(params.range, range) {
504 return;
505 }
506
507 let Some(Located::Statement(TypedStatement::Assignment(TypedAssignment {
508 value,
509 pattern,
510 kind: AssignmentKind::Let,
511 location,
512 compiled_case: _,
513 annotation: _,
514 }))) = module.find_node(location.start)
515 else {
516 continue;
517 };
518
519 // Get the source code for the tested expression
520 let value_location = value.location();
521 let expr = module
522 .code
523 .get(value_location.start as usize..value_location.end as usize)
524 .expect("Location must be valid");
525
526 // Get the source code for the pattern
527 let pattern_location = pattern.location();
528 let pattern_code = module
529 .code
530 .get(pattern_location.start as usize..pattern_location.end as usize)
531 .expect("Location must be valid");
532
533 let range = text_edits.src_span_to_lsp_range(*location);
534
535 // Figure out which variables are assigned in the pattern
536 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
537
538 let assigned = match variables.len() {
539 0 => "_",
540 1 => variables.first().expect("Variables is length one"),
541 _ => &format!("#({})", variables.join(", ")),
542 };
543
544 let mut new_text = format!("let {assigned} = ");
545 print_case_expression(
546 range.start.character as usize,
547 expr,
548 iter::once(CaseClause {
549 pattern: pattern_code,
550 expression: if assigned == "_" { "Nil" } else { assigned },
551 })
552 .chain(missing.iter().map(|pattern| CaseClause {
553 pattern,
554 expression: "todo",
555 }))
556 .collect(),
557 &mut new_text,
558 );
559
560 let uri = ¶ms.text_document.uri;
561
562 text_edits.replace(*location, new_text);
563
564 CodeActionBuilder::new("Convert to case")
565 .kind(CodeActionKind::QUICKFIX)
566 .changes(uri.clone(), text_edits.edits)
567 .preferred(true)
568 .push_to(actions);
569 }
570}
571
572struct CaseClause<'a> {
573 pattern: &'a str,
574 expression: &'a str,
575}
576
577fn print_case_expression(
578 indent_size: usize,
579 subject: &str,
580 clauses: Vec<CaseClause<'_>>,
581 buffer: &mut String,
582) {
583 let indent = " ".repeat(indent_size);
584
585 // Print the beginning of the expression
586 buffer.push_str("case ");
587 buffer.push_str(subject);
588 buffer.push_str(" {");
589
590 for clause in clauses.iter() {
591 // Print the newline and indentation for this clause
592 buffer.push('\n');
593 buffer.push_str(&indent);
594 // Indent this clause one level deeper than the case expression
595 buffer.push_str(" ");
596
597 // Print the clause
598 buffer.push_str(clause.pattern);
599 buffer.push_str(" -> ");
600 buffer.push_str(clause.expression);
601 }
602
603 // If there are no clauses to print, the closing brace should be
604 // on the same line as the opening one, with no space between.
605 if !clauses.is_empty() {
606 buffer.push('\n');
607 buffer.push_str(&indent);
608 }
609 buffer.push('}');
610}
611
612/// Builder for code action to apply the label shorthand syntax on arguments
613/// where the label has the same name as the variable.
614///
615pub struct UseLabelShorthandSyntax<'a> {
616 module: &'a Module,
617 params: &'a CodeActionParams,
618 edits: TextEdits<'a>,
619}
620
621impl<'a> UseLabelShorthandSyntax<'a> {
622 pub fn new(
623 module: &'a Module,
624 line_numbers: &'a LineNumbers,
625 params: &'a CodeActionParams,
626 ) -> Self {
627 Self {
628 module,
629 params,
630 edits: TextEdits::new(line_numbers),
631 }
632 }
633
634 pub fn code_actions(mut self) -> Vec<CodeAction> {
635 self.visit_typed_module(&self.module.ast);
636 if self.edits.edits.is_empty() {
637 return vec![];
638 }
639 let mut action = Vec::with_capacity(1);
640 CodeActionBuilder::new("Use label shorthand syntax")
641 .kind(CodeActionKind::REFACTOR)
642 .changes(self.params.text_document.uri.clone(), self.edits.edits)
643 .preferred(false)
644 .push_to(&mut action);
645 action
646 }
647}
648
649impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
650 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
651 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
652 let is_selected = overlaps(arg_range, self.params.range);
653
654 match arg {
655 CallArg {
656 label: Some(label),
657 value: TypedExpr::Var { name, location, .. },
658 ..
659 } if is_selected && !arg.uses_label_shorthand() && label == name => {
660 self.edits.delete(*location)
661 }
662 _ => (),
663 }
664
665 ast::visit::visit_typed_call_arg(self, arg)
666 }
667
668 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
669 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
670 let is_selected = overlaps(arg_range, self.params.range);
671
672 match arg {
673 CallArg {
674 label: Some(label),
675 value: TypedPattern::Variable { name, location, .. },
676 ..
677 } if is_selected && !arg.uses_label_shorthand() && label == name => {
678 self.edits.delete(*location)
679 }
680 _ => (),
681 }
682
683 ast::visit::visit_typed_pattern_call_arg(self, arg)
684 }
685}
686
687/// Builder for code action to apply the fill in the missing labelled arguments
688/// of the selected function call.
689///
690pub struct FillInMissingLabelledArgs<'a> {
691 module: &'a Module,
692 params: &'a CodeActionParams,
693 edits: TextEdits<'a>,
694 use_right_hand_side_location: Option<SrcSpan>,
695 selected_call: Option<SelectedCall<'a>>,
696}
697
698struct SelectedCall<'a> {
699 location: SrcSpan,
700 field_map: &'a FieldMap,
701 arguments: Vec<CallArg<()>>,
702 kind: SelectedCallKind,
703}
704
705enum SelectedCallKind {
706 Value,
707 Pattern,
708}
709
710impl<'a> FillInMissingLabelledArgs<'a> {
711 pub fn new(
712 module: &'a Module,
713 line_numbers: &'a LineNumbers,
714 params: &'a CodeActionParams,
715 ) -> Self {
716 Self {
717 module,
718 params,
719 edits: TextEdits::new(line_numbers),
720 use_right_hand_side_location: None,
721 selected_call: None,
722 }
723 }
724
725 pub fn code_actions(mut self) -> Vec<CodeAction> {
726 self.visit_typed_module(&self.module.ast);
727
728 if let Some(SelectedCall {
729 location: call_location,
730 field_map,
731 arguments,
732 kind,
733 }) = self.selected_call
734 {
735 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
736 let missing_labels = field_map.missing_labels(&arguments);
737
738 // If we're applying the code action to a use call, then we know
739 // that the last missing argument is going to be implicitly inserted
740 // by the compiler, so in that case we don't want to also add that
741 // last label to the completions.
742 let missing_labels = missing_labels.iter().peekable();
743 let mut missing_labels = if is_use_call {
744 missing_labels.dropping_back(1)
745 } else {
746 missing_labels
747 };
748
749 // If we couldn't find any missing label to insert we just return.
750 if missing_labels.peek().is_none() {
751 return vec![];
752 }
753
754 // Now we need to figure out if there's a comma at the end of the
755 // arguments list:
756 //
757 // call(one, |)
758 // ^ Cursor here, with a comma behind
759 //
760 // call(one|)
761 // ^ Cursor here, no comma behind, we'll have to add one!
762 //
763 let label_insertion_start = call_location.end - 1;
764 let has_comma_after_last_argument = if let Some(last_arg) = arguments
765 .iter()
766 .filter(|arg| !arg.is_implicit())
767 .next_back()
768 {
769 self.module
770 .code
771 .get(last_arg.location.end as usize..=label_insertion_start as usize)
772 .is_some_and(|text| text.contains(','))
773 } else {
774 false
775 };
776
777 let format_label = match kind {
778 SelectedCallKind::Value => |label| format!("{label}: todo"),
779 SelectedCallKind::Pattern => |label| format!("{label}:"),
780 };
781
782 let labels_list = missing_labels.map(format_label).join(", ");
783
784 let has_no_explicit_arguments = arguments
785 .iter()
786 .filter(|arg| !arg.is_implicit())
787 .peekable()
788 .peek()
789 .is_none();
790
791 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
792 labels_list
793 } else {
794 format!(", {labels_list}")
795 };
796
797 self.edits.insert(label_insertion_start, labels_list);
798
799 let mut action = Vec::with_capacity(1);
800 CodeActionBuilder::new("Fill labels")
801 .kind(CodeActionKind::QUICKFIX)
802 .changes(self.params.text_document.uri.clone(), self.edits.edits)
803 .preferred(true)
804 .push_to(&mut action);
805 return action;
806 }
807
808 vec![]
809 }
810
811 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
812 CallArg {
813 label: argument.label.clone(),
814 location: argument.location,
815 value: (),
816 implicit: argument.implicit,
817 }
818 }
819}
820
821impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
822 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
823 // If we're adding labels to a use call the correct location of the
824 // function we need to add labels to is `use_right_hand_side_location`.
825 // So we store it for when we're typing the use call.
826 let previous = self.use_right_hand_side_location;
827 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
828 ast::visit::visit_typed_use(self, use_);
829 self.use_right_hand_side_location = previous;
830 }
831
832 fn visit_typed_expr_call(
833 &mut self,
834 location: &'ast SrcSpan,
835 type_: &'ast Arc<Type>,
836 fun: &'ast TypedExpr,
837 args: &'ast [TypedCallArg],
838 ) {
839 let call_range = self.edits.src_span_to_lsp_range(*location);
840 if !within(self.params.range, call_range) {
841 return;
842 }
843
844 if let Some(field_map) = fun.field_map() {
845 let location = self.use_right_hand_side_location.unwrap_or(*location);
846 self.selected_call = Some(SelectedCall {
847 location,
848 field_map,
849 arguments: args.iter().map(Self::empty_argument).collect(),
850 kind: SelectedCallKind::Value,
851 })
852 }
853
854 // We only want to take into account the innermost function call
855 // containing the current selection so we can't stop at the first call
856 // we find (the outermost one) and have to keep traversing it in case
857 // we're inside a nested call.
858 let previous = self.use_right_hand_side_location;
859 self.use_right_hand_side_location = None;
860 ast::visit::visit_typed_expr_call(self, location, type_, fun, args);
861 self.use_right_hand_side_location = previous;
862 }
863
864 fn visit_typed_pattern_constructor(
865 &mut self,
866 location: &'ast SrcSpan,
867 name_location: &'ast SrcSpan,
868 name: &'ast EcoString,
869 arguments: &'ast Vec<CallArg<TypedPattern>>,
870 module: &'ast Option<(EcoString, SrcSpan)>,
871 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
872 spread: &'ast Option<SrcSpan>,
873 type_: &'ast Arc<Type>,
874 ) {
875 let call_range = self.edits.src_span_to_lsp_range(*location);
876 if !within(self.params.range, call_range) {
877 return;
878 }
879
880 if let Some(field_map) = constructor.field_map() {
881 self.selected_call = Some(SelectedCall {
882 location: *location,
883 field_map,
884 arguments: arguments.iter().map(Self::empty_argument).collect(),
885 kind: SelectedCallKind::Pattern,
886 })
887 }
888
889 ast::visit::visit_typed_pattern_constructor(
890 self,
891 location,
892 name_location,
893 name,
894 arguments,
895 module,
896 constructor,
897 spread,
898 type_,
899 );
900 }
901}
902
903struct MissingImport {
904 location: SrcSpan,
905 suggestions: Vec<ImportSuggestion>,
906}
907
908struct ImportSuggestion {
909 // The name to replace with, if the user made a typo
910 name: EcoString,
911 // The optional module to import, if suggesting an importable module
912 import: Option<EcoString>,
913}
914
915pub fn code_action_import_module(
916 module: &Module,
917 line_numbers: &LineNumbers,
918 params: &CodeActionParams,
919 error: &Option<Error>,
920 actions: &mut Vec<CodeAction>,
921) {
922 let uri = ¶ms.text_document.uri;
923 let Some(Error::Type { errors, .. }) = error else {
924 return;
925 };
926
927 let missing_imports = errors
928 .into_iter()
929 .filter_map(|e| match e {
930 type_::Error::UnknownModule {
931 location,
932 suggestions,
933 ..
934 } => suggest_imports(*location, suggestions),
935 _ => None,
936 })
937 .collect_vec();
938
939 if missing_imports.is_empty() {
940 return;
941 }
942
943 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
944 let first_is_import = first_import_pos.is_some();
945 let import_location = first_import_pos.unwrap_or_default();
946
947 let after_import_newlines =
948 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
949
950 for missing_import in missing_imports {
951 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
952 if !overlaps(params.range, range) {
953 continue;
954 }
955
956 for suggestion in missing_import.suggestions {
957 let mut edits = vec![TextEdit {
958 range,
959 new_text: suggestion.name.to_string(),
960 }];
961 if let Some(import) = &suggestion.import {
962 edits.push(get_import_edit(
963 import_location,
964 import,
965 &after_import_newlines,
966 ))
967 };
968
969 let title = match &suggestion.import {
970 Some(import) => &format!("Import `{import}`"),
971 _ => &format!("Did you mean `{}`", suggestion.name),
972 };
973
974 CodeActionBuilder::new(title)
975 .kind(CodeActionKind::QUICKFIX)
976 .changes(uri.clone(), edits)
977 .preferred(true)
978 .push_to(actions);
979 }
980 }
981}
982
983fn suggest_imports(
984 location: SrcSpan,
985 importable_modules: &[ModuleSuggestion],
986) -> Option<MissingImport> {
987 let suggestions = importable_modules
988 .iter()
989 .map(|suggestion| {
990 let imported_name = suggestion.last_name_component();
991 match suggestion {
992 ModuleSuggestion::Importable(name) => ImportSuggestion {
993 name: imported_name.into(),
994 import: Some(name.clone()),
995 },
996 ModuleSuggestion::Imported(_) => ImportSuggestion {
997 name: imported_name.into(),
998 import: None,
999 },
1000 }
1001 })
1002 .collect_vec();
1003
1004 if suggestions.is_empty() {
1005 None
1006 } else {
1007 Some(MissingImport {
1008 location,
1009 suggestions,
1010 })
1011 }
1012}
1013
1014pub fn code_action_add_missing_patterns(
1015 module: &Module,
1016 line_numbers: &LineNumbers,
1017 params: &CodeActionParams,
1018 error: &Option<Error>,
1019 actions: &mut Vec<CodeAction>,
1020) {
1021 let uri = ¶ms.text_document.uri;
1022 let Some(Error::Type { errors, .. }) = error else {
1023 return;
1024 };
1025 let missing_patterns = errors
1026 .iter()
1027 .filter_map(|error| match error {
1028 type_::Error::InexhaustiveCaseExpression { location, missing } => {
1029 Some((*location, missing))
1030 }
1031 _ => None,
1032 })
1033 .collect_vec();
1034
1035 if missing_patterns.is_empty() {
1036 return;
1037 }
1038
1039 for (location, missing) in missing_patterns {
1040 let mut edits = TextEdits::new(line_numbers);
1041 let range = edits.src_span_to_lsp_range(location);
1042 if !overlaps(params.range, range) {
1043 return;
1044 }
1045
1046 let Some(Located::Expression {
1047 expression: TypedExpr::Case {
1048 clauses, subjects, ..
1049 },
1050 ..
1051 }) = module.find_node(location.start)
1052 else {
1053 continue;
1054 };
1055
1056 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1057
1058 let indent = " ".repeat(indent_size);
1059
1060 // Insert the missing patterns just after the final clause, or just before
1061 // the closing brace if there are no clauses
1062
1063 let insert_at = clauses
1064 .last()
1065 .map(|clause| clause.location.end)
1066 .unwrap_or(location.end - 1);
1067
1068 for pattern in missing {
1069 let new_text = format!("\n{indent} {pattern} -> todo");
1070 edits.insert(insert_at, new_text);
1071 }
1072
1073 // Add a newline + indent after the last pattern if there are no clauses
1074 //
1075 // This improves the generated code for this case:
1076 // ```gleam
1077 // case True {}
1078 // ```
1079 // This produces:
1080 // ```gleam
1081 // case True {
1082 // True -> todo
1083 // False -> todo
1084 // }
1085 // ```
1086 // Instead of:
1087 // ```gleam
1088 // case True {
1089 // True -> todo
1090 // False -> todo}
1091 // ```
1092 //
1093 if clauses.is_empty() {
1094 let last_subject_location = subjects
1095 .last()
1096 .expect("Case expressions have at least one subject")
1097 .location()
1098 .end;
1099
1100 // Find the opening brace of the case expression
1101 let chars = module.code[last_subject_location as usize..].chars();
1102 let mut start_brace_location = last_subject_location;
1103 for char in chars {
1104 start_brace_location += 1;
1105 if char == '{' {
1106 break;
1107 }
1108 }
1109
1110 // Remove any blank spaces/lines between the start brace and end brace
1111 edits.delete(SrcSpan::new(start_brace_location, insert_at));
1112 edits.insert(insert_at, format!("\n{indent}"));
1113 }
1114
1115 CodeActionBuilder::new("Add missing patterns")
1116 .kind(CodeActionKind::QUICKFIX)
1117 .changes(uri.clone(), edits.edits)
1118 .preferred(true)
1119 .push_to(actions);
1120 }
1121}
1122
1123/// Builder for code action to add annotations to an assignment or function
1124///
1125pub struct AddAnnotations<'a> {
1126 module: &'a Module,
1127 params: &'a CodeActionParams,
1128 edits: TextEdits<'a>,
1129 printer: Printer<'a>,
1130}
1131
1132impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1133 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1134 self.visit_typed_expr(&assignment.value);
1135
1136 // We only offer this code action between `let` and `=`, because
1137 // otherwise it could lead to confusing behaviour if inside a block
1138 // which is part of a let binding.
1139 let pattern_location = assignment.pattern.location();
1140 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1141 let code_action_range = self.edits.src_span_to_lsp_range(location);
1142
1143 // Only offer the code action if the cursor is over the statement
1144 if !overlaps(code_action_range, self.params.range) {
1145 return;
1146 }
1147
1148 // We don't need to add an annotation if there already is one
1149 if assignment.annotation.is_some() {
1150 return;
1151 }
1152
1153 // Various expressions such as pipelines and `use` expressions generate assignments
1154 // internally. However, these cannot be annotated and so we don't offer a code action here.
1155 if matches!(assignment.kind, AssignmentKind::Generated) {
1156 return;
1157 }
1158
1159 self.edits.insert(
1160 pattern_location.end,
1161 format!(": {}", self.printer.print_type(&assignment.type_())),
1162 );
1163 }
1164
1165 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1166 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1167
1168 // Only offer the code action if the cursor is over the statement
1169 if !overlaps(code_action_range, self.params.range) {
1170 return;
1171 }
1172
1173 // We don't need to add an annotation if there already is one
1174 if constant.annotation.is_some() {
1175 return;
1176 }
1177
1178 self.edits.insert(
1179 constant.name_location.end,
1180 format!(": {}", self.printer.print_type(&constant.type_)),
1181 );
1182 }
1183
1184 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1185 ast::visit::visit_typed_function(self, fun);
1186
1187 let code_action_range = self.edits.src_span_to_lsp_range(fun.location);
1188
1189 // Only offer the code action if the cursor is over the statement
1190 if !overlaps(code_action_range, self.params.range) {
1191 return;
1192 }
1193
1194 // Annotate each argument separately
1195 for argument in fun.arguments.iter() {
1196 // Don't annotate the argument if it's already annotated
1197 if argument.annotation.is_some() {
1198 continue;
1199 }
1200
1201 self.edits.insert(
1202 argument.location.end,
1203 format!(": {}", self.printer.print_type(&argument.type_)),
1204 );
1205 }
1206
1207 // Annotate the return type if it isn't already annotated
1208 if fun.return_annotation.is_none() {
1209 self.edits.insert(
1210 fun.location.end,
1211 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1212 );
1213 }
1214 }
1215
1216 fn visit_typed_expr_fn(
1217 &mut self,
1218 location: &'ast SrcSpan,
1219 type_: &'ast Arc<Type>,
1220 kind: &'ast FunctionLiteralKind,
1221 args: &'ast [TypedArg],
1222 body: &'ast Vec1<TypedStatement>,
1223 return_annotation: &'ast Option<ast::TypeAst>,
1224 ) {
1225 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1226
1227 // If the function doesn't have a head, we can't annotate it
1228 let location = match kind {
1229 // Function captures don't need any type annotations
1230 FunctionLiteralKind::Capture { .. } => return,
1231 FunctionLiteralKind::Anonymous { head } => head,
1232 FunctionLiteralKind::Use { location } => location,
1233 };
1234
1235 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1236
1237 // Only offer the code action if the cursor is over the expression
1238 if !overlaps(code_action_range, self.params.range) {
1239 return;
1240 }
1241
1242 // Annotate each argument separately
1243 for argument in args.iter() {
1244 // Don't annotate the argument if it's already annotated
1245 if argument.annotation.is_some() {
1246 continue;
1247 }
1248
1249 self.edits.insert(
1250 argument.location.end,
1251 format!(": {}", self.printer.print_type(&argument.type_)),
1252 );
1253 }
1254
1255 // Annotate the return type if it isn't already annotated, and this is
1256 // an anonymous function.
1257 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1258 let return_type = &type_.return_type().expect("Type must be a function");
1259 let pretty_type = self.printer.print_type(return_type);
1260 self.edits
1261 .insert(location.end, format!(" -> {pretty_type}"));
1262 }
1263 }
1264}
1265
1266impl<'a> AddAnnotations<'a> {
1267 pub fn new(
1268 module: &'a Module,
1269 line_numbers: &'a LineNumbers,
1270 params: &'a CodeActionParams,
1271 ) -> Self {
1272 Self {
1273 module,
1274 params,
1275 edits: TextEdits::new(line_numbers),
1276 // We need to use the same printer for all the edits because otherwise
1277 // we could get duplicate type variable names.
1278 printer: Printer::new(&module.ast.names),
1279 }
1280 }
1281
1282 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1283 self.visit_typed_module(&self.module.ast);
1284
1285 let uri = &self.params.text_document.uri;
1286
1287 let title = match self.edits.edits.len() {
1288 // We don't offer a code action if there is no action to perform
1289 0 => return,
1290 1 => "Add type annotation",
1291 _ => "Add type annotations",
1292 };
1293
1294 CodeActionBuilder::new(title)
1295 .kind(CodeActionKind::REFACTOR)
1296 .changes(uri.clone(), self.edits.edits)
1297 .preferred(false)
1298 .push_to(actions);
1299 }
1300}
1301
1302pub struct QualifiedConstructor<'a> {
1303 import: &'a Import<EcoString>,
1304 module_aliased: bool,
1305 used_name: EcoString,
1306 constructor: EcoString,
1307 layer: ast::Layer,
1308}
1309
1310impl QualifiedConstructor<'_> {
1311 fn constructor_import(&self) -> String {
1312 if self.layer.is_value() {
1313 self.constructor.to_string()
1314 } else {
1315 format!("type {}", self.constructor)
1316 }
1317 }
1318}
1319
1320pub struct QualifiedToUnqualifiedImportFirstPass<'a> {
1321 module: &'a Module,
1322 params: &'a CodeActionParams,
1323 line_numbers: &'a LineNumbers,
1324 qualified_constructor: Option<QualifiedConstructor<'a>>,
1325}
1326
1327impl<'a> QualifiedToUnqualifiedImportFirstPass<'a> {
1328 fn new(
1329 module: &'a Module,
1330 params: &'a CodeActionParams,
1331 line_numbers: &'a LineNumbers,
1332 ) -> Self {
1333 Self {
1334 module,
1335 params,
1336 line_numbers,
1337 qualified_constructor: None,
1338 }
1339 }
1340
1341 fn get_module_import(
1342 &self,
1343 module_name: &EcoString,
1344 constructor: &EcoString,
1345 layer: ast::Layer,
1346 ) -> Option<&'a Import<EcoString>> {
1347 let mut matching_import = None;
1348
1349 for def in &self.module.ast.definitions {
1350 if let ast::Definition::Import(import) = def {
1351 let imported = if layer.is_value() {
1352 &import.unqualified_values
1353 } else {
1354 &import.unqualified_types
1355 };
1356
1357 if import.module != *module_name
1358 && imported.iter().any(|imp| imp.used_name() == constructor)
1359 {
1360 return None;
1361 }
1362
1363 if import.module == *module_name {
1364 matching_import = Some(import);
1365 }
1366 }
1367 }
1368
1369 matching_import
1370 }
1371}
1372
1373impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast> {
1374 fn visit_typed_expr_fn(
1375 &mut self,
1376 location: &'ast SrcSpan,
1377 type_: &'ast Arc<Type>,
1378 kind: &'ast FunctionLiteralKind,
1379 args: &'ast [TypedArg],
1380 body: &'ast Vec1<TypedStatement>,
1381 return_annotation: &'ast Option<ast::TypeAst>,
1382 ) {
1383 for arg in args {
1384 if let Some(annotation) = &arg.annotation {
1385 self.visit_type_ast(annotation);
1386 }
1387 }
1388 if let Some(return_) = return_annotation {
1389 self.visit_type_ast(return_);
1390 }
1391 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1392 }
1393
1394 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1395 for arg in &fun.arguments {
1396 if let Some(annotation) = &arg.annotation {
1397 self.visit_type_ast(annotation);
1398 }
1399 }
1400
1401 if let Some(return_annotation) = &fun.return_annotation {
1402 self.visit_type_ast(return_annotation);
1403 }
1404 ast::visit::visit_typed_function(self, fun);
1405 }
1406
1407 fn visit_type_ast_constructor(
1408 &mut self,
1409 location: &'ast SrcSpan,
1410 name_location: &'ast SrcSpan,
1411 module: &'ast Option<(EcoString, SrcSpan)>,
1412 name: &'ast EcoString,
1413 arguments: &'ast Vec<ast::TypeAst>,
1414 ) {
1415 let range = src_span_to_lsp_range(*location, self.line_numbers);
1416 if overlaps(self.params.range, range) {
1417 if let Some((module_alias, _)) = module {
1418 if let Some(import) = self.module.find_node(location.end).and_then(|node| {
1419 if let Located::Annotation { type_, .. } = node {
1420 if let Some((module, _)) = type_.named_type_name() {
1421 return self.get_module_import(&module, name, ast::Layer::Type);
1422 }
1423 }
1424 None
1425 }) {
1426 self.qualified_constructor = Some(QualifiedConstructor {
1427 import,
1428 module_aliased: import.as_name.is_some(),
1429 used_name: module_alias.clone(),
1430 constructor: name.clone(),
1431 layer: ast::Layer::Type,
1432 });
1433 }
1434 }
1435 }
1436 ast::visit::visit_type_ast_constructor(
1437 self,
1438 location,
1439 name_location,
1440 module,
1441 name,
1442 arguments,
1443 );
1444 }
1445
1446 fn visit_typed_expr_module_select(
1447 &mut self,
1448 location: &'ast SrcSpan,
1449 field_start: &'ast u32,
1450 type_: &'ast Arc<Type>,
1451 label: &'ast EcoString,
1452 module_name: &'ast EcoString,
1453 module_alias: &'ast EcoString,
1454 constructor: &'ast ModuleValueConstructor,
1455 ) {
1456 // When hovering over a Record Value Constructor, we want to expand the source span to
1457 // include the module name:
1458 // option.Some
1459 // ↑
1460 // This allows us to offer a code action when hovering over the module name.
1461 let range = src_span_to_lsp_range(*location, self.line_numbers);
1462 if overlaps(self.params.range, range) {
1463 if let ModuleValueConstructor::Record {
1464 name: constructor_name,
1465 ..
1466 } = constructor
1467 {
1468 if let Some(import) =
1469 self.get_module_import(module_name, constructor_name, ast::Layer::Value)
1470 {
1471 self.qualified_constructor = Some(QualifiedConstructor {
1472 import,
1473 module_aliased: import.as_name.is_some(),
1474 used_name: module_alias.clone(),
1475 constructor: constructor_name.clone(),
1476 layer: ast::Layer::Value,
1477 });
1478 }
1479 }
1480 }
1481 ast::visit::visit_typed_expr_module_select(
1482 self,
1483 location,
1484 field_start,
1485 type_,
1486 label,
1487 module_name,
1488 module_alias,
1489 constructor,
1490 )
1491 }
1492
1493 fn visit_typed_pattern_constructor(
1494 &mut self,
1495 location: &'ast SrcSpan,
1496 name_location: &'ast SrcSpan,
1497 name: &'ast EcoString,
1498 arguments: &'ast Vec<CallArg<TypedPattern>>,
1499 module: &'ast Option<(EcoString, SrcSpan)>,
1500 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1501 spread: &'ast Option<SrcSpan>,
1502 type_: &'ast Arc<Type>,
1503 ) {
1504 let range = src_span_to_lsp_range(*location, self.line_numbers);
1505 if overlaps(self.params.range, range) {
1506 if let Some((module_alias, _)) = module {
1507 if let analyse::Inferred::Known(constructor) = constructor {
1508 if let Some(import) =
1509 self.get_module_import(&constructor.module, name, ast::Layer::Value)
1510 {
1511 self.qualified_constructor = Some(QualifiedConstructor {
1512 import,
1513 module_aliased: import.as_name.is_some(),
1514 used_name: module_alias.clone(),
1515 constructor: name.clone(),
1516 layer: ast::Layer::Value,
1517 });
1518 }
1519 }
1520 }
1521 }
1522 ast::visit::visit_typed_pattern_constructor(
1523 self,
1524 location,
1525 name_location,
1526 name,
1527 arguments,
1528 module,
1529 constructor,
1530 spread,
1531 type_,
1532 );
1533 }
1534}
1535
1536pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
1537 module: &'a Module,
1538 params: &'a CodeActionParams,
1539 edits: TextEdits<'a>,
1540 qualified_constructor: QualifiedConstructor<'a>,
1541}
1542
1543impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
1544 pub fn new(
1545 module: &'a Module,
1546 params: &'a CodeActionParams,
1547 line_numbers: &'a LineNumbers,
1548 qualified_constructor: QualifiedConstructor<'a>,
1549 ) -> Self {
1550 Self {
1551 module,
1552 params,
1553 edits: TextEdits::new(line_numbers),
1554 qualified_constructor,
1555 }
1556 }
1557
1558 pub fn code_actions(mut self) -> Vec<CodeAction> {
1559 self.visit_typed_module(&self.module.ast);
1560 if self.edits.edits.is_empty() {
1561 return vec![];
1562 }
1563 self.edit_import();
1564 let mut action = Vec::with_capacity(1);
1565 CodeActionBuilder::new(&format!(
1566 "Unqualify {}.{}",
1567 self.qualified_constructor.used_name, self.qualified_constructor.constructor
1568 ))
1569 .kind(CodeActionKind::REFACTOR)
1570 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1571 .preferred(false)
1572 .push_to(&mut action);
1573 action
1574 }
1575
1576 fn remove_module_qualifier(&mut self, location: SrcSpan) {
1577 self.edits.delete(SrcSpan {
1578 start: location.start,
1579 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
1580 })
1581 }
1582
1583 fn edit_import(&mut self) {
1584 let QualifiedConstructor {
1585 constructor,
1586 layer,
1587 import,
1588 ..
1589 } = &self.qualified_constructor;
1590 let is_imported = if layer.is_value() {
1591 import
1592 .unqualified_values
1593 .iter()
1594 .any(|value| value.used_name() == constructor)
1595 } else {
1596 import
1597 .unqualified_types
1598 .iter()
1599 .any(|type_| type_.used_name() == constructor)
1600 };
1601 if is_imported {
1602 return;
1603 }
1604 let (insert_pos, new_text) = self.determine_insert_position_and_text();
1605 let span = SrcSpan::new(insert_pos, insert_pos);
1606 self.edits.replace(span, new_text);
1607 }
1608
1609 fn find_last_char_before_closing_brace(&self) -> Option<(usize, char)> {
1610 let QualifiedConstructor {
1611 import: Import { location, .. },
1612 ..
1613 } = self.qualified_constructor;
1614 let import_code = self.get_import_code();
1615 let closing_brace_pos = import_code.rfind('}')?;
1616
1617 let bytes = import_code.as_bytes();
1618 let mut pos = closing_brace_pos;
1619 while pos > 0 {
1620 pos -= 1;
1621 let c = (*bytes.get(pos)?) as char;
1622 if c.is_whitespace() {
1623 continue;
1624 }
1625 if c == '{' {
1626 break;
1627 }
1628 return Some((location.start as usize + pos, c));
1629 }
1630 None
1631 }
1632
1633 fn get_import_code(&self) -> &str {
1634 let QualifiedConstructor {
1635 import: Import { location, .. },
1636 ..
1637 } = self.qualified_constructor;
1638 self.module
1639 .code
1640 .get(location.start as usize..location.end as usize)
1641 .expect("import not found")
1642 }
1643
1644 fn determine_insert_position_and_text(&self) -> (u32, String) {
1645 let QualifiedConstructor { module_aliased, .. } = &self.qualified_constructor;
1646
1647 let name = self.qualified_constructor.constructor_import();
1648 let import_code = self.get_import_code();
1649 let has_brace = import_code.contains('}');
1650
1651 if has_brace {
1652 self.insert_into_braced_import(name)
1653 } else {
1654 self.insert_into_unbraced_import(name, *module_aliased)
1655 }
1656 }
1657
1658 // Handle inserting into an unbraced import
1659 fn insert_into_unbraced_import(&self, name: String, module_aliased: bool) -> (u32, String) {
1660 let QualifiedConstructor {
1661 import: Import { location, .. },
1662 ..
1663 } = self.qualified_constructor;
1664 if !module_aliased {
1665 // Case: import module
1666 (location.end, format!(".{{{}}}", name))
1667 } else {
1668 // Case: import module as alias
1669 let import_code = &self.get_import_code();
1670 let as_pos = import_code
1671 .find(" as ")
1672 .expect("Expected ' as ' in import statement");
1673 let before_as_pos = import_code
1674 .get(..as_pos)
1675 .and_then(|s| s.rfind(|c: char| !c.is_whitespace()))
1676 .map(|pos| location.start as usize + pos + 1)
1677 .expect("Expected non-whitespace character before ' as '");
1678 (before_as_pos as u32, format!(".{{{}}}", name))
1679 }
1680 }
1681
1682 // Handle inserting into a braced import
1683 fn insert_into_braced_import(&self, name: String) -> (u32, String) {
1684 let QualifiedConstructor {
1685 import: Import { location, .. },
1686 ..
1687 } = self.qualified_constructor;
1688 if let Some((pos, c)) = self.find_last_char_before_closing_brace() {
1689 // Case: import module.{Existing, } (as alias)
1690 if c == ',' {
1691 (pos as u32 + 1, format!(" {}", name))
1692 } else {
1693 // Case: import module.{Existing} (as alias)
1694 (pos as u32 + 1, format!(", {}", name))
1695 }
1696 } else {
1697 // Case: import module.{} (as alias)
1698 let import_code = self.get_import_code();
1699 let left_brace_pos = import_code
1700 .find('{')
1701 .map(|pos| location.start as usize + pos)
1702 .expect("Expected '{' in import statement");
1703 (left_brace_pos as u32 + 1, name)
1704 }
1705 }
1706}
1707
1708impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
1709 fn visit_typed_expr_fn(
1710 &mut self,
1711 location: &'ast SrcSpan,
1712 type_: &'ast Arc<Type>,
1713 kind: &'ast FunctionLiteralKind,
1714 args: &'ast [TypedArg],
1715 body: &'ast Vec1<TypedStatement>,
1716 return_annotation: &'ast Option<ast::TypeAst>,
1717 ) {
1718 for arg in args {
1719 if let Some(annotation) = &arg.annotation {
1720 self.visit_type_ast(annotation);
1721 }
1722 }
1723 if let Some(return_) = return_annotation {
1724 self.visit_type_ast(return_);
1725 }
1726 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1727 }
1728
1729 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1730 for arg in &fun.arguments {
1731 if let Some(annotation) = &arg.annotation {
1732 self.visit_type_ast(annotation);
1733 }
1734 }
1735
1736 if let Some(return_annotation) = &fun.return_annotation {
1737 self.visit_type_ast(return_annotation);
1738 }
1739 ast::visit::visit_typed_function(self, fun);
1740 }
1741
1742 fn visit_type_ast_constructor(
1743 &mut self,
1744 location: &'ast SrcSpan,
1745 name_location: &'ast SrcSpan,
1746 module: &'ast Option<(EcoString, SrcSpan)>,
1747 name: &'ast EcoString,
1748 arguments: &'ast Vec<ast::TypeAst>,
1749 ) {
1750 if let Some((module_name, _)) = module {
1751 let QualifiedConstructor {
1752 used_name,
1753 constructor,
1754 layer,
1755 ..
1756 } = &self.qualified_constructor;
1757
1758 if !layer.is_value() && used_name == module_name && name == constructor {
1759 self.remove_module_qualifier(*location);
1760 }
1761 }
1762 ast::visit::visit_type_ast_constructor(
1763 self,
1764 location,
1765 name_location,
1766 module,
1767 name,
1768 arguments,
1769 );
1770 }
1771
1772 fn visit_typed_expr_module_select(
1773 &mut self,
1774 location: &'ast SrcSpan,
1775 field_start: &'ast u32,
1776 type_: &'ast Arc<Type>,
1777 label: &'ast EcoString,
1778 module_name: &'ast EcoString,
1779 module_alias: &'ast EcoString,
1780 constructor: &'ast ModuleValueConstructor,
1781 ) {
1782 if let ModuleValueConstructor::Record { name, .. } = constructor {
1783 let QualifiedConstructor {
1784 used_name,
1785 constructor,
1786 layer,
1787 ..
1788 } = &self.qualified_constructor;
1789
1790 if layer.is_value() && used_name == module_alias && name == constructor {
1791 self.remove_module_qualifier(*location);
1792 }
1793 }
1794 ast::visit::visit_typed_expr_module_select(
1795 self,
1796 location,
1797 field_start,
1798 type_,
1799 label,
1800 module_name,
1801 module_alias,
1802 constructor,
1803 )
1804 }
1805
1806 fn visit_typed_pattern_constructor(
1807 &mut self,
1808 location: &'ast SrcSpan,
1809 name_location: &'ast SrcSpan,
1810 name: &'ast EcoString,
1811 arguments: &'ast Vec<CallArg<TypedPattern>>,
1812 module: &'ast Option<(EcoString, SrcSpan)>,
1813 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1814 spread: &'ast Option<SrcSpan>,
1815 type_: &'ast Arc<Type>,
1816 ) {
1817 if let Some((module_alias, _)) = module {
1818 if let analyse::Inferred::Known(_) = constructor {
1819 let QualifiedConstructor {
1820 used_name,
1821 constructor,
1822 layer,
1823 ..
1824 } = &self.qualified_constructor;
1825
1826 if layer.is_value() && used_name == module_alias && name == constructor {
1827 self.remove_module_qualifier(*location);
1828 }
1829 }
1830 }
1831 ast::visit::visit_typed_pattern_constructor(
1832 self,
1833 location,
1834 name_location,
1835 name,
1836 arguments,
1837 module,
1838 constructor,
1839 spread,
1840 type_,
1841 );
1842 }
1843}
1844
1845pub fn code_action_convert_qualified_constructor_to_unqualified(
1846 module: &Module,
1847 line_numbers: &LineNumbers,
1848 params: &CodeActionParams,
1849 actions: &mut Vec<CodeAction>,
1850) {
1851 let mut first_pass = QualifiedToUnqualifiedImportFirstPass::new(module, params, line_numbers);
1852 first_pass.visit_typed_module(&module.ast);
1853 let Some(qualified_constructor) = first_pass.qualified_constructor else {
1854 return;
1855 };
1856 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
1857 module,
1858 params,
1859 line_numbers,
1860 qualified_constructor,
1861 );
1862 let new_actions = second_pass.code_actions();
1863 actions.extend(new_actions);
1864}
1865
1866struct UnqualifiedConstructor<'a> {
1867 module_name: EcoString,
1868 constructor: &'a ast::UnqualifiedImport,
1869 layer: ast::Layer,
1870}
1871
1872struct UnqualifiedToQualifiedImportFirstPass<'a> {
1873 module: &'a Module,
1874 params: &'a CodeActionParams,
1875 line_numbers: &'a LineNumbers,
1876 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
1877}
1878
1879impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
1880 fn new(
1881 module: &'a Module,
1882 params: &'a CodeActionParams,
1883 line_numbers: &'a LineNumbers,
1884 ) -> Self {
1885 Self {
1886 module,
1887 params,
1888 line_numbers,
1889 unqualified_constructor: None,
1890 }
1891 }
1892
1893 fn get_module_import_from_value_constructor(
1894 &mut self,
1895 module_name: &EcoString,
1896 constructor_name: &EcoString,
1897 ) {
1898 self.unqualified_constructor =
1899 self.module
1900 .ast
1901 .definitions
1902 .iter()
1903 .find_map(|def| match def {
1904 ast::Definition::Import(import) if import.module == *module_name => import
1905 .unqualified_values
1906 .iter()
1907 .find(|value| value.used_name() == constructor_name)
1908 .and_then(|value| {
1909 Some(UnqualifiedConstructor {
1910 constructor: value,
1911 module_name: import.used_name()?,
1912 layer: ast::Layer::Value,
1913 })
1914 }),
1915 _ => None,
1916 })
1917 }
1918
1919 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
1920 self.unqualified_constructor =
1921 self.module
1922 .ast
1923 .definitions
1924 .iter()
1925 .find_map(|def| match def {
1926 ast::Definition::Import(import) => {
1927 if let Some(ty) = import
1928 .unqualified_types
1929 .iter()
1930 .find(|ty| ty.used_name() == constructor_name)
1931 {
1932 return Some(UnqualifiedConstructor {
1933 constructor: ty,
1934 module_name: import.used_name()?,
1935 layer: ast::Layer::Type,
1936 });
1937 }
1938 None
1939 }
1940 _ => None,
1941 })
1942 }
1943}
1944
1945impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
1946 fn visit_typed_expr_fn(
1947 &mut self,
1948 location: &'ast SrcSpan,
1949 type_: &'ast Arc<Type>,
1950 kind: &'ast FunctionLiteralKind,
1951 args: &'ast [TypedArg],
1952 body: &'ast Vec1<TypedStatement>,
1953 return_annotation: &'ast Option<ast::TypeAst>,
1954 ) {
1955 for arg in args {
1956 if let Some(annotation) = &arg.annotation {
1957 self.visit_type_ast(annotation);
1958 }
1959 }
1960 if let Some(return_) = return_annotation {
1961 self.visit_type_ast(return_);
1962 }
1963 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1964 }
1965
1966 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1967 for arg in &fun.arguments {
1968 if let Some(annotation) = &arg.annotation {
1969 self.visit_type_ast(annotation);
1970 }
1971 }
1972
1973 if let Some(return_annotation) = &fun.return_annotation {
1974 self.visit_type_ast(return_annotation);
1975 }
1976 ast::visit::visit_typed_function(self, fun);
1977 }
1978 fn visit_type_ast_constructor(
1979 &mut self,
1980 location: &'ast SrcSpan,
1981 name_location: &'ast SrcSpan,
1982 module: &'ast Option<(EcoString, SrcSpan)>,
1983 name: &'ast EcoString,
1984 arguments: &'ast Vec<ast::TypeAst>,
1985 ) {
1986 if module.is_none()
1987 && overlaps(
1988 self.params.range,
1989 src_span_to_lsp_range(*location, self.line_numbers),
1990 )
1991 {
1992 self.get_module_import_from_type_constructor(name);
1993 }
1994
1995 ast::visit::visit_type_ast_constructor(
1996 self,
1997 location,
1998 name_location,
1999 module,
2000 name,
2001 arguments,
2002 );
2003 }
2004
2005 fn visit_typed_expr_var(
2006 &mut self,
2007 location: &'ast SrcSpan,
2008 constructor: &'ast ValueConstructor,
2009 name: &'ast EcoString,
2010 ) {
2011 let range = src_span_to_lsp_range(*location, self.line_numbers);
2012 if overlaps(self.params.range, range) {
2013 if let Some(module_name) = match &constructor.variant {
2014 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2015 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2016 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2017
2018 type_::ValueConstructorVariant::LocalVariable { .. }
2019 | type_::ValueConstructorVariant::LocalConstant { .. } => None,
2020 } {
2021 self.get_module_import_from_value_constructor(module_name, name);
2022 }
2023 }
2024 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2025 }
2026
2027 fn visit_typed_pattern_constructor(
2028 &mut self,
2029 location: &'ast SrcSpan,
2030 name_location: &'ast SrcSpan,
2031 name: &'ast EcoString,
2032 arguments: &'ast Vec<CallArg<TypedPattern>>,
2033 module: &'ast Option<(EcoString, SrcSpan)>,
2034 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
2035 spread: &'ast Option<SrcSpan>,
2036 type_: &'ast Arc<Type>,
2037 ) {
2038 if module.is_none()
2039 && overlaps(
2040 self.params.range,
2041 src_span_to_lsp_range(*location, self.line_numbers),
2042 )
2043 {
2044 if let analyse::Inferred::Known(constructor) = constructor {
2045 self.get_module_import_from_value_constructor(&constructor.module, name);
2046 }
2047 }
2048
2049 ast::visit::visit_typed_pattern_constructor(
2050 self,
2051 location,
2052 name_location,
2053 name,
2054 arguments,
2055 module,
2056 constructor,
2057 spread,
2058 type_,
2059 );
2060 }
2061}
2062
2063struct UnqualifiedToQualifiedImportSecondPass<'a> {
2064 module: &'a Module,
2065 params: &'a CodeActionParams,
2066 edits: TextEdits<'a>,
2067 unqualified_constructor: UnqualifiedConstructor<'a>,
2068}
2069
2070impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2071 pub fn new(
2072 module: &'a Module,
2073 params: &'a CodeActionParams,
2074 line_numbers: &'a LineNumbers,
2075 unqualified_constructor: UnqualifiedConstructor<'a>,
2076 ) -> Self {
2077 Self {
2078 module,
2079 params,
2080 edits: TextEdits::new(line_numbers),
2081 unqualified_constructor,
2082 }
2083 }
2084
2085 fn add_module_qualifier(&mut self, location: SrcSpan) {
2086 let src_span = SrcSpan::new(
2087 location.start,
2088 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
2089 );
2090
2091 self.edits.replace(
2092 src_span,
2093 format!(
2094 "{}.{}",
2095 self.unqualified_constructor.module_name,
2096 self.unqualified_constructor.constructor.name
2097 ),
2098 );
2099 }
2100
2101 pub fn code_actions(mut self) -> Vec<CodeAction> {
2102 self.visit_typed_module(&self.module.ast);
2103 if self.edits.edits.is_empty() {
2104 return vec![];
2105 }
2106 self.edit_import();
2107 let mut action = Vec::with_capacity(1);
2108 let UnqualifiedConstructor {
2109 module_name,
2110 constructor,
2111 ..
2112 } = self.unqualified_constructor;
2113 CodeActionBuilder::new(&format!(
2114 "Qualify {} as {}.{}",
2115 constructor.used_name(),
2116 module_name,
2117 constructor.name,
2118 ))
2119 .kind(CodeActionKind::REFACTOR)
2120 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2121 .preferred(false)
2122 .push_to(&mut action);
2123 action
2124 }
2125
2126 fn edit_import(&mut self) {
2127 let UnqualifiedConstructor {
2128 constructor:
2129 ast::UnqualifiedImport {
2130 location: constructor_import_span,
2131 ..
2132 },
2133 ..
2134 } = self.unqualified_constructor;
2135
2136 let mut last_char_pos = constructor_import_span.end as usize;
2137 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2138 last_char_pos += 1;
2139 }
2140 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2141 last_char_pos += 1;
2142 }
2143 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2144 last_char_pos += 1;
2145 }
2146
2147 self.edits.delete(SrcSpan::new(
2148 constructor_import_span.start,
2149 last_char_pos as u32,
2150 ));
2151 }
2152}
2153
2154impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2155 fn visit_typed_expr_fn(
2156 &mut self,
2157 location: &'ast SrcSpan,
2158 type_: &'ast Arc<Type>,
2159 kind: &'ast FunctionLiteralKind,
2160 args: &'ast [TypedArg],
2161 body: &'ast Vec1<TypedStatement>,
2162 return_annotation: &'ast Option<ast::TypeAst>,
2163 ) {
2164 for arg in args {
2165 if let Some(annotation) = &arg.annotation {
2166 self.visit_type_ast(annotation);
2167 }
2168 }
2169 if let Some(return_) = return_annotation {
2170 self.visit_type_ast(return_);
2171 }
2172 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
2173 }
2174
2175 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
2176 for arg in &fun.arguments {
2177 if let Some(annotation) = &arg.annotation {
2178 self.visit_type_ast(annotation);
2179 }
2180 }
2181
2182 if let Some(return_annotation) = &fun.return_annotation {
2183 self.visit_type_ast(return_annotation);
2184 }
2185 ast::visit::visit_typed_function(self, fun);
2186 }
2187
2188 fn visit_type_ast_constructor(
2189 &mut self,
2190 location: &'ast SrcSpan,
2191 name_location: &'ast SrcSpan,
2192 module: &'ast Option<(EcoString, SrcSpan)>,
2193 name: &'ast EcoString,
2194 arguments: &'ast Vec<ast::TypeAst>,
2195 ) {
2196 if module.is_none() {
2197 let UnqualifiedConstructor {
2198 constructor, layer, ..
2199 } = &self.unqualified_constructor;
2200 if !layer.is_value() && constructor.used_name() == name {
2201 self.add_module_qualifier(*location);
2202 }
2203 }
2204 ast::visit::visit_type_ast_constructor(
2205 self,
2206 location,
2207 name_location,
2208 module,
2209 name,
2210 arguments,
2211 );
2212 }
2213
2214 fn visit_typed_expr_var(
2215 &mut self,
2216 location: &'ast SrcSpan,
2217 constructor: &'ast ValueConstructor,
2218 name: &'ast EcoString,
2219 ) {
2220 let UnqualifiedConstructor {
2221 constructor: wanted_constructor,
2222 layer,
2223 ..
2224 } = &self.unqualified_constructor;
2225
2226 if layer.is_value()
2227 && wanted_constructor.used_name() == name
2228 && !constructor.is_local_variable()
2229 {
2230 self.add_module_qualifier(*location);
2231 }
2232 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2233 }
2234
2235 fn visit_typed_pattern_constructor(
2236 &mut self,
2237 location: &'ast SrcSpan,
2238 name_location: &'ast SrcSpan,
2239 name: &'ast EcoString,
2240 arguments: &'ast Vec<CallArg<TypedPattern>>,
2241 module: &'ast Option<(EcoString, SrcSpan)>,
2242 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
2243 spread: &'ast Option<SrcSpan>,
2244 type_: &'ast Arc<Type>,
2245 ) {
2246 if module.is_none() {
2247 let UnqualifiedConstructor {
2248 constructor: wanted_constructor,
2249 layer,
2250 ..
2251 } = &self.unqualified_constructor;
2252 if layer.is_value() && wanted_constructor.used_name() == name {
2253 self.add_module_qualifier(*location);
2254 }
2255 }
2256 ast::visit::visit_typed_pattern_constructor(
2257 self,
2258 location,
2259 name_location,
2260 name,
2261 arguments,
2262 module,
2263 constructor,
2264 spread,
2265 type_,
2266 );
2267 }
2268}
2269
2270pub fn code_action_convert_unqualified_constructor_to_qualified(
2271 module: &Module,
2272 line_numbers: &LineNumbers,
2273 params: &CodeActionParams,
2274 actions: &mut Vec<CodeAction>,
2275) {
2276 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2277 first_pass.visit_typed_module(&module.ast);
2278 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2279 return;
2280 };
2281 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2282 module,
2283 params,
2284 line_numbers,
2285 unqualified_constructor,
2286 );
2287 let new_actions = second_pass.code_actions();
2288 actions.extend(new_actions);
2289}
2290
2291/// Builder for code action to apply the convert from use action, turning a use
2292/// expression into a regular function call.
2293///
2294pub struct ConvertFromUse<'a> {
2295 module: &'a Module,
2296 params: &'a CodeActionParams,
2297 edits: TextEdits<'a>,
2298 selected_use: Option<&'a TypedUse>,
2299}
2300
2301impl<'a> ConvertFromUse<'a> {
2302 pub fn new(
2303 module: &'a Module,
2304 line_numbers: &'a LineNumbers,
2305 params: &'a CodeActionParams,
2306 ) -> Self {
2307 Self {
2308 module,
2309 params,
2310 edits: TextEdits::new(line_numbers),
2311 selected_use: None,
2312 }
2313 }
2314
2315 pub fn code_actions(mut self) -> Vec<CodeAction> {
2316 self.visit_typed_module(&self.module.ast);
2317
2318 let Some(use_) = self.selected_use else {
2319 return vec![];
2320 };
2321
2322 let TypedExpr::Call { args, fun, .. } = use_.call.as_ref() else {
2323 return vec![];
2324 };
2325
2326 // If the use callback we're desugaring is using labels, that means we
2327 // have to add the last argument's label when writing the callback;
2328 // otherwise, it would result in invalid code.
2329 //
2330 // use acc, item <- list.fold(over: list, from: 1)
2331 // todo
2332 //
2333 // Needs to be rewritten as:
2334 //
2335 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2336 // ^^^^^ We cannot forget to add this label back!
2337 //
2338 let callback_label = if args.iter().any(|arg| arg.label.is_some()) {
2339 fun.field_map()
2340 .and_then(|field_map| field_map.missing_labels(args).last().cloned())
2341 .map(|label| eco_format!("{label}: "))
2342 .unwrap_or(EcoString::from(""))
2343 } else {
2344 EcoString::from("")
2345 };
2346
2347 // The use callback is not necessarily the last argument. If you have
2348 // the following function: `wibble(a a, b b) { todo }`
2349 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2350 // is going to be the use callback, not the last one!
2351 let use_callback = args.iter().find(|arg| arg.is_use_implicit_callback());
2352 let Some(CallArg {
2353 implicit: Some(ImplicitCallArgOrigin::Use),
2354 value: TypedExpr::Fn { body, type_, .. },
2355 ..
2356 }) = use_callback
2357 else {
2358 return vec![];
2359 };
2360
2361 // If there's arguments on the left hand side of the function we extract
2362 // those so we can paste them back as the anonymous function arguments.
2363 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2364 let assignments_range =
2365 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2366 self.module
2367 .code
2368 .get(assignments_range)
2369 .expect("use assignments")
2370 } else {
2371 ""
2372 };
2373
2374 // We first delete everything on the left hand side of use and the use
2375 // arrow.
2376 self.edits.delete(SrcSpan {
2377 start: use_.location.start,
2378 end: use_.right_hand_side_location.start,
2379 });
2380
2381 let use_line_end = use_.right_hand_side_location.end;
2382 let use_rhs_function_has_some_explicit_args = args
2383 .iter()
2384 .filter(|arg| !arg.is_use_implicit_callback())
2385 .peekable()
2386 .peek()
2387 .is_some();
2388
2389 let use_rhs_function_ends_with_closed_parentheses = self
2390 .module
2391 .code
2392 .get(use_line_end as usize - 1..use_line_end as usize)
2393 == Some(")");
2394
2395 let last_explicit_arg = args.iter().filter(|arg| !arg.is_implicit()).next_back();
2396 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2397
2398 // This is the piece of code between the end of the last argument and
2399 // the end of the use_expression:
2400 //
2401 // use <- wibble(a, b, )
2402 // ^^^^^ This piece right here, from `,` included
2403 // up to `)` excluded.
2404 //
2405 let text_after_last_argument = self
2406 .module
2407 .code
2408 .get(last_arg_end as usize..use_line_end as usize - 1);
2409 let use_rhs_has_comma_after_last_argument =
2410 text_after_last_argument.is_some_and(|code| code.contains(','));
2411 let needs_space_before_callback =
2412 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2413
2414 if use_rhs_function_ends_with_closed_parentheses {
2415 // If the function on the right hand side of use ends with a closed
2416 // parentheses then we have to remove it and add it later at the end
2417 // of the anonymous function we're inserting.
2418 //
2419 // use <- wibble()
2420 // ^ To add the fn() we need to first remove this
2421 //
2422 // So here we write over the last closed parentheses to remove it.
2423 let callback_start = format!("{callback_label}fn({assignments}) {{");
2424 self.edits.replace(
2425 SrcSpan {
2426 start: use_line_end - 1,
2427 end: use_line_end,
2428 },
2429 // If the function on the rhs of use has other orguments besides
2430 // the implicit fn expression then we need to put a comma after
2431 // the last argument.
2432 if use_rhs_function_has_some_explicit_args && !use_rhs_has_comma_after_last_argument
2433 {
2434 format!(", {callback_start}")
2435 } else if needs_space_before_callback {
2436 format!(" {callback_start}")
2437 } else {
2438 callback_start.to_string()
2439 },
2440 )
2441 } else {
2442 // On the other hand, if the function on the right hand side doesn't
2443 // end with a closed parenthese then we have to manually add it.
2444 //
2445 // use <- wibble
2446 // ^ No parentheses
2447 //
2448 self.edits
2449 .insert(use_line_end, format!("(fn({}) {{", assignments))
2450 };
2451
2452 // Then we have to increase indentation for all the lines of the use
2453 // body.
2454 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
2455 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
2456
2457 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
2458 self.edits.edits.push(TextEdit {
2459 range: Range {
2460 start: Position { line, character: 0 },
2461 end: Position { line, character: 0 },
2462 },
2463 new_text: " ".to_string(),
2464 })
2465 }
2466
2467 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
2468 self.edits.insert(
2469 use_.call.location().end,
2470 format!("\n{final_line_indentation}}})"),
2471 );
2472
2473 let mut action = Vec::with_capacity(1);
2474 CodeActionBuilder::new("Convert from `use`")
2475 .kind(CodeActionKind::REFACTOR_REWRITE)
2476 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2477 .preferred(false)
2478 .push_to(&mut action);
2479 action
2480 }
2481}
2482
2483impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
2484 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
2485 let use_range = self.edits.src_span_to_lsp_range(use_.location);
2486
2487 // If the use expression is using patterns that are not just variable
2488 // assignments then we can't automatically rewrite it as it would result
2489 // in a syntax error as we can't pattern match in an anonymous function
2490 // head.
2491 // At the same time we can't safely add bindings inside the anonymous
2492 // function body by picking placeholder names as we'd risk shadowing
2493 // variables coming from the outer scope.
2494 // So we just skip those use expressions we can't safely rewrite!
2495 if within(self.params.range, use_range)
2496 && use_
2497 .assignments
2498 .iter()
2499 .all(|assignment| assignment.pattern.is_variable())
2500 {
2501 self.selected_use = Some(use_);
2502 }
2503
2504 // We still want to visit the use expression so that we always end up
2505 // picking the innermost, most relevant use under the cursor.
2506 self.visit_typed_expr(&use_.call);
2507 }
2508}
2509
2510/// Builder for code action to apply the convert to use action.
2511///
2512pub struct ConvertToUse<'a> {
2513 module: &'a Module,
2514 params: &'a CodeActionParams,
2515 edits: TextEdits<'a>,
2516 selected_call: Option<CallLocations>,
2517}
2518
2519/// All the locations we'll need to transform a function call into a use
2520/// expression.
2521///
2522struct CallLocations {
2523 call_span: SrcSpan,
2524 called_function_span: SrcSpan,
2525 callback_args_span: Option<SrcSpan>,
2526 arg_before_callback_span: Option<SrcSpan>,
2527 callback_body_span: SrcSpan,
2528}
2529
2530impl<'a> ConvertToUse<'a> {
2531 pub fn new(
2532 module: &'a Module,
2533 line_numbers: &'a LineNumbers,
2534 params: &'a CodeActionParams,
2535 ) -> Self {
2536 Self {
2537 module,
2538 params,
2539 edits: TextEdits::new(line_numbers),
2540 selected_call: None,
2541 }
2542 }
2543
2544 pub fn code_actions(mut self) -> Vec<CodeAction> {
2545 self.visit_typed_module(&self.module.ast);
2546
2547 let Some(CallLocations {
2548 call_span,
2549 called_function_span,
2550 callback_args_span,
2551 arg_before_callback_span,
2552 callback_body_span,
2553 }) = self.selected_call
2554 else {
2555 return vec![];
2556 };
2557
2558 // This is the nesting level of the `use` keyword we've inserted, we
2559 // want to move the entire body of the anonymous function to this level.
2560 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
2561 let indentation = " ".repeat(use_nesting_level as usize);
2562
2563 // First we move the callback arguments to the left hand side of the
2564 // call and add the `use` keyword.
2565 let left_hand_side_text = if let Some(args_location) = callback_args_span {
2566 let args_start = args_location.start as usize;
2567 let args_end = args_location.end as usize;
2568 let args_text = self.module.code.get(args_start..args_end).expect("fn args");
2569 format!("use {args_text} <- ")
2570 } else {
2571 "use <- ".into()
2572 };
2573
2574 self.edits.insert(call_span.start, left_hand_side_text);
2575
2576 match arg_before_callback_span {
2577 // If the function call has no other arguments besides the callback then
2578 // we just have to remove the `fn(...) {` part.
2579 //
2580 // wibble(fn(...) { ... })
2581 // ^^^^^^^^^^ This goes from the end of the called function
2582 // To the start of the first thing in the anonymous
2583 // function's body.
2584 //
2585 None => self.edits.replace(
2586 SrcSpan::new(called_function_span.end, callback_body_span.start),
2587 format!("\n{indentation}"),
2588 ),
2589 // If it has other arguments we'll have to remove those and add a closed
2590 // parentheses too:
2591 //
2592 // wibble(1, 2, fn(...) { ... })
2593 // ^^^^^^^^^^^ We have to replace this with a `)`, it
2594 // goes from the end of the second-to-last
2595 // argument to the start of the first thing
2596 // in the anonymous function's body.
2597 //
2598 Some(arg_before_callback) => self.edits.replace(
2599 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
2600 format!(")\n{indentation}"),
2601 ),
2602 };
2603
2604 // Then we have to remove two spaces of indentation from each line of
2605 // the callback function's body.
2606 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
2607 for line in body_range.start.line + 1..=body_range.end.line {
2608 self.edits.delete_range(Range::new(
2609 Position { line, character: 0 },
2610 Position { line, character: 2 },
2611 ))
2612 }
2613
2614 // Then we have to remove the anonymous fn closing `}` and the call's
2615 // closing `)`.
2616 self.edits
2617 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
2618
2619 let mut action = Vec::with_capacity(1);
2620 CodeActionBuilder::new("Convert to `use`")
2621 .kind(CodeActionKind::REFACTOR_REWRITE)
2622 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2623 .preferred(false)
2624 .push_to(&mut action);
2625 action
2626 }
2627}
2628
2629impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
2630 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
2631 // The cursor has to be inside the last statement of the function to
2632 // offer the code action.
2633 let last_statement_range = self.edits.src_span_to_lsp_range(fun.body.last().location());
2634 if within(self.params.range, last_statement_range) {
2635 if let Some(call_data) = turn_statement_into_use(fun.body.last()) {
2636 self.selected_call = Some(call_data);
2637 }
2638 }
2639
2640 ast::visit::visit_typed_function(self, fun)
2641 }
2642
2643 fn visit_typed_expr_fn(
2644 &mut self,
2645 location: &'ast SrcSpan,
2646 type_: &'ast Arc<Type>,
2647 kind: &'ast FunctionLiteralKind,
2648 args: &'ast [TypedArg],
2649 body: &'ast Vec1<TypedStatement>,
2650 return_annotation: &'ast Option<ast::TypeAst>,
2651 ) {
2652 // The cursor has to be inside the last statement of the body to
2653 // offer the code action.
2654 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
2655 if within(self.params.range, last_statement_range) {
2656 if let Some(call_data) = turn_statement_into_use(body.last()) {
2657 self.selected_call = Some(call_data);
2658 }
2659 }
2660
2661 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
2662 }
2663
2664 fn visit_typed_expr_block(
2665 &mut self,
2666 location: &'ast SrcSpan,
2667 statements: &'ast [TypedStatement],
2668 ) {
2669 let Some(last_statement) = statements.last() else {
2670 return;
2671 };
2672
2673 // The cursor has to be inside the last statement of the block to offer
2674 // the code action.
2675 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
2676 if within(self.params.range, statement_range) {
2677 // Only the last statement of a block can be turned into a use!
2678 if let Some(selected_call) = turn_statement_into_use(last_statement) {
2679 self.selected_call = Some(selected_call)
2680 }
2681 }
2682
2683 ast::visit::visit_typed_expr_block(self, location, statements);
2684 }
2685}
2686
2687fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
2688 match statement {
2689 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
2690 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
2691 }
2692}
2693
2694fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
2695 let TypedExpr::Call {
2696 args,
2697 location: call_span,
2698 fun: called_function,
2699 ..
2700 } = expr
2701 else {
2702 return None;
2703 };
2704
2705 // The function arguments in the ast are reordered using function's field map.
2706 // This means that in the `args` array they might not appear in the same order
2707 // in which they are written by the user. Since the rest of the code relies
2708 // on their order in the written code we first have to sort them by their
2709 // source position.
2710 let args = args
2711 .iter()
2712 .sorted_by_key(|arg| arg.location.start)
2713 .collect_vec();
2714
2715 let CallArg {
2716 value: last_arg,
2717 implicit: None,
2718 ..
2719 } = args.last()?
2720 else {
2721 return None;
2722 };
2723
2724 let TypedExpr::Fn {
2725 args: callback_args,
2726 body,
2727 ..
2728 } = last_arg
2729 else {
2730 return None;
2731 };
2732
2733 let callback_args_span = match (callback_args.first(), callback_args.last()) {
2734 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
2735 _ => None,
2736 };
2737
2738 let arg_before_callback_span = if args.len() >= 2 {
2739 args.get(args.len() - 2).map(|call_arg| call_arg.location)
2740 } else {
2741 None
2742 };
2743
2744 let callback_body_span = body.first().location().merge(&body.last().last_location());
2745
2746 Some(CallLocations {
2747 call_span: *call_span,
2748 called_function_span: called_function.location(),
2749 callback_args_span,
2750 arg_before_callback_span,
2751 callback_body_span,
2752 })
2753}
2754
2755/// Builder for code action to extract expression into a variable.
2756/// The action will wrap the expression in a block if needed in the appropriate scope.
2757///
2758/// For using the code action on the following selection:
2759///
2760/// ```gleam
2761/// fn void() {
2762/// case result {
2763/// Ok(value) -> 2 * value + 1
2764/// // ^^^^^^^^^
2765/// Error(_) -> panic
2766/// }
2767/// }
2768/// ```
2769///
2770/// Will result:
2771///
2772/// ```gleam
2773/// fn void() {
2774/// case result {
2775/// Ok(value) -> {
2776/// let int = 2 * value
2777/// int + 1
2778/// }
2779/// Error(_) -> panic
2780/// }
2781/// }
2782/// ```
2783pub struct ExtractVariable<'a> {
2784 module: &'a Module,
2785 params: &'a CodeActionParams,
2786 edits: TextEdits<'a>,
2787 position: Option<ExtractVariablePosition>,
2788 selected_expression: Option<(SrcSpan, Arc<Type>)>,
2789 statement_before_selected_expression: Option<SrcSpan>,
2790 latest_statement: Option<SrcSpan>,
2791 to_be_wrapped: bool,
2792}
2793
2794/// The Position of the selected code
2795#[derive(PartialEq, Eq, Copy, Clone, Debug)]
2796enum ExtractVariablePosition {
2797 InsideCaptureBody,
2798 /// Full statements (i.e. assignments, `use`s, and simple expressions).
2799 TopLevelStatement,
2800 /// The call on the right hand side of a pipe `|>`.
2801 PipelineCall,
2802 /// The right hand side of the `->` in a case expression.
2803 InsideCaseClause,
2804 // A call argument. This can also be a `use` callback.
2805 CallArg,
2806}
2807
2808impl<'a> ExtractVariable<'a> {
2809 pub fn new(
2810 module: &'a Module,
2811 line_numbers: &'a LineNumbers,
2812 params: &'a CodeActionParams,
2813 ) -> Self {
2814 Self {
2815 module,
2816 params,
2817 edits: TextEdits::new(line_numbers),
2818 position: None,
2819 selected_expression: None,
2820 latest_statement: None,
2821 statement_before_selected_expression: None,
2822 to_be_wrapped: false,
2823 }
2824 }
2825
2826 pub fn code_actions(mut self) -> Vec<CodeAction> {
2827 self.visit_typed_module(&self.module.ast);
2828
2829 let (Some((expression_span, expression_type)), Some(insert_location)) = (
2830 self.selected_expression,
2831 self.statement_before_selected_expression,
2832 ) else {
2833 return vec![];
2834 };
2835
2836 let mut name_generator = NameGenerator::new();
2837 let variable_name = name_generator.generate_name_from_type(&expression_type);
2838
2839 let content = self
2840 .module
2841 .code
2842 .get(expression_span.start as usize..expression_span.end as usize)
2843 .expect("selected expression");
2844
2845 let range = self.edits.src_span_to_lsp_range(insert_location);
2846
2847 let indent_size =
2848 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
2849
2850 let mut indent = " ".repeat(indent_size);
2851
2852 // We insert the variable declaration
2853 // Wrap in a block if needed
2854 let mut insertion = format!("let {variable_name} = {content}");
2855 if self.to_be_wrapped {
2856 let line_end = self
2857 .edits
2858 .line_numbers
2859 .line_starts
2860 .get((range.end.line + 1) as usize)
2861 .expect("Line number should be valid");
2862
2863 self.edits.insert(*line_end, format!("{indent}}}\n"));
2864 indent += " ";
2865 insertion = format!("{{\n{indent}{insertion}");
2866 };
2867 self.edits
2868 .insert(insert_location.start, insertion + &format!("\n{indent}"));
2869 self.edits
2870 .replace(expression_span, String::from(variable_name));
2871
2872 let mut action = Vec::with_capacity(1);
2873 CodeActionBuilder::new("Extract variable")
2874 .kind(CodeActionKind::REFACTOR_EXTRACT)
2875 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2876 .preferred(false)
2877 .push_to(&mut action);
2878 action
2879 }
2880
2881 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
2882 where
2883 F: Fn(&mut Self),
2884 {
2885 self.at_optional_position(Some(position), fun);
2886 }
2887
2888 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
2889 where
2890 F: Fn(&mut Self),
2891 {
2892 let previous_statement = self.latest_statement;
2893 let previous_position = self.position;
2894 self.position = position;
2895 fun(self);
2896 self.position = previous_position;
2897 self.latest_statement = previous_statement;
2898 }
2899}
2900
2901impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
2902 fn visit_typed_statement(&mut self, stmt: &'ast TypedStatement) {
2903 let range = self.edits.src_span_to_lsp_range(stmt.location());
2904 if !within(self.params.range, range) {
2905 self.latest_statement = Some(stmt.location());
2906 ast::visit::visit_typed_statement(self, stmt);
2907 return;
2908 }
2909
2910 match self.position {
2911 // A capture body is comprised of just a single expression statement
2912 // that is inserted by the compiler, we don't really want to put
2913 // anything before that; so in this case we avoid tracking it.
2914 Some(ExtractVariablePosition::InsideCaptureBody) => {}
2915 Some(ExtractVariablePosition::PipelineCall) => {
2916 // Insert above the pipeline start
2917 self.latest_statement = Some(stmt.location());
2918 }
2919 _ => {
2920 // Insert below the previous statement
2921 self.latest_statement = Some(stmt.location());
2922 self.statement_before_selected_expression = self.latest_statement;
2923 }
2924 }
2925
2926 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
2927 ast::visit::visit_typed_statement(this, stmt);
2928 });
2929 }
2930
2931 fn visit_typed_expr_pipeline(
2932 &mut self,
2933 location: &'ast SrcSpan,
2934 first_value: &'ast TypedPipelineAssignment,
2935 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
2936 finally: &'ast TypedExpr,
2937 finally_kind: &'ast PipelineAssignmentKind,
2938 ) {
2939 let expr_range = self.edits.src_span_to_lsp_range(*location);
2940 if !within(self.params.range, expr_range) {
2941 ast::visit::visit_typed_expr_pipeline(
2942 self,
2943 location,
2944 first_value,
2945 assignments,
2946 finally,
2947 finally_kind,
2948 );
2949 return;
2950 };
2951
2952 // When visiting the assignments or the final pipeline call we want to
2953 // keep track of out position so that we can avoid extracting those.
2954 let all_assignments =
2955 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
2956
2957 for assignment in all_assignments {
2958 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
2959 this.visit_typed_pipeline_assignment(assignment);
2960 });
2961 }
2962
2963 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
2964 this.visit_typed_expr(finally)
2965 });
2966 }
2967
2968 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
2969 let expr_location = expr.location();
2970 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
2971 if !within(self.params.range, expr_range) {
2972 ast::visit::visit_typed_expr(self, expr);
2973 return;
2974 }
2975
2976 // If the expression is a top level statement we don't want to extract
2977 // it into a variable. It would mean we would turn this:
2978 //
2979 // ```gleam
2980 // pub fn main() {
2981 // let wibble = 1
2982 // // ^ cursor here
2983 // }
2984 //
2985 // // into:
2986 //
2987 // pub fn main() {
2988 // let int = 1
2989 // let wibble = int
2990 // }
2991 // ```
2992 //
2993 // Not all that useful!
2994 //
2995 match self.position {
2996 Some(
2997 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
2998 ) => {
2999 self.at_optional_position(None, |this| {
3000 ast::visit::visit_typed_expr(this, expr);
3001 });
3002 return;
3003 }
3004 Some(
3005 ExtractVariablePosition::InsideCaptureBody
3006 | ExtractVariablePosition::InsideCaseClause
3007 | ExtractVariablePosition::CallArg,
3008 )
3009 | None => {
3010 match expr {
3011 // We don't extract variables, they're already good.
3012 // And we don't extract module selects by themselves but always
3013 // want to consider those as part of a function call.
3014 TypedExpr::Var { .. } | TypedExpr::ModuleSelect { .. } => (),
3015 _ => {
3016 self.selected_expression = Some((expr_location, expr.type_()));
3017
3018 if !matches!(self.position, Some(ExtractVariablePosition::CallArg)) {
3019 self.statement_before_selected_expression = self.latest_statement;
3020 }
3021 }
3022 }
3023 }
3024 }
3025
3026 match expr {
3027 // Expressions that don't make sense to extract
3028 TypedExpr::Panic { .. }
3029 | TypedExpr::Echo { .. }
3030 | TypedExpr::Block { .. }
3031 | TypedExpr::ModuleSelect { .. }
3032 | TypedExpr::Invalid { .. }
3033 | TypedExpr::Var { .. } => (),
3034
3035 TypedExpr::Int { location, .. }
3036 | TypedExpr::Float { location, .. }
3037 | TypedExpr::String { location, .. }
3038 | TypedExpr::Pipeline { location, .. }
3039 | TypedExpr::Fn { location, .. }
3040 | TypedExpr::Todo { location, .. }
3041 | TypedExpr::List { location, .. }
3042 | TypedExpr::Call { location, .. }
3043 | TypedExpr::BinOp { location, .. }
3044 | TypedExpr::Case { location, .. }
3045 | TypedExpr::RecordAccess { location, .. }
3046 | TypedExpr::Tuple { location, .. }
3047 | TypedExpr::TupleIndex { location, .. }
3048 | TypedExpr::BitArray { location, .. }
3049 | TypedExpr::RecordUpdate { location, .. }
3050 | TypedExpr::NegateBool { location, .. }
3051 | TypedExpr::NegateInt { location, .. } => {
3052 if let Some(ExtractVariablePosition::CallArg) = self.position {
3053 // Don't update latest statement, we don't want to insert the extracted
3054 // variable inside the parenthesis where the call argument is located.
3055 } else {
3056 self.statement_before_selected_expression = self.latest_statement;
3057 };
3058 self.selected_expression = Some((*location, expr.type_()));
3059 }
3060 }
3061
3062 ast::visit::visit_typed_expr(self, expr);
3063 }
3064
3065 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3066 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3067 if !within(self.params.range, range) {
3068 ast::visit::visit_typed_use(self, use_);
3069 return;
3070 }
3071
3072 // Insert code under the `use`
3073 self.statement_before_selected_expression = Some(use_.call.location());
3074 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3075 ast::visit::visit_typed_use(this, use_);
3076 });
3077 }
3078
3079 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3080 let range = self.edits.src_span_to_lsp_range(clause.location());
3081 if !within(self.params.range, range) {
3082 ast::visit::visit_typed_clause(self, clause);
3083 return;
3084 }
3085
3086 // Insert code after the `->`
3087 self.latest_statement = Some(clause.then.location());
3088 self.to_be_wrapped = true;
3089 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3090 ast::visit::visit_typed_clause(this, clause);
3091 });
3092 }
3093
3094 fn visit_typed_expr_block(
3095 &mut self,
3096 location: &'ast SrcSpan,
3097 statements: &'ast [TypedStatement],
3098 ) {
3099 let range = self.edits.src_span_to_lsp_range(*location);
3100 if !within(self.params.range, range) {
3101 ast::visit::visit_typed_expr_block(self, location, statements);
3102 return;
3103 }
3104
3105 // Don't extract block as variable
3106 let mut position = self.position;
3107 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3108 position = None;
3109 self.to_be_wrapped = false;
3110 }
3111
3112 self.at_optional_position(position, |this| {
3113 ast::visit::visit_typed_expr_block(this, location, statements);
3114 });
3115 }
3116
3117 fn visit_typed_expr_fn(
3118 &mut self,
3119 location: &'ast SrcSpan,
3120 type_: &'ast Arc<Type>,
3121 kind: &'ast FunctionLiteralKind,
3122 args: &'ast [TypedArg],
3123 body: &'ast Vec1<TypedStatement>,
3124 return_annotation: &'ast Option<ast::TypeAst>,
3125 ) {
3126 let range = self.edits.src_span_to_lsp_range(*location);
3127 if !within(self.params.range, range) {
3128 ast::visit::visit_typed_expr_fn(
3129 self,
3130 location,
3131 type_,
3132 kind,
3133 args,
3134 body,
3135 return_annotation,
3136 );
3137 return;
3138 }
3139
3140 let position = match kind {
3141 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3142 // just a single expression statement. When visiting we must record
3143 // we're inside a capture body.
3144 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3145 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3146 FunctionLiteralKind::Anonymous { .. } => self.position,
3147 };
3148
3149 self.at_optional_position(position, |this| {
3150 ast::visit::visit_typed_expr_fn(
3151 this,
3152 location,
3153 type_,
3154 kind,
3155 args,
3156 body,
3157 return_annotation,
3158 );
3159 });
3160 }
3161
3162 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3163 let range = self.edits.src_span_to_lsp_range(arg.location);
3164 if !within(self.params.range, range) {
3165 ast::visit::visit_typed_call_arg(self, arg);
3166 return;
3167 }
3168
3169 let position = if arg.is_use_implicit_callback() {
3170 Some(ExtractVariablePosition::TopLevelStatement)
3171 } else {
3172 Some(ExtractVariablePosition::CallArg)
3173 };
3174
3175 self.at_optional_position(position, |this| {
3176 ast::visit::visit_typed_call_arg(this, arg);
3177 });
3178 }
3179
3180 // We don't want to offer the action if the cursor is over some invalid
3181 // piece of code.
3182 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, _type_: &'ast Arc<Type>) {
3183 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3184 if within(self.params.range, invalid_range) {
3185 self.selected_expression = None;
3186 }
3187 }
3188}
3189
3190/// Builder for code action to convert a literal use into a const.
3191///
3192/// For using the code action on each of the following lines:
3193///
3194/// ```gleam
3195/// fn void() {
3196/// let var = [1, 2, 3]
3197/// let res = function("Statement", var)
3198/// }
3199/// ```
3200///
3201/// Both value literals will become:
3202///
3203/// ```gleam
3204/// const var = [1, 2, 3]
3205/// const string = "Statement"
3206///
3207/// fn void() {
3208/// let res = function(string, var)
3209/// }
3210/// ```
3211pub struct ExtractConstant<'a> {
3212 module: &'a Module,
3213 params: &'a CodeActionParams,
3214 edits: TextEdits<'a>,
3215 /// The whole selected expression
3216 selected_expression: Option<SrcSpan>,
3217 /// The location of the start of the function containing the expression
3218 container_function_start: Option<u32>,
3219 /// The variant of the extractable expression being extracted (if any)
3220 variant_of_extractable: Option<ExtractableToConstant>,
3221 /// The name of the newly created constant
3222 name_to_use: Option<EcoString>,
3223 /// The right hand side expression of the newly created constant
3224 value_to_use: Option<EcoString>,
3225}
3226
3227/// Used when an expression can be extracted to a constant
3228enum ExtractableToConstant {
3229 /// Used for collections and operator uses. This means that elements
3230 /// inside, are also extractable as constants.
3231 ComposedValue,
3232 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
3233 /// and type variants (not records).
3234 SingleValue,
3235 /// Used for whole variable assignments. If the right hand side of the
3236 /// expression can be extracted, the whole expression extracted and use the
3237 /// local variable as a constant.
3238 Assignment,
3239}
3240
3241fn can_be_constant(
3242 module: &Module,
3243 expr: &TypedExpr,
3244 module_constants: Option<&HashSet<&EcoString>>,
3245) -> bool {
3246 // We pass the `module_constants` on recursion to not compute them each time
3247 let mc = match module_constants {
3248 None => &module
3249 .ast
3250 .definitions
3251 .iter()
3252 .filter_map(|definition| match definition {
3253 ast::Definition::ModuleConstant(module_constant) => Some(&module_constant.name),
3254
3255 ast::Definition::Function(_)
3256 | ast::Definition::TypeAlias(_)
3257 | ast::Definition::CustomType(_)
3258 | ast::Definition::Import(_) => None,
3259 })
3260 .collect(),
3261 Some(mc) => mc,
3262 };
3263
3264 match expr {
3265 // Attempt to extract whole list as long as it's comprised of only literals
3266 TypedExpr::List { elements, tail, .. } => {
3267 elements
3268 .iter()
3269 .all(|element| can_be_constant(module, element, Some(mc)))
3270 && tail.is_none()
3271 }
3272
3273 // Attempt to extract whole bit array as long as it's made up of literals
3274 TypedExpr::BitArray { segments, .. } => {
3275 segments
3276 .iter()
3277 .all(|segment| can_be_constant(module, &segment.value, Some(mc)))
3278 && segments.iter().all(|segment| {
3279 segment.options.iter().all(|option| match option {
3280 ast::BitArrayOption::Size { value, .. } => {
3281 can_be_constant(module, value, Some(mc))
3282 }
3283
3284 ast::BitArrayOption::Bytes { .. }
3285 | ast::BitArrayOption::Int { .. }
3286 | ast::BitArrayOption::Float { .. }
3287 | ast::BitArrayOption::Bits { .. }
3288 | ast::BitArrayOption::Utf8 { .. }
3289 | ast::BitArrayOption::Utf16 { .. }
3290 | ast::BitArrayOption::Utf32 { .. }
3291 | ast::BitArrayOption::Utf8Codepoint { .. }
3292 | ast::BitArrayOption::Utf16Codepoint { .. }
3293 | ast::BitArrayOption::Utf32Codepoint { .. }
3294 | ast::BitArrayOption::Signed { .. }
3295 | ast::BitArrayOption::Unsigned { .. }
3296 | ast::BitArrayOption::Big { .. }
3297 | ast::BitArrayOption::Little { .. }
3298 | ast::BitArrayOption::Native { .. }
3299 | ast::BitArrayOption::Unit { .. } => true,
3300 })
3301 })
3302 }
3303
3304 // Attempt to extract whole tuple as long as it's comprised of only literals
3305 TypedExpr::Tuple { elements, .. } => elements
3306 .iter()
3307 .all(|element| can_be_constant(module, element, Some(mc))),
3308
3309 // Extract literals directly
3310 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
3311
3312 // Extract non-record types directly
3313 TypedExpr::Var {
3314 constructor, name, ..
3315 } => {
3316 matches!(
3317 constructor.variant,
3318 type_::ValueConstructorVariant::Record { arity: 0, .. }
3319 ) || mc.contains(name)
3320 }
3321
3322 // Extract record types as long as arguments can be constant
3323 TypedExpr::Call { args, fun, .. } => {
3324 fun.is_record_builder()
3325 && args
3326 .iter()
3327 .all(|arg| can_be_constant(module, &arg.value, module_constants))
3328 }
3329
3330 // Extract concat binary operation if both sides can be constants
3331 TypedExpr::BinOp {
3332 name, left, right, ..
3333 } => {
3334 matches!(name, ast::BinOp::Concatenate)
3335 && can_be_constant(module, left, Some(mc))
3336 && can_be_constant(module, right, Some(mc))
3337 }
3338
3339 TypedExpr::Block { .. }
3340 | TypedExpr::Pipeline { .. }
3341 | TypedExpr::Fn { .. }
3342 | TypedExpr::Case { .. }
3343 | TypedExpr::RecordAccess { .. }
3344 | TypedExpr::ModuleSelect { .. }
3345 | TypedExpr::TupleIndex { .. }
3346 | TypedExpr::Todo { .. }
3347 | TypedExpr::Panic { .. }
3348 | TypedExpr::Echo { .. }
3349 | TypedExpr::RecordUpdate { .. }
3350 | TypedExpr::NegateBool { .. }
3351 | TypedExpr::NegateInt { .. }
3352 | TypedExpr::Invalid { .. } => false,
3353 }
3354}
3355
3356/// Takes the list of already existing constants and functions and creates a
3357/// name that doesn't conflict with them
3358///
3359fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
3360 let mut name_generator = NameGenerator::new();
3361 let already_taken_names = VariablesNames {
3362 names: module
3363 .ast
3364 .definitions
3365 .iter()
3366 .filter_map(|definition| match definition {
3367 ast::Definition::ModuleConstant(constant) => Some(constant.name.clone()),
3368 ast::Definition::Function(function) => function.name.as_ref().map(|n| n.1.clone()),
3369
3370 ast::Definition::TypeAlias(_)
3371 | ast::Definition::CustomType(_)
3372 | ast::Definition::Import(_) => None,
3373 })
3374 .collect(),
3375 };
3376 name_generator.reserve_variable_names(already_taken_names);
3377
3378 name_generator.generate_name_from_type(&expr.type_())
3379}
3380
3381impl<'a> ExtractConstant<'a> {
3382 pub fn new(
3383 module: &'a Module,
3384 line_numbers: &'a LineNumbers,
3385 params: &'a CodeActionParams,
3386 ) -> Self {
3387 Self {
3388 module,
3389 params,
3390 edits: TextEdits::new(line_numbers),
3391 selected_expression: None,
3392 container_function_start: None,
3393 variant_of_extractable: None,
3394 name_to_use: None,
3395 value_to_use: None,
3396 }
3397 }
3398
3399 pub fn code_actions(mut self) -> Vec<CodeAction> {
3400 self.visit_typed_module(&self.module.ast);
3401
3402 let (
3403 Some(expr_span),
3404 Some(function_start),
3405 Some(type_of_extractable),
3406 Some(new_const_name),
3407 Some(const_value),
3408 ) = (
3409 self.selected_expression,
3410 self.container_function_start,
3411 self.variant_of_extractable,
3412 self.name_to_use,
3413 self.value_to_use,
3414 )
3415 else {
3416 return vec![];
3417 };
3418
3419 // We insert the constant declaration
3420 self.edits.insert(
3421 function_start,
3422 format!("const {new_const_name} = {const_value}\n\n"),
3423 );
3424
3425 // We remove or replace the selected expression
3426 match type_of_extractable {
3427 // The whole expression is deleted for assignments
3428 ExtractableToConstant::Assignment => {
3429 let range = self
3430 .edits
3431 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
3432
3433 let indent_size =
3434 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3435
3436 let expr_span_with_new_line = SrcSpan {
3437 // We remove leading indentation + 1 to remove the newline with it
3438 start: expr_span.start - (indent_size as u32 + 1),
3439 end: expr_span.end,
3440 };
3441 self.edits.delete(expr_span_with_new_line);
3442 }
3443
3444 // Only right hand side is replaced for collection or values
3445 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
3446 self.edits.replace(expr_span, String::from(new_const_name));
3447 }
3448 }
3449
3450 let mut action = Vec::with_capacity(1);
3451 CodeActionBuilder::new("Extract constant")
3452 .kind(CodeActionKind::REFACTOR_EXTRACT)
3453 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3454 .preferred(false)
3455 .push_to(&mut action);
3456 action
3457 }
3458}
3459
3460impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
3461 /// To get the position of the function containing the value or assignment
3462 /// to extract
3463 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
3464 let fun_location = fun.location;
3465 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3466 start: fun_location.start,
3467 end: fun.end_position,
3468 });
3469
3470 if !within(self.params.range, fun_range) {
3471 return;
3472 }
3473 self.container_function_start = Some(fun.location.start);
3474
3475 ast::visit::visit_typed_function(self, fun);
3476 }
3477
3478 /// To extract the whole assignment
3479 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3480 let expr_location = assignment.location;
3481
3482 // We only offer this code action for extracting the whole assignment
3483 // between `let` and `=`.
3484 let pattern_location = assignment.pattern.location();
3485 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
3486 let code_action_range = self.edits.src_span_to_lsp_range(location);
3487
3488 if !within(self.params.range, code_action_range) {
3489 ast::visit::visit_typed_assignment(self, assignment);
3490 return;
3491 }
3492
3493 // Has to be variable because patterns can't be constants.
3494 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
3495 {
3496 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
3497 self.selected_expression = Some(expr_location);
3498 self.name_to_use = match &assignment.pattern {
3499 Pattern::Variable { name, .. } => Some(name.clone()),
3500 _ => None,
3501 };
3502 self.value_to_use = Some(EcoString::from(
3503 self.module
3504 .code
3505 .get(
3506 (assignment.value.location().start as usize)
3507 ..(assignment.location.end as usize),
3508 )
3509 .expect("selected expression"),
3510 ));
3511 }
3512 }
3513
3514 /// To extract only the literal
3515 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3516 let expr_location = expr.location();
3517 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3518
3519 if !within(self.params.range, expr_range) {
3520 ast::visit::visit_typed_expr(self, expr);
3521 return;
3522 }
3523
3524 // Keep going down recursively if:
3525 // - It's no extractable has been found yet (`None`).
3526 // - It's a collection, which may or may not contain a value that can
3527 // be extracted.
3528 // - It's a binary operator, which may or may not operate on
3529 // extractable values.
3530 if matches!(
3531 self.variant_of_extractable,
3532 None | Some(ExtractableToConstant::ComposedValue)
3533 ) && can_be_constant(self.module, expr, None)
3534 {
3535 self.variant_of_extractable = match expr {
3536 TypedExpr::Var { .. }
3537 | TypedExpr::Int { .. }
3538 | TypedExpr::Float { .. }
3539 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
3540
3541 TypedExpr::List { .. }
3542 | TypedExpr::Tuple { .. }
3543 | TypedExpr::BitArray { .. }
3544 | TypedExpr::BinOp { .. }
3545 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
3546
3547 TypedExpr::Block { .. }
3548 | TypedExpr::Pipeline { .. }
3549 | TypedExpr::Fn { .. }
3550 | TypedExpr::Case { .. }
3551 | TypedExpr::RecordAccess { .. }
3552 | TypedExpr::ModuleSelect { .. }
3553 | TypedExpr::TupleIndex { .. }
3554 | TypedExpr::Todo { .. }
3555 | TypedExpr::Panic { .. }
3556 | TypedExpr::Echo { .. }
3557 | TypedExpr::RecordUpdate { .. }
3558 | TypedExpr::NegateBool { .. }
3559 | TypedExpr::NegateInt { .. }
3560 | TypedExpr::Invalid { .. } => None,
3561 };
3562
3563 self.selected_expression = Some(expr_location);
3564 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
3565 self.value_to_use = Some(EcoString::from(
3566 self.module
3567 .code
3568 .get((expr_location.start as usize)..(expr_location.end as usize))
3569 .expect("selected expression"),
3570 ));
3571 }
3572
3573 ast::visit::visit_typed_expr(self, expr);
3574 }
3575}
3576
3577/// Builder for code action to apply the "expand function capture" action.
3578///
3579pub struct ExpandFunctionCapture<'a> {
3580 module: &'a Module,
3581 params: &'a CodeActionParams,
3582 edits: TextEdits<'a>,
3583 function_capture_data: Option<FunctionCaptureData>,
3584}
3585
3586pub struct FunctionCaptureData {
3587 function_span: SrcSpan,
3588 hole_span: SrcSpan,
3589 hole_type: Arc<Type>,
3590 reserved_names: VariablesNames,
3591}
3592
3593impl<'a> ExpandFunctionCapture<'a> {
3594 pub fn new(
3595 module: &'a Module,
3596 line_numbers: &'a LineNumbers,
3597 params: &'a CodeActionParams,
3598 ) -> Self {
3599 Self {
3600 module,
3601 params,
3602 edits: TextEdits::new(line_numbers),
3603 function_capture_data: None,
3604 }
3605 }
3606
3607 pub fn code_actions(mut self) -> Vec<CodeAction> {
3608 self.visit_typed_module(&self.module.ast);
3609
3610 let Some(FunctionCaptureData {
3611 function_span,
3612 hole_span,
3613 hole_type,
3614 reserved_names,
3615 }) = self.function_capture_data
3616 else {
3617 return vec![];
3618 };
3619
3620 let mut name_generator = NameGenerator::new();
3621 name_generator.reserve_variable_names(reserved_names);
3622 let name = name_generator.generate_name_from_type(&hole_type);
3623
3624 self.edits.replace(hole_span, name.clone().into());
3625 self.edits.insert(function_span.end, " }".into());
3626 self.edits
3627 .insert(function_span.start, format!("fn({name}) {{ "));
3628
3629 let mut action = Vec::with_capacity(1);
3630 CodeActionBuilder::new("Expand function capture")
3631 .kind(CodeActionKind::REFACTOR_REWRITE)
3632 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3633 .preferred(false)
3634 .push_to(&mut action);
3635 action
3636 }
3637}
3638
3639impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
3640 fn visit_typed_expr_fn(
3641 &mut self,
3642 location: &'ast SrcSpan,
3643 type_: &'ast Arc<Type>,
3644 kind: &'ast FunctionLiteralKind,
3645 args: &'ast [TypedArg],
3646 body: &'ast Vec1<TypedStatement>,
3647 return_annotation: &'ast Option<ast::TypeAst>,
3648 ) {
3649 let fn_range = self.edits.src_span_to_lsp_range(*location);
3650 if within(self.params.range, fn_range) && kind.is_capture() {
3651 if let [arg] = args {
3652 self.function_capture_data = Some(FunctionCaptureData {
3653 function_span: *location,
3654 hole_span: arg.location,
3655 hole_type: arg.type_.clone(),
3656 reserved_names: VariablesNames::from_statements(body),
3657 });
3658 }
3659 }
3660
3661 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation)
3662 }
3663}
3664
3665struct VariablesNames {
3666 names: HashSet<EcoString>,
3667}
3668
3669impl VariablesNames {
3670 fn from_statements(statements: &[TypedStatement]) -> Self {
3671 let mut variables = Self {
3672 names: HashSet::new(),
3673 };
3674
3675 for statement in statements {
3676 variables.visit_typed_statement(statement);
3677 }
3678 variables
3679 }
3680}
3681
3682impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
3683 fn visit_typed_expr_var(
3684 &mut self,
3685 _location: &'ast SrcSpan,
3686 _constructor: &'ast ValueConstructor,
3687 name: &'ast EcoString,
3688 ) {
3689 let _ = self.names.insert(name.clone());
3690 }
3691}
3692
3693/// Builder for code action to apply the "generate dynamic decoder action.
3694///
3695pub struct GenerateDynamicDecoder<'a> {
3696 module: &'a Module,
3697 params: &'a CodeActionParams,
3698 edits: TextEdits<'a>,
3699 printer: Printer<'a>,
3700 actions: &'a mut Vec<CodeAction>,
3701}
3702
3703const DECODE_MODULE: &str = "gleam/dynamic/decode";
3704
3705impl<'a> GenerateDynamicDecoder<'a> {
3706 pub fn new(
3707 module: &'a Module,
3708 line_numbers: &'a LineNumbers,
3709 params: &'a CodeActionParams,
3710 actions: &'a mut Vec<CodeAction>,
3711 ) -> Self {
3712 let printer = Printer::new(&module.ast.names);
3713 Self {
3714 module,
3715 params,
3716 edits: TextEdits::new(line_numbers),
3717 printer,
3718 actions,
3719 }
3720 }
3721
3722 pub fn code_actions(&mut self) {
3723 self.visit_typed_module(&self.module.ast);
3724 }
3725
3726 fn custom_type_decoder_body(
3727 &mut self,
3728 custom_type: &CustomType<Arc<Type>>,
3729 ) -> Option<EcoString> {
3730 // We cannot generate a decoder for an external type with no constructors!
3731 let constructors_size = custom_type.constructors.len();
3732 let (first, rest) = custom_type.constructors.split_first()?;
3733 let mode = EncodingMode::for_custom_type(custom_type);
3734
3735 // We generate a decoder for a type with a single constructor: it does not
3736 // require pattern matching on a tag as there's no variants to tell apart.
3737 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
3738 return self.constructor_decoder(mode, custom_type, first, 0);
3739 }
3740
3741 // Otherwise we need to generate a decoder that has to tell apart different
3742 // variants, depending on the mode we might have to decode a type field or
3743 // plain strings!
3744 let module = self.printer.print_module(DECODE_MODULE);
3745 let discriminant = if mode == EncodingMode::PlainString {
3746 eco_format!("use variant <- {module}.then({module}.string)")
3747 } else {
3748 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
3749 };
3750
3751 let mut branches = Vec::with_capacity(constructors_size);
3752 for constructor in iter::once(first).chain(rest) {
3753 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
3754 let name = to_snake_case(&constructor.name);
3755 branches.push(eco_format!(r#" "{name}" -> {body}"#));
3756 }
3757
3758 let cases = branches.join("\n");
3759 let type_name = &custom_type.name;
3760 Some(eco_format!(
3761 r#"{{
3762 {discriminant}
3763 case variant {{
3764{cases}
3765 _ -> {module}.failure(todo as "Zero value for {type_name}", "{type_name}")
3766 }}
3767}}"#,
3768 ))
3769 }
3770
3771 fn constructor_decoder(
3772 &mut self,
3773 mode: EncodingMode,
3774 custom_type: &ast::TypedCustomType,
3775 constructor: &TypedRecordConstructor,
3776 nesting: usize,
3777 ) -> Option<EcoString> {
3778 let decode_module = self.printer.print_module(DECODE_MODULE);
3779 let constructor_name = &constructor.name;
3780
3781 // If the constructor was encoded as a plain string with no additional
3782 // fields it means there's nothing else to decode and we can just
3783 // succeed.
3784 if mode == EncodingMode::PlainString {
3785 return Some(eco_format!("{decode_module}.success({constructor_name})"));
3786 }
3787
3788 // Otherwise we have to decode all the constructor fields to build it.
3789 let mut fields = Vec::with_capacity(constructor.arguments.len());
3790 for argument in constructor.arguments.iter() {
3791 let (_, name) = argument.label.as_ref()?;
3792 let field = RecordField {
3793 label: RecordLabel::Labeled(name),
3794 type_: &argument.type_,
3795 };
3796 fields.push(field);
3797 }
3798
3799 let mut decoder_printer = DecoderPrinter::new(
3800 &self.module.ast.names,
3801 custom_type.name.clone(),
3802 self.module.name.clone(),
3803 );
3804
3805 let decoders = fields
3806 .iter()
3807 .map(|field| decoder_printer.decode_field(field, nesting + 2))
3808 .join("\n");
3809
3810 let indent = " ".repeat(nesting);
3811
3812 Some(if decoders.is_empty() {
3813 eco_format!("{decode_module}.success({constructor_name})")
3814 } else {
3815 let field_names = fields
3816 .iter()
3817 .map(|field| format!("{}:", field.label.variable_name()))
3818 .join(", ");
3819
3820 eco_format!(
3821 "{{
3822{decoders}
3823{indent} {decode_module}.success({constructor_name}({field_names}))
3824{indent}}}",
3825 )
3826 })
3827 }
3828}
3829
3830impl<'ast> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast> {
3831 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
3832 let range = self.edits.src_span_to_lsp_range(custom_type.location);
3833 if !overlaps(self.params.range, range) {
3834 return;
3835 }
3836
3837 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
3838 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
3839 return;
3840 };
3841
3842 let parameters = match custom_type.parameters.len() {
3843 0 => EcoString::new(),
3844 _ => eco_format!(
3845 "({})",
3846 custom_type
3847 .parameters
3848 .iter()
3849 .map(|(_, name)| name)
3850 .join(", ")
3851 ),
3852 };
3853
3854 let decoder_type = self.printer.print_type(&Type::Named {
3855 publicity: ast::Publicity::Public,
3856 package: STDLIB_PACKAGE_NAME.into(),
3857 module: DECODE_MODULE.into(),
3858 name: "Decoder".into(),
3859 args: vec![],
3860 inferred_variant: None,
3861 });
3862
3863 let function = format!(
3864 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
3865 type_name = custom_type.name,
3866 );
3867
3868 self.edits.insert(custom_type.end_position, function);
3869 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
3870
3871 CodeActionBuilder::new("Generate dynamic decoder")
3872 .kind(CodeActionKind::REFACTOR)
3873 .preferred(false)
3874 .changes(
3875 self.params.text_document.uri.clone(),
3876 std::mem::take(&mut self.edits.edits),
3877 )
3878 .push_to(self.actions);
3879 }
3880}
3881
3882/// If `module_name` is not already imported inside `module`, adds an edit to
3883/// add that import.
3884/// This function also makes sure not to import a module in itself.
3885///
3886fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
3887 if module.ast.names.is_imported(module_name) || module.name == module_name {
3888 return;
3889 }
3890
3891 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
3892 let first_is_import = first_import_pos.is_some();
3893 let import_location = first_import_pos.unwrap_or_default();
3894 let after_import_newlines = add_newlines_after_import(
3895 import_location,
3896 first_is_import,
3897 edits.line_numbers,
3898 &module.code,
3899 );
3900
3901 edits.edits.push(get_import_edit(
3902 import_location,
3903 module_name,
3904 &after_import_newlines,
3905 ));
3906}
3907
3908struct DecoderPrinter<'a> {
3909 printer: Printer<'a>,
3910 /// The name of the root type we are printing a decoder for
3911 type_name: EcoString,
3912 /// The module name of the root type we are printing a decoder for
3913 type_module: EcoString,
3914}
3915
3916struct RecordField<'a> {
3917 label: RecordLabel<'a>,
3918 type_: &'a Type,
3919}
3920
3921enum RecordLabel<'a> {
3922 Labeled(&'a str),
3923 Unlabeled(usize),
3924}
3925
3926impl RecordLabel<'_> {
3927 fn field_key(&self) -> EcoString {
3928 match self {
3929 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
3930 RecordLabel::Unlabeled(index) => {
3931 eco_format!("{index}")
3932 }
3933 }
3934 }
3935
3936 fn variable_name(&self) -> EcoString {
3937 match self {
3938 RecordLabel::Labeled(label) => (*label).into(),
3939 &RecordLabel::Unlabeled(mut index) => {
3940 let mut characters = Vec::new();
3941 let alphabet_length = 26;
3942 let alphabet_offset = b'a';
3943 loop {
3944 let alphabet_index = (index % alphabet_length) as u8;
3945 characters.push((alphabet_offset + alphabet_index) as char);
3946 index /= alphabet_length;
3947
3948 if index == 0 {
3949 break;
3950 }
3951 index -= 1;
3952 }
3953 characters.into_iter().rev().collect()
3954 }
3955 }
3956 }
3957}
3958
3959impl<'a> DecoderPrinter<'a> {
3960 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self {
3961 Self {
3962 type_name,
3963 type_module,
3964 printer: Printer::new(names),
3965 }
3966 }
3967
3968 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
3969 let module_name = self.printer.print_module(DECODE_MODULE);
3970 if type_.is_bit_array() {
3971 eco_format!("{module_name}.bit_array")
3972 } else if type_.is_bool() {
3973 eco_format!("{module_name}.bool")
3974 } else if type_.is_float() {
3975 eco_format!("{module_name}.float")
3976 } else if type_.is_int() {
3977 eco_format!("{module_name}.int")
3978 } else if type_.is_string() {
3979 eco_format!("{module_name}.string")
3980 } else {
3981 match type_.tuple_types() {
3982 Some(types) => {
3983 let fields = types
3984 .iter()
3985 .enumerate()
3986 .map(|(index, type_)| RecordField {
3987 type_,
3988 label: RecordLabel::Unlabeled(index),
3989 })
3990 .collect_vec();
3991 let decoders = fields
3992 .iter()
3993 .map(|field| self.decode_field(field, indent + 2))
3994 .join("\n");
3995 let mut field_names = fields.iter().map(|field| field.label.variable_name());
3996
3997 eco_format!(
3998 "{{
3999{decoders}
4000
4001{indent} {module_name}.success(#({fields}))
4002{indent}}}",
4003 fields = field_names.join(", "),
4004 indent = " ".repeat(indent)
4005 )
4006 }
4007 _ => {
4008 let type_information = type_.named_type_information();
4009 let type_information =
4010 type_information.as_ref().map(|(module, name, arguments)| {
4011 (module.as_str(), name.as_str(), arguments.as_slice())
4012 });
4013
4014 match type_information {
4015 Some(("gleam/dynamic", "Dynamic", _)) => {
4016 eco_format!("{module_name}.dynamic")
4017 }
4018 Some(("gleam", "List", [element])) => {
4019 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
4020 }
4021 Some(("gleam/option", "Option", [some])) => {
4022 eco_format!(
4023 "{module_name}.optional({})",
4024 self.decoder_for(some, indent)
4025 )
4026 }
4027 Some(("gleam/dict", "Dict", [key, value])) => {
4028 eco_format!(
4029 "{module_name}.dict({}, {})",
4030 self.decoder_for(key, indent),
4031 self.decoder_for(value, indent)
4032 )
4033 }
4034 Some((module, name, _))
4035 if module == self.type_module && name == self.type_name =>
4036 {
4037 eco_format!("{}_decoder()", to_snake_case(name))
4038 }
4039 _ => eco_format!(
4040 r#"todo as "Decoder for {}""#,
4041 self.printer.print_type(type_)
4042 ),
4043 }
4044 }
4045 }
4046 }
4047 }
4048
4049 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4050 let decoder = self.decoder_for(field.type_, indent);
4051
4052 eco_format!(
4053 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4054 indent = " ".repeat(indent),
4055 variable = field.label.variable_name(),
4056 field = field.label.field_key(),
4057 module = self.printer.print_module(DECODE_MODULE)
4058 )
4059 }
4060}
4061
4062/// Builder for code action to apply the "Generate to-JSON function" action.
4063///
4064pub struct GenerateJsonEncoder<'a> {
4065 module: &'a Module,
4066 params: &'a CodeActionParams,
4067 edits: TextEdits<'a>,
4068 printer: Printer<'a>,
4069 actions: &'a mut Vec<CodeAction>,
4070 config: &'a PackageConfig,
4071}
4072
4073const JSON_MODULE: &str = "gleam/json";
4074const JSON_PACKAGE_NAME: &str = "gleam_json";
4075
4076#[derive(Eq, PartialEq, Copy, Clone)]
4077enum EncodingMode {
4078 PlainString,
4079 ObjectWithTypeTag,
4080 ObjectWithNoTypeTag,
4081}
4082
4083impl EncodingMode {
4084 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
4085 match type_.constructors.as_slice() {
4086 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
4087 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
4088 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
4089 EncodingMode::PlainString
4090 }
4091 _constructors => EncodingMode::ObjectWithTypeTag,
4092 }
4093 }
4094}
4095
4096impl<'a> GenerateJsonEncoder<'a> {
4097 pub fn new(
4098 module: &'a Module,
4099 line_numbers: &'a LineNumbers,
4100 params: &'a CodeActionParams,
4101 actions: &'a mut Vec<CodeAction>,
4102 config: &'a PackageConfig,
4103 ) -> Self {
4104 let printer = Printer::new(&module.ast.names);
4105 Self {
4106 module,
4107 params,
4108 edits: TextEdits::new(line_numbers),
4109 printer,
4110 actions,
4111 config,
4112 }
4113 }
4114
4115 pub fn code_actions(&mut self) {
4116 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
4117 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
4118 {
4119 self.visit_typed_module(&self.module.ast);
4120 }
4121 }
4122
4123 fn custom_type_encoder_body(
4124 &mut self,
4125 record_name: EcoString,
4126 custom_type: &CustomType<Arc<Type>>,
4127 ) -> Option<EcoString> {
4128 // We cannot generate a decoder for an external type with no constructors!
4129 let constructors_size = custom_type.constructors.len();
4130 let (first, rest) = custom_type.constructors.split_first()?;
4131 let mode = EncodingMode::for_custom_type(custom_type);
4132
4133 // We generate an encoder for a type with a single constructor: it does not
4134 // require pattern matching on the argument as we can access all its fields
4135 // with the usual record access syntax.
4136 if rest.is_empty() {
4137 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
4138 let unpacking = if first.arguments.is_empty() {
4139 ""
4140 } else {
4141 &eco_format!(
4142 "let {name}({fields}:) = {record_name}\n ",
4143 name = first.name,
4144 fields = first
4145 .arguments
4146 .iter()
4147 .filter_map(|argument| {
4148 argument.label.as_ref().map(|(_location, label)| label)
4149 })
4150 .join(":, ")
4151 )
4152 };
4153 return Some(eco_format!("{unpacking}{encoder}"));
4154 }
4155
4156 // Otherwise we generate an encoder for a type with multiple constructors:
4157 // it will need to pattern match on the various constructors and encode each
4158 // one separately.
4159 let mut branches = Vec::with_capacity(constructors_size);
4160 for constructor in iter::once(first).chain(rest) {
4161 let RecordConstructor { name, .. } = constructor;
4162 let encoder =
4163 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
4164 let unpacking = if constructor.arguments.is_empty() {
4165 ""
4166 } else {
4167 &eco_format!(
4168 "({}:)",
4169 constructor
4170 .arguments
4171 .iter()
4172 .filter_map(|argument| {
4173 argument.label.as_ref().map(|(_location, label)| label)
4174 })
4175 .join(":, ")
4176 )
4177 };
4178 branches.push(eco_format!(" {name}{unpacking} -> {encoder}"));
4179 }
4180
4181 let branches = branches.join("\n");
4182 Some(eco_format!(
4183 "case {record_name} {{
4184{branches}
4185 }}",
4186 ))
4187 }
4188
4189 fn constructor_encoder(
4190 &mut self,
4191 mode: EncodingMode,
4192 constructor: &TypedRecordConstructor,
4193 type_name: EcoString,
4194 nesting: usize,
4195 ) -> Option<EcoString> {
4196 let json_module = self.printer.print_module(JSON_MODULE);
4197 let tag = to_snake_case(&constructor.name);
4198 let indent = " ".repeat(nesting);
4199
4200 // If the variant is encoded as a simple json string we just call the
4201 // `json.string` with the variant tag as an argument.
4202 if mode == EncodingMode::PlainString {
4203 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
4204 }
4205
4206 // Otherwise we turn it into an object with a `type` tag field.
4207 let mut encoder_printer =
4208 JsonEncoderPrinter::new(&self.module.ast.names, type_name, self.module.name.clone());
4209
4210 // These are the fields of the json object to encode.
4211 let mut fields = Vec::with_capacity(constructor.arguments.len());
4212 if mode == EncodingMode::ObjectWithTypeTag {
4213 // Any needed type tag is always going to be the first field in the object
4214 fields.push(eco_format!(
4215 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
4216 ));
4217 }
4218
4219 for argument in constructor.arguments.iter() {
4220 let (_, label) = argument.label.as_ref()?;
4221 let field = RecordField {
4222 label: RecordLabel::Labeled(label),
4223 type_: &argument.type_,
4224 };
4225 let encoder = encoder_printer.encode_field(&field, nesting + 2);
4226 fields.push(encoder);
4227 }
4228
4229 let fields = fields.join(",\n");
4230 Some(eco_format!(
4231 "{json_module}.object([
4232{fields},
4233{indent}])"
4234 ))
4235 }
4236}
4237
4238impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
4239 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4240 let range = self.edits.src_span_to_lsp_range(custom_type.location);
4241 if !overlaps(self.params.range, range) {
4242 return;
4243 }
4244
4245 let record_name = to_snake_case(&custom_type.name);
4246 let name = eco_format!("{record_name}_to_json");
4247 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
4248 return;
4249 };
4250
4251 let json_type = self.printer.print_type(&Type::Named {
4252 publicity: ast::Publicity::Public,
4253 package: JSON_PACKAGE_NAME.into(),
4254 module: JSON_MODULE.into(),
4255 name: "Json".into(),
4256 args: vec![],
4257 inferred_variant: None,
4258 });
4259
4260 let type_ = if custom_type.parameters.is_empty() {
4261 custom_type.name.clone()
4262 } else {
4263 let parameters = custom_type
4264 .parameters
4265 .iter()
4266 .map(|(_, name)| name)
4267 .join(", ");
4268 eco_format!("{}({})", custom_type.name, parameters)
4269 };
4270
4271 let function = format!(
4272 "
4273
4274fn {name}({record_name}: {type_}) -> {json_type} {{
4275 {encoder}
4276}}",
4277 );
4278
4279 self.edits.insert(custom_type.end_position, function);
4280 maybe_import(&mut self.edits, self.module, JSON_MODULE);
4281
4282 CodeActionBuilder::new("Generate to-JSON function")
4283 .kind(CodeActionKind::REFACTOR)
4284 .preferred(false)
4285 .changes(
4286 self.params.text_document.uri.clone(),
4287 std::mem::take(&mut self.edits.edits),
4288 )
4289 .push_to(self.actions);
4290 }
4291}
4292
4293struct JsonEncoderPrinter<'a> {
4294 printer: Printer<'a>,
4295 /// The name of the root type we are printing an encoder for
4296 type_name: EcoString,
4297 /// The module name of the root type we are printing an encoder for
4298 type_module: EcoString,
4299}
4300
4301impl<'a> JsonEncoderPrinter<'a> {
4302 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self {
4303 Self {
4304 type_name,
4305 type_module,
4306 printer: Printer::new(names),
4307 }
4308 }
4309
4310 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
4311 let module_name = self.printer.print_module(JSON_MODULE);
4312 let is_capture = encoded_value == "_";
4313 let maybe_capture = |mut function: EcoString| {
4314 if is_capture {
4315 function
4316 } else {
4317 function.push('(');
4318 function.push_str(encoded_value);
4319 function.push(')');
4320 function
4321 }
4322 };
4323
4324 if type_.is_bool() {
4325 maybe_capture(eco_format!("{module_name}.bool"))
4326 } else if type_.is_float() {
4327 maybe_capture(eco_format!("{module_name}.float"))
4328 } else if type_.is_int() {
4329 maybe_capture(eco_format!("{module_name}.int"))
4330 } else if type_.is_string() {
4331 maybe_capture(eco_format!("{module_name}.string"))
4332 } else {
4333 match type_.tuple_types() {
4334 Some(types) => {
4335 let (tuple, new_indent) = if is_capture {
4336 ("value", indent + 4)
4337 } else {
4338 (encoded_value, indent + 2)
4339 };
4340
4341 let encoders = types
4342 .iter()
4343 .enumerate()
4344 .map(|(index, type_)| {
4345 self.encoder_for(&format!("{tuple}.{index}"), type_, new_indent)
4346 })
4347 .collect_vec();
4348
4349 if is_capture {
4350 eco_format!(
4351 "fn(value) {{
4352{indent} {module_name}.preprocessed_array([
4353{indent} {encoders},
4354{indent} ])
4355{indent}}}",
4356 indent = " ".repeat(indent),
4357 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
4358 )
4359 } else {
4360 eco_format!(
4361 "{module_name}.preprocessed_array([
4362{indent} {encoders},
4363{indent}])",
4364 indent = " ".repeat(indent),
4365 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
4366 )
4367 }
4368 }
4369 _ => {
4370 let type_information = type_.named_type_information();
4371 let type_information: Option<(&str, &str, &[Arc<Type>])> =
4372 type_information.as_ref().map(|(module, name, arguments)| {
4373 (module.as_str(), name.as_str(), arguments.as_slice())
4374 });
4375
4376 match type_information {
4377 Some(("gleam", "List", [element])) => {
4378 eco_format!(
4379 "{module_name}.array({encoded_value}, {map_function})",
4380 map_function = self.encoder_for("_", element, indent)
4381 )
4382 }
4383 Some(("gleam/option", "Option", [some])) => {
4384 eco_format!(
4385 "case {encoded_value} {{
4386{indent} {none} -> {module_name}.null()
4387{indent} {some}(value) -> {encoder}
4388{indent}}}",
4389 indent = " ".repeat(indent),
4390 none = self
4391 .printer
4392 .print_constructor(&"gleam/option".into(), &"None".into()),
4393 some = self
4394 .printer
4395 .print_constructor(&"gleam/option".into(), &"Some".into()),
4396 encoder = self.encoder_for("value", some, indent + 2)
4397 )
4398 }
4399 Some(("gleam/dict", "Dict", [key, value])) => {
4400 let stringify_function = match key
4401 .named_type_information()
4402 .as_ref()
4403 .map(|(module, name, arguments)| {
4404 (module.as_str(), name.as_str(), arguments.as_slice())
4405 }) {
4406 Some(("gleam", "String", [])) => "fn(string) { string }",
4407 _ => &format!(
4408 r#"todo as "Function to stringify {}""#,
4409 self.printer.print_type(key)
4410 ),
4411 };
4412 eco_format!(
4413 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
4414 self.encoder_for("_", value, indent)
4415 )
4416 }
4417 Some((module, name, _))
4418 if module == self.type_module && name == self.type_name =>
4419 {
4420 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
4421 }
4422 _ => eco_format!(
4423 r#"todo as "Encoder for {}""#,
4424 self.printer.print_type(type_)
4425 ),
4426 }
4427 }
4428 }
4429 }
4430 }
4431
4432 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4433 let field_name = field.label.variable_name();
4434 let encoder = self.encoder_for(&field_name, field.type_, indent);
4435
4436 eco_format!(
4437 r#"{indent}#("{field_name}", {encoder})"#,
4438 indent = " ".repeat(indent),
4439 )
4440 }
4441}
4442
4443/// Builder for code action to pattern match on things like (anonymous) function
4444/// arguments or variables.
4445/// For example:
4446///
4447/// ```gleam
4448/// pub fn wibble(arg: #(key, value)) {
4449/// // ^ [pattern match on argument]
4450/// }
4451///
4452/// // Generates
4453///
4454/// pub fn wibble(arg: #(key, value)) {
4455/// let #(value_0, value_1) = arg
4456/// }
4457/// ```
4458///
4459/// Another example with variables:
4460///
4461/// ```gleam
4462/// pub fn main() {
4463/// let pair = #(1, 3)
4464/// // ^ [pattern match on value]
4465/// }
4466///
4467/// // Generates
4468///
4469/// pub fn main() {
4470/// let pair = #(1, 3)
4471/// let #(value_0, value_1) = pair
4472/// }
4473/// ```
4474///
4475pub struct PatternMatchOnValue<'a, A> {
4476 module: &'a Module,
4477 params: &'a CodeActionParams,
4478 compiler: &'a LspProjectCompiler<A>,
4479 selected_value: Option<PatternMatchedValue<'a>>,
4480 edits: TextEdits<'a>,
4481}
4482
4483/// A value we might want to pattern match on.
4484/// Each variant will also contain all the info needed to know how to properly
4485/// print and format the corresponding pattern matching code; that's why you'll
4486/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
4487///
4488pub enum PatternMatchedValue<'a> {
4489 FunctionArgument {
4490 /// The argument being pattern matched on.
4491 ///
4492 arg: &'a TypedArg,
4493 /// The first statement inside the function body. Used to correctly
4494 /// position the inserted pattern matching.
4495 ///
4496 first_statement: &'a TypedStatement,
4497 /// The range of the entire function holding the argument.
4498 ///
4499 function_range: Range,
4500 },
4501 LetVariable {
4502 variable_name: &'a EcoString,
4503 variable_type: &'a Arc<Type>,
4504 /// The location of the entire let assignment the variable is part of,
4505 /// so that we can add the pattern matching _after_ it.
4506 ///
4507 assignment_location: SrcSpan,
4508 },
4509}
4510
4511impl<'a, IO> PatternMatchOnValue<'a, IO>
4512where
4513 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone,
4514{
4515 pub fn new(
4516 module: &'a Module,
4517 line_numbers: &'a LineNumbers,
4518 params: &'a CodeActionParams,
4519 compiler: &'a LspProjectCompiler<IO>,
4520 ) -> Self {
4521 Self {
4522 module,
4523 params,
4524 compiler,
4525 selected_value: None,
4526 edits: TextEdits::new(line_numbers),
4527 }
4528 }
4529
4530 pub fn code_actions(mut self) -> Vec<CodeAction> {
4531 self.visit_typed_module(&self.module.ast);
4532
4533 let action_title = match self.selected_value {
4534 Some(PatternMatchedValue::FunctionArgument {
4535 arg,
4536 first_statement: function_body,
4537 function_range,
4538 }) => {
4539 self.match_on_function_argument(arg, function_body, function_range);
4540 "Pattern match on argument"
4541 }
4542 Some(PatternMatchedValue::LetVariable {
4543 variable_name,
4544 variable_type,
4545 assignment_location,
4546 }) => {
4547 self.match_on_let_variable(variable_name, variable_type, assignment_location);
4548 "Pattern match on variable"
4549 }
4550 None => return vec![],
4551 };
4552
4553 if self.edits.edits.is_empty() {
4554 return vec![];
4555 }
4556
4557 let mut action = Vec::with_capacity(1);
4558 CodeActionBuilder::new(action_title)
4559 .kind(CodeActionKind::REFACTOR_REWRITE)
4560 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4561 .preferred(false)
4562 .push_to(&mut action);
4563 action
4564 }
4565
4566 fn match_on_function_argument(
4567 &mut self,
4568 arg: &TypedArg,
4569 first_statement: &TypedStatement,
4570 function_range: Range,
4571 ) {
4572 let Some(arg_name) = arg.get_variable_name() else {
4573 return;
4574 };
4575
4576 let Some(patterns) = self.type_to_destructure_patterns(arg.type_.as_ref()) else {
4577 return;
4578 };
4579
4580 let first_statement_location = first_statement.location();
4581 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
4582
4583 // If we're trying to insert the pattern matching on the same
4584 // line as the one where the function is defined we will want to
4585 // put it on a new line instead. So in that case the nesting will
4586 // be the default 2 spaces.
4587 let needs_newline = function_range.start.line == first_statement_range.start.line;
4588 let nesting = if needs_newline {
4589 String::from(" ")
4590 } else {
4591 " ".repeat(first_statement_range.start.character as usize)
4592 };
4593
4594 let pattern_matching = if patterns.len() == 1 {
4595 let pattern = patterns.first();
4596 format!("let {pattern} = {arg_name}")
4597 } else {
4598 let patterns = patterns
4599 .iter()
4600 .map(|p| format!(" {nesting}{p} -> todo"))
4601 .join("\n");
4602 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
4603 };
4604
4605 let pattern_matching = if needs_newline {
4606 format!("\n{nesting}{pattern_matching}")
4607 } else {
4608 pattern_matching
4609 };
4610
4611 let has_empty_body = match first_statement {
4612 ast::Statement::Expression(TypedExpr::Todo {
4613 kind: TodoKind::EmptyFunction { .. },
4614 ..
4615 }) => true,
4616 _ => false,
4617 };
4618
4619 // If the pattern matching is added to a function with an empty
4620 // body then we do not add any nesting after it, or we would be
4621 // increasing the nesting of the closing `}`!
4622 let pattern_matching = if has_empty_body {
4623 format!("{pattern_matching}\n")
4624 } else {
4625 format!("{pattern_matching}\n{nesting}")
4626 };
4627
4628 self.edits
4629 .insert(first_statement_location.start, pattern_matching);
4630 }
4631
4632 fn match_on_let_variable(
4633 &mut self,
4634 variable_name: &EcoString,
4635 variable_type: &Arc<Type>,
4636 assignment_location: SrcSpan,
4637 ) {
4638 let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref()) else {
4639 return;
4640 };
4641
4642 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
4643 let nesting = " ".repeat(assignment_range.start.character as usize);
4644
4645 let pattern_matching = if patterns.len() == 1 {
4646 let pattern = patterns.first();
4647 format!("let {pattern} = {variable_name}")
4648 } else {
4649 let patterns = patterns
4650 .iter()
4651 .map(|p| format!(" {nesting}{p} -> todo"))
4652 .join("\n");
4653 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
4654 };
4655
4656 self.edits.insert(
4657 assignment_location.end,
4658 format!("\n{nesting}{pattern_matching}"),
4659 );
4660 }
4661
4662 /// Will produce a pattern that can be used on the left hand side of a let
4663 /// assignment to destructure a value of the given type. For example given
4664 /// this type:
4665 ///
4666 /// ```gleam
4667 /// pub type Wibble {
4668 /// Wobble(Int, label: String)
4669 /// }
4670 /// ```
4671 ///
4672 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
4673 /// The pattern will use the correct qualified/unqualified name for the
4674 /// constructor if it comes from another package.
4675 ///
4676 /// The function will only produce a list of patterns that can be used from
4677 /// the current module. So if the type comes from another module it must be
4678 /// public! Otherwise this function will return an empty vec.
4679 ///
4680 fn type_to_destructure_patterns(&mut self, type_: &Type) -> Option<Vec1<EcoString>> {
4681 match type_ {
4682 Type::Fn { .. } => None,
4683 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow()),
4684 Type::Named {
4685 module: type_module,
4686 name: type_name,
4687 ..
4688 } => {
4689 let patterns =
4690 get_type_constructors(self.compiler, &self.module.name, type_module, type_name)
4691 .iter()
4692 .filter_map(|c| self.record_constructor_to_destructure_pattern(c))
4693 .collect_vec();
4694
4695 Vec1::try_from_vec(patterns).ok()
4696 }
4697 // We don't want to suggest this action for empty tuple as it
4698 // doesn't make a lot of sense to match on those.
4699 Type::Tuple { elements } if elements.is_empty() => None,
4700 Type::Tuple { elements } => Some(vec1![eco_format!(
4701 "#({})",
4702 (0..elements.len() as u32)
4703 .map(|i| format!("value_{i}"))
4704 .join(", ")
4705 )]),
4706 }
4707 }
4708
4709 fn type_var_to_destructure_patterns(&mut self, type_var: &TypeVar) -> Option<Vec1<EcoString>> {
4710 match type_var {
4711 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
4712 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_),
4713 }
4714 }
4715
4716 /// Given the value constructor of a record, returns a string with the
4717 /// pattern used to match on that specific variant.
4718 ///
4719 /// Note how:
4720 /// - If the constructor is internal to another module or comes from another
4721 /// module, then this returns `None` since one cannot pattern match on it.
4722 /// - If the provided `ValueConstructor` is not a record constructor this
4723 /// will return `None`.
4724 ///
4725 fn record_constructor_to_destructure_pattern(
4726 &self,
4727 constructor: &ValueConstructor,
4728 ) -> Option<EcoString> {
4729 let type_::ValueConstructorVariant::Record {
4730 name: constructor_name,
4731 arity: constructor_arity,
4732 module: constructor_module,
4733 field_map,
4734 ..
4735 } = &constructor.variant
4736 else {
4737 // The constructor should always be a record, in case it's not
4738 // there's not much we can do and just fail.
4739 return None;
4740 };
4741
4742 // Since the constructor is a record constructor we know that its type
4743 // is either `Named` or a `Fn` type, in either case we have to get the
4744 // arguments types out of it.
4745 let Some(arguments_types) = constructor
4746 .type_
4747 .fn_types()
4748 .map(|(arguments_types, _return)| arguments_types)
4749 .or_else(|| constructor.type_.constructor_types())
4750 else {
4751 // This should never happen but just in case we don't want to unwrap
4752 // and panic.
4753 return None;
4754 };
4755
4756 let mut name_generator = NameGenerator::new();
4757 let index_to_label = match field_map {
4758 None => HashMap::new(),
4759 Some(field_map) => {
4760 name_generator.reserve_all_labels(field_map);
4761
4762 field_map
4763 .fields
4764 .iter()
4765 .map(|(label, index)| (index, label))
4766 .collect::<HashMap<_, _>>()
4767 }
4768 };
4769
4770 let mut pattern =
4771 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
4772
4773 if *constructor_arity == 0 {
4774 return Some(pattern);
4775 }
4776
4777 pattern.push('(');
4778 let args = (0..*constructor_arity as u32)
4779 .map(|i| match index_to_label.get(&i) {
4780 Some(label) => eco_format!("{label}:"),
4781 None => match arguments_types.get(i as usize) {
4782 None => name_generator.rename_to_avoid_shadowing(EcoString::from("value")),
4783 Some(type_) => name_generator.generate_name_from_type(type_),
4784 },
4785 })
4786 .join(", ");
4787
4788 pattern.push_str(&args);
4789 pattern.push(')');
4790 Some(pattern)
4791 }
4792}
4793
4794impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO>
4795where
4796 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone,
4797{
4798 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
4799 // If we're not inside the function there's no point in exploring its
4800 // ast further.
4801 let function_span = SrcSpan {
4802 start: fun.location.start,
4803 end: fun.end_position,
4804 };
4805 let function_range = self.edits.src_span_to_lsp_range(function_span);
4806 if !within(self.params.range, function_range) {
4807 return;
4808 }
4809
4810 for arg in &fun.arguments {
4811 // If the cursor is placed on one of the arguments, then we can try
4812 // and generate code for that one.
4813 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
4814 if within(self.params.range, arg_range) {
4815 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
4816 arg,
4817 first_statement: fun.body.first(),
4818 function_range,
4819 });
4820 return;
4821 }
4822 }
4823
4824 // If the cursor is not on any of the function arguments then we keep
4825 // exploring the function body as we might want to destructure the
4826 // argument of an expression function!
4827 ast::visit::visit_typed_function(self, fun);
4828 }
4829
4830 fn visit_typed_expr_fn(
4831 &mut self,
4832 location: &'ast SrcSpan,
4833 type_: &'ast Arc<Type>,
4834 kind: &'ast FunctionLiteralKind,
4835 args: &'ast [TypedArg],
4836 body: &'ast Vec1<TypedStatement>,
4837 return_annotation: &'ast Option<ast::TypeAst>,
4838 ) {
4839 // If we're not inside the function there's no point in exploring its
4840 // ast further.
4841 let function_range = self.edits.src_span_to_lsp_range(*location);
4842 if !within(self.params.range, function_range) {
4843 return;
4844 }
4845
4846 for arg in args {
4847 // If the cursor is placed on one of the arguments, then we can try
4848 // and generate code for that one.
4849 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
4850 if within(self.params.range, arg_range) {
4851 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
4852 arg,
4853 first_statement: body.first(),
4854 function_range,
4855 });
4856 return;
4857 }
4858 }
4859
4860 // If the cursor is not on any of the function arguments then we keep
4861 // exploring the function body as we might want to destructure the
4862 // argument of an expression function!
4863 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
4864 }
4865
4866 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
4867 if let Pattern::Variable {
4868 name,
4869 location,
4870 type_,
4871 ..
4872 } = &assignment.pattern
4873 {
4874 let variable_range = self.edits.src_span_to_lsp_range(*location);
4875 if within(self.params.range, variable_range) {
4876 self.selected_value = Some(PatternMatchedValue::LetVariable {
4877 variable_name: name,
4878 variable_type: type_,
4879 assignment_location: assignment.location,
4880 });
4881 // If we've found the variable to pattern match on, there's no
4882 // point in keeping traversing the AST.
4883 return;
4884 }
4885 }
4886
4887 ast::visit::visit_typed_assignment(self, assignment);
4888 }
4889}
4890
4891/// Given a type and its module, returns a list of its *importable*
4892/// constructors.
4893///
4894/// Since this focuses just on importable constructors, if either the module or
4895/// the type are internal the returned array will be empty!
4896///
4897fn get_type_constructors<'a, 'b, IO>(
4898 compiler: &'a LspProjectCompiler<IO>,
4899 current_module: &'b EcoString,
4900 type_module: &'b EcoString,
4901 type_name: &'b EcoString,
4902) -> Vec<&'a ValueConstructor>
4903where
4904 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone,
4905{
4906 let type_is_inside_current_module = current_module == type_module;
4907 let module_interface = if !type_is_inside_current_module {
4908 // If the type is outside of the module we're in, we can only pattern
4909 // match on it if the module can be imported.
4910 // The `get_module_interface` already takes care of making this check.
4911 compiler.get_module_interface(type_module)
4912 } else {
4913 // However, if the type is defined in the module we're in, we can always
4914 // pattern match on it. So we get the current module's interface.
4915 compiler
4916 .modules
4917 .get(current_module)
4918 .map(|module| &module.ast.type_info)
4919 };
4920
4921 let Some(module_interface) = module_interface else {
4922 return vec![];
4923 };
4924
4925 // If the type is in an internal module that is not the current one, we
4926 // cannot use its constructors!
4927 if !type_is_inside_current_module && module_interface.is_internal {
4928 return vec![];
4929 }
4930
4931 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
4932 return vec![];
4933 };
4934
4935 constructors
4936 .variants
4937 .iter()
4938 .filter_map(|variant| {
4939 let constructor = module_interface.values.get(&variant.name)?;
4940 if type_is_inside_current_module || constructor.publicity.is_public() {
4941 Some(constructor)
4942 } else {
4943 None
4944 }
4945 })
4946 .collect_vec()
4947}
4948
4949/// Returns a pretty printed record constructor name, the way it would be used
4950/// inside the given `module` (with the correct name and qualification).
4951///
4952/// If the constructor cannot be used inside the module because it's not
4953/// imported, then this function will return `None`.
4954///
4955fn pretty_constructor_name(
4956 module: &Module,
4957 constructor_module: &EcoString,
4958 constructor_name: &EcoString,
4959) -> Option<EcoString> {
4960 match module
4961 .ast
4962 .names
4963 .named_constructor(constructor_module, constructor_name)
4964 {
4965 type_::printer::NameContextInformation::Unimported(_) => None,
4966 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
4967 Some(eco_format!("{constructor_name}"))
4968 }
4969 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
4970 Some(eco_format!("{module_name}.{constructor_name}"))
4971 }
4972 }
4973}
4974
4975/// Builder for the "generate function" code action.
4976/// Whenever someone hovers an invalid expression that is inferred to have a
4977/// function type the language server can generate a function definition for it.
4978/// For example:
4979///
4980/// ```gleam
4981/// pub fn main() {
4982/// wibble(1, 2, "hello")
4983/// // ^ [generate function]
4984/// }
4985/// ```
4986///
4987/// Will generate the following definition:
4988///
4989/// ```gleam
4990/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
4991/// todo
4992/// }
4993/// ```
4994///
4995pub struct GenerateFunction<'a> {
4996 module: &'a Module,
4997 params: &'a CodeActionParams,
4998 edits: TextEdits<'a>,
4999 last_visited_function_end: Option<u32>,
5000 function_to_generate: Option<FunctionToGenerate<'a>>,
5001}
5002
5003struct FunctionToGenerate<'a> {
5004 name: &'a str,
5005 arguments_types: Vec<Arc<Type>>,
5006
5007 /// The arguments actually supplied as input to the function, if any.
5008 /// A function to generate might as well be just a name passed as an argument
5009 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
5010 /// have any actual arguments!
5011 given_arguments: Option<&'a [TypedCallArg]>,
5012 return_type: Arc<Type>,
5013 previous_function_end: Option<u32>,
5014}
5015
5016impl<'a> GenerateFunction<'a> {
5017 pub fn new(
5018 module: &'a Module,
5019 line_numbers: &'a LineNumbers,
5020 params: &'a CodeActionParams,
5021 ) -> Self {
5022 Self {
5023 module,
5024 params,
5025 edits: TextEdits::new(line_numbers),
5026 last_visited_function_end: None,
5027 function_to_generate: None,
5028 }
5029 }
5030
5031 pub fn code_actions(mut self) -> Vec<CodeAction> {
5032 self.visit_typed_module(&self.module.ast);
5033
5034 let Some(FunctionToGenerate {
5035 name,
5036 arguments_types,
5037 given_arguments,
5038 previous_function_end: Some(insert_at),
5039 return_type,
5040 }) = self.function_to_generate
5041 else {
5042 return vec![];
5043 };
5044
5045 // Labels do not share the same namespace as argument so we use two separate
5046 // generators to avoid renaming a label in case it shares a name with an argument.
5047 let mut label_names = NameGenerator::new();
5048 let mut argument_names = NameGenerator::new();
5049 let mut printer = Printer::new(&self.module.ast.names);
5050 let arguments = arguments_types
5051 .iter()
5052 .enumerate()
5053 .map(|(index, argument_type)| {
5054 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
5055 let (label, name) =
5056 argument_names.generate_label_and_name(call_argument, argument_type);
5057 let pretty_type = printer.print_type(argument_type);
5058 if let Some(label) = label {
5059 let label = label_names.rename_to_avoid_shadowing(label.clone());
5060 format!("{label} {name}: {pretty_type}")
5061 } else {
5062 format!("{name}: {pretty_type}")
5063 }
5064 })
5065 .join(", ");
5066
5067 let return_type = printer.print_type(&return_type);
5068
5069 self.edits.insert(
5070 insert_at,
5071 format!("\n\nfn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
5072 );
5073
5074 let mut action = Vec::with_capacity(1);
5075 CodeActionBuilder::new("Generate function")
5076 .kind(CodeActionKind::REFACTOR_REWRITE)
5077 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5078 .preferred(false)
5079 .push_to(&mut action);
5080 action
5081 }
5082
5083 fn try_save_function_to_generate(
5084 &mut self,
5085 function_name_location: SrcSpan,
5086 function_type: &Arc<Type>,
5087 given_arguments: Option<&'a [TypedCallArg]>,
5088 ) {
5089 let name_range = function_name_location.start as usize..function_name_location.end as usize;
5090 let candidate_name = self.module.code.get(name_range);
5091 match (candidate_name, function_type.fn_types()) {
5092 (None, _) | (_, None) => (),
5093 (Some(name), _) if !is_valid_lowercase_name(name) => (),
5094 (Some(name), Some((arguments_types, return_type))) => {
5095 self.function_to_generate = Some(FunctionToGenerate {
5096 name,
5097 arguments_types,
5098 given_arguments,
5099 return_type,
5100 previous_function_end: self.last_visited_function_end,
5101 })
5102 }
5103 }
5104 }
5105}
5106
5107impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
5108 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
5109 self.last_visited_function_end = Some(fun.end_position);
5110 ast::visit::visit_typed_function(self, fun);
5111 }
5112
5113 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5114 let invalid_range = self.edits.src_span_to_lsp_range(*location);
5115 if within(self.params.range, invalid_range) {
5116 self.try_save_function_to_generate(*location, type_, None);
5117 }
5118
5119 ast::visit::visit_typed_expr_invalid(self, location, type_);
5120 }
5121
5122 fn visit_typed_expr_call(
5123 &mut self,
5124 location: &'ast SrcSpan,
5125 type_: &'ast Arc<Type>,
5126 fun: &'ast TypedExpr,
5127 args: &'ast [TypedCallArg],
5128 ) {
5129 // If the function being called is invalid we need to generate a
5130 // function that has the proper labels.
5131 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
5132
5133 if within(self.params.range, fun_range) && fun.is_invalid() {
5134 if labels_are_correct(args) {
5135 self.try_save_function_to_generate(fun.location(), &fun.type_(), Some(args));
5136 }
5137 } else {
5138 ast::visit::visit_typed_expr_call(self, location, type_, fun, args);
5139 }
5140 }
5141}
5142
5143/// Builder for the "generate variant" code action. This will generate a variant
5144/// for a type if it can tell the type it should come from. It will work with
5145/// non-existing variants both used as expressions
5146///
5147/// ```gleam
5148/// let a = IDoNotExist(1)
5149/// // ^^^^^^^^^^^ It would generate this variant here
5150/// ```
5151///
5152/// And as patterns:
5153///
5154/// ```gleam
5155/// let assert IDoNotExist(1) = todo
5156/// ^^^^^^^^^^^ It would generate this variant here
5157/// ```
5158///
5159pub struct GenerateVariant<'a, IO> {
5160 module: &'a Module,
5161 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
5162 params: &'a CodeActionParams,
5163 line_numbers: &'a LineNumbers,
5164 variant_to_generate: Option<VariantToGenerate<'a>>,
5165}
5166
5167struct VariantToGenerate<'a> {
5168 name: &'a str,
5169 end_position: u32,
5170 arguments_types: Vec<Arc<Type>>,
5171
5172 /// Wether the type we're adding the variant to is written with braces or
5173 /// not. We need this information to add braces when missing.
5174 ///
5175 type_braces: TypeBraces,
5176
5177 /// The module this variant will be added to.
5178 ///
5179 module_name: EcoString,
5180
5181 /// The arguments actually supplied as input to the variant, if any.
5182 /// A variant to generate might as well be just a name passed as an argument
5183 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
5184 /// have any actual arguments!
5185 ///
5186 given_arguments: Option<Arguments<'a>>,
5187}
5188
5189#[derive(Debug, Clone, Copy)]
5190enum TypeBraces {
5191 /// If the type is written like this: `pub type Wibble`
5192 HasBraces,
5193 /// If the type is written like this: `pub type Wibble {}`
5194 NoBraces,
5195}
5196
5197/// The arguments to an invalid call or pattern we can use to generate a variant.
5198///
5199enum Arguments<'a> {
5200 /// These are the arguments provided to the invalid variant constructor
5201 /// when it's used as a function: `let a = Wibble(1, 2)`.
5202 ///
5203 Expressions(&'a [TypedCallArg]),
5204 /// These are the arguments provided to the invalid variant constructor when
5205 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
5206 ///
5207 Patterns(&'a [CallArg<TypedPattern>]),
5208}
5209
5210/// An invalid variant might be used both as a pattern in a case expression or
5211/// as a regular value in an expression. We want to generate the variant in both
5212/// cases, so we use this enum to tell apart the two cases and be able to reuse
5213/// most of the code for both as they are very similar.
5214///
5215enum Argument<'a> {
5216 Expression(&'a TypedCallArg),
5217 Pattern(&'a CallArg<TypedPattern>),
5218}
5219
5220impl<'a> Arguments<'a> {
5221 fn get(&self, index: usize) -> Option<Argument<'a>> {
5222 match self {
5223 Arguments::Patterns(call_args) => call_args.get(index).map(Argument::Pattern),
5224 Arguments::Expressions(call_args) => call_args.get(index).map(Argument::Expression),
5225 }
5226 }
5227
5228 fn types(&self) -> Vec<Arc<Type>> {
5229 match self {
5230 Arguments::Expressions(call_args) => call_args
5231 .iter()
5232 .map(|argument| argument.value.type_())
5233 .collect_vec(),
5234
5235 Arguments::Patterns(call_args) => call_args
5236 .iter()
5237 .map(|argument| argument.value.type_())
5238 .collect_vec(),
5239 }
5240 }
5241}
5242
5243impl Argument<'_> {
5244 fn label(&self) -> Option<EcoString> {
5245 match self {
5246 Argument::Expression(call_arg) => call_arg.label.clone(),
5247 Argument::Pattern(call_arg) => call_arg.label.clone(),
5248 }
5249 }
5250}
5251
5252impl<'a, IO> GenerateVariant<'a, IO>
5253where
5254 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone,
5255{
5256 pub fn new(
5257 module: &'a Module,
5258 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
5259 line_numbers: &'a LineNumbers,
5260 params: &'a CodeActionParams,
5261 ) -> Self {
5262 Self {
5263 module,
5264 params,
5265 compiler,
5266 line_numbers,
5267 variant_to_generate: None,
5268 }
5269 }
5270
5271 pub fn code_actions(mut self) -> Vec<CodeAction> {
5272 self.visit_typed_module(&self.module.ast);
5273
5274 let Some(VariantToGenerate {
5275 name,
5276 arguments_types,
5277 given_arguments,
5278 module_name,
5279 end_position,
5280 type_braces,
5281 }) = &self.variant_to_generate
5282 else {
5283 return vec![];
5284 };
5285
5286 let Some((variant_module, variant_edits)) = self.edits_to_create_variant(
5287 name,
5288 arguments_types,
5289 given_arguments,
5290 module_name,
5291 *end_position,
5292 *type_braces,
5293 ) else {
5294 return vec![];
5295 };
5296
5297 let mut action = Vec::with_capacity(1);
5298 CodeActionBuilder::new("Generate variant")
5299 .kind(CodeActionKind::REFACTOR_REWRITE)
5300 .changes(variant_module, variant_edits)
5301 .preferred(false)
5302 .push_to(&mut action);
5303 action
5304 }
5305
5306 /// Returns the edits needed to add this new variant to the given module.
5307 /// It also returns the uri of the module the edits should be applied to.
5308 ///
5309 fn edits_to_create_variant(
5310 &self,
5311 variant_name: &str,
5312 arguments_types: &[Arc<Type>],
5313 given_arguments: &Option<Arguments<'_>>,
5314 module_name: &EcoString,
5315 end_position: u32,
5316 type_braces: TypeBraces,
5317 ) -> Option<(Url, Vec<TextEdit>)> {
5318 let mut label_names = NameGenerator::new();
5319 let mut printer = Printer::new(&self.module.ast.names);
5320 let arguments = arguments_types
5321 .iter()
5322 .enumerate()
5323 .map(|(index, argument_type)| {
5324 let label = given_arguments
5325 .as_ref()
5326 .and_then(|arguments| arguments.get(index)?.label())
5327 .map(|label| label_names.rename_to_avoid_shadowing(label));
5328
5329 let pretty_type = printer.print_type(argument_type);
5330 if let Some(arg_label) = label {
5331 format!("{arg_label}: {pretty_type}")
5332 } else {
5333 format!("{pretty_type}")
5334 }
5335 })
5336 .join(", ");
5337
5338 let variant = if arguments.is_empty() {
5339 variant_name.to_string()
5340 } else {
5341 format!("{variant_name}({arguments})")
5342 };
5343
5344 let (new_text, insert_at) = match type_braces {
5345 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
5346 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
5347 };
5348
5349 if *module_name == self.module.name {
5350 // If we're editing the current module we can use the line numbers that
5351 // were already computed before-hand without wasting any time to add the
5352 // new edit.
5353 let mut edits = TextEdits::new(self.line_numbers);
5354 edits.insert(insert_at, new_text);
5355 Some((self.params.text_document.uri.clone(), edits.edits))
5356 } else {
5357 // Otherwise we're changing a different module and we need to get its
5358 // code and line numbers to properly apply the new edit.
5359 let module = self
5360 .compiler
5361 .modules
5362 .get(module_name)
5363 .expect("module to exist");
5364 let line_numbers = LineNumbers::new(&module.code);
5365 let mut edits = TextEdits::new(&line_numbers);
5366 edits.insert(insert_at, new_text);
5367 Some((url_from_path(module.input_path.as_str())?, edits.edits))
5368 }
5369 }
5370
5371 fn try_save_variant_to_generate(
5372 &mut self,
5373 function_name_location: SrcSpan,
5374 function_type: &Arc<Type>,
5375 given_arguments: Option<Arguments<'a>>,
5376 ) {
5377 let variant_to_generate =
5378 self.variant_to_generate(function_name_location, function_type, given_arguments);
5379 if variant_to_generate.is_some() {
5380 self.variant_to_generate = variant_to_generate;
5381 }
5382 }
5383
5384 fn variant_to_generate(
5385 &mut self,
5386 function_name_location: SrcSpan,
5387 type_: &Arc<Type>,
5388 given_arguments: Option<Arguments<'a>>,
5389 ) -> Option<VariantToGenerate<'a>> {
5390 let name_range = function_name_location.start as usize..function_name_location.end as usize;
5391 let name = self.module.code.get(name_range).expect("valid code range");
5392 if !is_valid_uppercase_name(name) {
5393 return None;
5394 }
5395
5396 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
5397 (Some(result), _) => result,
5398 (None, Some(arguments)) => (arguments.types(), type_.clone()),
5399 (None, None) => (vec![], type_.clone()),
5400 };
5401
5402 let (module_name, type_name, _) = custom_type.named_type_information()?;
5403 let module = self.compiler.modules.get(&module_name)?;
5404 let (end_position, type_braces) =
5405 (module.ast.definitions.iter()).find_map(|definition| match definition {
5406 ast::Definition::CustomType(custom_type) if custom_type.name == type_name => {
5407 // If there's already a variant with this name then we definitely
5408 // don't want to generate a new variant with the same name!
5409 let variant_with_this_name_already_exists = custom_type
5410 .constructors
5411 .iter()
5412 .map(|constructor| &constructor.name)
5413 .any(|existing_constructor_name| existing_constructor_name == name);
5414 if variant_with_this_name_already_exists {
5415 return None;
5416 }
5417 let type_braces = if custom_type.end_position == custom_type.location.end {
5418 TypeBraces::NoBraces
5419 } else {
5420 TypeBraces::HasBraces
5421 };
5422 Some((custom_type.end_position, type_braces))
5423 }
5424 _ => None,
5425 })?;
5426
5427 Some(VariantToGenerate {
5428 name,
5429 arguments_types,
5430 given_arguments,
5431 module_name,
5432 end_position,
5433 type_braces,
5434 })
5435 }
5436}
5437
5438impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO>
5439where
5440 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone,
5441{
5442 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5443 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
5444 if within(self.params.range, invalid_range) {
5445 self.try_save_variant_to_generate(*location, type_, None);
5446 }
5447 ast::visit::visit_typed_expr_invalid(self, location, type_);
5448 }
5449
5450 fn visit_typed_expr_call(
5451 &mut self,
5452 location: &'ast SrcSpan,
5453 type_: &'ast Arc<Type>,
5454 fun: &'ast TypedExpr,
5455 args: &'ast [TypedCallArg],
5456 ) {
5457 // If the function being called is invalid we need to generate a
5458 // function that has the proper labels.
5459 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
5460 if within(self.params.range, fun_range) && fun.is_invalid() {
5461 if labels_are_correct(args) {
5462 self.try_save_variant_to_generate(
5463 fun.location(),
5464 &fun.type_(),
5465 Some(Arguments::Expressions(args)),
5466 );
5467 }
5468 } else {
5469 ast::visit::visit_typed_expr_call(self, location, type_, fun, args);
5470 }
5471 }
5472
5473 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5474 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
5475 if within(self.params.range, invalid_range) {
5476 self.try_save_variant_to_generate(*location, type_, None);
5477 }
5478 ast::visit::visit_typed_pattern_invalid(self, location, type_);
5479 }
5480
5481 fn visit_typed_pattern_constructor(
5482 &mut self,
5483 location: &'ast SrcSpan,
5484 name_location: &'ast SrcSpan,
5485 name: &'ast EcoString,
5486 arguments: &'ast Vec<CallArg<TypedPattern>>,
5487 module: &'ast Option<(EcoString, SrcSpan)>,
5488 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
5489 spread: &'ast Option<SrcSpan>,
5490 type_: &'ast Arc<Type>,
5491 ) {
5492 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
5493 // TODO)) Solo se il pattern non è valido!!!!!
5494 if within(self.params.range, pattern_range) {
5495 if labels_are_correct(arguments) {
5496 self.try_save_variant_to_generate(
5497 *name_location,
5498 type_,
5499 Some(Arguments::Patterns(arguments)),
5500 );
5501 }
5502 } else {
5503 ast::visit::visit_typed_pattern_constructor(
5504 self,
5505 location,
5506 name_location,
5507 name,
5508 arguments,
5509 module,
5510 constructor,
5511 spread,
5512 type_,
5513 );
5514 }
5515 }
5516}
5517
5518#[must_use]
5519/// Checks the labels in the given arguments are correct: that is there's no
5520/// duplicate labels and all labelled arguments come after the unlabelled ones.
5521fn labels_are_correct<A>(args: &[CallArg<A>]) -> bool {
5522 let mut labelled_arg_found = false;
5523 let mut used_labels = HashSet::new();
5524
5525 for arg in args {
5526 match &arg.label {
5527 // Labels are invalid if there's duplicate ones or if an unlabelled
5528 // argument comes after a labelled one.
5529 Some(label) if used_labels.contains(label) => return false,
5530 None if labelled_arg_found => return false,
5531 // Otherwise we just add the label to the used ones.
5532 Some(label) => {
5533 labelled_arg_found = true;
5534 let _ = used_labels.insert(label);
5535 }
5536 None => {}
5537 }
5538 }
5539
5540 true
5541}
5542
5543struct NameGenerator {
5544 used_names: HashSet<EcoString>,
5545}
5546
5547impl NameGenerator {
5548 pub fn new() -> Self {
5549 NameGenerator {
5550 used_names: HashSet::new(),
5551 }
5552 }
5553
5554 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
5555 let mut i = 1;
5556 let mut candidate_name = base.clone();
5557
5558 loop {
5559 if self.used_names.contains(&candidate_name) {
5560 i += 1;
5561 candidate_name = eco_format!("{base}_{i}");
5562 } else {
5563 let _ = self.used_names.insert(candidate_name.clone());
5564 return candidate_name;
5565 }
5566 }
5567 }
5568
5569 /// Given an argument type and the actual call argument (if any), comes up
5570 /// with a label and a name to use for that argument when generating a
5571 /// function.
5572 ///
5573 pub fn generate_label_and_name(
5574 &mut self,
5575 call_argument: Option<&CallArg<TypedExpr>>,
5576 argument_type: &Arc<Type>,
5577 ) -> (Option<EcoString>, EcoString) {
5578 let label = call_argument.and_then(|argument| argument.label.clone());
5579 let argument_name = call_argument
5580 // We always favour a name derived from the expression (for example if
5581 // the argument is a variable)
5582 .and_then(|argument| self.generate_name_from_expression(&argument.value))
5583 // If we don't have such a name and there's a label we use that name.
5584 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
5585 // If all else fails we fallback to using a name derived from the
5586 // argument's type.
5587 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
5588
5589 (label, argument_name)
5590 }
5591
5592 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
5593 let type_to_base_name = |type_: &Arc<Type>| {
5594 type_
5595 .named_type_name()
5596 .map(|(_type_module, type_name)| to_snake_case(&type_name))
5597 .filter(|name| is_valid_lowercase_name(name))
5598 .unwrap_or(EcoString::from("value"))
5599 };
5600
5601 let base_name = match type_.list_type() {
5602 None => type_to_base_name(type_),
5603 // If we're coming up with a name for a list we want to use the
5604 // plural form for the name of the inner type. For example:
5605 // `List(Pokemon)` should generate `pokemons`.
5606 Some(inner_type) => {
5607 let base_name = type_to_base_name(&inner_type);
5608 // If the inner type name already ends in "s" we leave it as it
5609 // is, or it would look funny.
5610 if base_name.ends_with('s') {
5611 base_name
5612 } else {
5613 eco_format!("{base_name}s")
5614 }
5615 }
5616 };
5617
5618 self.rename_to_avoid_shadowing(base_name)
5619 }
5620
5621 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
5622 match expression {
5623 // If the argument is a record, we can't use it as an argument name.
5624 // Similarly, we don't want to base the variable name off a
5625 // compiler-generated variable like `_pipe`.
5626 TypedExpr::Var {
5627 name, constructor, ..
5628 } if !constructor.variant.is_record()
5629 && !constructor.variant.is_generated_variable() =>
5630 {
5631 Some(self.rename_to_avoid_shadowing(name.clone()))
5632 }
5633 _ => None,
5634 }
5635 }
5636
5637 pub fn add_used_name(&mut self, name: EcoString) {
5638 let _ = self.used_names.insert(name);
5639 }
5640
5641 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
5642 field_map
5643 .fields
5644 .iter()
5645 .for_each(|(label, _)| self.add_used_name(label.clone()));
5646 }
5647
5648 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
5649 variable_names
5650 .names
5651 .iter()
5652 .for_each(|name| self.add_used_name(name.clone()));
5653 }
5654}
5655
5656#[must_use]
5657fn is_valid_lowercase_name(name: &str) -> bool {
5658 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
5659 return false;
5660 }
5661
5662 for char in name.chars() {
5663 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
5664 if !is_valid_char {
5665 return false;
5666 }
5667 }
5668
5669 str_to_keyword(name).is_none()
5670}
5671
5672#[must_use]
5673fn is_valid_uppercase_name(name: &str) -> bool {
5674 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
5675 return false;
5676 }
5677
5678 for char in name.chars() {
5679 if !char.is_ascii_alphanumeric() {
5680 return false;
5681 }
5682 }
5683
5684 true
5685}
5686
5687/// Code action to rewrite a single-step pipeline into a regular function call.
5688/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
5689///
5690pub struct ConvertToFunctionCall<'a> {
5691 module: &'a Module,
5692 params: &'a CodeActionParams,
5693 edits: TextEdits<'a>,
5694 locations: Option<ConvertToFunctionCallLocations>,
5695}
5696
5697/// All the different locations the "Convert to function call" code action needs
5698/// to properly rewrite a pipeline into a function call.
5699///
5700struct ConvertToFunctionCallLocations {
5701 /// This is the location of the value being piped into a call.
5702 ///
5703 /// ```gleam
5704 /// [1, 2, 3] |> list.length
5705 /// // ^^^^^^^^^ This one here
5706 /// ```
5707 ///
5708 first_value: SrcSpan,
5709
5710 /// This is the location of the call the value is being piped into.
5711 ///
5712 /// ```gleam
5713 /// [1, 2, 3] |> list.length
5714 /// // ^^^^^^^^^^^ This one here
5715 /// ```
5716 ///
5717 call: SrcSpan,
5718
5719 /// This is the kind of desugaring that is taking place when piping
5720 /// `first_value` into `call`.
5721 ///
5722 call_kind: PipelineAssignmentKind,
5723}
5724
5725impl<'a> ConvertToFunctionCall<'a> {
5726 pub fn new(
5727 module: &'a Module,
5728 line_numbers: &'a LineNumbers,
5729 params: &'a CodeActionParams,
5730 ) -> Self {
5731 Self {
5732 module,
5733 params,
5734 edits: TextEdits::new(line_numbers),
5735 locations: None,
5736 }
5737 }
5738
5739 pub fn code_actions(mut self) -> Vec<CodeAction> {
5740 self.visit_typed_module(&self.module.ast);
5741
5742 // If we couldn't find a pipeline to rewrite we don't return any action.
5743 let Some(ConvertToFunctionCallLocations {
5744 first_value,
5745 call,
5746 call_kind,
5747 }) = self.locations
5748 else {
5749 return vec![];
5750 };
5751
5752 // We first delete the first value of the pipeline as it's going to be
5753 // inlined as a function call argument.
5754 self.edits.delete(SrcSpan {
5755 start: first_value.start,
5756 end: call.start,
5757 });
5758
5759 // Then we have to insert the piped value in the appropriate position.
5760 // This will change based on how the pipeline is being desugared, we
5761 // know this thanks to the `call_kind`
5762 let first_value_text = self
5763 .module
5764 .code
5765 .get(first_value.start as usize..first_value.end as usize)
5766 .expect("invalid code span")
5767 .to_string();
5768
5769 match call_kind {
5770 // When piping into a `_` we replace the hole with the piped value:
5771 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
5772 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
5773
5774 // When piping is desguared as a function call we need to add the
5775 // missing parentheses:
5776 // `[1, 2] |> length` becomes `length([1, 2])`
5777 PipelineAssignmentKind::FunctionCall => {
5778 self.edits.insert(call.end, format!("({first_value_text})"))
5779 }
5780
5781 // When the piped value is inserted as the first argument there's two
5782 // possible scenarios:
5783 // - there's a second argument as well: in that case we insert it
5784 // before the second arg and add a comma
5785 // - there's no other argument: `[1, 2] |> length()` becomes
5786 // `length([1, 2])`, we insert the value between the empty
5787 // parentheses
5788 PipelineAssignmentKind::FirstArgument {
5789 second_argument: Some(SrcSpan { start, .. }),
5790 } => self.edits.insert(start, format!("{first_value_text}, ")),
5791 PipelineAssignmentKind::FirstArgument {
5792 second_argument: None,
5793 } => self.edits.insert(call.end - 1, first_value_text),
5794
5795 // When the value is piped into an echo, to rewrite the pipeline we
5796 // have to insert the value after the `echo` with no parentheses:
5797 // `a |> echo` is rewritten as `echo a`.
5798 PipelineAssignmentKind::Echo => {
5799 self.edits.insert(call.end, format!(" {first_value_text}"))
5800 }
5801 }
5802
5803 let mut action = Vec::with_capacity(1);
5804 CodeActionBuilder::new("Convert to function call")
5805 .kind(CodeActionKind::REFACTOR_REWRITE)
5806 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5807 .preferred(false)
5808 .push_to(&mut action);
5809 action
5810 }
5811}
5812
5813impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
5814 fn visit_typed_expr_pipeline(
5815 &mut self,
5816 location: &'ast SrcSpan,
5817 first_value: &'ast TypedPipelineAssignment,
5818 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
5819 finally: &'ast TypedExpr,
5820 finally_kind: &'ast PipelineAssignmentKind,
5821 ) {
5822 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
5823 if within(self.params.range, pipeline_range) {
5824 // We will always desugar the pipeline's first step. If there's no
5825 // intermediate assignment it means we're dealing with a single step
5826 // pipeline and the call is `finally`.
5827 let (call, call_kind) = assignments
5828 .first()
5829 .map(|(call, kind)| (call.location, *kind))
5830 .unwrap_or_else(|| (finally.location(), *finally_kind));
5831
5832 self.locations = Some(ConvertToFunctionCallLocations {
5833 first_value: first_value.location,
5834 call,
5835 call_kind,
5836 });
5837
5838 ast::visit::visit_typed_expr_pipeline(
5839 self,
5840 location,
5841 first_value,
5842 assignments,
5843 finally,
5844 finally_kind,
5845 );
5846 }
5847 }
5848}
5849
5850/// Builder for code action to inline a variable.
5851///
5852pub struct InlineVariable<'a> {
5853 module: &'a Module,
5854 params: &'a CodeActionParams,
5855 edits: TextEdits<'a>,
5856 actions: Vec<CodeAction>,
5857}
5858
5859impl<'a> InlineVariable<'a> {
5860 pub fn new(
5861 module: &'a Module,
5862 line_numbers: &'a LineNumbers,
5863 params: &'a CodeActionParams,
5864 ) -> Self {
5865 Self {
5866 module,
5867 params,
5868 edits: TextEdits::new(line_numbers),
5869 actions: Vec::new(),
5870 }
5871 }
5872
5873 pub fn code_actions(mut self) -> Vec<CodeAction> {
5874 self.visit_typed_module(&self.module.ast);
5875
5876 self.actions
5877 }
5878
5879 fn maybe_inline(&mut self, location: SrcSpan) {
5880 let variable_location =
5881 match find_variable_references(&self.module.ast, location).as_slice() {
5882 [only_reference] => only_reference.location,
5883 _ => return,
5884 };
5885
5886 let Some(ast::Statement::Assignment(assignment)) =
5887 self.module.ast.find_statement(location.start)
5888 else {
5889 return;
5890 };
5891
5892 // If the assignment does not simple bind a variable, for example:
5893 // ```gleam
5894 // let #(first, second, third)
5895 // io.println(first)
5896 // // ^ Inline here
5897 // ```
5898 // We can't inline it.
5899 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
5900 return;
5901 }
5902
5903 // If the assignment was generated by the compiler, it doesn't have a
5904 // syntactical representation, so we can't inline it.
5905 if matches!(assignment.kind, AssignmentKind::Generated) {
5906 return;
5907 }
5908
5909 let value_location = assignment.value.location();
5910 let value = self
5911 .module
5912 .code
5913 .get(value_location.start as usize..value_location.end as usize)
5914 .expect("Span is valid");
5915
5916 self.edits.replace(variable_location, value.into());
5917
5918 let mut location = assignment.location;
5919
5920 let mut chars = self.module.code[location.end as usize..].chars();
5921 // Delete any whitespace after the removed statement
5922 while chars.next().is_some_and(char::is_whitespace) {
5923 location.end += 1;
5924 }
5925
5926 self.edits.delete(location);
5927
5928 CodeActionBuilder::new("Inline variable")
5929 .kind(CodeActionKind::REFACTOR_INLINE)
5930 .changes(
5931 self.params.text_document.uri.clone(),
5932 std::mem::take(&mut self.edits.edits),
5933 )
5934 .preferred(false)
5935 .push_to(&mut self.actions);
5936 }
5937}
5938
5939impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
5940 fn visit_typed_expr_var(
5941 &mut self,
5942 location: &'ast SrcSpan,
5943 constructor: &'ast ValueConstructor,
5944 _name: &'ast EcoString,
5945 ) {
5946 let range = self.edits.src_span_to_lsp_range(*location);
5947
5948 if !overlaps(self.params.range, range) {
5949 return;
5950 }
5951
5952 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
5953 &constructor.variant
5954 else {
5955 return;
5956 };
5957
5958 // We can only inline it if it comes from a regular variable pattern,
5959 // not a complex pattern or generated assignment.
5960 match origin {
5961 VariableOrigin::Variable(_) => {}
5962 VariableOrigin::LabelShorthand(_)
5963 | VariableOrigin::AssignmentPattern
5964 | VariableOrigin::Generated => return,
5965 }
5966
5967 self.maybe_inline(*location);
5968 }
5969
5970 fn visit_typed_pattern_variable(
5971 &mut self,
5972 location: &'ast SrcSpan,
5973 _name: &'ast EcoString,
5974 _type: &'ast Arc<Type>,
5975 origin: &'ast VariableOrigin,
5976 ) {
5977 // We can only inline it if it is a regular variable pattern
5978 match origin {
5979 VariableOrigin::Variable(_) => {}
5980 VariableOrigin::LabelShorthand(_)
5981 | VariableOrigin::AssignmentPattern
5982 | VariableOrigin::Generated => return,
5983 }
5984
5985 let range = self.edits.src_span_to_lsp_range(*location);
5986
5987 if !overlaps(self.params.range, range) {
5988 return;
5989 }
5990
5991 self.maybe_inline(*location);
5992 }
5993}
5994
5995/// Builder for the "convert to pipe" code action.
5996///
5997/// ```gleam
5998/// pub fn main() {
5999/// wibble(wobble, woo)
6000/// // ^ [convert to pipe]
6001/// }
6002/// ```
6003///
6004/// Will turn the code into the following pipeline:
6005///
6006/// ```gleam
6007/// pub fn main() {
6008/// wobble |> wibble(woo)
6009/// }
6010/// ```
6011///
6012pub struct ConvertToPipe<'a> {
6013 module: &'a Module,
6014 params: &'a CodeActionParams,
6015 edits: TextEdits<'a>,
6016 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
6017 /// this will be true if we're visiting the call on the right hand side of a
6018 /// use expression. So we can skip it and not try to turn it into a
6019 /// function.
6020 visiting_use_call: bool,
6021}
6022
6023/// Holds all the data needed by the "convert to pipe" code action to properly
6024/// rewrite a call into a pipe. Here's what each span means:
6025///
6026/// ```gleam
6027/// wibble(wobb|le, woo)
6028/// // ^^^^^^^^^^^^^^^^^^^^ call
6029/// // ^^^^^^ called
6030/// // ^^^^^^^ arg
6031/// // ^^^ next arg
6032/// ```
6033///
6034/// In this example `position` is 0, since the cursor is over the first
6035/// argument.
6036///
6037pub struct ConvertToPipeArg<'a> {
6038 /// The span of the called function.
6039 called: SrcSpan,
6040 /// The span of the entire function call.
6041 call: SrcSpan,
6042 /// The position (0-based) of the argument.
6043 position: usize,
6044 /// The argument we have to pipe.
6045 arg: &'a TypedCallArg,
6046 /// The span of the argument following the one we have to pipe, if there's
6047 /// any.
6048 next_arg: Option<SrcSpan>,
6049}
6050
6051impl<'a> ConvertToPipe<'a> {
6052 pub fn new(
6053 module: &'a Module,
6054 line_numbers: &'a LineNumbers,
6055 params: &'a CodeActionParams,
6056 ) -> Self {
6057 Self {
6058 module,
6059 params,
6060 edits: TextEdits::new(line_numbers),
6061 visiting_use_call: false,
6062 argument_to_pipe: None,
6063 }
6064 }
6065
6066 pub fn code_actions(mut self) -> Vec<CodeAction> {
6067 self.visit_typed_module(&self.module.ast);
6068
6069 let Some(ConvertToPipeArg {
6070 called,
6071 call,
6072 position,
6073 arg,
6074 next_arg,
6075 }) = self.argument_to_pipe
6076 else {
6077 return vec![];
6078 };
6079
6080 let arg_range = if arg.uses_label_shorthand() {
6081 arg.location.start as usize..arg.location.end as usize - 1
6082 } else if arg.label.is_some() {
6083 let value = arg.value.location();
6084 value.start as usize..value.end as usize
6085 } else {
6086 arg.location.start as usize..arg.location.end as usize
6087 };
6088
6089 let arg_text = self.module.code.get(arg_range).expect("invalid srcspan");
6090 let arg_text = match arg.value {
6091 // If the expression being piped is a binary operation with
6092 // precedence lower than pipes then we have to wrap it in curly
6093 // braces to not mess with the order of operations.
6094 TypedExpr::BinOp { name, .. } if name.precedence() < PIPE_PRECEDENCE => {
6095 &format!("{{ {arg_text} }}")
6096 }
6097 _ => arg_text,
6098 };
6099
6100 match next_arg {
6101 // When extracting an argument we never want to remove any explicit
6102 // label that was written down, so in case it is labelled (be it a
6103 // shorthand or not) we'll always replace the value with a `_`
6104 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
6105 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
6106
6107 // Now we can deal with unlabelled arguments:
6108 // If we're removing the first argument and there's other arguments
6109 // after it, we need to delete the comma that was separating the
6110 // two.
6111 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
6112 start: arg.location.start,
6113 end: next_arg.start,
6114 }),
6115 // Otherwise, if we're deleting the first argument and there's
6116 // no other arguments following it, we remove the call's
6117 // parentheses.
6118 None if position == 0 => self.edits.delete(SrcSpan {
6119 start: called.end,
6120 end: call.end,
6121 }),
6122 // In all other cases we're piping something that is not the first
6123 // argument so we just replace it with an `_`.
6124 _ => self.edits.replace(arg.location, "_".into()),
6125 };
6126
6127 // Finally we can add the argument that was removed as the first step
6128 // of the newly defined pipeline.
6129 self.edits.insert(call.start, format!("{arg_text} |> "));
6130
6131 let mut action = Vec::with_capacity(1);
6132 CodeActionBuilder::new("Convert to pipe")
6133 .kind(CodeActionKind::REFACTOR_REWRITE)
6134 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6135 .preferred(false)
6136 .push_to(&mut action);
6137 action
6138 }
6139}
6140
6141impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
6142 fn visit_typed_expr_call(
6143 &mut self,
6144 location: &'ast SrcSpan,
6145 _type_: &'ast Arc<Type>,
6146 fun: &'ast TypedExpr,
6147 args: &'ast [TypedCallArg],
6148 ) {
6149 if args.iter().any(|arg| arg.is_capture_hole()) {
6150 return;
6151 }
6152
6153 // If we're visiting the typed function produced by typing a use, we
6154 // skip the thing itself and only visit its arguments and called
6155 // function, that is the body of the use.
6156 if self.visiting_use_call {
6157 self.visiting_use_call = false;
6158 ast::visit::visit_typed_expr(self, fun);
6159 args.iter()
6160 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
6161 return;
6162 }
6163
6164 // We only visit a call if the cursor is somewhere within its location,
6165 // otherwise we skip it entirely.
6166 let call_range = self.edits.src_span_to_lsp_range(*location);
6167 if !within(self.params.range, call_range) {
6168 return;
6169 }
6170
6171 // If the cursor is over any of the arguments then we'll use that as
6172 // the one to extract.
6173 // Otherwise the cursor must be over the called function, in that case
6174 // we extract the first argument (if there's one):
6175 //
6176 // ```gleam
6177 // wibble(wobble, woo)
6178 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
6179 // // ^^^ pipe the second argument if I'm here
6180 // ```
6181 let argument_to_pipe = args
6182 .iter()
6183 .enumerate()
6184 .find_map(|(position, arg)| {
6185 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
6186 if within(self.params.range, arg_range) {
6187 Some((position, arg))
6188 } else {
6189 None
6190 }
6191 })
6192 .or_else(|| args.first().map(|arg| (0, arg)));
6193
6194 // If we're not hovering over any of the arguments _or_ there's no
6195 // argument to extract at all we just return, there's nothing we can do
6196 // on this call or any of its arguments (since we've determined the
6197 // cursor is not over any of those).
6198 let Some((position, arg)) = argument_to_pipe else {
6199 return;
6200 };
6201
6202 self.argument_to_pipe = Some(ConvertToPipeArg {
6203 called: fun.location(),
6204 call: *location,
6205 position,
6206 arg,
6207 next_arg: args.get(position + 1).map(|arg| arg.location),
6208 })
6209 }
6210
6211 fn visit_typed_expr_pipeline(
6212 &mut self,
6213 _location: &'ast SrcSpan,
6214 first_value: &'ast TypedPipelineAssignment,
6215 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6216 _finally: &'ast TypedExpr,
6217 _finally_kind: &'ast PipelineAssignmentKind,
6218 ) {
6219 // We can only apply the action on the first step of a pipeline, so we
6220 // visit just that one and skip all the others.
6221 ast::visit::visit_typed_pipeline_assignment(self, first_value);
6222 }
6223
6224 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
6225 self.visiting_use_call = true;
6226 ast::visit::visit_typed_use(self, use_);
6227 }
6228}
6229
6230/// Code action to interpolate a string. If the cursor is inside the string
6231/// (not selecting anything) the language server will offer to split it:
6232///
6233/// ```gleam
6234/// "wibble | wobble"
6235/// // ^ [Split string]
6236/// // Will produce the following
6237/// "wibble " <> todo <> " wobble"
6238/// ```
6239///
6240/// If the cursor is selecting an entire valid gleam name, then the language
6241/// server will offer to interpolate it as a variable:
6242///
6243/// ```gleam
6244/// "wibble wobble woo"
6245/// // ^^^^^^ [Interpolate variable]
6246/// // Will produce the following
6247/// "wibble " <> wobble <> " woo"
6248/// ```
6249///
6250/// > Note: the cursor won't end up right after the inserted variable/todo.
6251/// > that's a bit annoying, but in a future LSP version we will be able to
6252/// > isnert tab stops to allow one to jump to the newly added variable/todo.
6253///
6254pub struct InterpolateString<'a> {
6255 module: &'a Module,
6256 params: &'a CodeActionParams,
6257 edits: TextEdits<'a>,
6258 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
6259 string_literal_position: StringLiteralPosition,
6260}
6261
6262#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6263pub enum StringLiteralPosition {
6264 FirstPipelineStep,
6265 Other,
6266}
6267
6268#[derive(Clone, Copy)]
6269enum StringInterpolation {
6270 InterpolateValue { value_location: SrcSpan },
6271 SplitString { split_at: u32 },
6272}
6273
6274impl<'a> InterpolateString<'a> {
6275 pub fn new(
6276 module: &'a Module,
6277 line_numbers: &'a LineNumbers,
6278 params: &'a CodeActionParams,
6279 ) -> Self {
6280 Self {
6281 module,
6282 params,
6283 edits: TextEdits::new(line_numbers),
6284 string_interpolation: None,
6285 string_literal_position: StringLiteralPosition::Other,
6286 }
6287 }
6288
6289 pub fn code_actions(mut self) -> Vec<CodeAction> {
6290 self.visit_typed_module(&self.module.ast);
6291
6292 let Some((string_location, interpolation)) = self.string_interpolation else {
6293 return vec![];
6294 };
6295
6296 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
6297 self.edits.insert(string_location.start, "{ ".into());
6298 }
6299
6300 match interpolation {
6301 StringInterpolation::InterpolateValue { value_location } => {
6302 let name = self
6303 .module
6304 .code
6305 .get(value_location.start as usize..value_location.end as usize)
6306 .expect("invalid value range");
6307
6308 if is_valid_lowercase_name(name) {
6309 self.edits
6310 .insert(value_location.start, format!("\" <> {name} <> \""));
6311 self.edits.delete(value_location);
6312 } else if self.can_split_string_at(value_location.end) {
6313 // If the string is not a valid name we just try and split
6314 // the string at the end of the selection.
6315 self.edits
6316 .insert(value_location.end, "\" <> todo <> \"".into());
6317 } else {
6318 // Otherwise there's no meaningful action we can do.
6319 return vec![];
6320 }
6321 }
6322
6323 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
6324 self.edits.insert(split_at, "\" <> todo <> \"".into());
6325 }
6326
6327 StringInterpolation::SplitString { .. } => return vec![],
6328 };
6329
6330 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
6331 self.edits.insert(string_location.end, " }".into());
6332 }
6333
6334 let mut action = Vec::with_capacity(1);
6335 CodeActionBuilder::new("Interpolate string")
6336 .kind(CodeActionKind::REFACTOR_REWRITE)
6337 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6338 .preferred(false)
6339 .push_to(&mut action);
6340 action
6341 }
6342
6343 fn can_split_string_at(&self, at: u32) -> bool {
6344 self.string_interpolation
6345 .is_some_and(|(string_location, _)| {
6346 !(at <= string_location.start + 1 || at >= string_location.end - 1)
6347 })
6348 }
6349
6350 fn visit_literal_string(
6351 &mut self,
6352 string_location: SrcSpan,
6353 string_position: StringLiteralPosition,
6354 ) {
6355 // We can only interpolate/split a string if the cursor is somewhere
6356 // within its location, otherwise we skip it.
6357 let string_range = self.edits.src_span_to_lsp_range(string_location);
6358 if !within(self.params.range, string_range) {
6359 return;
6360 }
6361
6362 let selection @ SrcSpan { start, end } =
6363 self.edits.lsp_range_to_src_span(self.params.range);
6364
6365 let interpolation = if start == end {
6366 StringInterpolation::SplitString { split_at: start }
6367 } else {
6368 StringInterpolation::InterpolateValue {
6369 value_location: selection,
6370 }
6371 };
6372 self.string_interpolation = Some((string_location, interpolation));
6373 self.string_literal_position = string_position;
6374 }
6375}
6376
6377impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
6378 fn visit_typed_expr_string(
6379 &mut self,
6380 location: &'ast SrcSpan,
6381 _type_: &'ast Arc<Type>,
6382 _value: &'ast EcoString,
6383 ) {
6384 self.visit_literal_string(*location, StringLiteralPosition::Other);
6385 }
6386
6387 fn visit_typed_expr_pipeline(
6388 &mut self,
6389 _location: &'ast SrcSpan,
6390 first_value: &'ast TypedPipelineAssignment,
6391 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6392 finally: &'ast TypedExpr,
6393 _finally_kind: &'ast PipelineAssignmentKind,
6394 ) {
6395 if first_value.value.is_literal_string() {
6396 self.visit_literal_string(
6397 first_value.location,
6398 StringLiteralPosition::FirstPipelineStep,
6399 );
6400 } else {
6401 ast::visit::visit_typed_pipeline_assignment(self, first_value);
6402 }
6403
6404 assignments
6405 .iter()
6406 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
6407 self.visit_typed_expr(finally);
6408 }
6409}
6410
6411/// Code action to replace a `..` in a pattern with all the missing fields that
6412/// have not been explicitly provided; labelled ones are introduced with the
6413/// shorthand syntax.
6414///
6415/// ```gleam
6416/// pub type Pokemon {
6417/// Pokemon(Int, name: String, moves: List(String))
6418/// }
6419///
6420/// pub fn main() {
6421/// let Pokemon(..) = todo
6422/// // ^^ Cursor over the spread
6423/// }
6424/// ```
6425/// Would become
6426/// ```gleam
6427/// pub fn main() {
6428/// let Pokemon(int, name:, moves:) = todo
6429/// }
6430///
6431pub struct FillUnusedFields<'a> {
6432 module: &'a Module,
6433 params: &'a CodeActionParams,
6434 edits: TextEdits<'a>,
6435 data: Option<FillUnusedFieldsData>,
6436}
6437
6438pub struct FillUnusedFieldsData {
6439 /// All the missing positional and labelled fields.
6440 positional: Vec<Arc<Type>>,
6441 labelled: Vec<(EcoString, Arc<Type>)>,
6442 /// We need this in order to tell where the missing positional arguments
6443 /// should be inserted.
6444 first_labelled_argument_start: Option<u32>,
6445 /// The end of the final argument before the spread, if there's any.
6446 /// We'll use this to delete everything that comes after the final argument,
6447 /// after adding all the ignored fields.
6448 last_argument_end: Option<u32>,
6449 spread_location: SrcSpan,
6450}
6451
6452impl<'a> FillUnusedFields<'a> {
6453 pub fn new(
6454 module: &'a Module,
6455 line_numbers: &'a LineNumbers,
6456 params: &'a CodeActionParams,
6457 ) -> Self {
6458 Self {
6459 module,
6460 params,
6461 edits: TextEdits::new(line_numbers),
6462 data: None,
6463 }
6464 }
6465
6466 pub fn code_actions(mut self) -> Vec<CodeAction> {
6467 self.visit_typed_module(&self.module.ast);
6468
6469 let Some(FillUnusedFieldsData {
6470 positional,
6471 labelled,
6472 first_labelled_argument_start,
6473 last_argument_end,
6474 spread_location,
6475 }) = self.data
6476 else {
6477 return vec![];
6478 };
6479
6480 // Do not suggest this code action if there's no ignored fields at all.
6481 if positional.is_empty() && labelled.is_empty() {
6482 return vec![];
6483 };
6484
6485 // We add all the missing positional arguments before the first
6486 // labelled one (and so after all the already existing positional ones).
6487 if !positional.is_empty() {
6488 // We want to make sure that all positional args will have a name
6489 // that's different from any label. So we add those as already used
6490 // names.
6491 let mut names = NameGenerator::new();
6492 for (label, _) in labelled.iter() {
6493 names.add_used_name(label.clone());
6494 }
6495
6496 let positional_args = positional
6497 .iter()
6498 .map(|type_| names.generate_name_from_type(type_))
6499 .join(", ");
6500 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
6501
6502 // The positional arguments are going to be followed by some other
6503 // arguments if there's some already existing labelled args
6504 // (`last_argument_end.is_some`), of if we're adding those labelled args
6505 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
6506 // final positional argument we're adding to separate it from the ones that
6507 // are going to come after.
6508 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
6509 let positional_args = if has_arguments_after {
6510 format!("{positional_args}, ")
6511 } else {
6512 positional_args
6513 };
6514
6515 self.edits.insert(insert_at, positional_args);
6516 }
6517
6518 if !labelled.is_empty() {
6519 // If there's labelled arguments to add, we replace the existing spread
6520 // with the arguments to be added. This way commas and all should already
6521 // be correct.
6522 let labelled_args = labelled
6523 .iter()
6524 .map(|(label, _)| format!("{label}:"))
6525 .join(", ");
6526 self.edits.replace(spread_location, labelled_args);
6527 } else if let Some(delete_start) = last_argument_end {
6528 // However, if there's no labelled arguments to insert we still need
6529 // to delete the entire spread: we start deleting from the end of the
6530 // final argument, if there's one.
6531 // This way we also get rid of any comma separating the last argument
6532 // and the spread to be removed.
6533 self.edits
6534 .delete(SrcSpan::new(delete_start, spread_location.end))
6535 } else {
6536 // Otherwise we just delete the spread.
6537 self.edits.delete(spread_location)
6538 }
6539
6540 let mut action = Vec::with_capacity(1);
6541 CodeActionBuilder::new("Fill unused fields")
6542 .kind(CodeActionKind::REFACTOR_REWRITE)
6543 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6544 .preferred(false)
6545 .push_to(&mut action);
6546 action
6547 }
6548}
6549
6550impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
6551 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
6552 // We can only interpolate/split a string if the cursor is somewhere
6553 // within its location, otherwise we skip it.
6554 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
6555 if !within(self.params.range, pattern_range) {
6556 return;
6557 }
6558
6559 if let TypedPattern::Constructor {
6560 arguments,
6561 spread: Some(spread_location),
6562 ..
6563 } = pattern
6564 {
6565 if let Some(PatternUnusedArguments {
6566 positional,
6567 labelled,
6568 }) = pattern.unused_arguments()
6569 {
6570 // If there's any unused argument that's being ignored we want to
6571 // suggest the code action.
6572 let first_labelled_argument_start = arguments
6573 .iter()
6574 .find(|arg| !arg.is_implicit() && arg.label.is_some())
6575 .map(|arg| arg.location.start);
6576
6577 let last_argument_end = arguments
6578 .iter()
6579 .filter(|arg| !arg.is_implicit())
6580 .next_back()
6581 .map(|arg| arg.location.end);
6582
6583 self.data = Some(FillUnusedFieldsData {
6584 positional,
6585 labelled,
6586 first_labelled_argument_start,
6587 last_argument_end,
6588 spread_location: *spread_location,
6589 });
6590 };
6591 }
6592
6593 ast::visit::visit_typed_pattern(self, pattern);
6594 }
6595}
6596
6597/// Code action to remove an echo.
6598///
6599pub struct RemoveEchos<'a> {
6600 module: &'a Module,
6601 params: &'a CodeActionParams,
6602 edits: TextEdits<'a>,
6603 is_hovering_echo: bool,
6604 echo_spans_to_delete: Vec<SrcSpan>,
6605 // We need to keep a reference to the two latest pipeline assignments we
6606 // run into to properly delete an echo that's inside a pipeline.
6607 latest_pipe_step: Option<SrcSpan>,
6608 second_to_latest_pipe_step: Option<SrcSpan>,
6609}
6610
6611impl<'a> RemoveEchos<'a> {
6612 pub fn new(
6613 module: &'a Module,
6614 line_numbers: &'a LineNumbers,
6615 params: &'a CodeActionParams,
6616 ) -> Self {
6617 Self {
6618 module,
6619 params,
6620 edits: TextEdits::new(line_numbers),
6621 is_hovering_echo: false,
6622 echo_spans_to_delete: vec![],
6623 latest_pipe_step: None,
6624 second_to_latest_pipe_step: None,
6625 }
6626 }
6627
6628 pub fn code_actions(mut self) -> Vec<CodeAction> {
6629 self.visit_typed_module(&self.module.ast);
6630
6631 // We only want to trigger the action if we're over one of the echos in
6632 // the module
6633 if !self.is_hovering_echo {
6634 return vec![];
6635 };
6636
6637 for span in self.echo_spans_to_delete {
6638 self.edits.delete(span);
6639 }
6640
6641 let mut action = Vec::with_capacity(1);
6642 CodeActionBuilder::new("Remove all `echo`s from this module")
6643 .kind(CodeActionKind::REFACTOR_REWRITE)
6644 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6645 .preferred(false)
6646 .push_to(&mut action);
6647 action
6648 }
6649}
6650
6651impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
6652 fn visit_typed_expr_echo(
6653 &mut self,
6654 location: &'ast SrcSpan,
6655 type_: &'ast Arc<Type>,
6656 expression: &'ast Option<Box<TypedExpr>>,
6657 ) {
6658 // We also want to trigger the action if we're hovering over the expression
6659 // being printed. So we create a unique span starting from the start of echo
6660 // end ending at the end of the expression.
6661 //
6662 // ```
6663 // echo 1 + 2
6664 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
6665 // inside it, not just the keyword
6666 // ```
6667 //
6668 let echo_range = self.edits.src_span_to_lsp_range(*location);
6669 if within(self.params.range, echo_range) {
6670 self.is_hovering_echo = true;
6671 }
6672
6673 if let Some(expression) = expression {
6674 // If there's an expression we delete everything we find until its
6675 // start (excluded).
6676 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
6677 self.echo_spans_to_delete.push(span_to_delete);
6678 } else {
6679 // Othwerise we know we're inside a pipeline, we take the closest step
6680 // that is not echo itself and delete everything from its end until the
6681 // end of the echo keyword:
6682 //
6683 // ```txt
6684 // wibble |> echo |> wobble
6685 // ^^^^^^^^ This span right here
6686 // ```
6687 let step_preceding_echo = self
6688 .latest_pipe_step
6689 .filter(|l| l != location)
6690 .or(self.second_to_latest_pipe_step);
6691 if let Some(step_preceding_echo) = step_preceding_echo {
6692 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
6693 self.echo_spans_to_delete.push(span_to_delete);
6694 }
6695 }
6696
6697 ast::visit::visit_typed_expr_echo(self, location, type_, expression);
6698 }
6699
6700 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
6701 if self.latest_pipe_step.is_some() {
6702 self.second_to_latest_pipe_step = self.latest_pipe_step;
6703 }
6704 self.latest_pipe_step = Some(assignment.location);
6705 ast::visit::visit_typed_pipeline_assignment(self, assignment);
6706 }
6707}
6708
6709/// Code action to wrap assignment and case clause values in a block.
6710///
6711/// ```gleam
6712/// pub type PokemonType {
6713/// Fire
6714/// Water
6715/// }
6716///
6717/// pub fn main() {
6718/// let pokemon_type: PokemonType = todo
6719/// case pokemon_type {
6720/// Water -> soak()
6721/// ^^^^^^ Cursor over the spread
6722/// Fire -> burn()
6723/// }
6724/// }
6725/// ```
6726/// Becomes
6727/// ```gleam
6728/// pub type PokemonType {
6729/// Fire
6730/// Water
6731/// }
6732///
6733/// pub fn main() {
6734/// let pokemon_type: PokemonType = todo
6735/// case pokemon_type {
6736/// Water -> {
6737/// soak()
6738/// }
6739/// Fire -> burn()
6740/// }
6741/// }
6742/// ```
6743///
6744pub struct WrapInBlock<'a> {
6745 module: &'a Module,
6746 params: &'a CodeActionParams,
6747 edits: TextEdits<'a>,
6748 selected_expression: Option<SrcSpan>,
6749}
6750
6751impl<'a> WrapInBlock<'a> {
6752 pub fn new(
6753 module: &'a Module,
6754 line_numbers: &'a LineNumbers,
6755 params: &'a CodeActionParams,
6756 ) -> Self {
6757 Self {
6758 module,
6759 params,
6760 edits: TextEdits::new(line_numbers),
6761 selected_expression: None,
6762 }
6763 }
6764
6765 pub fn code_actions(mut self) -> Vec<CodeAction> {
6766 self.visit_typed_module(&self.module.ast);
6767
6768 let Some(expr_span) = self.selected_expression else {
6769 return vec![];
6770 };
6771
6772 let Some(expr_string) = self
6773 .module
6774 .code
6775 .get(expr_span.start as usize..(expr_span.end as usize + 1))
6776 else {
6777 return vec![];
6778 };
6779
6780 let range = self
6781 .edits
6782 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
6783
6784 let indent_size =
6785 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
6786
6787 let expr_indent_size = indent_size + 2;
6788
6789 let indent = " ".repeat(indent_size);
6790 let inner_indent = " ".repeat(expr_indent_size);
6791
6792 self.edits.replace(
6793 expr_span,
6794 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
6795 );
6796
6797 let mut action = Vec::with_capacity(1);
6798 CodeActionBuilder::new("Wrap in block")
6799 .kind(CodeActionKind::REFACTOR_EXTRACT)
6800 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6801 .preferred(false)
6802 .push_to(&mut action);
6803 action
6804 }
6805}
6806
6807impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
6808 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
6809 ast::visit::visit_typed_expr(self, &assignment.value);
6810 if !within(
6811 self.params.range,
6812 self.edits
6813 .src_span_to_lsp_range(assignment.value.location()),
6814 ) {
6815 return;
6816 }
6817 match assignment.value.as_ref() {
6818 // To avoid wrapping the same expression in multiple, nested blocks.
6819 TypedExpr::Block { .. } => {}
6820 TypedExpr::RecordAccess { .. }
6821 | TypedExpr::Int { .. }
6822 | TypedExpr::Float { .. }
6823 | TypedExpr::String { .. }
6824 | TypedExpr::Pipeline { .. }
6825 | TypedExpr::Var { .. }
6826 | TypedExpr::Fn { .. }
6827 | TypedExpr::List { .. }
6828 | TypedExpr::Call { .. }
6829 | TypedExpr::BinOp { .. }
6830 | TypedExpr::Case { .. }
6831 | TypedExpr::ModuleSelect { .. }
6832 | TypedExpr::Tuple { .. }
6833 | TypedExpr::TupleIndex { .. }
6834 | TypedExpr::Todo { .. }
6835 | TypedExpr::Panic { .. }
6836 | TypedExpr::Echo { .. }
6837 | TypedExpr::BitArray { .. }
6838 | TypedExpr::RecordUpdate { .. }
6839 | TypedExpr::NegateBool { .. }
6840 | TypedExpr::NegateInt { .. }
6841 | TypedExpr::Invalid { .. } => {
6842 self.selected_expression = Some(assignment.value.location());
6843 }
6844 };
6845 ast::visit::visit_typed_assignment(self, assignment);
6846 }
6847
6848 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
6849 ast::visit::visit_typed_clause(self, clause);
6850
6851 if !within(
6852 self.params.range,
6853 self.edits.src_span_to_lsp_range(clause.then.location()),
6854 ) {
6855 return;
6856 }
6857
6858 // To avoid wrapping the same expression in multiple, nested blocks.
6859 if !matches!(clause.then, TypedExpr::Block { .. }) {
6860 self.selected_expression = Some(clause.then.location());
6861 };
6862
6863 ast::visit::visit_typed_clause(self, clause);
6864 }
6865}
6866
6867/// Code action to fix wrong binary operators when the compiler can easily tell
6868/// what the correct alternative is.
6869///
6870/// ```gleam
6871///
6872/// ```
6873///
6874pub struct FixBinaryOperation<'a> {
6875 module: &'a Module,
6876 params: &'a CodeActionParams,
6877 edits: TextEdits<'a>,
6878 fix: Option<(SrcSpan, ast::BinOp)>,
6879}
6880
6881impl<'a> FixBinaryOperation<'a> {
6882 pub fn new(
6883 module: &'a Module,
6884 line_numbers: &'a LineNumbers,
6885 params: &'a CodeActionParams,
6886 ) -> Self {
6887 Self {
6888 module,
6889 params,
6890 edits: TextEdits::new(line_numbers),
6891 fix: None,
6892 }
6893 }
6894
6895 pub fn code_actions(mut self) -> Vec<CodeAction> {
6896 self.visit_typed_module(&self.module.ast);
6897
6898 let Some((location, replacement)) = self.fix else {
6899 return vec![];
6900 };
6901
6902 self.edits.replace(location, replacement.name().into());
6903
6904 let mut action = Vec::with_capacity(1);
6905 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
6906 .kind(CodeActionKind::REFACTOR_REWRITE)
6907 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6908 .preferred(true)
6909 .push_to(&mut action);
6910 action
6911 }
6912}
6913
6914impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
6915 fn visit_typed_expr_bin_op(
6916 &mut self,
6917 location: &'ast SrcSpan,
6918 type_: &'ast Arc<Type>,
6919 name: &'ast ast::BinOp,
6920 name_location: &'ast SrcSpan,
6921 left: &'ast TypedExpr,
6922 right: &'ast TypedExpr,
6923 ) {
6924 let binop_range = self.edits.src_span_to_lsp_range(*location);
6925 if !within(self.params.range, binop_range) {
6926 return;
6927 }
6928
6929 if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() {
6930 self.fix = name.float_equivalent().map(|fix| (*name_location, fix));
6931 } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() {
6932 self.fix = name.int_equivalent().map(|fix| (*name_location, fix))
6933 } else if *name == ast::BinOp::AddInt
6934 && left.type_().is_string()
6935 && right.type_().is_string()
6936 {
6937 self.fix = Some((*name_location, ast::BinOp::Concatenate))
6938 }
6939
6940 ast::visit::visit_typed_expr_bin_op(
6941 self,
6942 location,
6943 type_,
6944 name,
6945 name_location,
6946 left,
6947 right,
6948 );
6949 }
6950}
6951
6952/// Code action builder to automatically fix segments that have a value that's
6953/// guaranteed to overflow.
6954///
6955pub struct FixTruncatedBitArraySegment<'a> {
6956 module: &'a Module,
6957 params: &'a CodeActionParams,
6958 edits: TextEdits<'a>,
6959 truncation: Option<BitArraySegmentTruncation>,
6960}
6961
6962impl<'a> FixTruncatedBitArraySegment<'a> {
6963 pub fn new(
6964 module: &'a Module,
6965 line_numbers: &'a LineNumbers,
6966 params: &'a CodeActionParams,
6967 ) -> Self {
6968 Self {
6969 module,
6970 params,
6971 edits: TextEdits::new(line_numbers),
6972 truncation: None,
6973 }
6974 }
6975
6976 pub fn code_actions(mut self) -> Vec<CodeAction> {
6977 self.visit_typed_module(&self.module.ast);
6978
6979 let Some(truncation) = self.truncation else {
6980 return vec![];
6981 };
6982
6983 let replacement = truncation.truncated_into.to_string();
6984 self.edits
6985 .replace(truncation.value_location, replacement.clone());
6986
6987 let mut action = Vec::with_capacity(1);
6988 CodeActionBuilder::new(&format!("Replace with `{}`", replacement))
6989 .kind(CodeActionKind::REFACTOR_REWRITE)
6990 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6991 .preferred(true)
6992 .push_to(&mut action);
6993 action
6994 }
6995}
6996
6997impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
6998 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
6999 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
7000 if !within(self.params.range, segment_range) {
7001 return;
7002 }
7003
7004 if let Some(truncation) = segment.check_for_truncated_value() {
7005 self.truncation = Some(truncation);
7006 }
7007
7008 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
7009 }
7010}
7011
7012/// Code action builder to remove unused imports and values.
7013///
7014pub struct RemoveUnusedImports<'a> {
7015 module: &'a Module,
7016 params: &'a CodeActionParams,
7017 imports: Vec<&'a Import<EcoString>>,
7018 edits: TextEdits<'a>,
7019}
7020
7021#[derive(Debug)]
7022enum UnusedImport {
7023 Value(SrcSpan),
7024 Module(SrcSpan),
7025 ModuleAlias(SrcSpan),
7026}
7027
7028impl UnusedImport {
7029 fn location(&self) -> SrcSpan {
7030 match self {
7031 UnusedImport::Value(location)
7032 | UnusedImport::Module(location)
7033 | UnusedImport::ModuleAlias(location) => *location,
7034 }
7035 }
7036}
7037
7038impl<'a> RemoveUnusedImports<'a> {
7039 pub fn new(
7040 module: &'a Module,
7041 line_numbers: &'a LineNumbers,
7042 params: &'a CodeActionParams,
7043 ) -> Self {
7044 Self {
7045 module,
7046 params,
7047 edits: TextEdits::new(line_numbers),
7048 imports: vec![],
7049 }
7050 }
7051
7052 pub fn code_actions(mut self) -> Vec<CodeAction> {
7053 // If there's no import in the module then there can't be any unused
7054 // import to remove.
7055 self.visit_typed_module(&self.module.ast);
7056 if self.imports.is_empty() {
7057 return vec![];
7058 }
7059
7060 let unused_imports = (self.module.ast.type_info.warnings.iter())
7061 .filter_map(|warning| match warning {
7062 type_::Warning::UnusedImportedValue { location, .. } => {
7063 Some(UnusedImport::Value(*location))
7064 }
7065 type_::Warning::UnusedImportedModule { location, .. } => {
7066 Some(UnusedImport::Module(*location))
7067 }
7068 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
7069 Some(UnusedImport::ModuleAlias(*location))
7070 }
7071 _ => None,
7072 })
7073 .collect_vec();
7074
7075 println!("{:#?}", unused_imports);
7076 println!("{:#?}", self.params.range);
7077
7078 // If the cursor is not over any of the unused imports then we don't offer
7079 // the code action.
7080 let hovering_unused_import = unused_imports.iter().any(|import| {
7081 let unused_range = self.edits.src_span_to_lsp_range(import.location());
7082 overlaps(self.params.range, unused_range)
7083 });
7084 if !hovering_unused_import {
7085 return vec![];
7086 }
7087
7088 // Otherwise we start removing all unused imports:
7089 for import in unused_imports {
7090 match import {
7091 // When an entire module is unused we can delete its entire location
7092 // in the source code.
7093 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
7094 if self.edits.line_numbers.spans_entire_line(&location) {
7095 // If the unused module spans over the entire line then
7096 // we also take care of removing the following newline
7097 // characther!
7098 self.edits.delete(SrcSpan {
7099 start: location.start,
7100 end: location.end + 1,
7101 })
7102 } else {
7103 self.edits.delete(location)
7104 }
7105 }
7106
7107 // When removing unused imported values we have to be a bit more
7108 // careful: an unused value might be followed by a comma that we
7109 // also need to remove! So we have to look for this imported
7110 // value and also delete all characters between it and the
7111 // following imported value. For example:
7112 //
7113 // ```gleam
7114 // import wibble.{wobble, woo}
7115 // // ^^^^^^^^^^^ If wobble is unused we must remove
7116 // // all of this!
7117 // ```
7118 //
7119 UnusedImport::Value(_) => continue,
7120 }
7121 }
7122
7123 let mut action = Vec::with_capacity(1);
7124 CodeActionBuilder::new("Remove unused imports")
7125 .kind(CodeActionKind::REFACTOR_REWRITE)
7126 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7127 .preferred(true)
7128 .push_to(&mut action);
7129 action
7130 }
7131}
7132
7133impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> {
7134 fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) {
7135 self.imports = module
7136 .definitions
7137 .iter()
7138 .filter_map(|definition| match definition {
7139 ast::Definition::Import(import) => Some(import),
7140 _ => None,
7141 })
7142 .collect_vec();
7143 }
7144}