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