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