Fork of daniellemaywood.uk/gleam — Wasm codegen work
396 kB
11338 lines
1use std::{collections::HashSet, iter, sync::Arc};
2
3use ecow::{EcoString, eco_format};
4use gleam_core::{
5 Error, STDLIB_PACKAGE_NAME,
6 analyse::Inferred,
7 ast::{
8 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable,
9 BoundVariableName, CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import,
10 InvalidExpression, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments,
11 PipelineAssignmentKind, Publicity, RecordConstructor, SrcSpan, TodoKind,
12 TypeAstConstructorName, TypedArg, TypedAssignment, TypedClauseGuard, TypedDefinitions,
13 TypedExpr, TypedFunction, TypedModuleConstant, TypedPattern, TypedPipelineAssignment,
14 TypedRecordConstructor, TypedStatement, TypedTailPattern, TypedUse, visit::Visit as _,
15 },
16 build::{Located, Module},
17 config::PackageConfig,
18 exhaustiveness::CompiledCase,
19 line_numbers::LineNumbers,
20 parse::{extra::ModuleExtra, lexer::string_to_keyword},
21 strings::to_snake_case,
22 type_::{
23 self, FieldMap, ModuleValueConstructor, Opaque, Type, TypeVar, TypedCallArg,
24 ValueConstructor,
25 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin},
26 printer::Printer,
27 },
28};
29use im::HashMap;
30use itertools::Itertools;
31use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
32use vec1::{Vec1, vec1};
33
34use crate::engine::{completely_within, position_within};
35
36use super::{
37 TextEdits,
38 compiler::LspProjectCompiler,
39 edits,
40 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
41 engine::{overlaps, within},
42 files::FileSystemProxy,
43 reference::{FindVariableReferences, VariableReferenceKind},
44 src_span_to_lsp_range, url_from_path,
45};
46
47#[derive(Debug)]
48pub struct CodeActionBuilder {
49 action: CodeAction,
50}
51
52impl CodeActionBuilder {
53 pub fn new(title: &str) -> Self {
54 Self {
55 action: CodeAction {
56 title: title.to_string(),
57 kind: None,
58 diagnostics: None,
59 edit: None,
60 command: None,
61 is_preferred: None,
62 disabled: None,
63 data: None,
64 },
65 }
66 }
67
68 pub fn kind(mut self, kind: CodeActionKind) -> Self {
69 self.action.kind = Some(kind);
70 self
71 }
72
73 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
74 let mut edit = self.action.edit.take().unwrap_or_default();
75 let mut changes = edit.changes.take().unwrap_or_default();
76 _ = changes.insert(uri, edits);
77
78 edit.changes = Some(changes);
79 self.action.edit = Some(edit);
80 self
81 }
82
83 pub fn preferred(mut self, is_preferred: bool) -> Self {
84 self.action.is_preferred = Some(is_preferred);
85 self
86 }
87
88 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
89 actions.push(self.action);
90 }
91}
92
93/// A small helper function to get the indentation at a given position.
94fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
95 let mut indent_size = 0;
96 let line_start = *line_numbers
97 .line_starts
98 .get(line as usize)
99 .expect("Line number should be valid");
100
101 let mut chars = code[line_start as usize..].chars();
102 while chars.next() == Some(' ') {
103 indent_size += 1;
104 }
105
106 indent_size
107}
108
109/// Code action to remove literal tuples in case subjects, essentially making
110/// the elements of the tuples into the case's subjects.
111///
112/// The code action is only available for the i'th subject if:
113/// - it is a non-empty tuple, and
114/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
115///
116/// # Basic example:
117///
118/// The following case expression:
119///
120/// ```gleam
121/// case #(1, 2) {
122/// #(a, b) -> 0
123/// }
124/// ```
125///
126/// Becomes:
127///
128/// ```gleam
129/// case 1, 2 {
130/// a, b -> 0
131/// }
132/// ```
133///
134/// # Another example:
135///
136/// The following case expression does not produce any code action
137///
138/// ```gleam
139/// case #(1, 2) {
140/// a -> 0 // <- the pattern is not a tuple
141/// }
142/// ```
143pub struct RedundantTupleInCaseSubject<'a> {
144 edits: TextEdits<'a>,
145 code: &'a EcoString,
146 extra: &'a ModuleExtra,
147 params: &'a CodeActionParams,
148 module: &'a ast::TypedModule,
149 hovered: bool,
150}
151
152impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
153 fn visit_typed_expr_case(
154 &mut self,
155 location: &'ast SrcSpan,
156 type_: &'ast Arc<Type>,
157 subjects: &'ast [TypedExpr],
158 clauses: &'ast [ast::TypedClause],
159 compiled_case: &'ast CompiledCase,
160 ) {
161 for (subject_idx, subject) in subjects.iter().enumerate() {
162 let TypedExpr::Tuple {
163 location, elements, ..
164 } = subject
165 else {
166 continue;
167 };
168
169 // Ignore empty tuple
170 if elements.is_empty() {
171 continue;
172 }
173
174 // We cannot rewrite clauses whose i-th pattern is not a discard or
175 // tuples.
176 let all_ith_patterns_are_tuples_or_discards = clauses
177 .iter()
178 .map(|clause| clause.pattern.get(subject_idx))
179 .all(|pattern| {
180 matches!(
181 pattern,
182 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
183 )
184 });
185
186 if !all_ith_patterns_are_tuples_or_discards {
187 continue;
188 }
189
190 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
191
192 for clause in clauses {
193 match clause.pattern.get(subject_idx) {
194 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
195 *location,
196 elements.last().map(|element| element.location()),
197 ),
198 Some(Pattern::Discard { location, .. }) => {
199 self.discard_tuple_items(*location, elements.len())
200 }
201 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
202 }
203 }
204 let range = self.edits.src_span_to_lsp_range(*location);
205 self.hovered = self.hovered || overlaps(self.params.range, range);
206 }
207
208 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case)
209 }
210}
211
212impl<'a> RedundantTupleInCaseSubject<'a> {
213 pub fn new(
214 module: &'a Module,
215 line_numbers: &'a LineNumbers,
216 params: &'a CodeActionParams,
217 ) -> Self {
218 Self {
219 edits: TextEdits::new(line_numbers),
220 code: &module.code,
221 extra: &module.extra,
222 params,
223 module: &module.ast,
224 hovered: false,
225 }
226 }
227
228 pub fn code_actions(mut self) -> Vec<CodeAction> {
229 self.visit_typed_module(self.module);
230 if !self.hovered {
231 return vec![];
232 }
233
234 self.edits.edits.sort_by_key(|edit| edit.range.start);
235
236 let mut actions = vec![];
237 CodeActionBuilder::new("Remove redundant tuples")
238 .kind(CodeActionKind::REFACTOR_REWRITE)
239 .changes(self.params.text_document.uri.clone(), self.edits.edits)
240 .preferred(true)
241 .push_to(&mut actions);
242
243 actions
244 }
245
246 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
247 let tuple_code = self
248 .code
249 .get(location.start as usize..location.end as usize)
250 .expect("valid span");
251
252 // Delete `#`
253 self.edits
254 .delete(SrcSpan::new(location.start, location.start + 1));
255
256 // Delete `(`
257 let (lparen_offset, _) = tuple_code
258 .match_indices('(')
259 // Ignore in comments
260 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
261 .expect("`(` not found in tuple");
262
263 self.edits.delete(SrcSpan::new(
264 location.start + lparen_offset as u32,
265 location.start + lparen_offset as u32 + 1,
266 ));
267
268 // Delete trailing `,` (if applicable)
269 if let Some(last_elem_location) = last_elem_location {
270 // Get the code after the last element until the tuple's `)`
271 let code_after_last_elem = self
272 .code
273 .get(last_elem_location.end as usize..location.end as usize)
274 .expect("valid span");
275
276 if let Some((trailing_comma_offset, _)) = code_after_last_elem
277 .rmatch_indices(',')
278 // Ignore in comments
279 .find(|(i, _)| {
280 !self
281 .extra
282 .is_within_comment(last_elem_location.end + *i as u32)
283 })
284 {
285 self.edits.delete(SrcSpan::new(
286 last_elem_location.end + trailing_comma_offset as u32,
287 last_elem_location.end + trailing_comma_offset as u32 + 1,
288 ));
289 }
290 }
291
292 // Delete )
293 self.edits
294 .delete(SrcSpan::new(location.end - 1, location.end));
295 }
296
297 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
298 // Replace the old discard with multiple discard, one for each of the
299 // tuple items.
300 self.edits.replace(
301 discard_location,
302 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
303 )
304 }
305}
306
307/// Builder for code action to convert `let assert` into a case expression.
308///
309pub struct LetAssertToCase<'a> {
310 module: &'a Module,
311 params: &'a CodeActionParams,
312 actions: Vec<CodeAction>,
313 edits: TextEdits<'a>,
314}
315
316impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
317 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
318 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
319 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan {
320 start: assignment.location.start,
321 end: assignment.value.location().start,
322 });
323 self.visit_typed_expr(&assignment.value);
324
325 // Only offer the code action if the cursor is over the statement and
326 // to prevent weird behaviour when `let assert` statements are nested,
327 // we only check for the code action between the `let` and `=`.
328 if !(within(self.params.range, assignment_range)
329 && overlaps(self.params.range, assignment_start_range))
330 {
331 return;
332 }
333
334 // This pattern only applies to `let assert`
335 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
336 return;
337 };
338
339 // Get the source code for the tested expression
340 let location = assignment.value.location();
341 let expr = self
342 .module
343 .code
344 .get(location.start as usize..location.end as usize)
345 .expect("Location must be valid");
346
347 // Get the source code for the pattern
348 let pattern_location = assignment.pattern.location();
349 let pattern = self
350 .module
351 .code
352 .get(pattern_location.start as usize..pattern_location.end as usize)
353 .expect("Location must be valid");
354
355 let message = message.as_ref().map(|message| {
356 let location = message.location();
357 self.module
358 .code
359 .get(location.start as usize..location.end as usize)
360 .expect("Location must be valid")
361 });
362
363 let range = self.edits.src_span_to_lsp_range(assignment.location);
364
365 // Figure out which variables are assigned in the pattern
366 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
367
368 let assigned = match variables.len() {
369 0 => "_",
370 1 => variables.first().expect("Variables is length one"),
371 _ => &format!("#({})", variables.join(", ")),
372 };
373
374 let mut new_text = format!("let {assigned} = ");
375 let panic_message = if let Some(message) = message {
376 &format!("panic as {message}")
377 } else {
378 "panic"
379 };
380 let clauses = vec![
381 // The existing pattern
382 CaseClause {
383 pattern,
384 // `_` is not a valid expression, so if we are not
385 // binding any variables in the pattern, we simply return Nil.
386 expression: if assigned == "_" { "Nil" } else { assigned },
387 },
388 CaseClause {
389 pattern: "_",
390 expression: panic_message,
391 },
392 ];
393 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
394
395 let uri = &self.params.text_document.uri;
396
397 CodeActionBuilder::new("Convert to case")
398 .kind(CodeActionKind::REFACTOR_REWRITE)
399 .changes(uri.clone(), vec![TextEdit { range, new_text }])
400 .preferred(false)
401 .push_to(&mut self.actions);
402 }
403}
404
405impl<'a> LetAssertToCase<'a> {
406 pub fn new(
407 module: &'a Module,
408 line_numbers: &'a LineNumbers,
409 params: &'a CodeActionParams,
410 ) -> Self {
411 Self {
412 module,
413 params,
414 actions: Vec::new(),
415 edits: TextEdits::new(line_numbers),
416 }
417 }
418
419 pub fn code_actions(mut self) -> Vec<CodeAction> {
420 self.visit_typed_module(&self.module.ast);
421 self.actions
422 }
423}
424
425struct PatternVariableFinder {
426 pattern_variables: Vec<EcoString>,
427}
428
429impl PatternVariableFinder {
430 fn new() -> Self {
431 Self {
432 pattern_variables: Vec::new(),
433 }
434 }
435
436 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
437 let mut finder = Self::new();
438 finder.visit_typed_pattern(pattern);
439 finder.pattern_variables
440 }
441}
442
443impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
444 fn visit_typed_pattern_variable(
445 &mut self,
446 _location: &'ast SrcSpan,
447 name: &'ast EcoString,
448 _type: &'ast Arc<Type>,
449 _origin: &'ast VariableOrigin,
450 ) {
451 self.pattern_variables.push(name.clone());
452 }
453
454 fn visit_typed_pattern_assign(
455 &mut self,
456 location: &'ast SrcSpan,
457 name: &'ast EcoString,
458 pattern: &'ast TypedPattern,
459 ) {
460 self.pattern_variables.push(name.clone());
461 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
462 }
463
464 fn visit_typed_pattern_string_prefix(
465 &mut self,
466 _location: &'ast SrcSpan,
467 _left_location: &'ast SrcSpan,
468 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
469 _right_location: &'ast SrcSpan,
470 _left_side_string: &'ast EcoString,
471 right_side_assignment: &'ast AssignName,
472 ) {
473 if let Some((name, _)) = left_side_assignment {
474 self.pattern_variables.push(name.clone());
475 }
476 if let AssignName::Variable(name) = right_side_assignment {
477 self.pattern_variables.push(name.clone());
478 }
479 }
480}
481
482pub fn code_action_inexhaustive_let_to_case(
483 module: &Module,
484 line_numbers: &LineNumbers,
485 params: &CodeActionParams,
486 error: &Option<Error>,
487 actions: &mut Vec<CodeAction>,
488) {
489 let Some(Error::Type { errors, .. }) = error else {
490 return;
491 };
492 let inexhaustive_assignments = errors
493 .iter()
494 .filter_map(|error| {
495 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error {
496 Some((*location, missing))
497 } else {
498 None
499 }
500 })
501 .collect_vec();
502
503 if inexhaustive_assignments.is_empty() {
504 return;
505 }
506
507 for (location, missing) in inexhaustive_assignments {
508 let mut text_edits = TextEdits::new(line_numbers);
509
510 let range = text_edits.src_span_to_lsp_range(location);
511 if !overlaps(params.range, range) {
512 return;
513 }
514
515 let Some(Located::Statement(TypedStatement::Assignment(assignment))) =
516 module.find_node(location.start)
517 else {
518 continue;
519 };
520
521 let TypedAssignment {
522 value,
523 pattern,
524 kind: AssignmentKind::Let,
525 location,
526 compiled_case: _,
527 annotation: _,
528 } = assignment.as_ref()
529 else {
530 continue;
531 };
532
533 // Get the source code for the tested expression
534 let value_location = value.location();
535 let expr = module
536 .code
537 .get(value_location.start as usize..value_location.end as usize)
538 .expect("Location must be valid");
539
540 // Get the source code for the pattern
541 let pattern_location = pattern.location();
542 let pattern_code = module
543 .code
544 .get(pattern_location.start as usize..pattern_location.end as usize)
545 .expect("Location must be valid");
546
547 let range = text_edits.src_span_to_lsp_range(*location);
548
549 // Figure out which variables are assigned in the pattern
550 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
551
552 let assigned = match variables.len() {
553 0 => "_",
554 1 => variables.first().expect("Variables is length one"),
555 _ => &format!("#({})", variables.join(", ")),
556 };
557
558 let mut new_text = format!("let {assigned} = ");
559 print_case_expression(
560 range.start.character as usize,
561 expr,
562 iter::once(CaseClause {
563 pattern: pattern_code,
564 expression: if assigned == "_" { "Nil" } else { assigned },
565 })
566 .chain(missing.iter().map(|pattern| CaseClause {
567 pattern,
568 expression: "todo",
569 }))
570 .collect(),
571 &mut new_text,
572 );
573
574 let uri = ¶ms.text_document.uri;
575
576 text_edits.replace(*location, new_text);
577
578 CodeActionBuilder::new("Convert to case")
579 .kind(CodeActionKind::QUICKFIX)
580 .changes(uri.clone(), text_edits.edits)
581 .preferred(true)
582 .push_to(actions);
583 }
584}
585
586struct CaseClause<'a> {
587 pattern: &'a str,
588 expression: &'a str,
589}
590
591fn print_case_expression(
592 indent_size: usize,
593 subject: &str,
594 clauses: Vec<CaseClause<'_>>,
595 buffer: &mut String,
596) {
597 let indent = " ".repeat(indent_size);
598
599 // Print the beginning of the expression
600 buffer.push_str("case ");
601 buffer.push_str(subject);
602 buffer.push_str(" {");
603
604 for clause in clauses.iter() {
605 // Print the newline and indentation for this clause
606 buffer.push('\n');
607 buffer.push_str(&indent);
608 // Indent this clause one level deeper than the case expression
609 buffer.push_str(" ");
610
611 // Print the clause
612 buffer.push_str(clause.pattern);
613 buffer.push_str(" -> ");
614 buffer.push_str(clause.expression);
615 }
616
617 // If there are no clauses to print, the closing brace should be
618 // on the same line as the opening one, with no space between.
619 if !clauses.is_empty() {
620 buffer.push('\n');
621 buffer.push_str(&indent);
622 }
623 buffer.push('}');
624}
625
626/// Builder for code action to apply the label shorthand syntax on arguments
627/// where the label has the same name as the variable.
628///
629pub struct UseLabelShorthandSyntax<'a> {
630 module: &'a Module,
631 params: &'a CodeActionParams,
632 edits: TextEdits<'a>,
633}
634
635impl<'a> UseLabelShorthandSyntax<'a> {
636 pub fn new(
637 module: &'a Module,
638 line_numbers: &'a LineNumbers,
639 params: &'a CodeActionParams,
640 ) -> Self {
641 Self {
642 module,
643 params,
644 edits: TextEdits::new(line_numbers),
645 }
646 }
647
648 pub fn code_actions(mut self) -> Vec<CodeAction> {
649 self.visit_typed_module(&self.module.ast);
650 if self.edits.edits.is_empty() {
651 return vec![];
652 }
653 let mut action = Vec::with_capacity(1);
654 CodeActionBuilder::new("Use label shorthand syntax")
655 .kind(CodeActionKind::REFACTOR)
656 .changes(self.params.text_document.uri.clone(), self.edits.edits)
657 .preferred(false)
658 .push_to(&mut action);
659 action
660 }
661}
662
663impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
664 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
665 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
666 let is_selected = overlaps(arg_range, self.params.range);
667
668 match arg {
669 CallArg {
670 label: Some(label),
671 value: TypedExpr::Var { name, location, .. },
672 ..
673 } if is_selected && !arg.uses_label_shorthand() && label == name => {
674 self.edits.delete(*location)
675 }
676 _ => (),
677 }
678
679 ast::visit::visit_typed_call_arg(self, arg)
680 }
681
682 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
683 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
684 let is_selected = overlaps(arg_range, self.params.range);
685
686 match arg {
687 CallArg {
688 label: Some(label),
689 value: TypedPattern::Variable { name, location, .. },
690 ..
691 } if is_selected && !arg.uses_label_shorthand() && label == name => {
692 self.edits.delete(*location)
693 }
694 _ => (),
695 }
696
697 ast::visit::visit_typed_pattern_call_arg(self, arg)
698 }
699}
700
701/// Builder for code action to apply the fill in the missing labelled arguments
702/// of the selected function call.
703///
704pub struct FillInMissingLabelledArgs<'a> {
705 module: &'a Module,
706 params: &'a CodeActionParams,
707 edits: TextEdits<'a>,
708 use_right_hand_side_location: Option<SrcSpan>,
709 selected_call: Option<SelectedCall<'a>>,
710 current_function: Option<&'a TypedFunction>,
711}
712
713struct SelectedCall<'a> {
714 location: SrcSpan,
715 field_map: &'a FieldMap,
716 arguments: Vec<CallArg<()>>,
717 kind: SelectedCallKind,
718 fun_type: Option<Arc<Type>>,
719 enclosing_function: Option<&'a TypedFunction>,
720}
721
722enum SelectedCallKind {
723 Value,
724 Pattern,
725}
726
727impl<'a> FillInMissingLabelledArgs<'a> {
728 pub fn new(
729 module: &'a Module,
730 line_numbers: &'a LineNumbers,
731 params: &'a CodeActionParams,
732 ) -> Self {
733 Self {
734 module,
735 params,
736 edits: TextEdits::new(line_numbers),
737 use_right_hand_side_location: None,
738 selected_call: None,
739 current_function: None,
740 }
741 }
742
743 pub fn code_actions(mut self) -> Vec<CodeAction> {
744 self.visit_typed_module(&self.module.ast);
745
746 if let Some(SelectedCall {
747 location: call_location,
748 field_map,
749 arguments,
750 kind,
751 fun_type,
752 enclosing_function,
753 }) = self.selected_call
754 {
755 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
756 let missing_labels = field_map.missing_labels(&arguments);
757
758 // If we're applying the code action to a use call, then we know
759 // that the last missing argument is going to be implicitly inserted
760 // by the compiler, so in that case we don't want to also add that
761 // last label to the completions.
762 let missing_labels = missing_labels.iter().peekable();
763 let mut missing_labels = if is_use_call {
764 missing_labels.dropping_back(1)
765 } else {
766 missing_labels
767 };
768
769 // If we couldn't find any missing label to insert we just return.
770 if missing_labels.peek().is_none() {
771 return vec![];
772 }
773
774 // A pattern could have been written with no parentheses at all!
775 // So we need to check for the last character to see if parentheses
776 // are there or not before filling the arguments in
777 let has_parentheses = ")"
778 == code_at(
779 self.module,
780 SrcSpan::new(call_location.end - 1, call_location.end),
781 );
782 let label_insertion_start = if has_parentheses {
783 // If it ends with a parentheses we'll need to start inserting
784 // right before the closing one...
785 call_location.end - 1
786 } else {
787 // ...otherwise we just append the result
788 call_location.end
789 };
790
791 // Now we need to figure out if there's a comma at the end of the
792 // arguments list:
793 //
794 // call(one, |)
795 // ^ Cursor here, with a comma behind
796 //
797 // call(one|)
798 // ^ Cursor here, no comma behind, we'll have to add one!
799 //
800 let has_comma_after_last_argument =
801 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) {
802 self.module
803 .code
804 .get(last_arg.location.end as usize..=label_insertion_start as usize)
805 .is_some_and(|text| text.contains(','))
806 } else {
807 false
808 };
809
810 let variables_in_scope = enclosing_function
811 .map(|fun| {
812 ScopeVariableCollector::new(call_location.start).collect_from_function(fun)
813 })
814 .unwrap_or_default();
815
816 let labels_list = missing_labels
817 .map(|label| {
818 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope)
819 })
820 .join(", ");
821
822 let has_no_explicit_arguments = arguments
823 .iter()
824 .filter(|arg| !arg.is_implicit())
825 .peekable()
826 .peek()
827 .is_none();
828
829 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
830 labels_list
831 } else {
832 format!(", {labels_list}")
833 };
834
835 let edit = if has_parentheses {
836 labels_list
837 } else {
838 // If the variant whose arguments we're filling in was written
839 // with no parentheses we need to add those as well to make it a
840 // valid constructor.
841 format!("({labels_list})")
842 };
843
844 self.edits.insert(label_insertion_start, edit);
845
846 let mut action = Vec::with_capacity(1);
847 CodeActionBuilder::new("Fill labels")
848 .kind(CodeActionKind::QUICKFIX)
849 .changes(self.params.text_document.uri.clone(), self.edits.edits)
850 .preferred(true)
851 .push_to(&mut action);
852 return action;
853 }
854
855 vec![]
856 }
857
858 /// Formats a label for insertion. Uses `label:` syntax if there's a variable
859 /// in scope with matching name and type, otherwise uses `label: todo`.
860 fn format_label(
861 label: &EcoString,
862 kind: &SelectedCallKind,
863 fun_type: &Option<Arc<Type>>,
864 field_map: &FieldMap,
865 variables_in_scope: &HashMap<EcoString, Arc<Type>>,
866 ) -> String {
867 if matches!(kind, SelectedCallKind::Pattern) {
868 return format!("{label}:");
869 }
870
871 if let Some(var_type) = variables_in_scope.get(label) {
872 let expected_type = fun_type.as_ref().and_then(|ft| {
873 let (arg_types, _) = ft.fn_types()?;
874 let &index = field_map.fields.get(label)?;
875 arg_types.get(index as usize).cloned()
876 });
877
878 if let Some(expected) = expected_type
879 && expected.same_as(var_type)
880 {
881 return format!("{label}:");
882 }
883 }
884
885 format!("{label}: todo")
886 }
887
888 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
889 CallArg {
890 label: argument.label.clone(),
891 location: argument.location,
892 value: (),
893 implicit: argument.implicit,
894 }
895 }
896}
897
898impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
899 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
900 // We store the current function as the containing function while we traverse
901 // it, allowing handling nested functions correctly.
902 let previous_function = self.current_function;
903 self.current_function = Some(fun);
904 ast::visit::visit_typed_function(self, fun);
905 self.current_function = previous_function;
906 }
907
908 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
909 // If we're adding labels to a use call the correct location of the
910 // function we need to add labels to is `use_right_hand_side_location`.
911 // So we store it for when we're typing the use call.
912 let previous = self.use_right_hand_side_location;
913 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
914 ast::visit::visit_typed_use(self, use_);
915 self.use_right_hand_side_location = previous;
916 }
917
918 fn visit_typed_expr_call(
919 &mut self,
920 location: &'ast SrcSpan,
921 type_: &'ast Arc<Type>,
922 fun: &'ast TypedExpr,
923 arguments: &'ast [TypedCallArg],
924 ) {
925 let call_range = self.edits.src_span_to_lsp_range(*location);
926 if !within(self.params.range, call_range) {
927 return;
928 }
929
930 if let Some(field_map) = fun.field_map() {
931 let location = self.use_right_hand_side_location.unwrap_or(*location);
932 self.selected_call = Some(SelectedCall {
933 location,
934 field_map,
935 arguments: arguments.iter().map(Self::empty_argument).collect(),
936 kind: SelectedCallKind::Value,
937 fun_type: Some(fun.type_()),
938 enclosing_function: self.current_function,
939 })
940 }
941
942 // We only want to take into account the innermost function call
943 // containing the current selection so we can't stop at the first call
944 // we find (the outermost one) and have to keep traversing it in case
945 // we're inside a nested call.
946 let previous = self.use_right_hand_side_location;
947 self.use_right_hand_side_location = None;
948 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
949 self.use_right_hand_side_location = previous;
950 }
951
952 fn visit_typed_pattern_constructor(
953 &mut self,
954 location: &'ast SrcSpan,
955 name_location: &'ast SrcSpan,
956 name: &'ast EcoString,
957 arguments: &'ast Vec<CallArg<TypedPattern>>,
958 module: &'ast Option<(EcoString, SrcSpan)>,
959 constructor: &'ast Inferred<type_::PatternConstructor>,
960 spread: &'ast Option<SrcSpan>,
961 type_: &'ast Arc<Type>,
962 ) {
963 let call_range = self.edits.src_span_to_lsp_range(*location);
964 if !within(self.params.range, call_range) {
965 return;
966 }
967
968 if let Some(field_map) = constructor.field_map() {
969 self.selected_call = Some(SelectedCall {
970 location: *location,
971 field_map,
972 arguments: arguments.iter().map(Self::empty_argument).collect(),
973 kind: SelectedCallKind::Pattern,
974 fun_type: None,
975 enclosing_function: self.current_function,
976 })
977 }
978
979 ast::visit::visit_typed_pattern_constructor(
980 self,
981 location,
982 name_location,
983 name,
984 arguments,
985 module,
986 constructor,
987 spread,
988 type_,
989 );
990 }
991}
992
993/// Collects variables that are in scope at a given cursor position.
994struct ScopeVariableCollector {
995 cursor: u32,
996 variables: HashMap<EcoString, Arc<Type>>,
997}
998
999impl ScopeVariableCollector {
1000 fn new(cursor: u32) -> Self {
1001 Self {
1002 cursor,
1003 variables: HashMap::new(),
1004 }
1005 }
1006
1007 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> {
1008 for arg in &fun.arguments {
1009 if let Some(name) = arg.get_variable_name() {
1010 _ = self.variables.insert(name.clone(), arg.type_.clone());
1011 }
1012 }
1013
1014 for statement in &fun.body {
1015 self.visit_typed_statement(statement);
1016 }
1017
1018 self.variables
1019 }
1020}
1021
1022impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector {
1023 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
1024 // We need to consider variables defined only before the cursor
1025 if statement.location().start >= self.cursor {
1026 return;
1027 }
1028
1029 ast::visit::visit_typed_statement(self, statement);
1030 }
1031
1032 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1033 // if cursor is inside the assignment, we only visit the value expression,
1034 // because the variable being defined isn't in scope yet.
1035 if assignment.location.contains(self.cursor) {
1036 self.visit_typed_expr(&assignment.value);
1037 } else {
1038 self.visit_typed_pattern(&assignment.pattern);
1039 }
1040 }
1041
1042 fn visit_typed_expr_fn(
1043 &mut self,
1044 _location: &'ast SrcSpan,
1045 _type_: &'ast Arc<Type>,
1046 _kind: &'ast FunctionLiteralKind,
1047 _arguments: &'ast [TypedArg],
1048 _body: &'ast Vec1<TypedStatement>,
1049 _return_annotation: &'ast Option<ast::TypeAst>,
1050 ) {
1051 // We don't descend into nested functions, as their variables aren't in
1052 // our scope
1053 }
1054
1055 fn visit_typed_expr_block(
1056 &mut self,
1057 location: &'ast SrcSpan,
1058 statements: &'ast [TypedStatement],
1059 ) {
1060 if self.cursor >= location.end {
1061 return;
1062 }
1063 ast::visit::visit_typed_expr_block(self, location, statements);
1064 }
1065
1066 fn visit_typed_pattern_variable(
1067 &mut self,
1068 _location: &'ast SrcSpan,
1069 name: &'ast EcoString,
1070 type_: &'ast Arc<Type>,
1071 _origin: &'ast VariableOrigin,
1072 ) {
1073 if !name.starts_with('_') {
1074 _ = self.variables.insert(name.clone(), type_.clone());
1075 }
1076 }
1077
1078 fn visit_typed_pattern_assign(
1079 &mut self,
1080 _location: &'ast SrcSpan,
1081 name: &'ast EcoString,
1082 pattern: &'ast Pattern<Arc<Type>>,
1083 ) {
1084 self.visit_typed_pattern(pattern);
1085 if !name.starts_with('_') {
1086 _ = self.variables.insert(name.clone(), pattern.type_().clone());
1087 }
1088 }
1089}
1090
1091struct MissingImport {
1092 location: SrcSpan,
1093 suggestions: Vec<ImportSuggestion>,
1094}
1095
1096struct ImportSuggestion {
1097 // The name to replace with, if the user made a typo
1098 name: EcoString,
1099 // The optional module to import, if suggesting an importable module
1100 import: Option<EcoString>,
1101}
1102
1103pub fn code_action_import_module(
1104 module: &Module,
1105 line_numbers: &LineNumbers,
1106 params: &CodeActionParams,
1107 error: &Option<Error>,
1108 actions: &mut Vec<CodeAction>,
1109) {
1110 let uri = ¶ms.text_document.uri;
1111 let Some(Error::Type { errors, .. }) = error else {
1112 return;
1113 };
1114
1115 let missing_imports = errors
1116 .into_iter()
1117 .filter_map(|e| {
1118 if let type_::Error::UnknownModule {
1119 location,
1120 suggestions,
1121 ..
1122 } = e
1123 {
1124 suggest_imports(*location, suggestions)
1125 } else {
1126 None
1127 }
1128 })
1129 .collect_vec();
1130
1131 if missing_imports.is_empty() {
1132 return;
1133 }
1134
1135 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
1136 let first_is_import = first_import_pos.is_some();
1137 let import_location = first_import_pos.unwrap_or_default();
1138
1139 let after_import_newlines =
1140 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
1141
1142 for missing_import in missing_imports {
1143 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
1144 if !overlaps(params.range, range) {
1145 continue;
1146 }
1147
1148 for suggestion in missing_import.suggestions {
1149 let mut edits = vec![TextEdit {
1150 range,
1151 new_text: suggestion.name.to_string(),
1152 }];
1153 if let Some(import) = &suggestion.import {
1154 edits.push(get_import_edit(
1155 import_location,
1156 import,
1157 &after_import_newlines,
1158 ))
1159 };
1160
1161 let title = match &suggestion.import {
1162 Some(import) => &format!("Import `{import}`"),
1163 _ => &format!("Did you mean `{}`", suggestion.name),
1164 };
1165
1166 CodeActionBuilder::new(title)
1167 .kind(CodeActionKind::QUICKFIX)
1168 .changes(uri.clone(), edits)
1169 .preferred(true)
1170 .push_to(actions);
1171 }
1172 }
1173}
1174
1175fn suggest_imports(
1176 location: SrcSpan,
1177 importable_modules: &[ModuleSuggestion],
1178) -> Option<MissingImport> {
1179 let suggestions = importable_modules
1180 .iter()
1181 .map(|suggestion| {
1182 let imported_name = suggestion.last_name_component();
1183 match suggestion {
1184 ModuleSuggestion::Importable(name) => ImportSuggestion {
1185 name: imported_name.into(),
1186 import: Some(name.clone()),
1187 },
1188 ModuleSuggestion::Imported(_) => ImportSuggestion {
1189 name: imported_name.into(),
1190 import: None,
1191 },
1192 }
1193 })
1194 .collect_vec();
1195
1196 if suggestions.is_empty() {
1197 None
1198 } else {
1199 Some(MissingImport {
1200 location,
1201 suggestions,
1202 })
1203 }
1204}
1205
1206pub fn code_action_add_missing_patterns(
1207 module: &Module,
1208 line_numbers: &LineNumbers,
1209 params: &CodeActionParams,
1210 error: &Option<Error>,
1211 actions: &mut Vec<CodeAction>,
1212) {
1213 let uri = ¶ms.text_document.uri;
1214 let Some(Error::Type { errors, .. }) = error else {
1215 return;
1216 };
1217 let missing_patterns = errors
1218 .iter()
1219 .filter_map(|error| {
1220 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error {
1221 Some((*location, missing))
1222 } else {
1223 None
1224 }
1225 })
1226 .collect_vec();
1227
1228 if missing_patterns.is_empty() {
1229 return;
1230 }
1231
1232 for (location, missing) in missing_patterns {
1233 let mut edits = TextEdits::new(line_numbers);
1234 let range = edits.src_span_to_lsp_range(location);
1235 if !overlaps(params.range, range) {
1236 return;
1237 }
1238
1239 let Some(Located::Expression {
1240 expression: TypedExpr::Case {
1241 clauses, subjects, ..
1242 },
1243 ..
1244 }) = module.find_node(location.start)
1245 else {
1246 continue;
1247 };
1248
1249 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1250
1251 let indent = " ".repeat(indent_size);
1252
1253 // Insert the missing patterns just after the final clause, or just before
1254 // the closing brace if there are no clauses
1255
1256 let insert_at = clauses
1257 .last()
1258 .map(|clause| clause.location.end)
1259 .unwrap_or(location.end - 1);
1260
1261 for pattern in missing {
1262 let new_text = format!("\n{indent} {pattern} -> todo");
1263 edits.insert(insert_at, new_text);
1264 }
1265
1266 // Add a newline + indent after the last pattern if there are no clauses
1267 //
1268 // This improves the generated code for this case:
1269 // ```gleam
1270 // case True {}
1271 // ```
1272 // This produces:
1273 // ```gleam
1274 // case True {
1275 // True -> todo
1276 // False -> todo
1277 // }
1278 // ```
1279 // Instead of:
1280 // ```gleam
1281 // case True {
1282 // True -> todo
1283 // False -> todo}
1284 // ```
1285 //
1286 if clauses.is_empty() {
1287 let last_subject_location = subjects
1288 .last()
1289 .expect("Case expressions have at least one subject")
1290 .location()
1291 .end;
1292
1293 // Find the opening brace of the case expression
1294 let chars = module.code[last_subject_location as usize..].chars();
1295 let mut start_brace_location = last_subject_location;
1296 for char in chars {
1297 start_brace_location += 1;
1298 if char == '{' {
1299 break;
1300 }
1301 }
1302
1303 // We remove any blank spaces/lines between the end of the last
1304 // comment inside the case expression and the insertion point.
1305 // If there's no comments we remove all empty space in between the
1306 // two braces
1307 let deletion_start = module
1308 .extra
1309 .last_comment_between(start_brace_location, insert_at)
1310 .map(|src_span| src_span.end)
1311 .unwrap_or(start_brace_location);
1312
1313 edits.delete(SrcSpan::new(deletion_start, insert_at));
1314 edits.insert(insert_at, format!("\n{indent}"));
1315 }
1316
1317 CodeActionBuilder::new("Add missing patterns")
1318 .kind(CodeActionKind::QUICKFIX)
1319 .changes(uri.clone(), edits.edits)
1320 .preferred(true)
1321 .push_to(actions);
1322 }
1323}
1324
1325/// Builder for code action to add annotations to an assignment or function
1326///
1327pub struct AddAnnotations<'a> {
1328 module: &'a Module,
1329 params: &'a CodeActionParams,
1330 edits: TextEdits<'a>,
1331 printer: Printer<'a>,
1332}
1333
1334impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1335 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1336 self.visit_typed_expr(&assignment.value);
1337
1338 // We only offer this code action between `let` and `=`, because
1339 // otherwise it could lead to confusing behaviour if inside a block
1340 // which is part of a let binding.
1341 let pattern_location = assignment.pattern.location();
1342 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1343 let code_action_range = self.edits.src_span_to_lsp_range(location);
1344
1345 // Only offer the code action if the cursor is over the statement
1346 if !overlaps(code_action_range, self.params.range) {
1347 return;
1348 }
1349
1350 // We don't need to add an annotation if there already is one
1351 if assignment.annotation.is_some() {
1352 return;
1353 }
1354
1355 // Various expressions such as pipelines and `use` expressions generate assignments
1356 // internally. However, these cannot be annotated and so we don't offer a code action here.
1357 if matches!(assignment.kind, AssignmentKind::Generated) {
1358 return;
1359 }
1360
1361 self.edits.insert(
1362 pattern_location.end,
1363 format!(": {}", self.printer.print_type(&assignment.type_())),
1364 );
1365 }
1366
1367 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1368 // Since type variable names are local to definitions, any type variables
1369 // in other parts of the module shouldn't affect what we print for the
1370 // annotations of this constant.
1371 self.printer.clear_type_variables();
1372
1373 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1374
1375 // Only offer the code action if the cursor is over the statement
1376 if !overlaps(code_action_range, self.params.range) {
1377 return;
1378 }
1379
1380 // We don't need to add an annotation if there already is one
1381 if constant.annotation.is_some() {
1382 return;
1383 }
1384
1385 self.edits.insert(
1386 constant.name_location.end,
1387 format!(": {}", self.printer.print_type(&constant.type_)),
1388 );
1389 }
1390
1391 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1392 // Since type variable names are local to definitions, any type variables
1393 // in other parts of the module shouldn't affect what we print for the
1394 // annotations of this functions. The only variables which cannot clash
1395 // are ones defined in the signature of this function, which we register
1396 // when we visit the parameters of this function inside `collect_type_variables`.
1397 self.printer.clear_type_variables();
1398 collect_type_variables(&mut self.printer, fun);
1399
1400 ast::visit::visit_typed_function(self, fun);
1401
1402 let code_action_range = self.edits.src_span_to_lsp_range(
1403 fun.body_start
1404 .map(|body_start| SrcSpan {
1405 start: fun.location.start,
1406 end: body_start,
1407 })
1408 .unwrap_or(fun.location),
1409 );
1410
1411 // Only offer the code action if the cursor is over the statement
1412 if !overlaps(code_action_range, self.params.range) {
1413 return;
1414 }
1415
1416 // Annotate each argument separately
1417 for argument in fun.arguments.iter() {
1418 // Don't annotate the argument if it's already annotated
1419 if argument.annotation.is_some() {
1420 continue;
1421 }
1422
1423 self.edits.insert(
1424 argument.location.end,
1425 format!(": {}", self.printer.print_type(&argument.type_)),
1426 );
1427 }
1428
1429 // Annotate the return type if it isn't already annotated
1430 if fun.return_annotation.is_none() {
1431 self.edits.insert(
1432 fun.location.end,
1433 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1434 );
1435 }
1436 }
1437
1438 fn visit_typed_expr_fn(
1439 &mut self,
1440 location: &'ast SrcSpan,
1441 type_: &'ast Arc<Type>,
1442 kind: &'ast FunctionLiteralKind,
1443 arguments: &'ast [TypedArg],
1444 body: &'ast Vec1<TypedStatement>,
1445 return_annotation: &'ast Option<ast::TypeAst>,
1446 ) {
1447 ast::visit::visit_typed_expr_fn(
1448 self,
1449 location,
1450 type_,
1451 kind,
1452 arguments,
1453 body,
1454 return_annotation,
1455 );
1456
1457 // If the function doesn't have a head, we can't annotate it
1458 let location = match kind {
1459 // Function captures don't need any type annotations
1460 FunctionLiteralKind::Capture { .. } => return,
1461 FunctionLiteralKind::Anonymous { head } => head,
1462 FunctionLiteralKind::Use { location } => location,
1463 };
1464
1465 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1466
1467 // Only offer the code action if the cursor is over the expression
1468 if !overlaps(code_action_range, self.params.range) {
1469 return;
1470 }
1471
1472 // Annotate each argument separately
1473 for argument in arguments.iter() {
1474 // Don't annotate the argument if it's already annotated
1475 if argument.annotation.is_some() {
1476 continue;
1477 }
1478
1479 self.edits.insert(
1480 argument.location.end,
1481 format!(": {}", self.printer.print_type(&argument.type_)),
1482 );
1483 }
1484
1485 // Annotate the return type if it isn't already annotated, and this is
1486 // an anonymous function.
1487 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1488 let return_type = &type_.return_type().expect("Type must be a function");
1489 let pretty_type = self.printer.print_type(return_type);
1490 self.edits
1491 .insert(location.end, format!(" -> {pretty_type}"));
1492 }
1493 }
1494}
1495
1496impl<'a> AddAnnotations<'a> {
1497 pub fn new(
1498 module: &'a Module,
1499 line_numbers: &'a LineNumbers,
1500 params: &'a CodeActionParams,
1501 ) -> Self {
1502 Self {
1503 module,
1504 params,
1505 edits: TextEdits::new(line_numbers),
1506 // We need to use the same printer for all the edits because otherwise
1507 // we could get duplicate type variable names.
1508 printer: Printer::new_without_type_variables(&module.ast.names),
1509 }
1510 }
1511
1512 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1513 self.visit_typed_module(&self.module.ast);
1514
1515 let uri = &self.params.text_document.uri;
1516
1517 let title = match self.edits.edits.len() {
1518 // We don't offer a code action if there is no action to perform
1519 0 => return,
1520 1 => "Add type annotation",
1521 _ => "Add type annotations",
1522 };
1523
1524 CodeActionBuilder::new(title)
1525 .kind(CodeActionKind::REFACTOR)
1526 .changes(uri.clone(), self.edits.edits)
1527 .preferred(false)
1528 .push_to(actions);
1529 }
1530}
1531
1532/// Code action to add type annotations to all top level definitions
1533///
1534pub struct AnnotateTopLevelDefinitions<'a> {
1535 module: &'a Module,
1536 params: &'a CodeActionParams,
1537 edits: TextEdits<'a>,
1538 is_hovering_definition_requiring_annotations: bool,
1539}
1540
1541impl<'a> AnnotateTopLevelDefinitions<'a> {
1542 pub fn new(
1543 module: &'a Module,
1544 line_numbers: &'a LineNumbers,
1545 params: &'a CodeActionParams,
1546 ) -> Self {
1547 Self {
1548 module,
1549 params,
1550 edits: TextEdits::new(line_numbers),
1551 is_hovering_definition_requiring_annotations: false,
1552 }
1553 }
1554
1555 pub fn code_actions(mut self) -> Vec<CodeAction> {
1556 self.visit_typed_module(&self.module.ast);
1557
1558 // We only want to trigger the action if we're over one of the definition
1559 // which is lacking some annotations in the module
1560 if !self.is_hovering_definition_requiring_annotations {
1561 return vec![];
1562 };
1563
1564 let mut action = Vec::with_capacity(1);
1565 CodeActionBuilder::new("Annotate all top level definitions")
1566 .kind(CodeActionKind::REFACTOR_REWRITE)
1567 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1568 .preferred(false)
1569 .push_to(&mut action);
1570 action
1571 }
1572}
1573
1574impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> {
1575 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1576 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1577
1578 // We don't need to add an annotation if there already is one
1579 if constant.annotation.is_some() {
1580 return;
1581 }
1582
1583 // We're hovering definition which needs some annotations
1584 if overlaps(code_action_range, self.params.range) {
1585 self.is_hovering_definition_requiring_annotations = true;
1586 }
1587
1588 self.edits.insert(
1589 constant.name_location.end,
1590 format!(
1591 ": {}",
1592 // Create new printer to ignore type variables from other definitions
1593 Printer::new_without_type_variables(&self.module.ast.names)
1594 .print_type(&constant.type_)
1595 ),
1596 );
1597 }
1598
1599 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1600 // Don't annotate already annotated arguments
1601 let arguments_to_annotate = fun
1602 .arguments
1603 .iter()
1604 .filter(|argument| argument.annotation.is_none())
1605 .collect::<Vec<_>>();
1606 let needs_return_annotation = fun.return_annotation.is_none();
1607
1608 if arguments_to_annotate.is_empty() && !needs_return_annotation {
1609 return;
1610 }
1611
1612 let code_action_range = self.edits.src_span_to_lsp_range(fun.location);
1613 if overlaps(code_action_range, self.params.range) {
1614 self.is_hovering_definition_requiring_annotations = true;
1615 }
1616
1617 // Create new printer to ignore type variables from other definitions
1618 let mut printer = Printer::new_without_type_variables(&self.module.ast.names);
1619 collect_type_variables(&mut printer, fun);
1620
1621 // Annotate each argument separately
1622 for argument in arguments_to_annotate {
1623 self.edits.insert(
1624 argument.location.end,
1625 format!(": {}", printer.print_type(&argument.type_)),
1626 );
1627 }
1628
1629 // Annotate the return type if it isn't already annotated
1630 if needs_return_annotation {
1631 self.edits.insert(
1632 fun.location.end,
1633 format!(" -> {}", printer.print_type(&fun.return_type)),
1634 );
1635 }
1636 }
1637}
1638
1639struct TypeVariableCollector<'a, 'b> {
1640 printer: &'a mut Printer<'b>,
1641}
1642
1643/// Collect type variables defined within a function and register them for a
1644/// `Printer`
1645fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) {
1646 TypeVariableCollector { printer }.visit_typed_function(function);
1647}
1648
1649impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> {
1650 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) {
1651 // Register this type variable so that we don't duplicate names when
1652 // adding annotations.
1653 self.printer.register_type_variable(name.clone());
1654 }
1655}
1656
1657pub struct QualifiedConstructor<'a> {
1658 import: &'a Import<EcoString>,
1659 used_name: EcoString,
1660 constructor: EcoString,
1661 layer: ast::Layer,
1662}
1663
1664impl QualifiedConstructor<'_> {
1665 fn constructor_import(&self) -> String {
1666 if self.layer.is_value() {
1667 self.constructor.to_string()
1668 } else {
1669 format!("type {}", self.constructor)
1670 }
1671 }
1672}
1673
1674pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1675 module: &'a Module,
1676 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1677 params: &'a CodeActionParams,
1678 line_numbers: &'a LineNumbers,
1679 qualified_constructor: Option<QualifiedConstructor<'a>>,
1680}
1681
1682impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1683 fn new(
1684 module: &'a Module,
1685 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1686 params: &'a CodeActionParams,
1687 line_numbers: &'a LineNumbers,
1688 ) -> Self {
1689 Self {
1690 module,
1691 compiler,
1692 params,
1693 line_numbers,
1694 qualified_constructor: None,
1695 }
1696 }
1697
1698 fn get_module_import(
1699 &self,
1700 module_name: &EcoString,
1701 constructor: &EcoString,
1702 layer: ast::Layer,
1703 ) -> Option<&'a Import<EcoString>> {
1704 let mut matching_import = None;
1705
1706 for import in &self.module.ast.definitions.imports {
1707 if import.used_name().as_deref() == Some(module_name)
1708 && let Some(module) = self.compiler.get_module_interface(&import.module)
1709 {
1710 // If the import is the one we're referring to, we see if the
1711 // referred module exports the type/value we are trying to
1712 // unqualify: we don't want to offer the action indiscriminately if
1713 // it would generate invalid code!
1714 let module_exports_constructor = match layer {
1715 ast::Layer::Value => module.get_public_value(constructor).is_some(),
1716 ast::Layer::Type => module.get_public_type(constructor).is_some(),
1717 };
1718 if module_exports_constructor {
1719 matching_import = Some(import);
1720 }
1721 } else {
1722 // If the import refers to another module we still want to check
1723 // if in its unqualified import list there is a name that's equal
1724 // to the one we're trying to unqualify. In this case we can't
1725 // offer the action as it would generate invalid code.
1726 //
1727 // For example:
1728 // ```gleam
1729 // import wibble.{Some}
1730 // import option
1731 //
1732 // pub fn something() {
1733 // option.Some(1)
1734 // ^^^^ We can't unqualify this because `Some` is already
1735 // imported unqualified from the `wibble` module
1736 // }
1737 // ```
1738 //
1739 let imported = match layer {
1740 ast::Layer::Value => &import.unqualified_values,
1741 ast::Layer::Type => &import.unqualified_types,
1742 };
1743 let constructor_already_imported_by_other_module = imported
1744 .iter()
1745 .any(|value| value.used_name() == constructor);
1746
1747 if constructor_already_imported_by_other_module {
1748 return None;
1749 }
1750 }
1751 }
1752
1753 matching_import
1754 }
1755}
1756
1757impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> {
1758 fn visit_type_ast_constructor(
1759 &mut self,
1760 location: &'ast SrcSpan,
1761 name: &'ast TypeAstConstructorName,
1762 arguments: &'ast [ast::TypeAst],
1763 arguments_types: Option<Vec<Arc<Type>>>,
1764 ) {
1765 let range = src_span_to_lsp_range(*location, self.line_numbers);
1766 if overlaps(self.params.range, range)
1767 && let Some(module_alias) = name.module_name()
1768 && let Some(name) = name.name()
1769 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type)
1770 {
1771 self.qualified_constructor = Some(QualifiedConstructor {
1772 import,
1773 used_name: module_alias.clone(),
1774 constructor: name.clone(),
1775 layer: ast::Layer::Type,
1776 });
1777 }
1778 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
1779 }
1780
1781 fn visit_typed_expr_module_select(
1782 &mut self,
1783 location: &'ast SrcSpan,
1784 field_start: &'ast u32,
1785 type_: &'ast Arc<Type>,
1786 label: &'ast EcoString,
1787 module_name: &'ast EcoString,
1788 module_alias: &'ast EcoString,
1789 constructor: &'ast ModuleValueConstructor,
1790 ) {
1791 // When hovering over a Record Value Constructor, we want to expand the source span to
1792 // include the module name:
1793 // option.Some
1794 // ↑
1795 // This allows us to offer a code action when hovering over the module name.
1796 let range = src_span_to_lsp_range(*location, self.line_numbers);
1797 if overlaps(self.params.range, range)
1798 && let ModuleValueConstructor::Record {
1799 name: constructor_name,
1800 ..
1801 } = constructor
1802 && let Some(import) =
1803 self.get_module_import(module_alias, constructor_name, ast::Layer::Value)
1804 {
1805 self.qualified_constructor = Some(QualifiedConstructor {
1806 import,
1807 used_name: module_alias.clone(),
1808 constructor: constructor_name.clone(),
1809 layer: ast::Layer::Value,
1810 });
1811 }
1812 ast::visit::visit_typed_expr_module_select(
1813 self,
1814 location,
1815 field_start,
1816 type_,
1817 label,
1818 module_name,
1819 module_alias,
1820 constructor,
1821 )
1822 }
1823
1824 fn visit_typed_pattern_constructor(
1825 &mut self,
1826 location: &'ast SrcSpan,
1827 name_location: &'ast SrcSpan,
1828 name: &'ast EcoString,
1829 arguments: &'ast Vec<CallArg<TypedPattern>>,
1830 module: &'ast Option<(EcoString, SrcSpan)>,
1831 constructor: &'ast Inferred<type_::PatternConstructor>,
1832 spread: &'ast Option<SrcSpan>,
1833 type_: &'ast Arc<Type>,
1834 ) {
1835 let range = src_span_to_lsp_range(*location, self.line_numbers);
1836 if overlaps(self.params.range, range)
1837 && let Some((module_alias, _)) = module
1838 && let Inferred::Known(_) = constructor
1839 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
1840 {
1841 self.qualified_constructor = Some(QualifiedConstructor {
1842 import,
1843 used_name: module_alias.clone(),
1844 constructor: name.clone(),
1845 layer: ast::Layer::Value,
1846 });
1847 }
1848 ast::visit::visit_typed_pattern_constructor(
1849 self,
1850 location,
1851 name_location,
1852 name,
1853 arguments,
1854 module,
1855 constructor,
1856 spread,
1857 type_,
1858 );
1859 }
1860
1861 fn visit_typed_constant_record(
1862 &mut self,
1863 location: &'ast SrcSpan,
1864 module: &'ast Option<(EcoString, SrcSpan)>,
1865 name: &'ast EcoString,
1866 arguments: &'ast Vec<CallArg<ast::TypedConstant>>,
1867 tag: &'ast EcoString,
1868 type_: &'ast Arc<Type>,
1869 field_map: &'ast Inferred<FieldMap>,
1870 record_constructor: &'ast Option<Box<ValueConstructor>>,
1871 ) {
1872 let range = src_span_to_lsp_range(*location, self.line_numbers);
1873 if overlaps(self.params.range, range)
1874 && let Some((module_alias, _)) = module
1875 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
1876 {
1877 self.qualified_constructor = Some(QualifiedConstructor {
1878 import,
1879 used_name: module_alias.clone(),
1880 constructor: name.clone(),
1881 layer: ast::Layer::Value,
1882 });
1883 }
1884 ast::visit::visit_typed_constant_record(
1885 self,
1886 location,
1887 module,
1888 name,
1889 arguments,
1890 tag,
1891 type_,
1892 field_map,
1893 record_constructor,
1894 );
1895 }
1896
1897 fn visit_typed_constant_var(
1898 &mut self,
1899 location: &'ast SrcSpan,
1900 module: &'ast Option<(EcoString, SrcSpan)>,
1901 name: &'ast EcoString,
1902 constructor: &'ast Option<Box<ValueConstructor>>,
1903 type_: &'ast Arc<Type>,
1904 ) {
1905 let range = src_span_to_lsp_range(*location, self.line_numbers);
1906 if overlaps(self.params.range, range)
1907 && let Some((module_alias, _)) = module
1908 && let Some(constructor) = constructor
1909 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant
1910 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
1911 {
1912 self.qualified_constructor = Some(QualifiedConstructor {
1913 import,
1914 used_name: module_alias.clone(),
1915 constructor: name.clone(),
1916 layer: ast::Layer::Value,
1917 });
1918 }
1919 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
1920 }
1921}
1922
1923pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
1924 module: &'a Module,
1925 params: &'a CodeActionParams,
1926 edits: TextEdits<'a>,
1927 qualified_constructor: QualifiedConstructor<'a>,
1928}
1929
1930impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
1931 pub fn new(
1932 module: &'a Module,
1933 params: &'a CodeActionParams,
1934 line_numbers: &'a LineNumbers,
1935 qualified_constructor: QualifiedConstructor<'a>,
1936 ) -> Self {
1937 Self {
1938 module,
1939 params,
1940 edits: TextEdits::new(line_numbers),
1941 qualified_constructor,
1942 }
1943 }
1944
1945 pub fn code_actions(mut self) -> Vec<CodeAction> {
1946 self.visit_typed_module(&self.module.ast);
1947 if self.edits.edits.is_empty() {
1948 return vec![];
1949 }
1950 self.edit_import();
1951 let mut action = Vec::with_capacity(1);
1952 CodeActionBuilder::new(&format!(
1953 "Unqualify {}.{}",
1954 self.qualified_constructor.used_name, self.qualified_constructor.constructor
1955 ))
1956 .kind(CodeActionKind::REFACTOR)
1957 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1958 .preferred(false)
1959 .push_to(&mut action);
1960 action
1961 }
1962
1963 fn remove_module_qualifier(&mut self, location: SrcSpan) {
1964 self.edits.delete(SrcSpan {
1965 start: location.start,
1966 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
1967 })
1968 }
1969
1970 fn edit_import(&mut self) {
1971 let QualifiedConstructor {
1972 constructor,
1973 layer,
1974 import,
1975 ..
1976 } = &self.qualified_constructor;
1977 let is_imported = if layer.is_value() {
1978 import
1979 .unqualified_values
1980 .iter()
1981 .any(|value| value.used_name() == constructor)
1982 } else {
1983 import
1984 .unqualified_types
1985 .iter()
1986 .any(|type_| type_.used_name() == constructor)
1987 };
1988 if is_imported {
1989 return;
1990 }
1991 let (insert_pos, new_text) = edits::insert_unqualified_import(
1992 import,
1993 &self.module.code,
1994 self.qualified_constructor.constructor_import(),
1995 );
1996 let span = SrcSpan::new(insert_pos, insert_pos);
1997 self.edits.replace(span, new_text);
1998 }
1999}
2000
2001impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
2002 fn visit_type_ast_constructor(
2003 &mut self,
2004 location: &'ast SrcSpan,
2005 name: &'ast TypeAstConstructorName,
2006 arguments: &'ast [ast::TypeAst],
2007 arguments_types: Option<Vec<Arc<Type>>>,
2008 ) {
2009 if let Some(module_name) = name.module_name()
2010 && let Some(name) = name.name()
2011 {
2012 let QualifiedConstructor {
2013 used_name,
2014 constructor,
2015 layer,
2016 ..
2017 } = &self.qualified_constructor;
2018
2019 if !layer.is_value() && used_name == module_name && name == constructor {
2020 self.remove_module_qualifier(*location);
2021 }
2022 }
2023 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2024 }
2025
2026 fn visit_typed_expr_module_select(
2027 &mut self,
2028 location: &'ast SrcSpan,
2029 field_start: &'ast u32,
2030 type_: &'ast Arc<Type>,
2031 label: &'ast EcoString,
2032 module_name: &'ast EcoString,
2033 module_alias: &'ast EcoString,
2034 constructor: &'ast ModuleValueConstructor,
2035 ) {
2036 if let ModuleValueConstructor::Record { name, .. } = constructor {
2037 let QualifiedConstructor {
2038 used_name,
2039 constructor,
2040 layer,
2041 ..
2042 } = &self.qualified_constructor;
2043
2044 if layer.is_value() && used_name == module_alias && name == constructor {
2045 self.remove_module_qualifier(*location);
2046 }
2047 }
2048 ast::visit::visit_typed_expr_module_select(
2049 self,
2050 location,
2051 field_start,
2052 type_,
2053 label,
2054 module_name,
2055 module_alias,
2056 constructor,
2057 )
2058 }
2059
2060 fn visit_typed_pattern_constructor(
2061 &mut self,
2062 location: &'ast SrcSpan,
2063 name_location: &'ast SrcSpan,
2064 name: &'ast EcoString,
2065 arguments: &'ast Vec<CallArg<TypedPattern>>,
2066 module: &'ast Option<(EcoString, SrcSpan)>,
2067 constructor: &'ast Inferred<type_::PatternConstructor>,
2068 spread: &'ast Option<SrcSpan>,
2069 type_: &'ast Arc<Type>,
2070 ) {
2071 if let Some((module_alias, _)) = module
2072 && let Inferred::Known(_) = constructor
2073 {
2074 let QualifiedConstructor {
2075 used_name,
2076 constructor,
2077 layer,
2078 ..
2079 } = &self.qualified_constructor;
2080
2081 if layer.is_value() && used_name == module_alias && name == constructor {
2082 self.remove_module_qualifier(*location);
2083 }
2084 }
2085 ast::visit::visit_typed_pattern_constructor(
2086 self,
2087 location,
2088 name_location,
2089 name,
2090 arguments,
2091 module,
2092 constructor,
2093 spread,
2094 type_,
2095 );
2096 }
2097
2098 fn visit_typed_constant_record(
2099 &mut self,
2100 location: &'ast SrcSpan,
2101 module: &'ast Option<(EcoString, SrcSpan)>,
2102 name: &'ast EcoString,
2103 arguments: &'ast Vec<CallArg<ast::TypedConstant>>,
2104 tag: &'ast EcoString,
2105 type_: &'ast Arc<Type>,
2106 field_map: &'ast Inferred<FieldMap>,
2107 record_constructor: &'ast Option<Box<ValueConstructor>>,
2108 ) {
2109 if let Some((module_alias, _)) = module {
2110 let QualifiedConstructor {
2111 used_name,
2112 constructor,
2113 layer,
2114 ..
2115 } = &self.qualified_constructor;
2116
2117 if layer.is_value() && used_name == module_alias && name == constructor {
2118 self.remove_module_qualifier(*location);
2119 }
2120 }
2121 ast::visit::visit_typed_constant_record(
2122 self,
2123 location,
2124 module,
2125 name,
2126 arguments,
2127 tag,
2128 type_,
2129 field_map,
2130 record_constructor,
2131 );
2132 }
2133
2134 fn visit_typed_constant_var(
2135 &mut self,
2136 location: &'ast SrcSpan,
2137 module: &'ast Option<(EcoString, SrcSpan)>,
2138 name: &'ast EcoString,
2139 constructor: &'ast Option<Box<ValueConstructor>>,
2140 type_: &'ast Arc<Type>,
2141 ) {
2142 if let Some((module_alias, _)) = module {
2143 let QualifiedConstructor {
2144 used_name,
2145 constructor: wanted_constructor,
2146 layer,
2147 ..
2148 } = &self.qualified_constructor;
2149
2150 if layer.is_value() && used_name == module_alias && name == wanted_constructor {
2151 self.remove_module_qualifier(*location);
2152 }
2153 }
2154 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2155 }
2156}
2157
2158pub fn code_action_convert_qualified_constructor_to_unqualified<IO>(
2159 module: &Module,
2160 compiler: &LspProjectCompiler<FileSystemProxy<IO>>,
2161 line_numbers: &LineNumbers,
2162 params: &CodeActionParams,
2163 actions: &mut Vec<CodeAction>,
2164) {
2165 let mut first_pass =
2166 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers);
2167 first_pass.visit_typed_module(&module.ast);
2168 let Some(qualified_constructor) = first_pass.qualified_constructor else {
2169 return;
2170 };
2171 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
2172 module,
2173 params,
2174 line_numbers,
2175 qualified_constructor,
2176 );
2177 let new_actions = second_pass.code_actions();
2178 actions.extend(new_actions);
2179}
2180
2181struct UnqualifiedConstructor<'a> {
2182 module_name: EcoString,
2183 constructor: &'a ast::UnqualifiedImport,
2184 layer: ast::Layer,
2185}
2186
2187struct UnqualifiedToQualifiedImportFirstPass<'a> {
2188 module: &'a Module,
2189 params: &'a CodeActionParams,
2190 line_numbers: &'a LineNumbers,
2191 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
2192}
2193
2194impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
2195 fn new(
2196 module: &'a Module,
2197 params: &'a CodeActionParams,
2198 line_numbers: &'a LineNumbers,
2199 ) -> Self {
2200 Self {
2201 module,
2202 params,
2203 line_numbers,
2204 unqualified_constructor: None,
2205 }
2206 }
2207
2208 fn get_module_import_from_value_constructor(
2209 &mut self,
2210 module_name: &EcoString,
2211 constructor_name: &EcoString,
2212 ) {
2213 self.unqualified_constructor = self
2214 .module
2215 .ast
2216 .definitions
2217 .imports
2218 .iter()
2219 .filter(|import| import.module == *module_name)
2220 .find_map(|import| {
2221 import
2222 .unqualified_values
2223 .iter()
2224 .find(|value| value.used_name() == constructor_name)
2225 .and_then(|value| {
2226 Some(UnqualifiedConstructor {
2227 constructor: value,
2228 module_name: import.used_name()?,
2229 layer: ast::Layer::Value,
2230 })
2231 })
2232 })
2233 }
2234
2235 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
2236 self.unqualified_constructor =
2237 self.module
2238 .ast
2239 .definitions
2240 .imports
2241 .iter()
2242 .find_map(|import| {
2243 if let Some(ty) = import
2244 .unqualified_types
2245 .iter()
2246 .find(|ty| ty.used_name() == constructor_name)
2247 {
2248 return Some(UnqualifiedConstructor {
2249 constructor: ty,
2250 module_name: import.used_name()?,
2251 layer: ast::Layer::Type,
2252 });
2253 }
2254 None
2255 })
2256 }
2257}
2258
2259impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
2260 fn visit_type_ast_constructor(
2261 &mut self,
2262 location: &'ast SrcSpan,
2263 name: &'ast TypeAstConstructorName,
2264 arguments: &'ast [ast::TypeAst],
2265 arguments_types: Option<Vec<Arc<Type>>>,
2266 ) {
2267 if !name.is_qualified()
2268 && let Some(name) = name.name()
2269 && overlaps(
2270 self.params.range,
2271 src_span_to_lsp_range(*location, self.line_numbers),
2272 )
2273 {
2274 self.get_module_import_from_type_constructor(name);
2275 }
2276
2277 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2278 }
2279
2280 fn visit_typed_expr_var(
2281 &mut self,
2282 location: &'ast SrcSpan,
2283 constructor: &'ast ValueConstructor,
2284 name: &'ast EcoString,
2285 ) {
2286 let range = src_span_to_lsp_range(*location, self.line_numbers);
2287 if overlaps(self.params.range, range)
2288 && let Some(module_name) = match &constructor.variant {
2289 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2290 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2291 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2292
2293 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2294 }
2295 {
2296 self.get_module_import_from_value_constructor(module_name, name);
2297 }
2298 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2299 }
2300
2301 fn visit_typed_pattern_constructor(
2302 &mut self,
2303 location: &'ast SrcSpan,
2304 name_location: &'ast SrcSpan,
2305 name: &'ast EcoString,
2306 arguments: &'ast Vec<CallArg<TypedPattern>>,
2307 module: &'ast Option<(EcoString, SrcSpan)>,
2308 constructor: &'ast Inferred<type_::PatternConstructor>,
2309 spread: &'ast Option<SrcSpan>,
2310 type_: &'ast Arc<Type>,
2311 ) {
2312 if module.is_none()
2313 && overlaps(
2314 self.params.range,
2315 src_span_to_lsp_range(*location, self.line_numbers),
2316 )
2317 && let Inferred::Known(constructor) = constructor
2318 {
2319 self.get_module_import_from_value_constructor(&constructor.module, name);
2320 }
2321
2322 ast::visit::visit_typed_pattern_constructor(
2323 self,
2324 location,
2325 name_location,
2326 name,
2327 arguments,
2328 module,
2329 constructor,
2330 spread,
2331 type_,
2332 );
2333 }
2334
2335 fn visit_typed_constant_record(
2336 &mut self,
2337 location: &'ast SrcSpan,
2338 module: &'ast Option<(EcoString, SrcSpan)>,
2339 name: &'ast EcoString,
2340 arguments: &'ast Vec<CallArg<ast::TypedConstant>>,
2341 _tag: &'ast EcoString,
2342 _type_: &'ast Arc<Type>,
2343 _field_map: &'ast Inferred<FieldMap>,
2344 record_constructor: &'ast Option<Box<ValueConstructor>>,
2345 ) {
2346 if module.is_none()
2347 && overlaps(
2348 self.params.range,
2349 src_span_to_lsp_range(*location, self.line_numbers),
2350 )
2351 && let Some(record_constructor) = record_constructor
2352 && let Some(module_name) = match &record_constructor.variant {
2353 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2354 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2355 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2356
2357 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2358 }
2359 {
2360 self.get_module_import_from_value_constructor(module_name, name);
2361 }
2362 ast::visit::visit_typed_constant_record(
2363 self,
2364 location,
2365 module,
2366 name,
2367 arguments,
2368 _tag,
2369 _type_,
2370 _field_map,
2371 record_constructor,
2372 );
2373 }
2374
2375 fn visit_typed_constant_var(
2376 &mut self,
2377 location: &'ast SrcSpan,
2378 module: &'ast Option<(EcoString, SrcSpan)>,
2379 name: &'ast EcoString,
2380 constructor: &'ast Option<Box<ValueConstructor>>,
2381 type_: &'ast Arc<Type>,
2382 ) {
2383 if module.is_none()
2384 && overlaps(
2385 self.params.range,
2386 src_span_to_lsp_range(*location, self.line_numbers),
2387 )
2388 && let Some(constructor) = constructor
2389 && let Some(module_name) = match &constructor.variant {
2390 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2391 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2392 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2393
2394 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2395 }
2396 {
2397 self.get_module_import_from_value_constructor(module_name, name);
2398 }
2399 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2400 }
2401}
2402
2403struct UnqualifiedToQualifiedImportSecondPass<'a> {
2404 module: &'a Module,
2405 params: &'a CodeActionParams,
2406 edits: TextEdits<'a>,
2407 unqualified_constructor: UnqualifiedConstructor<'a>,
2408}
2409
2410impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2411 pub fn new(
2412 module: &'a Module,
2413 params: &'a CodeActionParams,
2414 line_numbers: &'a LineNumbers,
2415 unqualified_constructor: UnqualifiedConstructor<'a>,
2416 ) -> Self {
2417 Self {
2418 module,
2419 params,
2420 edits: TextEdits::new(line_numbers),
2421 unqualified_constructor,
2422 }
2423 }
2424
2425 fn add_module_qualifier(&mut self, location: SrcSpan) {
2426 let src_span = SrcSpan::new(
2427 location.start,
2428 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
2429 );
2430
2431 self.edits.replace(
2432 src_span,
2433 format!(
2434 "{}.{}",
2435 self.unqualified_constructor.module_name,
2436 self.unqualified_constructor.constructor.name
2437 ),
2438 );
2439 }
2440
2441 pub fn code_actions(mut self) -> Vec<CodeAction> {
2442 self.visit_typed_module(&self.module.ast);
2443 if self.edits.edits.is_empty() {
2444 return vec![];
2445 }
2446 self.edit_import();
2447 let mut action = Vec::with_capacity(1);
2448 let UnqualifiedConstructor {
2449 module_name,
2450 constructor,
2451 ..
2452 } = self.unqualified_constructor;
2453 CodeActionBuilder::new(&format!(
2454 "Qualify {} as {}.{}",
2455 constructor.used_name(),
2456 module_name,
2457 constructor.name,
2458 ))
2459 .kind(CodeActionKind::REFACTOR)
2460 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2461 .preferred(false)
2462 .push_to(&mut action);
2463 action
2464 }
2465
2466 fn edit_import(&mut self) {
2467 let UnqualifiedConstructor {
2468 constructor:
2469 ast::UnqualifiedImport {
2470 location: constructor_import_span,
2471 ..
2472 },
2473 ..
2474 } = self.unqualified_constructor;
2475
2476 let mut last_char_pos = constructor_import_span.end as usize;
2477 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2478 last_char_pos += 1;
2479 }
2480 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2481 last_char_pos += 1;
2482 }
2483 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2484 last_char_pos += 1;
2485 }
2486
2487 self.edits.delete(SrcSpan::new(
2488 constructor_import_span.start,
2489 last_char_pos as u32,
2490 ));
2491 }
2492}
2493
2494impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2495 fn visit_type_ast_constructor(
2496 &mut self,
2497 location: &'ast SrcSpan,
2498 name: &'ast TypeAstConstructorName,
2499 arguments: &'ast [ast::TypeAst],
2500 arguments_types: Option<Vec<Arc<Type>>>,
2501 ) {
2502 if !name.is_qualified()
2503 && let Some(name) = name.name()
2504 {
2505 let UnqualifiedConstructor {
2506 constructor, layer, ..
2507 } = &self.unqualified_constructor;
2508 if !layer.is_value() && constructor.used_name() == name {
2509 self.add_module_qualifier(*location);
2510 }
2511 }
2512 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2513 }
2514
2515 fn visit_typed_expr_var(
2516 &mut self,
2517 location: &'ast SrcSpan,
2518 constructor: &'ast ValueConstructor,
2519 name: &'ast EcoString,
2520 ) {
2521 let UnqualifiedConstructor {
2522 constructor: wanted_constructor,
2523 layer,
2524 ..
2525 } = &self.unqualified_constructor;
2526
2527 if layer.is_value()
2528 && wanted_constructor.used_name() == name
2529 && !constructor.is_local_variable()
2530 {
2531 self.add_module_qualifier(*location);
2532 }
2533 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2534 }
2535
2536 fn visit_typed_pattern_constructor(
2537 &mut self,
2538 location: &'ast SrcSpan,
2539 name_location: &'ast SrcSpan,
2540 name: &'ast EcoString,
2541 arguments: &'ast Vec<CallArg<TypedPattern>>,
2542 module: &'ast Option<(EcoString, SrcSpan)>,
2543 constructor: &'ast Inferred<type_::PatternConstructor>,
2544 spread: &'ast Option<SrcSpan>,
2545 type_: &'ast Arc<Type>,
2546 ) {
2547 if module.is_none() {
2548 let UnqualifiedConstructor {
2549 constructor: wanted_constructor,
2550 layer,
2551 ..
2552 } = &self.unqualified_constructor;
2553 if layer.is_value() && wanted_constructor.used_name() == name {
2554 self.add_module_qualifier(*location);
2555 }
2556 }
2557 ast::visit::visit_typed_pattern_constructor(
2558 self,
2559 location,
2560 name_location,
2561 name,
2562 arguments,
2563 module,
2564 constructor,
2565 spread,
2566 type_,
2567 );
2568 }
2569
2570 fn visit_typed_constant_record(
2571 &mut self,
2572 location: &'ast SrcSpan,
2573 module: &'ast Option<(EcoString, SrcSpan)>,
2574 name: &'ast EcoString,
2575 arguments: &'ast Vec<CallArg<ast::TypedConstant>>,
2576 tag: &'ast EcoString,
2577 type_: &'ast Arc<Type>,
2578 field_map: &'ast Inferred<FieldMap>,
2579 record_constructor: &'ast Option<Box<ValueConstructor>>,
2580 ) {
2581 if module.is_none() {
2582 let UnqualifiedConstructor {
2583 constructor: wanted_constructor,
2584 layer,
2585 ..
2586 } = &self.unqualified_constructor;
2587 if layer.is_value() && wanted_constructor.used_name() == name {
2588 self.add_module_qualifier(*location);
2589 }
2590 }
2591 ast::visit::visit_typed_constant_record(
2592 self,
2593 location,
2594 module,
2595 name,
2596 arguments,
2597 tag,
2598 type_,
2599 field_map,
2600 record_constructor,
2601 );
2602 }
2603
2604 fn visit_typed_constant_var(
2605 &mut self,
2606 location: &'ast SrcSpan,
2607 module: &'ast Option<(EcoString, SrcSpan)>,
2608 name: &'ast EcoString,
2609 constructor: &'ast Option<Box<ValueConstructor>>,
2610 type_: &'ast Arc<Type>,
2611 ) {
2612 if module.is_none() {
2613 let UnqualifiedConstructor {
2614 constructor: wanted_constructor,
2615 layer,
2616 ..
2617 } = &self.unqualified_constructor;
2618 if layer.is_value() && wanted_constructor.used_name() == name {
2619 self.add_module_qualifier(*location);
2620 }
2621 }
2622 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2623 }
2624}
2625
2626pub fn code_action_convert_unqualified_constructor_to_qualified(
2627 module: &Module,
2628 line_numbers: &LineNumbers,
2629 params: &CodeActionParams,
2630 actions: &mut Vec<CodeAction>,
2631) {
2632 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2633 first_pass.visit_typed_module(&module.ast);
2634 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2635 return;
2636 };
2637 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2638 module,
2639 params,
2640 line_numbers,
2641 unqualified_constructor,
2642 );
2643 let new_actions = second_pass.code_actions();
2644 actions.extend(new_actions);
2645}
2646
2647/// Builder for code action to apply the convert from use action, turning a use
2648/// expression into a regular function call.
2649///
2650pub struct ConvertFromUse<'a> {
2651 module: &'a Module,
2652 params: &'a CodeActionParams,
2653 edits: TextEdits<'a>,
2654 selected_use: Option<&'a TypedUse>,
2655}
2656
2657impl<'a> ConvertFromUse<'a> {
2658 pub fn new(
2659 module: &'a Module,
2660 line_numbers: &'a LineNumbers,
2661 params: &'a CodeActionParams,
2662 ) -> Self {
2663 Self {
2664 module,
2665 params,
2666 edits: TextEdits::new(line_numbers),
2667 selected_use: None,
2668 }
2669 }
2670
2671 pub fn code_actions(mut self) -> Vec<CodeAction> {
2672 self.visit_typed_module(&self.module.ast);
2673
2674 let Some(use_) = self.selected_use else {
2675 return vec![];
2676 };
2677
2678 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else {
2679 return vec![];
2680 };
2681
2682 // If the use callback we're desugaring is using labels, that means we
2683 // have to add the last argument's label when writing the callback;
2684 // otherwise, it would result in invalid code.
2685 //
2686 // use acc, item <- list.fold(over: list, from: 1)
2687 // todo
2688 //
2689 // Needs to be rewritten as:
2690 //
2691 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2692 // ^^^^^ We cannot forget to add this label back!
2693 //
2694 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) {
2695 fun.field_map()
2696 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned())
2697 .map(|label| eco_format!("{label}: "))
2698 .unwrap_or(EcoString::from(""))
2699 } else {
2700 EcoString::from("")
2701 };
2702
2703 // The use callback is not necessarily the last argument. If you have
2704 // the following function: `wibble(a a, b b) { todo }`
2705 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2706 // is going to be the use callback, not the last one!
2707 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback());
2708 let Some(CallArg {
2709 implicit: Some(ImplicitCallArgOrigin::Use),
2710 value: TypedExpr::Fn { body, type_, .. },
2711 ..
2712 }) = use_callback
2713 else {
2714 return vec![];
2715 };
2716
2717 // If there's arguments on the left hand side of the function we extract
2718 // those so we can paste them back as the anonymous function arguments.
2719 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2720 let assignments_range =
2721 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2722 self.module
2723 .code
2724 .get(assignments_range)
2725 .expect("use assignments")
2726 } else {
2727 ""
2728 };
2729
2730 // We first delete everything on the left hand side of use and the use
2731 // arrow.
2732 self.edits.delete(SrcSpan {
2733 start: use_.location.start,
2734 end: use_.right_hand_side_location.start,
2735 });
2736
2737 let use_line_end = use_.right_hand_side_location.end;
2738 let use_rhs_function_has_some_explicit_arguments = arguments
2739 .iter()
2740 .filter(|argument| !argument.is_use_implicit_callback())
2741 .peekable()
2742 .peek()
2743 .is_some();
2744
2745 let use_rhs_function_ends_with_closed_parentheses = self
2746 .module
2747 .code
2748 .get(use_line_end as usize - 1..use_line_end as usize)
2749 == Some(")");
2750
2751 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit());
2752 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2753
2754 // This is the piece of code between the end of the last argument and
2755 // the end of the use_expression:
2756 //
2757 // use <- wibble(a, b, )
2758 // ^^^^^ This piece right here, from `,` included
2759 // up to `)` excluded.
2760 //
2761 let text_after_last_argument = self
2762 .module
2763 .code
2764 .get(last_arg_end as usize..use_line_end as usize - 1);
2765 let use_rhs_has_comma_after_last_argument =
2766 text_after_last_argument.is_some_and(|code| code.contains(','));
2767 let needs_space_before_callback =
2768 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2769
2770 if use_rhs_function_ends_with_closed_parentheses {
2771 // If the function on the right hand side of use ends with a closed
2772 // parentheses then we have to remove it and add it later at the end
2773 // of the anonymous function we're inserting.
2774 //
2775 // use <- wibble()
2776 // ^ To add the fn() we need to first remove this
2777 //
2778 // So here we write over the last closed parentheses to remove it.
2779 let callback_start = format!("{callback_label}fn({assignments}) {{");
2780 self.edits.replace(
2781 SrcSpan {
2782 start: use_line_end - 1,
2783 end: use_line_end,
2784 },
2785 // If the function on the rhs of use has other orguments besides
2786 // the implicit fn expression then we need to put a comma after
2787 // the last argument.
2788 if use_rhs_function_has_some_explicit_arguments
2789 && !use_rhs_has_comma_after_last_argument
2790 {
2791 format!(", {callback_start}")
2792 } else if needs_space_before_callback {
2793 format!(" {callback_start}")
2794 } else {
2795 callback_start.to_string()
2796 },
2797 )
2798 } else {
2799 // On the other hand, if the function on the right hand side doesn't
2800 // end with a closed parenthese then we have to manually add it.
2801 //
2802 // use <- wibble
2803 // ^ No parentheses
2804 //
2805 self.edits
2806 .insert(use_line_end, format!("(fn({assignments}) {{"))
2807 };
2808
2809 // Then we have to increase indentation for all the lines of the use
2810 // body.
2811 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
2812 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
2813
2814 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
2815 self.edits.edits.push(TextEdit {
2816 range: Range {
2817 start: Position { line, character: 0 },
2818 end: Position { line, character: 0 },
2819 },
2820 new_text: " ".to_string(),
2821 })
2822 }
2823
2824 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
2825 self.edits.insert(
2826 use_.call.location().end,
2827 format!("\n{final_line_indentation}}})"),
2828 );
2829
2830 let mut action = Vec::with_capacity(1);
2831 CodeActionBuilder::new("Convert from `use`")
2832 .kind(CodeActionKind::REFACTOR_REWRITE)
2833 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2834 .preferred(false)
2835 .push_to(&mut action);
2836 action
2837 }
2838}
2839
2840impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
2841 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
2842 let use_range = self.edits.src_span_to_lsp_range(use_.location);
2843
2844 // If the use expression is using patterns that are not just variable
2845 // assignments then we can't automatically rewrite it as it would result
2846 // in a syntax error as we can't pattern match in an anonymous function
2847 // head.
2848 // At the same time we can't safely add bindings inside the anonymous
2849 // function body by picking placeholder names as we'd risk shadowing
2850 // variables coming from the outer scope.
2851 // So we just skip those use expressions we can't safely rewrite!
2852 if within(self.params.range, use_range)
2853 && use_
2854 .assignments
2855 .iter()
2856 .all(|assignment| assignment.pattern.is_variable())
2857 {
2858 self.selected_use = Some(use_);
2859 }
2860
2861 // We still want to visit the use expression so that we always end up
2862 // picking the innermost, most relevant use under the cursor.
2863 self.visit_typed_expr(&use_.call);
2864 }
2865}
2866
2867/// Builder for code action to apply the convert to use action.
2868///
2869pub struct ConvertToUse<'a> {
2870 module: &'a Module,
2871 params: &'a CodeActionParams,
2872 edits: TextEdits<'a>,
2873 selected_call: Option<CallLocations>,
2874}
2875
2876/// All the locations we'll need to transform a function call into a use
2877/// expression.
2878///
2879struct CallLocations {
2880 call_span: SrcSpan,
2881 called_function_span: SrcSpan,
2882 callback_arguments_span: Option<SrcSpan>,
2883 arg_before_callback_span: Option<SrcSpan>,
2884 callback_body_span: SrcSpan,
2885}
2886
2887impl<'a> ConvertToUse<'a> {
2888 pub fn new(
2889 module: &'a Module,
2890 line_numbers: &'a LineNumbers,
2891 params: &'a CodeActionParams,
2892 ) -> Self {
2893 Self {
2894 module,
2895 params,
2896 edits: TextEdits::new(line_numbers),
2897 selected_call: None,
2898 }
2899 }
2900
2901 pub fn code_actions(mut self) -> Vec<CodeAction> {
2902 self.visit_typed_module(&self.module.ast);
2903
2904 let Some(CallLocations {
2905 call_span,
2906 called_function_span,
2907 callback_arguments_span,
2908 arg_before_callback_span,
2909 callback_body_span,
2910 }) = self.selected_call
2911 else {
2912 return vec![];
2913 };
2914
2915 // This is the nesting level of the `use` keyword we've inserted, we
2916 // want to move the entire body of the anonymous function to this level.
2917 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
2918 let indentation = " ".repeat(use_nesting_level as usize);
2919
2920 // First we move the callback arguments to the left hand side of the
2921 // call and add the `use` keyword.
2922 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span {
2923 let arguments_start = arguments_location.start as usize;
2924 let arguments_end = arguments_location.end as usize;
2925 let arguments_text = self
2926 .module
2927 .code
2928 .get(arguments_start..arguments_end)
2929 .expect("fn args");
2930 format!("use {arguments_text} <- ")
2931 } else {
2932 "use <- ".into()
2933 };
2934
2935 self.edits.insert(call_span.start, left_hand_side_text);
2936
2937 match arg_before_callback_span {
2938 // If the function call has no other arguments besides the callback then
2939 // we just have to remove the `fn(...) {` part.
2940 //
2941 // wibble(fn(...) { ... })
2942 // ^^^^^^^^^^ This goes from the end of the called function
2943 // To the start of the first thing in the anonymous
2944 // function's body.
2945 //
2946 None => self.edits.replace(
2947 SrcSpan::new(called_function_span.end, callback_body_span.start),
2948 format!("\n{indentation}"),
2949 ),
2950 // If it has other arguments we'll have to remove those and add a closed
2951 // parentheses too:
2952 //
2953 // wibble(1, 2, fn(...) { ... })
2954 // ^^^^^^^^^^^ We have to replace this with a `)`, it
2955 // goes from the end of the second-to-last
2956 // argument to the start of the first thing
2957 // in the anonymous function's body.
2958 //
2959 Some(arg_before_callback) => self.edits.replace(
2960 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
2961 format!(")\n{indentation}"),
2962 ),
2963 };
2964
2965 // Then we have to remove two spaces of indentation from each line of
2966 // the callback function's body.
2967 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
2968 for line in body_range.start.line + 1..=body_range.end.line {
2969 self.edits.delete_range(Range::new(
2970 Position { line, character: 0 },
2971 Position { line, character: 2 },
2972 ))
2973 }
2974
2975 // Then we have to remove the anonymous fn closing `}` and the call's
2976 // closing `)`.
2977 self.edits
2978 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
2979
2980 let mut action = Vec::with_capacity(1);
2981 CodeActionBuilder::new("Convert to `use`")
2982 .kind(CodeActionKind::REFACTOR_REWRITE)
2983 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2984 .preferred(false)
2985 .push_to(&mut action);
2986 action
2987 }
2988}
2989
2990impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
2991 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
2992 // The cursor has to be inside the last statement of the function to
2993 // offer the code action.
2994 if let Some(last) = &fun.body.last()
2995 && within(
2996 self.params.range,
2997 self.edits.src_span_to_lsp_range(last.location()),
2998 )
2999 && let Some(call_data) = turn_statement_into_use(last)
3000 {
3001 self.selected_call = Some(call_data);
3002 }
3003
3004 ast::visit::visit_typed_function(self, fun)
3005 }
3006
3007 fn visit_typed_expr_fn(
3008 &mut self,
3009 location: &'ast SrcSpan,
3010 type_: &'ast Arc<Type>,
3011 kind: &'ast FunctionLiteralKind,
3012 arguments: &'ast [TypedArg],
3013 body: &'ast Vec1<TypedStatement>,
3014 return_annotation: &'ast Option<ast::TypeAst>,
3015 ) {
3016 // The cursor has to be inside the last statement of the body to
3017 // offer the code action.
3018 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
3019 if within(self.params.range, last_statement_range)
3020 && let Some(call_data) = turn_statement_into_use(body.last())
3021 {
3022 self.selected_call = Some(call_data);
3023 }
3024
3025 ast::visit::visit_typed_expr_fn(
3026 self,
3027 location,
3028 type_,
3029 kind,
3030 arguments,
3031 body,
3032 return_annotation,
3033 );
3034 }
3035
3036 fn visit_typed_expr_block(
3037 &mut self,
3038 location: &'ast SrcSpan,
3039 statements: &'ast [TypedStatement],
3040 ) {
3041 let Some(last_statement) = statements.last() else {
3042 return;
3043 };
3044
3045 // The cursor has to be inside the last statement of the block to offer
3046 // the code action.
3047 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
3048 if within(self.params.range, statement_range) {
3049 // Only the last statement of a block can be turned into a use!
3050 if let Some(selected_call) = turn_statement_into_use(last_statement) {
3051 self.selected_call = Some(selected_call)
3052 }
3053 }
3054
3055 ast::visit::visit_typed_expr_block(self, location, statements);
3056 }
3057}
3058
3059fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
3060 match statement {
3061 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
3062 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
3063 }
3064}
3065
3066fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
3067 let TypedExpr::Call {
3068 arguments,
3069 location: call_span,
3070 fun: called_function,
3071 ..
3072 } = expr
3073 else {
3074 return None;
3075 };
3076
3077 // The function arguments in the ast are reordered using function's field map.
3078 // This means that in the `args` array they might not appear in the same order
3079 // in which they are written by the user. Since the rest of the code relies
3080 // on their order in the written code we first have to sort them by their
3081 // source position.
3082 let arguments = arguments
3083 .iter()
3084 .sorted_by_key(|argument| argument.location.start)
3085 .collect_vec();
3086
3087 let CallArg {
3088 value: last_arg,
3089 implicit: None,
3090 ..
3091 } = arguments.last()?
3092 else {
3093 return None;
3094 };
3095
3096 let TypedExpr::Fn {
3097 arguments: callback_arguments,
3098 body,
3099 ..
3100 } = last_arg
3101 else {
3102 return None;
3103 };
3104
3105 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) {
3106 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
3107 _ => None,
3108 };
3109
3110 let arg_before_callback_span = if arguments.len() >= 2 {
3111 arguments
3112 .get(arguments.len() - 2)
3113 .map(|call_arg| call_arg.location)
3114 } else {
3115 None
3116 };
3117
3118 let callback_body_span = body.first().location().merge(&body.last().last_location());
3119
3120 Some(CallLocations {
3121 call_span: *call_span,
3122 called_function_span: called_function.location(),
3123 callback_arguments_span,
3124 arg_before_callback_span,
3125 callback_body_span,
3126 })
3127}
3128
3129/// Builder for code action to extract expression into a variable.
3130/// The action will wrap the expression in a block if needed in the appropriate scope.
3131///
3132/// For using the code action on the following selection:
3133///
3134/// ```gleam
3135/// fn void() {
3136/// case result {
3137/// Ok(value) -> 2 * value + 1
3138/// // ^^^^^^^^^
3139/// Error(_) -> panic
3140/// }
3141/// }
3142/// ```
3143///
3144/// Will result:
3145///
3146/// ```gleam
3147/// fn void() {
3148/// case result {
3149/// Ok(value) -> {
3150/// let int = 2 * value
3151/// int + 1
3152/// }
3153/// Error(_) -> panic
3154/// }
3155/// }
3156/// ```
3157pub struct ExtractVariable<'a> {
3158 module: &'a Module,
3159 params: &'a CodeActionParams,
3160 edits: TextEdits<'a>,
3161 position: Option<ExtractVariablePosition>,
3162 selected_expression: Option<ExtractedToVariable>,
3163 statement_before_selected_expression: Option<SrcSpan>,
3164 latest_statement: Option<SrcSpan>,
3165 to_be_wrapped: bool,
3166 name_generator: NameGenerator,
3167}
3168
3169pub enum ExtractedToVariable {
3170 Expression { location: SrcSpan, name: EcoString },
3171 StartOfPipeline { location: SrcSpan, name: EcoString },
3172}
3173
3174/// The Position of the selected code
3175#[derive(PartialEq, Eq, Copy, Clone, Debug)]
3176enum ExtractVariablePosition {
3177 InsideCaptureBody,
3178 /// Full statements (i.e. assignments, `use`s, and simple expressions).
3179 TopLevelStatement,
3180 /// The call on the right hand side of a pipe `|>`.
3181 PipelineCall,
3182 /// The right hand side of the `->` in a case expression.
3183 InsideCaseClause,
3184 /// A call argument. This can also be a `use` callback.
3185 CallArg,
3186}
3187
3188impl<'a> ExtractVariable<'a> {
3189 pub fn new(
3190 module: &'a Module,
3191 line_numbers: &'a LineNumbers,
3192 params: &'a CodeActionParams,
3193 ) -> Self {
3194 Self {
3195 module,
3196 params,
3197 edits: TextEdits::new(line_numbers),
3198 position: None,
3199 selected_expression: None,
3200 statement_before_selected_expression: None,
3201 latest_statement: None,
3202 to_be_wrapped: false,
3203 name_generator: NameGenerator::new(),
3204 }
3205 }
3206
3207 pub fn code_actions(mut self) -> Vec<CodeAction> {
3208 self.visit_typed_module(&self.module.ast);
3209
3210 let (Some(extracted_value), Some(insert_location)) = (
3211 self.selected_expression,
3212 self.statement_before_selected_expression,
3213 ) else {
3214 return vec![];
3215 };
3216
3217 let variable_name = match &extracted_value {
3218 ExtractedToVariable::Expression { name, .. }
3219 | ExtractedToVariable::StartOfPipeline { name, .. } => name,
3220 };
3221 let expression_span = match &extracted_value {
3222 ExtractedToVariable::Expression { location, .. }
3223 | ExtractedToVariable::StartOfPipeline { location, .. } => location,
3224 };
3225
3226 let content = self
3227 .module
3228 .code
3229 .get(expression_span.start as usize..expression_span.end as usize)
3230 .expect("selected expression");
3231
3232 let range = self.edits.src_span_to_lsp_range(insert_location);
3233
3234 let indent_size =
3235 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3236
3237 let mut indent = " ".repeat(indent_size);
3238
3239 // We insert the variable declaration
3240 // Wrap in a block if needed
3241 let mut insertion = match extracted_value {
3242 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"),
3243 ExtractedToVariable::StartOfPipeline { .. } => {
3244 format!("let {variable_name} =\n{indent} {content}\n")
3245 }
3246 };
3247
3248 if self.to_be_wrapped {
3249 let line_end = self
3250 .edits
3251 .line_numbers
3252 .line_starts
3253 .get((range.end.line + 1) as usize)
3254 .expect("Line number should be valid");
3255
3256 self.edits.insert(*line_end, format!("{indent}}}\n"));
3257 indent += " ";
3258 insertion = format!("{{\n{indent}{insertion}");
3259 };
3260
3261 self.edits
3262 .insert(insert_location.start, format!("{insertion}\n{indent}"));
3263
3264 self.edits
3265 .replace(*expression_span, String::from(variable_name));
3266
3267 let mut action = Vec::with_capacity(1);
3268 CodeActionBuilder::new("Extract variable")
3269 .kind(CodeActionKind::REFACTOR_EXTRACT)
3270 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3271 .preferred(false)
3272 .push_to(&mut action);
3273 action
3274 }
3275
3276 fn inside_new_scope<F>(&mut self, fun: F)
3277 where
3278 F: Fn(&mut Self),
3279 {
3280 let names = self.name_generator.clone();
3281 fun(self);
3282 self.name_generator = names;
3283 }
3284
3285 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString {
3286 let name = self.name_generator.generate_name_from_type(&type_);
3287 // When the generator generates a name, it rightfully inserts it in the
3288 // current scope so that it cannot be used again.
3289 // However, in our case it's not what we want: the name we're generating
3290 // is a candidate for what we might use for a single variable, at the
3291 // end of the whole process we're gonna pick just a single name.
3292 // If we were to insert this name into scope, that means that all the
3293 // other candidates would have a suffix `int_2`, `int_3`, ...
3294 // When we finally pick one it would be strange if the picked name had
3295 // a suffix but no `int`, `int_1`, ... were in scope!
3296 let _ = self.name_generator.used_names.remove(&name);
3297 name
3298 }
3299
3300 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
3301 where
3302 F: Fn(&mut Self),
3303 {
3304 self.at_optional_position(Some(position), fun);
3305 }
3306
3307 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
3308 where
3309 F: Fn(&mut Self),
3310 {
3311 let previous_statement = self.latest_statement;
3312 let previous_position = self.position;
3313 self.position = position;
3314 fun(self);
3315 self.position = previous_position;
3316 self.latest_statement = previous_statement;
3317 }
3318}
3319
3320impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
3321 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
3322 let range = self.edits.src_span_to_lsp_range(statement.location());
3323 if !within(self.params.range, range) {
3324 self.latest_statement = Some(statement.location());
3325 ast::visit::visit_typed_statement(self, statement);
3326 return;
3327 }
3328
3329 match self.position {
3330 // A capture body is comprised of just a single expression statement
3331 // that is inserted by the compiler, we don't really want to put
3332 // anything before that; so in this case we avoid tracking it.
3333 Some(ExtractVariablePosition::InsideCaptureBody) => {}
3334 Some(ExtractVariablePosition::PipelineCall) => {
3335 // Insert above the pipeline start
3336 self.latest_statement = Some(statement.location());
3337 }
3338 _ => {
3339 // Insert below the previous statement
3340 self.latest_statement = Some(statement.location());
3341 self.statement_before_selected_expression = self.latest_statement;
3342 }
3343 }
3344
3345 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3346 ast::visit::visit_typed_statement(this, statement);
3347 });
3348 }
3349
3350 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3351 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3352 start: fun.location.start,
3353 end: fun.end_position,
3354 });
3355
3356 if !within(self.params.range, fun_range) {
3357 return;
3358 }
3359
3360 // We reset the name generator to purge the variable names from other
3361 // scopes.
3362 // We then reserve the names already used by top level definitions.
3363 self.name_generator = NameGenerator::new();
3364 self.name_generator
3365 .reserve_module_value_names(&self.module.ast.definitions);
3366
3367 ast::visit::visit_typed_function(self, fun);
3368 }
3369
3370 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3371 if let Pattern::Variable { name, .. } = &assignment.pattern {
3372 self.name_generator.add_used_name(name.clone())
3373 };
3374 ast::visit::visit_typed_assignment(self, assignment);
3375 }
3376
3377 fn visit_typed_expr_pipeline(
3378 &mut self,
3379 location: &'ast SrcSpan,
3380 first_value: &'ast TypedPipelineAssignment,
3381 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
3382 finally: &'ast TypedExpr,
3383 finally_kind: &'ast PipelineAssignmentKind,
3384 ) {
3385 let expr_range = self.edits.src_span_to_lsp_range(*location);
3386 if !within(self.params.range, expr_range) {
3387 ast::visit::visit_typed_expr_pipeline(
3388 self,
3389 location,
3390 first_value,
3391 assignments,
3392 finally,
3393 finally_kind,
3394 );
3395 return;
3396 };
3397
3398 // Visiting a pipeline requires a bit of care, we don't want to extract
3399 // intermediate steps as variables (those are function calls)!
3400 // So we start by checking if the selected section contains multiple
3401 // steps including the first one: in that case we can extract all those
3402 // steps as a single variable.
3403 let selection = self.edits.lsp_range_to_src_span(self.params.range);
3404 let is_inside_first_step = first_value.location.contains(selection.start);
3405 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| {
3406 if assignment.location.contains(selection.end) {
3407 Some(assignment)
3408 } else {
3409 None
3410 }
3411 });
3412
3413 if let Some(last) = last_included_step
3414 && is_inside_first_step
3415 {
3416 let location = first_value.location.merge(&last.value.location());
3417 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline {
3418 location,
3419 name: self.generate_candidate_name(last.type_()),
3420 });
3421 return;
3422 }
3423
3424 // Otherwise we visit all the steps individually to see if there's
3425 // something _inside_ a step that might be extracted.
3426 let all_assignments =
3427 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
3428 for assignment in all_assignments {
3429 // With the position as "PipelineCall" we know we can't extract the
3430 // pipeline step itself!
3431 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3432 this.visit_typed_pipeline_assignment(assignment);
3433 });
3434 }
3435
3436 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3437 this.visit_typed_expr(finally)
3438 });
3439 }
3440
3441 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3442 let expr_location = expr.location();
3443 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3444 if !within(self.params.range, expr_range) {
3445 ast::visit::visit_typed_expr(self, expr);
3446 return;
3447 }
3448
3449 // If the expression is a top level statement we don't want to extract
3450 // it into a variable. It would mean we would turn this:
3451 //
3452 // ```gleam
3453 // pub fn main() {
3454 // let wibble = 1
3455 // // ^ cursor here
3456 // }
3457 //
3458 // // into:
3459 //
3460 // pub fn main() {
3461 // let int = 1
3462 // let wibble = int
3463 // }
3464 // ```
3465 //
3466 // Not all that useful!
3467 //
3468 match self.position {
3469 Some(
3470 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
3471 ) => {
3472 self.at_optional_position(None, |this| {
3473 ast::visit::visit_typed_expr(this, expr);
3474 });
3475 return;
3476 }
3477 Some(
3478 ExtractVariablePosition::InsideCaptureBody
3479 | ExtractVariablePosition::InsideCaseClause
3480 | ExtractVariablePosition::CallArg,
3481 )
3482 | None => {}
3483 }
3484
3485 match expr {
3486 TypedExpr::Fn {
3487 kind: FunctionLiteralKind::Anonymous { .. },
3488 ..
3489 } => {
3490 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3491 ast::visit::visit_typed_expr(this, expr);
3492 });
3493 return;
3494 }
3495
3496 // Expressions that don't make sense to extract
3497 TypedExpr::Panic { .. }
3498 | TypedExpr::Echo { .. }
3499 | TypedExpr::Block { .. }
3500 | TypedExpr::ModuleSelect { .. }
3501 | TypedExpr::Invalid { .. }
3502 | TypedExpr::PositionalAccess { .. }
3503 | TypedExpr::Var { .. } => (),
3504
3505 TypedExpr::Int { location, .. }
3506 | TypedExpr::Float { location, .. }
3507 | TypedExpr::String { location, .. }
3508 | TypedExpr::Pipeline { location, .. }
3509 | TypedExpr::Fn { location, .. }
3510 | TypedExpr::Todo { location, .. }
3511 | TypedExpr::List { location, .. }
3512 | TypedExpr::Call { location, .. }
3513 | TypedExpr::BinOp { location, .. }
3514 | TypedExpr::Case { location, .. }
3515 | TypedExpr::RecordAccess { location, .. }
3516 | TypedExpr::Tuple { location, .. }
3517 | TypedExpr::TupleIndex { location, .. }
3518 | TypedExpr::BitArray { location, .. }
3519 | TypedExpr::RecordUpdate { location, .. }
3520 | TypedExpr::NegateBool { location, .. }
3521 | TypedExpr::NegateInt { location, .. } => {
3522 if let Some(ExtractVariablePosition::CallArg) = self.position {
3523 // Don't update latest statement, we don't want to insert the extracted
3524 // variable inside the parenthesis where the call argument is located.
3525 } else {
3526 self.statement_before_selected_expression = self.latest_statement;
3527 };
3528 self.selected_expression = Some(ExtractedToVariable::Expression {
3529 location: *location,
3530 name: self.generate_candidate_name(expr.type_()),
3531 });
3532 }
3533 }
3534
3535 ast::visit::visit_typed_expr(self, expr);
3536 }
3537
3538 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3539 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3540 if !within(self.params.range, range) {
3541 ast::visit::visit_typed_use(self, use_);
3542 return;
3543 }
3544
3545 // Insert code under the `use`
3546 self.statement_before_selected_expression = Some(use_.call.location());
3547 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3548 ast::visit::visit_typed_use(this, use_);
3549 });
3550 }
3551
3552 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3553 let range = self.edits.src_span_to_lsp_range(clause.location());
3554 if !within(self.params.range, range) {
3555 self.inside_new_scope(|this| {
3556 ast::visit::visit_typed_clause(this, clause);
3557 });
3558 return;
3559 }
3560
3561 // Insert code after the `->`
3562 self.latest_statement = Some(clause.then.location());
3563 self.to_be_wrapped = true;
3564 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3565 this.inside_new_scope(|this| {
3566 ast::visit::visit_typed_clause(this, clause);
3567 });
3568 });
3569 }
3570
3571 fn visit_typed_expr_block(
3572 &mut self,
3573 location: &'ast SrcSpan,
3574 statements: &'ast [TypedStatement],
3575 ) {
3576 let range = self.edits.src_span_to_lsp_range(*location);
3577 if !within(self.params.range, range) {
3578 self.inside_new_scope(|this| {
3579 ast::visit::visit_typed_expr_block(this, location, statements);
3580 });
3581 return;
3582 }
3583
3584 // Don't extract block as variable
3585 let mut position = self.position;
3586 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3587 position = None;
3588 self.to_be_wrapped = false;
3589 }
3590
3591 self.at_optional_position(position, |this| {
3592 this.inside_new_scope(|this| {
3593 ast::visit::visit_typed_expr_block(this, location, statements);
3594 });
3595 });
3596 }
3597
3598 fn visit_typed_expr_fn(
3599 &mut self,
3600 location: &'ast SrcSpan,
3601 type_: &'ast Arc<Type>,
3602 kind: &'ast FunctionLiteralKind,
3603 arguments: &'ast [TypedArg],
3604 body: &'ast Vec1<TypedStatement>,
3605 return_annotation: &'ast Option<ast::TypeAst>,
3606 ) {
3607 let range = self.edits.src_span_to_lsp_range(*location);
3608 if !within(self.params.range, range) {
3609 self.inside_new_scope(|this| {
3610 ast::visit::visit_typed_expr_fn(
3611 this,
3612 location,
3613 type_,
3614 kind,
3615 arguments,
3616 body,
3617 return_annotation,
3618 );
3619 });
3620 return;
3621 }
3622
3623 let position = match kind {
3624 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3625 // just a single expression statement. When visiting we must record
3626 // we're inside a capture body.
3627 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3628 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3629 FunctionLiteralKind::Anonymous { .. } => self.position,
3630 };
3631
3632 self.at_optional_position(position, |this| {
3633 this.inside_new_scope(|this| {
3634 ast::visit::visit_typed_expr_fn(
3635 this,
3636 location,
3637 type_,
3638 kind,
3639 arguments,
3640 body,
3641 return_annotation,
3642 );
3643 });
3644 });
3645 }
3646
3647 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3648 let range = self.edits.src_span_to_lsp_range(arg.location);
3649 if !within(self.params.range, range) {
3650 ast::visit::visit_typed_call_arg(self, arg);
3651 return;
3652 }
3653
3654 // An implicit record update arg in inserted by the compiler, we don't
3655 // want folks to interact with this since it doesn't translate to
3656 // anything in the source code despite having a default position.
3657 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit {
3658 return;
3659 }
3660
3661 let position = if arg.is_use_implicit_callback() {
3662 Some(ExtractVariablePosition::TopLevelStatement)
3663 } else {
3664 Some(ExtractVariablePosition::CallArg)
3665 };
3666
3667 self.at_optional_position(position, |this| {
3668 ast::visit::visit_typed_call_arg(this, arg);
3669 });
3670 }
3671
3672 // We don't want to offer the action if the cursor is over some invalid
3673 // piece of code.
3674 fn visit_typed_expr_invalid(
3675 &mut self,
3676 location: &'ast SrcSpan,
3677 _type_: &'ast Arc<Type>,
3678 _extra_information: &'ast Option<InvalidExpression>,
3679 ) {
3680 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3681 if within(self.params.range, invalid_range) {
3682 self.selected_expression = None;
3683 }
3684 }
3685}
3686
3687/// Builder for code action to convert a literal use into a const.
3688///
3689/// For using the code action on each of the following lines:
3690///
3691/// ```gleam
3692/// fn void() {
3693/// let var = [1, 2, 3]
3694/// let res = function("Statement", var)
3695/// }
3696/// ```
3697///
3698/// Both value literals will become:
3699///
3700/// ```gleam
3701/// const var = [1, 2, 3]
3702/// const string = "Statement"
3703///
3704/// fn void() {
3705/// let res = function(string, var)
3706/// }
3707/// ```
3708pub struct ExtractConstant<'a> {
3709 module: &'a Module,
3710 params: &'a CodeActionParams,
3711 edits: TextEdits<'a>,
3712 /// The whole selected expression
3713 selected_expression: Option<SrcSpan>,
3714 /// The location of the start of the function containing the expression.
3715 /// It includes function's documentation as well
3716 container_function_start: Option<u32>,
3717 /// The variant of the extractable expression being extracted (if any)
3718 variant_of_extractable: Option<ExtractableToConstant>,
3719 /// The name of the newly created constant
3720 name_to_use: Option<EcoString>,
3721 /// The right hand side expression of the newly created constant
3722 value_to_use: Option<EcoString>,
3723}
3724
3725/// Used when an expression can be extracted to a constant
3726enum ExtractableToConstant {
3727 /// Used for collections and operator uses. This means that elements
3728 /// inside, are also extractable as constants.
3729 ComposedValue,
3730 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
3731 /// and type variants (not records).
3732 SingleValue,
3733 /// Used for whole variable assignments. If the right hand side of the
3734 /// expression can be extracted, the whole expression extracted and use the
3735 /// local variable as a constant.
3736 Assignment,
3737}
3738
3739fn can_be_constant(
3740 module: &Module,
3741 expr: &TypedExpr,
3742 module_constants: Option<&HashSet<&EcoString>>,
3743) -> bool {
3744 // We pass the `module_constants` on recursion to not compute them each time
3745 let module_constants = match module_constants {
3746 Some(module_constants) => module_constants,
3747 None => &module
3748 .ast
3749 .definitions
3750 .constants
3751 .iter()
3752 .map(|constant| &constant.name)
3753 .collect(),
3754 };
3755
3756 match expr {
3757 // Attempt to extract whole list as long as it's comprised of only literals
3758 TypedExpr::List { elements, tail, .. } => {
3759 elements
3760 .iter()
3761 .all(|element| can_be_constant(module, element, Some(module_constants)))
3762 && tail.is_none()
3763 }
3764
3765 // Attempt to extract whole bit array as long as it's made up of literals
3766 TypedExpr::BitArray { segments, .. } => {
3767 segments
3768 .iter()
3769 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants)))
3770 && segments.iter().all(|segment| {
3771 segment.options.iter().all(|option| match option {
3772 ast::BitArrayOption::Size { value, .. } => {
3773 can_be_constant(module, value, Some(module_constants))
3774 }
3775
3776 ast::BitArrayOption::Bytes { .. }
3777 | ast::BitArrayOption::Int { .. }
3778 | ast::BitArrayOption::Float { .. }
3779 | ast::BitArrayOption::Bits { .. }
3780 | ast::BitArrayOption::Utf8 { .. }
3781 | ast::BitArrayOption::Utf16 { .. }
3782 | ast::BitArrayOption::Utf32 { .. }
3783 | ast::BitArrayOption::Utf8Codepoint { .. }
3784 | ast::BitArrayOption::Utf16Codepoint { .. }
3785 | ast::BitArrayOption::Utf32Codepoint { .. }
3786 | ast::BitArrayOption::Signed { .. }
3787 | ast::BitArrayOption::Unsigned { .. }
3788 | ast::BitArrayOption::Big { .. }
3789 | ast::BitArrayOption::Little { .. }
3790 | ast::BitArrayOption::Native { .. }
3791 | ast::BitArrayOption::Unit { .. } => true,
3792 })
3793 })
3794 }
3795
3796 // Attempt to extract whole tuple as long as it's comprised of only literals
3797 TypedExpr::Tuple { elements, .. } => elements
3798 .iter()
3799 .all(|element| can_be_constant(module, element, Some(module_constants))),
3800
3801 // Extract literals directly
3802 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
3803
3804 // Extract non-record types directly
3805 TypedExpr::Var {
3806 constructor, name, ..
3807 } => {
3808 matches!(
3809 constructor.variant,
3810 type_::ValueConstructorVariant::Record { arity: 0, .. }
3811 ) || module_constants.contains(name)
3812 }
3813
3814 // Extract record types as long as arguments can be constant
3815 TypedExpr::Call { arguments, fun, .. } => {
3816 fun.is_record_literal()
3817 && arguments
3818 .iter()
3819 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants)))
3820 }
3821
3822 // Extract concat binary operation if both sides can be constants
3823 TypedExpr::BinOp {
3824 name, left, right, ..
3825 } => {
3826 matches!(name, ast::BinOp::Concatenate)
3827 && can_be_constant(module, left, Some(module_constants))
3828 && can_be_constant(module, right, Some(module_constants))
3829 }
3830
3831 TypedExpr::Block { .. }
3832 | TypedExpr::Pipeline { .. }
3833 | TypedExpr::Fn { .. }
3834 | TypedExpr::Case { .. }
3835 | TypedExpr::RecordAccess { .. }
3836 | TypedExpr::PositionalAccess { .. }
3837 | TypedExpr::ModuleSelect { .. }
3838 | TypedExpr::TupleIndex { .. }
3839 | TypedExpr::Todo { .. }
3840 | TypedExpr::Panic { .. }
3841 | TypedExpr::Echo { .. }
3842 | TypedExpr::RecordUpdate { .. }
3843 | TypedExpr::NegateBool { .. }
3844 | TypedExpr::NegateInt { .. }
3845 | TypedExpr::Invalid { .. } => false,
3846 }
3847}
3848
3849/// Takes the list of already existing constants and functions and creates a
3850/// name that doesn't conflict with them
3851///
3852fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
3853 let mut name_generator = NameGenerator::new();
3854 name_generator.reserve_module_value_names(&module.ast.definitions);
3855 name_generator.generate_name_from_type(&expr.type_())
3856}
3857
3858/// Converts the source start position of a documentation comment's contents into
3859/// the position of the leading slash in its marker ('///').
3860fn get_doc_marker_position(content_pos: u32) -> u32 {
3861 content_pos.saturating_sub(3)
3862}
3863
3864impl<'a> ExtractConstant<'a> {
3865 pub fn new(
3866 module: &'a Module,
3867 line_numbers: &'a LineNumbers,
3868 params: &'a CodeActionParams,
3869 ) -> Self {
3870 Self {
3871 module,
3872 params,
3873 edits: TextEdits::new(line_numbers),
3874 selected_expression: None,
3875 container_function_start: None,
3876 variant_of_extractable: None,
3877 name_to_use: None,
3878 value_to_use: None,
3879 }
3880 }
3881
3882 pub fn code_actions(mut self) -> Vec<CodeAction> {
3883 self.visit_typed_module(&self.module.ast);
3884
3885 let (
3886 Some(expr_span),
3887 Some(function_start),
3888 Some(type_of_extractable),
3889 Some(new_const_name),
3890 Some(const_value),
3891 ) = (
3892 self.selected_expression,
3893 self.container_function_start,
3894 self.variant_of_extractable,
3895 self.name_to_use,
3896 self.value_to_use,
3897 )
3898 else {
3899 return vec![];
3900 };
3901
3902 // We insert the constant declaration
3903 self.edits.insert(
3904 function_start,
3905 format!("const {new_const_name} = {const_value}\n\n"),
3906 );
3907
3908 // We remove or replace the selected expression
3909 match type_of_extractable {
3910 // The whole expression is deleted for assignments
3911 ExtractableToConstant::Assignment => {
3912 let range = self
3913 .edits
3914 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
3915
3916 let indent_size =
3917 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3918
3919 let expr_span_with_new_line = SrcSpan {
3920 // We remove leading indentation + 1 to remove the newline with it
3921 start: expr_span.start - (indent_size as u32 + 1),
3922 end: expr_span.end,
3923 };
3924 self.edits.delete(expr_span_with_new_line);
3925 }
3926
3927 // Only right hand side is replaced for collection or values
3928 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
3929 self.edits.replace(expr_span, String::from(new_const_name));
3930 }
3931 }
3932
3933 let mut action = Vec::with_capacity(1);
3934 CodeActionBuilder::new("Extract constant")
3935 .kind(CodeActionKind::REFACTOR_EXTRACT)
3936 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3937 .preferred(false)
3938 .push_to(&mut action);
3939 action
3940 }
3941}
3942
3943impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
3944 /// To get the position of the function containing the value or assignment
3945 /// to extract
3946 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3947 let fun_location = fun.location;
3948 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3949 start: fun_location.start,
3950 end: fun.end_position,
3951 });
3952
3953 if !within(self.params.range, fun_range) {
3954 return;
3955 }
3956
3957 // Here we need to get position of the function, starting from the leading slash in the
3958 // documentation comment's marker ('///'), not from comment's content (of which
3959 // we have the position), so we must convert the content start position
3960 // to the leading slash's position.
3961 self.container_function_start = Some(
3962 fun.documentation
3963 .as_ref()
3964 .map(|(doc_start, _)| get_doc_marker_position(*doc_start))
3965 .unwrap_or(fun_location.start),
3966 );
3967
3968 ast::visit::visit_typed_function(self, fun);
3969 }
3970
3971 /// To extract the whole assignment
3972 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3973 let expr_location = assignment.location;
3974
3975 // We only offer this code action for extracting the whole assignment
3976 // between `let` and `=`.
3977 let pattern_location = assignment.pattern.location();
3978 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
3979 let code_action_range = self.edits.src_span_to_lsp_range(location);
3980
3981 if !within(self.params.range, code_action_range) {
3982 ast::visit::visit_typed_assignment(self, assignment);
3983 return;
3984 }
3985
3986 // Has to be variable because patterns can't be constants.
3987 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
3988 {
3989 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
3990 self.selected_expression = Some(expr_location);
3991 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern {
3992 Some(name.clone())
3993 } else {
3994 None
3995 };
3996 self.value_to_use = Some(EcoString::from(
3997 self.module
3998 .code
3999 .get(
4000 (assignment.value.location().start as usize)
4001 ..(assignment.location.end as usize),
4002 )
4003 .expect("selected expression"),
4004 ));
4005 }
4006 }
4007
4008 /// To extract only the literal
4009 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
4010 let expr_location = expr.location();
4011 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
4012
4013 if !within(self.params.range, expr_range) {
4014 ast::visit::visit_typed_expr(self, expr);
4015 return;
4016 }
4017
4018 // Keep going down recursively if:
4019 // - It's no extractable has been found yet (`None`).
4020 // - It's a collection, which may or may not contain a value that can
4021 // be extracted.
4022 // - It's a binary operator, which may or may not operate on
4023 // extractable values.
4024 if matches!(
4025 self.variant_of_extractable,
4026 None | Some(ExtractableToConstant::ComposedValue)
4027 ) && can_be_constant(self.module, expr, None)
4028 {
4029 self.variant_of_extractable = match expr {
4030 TypedExpr::Var { .. }
4031 | TypedExpr::Int { .. }
4032 | TypedExpr::Float { .. }
4033 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
4034
4035 TypedExpr::List { .. }
4036 | TypedExpr::Tuple { .. }
4037 | TypedExpr::BitArray { .. }
4038 | TypedExpr::BinOp { .. }
4039 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
4040
4041 TypedExpr::Block { .. }
4042 | TypedExpr::Pipeline { .. }
4043 | TypedExpr::Fn { .. }
4044 | TypedExpr::Case { .. }
4045 | TypedExpr::RecordAccess { .. }
4046 | TypedExpr::PositionalAccess { .. }
4047 | TypedExpr::ModuleSelect { .. }
4048 | TypedExpr::TupleIndex { .. }
4049 | TypedExpr::Todo { .. }
4050 | TypedExpr::Panic { .. }
4051 | TypedExpr::Echo { .. }
4052 | TypedExpr::RecordUpdate { .. }
4053 | TypedExpr::NegateBool { .. }
4054 | TypedExpr::NegateInt { .. }
4055 | TypedExpr::Invalid { .. } => None,
4056 };
4057
4058 self.selected_expression = Some(expr_location);
4059 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
4060 self.value_to_use = Some(EcoString::from(
4061 self.module
4062 .code
4063 .get((expr_location.start as usize)..(expr_location.end as usize))
4064 .expect("selected expression"),
4065 ));
4066 }
4067
4068 ast::visit::visit_typed_expr(self, expr);
4069 }
4070}
4071
4072/// Builder for code action to apply the "expand function capture" action.
4073///
4074pub struct ExpandFunctionCapture<'a> {
4075 module: &'a Module,
4076 params: &'a CodeActionParams,
4077 edits: TextEdits<'a>,
4078 function_capture_data: Option<FunctionCaptureData>,
4079}
4080
4081pub struct FunctionCaptureData {
4082 function_span: SrcSpan,
4083 hole_span: SrcSpan,
4084 hole_type: Arc<Type>,
4085 reserved_names: VariablesNames,
4086}
4087
4088impl<'a> ExpandFunctionCapture<'a> {
4089 pub fn new(
4090 module: &'a Module,
4091 line_numbers: &'a LineNumbers,
4092 params: &'a CodeActionParams,
4093 ) -> Self {
4094 Self {
4095 module,
4096 params,
4097 edits: TextEdits::new(line_numbers),
4098 function_capture_data: None,
4099 }
4100 }
4101
4102 pub fn code_actions(mut self) -> Vec<CodeAction> {
4103 self.visit_typed_module(&self.module.ast);
4104
4105 let Some(FunctionCaptureData {
4106 function_span,
4107 hole_span,
4108 hole_type,
4109 reserved_names,
4110 }) = self.function_capture_data
4111 else {
4112 return vec![];
4113 };
4114
4115 let mut name_generator = NameGenerator::new();
4116 name_generator.reserve_variable_names(reserved_names);
4117 let name = name_generator.generate_name_from_type(&hole_type);
4118
4119 self.edits.replace(hole_span, name.clone().into());
4120 self.edits.insert(function_span.end, " }".into());
4121 self.edits
4122 .insert(function_span.start, format!("fn({name}) {{ "));
4123
4124 let mut action = Vec::with_capacity(1);
4125 CodeActionBuilder::new("Expand function capture")
4126 .kind(CodeActionKind::REFACTOR_REWRITE)
4127 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4128 .preferred(false)
4129 .push_to(&mut action);
4130 action
4131 }
4132}
4133
4134impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
4135 fn visit_typed_expr_fn(
4136 &mut self,
4137 location: &'ast SrcSpan,
4138 type_: &'ast Arc<Type>,
4139 kind: &'ast FunctionLiteralKind,
4140 arguments: &'ast [TypedArg],
4141 body: &'ast Vec1<TypedStatement>,
4142 return_annotation: &'ast Option<ast::TypeAst>,
4143 ) {
4144 let fn_range = self.edits.src_span_to_lsp_range(*location);
4145 if within(self.params.range, fn_range)
4146 && kind.is_capture()
4147 && let [argument] = arguments
4148 {
4149 self.function_capture_data = Some(FunctionCaptureData {
4150 function_span: *location,
4151 hole_span: argument.location,
4152 hole_type: argument.type_.clone(),
4153 reserved_names: VariablesNames::from_statements(body),
4154 });
4155 }
4156
4157 ast::visit::visit_typed_expr_fn(
4158 self,
4159 location,
4160 type_,
4161 kind,
4162 arguments,
4163 body,
4164 return_annotation,
4165 )
4166 }
4167}
4168
4169struct VariablesNames {
4170 names: HashSet<EcoString>,
4171}
4172
4173impl VariablesNames {
4174 fn from_statements(statements: &[TypedStatement]) -> Self {
4175 let mut variables = Self {
4176 names: HashSet::new(),
4177 };
4178
4179 for statement in statements {
4180 variables.visit_typed_statement(statement);
4181 }
4182 variables
4183 }
4184}
4185
4186impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
4187 fn visit_typed_expr_var(
4188 &mut self,
4189 _location: &'ast SrcSpan,
4190 _constructor: &'ast ValueConstructor,
4191 name: &'ast EcoString,
4192 ) {
4193 let _ = self.names.insert(name.clone());
4194 }
4195}
4196
4197/// Builder for code action to apply the "generate dynamic decoder action.
4198///
4199pub struct GenerateDynamicDecoder<'a, IO> {
4200 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4201 module: &'a Module,
4202 params: &'a CodeActionParams,
4203 edits: TextEdits<'a>,
4204 printer: Printer<'a>,
4205 actions: &'a mut Vec<CodeAction>,
4206}
4207
4208const DECODE_MODULE: &str = "gleam/dynamic/decode";
4209
4210impl<'a, IO> GenerateDynamicDecoder<'a, IO> {
4211 pub fn new(
4212 module: &'a Module,
4213 line_numbers: &'a LineNumbers,
4214 params: &'a CodeActionParams,
4215 actions: &'a mut Vec<CodeAction>,
4216 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4217 ) -> Self {
4218 // Since we are generating a new function, type variables from other
4219 // functions and constants are irrelevant to the types we print.
4220 let printer = Printer::new_without_type_variables(&module.ast.names);
4221 Self {
4222 module,
4223 params,
4224 edits: TextEdits::new(line_numbers),
4225 printer,
4226 actions,
4227 compiler,
4228 }
4229 }
4230
4231 pub fn code_actions(&mut self) {
4232 self.visit_typed_module(&self.module.ast);
4233 }
4234
4235 fn custom_type_decoder_body(
4236 &mut self,
4237 custom_type: &CustomType<Arc<Type>>,
4238 ) -> Option<EcoString> {
4239 // We cannot generate a decoder for an external type with no constructors!
4240 let constructors_size = custom_type.constructors.len();
4241 let (first, rest) = custom_type.constructors.split_first()?;
4242 let mode = EncodingMode::for_custom_type(custom_type);
4243
4244 // We generate a decoder for a type with a single constructor: it does not
4245 // require pattern matching on a tag as there's no variants to tell apart.
4246 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
4247 return self.constructor_decoder(mode, custom_type, first, 0);
4248 }
4249
4250 // Otherwise we need to generate a decoder that has to tell apart different
4251 // variants, depending on the mode we might have to decode a type field or
4252 // plain strings!
4253 let module = self.printer.print_module(DECODE_MODULE);
4254 let discriminant = if mode == EncodingMode::PlainString {
4255 eco_format!("use variant <- {module}.then({module}.string)")
4256 } else {
4257 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
4258 };
4259
4260 let mut clauses = Vec::with_capacity(constructors_size);
4261 for constructor in iter::once(first).chain(rest) {
4262 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
4263 let name = to_snake_case(&constructor.name);
4264 clauses.push(eco_format!(r#" "{name}" -> {body}"#));
4265 }
4266
4267 let failure_clause = self.failure_clause(custom_type);
4268
4269 let cases = clauses.join("\n");
4270 Some(eco_format!(
4271 r#"{{
4272 {discriminant}
4273 case variant {{
4274{cases}
4275{failure_clause}
4276 }}
4277}}"#,
4278 ))
4279 }
4280
4281 fn constructor_decoder(
4282 &mut self,
4283 mode: EncodingMode,
4284 custom_type: &ast::TypedCustomType,
4285 constructor: &TypedRecordConstructor,
4286 nesting: usize,
4287 ) -> Option<EcoString> {
4288 let decode_module = self.printer.print_module(DECODE_MODULE);
4289 let constructor_name = &constructor.name;
4290
4291 // If the constructor was encoded as a plain string with no additional
4292 // fields it means there's nothing else to decode and we can just
4293 // succeed.
4294 if mode == EncodingMode::PlainString {
4295 return Some(eco_format!("{decode_module}.success({constructor_name})"));
4296 }
4297
4298 // Otherwise we have to decode all the constructor fields to build it.
4299 let mut fields = Vec::with_capacity(constructor.arguments.len());
4300 for argument in constructor.arguments.iter() {
4301 let (_, name) = argument.label.as_ref()?;
4302 let field = RecordField {
4303 label: RecordLabel::Labeled(name),
4304 type_: &argument.type_,
4305 };
4306 fields.push(field);
4307 }
4308
4309 let mut decoder_printer = DecoderPrinter::new(
4310 &mut self.printer,
4311 custom_type.name.clone(),
4312 self.module.name.clone(),
4313 self.compiler,
4314 );
4315
4316 let decoders = fields
4317 .iter()
4318 .map(|field| decoder_printer.decode_field(field, nesting + 2))
4319 .join("\n");
4320
4321 let indent = " ".repeat(nesting);
4322
4323 Some(if decoders.is_empty() {
4324 eco_format!("{decode_module}.success({constructor_name})")
4325 } else {
4326 let field_names = fields
4327 .iter()
4328 .map(|field| format!("{}:", field.label.variable_name()))
4329 .join(", ");
4330
4331 eco_format!(
4332 "{{
4333{decoders}
4334{indent} {decode_module}.success({constructor_name}({field_names}))
4335{indent}}}",
4336 )
4337 })
4338 }
4339
4340 /// Generates the failure/catch-all clause in a decoder function for a
4341 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()`
4342 /// clause executed when the `type` field does not match any of the known variants.
4343 ///
4344 /// # Arguments
4345 /// * `custom_type` - The root type we are printing a decoder for
4346 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString {
4347 let decode_module = self.printer.print_module(DECODE_MODULE);
4348 let type_name = &custom_type.name;
4349
4350 let mut decoder_printer = DecoderPrinter::new(
4351 &mut self.printer,
4352 type_name.clone(),
4353 self.module.name.clone(),
4354 self.compiler,
4355 );
4356
4357 // The construction of the zero value might necessitate importing
4358 // modules that aren't currently in scope. We keep track of them here.
4359 let mut modules_to_import = HashSet::new();
4360 // We also need to keep track of the path we are tracing through the types
4361 // to make sure we don't get stuck in an infinite loop building a recursive
4362 // type.
4363 let mut path = vec![];
4364
4365 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type(
4366 &self.module.name,
4367 type_name,
4368 &mut path,
4369 &mut modules_to_import,
4370 ) {
4371 for module_name in modules_to_import {
4372 maybe_import(&mut self.edits, self.module, &module_name);
4373 }
4374 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,)
4375 } else {
4376 eco_format!(
4377 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"#
4378 )
4379 }
4380 }
4381}
4382
4383impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> {
4384 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4385 let range = self.edits.src_span_to_lsp_range(custom_type.location);
4386 if !overlaps(self.params.range, range) {
4387 return;
4388 }
4389
4390 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
4391 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
4392 return;
4393 };
4394
4395 let parameters = match custom_type.parameters.len() {
4396 0 => EcoString::new(),
4397 _ => eco_format!(
4398 "({})",
4399 custom_type
4400 .parameters
4401 .iter()
4402 .map(|(_, name)| name)
4403 .join(", ")
4404 ),
4405 };
4406
4407 let decoder_type = self.printer.print_type(&Type::Named {
4408 publicity: Publicity::Public,
4409 package: STDLIB_PACKAGE_NAME.into(),
4410 module: DECODE_MODULE.into(),
4411 name: "Decoder".into(),
4412 arguments: vec![],
4413 inferred_variant: None,
4414 });
4415
4416 let function = format!(
4417 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
4418 type_name = custom_type.name,
4419 );
4420
4421 self.edits.insert(custom_type.end_position, function);
4422 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
4423
4424 CodeActionBuilder::new("Generate dynamic decoder")
4425 .kind(CodeActionKind::REFACTOR)
4426 .preferred(false)
4427 .changes(
4428 self.params.text_document.uri.clone(),
4429 std::mem::take(&mut self.edits.edits),
4430 )
4431 .push_to(self.actions);
4432 }
4433}
4434
4435/// If `module_name` is not already imported inside `module`, adds an edit to
4436/// add that import.
4437/// This function also makes sure not to import a module in itself.
4438///
4439fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
4440 if module.ast.names.is_imported(module_name) || module.name == module_name {
4441 return;
4442 }
4443
4444 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
4445 let first_is_import = first_import_pos.is_some();
4446 let import_location = first_import_pos.unwrap_or_default();
4447 let after_import_newlines = add_newlines_after_import(
4448 import_location,
4449 first_is_import,
4450 edits.line_numbers,
4451 &module.code,
4452 );
4453
4454 edits.edits.push(get_import_edit(
4455 import_location,
4456 module_name,
4457 &after_import_newlines,
4458 ));
4459}
4460
4461struct DecoderPrinter<'a, 'b, IO> {
4462 printer: &'a mut Printer<'b>,
4463 /// The name of the root type we are printing a decoder for
4464 type_name: EcoString,
4465 /// The module name of the root type we are printing a decoder for
4466 type_module: EcoString,
4467 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4468}
4469
4470const DYNAMIC_MODULE: &str = "gleam/dynamic";
4471const DICT_MODULE: &str = "gleam/dict";
4472const OPTION_MODULE: &str = "gleam/option";
4473
4474struct RecordField<'a> {
4475 label: RecordLabel<'a>,
4476 type_: &'a Type,
4477}
4478
4479enum RecordLabel<'a> {
4480 Labeled(&'a str),
4481 Unlabeled(usize),
4482}
4483
4484impl RecordLabel<'_> {
4485 fn field_key(&self) -> EcoString {
4486 match self {
4487 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
4488 RecordLabel::Unlabeled(index) => {
4489 eco_format!("{index}")
4490 }
4491 }
4492 }
4493
4494 fn variable_name(&self) -> EcoString {
4495 match self {
4496 RecordLabel::Labeled(label) => (*label).into(),
4497 &RecordLabel::Unlabeled(mut index) => {
4498 let mut characters = Vec::new();
4499 let alphabet_length = 26;
4500 let alphabet_offset = b'a';
4501 loop {
4502 let alphabet_index = (index % alphabet_length) as u8;
4503 characters.push((alphabet_offset + alphabet_index) as char);
4504 index /= alphabet_length;
4505
4506 if index == 0 {
4507 break;
4508 }
4509 index -= 1;
4510 }
4511 characters.into_iter().rev().collect()
4512 }
4513 }
4514 }
4515}
4516
4517impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> {
4518 fn new(
4519 printer: &'a mut Printer<'b>,
4520 type_name: EcoString,
4521 type_module: EcoString,
4522 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4523 ) -> Self {
4524 Self {
4525 type_name,
4526 type_module,
4527 printer,
4528 compiler,
4529 }
4530 }
4531
4532 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
4533 let module_name = self.printer.print_module(DECODE_MODULE);
4534 if type_.is_bit_array() {
4535 eco_format!("{module_name}.bit_array")
4536 } else if type_.is_bool() {
4537 eco_format!("{module_name}.bool")
4538 } else if type_.is_float() {
4539 eco_format!("{module_name}.float")
4540 } else if type_.is_int() {
4541 eco_format!("{module_name}.int")
4542 } else if type_.is_string() {
4543 eco_format!("{module_name}.string")
4544 } else if type_.is_nil() {
4545 eco_format!("{module_name}.success(Nil)")
4546 } else {
4547 match type_.tuple_types() {
4548 Some(types) => {
4549 let fields = types
4550 .iter()
4551 .enumerate()
4552 .map(|(index, type_)| RecordField {
4553 type_,
4554 label: RecordLabel::Unlabeled(index),
4555 })
4556 .collect_vec();
4557 let decoders = fields
4558 .iter()
4559 .map(|field| self.decode_field(field, indent + 2))
4560 .join("\n");
4561 let mut field_names = fields.iter().map(|field| field.label.variable_name());
4562
4563 eco_format!(
4564 "{{
4565{decoders}
4566
4567{indent} {module_name}.success(#({fields}))
4568{indent}}}",
4569 fields = field_names.join(", "),
4570 indent = " ".repeat(indent)
4571 )
4572 }
4573 _ => {
4574 let type_information = type_.named_type_information();
4575 let type_information =
4576 type_information.as_ref().map(|(module, name, arguments)| {
4577 (module.as_str(), name.as_str(), arguments.as_slice())
4578 });
4579
4580 match type_information {
4581 Some(("gleam/dynamic", "Dynamic", _)) => {
4582 eco_format!("{module_name}.dynamic")
4583 }
4584 Some(("gleam", "List", [element])) => {
4585 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
4586 }
4587 Some(("gleam/option", "Option", [some])) => {
4588 eco_format!(
4589 "{module_name}.optional({})",
4590 self.decoder_for(some, indent)
4591 )
4592 }
4593 Some(("gleam/dict", "Dict", [key, value])) => {
4594 eco_format!(
4595 "{module_name}.dict({}, {})",
4596 self.decoder_for(key, indent),
4597 self.decoder_for(value, indent)
4598 )
4599 }
4600 Some((module, name, _))
4601 if module == self.type_module && name == self.type_name =>
4602 {
4603 eco_format!("{}_decoder()", to_snake_case(name))
4604 }
4605 _ => eco_format!(
4606 r#"todo as "Decoder for {}""#,
4607 self.printer.print_type(type_)
4608 ),
4609 }
4610 }
4611 }
4612 }
4613 }
4614
4615 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4616 let decoder = self.decoder_for(field.type_, indent);
4617
4618 eco_format!(
4619 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4620 indent = " ".repeat(indent),
4621 variable = field.label.variable_name(),
4622 field = field.label.field_key(),
4623 module = self.printer.print_module(DECODE_MODULE)
4624 )
4625 }
4626
4627 /// Performs best-effort generation of zero values for a given type.
4628 /// Will succeed for all prelude types and most stdlib types.
4629 /// For specifics on how custom user-defined types are handled, see
4630 /// `zero_value_for_custom_type`.
4631 fn zero_value_for_type(
4632 &mut self,
4633 type_: &Type,
4634 // Keeps track of types visited already to ensure we don't end up in an infinite
4635 // loop due to recursive types
4636 path: &mut Vec<(EcoString, EcoString)>,
4637 modules_to_import: &mut HashSet<EcoString>,
4638 ) -> Option<EcoString> {
4639 if type_.is_bit_array() {
4640 return Some("<<>>".into());
4641 }
4642 if type_.is_bool() {
4643 return Some("False".into());
4644 }
4645 if type_.is_float() {
4646 return Some("0.0".into());
4647 }
4648 if type_.is_int() {
4649 return Some("0".into());
4650 }
4651 if type_.is_string() {
4652 return Some("\"\"".into());
4653 }
4654 if type_.is_list() {
4655 return Some("[]".into());
4656 }
4657 if type_.is_nil() {
4658 return Some("Nil".into());
4659 }
4660
4661 if let Some(types) = type_.tuple_types() {
4662 // We try generating zero values for the tuple members and exit early if any of them fail.
4663 let field_zeroes = types
4664 .iter()
4665 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import))
4666 .collect_vec();
4667
4668 if field_zeroes.len() < types.len() {
4669 return None;
4670 } else {
4671 return Some(eco_format!("#({})", field_zeroes.iter().join(", ")));
4672 }
4673 };
4674
4675 let (module, name, _) = type_.named_type_information()?;
4676 match (module.as_str(), name.as_str()) {
4677 (OPTION_MODULE, "Option") => {
4678 let _ = modules_to_import.insert(OPTION_MODULE.into());
4679 Some(eco_format!(
4680 "{}.None",
4681 self.printer.print_module(OPTION_MODULE)
4682 ))
4683 }
4684
4685 (DYNAMIC_MODULE, "Dynamic") => {
4686 let _ = modules_to_import.insert(DYNAMIC_MODULE.into());
4687 Some(eco_format!(
4688 "{}.nil()",
4689 self.printer.print_module(DYNAMIC_MODULE)
4690 ))
4691 }
4692
4693 (DICT_MODULE, "Dict") => {
4694 let _ = modules_to_import.insert(DICT_MODULE.into());
4695 Some(eco_format!(
4696 "{}.new()",
4697 self.printer.print_module(DICT_MODULE)
4698 ))
4699 }
4700
4701 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import),
4702 }
4703 }
4704
4705 /// Best-effort zero value generation for user-defined types in the
4706 /// current package.
4707 fn zero_value_for_custom_type(
4708 &mut self,
4709 custom_type_module: &EcoString,
4710 custom_type_name: &EcoString,
4711 path: &mut Vec<(EcoString, EcoString)>,
4712 modules_to_import: &mut HashSet<EcoString>,
4713 ) -> Option<EcoString> {
4714 // First we check that we have not already visited this type before to
4715 // avoid cycles.
4716 let already_seen_type = path.iter().any(|(path_module, path_type_name)| {
4717 path_module == custom_type_module && path_type_name == custom_type_name
4718 });
4719
4720 if already_seen_type {
4721 return None;
4722 };
4723
4724 let type_is_inside_current_module = &self.type_module == custom_type_module;
4725
4726 let current_module_interface = self
4727 .compiler
4728 .modules
4729 .get(&self.type_module)
4730 .map(|module| &module.ast.type_info)?;
4731
4732 let type_module_interface = if !type_is_inside_current_module {
4733 self.compiler.get_module_interface(custom_type_module)?
4734 } else {
4735 current_module_interface
4736 };
4737
4738 // We only try and generate zero values for user-defined types in the current
4739 // package. If you are expanding the scope of this functionality to
4740 // remove this limitation, make sure to check for internal modules.
4741 if current_module_interface.package != type_module_interface.package {
4742 return None;
4743 }
4744
4745 let constructors = type_module_interface
4746 .types_value_constructors
4747 .get(custom_type_name)?;
4748
4749 // Opaque types cannot be constructed outside the module they were defined in,
4750 // so we will be unable to produce a zero value.
4751 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque {
4752 return None;
4753 }
4754
4755 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor
4756 // to construct our zero value to reduce visual noise, but this might not always
4757 // be possible. So we check all constructors in increasing order of size to
4758 // find the first one that succeeds, and then short circuit.
4759 constructors
4760 .variants
4761 .iter()
4762 .sorted_by_key(|v| v.parameters.len())
4763 .find_map(|zero_constructor| {
4764 self.zero_value_for_custom_type_constructor(
4765 custom_type_module,
4766 custom_type_name,
4767 zero_constructor,
4768 type_is_inside_current_module,
4769 path,
4770 modules_to_import,
4771 )
4772 })
4773 }
4774
4775 /// Attempts to construct a zero value for one specific constructor
4776 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs
4777 /// _important checks on type visibility that this method does NOT_.
4778 /// This is a helper method extracted for readability.
4779 fn zero_value_for_custom_type_constructor(
4780 &mut self,
4781 custom_type_module: &EcoString,
4782 custom_type_name: &EcoString,
4783 zero_constructor: &type_::TypeValueConstructor,
4784 type_is_inside_current_module: bool,
4785 path: &mut Vec<(EcoString, EcoString)>,
4786 modules_to_import: &mut HashSet<EcoString>,
4787 ) -> Option<EcoString> {
4788 path.push((custom_type_module.clone(), custom_type_name.clone()));
4789
4790 // Try to generate zero values for all fields in the constructor
4791 let zero_params = zero_constructor
4792 .parameters
4793 .iter()
4794 .map_while(|parameter| {
4795 let zero = self.zero_value_for_type(¶meter.type_, path, modules_to_import)?;
4796
4797 if let Some(label) = ¶meter.label {
4798 Some(eco_format!("{label}: {zero}"))
4799 } else {
4800 Some(zero)
4801 }
4802 })
4803 .collect_vec();
4804
4805 // We need to make sure to clean up the path once we've finished
4806 // "visiting" this type.
4807 let _ = path.pop();
4808
4809 // Only proceed if we were able to construct every field successfully
4810 if zero_params.len() < zero_constructor.parameters.len() {
4811 return None;
4812 };
4813
4814 let zero_constructor = if !type_is_inside_current_module {
4815 // Type constructors from other modules need to be qualified appropriately,
4816 // and they might need to be brought into scope.
4817 let _ = modules_to_import.insert(custom_type_module.clone());
4818 eco_format!(
4819 "{}.{}",
4820 self.printer.print_module(custom_type_module),
4821 zero_constructor.name
4822 )
4823 } else {
4824 eco_format!("{}", zero_constructor.name)
4825 };
4826
4827 if zero_params.is_empty() {
4828 Some(eco_format!("{zero_constructor}"))
4829 } else {
4830 let zero_args = zero_params.iter().join(", ");
4831 Some(eco_format!("{zero_constructor}({zero_args})"))
4832 }
4833 }
4834}
4835
4836/// Builder for code action to apply the "Generate to-JSON function" action.
4837///
4838pub struct GenerateJsonEncoder<'a> {
4839 module: &'a Module,
4840 params: &'a CodeActionParams,
4841 edits: TextEdits<'a>,
4842 printer: Printer<'a>,
4843 actions: &'a mut Vec<CodeAction>,
4844 config: &'a PackageConfig,
4845}
4846
4847const JSON_MODULE: &str = "gleam/json";
4848const JSON_PACKAGE_NAME: &str = "gleam_json";
4849
4850#[derive(Eq, PartialEq, Copy, Clone)]
4851enum EncodingMode {
4852 PlainString,
4853 ObjectWithTypeTag,
4854 ObjectWithNoTypeTag,
4855}
4856
4857impl EncodingMode {
4858 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
4859 match type_.constructors.as_slice() {
4860 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
4861 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
4862 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
4863 EncodingMode::PlainString
4864 }
4865 _constructors => EncodingMode::ObjectWithTypeTag,
4866 }
4867 }
4868}
4869
4870impl<'a> GenerateJsonEncoder<'a> {
4871 pub fn new(
4872 module: &'a Module,
4873 line_numbers: &'a LineNumbers,
4874 params: &'a CodeActionParams,
4875 actions: &'a mut Vec<CodeAction>,
4876 config: &'a PackageConfig,
4877 ) -> Self {
4878 // Since we are generating a new function, type variables from other
4879 // functions and constants are irrelevant to the types we print.
4880 let printer = Printer::new_without_type_variables(&module.ast.names);
4881 Self {
4882 module,
4883 params,
4884 edits: TextEdits::new(line_numbers),
4885 printer,
4886 actions,
4887 config,
4888 }
4889 }
4890
4891 pub fn code_actions(&mut self) {
4892 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
4893 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
4894 {
4895 self.visit_typed_module(&self.module.ast);
4896 }
4897 }
4898
4899 fn custom_type_encoder_body(
4900 &mut self,
4901 record_name: EcoString,
4902 custom_type: &CustomType<Arc<Type>>,
4903 ) -> Option<EcoString> {
4904 // We cannot generate a decoder for an external type with no constructors!
4905 let constructors_size = custom_type.constructors.len();
4906 let (first, rest) = custom_type.constructors.split_first()?;
4907 let mode = EncodingMode::for_custom_type(custom_type);
4908
4909 // We generate an encoder for a type with a single constructor: it does not
4910 // require pattern matching on the argument as we can access all its fields
4911 // with the usual record access syntax.
4912 if rest.is_empty() {
4913 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
4914 let unpacking = if first.arguments.is_empty() {
4915 ""
4916 } else {
4917 &eco_format!(
4918 "let {name}({fields}:) = {record_name}\n ",
4919 name = first.name,
4920 fields = first
4921 .arguments
4922 .iter()
4923 .filter_map(|argument| {
4924 argument.label.as_ref().map(|(_location, label)| label)
4925 })
4926 .join(":, ")
4927 )
4928 };
4929 return Some(eco_format!("{unpacking}{encoder}"));
4930 }
4931
4932 // Otherwise we generate an encoder for a type with multiple constructors:
4933 // it will need to pattern match on the various constructors and encode each
4934 // one separately.
4935 let mut clauses = Vec::with_capacity(constructors_size);
4936 for constructor in iter::once(first).chain(rest) {
4937 let RecordConstructor { name, .. } = constructor;
4938 let encoder =
4939 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
4940 if constructor.arguments.is_empty() {
4941 clauses.push(eco_format!(" {name} -> {encoder}"));
4942 } else {
4943 let unpacking = constructor
4944 .arguments
4945 .iter()
4946 .filter_map(|argument| {
4947 argument.label.as_ref().map(|(_location, label)| {
4948 if is_nil_like(&argument.type_) {
4949 eco_format!("{}: _", label)
4950 } else {
4951 eco_format!("{}:", label)
4952 }
4953 })
4954 })
4955 .join(", ");
4956 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}"));
4957 }
4958 }
4959
4960 let clauses = clauses.join("\n");
4961 Some(eco_format!(
4962 "case {record_name} {{
4963{clauses}
4964 }}",
4965 ))
4966 }
4967
4968 fn constructor_encoder(
4969 &mut self,
4970 mode: EncodingMode,
4971 constructor: &TypedRecordConstructor,
4972 type_name: EcoString,
4973 nesting: usize,
4974 ) -> Option<EcoString> {
4975 let json_module = self.printer.print_module(JSON_MODULE);
4976 let tag = to_snake_case(&constructor.name);
4977 let indent = " ".repeat(nesting);
4978
4979 // If the variant is encoded as a simple json string we just call the
4980 // `json.string` with the variant tag as an argument.
4981 if mode == EncodingMode::PlainString {
4982 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
4983 }
4984
4985 // Otherwise we turn it into an object with a `type` tag field.
4986 let mut encoder_printer =
4987 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone());
4988
4989 // These are the fields of the json object to encode.
4990 let mut fields = Vec::with_capacity(constructor.arguments.len());
4991 if mode == EncodingMode::ObjectWithTypeTag {
4992 // Any needed type tag is always going to be the first field in the object
4993 fields.push(eco_format!(
4994 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
4995 ));
4996 }
4997
4998 for argument in constructor.arguments.iter() {
4999 let (_, label) = argument.label.as_ref()?;
5000 let field = RecordField {
5001 label: RecordLabel::Labeled(label),
5002 type_: &argument.type_,
5003 };
5004 let encoder = encoder_printer.encode_field(&field, nesting + 2);
5005 fields.push(encoder);
5006 }
5007
5008 let fields = fields.join(",\n");
5009 Some(eco_format!(
5010 "{json_module}.object([
5011{fields},
5012{indent}])"
5013 ))
5014 }
5015}
5016
5017/// When generating an encoder, we need to know when we can ignore the fields
5018/// destructured from a constructor.
5019/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil`
5020/// types, then we will never need to use the field in our encoder function.
5021fn is_nil_like(type_: &Type) -> bool {
5022 if type_.is_nil() {
5023 return true;
5024 }
5025
5026 match type_.tuple_types() {
5027 Some(types) => types.iter().all(|type_| is_nil_like(type_)),
5028 _ => false,
5029 }
5030}
5031
5032impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
5033 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
5034 let range = self.edits.src_span_to_lsp_range(custom_type.location);
5035 if !overlaps(self.params.range, range) {
5036 return;
5037 }
5038
5039 let record_name = to_snake_case(&custom_type.name);
5040 let name = eco_format!("{record_name}_to_json");
5041 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
5042 return;
5043 };
5044
5045 let json_type = self.printer.print_type(&Type::Named {
5046 publicity: Publicity::Public,
5047 package: JSON_PACKAGE_NAME.into(),
5048 module: JSON_MODULE.into(),
5049 name: "Json".into(),
5050 arguments: vec![],
5051 inferred_variant: None,
5052 });
5053
5054 let type_ = if custom_type.parameters.is_empty() {
5055 custom_type.name.clone()
5056 } else {
5057 let parameters = custom_type
5058 .parameters
5059 .iter()
5060 .map(|(_, name)| name)
5061 .join(", ");
5062 eco_format!("{}({})", custom_type.name, parameters)
5063 };
5064
5065 let function = format!(
5066 "
5067
5068fn {name}({record_name}: {type_}) -> {json_type} {{
5069 {encoder}
5070}}",
5071 );
5072
5073 self.edits.insert(custom_type.end_position, function);
5074 maybe_import(&mut self.edits, self.module, JSON_MODULE);
5075
5076 CodeActionBuilder::new("Generate to-JSON function")
5077 .kind(CodeActionKind::REFACTOR)
5078 .preferred(false)
5079 .changes(
5080 self.params.text_document.uri.clone(),
5081 std::mem::take(&mut self.edits.edits),
5082 )
5083 .push_to(self.actions);
5084 }
5085}
5086
5087struct JsonEncoderPrinter<'a, 'b> {
5088 printer: &'a mut Printer<'b>,
5089 /// The name of the root type we are printing an encoder for
5090 type_name: EcoString,
5091 /// The module name of the root type we are printing an encoder for
5092 type_module: EcoString,
5093}
5094
5095impl<'a, 'b> JsonEncoderPrinter<'a, 'b> {
5096 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self {
5097 Self {
5098 type_name,
5099 type_module,
5100 printer,
5101 }
5102 }
5103
5104 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
5105 let module_name = self.printer.print_module(JSON_MODULE);
5106 let is_capture = encoded_value == "_";
5107 let maybe_capture = |mut function: EcoString| {
5108 if is_capture {
5109 function
5110 } else {
5111 function.push('(');
5112 function.push_str(encoded_value);
5113 function.push(')');
5114 function
5115 }
5116 };
5117
5118 if type_.is_bool() {
5119 maybe_capture(eco_format!("{module_name}.bool"))
5120 } else if type_.is_float() {
5121 maybe_capture(eco_format!("{module_name}.float"))
5122 } else if type_.is_int() {
5123 maybe_capture(eco_format!("{module_name}.int"))
5124 } else if type_.is_string() {
5125 maybe_capture(eco_format!("{module_name}.string"))
5126 } else if type_.is_nil() {
5127 if is_capture {
5128 eco_format!("fn(_) {{ {module_name}.null() }}")
5129 } else {
5130 eco_format!("{module_name}.null()")
5131 }
5132 } else {
5133 match type_.tuple_types() {
5134 Some(types) => {
5135 let (tuple, new_indent) = if is_capture {
5136 ("value", indent + 4)
5137 } else {
5138 (encoded_value, indent + 2)
5139 };
5140
5141 // We need to iterate over all of the tuple's fields
5142 // to obtain an encoder for each one, so we reuse the
5143 // iteration to check whether this tuple can be ignored
5144 // in the encoder without calling `is_nil_like`.
5145 let mut encoders = Vec::new();
5146 let all_values_are_nil = types.iter().enumerate().fold(
5147 true,
5148 |all_values_are_nil, (index, type_)| {
5149 encoders.push(self.encoder_for(
5150 &format!("{tuple}.{index}"),
5151 type_,
5152 new_indent,
5153 ));
5154 all_values_are_nil && is_nil_like(type_)
5155 },
5156 );
5157
5158 if is_capture {
5159 eco_format!(
5160 "fn({value}) {{
5161{indent} {module_name}.preprocessed_array([
5162{indent} {encoders},
5163{indent} ])
5164{indent}}}",
5165 value = if all_values_are_nil { "_" } else { "value" },
5166 indent = " ".repeat(indent),
5167 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5168 )
5169 } else {
5170 eco_format!(
5171 "{module_name}.preprocessed_array([
5172{indent} {encoders},
5173{indent}])",
5174 indent = " ".repeat(indent),
5175 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5176 )
5177 }
5178 }
5179 _ => {
5180 let type_information = type_.named_type_information();
5181 let type_information: Option<(&str, &str, &[Arc<Type>])> =
5182 type_information.as_ref().map(|(module, name, arguments)| {
5183 (module.as_str(), name.as_str(), arguments.as_slice())
5184 });
5185
5186 match type_information {
5187 Some(("gleam", "List", [element])) => {
5188 eco_format!(
5189 "{module_name}.array({encoded_value}, {map_function})",
5190 map_function = self.encoder_for("_", element, indent)
5191 )
5192 }
5193 Some(("gleam/option", "Option", [some])) => {
5194 eco_format!(
5195 "case {encoded_value} {{
5196{indent} {none} -> {module_name}.null()
5197{indent} {some}({value}) -> {encoder}
5198{indent}}}",
5199 indent = " ".repeat(indent),
5200 none = self
5201 .printer
5202 .print_constructor(&"gleam/option".into(), &"None".into()),
5203 some = self
5204 .printer
5205 .print_constructor(&"gleam/option".into(), &"Some".into()),
5206 value = if is_nil_like(some) { "_" } else { "value" },
5207 encoder = self.encoder_for("value", some, indent + 2)
5208 )
5209 }
5210 Some(("gleam/dict", "Dict", [key, value])) => {
5211 let stringify_function = match key
5212 .named_type_information()
5213 .as_ref()
5214 .map(|(module, name, arguments)| {
5215 (module.as_str(), name.as_str(), arguments.as_slice())
5216 }) {
5217 Some(("gleam", "String", [])) => "fn(string) { string }",
5218 _ => &format!(
5219 r#"todo as "Function to stringify {}""#,
5220 self.printer.print_type(key)
5221 ),
5222 };
5223 eco_format!(
5224 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
5225 self.encoder_for("_", value, indent)
5226 )
5227 }
5228 Some((module, name, _))
5229 if module == self.type_module && name == self.type_name =>
5230 {
5231 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
5232 }
5233 _ => eco_format!(
5234 r#"todo as "Encoder for {}""#,
5235 self.printer.print_type(type_)
5236 ),
5237 }
5238 }
5239 }
5240 }
5241 }
5242
5243 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
5244 let field_name = field.label.variable_name();
5245 let encoder = self.encoder_for(&field_name, field.type_, indent);
5246
5247 eco_format!(
5248 r#"{indent}#("{field_name}", {encoder})"#,
5249 indent = " ".repeat(indent),
5250 )
5251 }
5252}
5253
5254/// Builder for code action to pattern match on things like (anonymous) function
5255/// arguments or variables.
5256/// For example:
5257///
5258/// ```gleam
5259/// pub fn wibble(arg: #(key, value)) {
5260/// // ^ [pattern match on argument]
5261/// }
5262///
5263/// // Generates
5264///
5265/// pub fn wibble(arg: #(key, value)) {
5266/// let #(value_0, value_1) = arg
5267/// }
5268/// ```
5269///
5270/// Another example with variables:
5271///
5272/// ```gleam
5273/// pub fn main() {
5274/// let pair = #(1, 3)
5275/// // ^ [pattern match on value]
5276/// }
5277///
5278/// // Generates
5279///
5280/// pub fn main() {
5281/// let pair = #(1, 3)
5282/// let #(value_0, value_1) = pair
5283/// }
5284/// ```
5285///
5286pub struct PatternMatchOnValue<'a, A> {
5287 module: &'a Module,
5288 params: &'a CodeActionParams,
5289 compiler: &'a LspProjectCompiler<A>,
5290 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>,
5291 selected_value: Option<PatternMatchedValue<'a>>,
5292 edits: TextEdits<'a>,
5293}
5294
5295/// A value we might want to pattern match on.
5296/// Each variant will also contain all the info needed to know how to properly
5297/// print and format the corresponding pattern matching code; that's why you'll
5298/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
5299///
5300#[derive(Clone)]
5301pub enum PatternMatchedValue<'a> {
5302 FunctionArgument {
5303 /// The argument being pattern matched on.
5304 ///
5305 arg: &'a TypedArg,
5306 /// The first statement inside the function body. Used to correctly
5307 /// position the inserted pattern matching.
5308 ///
5309 first_statement: &'a TypedStatement,
5310 /// The range of the entire function holding the argument.
5311 ///
5312 function_range: Range,
5313 },
5314 LetVariable {
5315 variable_name: &'a EcoString,
5316 variable_type: Arc<Type>,
5317 /// The location of the entire let assignment the variable is part of,
5318 /// so that we can add the pattern matching _after_ it.
5319 ///
5320 assignment_location: SrcSpan,
5321 },
5322 /// A variable that is bound in a case branch's pattern. For example:
5323 /// ```gleam
5324 /// case wibble {
5325 /// wobble -> 1
5326 /// // ^^^^^ This!
5327 /// }
5328 /// ```
5329 ///
5330 ClausePatternVariable {
5331 variable_type: Arc<Type>,
5332 variable_location: PatternLocation,
5333 clause_location: SrcSpan,
5334 /// All the names in the clause that are already taken by pattern variables.
5335 /// We need this to avoid generating invalid code were two pattern variables
5336 /// have the same name.
5337 ///
5338 /// For example:
5339 ///
5340 /// ```gleam
5341 /// case wibble {
5342 /// [first, ..rest] -> todo
5343 /// ^^^^^ When expanding `first` we can't add any variable pattern
5344 /// called `rest` as it would clash with the `rest` tail that is
5345 /// already there.
5346 /// }
5347 /// ```
5348 ///
5349 bound_variables: Vec<BoundVariable>,
5350 },
5351 UseVariable {
5352 variable_name: &'a EcoString,
5353 variable_type: Arc<Type>,
5354 /// The location of the entire use expression the variable is part of,
5355 /// so that we can add the pattern matching _after_ it.
5356 ///
5357 use_location: SrcSpan,
5358 },
5359}
5360
5361#[derive(Clone)]
5362pub enum PatternLocation {
5363 /// Any pattern that doesn't need any special handling.
5364 ///
5365 Regular { location: SrcSpan },
5366 /// List tails need some care to not generate invalid syntax when pattern
5367 /// matched on in case expressions.
5368 ///
5369 ListTail {
5370 /// This location covers the entire list tail pattern, including the `..`
5371 location: SrcSpan,
5372 },
5373}
5374
5375impl PatternLocation {
5376 fn regular(location: SrcSpan) -> Self {
5377 Self::Regular { location }
5378 }
5379}
5380
5381impl<'a, IO> PatternMatchOnValue<'a, IO> {
5382 pub fn new(
5383 module: &'a Module,
5384 line_numbers: &'a LineNumbers,
5385 params: &'a CodeActionParams,
5386 compiler: &'a LspProjectCompiler<IO>,
5387 ) -> Self {
5388 Self {
5389 module,
5390 params,
5391 compiler,
5392 selected_value: None,
5393 pattern_variable_under_cursor: None,
5394 edits: TextEdits::new(line_numbers),
5395 }
5396 }
5397
5398 pub fn code_actions(mut self) -> Vec<CodeAction> {
5399 self.visit_typed_module(&self.module.ast);
5400
5401 let action_title = match self.selected_value.clone() {
5402 Some(PatternMatchedValue::FunctionArgument {
5403 arg,
5404 first_statement: function_body,
5405 function_range,
5406 }) => {
5407 self.match_on_function_argument(arg, function_body, function_range);
5408 "Pattern match on argument"
5409 }
5410 Some(
5411 PatternMatchedValue::LetVariable {
5412 variable_name,
5413 variable_type,
5414 assignment_location: location,
5415 }
5416 | PatternMatchedValue::UseVariable {
5417 variable_name,
5418 variable_type,
5419 use_location: location,
5420 },
5421 ) => {
5422 self.match_on_let_variable(variable_name, variable_type, location);
5423 "Pattern match on variable"
5424 }
5425
5426 Some(PatternMatchedValue::ClausePatternVariable {
5427 variable_type,
5428 variable_location,
5429 clause_location,
5430 bound_variables,
5431 }) => {
5432 self.match_on_clause_variable(
5433 variable_type,
5434 variable_location,
5435 clause_location,
5436 &bound_variables,
5437 );
5438 "Pattern match on variable"
5439 }
5440
5441 None => return vec![],
5442 };
5443
5444 if self.edits.edits.is_empty() {
5445 return vec![];
5446 }
5447
5448 let mut action = Vec::with_capacity(1);
5449 CodeActionBuilder::new(action_title)
5450 .kind(CodeActionKind::REFACTOR_REWRITE)
5451 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5452 .preferred(false)
5453 .push_to(&mut action);
5454 action
5455 }
5456
5457 fn match_on_function_argument(
5458 &mut self,
5459 arg: &TypedArg,
5460 first_statement: &TypedStatement,
5461 function_range: Range,
5462 ) {
5463 let Some(arg_name) = arg.get_variable_name() else {
5464 return;
5465 };
5466
5467 let Some(patterns) =
5468 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new())
5469 else {
5470 return;
5471 };
5472
5473 let first_statement_location = first_statement.location();
5474 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
5475
5476 // If we're trying to insert the pattern matching on the same
5477 // line as the one where the function is defined we will want to
5478 // put it on a new line instead. So in that case the nesting will
5479 // be the default 2 spaces.
5480 let needs_newline = function_range.start.line == first_statement_range.start.line;
5481 let nesting = if needs_newline {
5482 String::from(" ")
5483 } else {
5484 " ".repeat(first_statement_range.start.character as usize)
5485 };
5486
5487 let pattern_matching = if patterns.len() == 1 {
5488 let pattern = patterns.first();
5489 format!("let {pattern} = {arg_name}")
5490 } else {
5491 let patterns = patterns
5492 .iter()
5493 .map(|p| format!(" {nesting}{p} -> todo"))
5494 .join("\n");
5495 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
5496 };
5497
5498 let pattern_matching = if needs_newline {
5499 format!("\n{nesting}{pattern_matching}")
5500 } else {
5501 pattern_matching
5502 };
5503
5504 let has_empty_body = match first_statement {
5505 ast::Statement::Expression(TypedExpr::Todo {
5506 kind: TodoKind::EmptyFunction { .. },
5507 ..
5508 }) => true,
5509 ast::Statement::Expression(_)
5510 | ast::Statement::Assignment(_)
5511 | ast::Statement::Use(_)
5512 | ast::Statement::Assert(_) => false,
5513 };
5514
5515 // If the pattern matching is added to a function with an empty
5516 // body then we do not add any nesting after it, or we would be
5517 // increasing the nesting of the closing `}`!
5518 let pattern_matching = if has_empty_body {
5519 format!("{pattern_matching}\n")
5520 } else {
5521 format!("{pattern_matching}\n{nesting}")
5522 };
5523
5524 self.edits
5525 .insert(first_statement_location.start, pattern_matching);
5526 }
5527
5528 fn match_on_let_variable(
5529 &mut self,
5530 variable_name: &EcoString,
5531 variable_type: Arc<Type>,
5532 assignment_location: SrcSpan,
5533 ) {
5534 let Some(patterns) =
5535 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new())
5536 else {
5537 return;
5538 };
5539
5540 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
5541 let nesting = " ".repeat(assignment_range.start.character as usize);
5542
5543 let pattern_matching = if patterns.len() == 1 {
5544 let pattern = patterns.first();
5545 format!("let {pattern} = {variable_name}")
5546 } else {
5547 let patterns = patterns
5548 .iter()
5549 .map(|p| format!(" {nesting}{p} -> todo"))
5550 .join("\n");
5551 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
5552 };
5553
5554 self.edits.insert(
5555 assignment_location.end,
5556 format!("\n{nesting}{pattern_matching}"),
5557 );
5558 }
5559
5560 fn match_on_clause_variable(
5561 &mut self,
5562 variable_type: Arc<Type>,
5563 variable_location: PatternLocation,
5564 clause_location: SrcSpan,
5565 bound_variables: &[BoundVariable],
5566 ) {
5567 let mut names = NameGenerator::new();
5568 names.reserve_bound_variables(bound_variables);
5569
5570 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) {
5571 // Here we're dealing with a special case: if someone wants to expand the tail
5572 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`.
5573 // That would result in invalid syntax. So we have to generate list patterns
5574 // that have no square brackets.
5575 let first = names.rename_to_avoid_shadowing("first".into());
5576 let rest = names.rename_to_avoid_shadowing("rest".into());
5577 vec1!["".into(), eco_format!("{first}, ..{rest}")]
5578 } else if let Some(patterns) =
5579 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names)
5580 {
5581 patterns
5582 } else {
5583 return;
5584 };
5585
5586 let clause_range = self.edits.src_span_to_lsp_range(clause_location);
5587 let nesting = " ".repeat(clause_range.start.character as usize);
5588
5589 let variable_location = match variable_location {
5590 PatternLocation::Regular { location } => location,
5591 PatternLocation::ListTail { location } => location,
5592 };
5593
5594 let variable_start = (variable_location.start - clause_location.start) as usize;
5595 let variable_end = variable_start + variable_location.len();
5596
5597 let clause_code = code_at(self.module, clause_location);
5598 let patterns = patterns
5599 .iter()
5600 .map(|pattern| {
5601 let mut clause_code = clause_code.to_string();
5602 // If we're replacing a variable that's using the shorthand
5603 // syntax we want to add a space to separate it from the
5604 // preceding `:`.
5605 let pattern = if variable_start == variable_end {
5606 &eco_format!(" {pattern}")
5607 } else {
5608 pattern
5609 };
5610
5611 clause_code.replace_range(variable_start..variable_end, pattern);
5612 clause_code
5613 })
5614 .join(&format!("\n{nesting}"));
5615
5616 self.edits.replace(clause_location, patterns);
5617 }
5618
5619 /// Will produce a pattern that can be used on the left hand side of a let
5620 /// assignment to destructure a value of the given type. For example given
5621 /// this type:
5622 ///
5623 /// ```gleam
5624 /// pub type Wibble {
5625 /// Wobble(Int, label: String)
5626 /// }
5627 /// ```
5628 ///
5629 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
5630 /// The pattern will use the correct qualified/unqualified name for the
5631 /// constructor if it comes from another package.
5632 ///
5633 /// The function will only produce a list of patterns that can be used from
5634 /// the current module. So if the type comes from another module it must be
5635 /// public! Otherwise this function will return an empty vec.
5636 ///
5637 fn type_to_destructure_patterns(
5638 &mut self,
5639 type_: &Type,
5640 names: &mut NameGenerator,
5641 ) -> Option<Vec1<EcoString>> {
5642 match type_ {
5643 Type::Fn { .. } => None,
5644 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names),
5645
5646 // We special case lists, they don't have "regular" constructors
5647 // like other types. Instead we always add the two clauses covering
5648 // the empty and non empty list.
5649 Type::Named { .. } if type_.is_list() => {
5650 let first = names.rename_to_avoid_shadowing("first".into());
5651 let rest = names.rename_to_avoid_shadowing("rest".into());
5652 Some(vec1![
5653 EcoString::from("[]"),
5654 eco_format!("[{first}, ..{rest}]")
5655 ])
5656 }
5657
5658 Type::Named {
5659 module: type_module,
5660 name: type_name,
5661 ..
5662 } => {
5663 let mut patterns = vec![];
5664 let constructors =
5665 get_type_constructors(self.compiler, &self.module.name, type_module, type_name);
5666 for constructor in constructors {
5667 let names_before = names.clone();
5668 if let Some(pattern) =
5669 self.record_constructor_to_destructure_pattern(constructor, names)
5670 {
5671 patterns.push(pattern);
5672 }
5673 *names = names_before;
5674 }
5675
5676 Vec1::try_from_vec(patterns).ok()
5677 }
5678
5679 // We don't want to suggest this action for empty tuple as it
5680 // doesn't make a lot of sense to match on those.
5681 Type::Tuple { elements } if elements.is_empty() => None,
5682 Type::Tuple { elements } => {
5683 let elements = elements
5684 .iter()
5685 .map(|element| names.generate_name_from_type(element))
5686 .join(", ");
5687 Some(vec1![eco_format!("#({elements})")])
5688 }
5689 }
5690 }
5691
5692 fn type_var_to_destructure_patterns(
5693 &mut self,
5694 type_var: &TypeVar,
5695 names: &mut NameGenerator,
5696 ) -> Option<Vec1<EcoString>> {
5697 match type_var {
5698 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
5699 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names),
5700 }
5701 }
5702
5703 /// Given the value constructor of a record, returns a string with the
5704 /// pattern used to match on that specific variant.
5705 ///
5706 /// Note how:
5707 /// - If the constructor is internal to another module or comes from another
5708 /// module, then this returns `None` since one cannot pattern match on it.
5709 /// - If the provided `ValueConstructor` is not a record constructor this
5710 /// will return `None`.
5711 ///
5712 fn record_constructor_to_destructure_pattern(
5713 &self,
5714 constructor: &ValueConstructor,
5715 names: &mut NameGenerator,
5716 ) -> Option<EcoString> {
5717 let type_::ValueConstructorVariant::Record {
5718 name: constructor_name,
5719 arity: constructor_arity,
5720 module: constructor_module,
5721 field_map,
5722 ..
5723 } = &constructor.variant
5724 else {
5725 // The constructor should always be a record, in case it's not
5726 // there's not much we can do and just fail.
5727 return None;
5728 };
5729
5730 // Since the constructor is a record constructor we know that its type
5731 // is either `Named` or a `Fn` type, in either case we have to get the
5732 // arguments types out of it.
5733 let Some(arguments_types) = constructor
5734 .type_
5735 .fn_types()
5736 .map(|(arguments_types, _return)| arguments_types)
5737 .or_else(|| constructor.type_.constructor_types())
5738 else {
5739 // This should never happen but just in case we don't want to unwrap
5740 // and panic.
5741 return None;
5742 };
5743
5744 let index_to_label = match field_map {
5745 None => HashMap::new(),
5746 Some(field_map) => {
5747 names.reserve_all_labels(field_map);
5748
5749 field_map
5750 .fields
5751 .iter()
5752 .map(|(label, index)| (index, label))
5753 .collect::<HashMap<_, _>>()
5754 }
5755 };
5756
5757 let mut pattern =
5758 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
5759
5760 if *constructor_arity == 0 {
5761 return Some(pattern);
5762 }
5763
5764 pattern.push('(');
5765 let arguments = (0..*constructor_arity as u32)
5766 .map(|i| match index_to_label.get(&i) {
5767 Some(label) => eco_format!("{label}:"),
5768 None => match arguments_types.get(i as usize) {
5769 None => names.rename_to_avoid_shadowing(EcoString::from("value")),
5770 Some(type_) => names.generate_name_from_type(type_),
5771 },
5772 })
5773 .join(", ");
5774
5775 pattern.push_str(&arguments);
5776 pattern.push(')');
5777 Some(pattern)
5778 }
5779}
5780
5781fn code_at(module: &Module, span: SrcSpan) -> &str {
5782 module
5783 .code
5784 .get(span.start as usize..span.end as usize)
5785 .expect("code location must be valid")
5786}
5787
5788impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> {
5789 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
5790 // If we're not inside the function there's no point in exploring its
5791 // ast further.
5792 let function_span = SrcSpan {
5793 start: fun.location.start,
5794 end: fun.end_position,
5795 };
5796 let function_range = self.edits.src_span_to_lsp_range(function_span);
5797 if !within(self.params.range, function_range) {
5798 return;
5799 }
5800
5801 for arg in &fun.arguments {
5802 // If the cursor is placed on one of the arguments, then we can try
5803 // and generate code for that one.
5804 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
5805 if within(self.params.range, arg_range)
5806 && let Some(first_statement) = fun.body.first()
5807 {
5808 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
5809 arg,
5810 first_statement,
5811 function_range,
5812 });
5813 return;
5814 }
5815 }
5816
5817 // If the cursor is not on any of the function arguments then we keep
5818 // exploring the function body as we might want to destructure the
5819 // argument of an expression function!
5820 ast::visit::visit_typed_function(self, fun);
5821 }
5822
5823 fn visit_typed_expr_fn(
5824 &mut self,
5825 location: &'ast SrcSpan,
5826 type_: &'ast Arc<Type>,
5827 kind: &'ast FunctionLiteralKind,
5828 arguments: &'ast [TypedArg],
5829 body: &'ast Vec1<TypedStatement>,
5830 return_annotation: &'ast Option<ast::TypeAst>,
5831 ) {
5832 // If we're not inside the function there's no point in exploring its
5833 // ast further.
5834 let function_range = self.edits.src_span_to_lsp_range(*location);
5835 if !within(self.params.range, function_range) {
5836 return;
5837 }
5838
5839 for argument in arguments {
5840 // If the cursor is placed on one of the arguments, then we can try
5841 // and generate code for that one.
5842 let arg_range = self.edits.src_span_to_lsp_range(argument.location);
5843 if within(self.params.range, arg_range) {
5844 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
5845 arg: argument,
5846 first_statement: body.first(),
5847 function_range,
5848 });
5849 return;
5850 }
5851 }
5852
5853 // If the cursor is not on any of the function arguments then we keep
5854 // exploring the function body as we might want to destructure the
5855 // argument of an expression function!
5856 ast::visit::visit_typed_expr_fn(
5857 self,
5858 location,
5859 type_,
5860 kind,
5861 arguments,
5862 body,
5863 return_annotation,
5864 );
5865 }
5866
5867 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
5868 // If we're not inside the assignment there's no point in exploring its
5869 // ast further.
5870 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
5871 if !within(self.params.range, assignment_range) {
5872 return;
5873 }
5874
5875 ast::visit::visit_typed_assignment(self, assignment);
5876 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor {
5877 self.selected_value = Some(PatternMatchedValue::LetVariable {
5878 variable_name: name,
5879 variable_type: type_.clone(),
5880 assignment_location: assignment.location,
5881 });
5882 }
5883 }
5884
5885 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
5886 // If we're not inside the clause there's no point in exploring its
5887 // ast further.
5888 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
5889 if !within(self.params.range, clause_range) {
5890 return;
5891 }
5892
5893 for pattern in clause.pattern.iter() {
5894 self.visit_typed_pattern(pattern);
5895 }
5896 for patterns in clause.alternative_patterns.iter() {
5897 for pattern in patterns {
5898 self.visit_typed_pattern(pattern);
5899 }
5900 }
5901
5902 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() {
5903 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable {
5904 variable_type: type_,
5905 variable_location,
5906 clause_location: clause.location(),
5907 bound_variables: clause.bound_variables().collect_vec(),
5908 });
5909 } else {
5910 self.visit_typed_expr(&clause.then);
5911 }
5912 }
5913
5914 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
5915 if let Some(assignments) = use_.callback_arguments() {
5916 for variable in assignments {
5917 let ast::Arg {
5918 names: ArgNames::Named { name, .. },
5919 location: variable_location,
5920 type_,
5921 ..
5922 } = variable
5923 else {
5924 continue;
5925 };
5926
5927 // If we use a pattern in a use assignment, that will end up
5928 // being called `_use` something. We don't want to offer the
5929 // action when hovering a pattern so we ignore those.
5930 if name.starts_with("_use") {
5931 continue;
5932 }
5933
5934 let variable_range = self.edits.src_span_to_lsp_range(*variable_location);
5935 if within(self.params.range, variable_range) {
5936 self.selected_value = Some(PatternMatchedValue::UseVariable {
5937 variable_name: name,
5938 variable_type: type_.clone(),
5939 use_location: use_.location,
5940 });
5941 // If we've found the variable to pattern match on, there's no
5942 // point in keeping traversing the AST.
5943 return;
5944 }
5945 }
5946 }
5947
5948 ast::visit::visit_typed_use(self, use_);
5949 }
5950
5951 fn visit_typed_pattern_variable(
5952 &mut self,
5953 location: &'ast SrcSpan,
5954 name: &'ast EcoString,
5955 type_: &'ast Arc<Type>,
5956 _origin: &'ast VariableOrigin,
5957 ) {
5958 if within(
5959 self.params.range,
5960 self.edits.src_span_to_lsp_range(*location),
5961 ) {
5962 let location = PatternLocation::regular(*location);
5963 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
5964 }
5965 }
5966
5967 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
5968 if let Some(name) = arg.label_shorthand_name()
5969 && within(
5970 self.params.range,
5971 self.edits.src_span_to_lsp_range(arg.location),
5972 )
5973 {
5974 let location = PatternLocation::regular(SrcSpan {
5975 start: arg.location.end,
5976 end: arg.location.end,
5977 });
5978 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_()));
5979 return;
5980 }
5981
5982 ast::visit::visit_typed_pattern_call_arg(self, arg);
5983 }
5984
5985 fn visit_typed_pattern_string_prefix(
5986 &mut self,
5987 _location: &'ast SrcSpan,
5988 _left_location: &'ast SrcSpan,
5989 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
5990 right_location: &'ast SrcSpan,
5991 _left_side_string: &'ast EcoString,
5992 right_side_assignment: &'ast AssignName,
5993 ) {
5994 if let Some((name, location)) = left_side_assignment
5995 && within(
5996 self.params.range,
5997 self.edits.src_span_to_lsp_range(*location),
5998 )
5999 {
6000 let location = PatternLocation::regular(*location);
6001 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6002 } else if let AssignName::Variable(name) = right_side_assignment
6003 && within(
6004 self.params.range,
6005 self.edits.src_span_to_lsp_range(*right_location),
6006 )
6007 {
6008 let location = PatternLocation::regular(*right_location);
6009 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6010 }
6011 }
6012
6013 fn visit_typed_pattern_list(
6014 &mut self,
6015 location: &'ast SrcSpan,
6016 elements: &'ast Vec<TypedPattern>,
6017 tail: &'ast Option<Box<TypedTailPattern>>,
6018 type_: &'ast Arc<Type>,
6019 ) {
6020 let (name, tail_location, tail_type) = if let Some(tail) = tail
6021 && let Pattern::Variable { name, type_, .. } = &tail.pattern
6022 {
6023 (name, tail.location, type_)
6024 } else {
6025 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6026 return;
6027 };
6028
6029 let tail_range = self.edits.src_span_to_lsp_range(tail_location);
6030 if !within(self.params.range, tail_range) {
6031 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6032 return;
6033 }
6034
6035 let location = PatternLocation::ListTail {
6036 location: tail_location,
6037 };
6038 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone()))
6039 }
6040}
6041
6042/// Given a type and its module, returns a list of its *importable*
6043/// constructors.
6044///
6045/// Since this focuses just on importable constructors, if either the module or
6046/// the type are internal the returned array will be empty!
6047///
6048fn get_type_constructors<'a, 'b, IO>(
6049 compiler: &'a LspProjectCompiler<IO>,
6050 current_module: &'b EcoString,
6051 type_module: &'b EcoString,
6052 type_name: &'b EcoString,
6053) -> Vec<&'a ValueConstructor> {
6054 let type_is_inside_current_module = current_module == type_module;
6055 let module_interface = if !type_is_inside_current_module {
6056 // If the type is outside of the module we're in, we can only pattern
6057 // match on it if the module can be imported.
6058 // The `get_module_interface` already takes care of making this check.
6059 compiler.get_module_interface(type_module)
6060 } else {
6061 // However, if the type is defined in the module we're in, we can always
6062 // pattern match on it. So we get the current module's interface.
6063 compiler
6064 .modules
6065 .get(current_module)
6066 .map(|module| &module.ast.type_info)
6067 };
6068
6069 let Some(module_interface) = module_interface else {
6070 return vec![];
6071 };
6072
6073 // If the type is in an internal module that is not the current one, we
6074 // cannot use its constructors!
6075 if !type_is_inside_current_module && module_interface.is_internal {
6076 return vec![];
6077 }
6078
6079 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
6080 return vec![];
6081 };
6082
6083 constructors
6084 .variants
6085 .iter()
6086 .filter_map(|variant| {
6087 let constructor = module_interface.values.get(&variant.name)?;
6088 if type_is_inside_current_module || constructor.publicity.is_public() {
6089 Some(constructor)
6090 } else {
6091 None
6092 }
6093 })
6094 .collect_vec()
6095}
6096
6097/// Returns a pretty printed record constructor name, the way it would be used
6098/// inside the given `module` (with the correct name and qualification).
6099///
6100/// If the constructor cannot be used inside the module because it's not
6101/// imported, then this function will return `None`.
6102///
6103fn pretty_constructor_name(
6104 module: &Module,
6105 constructor_module: &EcoString,
6106 constructor_name: &EcoString,
6107) -> Option<EcoString> {
6108 match module
6109 .ast
6110 .names
6111 .named_constructor(constructor_module, constructor_name)
6112 {
6113 type_::printer::NameContextInformation::Unimported(_, _) => None,
6114 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
6115 Some(eco_format!("{constructor_name}"))
6116 }
6117 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
6118 Some(eco_format!("{module_name}.{constructor_name}"))
6119 }
6120 }
6121}
6122
6123/// Builder for the "generate function" code action.
6124/// Whenever someone hovers an invalid expression that is inferred to have a
6125/// function type the language server can generate a function definition for it.
6126/// For example:
6127///
6128/// ```gleam
6129/// pub fn main() {
6130/// wibble(1, 2, "hello")
6131/// // ^ [generate function]
6132/// }
6133/// ```
6134///
6135/// Will generate the following definition:
6136///
6137/// ```gleam
6138/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
6139/// todo
6140/// }
6141/// ```
6142///
6143pub struct GenerateFunction<'a> {
6144 module: &'a Module,
6145 modules: &'a std::collections::HashMap<EcoString, Module>,
6146 params: &'a CodeActionParams,
6147 edits: TextEdits<'a>,
6148 last_visited_definition_end: Option<u32>,
6149 function_to_generate: Option<FunctionToGenerate<'a>>,
6150}
6151
6152struct FunctionToGenerate<'a> {
6153 module: Option<&'a str>,
6154 name: &'a str,
6155 arguments_types: Vec<Arc<Type>>,
6156
6157 /// The arguments actually supplied as input to the function, if any.
6158 /// A function to generate might as well be just a name passed as an argument
6159 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
6160 /// have any actual arguments!
6161 given_arguments: Option<&'a [TypedCallArg]>,
6162 return_type: Arc<Type>,
6163 previous_definition_end: Option<u32>,
6164}
6165
6166impl<'a> GenerateFunction<'a> {
6167 pub fn new(
6168 module: &'a Module,
6169 modules: &'a std::collections::HashMap<EcoString, Module>,
6170 line_numbers: &'a LineNumbers,
6171 params: &'a CodeActionParams,
6172 ) -> Self {
6173 Self {
6174 module,
6175 modules,
6176 params,
6177 edits: TextEdits::new(line_numbers),
6178 last_visited_definition_end: None,
6179 function_to_generate: None,
6180 }
6181 }
6182
6183 pub fn code_actions(mut self) -> Vec<CodeAction> {
6184 self.visit_typed_module(&self.module.ast);
6185
6186 let Some(
6187 function_to_generate @ FunctionToGenerate {
6188 module,
6189 previous_definition_end: Some(insert_at),
6190 ..
6191 },
6192 ) = self.function_to_generate.take()
6193 else {
6194 return vec![];
6195 };
6196
6197 if let Some(module) = module {
6198 if let Some(module) = self.modules.get(module) {
6199 let insert_at = module.code.len() as u32;
6200 self.code_action_for_module(
6201 module,
6202 Publicity::Public,
6203 function_to_generate,
6204 insert_at,
6205 )
6206 } else {
6207 Vec::new()
6208 }
6209 } else {
6210 let module = self.module;
6211 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at)
6212 }
6213 }
6214
6215 fn code_action_for_module(
6216 mut self,
6217 module: &'a Module,
6218 publicity: Publicity,
6219 function_to_generate: FunctionToGenerate<'a>,
6220 insert_at: u32,
6221 ) -> Vec<CodeAction> {
6222 let FunctionToGenerate {
6223 name,
6224 arguments_types,
6225 given_arguments,
6226 return_type,
6227 ..
6228 } = function_to_generate;
6229
6230 // This might be triggered on variants as well, in that case we don't
6231 // want to offer this action. The "generate variant" action will be
6232 // offered instead.
6233 if !is_valid_lowercase_name(name) {
6234 return vec![];
6235 }
6236
6237 // Labels do not share the same namespace as argument so we use two
6238 // separate generators to avoid renaming a label in case it shares a
6239 // name with an argument.
6240 let mut label_names = NameGenerator::new();
6241 let mut argument_names = NameGenerator::new();
6242
6243 // Since we are generating a new function, type variables from other
6244 // functions and constants are irrelevant to the types we print.
6245 let mut printer = Printer::new_without_type_variables(&module.ast.names);
6246 let arguments = arguments_types
6247 .iter()
6248 .enumerate()
6249 .map(|(index, argument_type)| {
6250 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
6251 let (label, name) =
6252 argument_names.generate_label_and_name(call_argument, argument_type);
6253 let pretty_type = printer.print_type(argument_type);
6254 if let Some(label) = label {
6255 let label = label_names.rename_to_avoid_shadowing(label.clone());
6256 format!("{label} {name}: {pretty_type}")
6257 } else {
6258 format!("{name}: {pretty_type}")
6259 }
6260 })
6261 .join(", ");
6262
6263 let return_type = printer.print_type(&return_type);
6264
6265 let publicity = if publicity.is_public() { "pub " } else { "" };
6266
6267 // Make sure we use the line number information of the module we are
6268 // editing, which might not be the module where the code action is
6269 // triggered.
6270 self.edits.line_numbers = &module.ast.type_info.line_numbers;
6271 self.edits.insert(
6272 insert_at,
6273 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
6274 );
6275
6276 let Some(uri) = url_from_path(module.input_path.as_str()) else {
6277 return Vec::new();
6278 };
6279 let mut action = Vec::with_capacity(1);
6280 CodeActionBuilder::new("Generate function")
6281 .kind(CodeActionKind::QUICKFIX)
6282 .changes(uri, self.edits.edits)
6283 .preferred(true)
6284 .push_to(&mut action);
6285 action
6286 }
6287
6288 fn try_save_function_to_generate(
6289 &mut self,
6290 name: &'a EcoString,
6291 function_type: &Arc<Type>,
6292 given_arguments: Option<&'a [TypedCallArg]>,
6293 ) {
6294 match function_type.fn_types() {
6295 None => {}
6296 Some((arguments_types, return_type)) => {
6297 self.function_to_generate = Some(FunctionToGenerate {
6298 name,
6299 arguments_types,
6300 given_arguments,
6301 return_type,
6302 previous_definition_end: self.last_visited_definition_end,
6303 module: None,
6304 })
6305 }
6306 }
6307 }
6308
6309 fn try_save_function_from_other_module(
6310 &mut self,
6311 module: &'a str,
6312 name: &'a str,
6313 function_type: &Arc<Type>,
6314 given_arguments: Option<&'a [TypedCallArg]>,
6315 ) {
6316 if let Some((arguments_types, return_type)) = function_type.fn_types()
6317 && is_valid_lowercase_name(name)
6318 {
6319 self.function_to_generate = Some(FunctionToGenerate {
6320 name,
6321 arguments_types,
6322 given_arguments,
6323 return_type,
6324 previous_definition_end: self.last_visited_definition_end,
6325 module: Some(module),
6326 })
6327 }
6328 }
6329}
6330
6331impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
6332 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6333 self.last_visited_definition_end = Some(fun.end_position);
6334 ast::visit::visit_typed_function(self, fun);
6335 }
6336
6337 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
6338 self.last_visited_definition_end = Some(constant.value.location().end);
6339 ast::visit::visit_typed_module_constant(self, constant);
6340 }
6341
6342 fn visit_typed_expr_invalid(
6343 &mut self,
6344 location: &'ast SrcSpan,
6345 type_: &'ast Arc<Type>,
6346 extra_information: &'ast Option<InvalidExpression>,
6347 ) {
6348 let invalid_range = self.edits.src_span_to_lsp_range(*location);
6349 if within(self.params.range, invalid_range) {
6350 match extra_information {
6351 Some(InvalidExpression::ModuleSelect { module_name, label }) => {
6352 self.try_save_function_from_other_module(module_name, label, type_, None)
6353 }
6354 Some(InvalidExpression::UnknownVariable { name }) => {
6355 self.try_save_function_to_generate(name, type_, None)
6356 }
6357 None => {}
6358 }
6359 }
6360
6361 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
6362 }
6363
6364 fn visit_typed_constant_invalid(
6365 &mut self,
6366 location: &'ast SrcSpan,
6367 type_: &'ast Arc<Type>,
6368 extra_information: &'ast Option<InvalidExpression>,
6369 ) {
6370 let constant_range = self.edits.src_span_to_lsp_range(*location);
6371 if let Some(extra_information) = extra_information
6372 && within(self.params.range, constant_range)
6373 {
6374 match extra_information {
6375 InvalidExpression::ModuleSelect { module_name, label } => {
6376 self.try_save_function_from_other_module(module_name, label, type_, None)
6377 }
6378 InvalidExpression::UnknownVariable { name } => {
6379 self.try_save_function_to_generate(name, type_, None)
6380 }
6381 }
6382 }
6383 }
6384
6385 fn visit_typed_expr_call(
6386 &mut self,
6387 location: &'ast SrcSpan,
6388 type_: &'ast Arc<Type>,
6389 fun: &'ast TypedExpr,
6390 arguments: &'ast [TypedCallArg],
6391 ) {
6392 // If the function being called is invalid we need to generate a
6393 // function that has the proper labels.
6394 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
6395
6396 if within(self.params.range, fun_range) {
6397 if !labels_are_correct(arguments) {
6398 return;
6399 }
6400
6401 match fun {
6402 TypedExpr::Invalid {
6403 type_,
6404 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }),
6405 location: _,
6406 } => {
6407 return self.try_save_function_from_other_module(
6408 module_name,
6409 label,
6410 type_,
6411 Some(arguments),
6412 );
6413 }
6414 TypedExpr::Invalid {
6415 type_,
6416 extra_information: Some(InvalidExpression::UnknownVariable { name }),
6417 location: _,
6418 } => {
6419 return self.try_save_function_to_generate(name, type_, Some(arguments));
6420 }
6421 TypedExpr::Int { .. }
6422 | TypedExpr::Float { .. }
6423 | TypedExpr::String { .. }
6424 | TypedExpr::Block { .. }
6425 | TypedExpr::Pipeline { .. }
6426 | TypedExpr::Var { .. }
6427 | TypedExpr::Fn { .. }
6428 | TypedExpr::List { .. }
6429 | TypedExpr::Call { .. }
6430 | TypedExpr::BinOp { .. }
6431 | TypedExpr::Case { .. }
6432 | TypedExpr::RecordAccess { .. }
6433 | TypedExpr::PositionalAccess { .. }
6434 | TypedExpr::ModuleSelect { .. }
6435 | TypedExpr::Tuple { .. }
6436 | TypedExpr::TupleIndex { .. }
6437 | TypedExpr::Todo { .. }
6438 | TypedExpr::Panic { .. }
6439 | TypedExpr::Echo { .. }
6440 | TypedExpr::BitArray { .. }
6441 | TypedExpr::RecordUpdate { .. }
6442 | TypedExpr::NegateBool { .. }
6443 | TypedExpr::NegateInt { .. }
6444 | TypedExpr::Invalid { .. } => {}
6445 }
6446 }
6447
6448 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
6449 }
6450}
6451
6452/// Builder for the "generate variant" code action. This will generate a variant
6453/// for a type if it can tell the type it should come from. It will work with
6454/// non-existing variants both used as expressions
6455///
6456/// ```gleam
6457/// let a = IDoNotExist(1)
6458/// // ^^^^^^^^^^^ It would generate this variant here
6459/// ```
6460///
6461/// And as patterns:
6462///
6463/// ```gleam
6464/// let assert IDoNotExist(1) = todo
6465/// ^^^^^^^^^^^ It would generate this variant here
6466/// ```
6467///
6468pub struct GenerateVariant<'a, IO> {
6469 module: &'a Module,
6470 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
6471 params: &'a CodeActionParams,
6472 line_numbers: &'a LineNumbers,
6473 variant_to_generate: Option<VariantToGenerate<'a>>,
6474}
6475
6476struct VariantToGenerate<'a> {
6477 name: &'a str,
6478 end_position: u32,
6479 arguments_types: Vec<Arc<Type>>,
6480
6481 /// Wether the type we're adding the variant to is written with braces or
6482 /// not. We need this information to add braces when missing.
6483 ///
6484 type_braces: TypeBraces,
6485
6486 /// The module this variant will be added to.
6487 ///
6488 module_name: EcoString,
6489
6490 /// The arguments actually supplied as input to the variant, if any.
6491 /// A variant to generate might as well be just a name passed as an argument
6492 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
6493 /// have any actual arguments!
6494 ///
6495 given_arguments: Option<Arguments<'a>>,
6496}
6497
6498#[derive(Debug, Clone, Copy)]
6499enum TypeBraces {
6500 /// If the type is written like this: `pub type Wibble`
6501 HasBraces,
6502 /// If the type is written like this: `pub type Wibble {}`
6503 NoBraces,
6504}
6505
6506/// The arguments to an invalid call or pattern we can use to generate a variant.
6507///
6508enum Arguments<'a> {
6509 /// These are the arguments provided to the invalid variant constructor
6510 /// when it's used as a function: `let a = Wibble(1, 2)`.
6511 ///
6512 Expressions(&'a [TypedCallArg]),
6513 /// These are the arguments provided to the invalid variant constructor when
6514 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
6515 ///
6516 Patterns(&'a [CallArg<TypedPattern>]),
6517}
6518
6519/// An invalid variant might be used both as a pattern in a case expression or
6520/// as a regular value in an expression. We want to generate the variant in both
6521/// cases, so we use this enum to tell apart the two cases and be able to reuse
6522/// most of the code for both as they are very similar.
6523///
6524enum Argument<'a> {
6525 Expression(&'a TypedCallArg),
6526 Pattern(&'a CallArg<TypedPattern>),
6527}
6528
6529impl<'a> Arguments<'a> {
6530 fn get(&self, index: usize) -> Option<Argument<'a>> {
6531 match self {
6532 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern),
6533 Arguments::Expressions(call_arguments) => {
6534 call_arguments.get(index).map(Argument::Expression)
6535 }
6536 }
6537 }
6538
6539 fn types(&self) -> Vec<Arc<Type>> {
6540 match self {
6541 Arguments::Expressions(call_arguments) => call_arguments
6542 .iter()
6543 .map(|argument| argument.value.type_())
6544 .collect_vec(),
6545
6546 Arguments::Patterns(call_arguments) => call_arguments
6547 .iter()
6548 .map(|argument| argument.value.type_())
6549 .collect_vec(),
6550 }
6551 }
6552}
6553
6554impl Argument<'_> {
6555 fn label(&self) -> Option<EcoString> {
6556 match self {
6557 Argument::Expression(call_arg) => call_arg.label.clone(),
6558 Argument::Pattern(call_arg) => call_arg.label.clone(),
6559 }
6560 }
6561}
6562
6563impl<'a, IO> GenerateVariant<'a, IO> {
6564 pub fn new(
6565 module: &'a Module,
6566 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
6567 line_numbers: &'a LineNumbers,
6568 params: &'a CodeActionParams,
6569 ) -> Self {
6570 Self {
6571 module,
6572 params,
6573 compiler,
6574 line_numbers,
6575 variant_to_generate: None,
6576 }
6577 }
6578
6579 pub fn code_actions(mut self) -> Vec<CodeAction> {
6580 self.visit_typed_module(&self.module.ast);
6581
6582 let Some(VariantToGenerate {
6583 name,
6584 arguments_types,
6585 given_arguments,
6586 module_name,
6587 end_position,
6588 type_braces,
6589 }) = &self.variant_to_generate
6590 else {
6591 return vec![];
6592 };
6593
6594 let Some((variant_module, variant_edits)) = self.edits_to_create_variant(
6595 name,
6596 arguments_types,
6597 given_arguments,
6598 module_name,
6599 *end_position,
6600 *type_braces,
6601 ) else {
6602 return vec![];
6603 };
6604
6605 let mut action = Vec::with_capacity(1);
6606 CodeActionBuilder::new("Generate variant")
6607 .kind(CodeActionKind::QUICKFIX)
6608 .changes(variant_module, variant_edits)
6609 .preferred(true)
6610 .push_to(&mut action);
6611 action
6612 }
6613
6614 /// Returns the edits needed to add this new variant to the given module.
6615 /// It also returns the uri of the module the edits should be applied to.
6616 ///
6617 fn edits_to_create_variant(
6618 &self,
6619 variant_name: &str,
6620 arguments_types: &[Arc<Type>],
6621 given_arguments: &Option<Arguments<'_>>,
6622 module_name: &EcoString,
6623 end_position: u32,
6624 type_braces: TypeBraces,
6625 ) -> Option<(Url, Vec<TextEdit>)> {
6626 let mut label_names = NameGenerator::new();
6627 let mut printer = Printer::new(&self.module.ast.names);
6628 let arguments = arguments_types
6629 .iter()
6630 .enumerate()
6631 .map(|(index, argument_type)| {
6632 let label = given_arguments
6633 .as_ref()
6634 .and_then(|arguments| arguments.get(index)?.label())
6635 .map(|label| label_names.rename_to_avoid_shadowing(label));
6636
6637 let pretty_type = printer.print_type(argument_type);
6638 if let Some(arg_label) = label {
6639 format!("{arg_label}: {pretty_type}")
6640 } else {
6641 format!("{pretty_type}")
6642 }
6643 })
6644 .join(", ");
6645
6646 let variant = if arguments.is_empty() {
6647 variant_name.to_string()
6648 } else {
6649 format!("{variant_name}({arguments})")
6650 };
6651
6652 let (new_text, insert_at) = match type_braces {
6653 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
6654 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
6655 };
6656
6657 if *module_name == self.module.name {
6658 // If we're editing the current module we can use the line numbers that
6659 // were already computed before-hand without wasting any time to add the
6660 // new edit.
6661 let mut edits = TextEdits::new(self.line_numbers);
6662 edits.insert(insert_at, new_text);
6663 Some((self.params.text_document.uri.clone(), edits.edits))
6664 } else {
6665 // Otherwise we're changing a different module and we need to get its
6666 // code and line numbers to properly apply the new edit.
6667 let module = self
6668 .compiler
6669 .modules
6670 .get(module_name)
6671 .expect("module to exist");
6672 let line_numbers = LineNumbers::new(&module.code);
6673 let mut edits = TextEdits::new(&line_numbers);
6674 edits.insert(insert_at, new_text);
6675 Some((url_from_path(module.input_path.as_str())?, edits.edits))
6676 }
6677 }
6678
6679 fn try_save_variant_to_generate(
6680 &mut self,
6681 function_name_location: SrcSpan,
6682 function_type: &Arc<Type>,
6683 given_arguments: Option<Arguments<'a>>,
6684 ) {
6685 let variant_to_generate =
6686 self.variant_to_generate(function_name_location, function_type, given_arguments);
6687 if variant_to_generate.is_some() {
6688 self.variant_to_generate = variant_to_generate;
6689 }
6690 }
6691
6692 fn variant_to_generate(
6693 &mut self,
6694 function_name_location: SrcSpan,
6695 type_: &Arc<Type>,
6696 given_arguments: Option<Arguments<'a>>,
6697 ) -> Option<VariantToGenerate<'a>> {
6698 let name = code_at(self.module, function_name_location);
6699 if !is_valid_uppercase_name(name) {
6700 return None;
6701 }
6702
6703 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
6704 (Some(result), _) => result,
6705 (None, Some(arguments)) => (arguments.types(), type_.clone()),
6706 (None, None) => (vec![], type_.clone()),
6707 };
6708
6709 let (module_name, type_name, _) = custom_type.named_type_information()?;
6710 let module = self.compiler.modules.get(&module_name)?;
6711 let (end_position, type_braces) = (module.ast.definitions.custom_types.iter())
6712 .filter(|custom_type| custom_type.name == type_name)
6713 .find_map(|custom_type| {
6714 // If there's already a variant with this name then we definitely
6715 // don't want to generate a new variant with the same name!
6716 let variant_with_this_name_already_exists = custom_type
6717 .constructors
6718 .iter()
6719 .map(|constructor| &constructor.name)
6720 .any(|existing_constructor_name| existing_constructor_name == name);
6721 if variant_with_this_name_already_exists {
6722 return None;
6723 }
6724 let type_braces = if custom_type.end_position == custom_type.location.end {
6725 TypeBraces::NoBraces
6726 } else {
6727 TypeBraces::HasBraces
6728 };
6729 Some((custom_type.end_position, type_braces))
6730 })?;
6731
6732 Some(VariantToGenerate {
6733 name,
6734 arguments_types,
6735 given_arguments,
6736 module_name,
6737 end_position,
6738 type_braces,
6739 })
6740 }
6741}
6742
6743impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> {
6744 fn visit_typed_expr_invalid(
6745 &mut self,
6746 location: &'ast SrcSpan,
6747 type_: &'ast Arc<Type>,
6748 extra_information: &'ast Option<InvalidExpression>,
6749 ) {
6750 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
6751 if within(self.params.range, invalid_range) {
6752 self.try_save_variant_to_generate(*location, type_, None);
6753 }
6754 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
6755 }
6756
6757 fn visit_typed_expr_call(
6758 &mut self,
6759 location: &'ast SrcSpan,
6760 type_: &'ast Arc<Type>,
6761 fun: &'ast TypedExpr,
6762 arguments: &'ast [TypedCallArg],
6763 ) {
6764 // If the function being called is invalid we need to generate a
6765 // function that has the proper labels.
6766 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
6767 if within(self.params.range, fun_range) && fun.is_invalid() {
6768 if labels_are_correct(arguments) {
6769 self.try_save_variant_to_generate(
6770 fun.location(),
6771 &fun.type_(),
6772 Some(Arguments::Expressions(arguments)),
6773 );
6774 }
6775 } else {
6776 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
6777 }
6778 }
6779
6780 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
6781 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
6782 if within(self.params.range, invalid_range) {
6783 self.try_save_variant_to_generate(*location, type_, None);
6784 }
6785 ast::visit::visit_typed_pattern_invalid(self, location, type_);
6786 }
6787
6788 fn visit_typed_pattern_constructor(
6789 &mut self,
6790 location: &'ast SrcSpan,
6791 name_location: &'ast SrcSpan,
6792 name: &'ast EcoString,
6793 arguments: &'ast Vec<CallArg<TypedPattern>>,
6794 module: &'ast Option<(EcoString, SrcSpan)>,
6795 constructor: &'ast Inferred<type_::PatternConstructor>,
6796 spread: &'ast Option<SrcSpan>,
6797 type_: &'ast Arc<Type>,
6798 ) {
6799 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
6800 if within(self.params.range, pattern_range) {
6801 if labels_are_correct(arguments) {
6802 self.try_save_variant_to_generate(
6803 *name_location,
6804 type_,
6805 Some(Arguments::Patterns(arguments)),
6806 );
6807 }
6808 } else {
6809 ast::visit::visit_typed_pattern_constructor(
6810 self,
6811 location,
6812 name_location,
6813 name,
6814 arguments,
6815 module,
6816 constructor,
6817 spread,
6818 type_,
6819 );
6820 }
6821 }
6822}
6823
6824#[must_use]
6825/// Checks the labels in the given arguments are correct: that is there's no
6826/// duplicate labels and all labelled arguments come after the unlabelled ones.
6827fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool {
6828 let mut labelled_arg_found = false;
6829 let mut used_labels = HashSet::new();
6830
6831 for argument in arguments {
6832 match &argument.label {
6833 // Labels are invalid if there's duplicate ones or if an unlabelled
6834 // argument comes after a labelled one.
6835 Some(label) if used_labels.contains(label) => return false,
6836 None if labelled_arg_found => return false,
6837 // Otherwise we just add the label to the used ones.
6838 Some(label) => {
6839 labelled_arg_found = true;
6840 let _ = used_labels.insert(label);
6841 }
6842 None => {}
6843 }
6844 }
6845
6846 true
6847}
6848
6849#[derive(Clone)]
6850struct NameGenerator {
6851 used_names: im::HashSet<EcoString>,
6852}
6853
6854impl NameGenerator {
6855 pub fn new() -> Self {
6856 NameGenerator {
6857 used_names: im::HashSet::new(),
6858 }
6859 }
6860
6861 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
6862 let mut i = 1;
6863 let mut candidate_name = base.clone();
6864
6865 loop {
6866 if self.used_names.contains(&candidate_name) {
6867 i += 1;
6868 candidate_name = eco_format!("{base}_{i}");
6869 } else {
6870 let _ = self.used_names.insert(candidate_name.clone());
6871 return candidate_name;
6872 }
6873 }
6874 }
6875
6876 /// Given an argument type and the actual call argument (if any), comes up
6877 /// with a label and a name to use for that argument when generating a
6878 /// function.
6879 ///
6880 pub fn generate_label_and_name(
6881 &mut self,
6882 call_argument: Option<&CallArg<TypedExpr>>,
6883 argument_type: &Arc<Type>,
6884 ) -> (Option<EcoString>, EcoString) {
6885 let label = call_argument.and_then(|argument| argument.label.clone());
6886 let argument_name = call_argument
6887 // We always favour a name derived from the expression (for example if
6888 // the argument is a variable)
6889 .and_then(|argument| self.generate_name_from_expression(&argument.value))
6890 // If we don't have such a name and there's a label we use that name.
6891 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
6892 // If all else fails we fallback to using a name derived from the
6893 // argument's type.
6894 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
6895
6896 (label, argument_name)
6897 }
6898
6899 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
6900 let type_to_base_name = |type_: &Arc<Type>| {
6901 type_
6902 .named_type_name()
6903 .map(|(_type_module, type_name)| to_snake_case(&type_name))
6904 .filter(|name| is_valid_lowercase_name(name))
6905 .unwrap_or(EcoString::from("value"))
6906 };
6907
6908 let base_name = match type_.list_type() {
6909 None => type_to_base_name(type_),
6910 // If we're coming up with a name for a list we want to use the
6911 // plural form for the name of the inner type. For example:
6912 // `List(Pokemon)` should generate `pokemons`.
6913 Some(inner_type) => {
6914 let base_name = type_to_base_name(&inner_type);
6915 // If the inner type name already ends in "s" we leave it as it
6916 // is, or it would look funny.
6917 if base_name.ends_with('s') {
6918 base_name
6919 } else {
6920 eco_format!("{base_name}s")
6921 }
6922 }
6923 };
6924
6925 self.rename_to_avoid_shadowing(base_name)
6926 }
6927
6928 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
6929 match expression {
6930 // If the argument is a record, we can't use it as an argument name.
6931 // Similarly, we don't want to base the variable name off a
6932 // compiler-generated variable like `_pipe`.
6933 TypedExpr::Var {
6934 name, constructor, ..
6935 } if !constructor.variant.is_record()
6936 && !constructor.variant.is_generated_variable() =>
6937 {
6938 Some(self.rename_to_avoid_shadowing(name.clone()))
6939 }
6940
6941 // If the argument is a record access, we generate a name from the
6942 // label used.
6943 // For example if we have `wibble.id` we would end up picking `id`.
6944 TypedExpr::RecordAccess { label, .. } => {
6945 Some(self.rename_to_avoid_shadowing(label.clone()))
6946 }
6947
6948 TypedExpr::Int { .. }
6949 | TypedExpr::Float { .. }
6950 | TypedExpr::String { .. }
6951 | TypedExpr::Block { .. }
6952 | TypedExpr::Pipeline { .. }
6953 | TypedExpr::Var { .. }
6954 | TypedExpr::Fn { .. }
6955 | TypedExpr::List { .. }
6956 | TypedExpr::Call { .. }
6957 | TypedExpr::BinOp { .. }
6958 | TypedExpr::Case { .. }
6959 | TypedExpr::PositionalAccess { .. }
6960 | TypedExpr::ModuleSelect { .. }
6961 | TypedExpr::Tuple { .. }
6962 | TypedExpr::TupleIndex { .. }
6963 | TypedExpr::Todo { .. }
6964 | TypedExpr::Panic { .. }
6965 | TypedExpr::Echo { .. }
6966 | TypedExpr::BitArray { .. }
6967 | TypedExpr::RecordUpdate { .. }
6968 | TypedExpr::NegateBool { .. }
6969 | TypedExpr::NegateInt { .. }
6970 | TypedExpr::Invalid { .. } => None,
6971 }
6972 }
6973
6974 /// Given some typed definitions this reserves all the value names defined
6975 /// by all the top level definitions. That is: all function names, constant
6976 /// names, and imported modules names.
6977 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) {
6978 for constant in &definitions.constants {
6979 self.add_used_name(constant.name.clone());
6980 }
6981
6982 for function in &definitions.functions {
6983 if let Some((_, name)) = &function.name {
6984 self.add_used_name(name.clone());
6985 }
6986 }
6987
6988 for import in &definitions.imports {
6989 let module_name = match &import.used_name() {
6990 Some(used_name) => used_name.clone(),
6991 None => import.module.clone(),
6992 };
6993 self.add_used_name(module_name);
6994 }
6995 }
6996
6997 pub fn add_used_name(&mut self, name: EcoString) {
6998 let _ = self.used_names.insert(name);
6999 }
7000
7001 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
7002 field_map
7003 .fields
7004 .iter()
7005 .for_each(|(label, _)| self.add_used_name(label.clone()));
7006 }
7007
7008 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
7009 variable_names
7010 .names
7011 .iter()
7012 .for_each(|name| self.add_used_name(name.clone()));
7013 }
7014
7015 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) {
7016 for variable in bound_variables {
7017 self.add_used_name(variable.name());
7018 }
7019 }
7020}
7021
7022#[must_use]
7023fn is_valid_lowercase_name(name: &str) -> bool {
7024 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
7025 return false;
7026 }
7027
7028 for char in name.chars() {
7029 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
7030 if !is_valid_char {
7031 return false;
7032 }
7033 }
7034
7035 string_to_keyword(name).is_none()
7036}
7037
7038#[must_use]
7039fn is_valid_uppercase_name(name: &str) -> bool {
7040 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
7041 return false;
7042 }
7043
7044 for char in name.chars() {
7045 if !char.is_ascii_alphanumeric() {
7046 return false;
7047 }
7048 }
7049
7050 true
7051}
7052
7053/// Code action to rewrite a single-step pipeline into a regular function call.
7054/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
7055///
7056pub struct ConvertToFunctionCall<'a> {
7057 module: &'a Module,
7058 params: &'a CodeActionParams,
7059 edits: TextEdits<'a>,
7060 locations: Option<ConvertToFunctionCallLocations>,
7061}
7062
7063/// All the different locations the "Convert to function call" code action needs
7064/// to properly rewrite a pipeline into a function call.
7065///
7066struct ConvertToFunctionCallLocations {
7067 /// This is the location of the value being piped into a call.
7068 ///
7069 /// ```gleam
7070 /// [1, 2, 3] |> list.length
7071 /// // ^^^^^^^^^ This one here
7072 /// ```
7073 ///
7074 first_value: SrcSpan,
7075
7076 /// This is the location of the call the value is being piped into.
7077 ///
7078 /// ```gleam
7079 /// [1, 2, 3] |> list.length
7080 /// // ^^^^^^^^^^^ This one here
7081 /// ```
7082 ///
7083 call: SrcSpan,
7084
7085 /// This is the kind of desugaring that is taking place when piping
7086 /// `first_value` into `call`.
7087 ///
7088 call_kind: PipelineAssignmentKind,
7089}
7090
7091impl<'a> ConvertToFunctionCall<'a> {
7092 pub fn new(
7093 module: &'a Module,
7094 line_numbers: &'a LineNumbers,
7095 params: &'a CodeActionParams,
7096 ) -> Self {
7097 Self {
7098 module,
7099 params,
7100 edits: TextEdits::new(line_numbers),
7101 locations: None,
7102 }
7103 }
7104
7105 pub fn code_actions(mut self) -> Vec<CodeAction> {
7106 self.visit_typed_module(&self.module.ast);
7107
7108 // If we couldn't find a pipeline to rewrite we don't return any action.
7109 let Some(ConvertToFunctionCallLocations {
7110 first_value,
7111 call,
7112 call_kind,
7113 }) = self.locations
7114 else {
7115 return vec![];
7116 };
7117
7118 // We first delete the first value of the pipeline as it's going to be
7119 // inlined as a function call argument.
7120 self.edits.delete(SrcSpan {
7121 start: first_value.start,
7122 end: call.start,
7123 });
7124
7125 // Then we have to insert the piped value in the appropriate position.
7126 // This will change based on how the pipeline is being desugared, we
7127 // know this thanks to the `call_kind`
7128 let first_value_text = self
7129 .module
7130 .code
7131 .get(first_value.start as usize..first_value.end as usize)
7132 .expect("invalid code span")
7133 .to_string();
7134
7135 match call_kind {
7136 // When piping into a `_` we replace the hole with the piped value:
7137 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
7138 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
7139
7140 // When piping is desguared as a function call we need to add the
7141 // missing parentheses:
7142 // `[1, 2] |> length` becomes `length([1, 2])`
7143 PipelineAssignmentKind::FunctionCall => {
7144 self.edits.insert(call.end, format!("({first_value_text})"))
7145 }
7146
7147 // When the piped value is inserted as the first argument there's two
7148 // possible scenarios:
7149 // - there's a second argument as well: in that case we insert it
7150 // before the second arg and add a comma
7151 // - there's no other argument: `[1, 2] |> length()` becomes
7152 // `length([1, 2])`, we insert the value between the empty
7153 // parentheses
7154 PipelineAssignmentKind::FirstArgument {
7155 second_argument: Some(SrcSpan { start, .. }),
7156 } => self.edits.insert(start, format!("{first_value_text}, ")),
7157 PipelineAssignmentKind::FirstArgument {
7158 second_argument: None,
7159 } => self.edits.insert(call.end - 1, first_value_text),
7160
7161 // When the value is piped into an echo, to rewrite the pipeline we
7162 // have to insert the value after the `echo` with no parentheses:
7163 // `a |> echo` is rewritten as `echo a`.
7164 PipelineAssignmentKind::Echo => {
7165 self.edits.insert(call.end, format!(" {first_value_text}"))
7166 }
7167 }
7168
7169 let mut action = Vec::with_capacity(1);
7170 CodeActionBuilder::new("Convert to function call")
7171 .kind(CodeActionKind::REFACTOR_REWRITE)
7172 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7173 .preferred(false)
7174 .push_to(&mut action);
7175 action
7176 }
7177}
7178
7179impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
7180 fn visit_typed_expr_pipeline(
7181 &mut self,
7182 location: &'ast SrcSpan,
7183 first_value: &'ast TypedPipelineAssignment,
7184 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
7185 finally: &'ast TypedExpr,
7186 finally_kind: &'ast PipelineAssignmentKind,
7187 ) {
7188 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
7189 if within(self.params.range, pipeline_range) {
7190 // We will always desugar the pipeline's first step. If there's no
7191 // intermediate assignment it means we're dealing with a single step
7192 // pipeline and the call is `finally`.
7193 let (call, call_kind) = assignments
7194 .first()
7195 .map(|(call, kind)| (call.location, *kind))
7196 .unwrap_or_else(|| (finally.location(), *finally_kind));
7197
7198 self.locations = Some(ConvertToFunctionCallLocations {
7199 first_value: first_value.location,
7200 call,
7201 call_kind,
7202 });
7203
7204 ast::visit::visit_typed_expr_pipeline(
7205 self,
7206 location,
7207 first_value,
7208 assignments,
7209 finally,
7210 finally_kind,
7211 );
7212 }
7213 }
7214}
7215
7216/// Builder for code action to inline a variable.
7217///
7218pub struct InlineVariable<'a> {
7219 module: &'a Module,
7220 params: &'a CodeActionParams,
7221 edits: TextEdits<'a>,
7222 actions: Vec<CodeAction>,
7223}
7224
7225impl<'a> InlineVariable<'a> {
7226 pub fn new(
7227 module: &'a Module,
7228 line_numbers: &'a LineNumbers,
7229 params: &'a CodeActionParams,
7230 ) -> Self {
7231 Self {
7232 module,
7233 params,
7234 edits: TextEdits::new(line_numbers),
7235 actions: Vec::new(),
7236 }
7237 }
7238
7239 pub fn code_actions(mut self) -> Vec<CodeAction> {
7240 self.visit_typed_module(&self.module.ast);
7241
7242 self.actions
7243 }
7244
7245 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) {
7246 let references =
7247 FindVariableReferences::new(location, name).find_in_module(&self.module.ast);
7248 let reference = if references.len() == 1 {
7249 references
7250 .into_iter()
7251 .next()
7252 .expect("References has length 1")
7253 } else {
7254 return;
7255 };
7256
7257 let Some(ast::Statement::Assignment(assignment)) =
7258 self.module.ast.find_statement(location.start)
7259 else {
7260 return;
7261 };
7262
7263 // If the assignment does not simple bind a variable, for example:
7264 // ```gleam
7265 // let #(first, second, third)
7266 // io.println(first)
7267 // // ^ Inline here
7268 // ```
7269 // We can't inline it.
7270 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
7271 return;
7272 }
7273
7274 // If the assignment was generated by the compiler, it doesn't have a
7275 // syntactical representation, so we can't inline it.
7276 if matches!(assignment.kind, AssignmentKind::Generated) {
7277 return;
7278 }
7279
7280 let value_location = assignment.value.location();
7281 let value = self
7282 .module
7283 .code
7284 .get(value_location.start as usize..value_location.end as usize)
7285 .expect("Span is valid");
7286
7287 match reference.kind {
7288 VariableReferenceKind::Variable => {
7289 self.edits.replace(reference.location, value.into());
7290 }
7291 VariableReferenceKind::LabelShorthand => {
7292 self.edits
7293 .insert(reference.location.end, format!(" {value}"));
7294 }
7295 }
7296
7297 let mut location = assignment.location;
7298
7299 let mut chars = self.module.code[location.end as usize..].chars();
7300 // Delete any whitespace after the removed statement
7301 while chars.next().is_some_and(char::is_whitespace) {
7302 location.end += 1;
7303 }
7304
7305 self.edits.delete(location);
7306
7307 CodeActionBuilder::new("Inline variable")
7308 .kind(CodeActionKind::REFACTOR_INLINE)
7309 .changes(
7310 self.params.text_document.uri.clone(),
7311 std::mem::take(&mut self.edits.edits),
7312 )
7313 .preferred(false)
7314 .push_to(&mut self.actions);
7315 }
7316}
7317
7318impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
7319 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
7320 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else {
7321 ast::visit::visit_typed_assignment(self, assignment);
7322 return;
7323 };
7324
7325 // We special case assignment variables because we want to trigger the
7326 // code action also if we're over the let keyword:
7327 //
7328 // ```gleam
7329 // let wibble = 11
7330 // // ^^^^^^^^^^ Here!
7331 // ```
7332 //
7333 let assignment_range = self
7334 .edits
7335 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end));
7336 if !within(self.params.range, assignment_range) {
7337 ast::visit::visit_typed_assignment(self, assignment);
7338 return;
7339 }
7340
7341 self.maybe_inline(*location, name.clone());
7342 }
7343
7344 fn visit_typed_expr_var(
7345 &mut self,
7346 location: &'ast SrcSpan,
7347 constructor: &'ast ValueConstructor,
7348 name: &'ast EcoString,
7349 ) {
7350 let range = self.edits.src_span_to_lsp_range(*location);
7351
7352 if !within(self.params.range, range) {
7353 return;
7354 }
7355
7356 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
7357 &constructor.variant
7358 else {
7359 return;
7360 };
7361
7362 // We can only inline variables assigned by `let` statements, as it
7363 //doesn't make sense to do so with any other kind of variable.
7364 match origin.declaration {
7365 VariableDeclaration::LetPattern => {}
7366 VariableDeclaration::UsePattern
7367 | VariableDeclaration::ClausePattern
7368 | VariableDeclaration::FunctionParameter { .. }
7369 | VariableDeclaration::Generated => return,
7370 }
7371
7372 self.maybe_inline(*location, name.clone());
7373 }
7374
7375 fn visit_typed_pattern_variable(
7376 &mut self,
7377 location: &'ast SrcSpan,
7378 name: &'ast EcoString,
7379 _type: &'ast Arc<Type>,
7380 origin: &'ast VariableOrigin,
7381 ) {
7382 // We can only inline variables assigned by `let` statements, as it
7383 //doesn't make sense to do so with any other kind of variable.
7384 match origin.declaration {
7385 VariableDeclaration::LetPattern => {}
7386 VariableDeclaration::UsePattern
7387 | VariableDeclaration::ClausePattern
7388 | VariableDeclaration::FunctionParameter { .. }
7389 | VariableDeclaration::Generated => return,
7390 }
7391
7392 let range = self.edits.src_span_to_lsp_range(*location);
7393
7394 if !within(self.params.range, range) {
7395 return;
7396 }
7397
7398 self.maybe_inline(*location, name.clone());
7399 }
7400}
7401
7402/// Builder for the "convert to pipe" code action.
7403///
7404/// ```gleam
7405/// pub fn main() {
7406/// wibble(wobble, woo)
7407/// // ^ [convert to pipe]
7408/// }
7409/// ```
7410///
7411/// Will turn the code into the following pipeline:
7412///
7413/// ```gleam
7414/// pub fn main() {
7415/// wobble |> wibble(woo)
7416/// }
7417/// ```
7418///
7419pub struct ConvertToPipe<'a> {
7420 module: &'a Module,
7421 params: &'a CodeActionParams,
7422 edits: TextEdits<'a>,
7423 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
7424 visited_item: VisitedItem,
7425}
7426
7427pub enum VisitedItem {
7428 RegularExpression,
7429 UseRightHandSide,
7430 PipelineFinalStep,
7431}
7432
7433/// Holds all the data needed by the "convert to pipe" code action to properly
7434/// rewrite a call into a pipe. Here's what each span means:
7435///
7436/// ```gleam
7437/// wibble(wobb|le, woo)
7438/// // ^^^^^^^^^^^^^^^^^^^^ call
7439/// // ^^^^^^ called
7440/// // ^^^^^^^ arg
7441/// // ^^^ next arg
7442/// ```
7443///
7444/// In this example `position` is 0, since the cursor is over the first
7445/// argument.
7446///
7447pub struct ConvertToPipeArg<'a> {
7448 /// The span of the called function.
7449 called: SrcSpan,
7450 /// The span of the entire function call.
7451 call: SrcSpan,
7452 /// The position (0-based) of the argument.
7453 position: usize,
7454 /// The argument we have to pipe.
7455 arg: &'a TypedCallArg,
7456 /// The span of the argument following the one we have to pipe, if there's
7457 /// any.
7458 next_arg: Option<SrcSpan>,
7459}
7460
7461impl<'a> ConvertToPipe<'a> {
7462 pub fn new(
7463 module: &'a Module,
7464 line_numbers: &'a LineNumbers,
7465 params: &'a CodeActionParams,
7466 ) -> Self {
7467 Self {
7468 module,
7469 params,
7470 edits: TextEdits::new(line_numbers),
7471 visited_item: VisitedItem::RegularExpression,
7472 argument_to_pipe: None,
7473 }
7474 }
7475
7476 pub fn code_actions(mut self) -> Vec<CodeAction> {
7477 self.visit_typed_module(&self.module.ast);
7478
7479 let Some(ConvertToPipeArg {
7480 called,
7481 call,
7482 position,
7483 arg,
7484 next_arg,
7485 }) = self.argument_to_pipe
7486 else {
7487 return vec![];
7488 };
7489
7490 let arg_location = if arg.uses_label_shorthand() {
7491 SrcSpan {
7492 start: arg.location.start,
7493 end: arg.location.end - 1,
7494 }
7495 } else if arg.label.is_some() {
7496 arg.value.location()
7497 } else {
7498 arg.location
7499 };
7500
7501 let arg_text = code_at(self.module, arg_location);
7502 // If the expression being piped is a binary operation with
7503 // precedence lower than pipes then we have to wrap it in curly
7504 // braces to not mess with the order of operations.
7505 let arg_text = if let TypedExpr::BinOp { name, .. } = arg.value
7506 && name.precedence() < PIPE_PRECEDENCE
7507 {
7508 &format!("{{ {arg_text} }}")
7509 } else {
7510 arg_text
7511 };
7512
7513 match next_arg {
7514 // When extracting an argument we never want to remove any explicit
7515 // label that was written down, so in case it is labelled (be it a
7516 // shorthand or not) we'll always replace the value with a `_`
7517 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
7518 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
7519
7520 // Now we can deal with unlabelled arguments:
7521 // If we're removing the first argument and there's other arguments
7522 // after it, we need to delete the comma that was separating the
7523 // two.
7524 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
7525 start: arg.location.start,
7526 end: next_arg.start,
7527 }),
7528 // Otherwise, if we're deleting the first argument and there's
7529 // no other arguments following it, we remove the call's
7530 // parentheses.
7531 None if position == 0 => self.edits.delete(SrcSpan {
7532 start: called.end,
7533 end: call.end,
7534 }),
7535 // In all other cases we're piping something that is not the first
7536 // argument so we just replace it with an `_`.
7537 _ => self.edits.replace(arg.location, "_".into()),
7538 };
7539
7540 // Finally we can add the argument that was removed as the first step
7541 // of the newly defined pipeline.
7542 self.edits.insert(call.start, format!("{arg_text} |> "));
7543
7544 let mut action = Vec::with_capacity(1);
7545 CodeActionBuilder::new("Convert to pipe")
7546 .kind(CodeActionKind::REFACTOR_REWRITE)
7547 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7548 .preferred(false)
7549 .push_to(&mut action);
7550 action
7551 }
7552}
7553
7554impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
7555 fn visit_typed_expr_call(
7556 &mut self,
7557 location: &'ast SrcSpan,
7558 _type_: &'ast Arc<Type>,
7559 fun: &'ast TypedExpr,
7560 arguments: &'ast [TypedCallArg],
7561 ) {
7562 if arguments.iter().any(|arg| arg.is_capture_hole()) {
7563 return;
7564 }
7565
7566 // If we're visiting the typed function produced by typing a use, we
7567 // skip the thing itself and only visit its arguments and called
7568 // function, that is the body of the use.
7569 match self.visited_item {
7570 VisitedItem::RegularExpression => (),
7571 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => {
7572 self.visited_item = VisitedItem::RegularExpression;
7573 ast::visit::visit_typed_expr(self, fun);
7574 arguments
7575 .iter()
7576 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
7577 return;
7578 }
7579 }
7580
7581 // We only visit a call if the cursor is somewhere within its location,
7582 // otherwise we skip it entirely.
7583 let call_range = self.edits.src_span_to_lsp_range(*location);
7584 if !within(self.params.range, call_range) {
7585 return;
7586 }
7587
7588 // If the cursor is over any of the arguments then we'll use that as
7589 // the one to extract.
7590 // Otherwise the cursor must be over the called function, in that case
7591 // we extract the first argument (if there's one):
7592 //
7593 // ```gleam
7594 // wibble(wobble, woo)
7595 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
7596 // // ^^^ pipe the second argument if I'm here
7597 // ```
7598 let argument_to_pipe = arguments
7599 .iter()
7600 .enumerate()
7601 .find_map(|(position, arg)| {
7602 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
7603 if within(self.params.range, arg_range) {
7604 Some((position, arg))
7605 } else {
7606 None
7607 }
7608 })
7609 .or_else(|| arguments.first().map(|argument| (0, argument)));
7610
7611 // If we're not hovering over any of the arguments _or_ there's no
7612 // argument to extract at all we just return, there's nothing we can do
7613 // on this call or any of its arguments (since we've determined the
7614 // cursor is not over any of those).
7615 let Some((position, arg)) = argument_to_pipe else {
7616 return;
7617 };
7618
7619 self.argument_to_pipe = Some(ConvertToPipeArg {
7620 called: fun.location(),
7621 call: *location,
7622 position,
7623 arg,
7624 next_arg: arguments
7625 .get(position + 1)
7626 .map(|argument| argument.location),
7627 });
7628
7629 // We still want to visit the arguments so that if we're hovering a
7630 // nested pipeline, that's going to be the one we transform:
7631 //
7632 // ```gleam
7633 // wibble(Wobble(
7634 // field: call(other(last(1)))
7635 // // ^^^^ We want to convert this one if we hover over it,
7636 // // not the outer `wibble(Wobble(...))` call
7637 // ))
7638 // ```
7639 //
7640 for argument in arguments {
7641 ast::visit::visit_typed_call_arg(self, argument);
7642 }
7643 }
7644
7645 fn visit_typed_expr_pipeline(
7646 &mut self,
7647 _location: &'ast SrcSpan,
7648 first_value: &'ast TypedPipelineAssignment,
7649 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
7650 finally: &'ast TypedExpr,
7651 _finally_kind: &'ast PipelineAssignmentKind,
7652 ) {
7653 // We can only apply the action on the first step of a pipeline, so we
7654 // visit just that one and skip all the others.
7655 ast::visit::visit_typed_pipeline_assignment(self, first_value);
7656 self.visited_item = VisitedItem::PipelineFinalStep;
7657 ast::visit::visit_typed_expr(self, finally);
7658 }
7659
7660 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
7661 self.visited_item = VisitedItem::UseRightHandSide;
7662 ast::visit::visit_typed_use(self, use_);
7663 }
7664}
7665
7666/// Code action to interpolate a string. If the cursor is inside the string
7667/// (not selecting anything) the language server will offer to split it:
7668///
7669/// ```gleam
7670/// "wibble | wobble"
7671/// // ^ [Split string]
7672/// // Will produce the following
7673/// "wibble " <> todo <> " wobble"
7674/// ```
7675///
7676/// If the cursor is selecting an entire valid gleam name, then the language
7677/// server will offer to interpolate it as a variable:
7678///
7679/// ```gleam
7680/// "wibble wobble woo"
7681/// // ^^^^^^ [Interpolate variable]
7682/// // Will produce the following
7683/// "wibble " <> wobble <> " woo"
7684/// ```
7685///
7686/// > Note: the cursor won't end up right after the inserted variable/todo.
7687/// > that's a bit annoying, but in a future LSP version we will be able to
7688/// > isnert tab stops to allow one to jump to the newly added variable/todo.
7689///
7690pub struct InterpolateString<'a> {
7691 module: &'a Module,
7692 params: &'a CodeActionParams,
7693 edits: TextEdits<'a>,
7694 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
7695 string_literal_position: StringLiteralPosition,
7696}
7697
7698#[derive(Debug, Clone, Copy, Eq, PartialEq)]
7699pub enum StringLiteralPosition {
7700 FirstPipelineStep,
7701 Other,
7702}
7703
7704#[derive(Clone, Copy)]
7705enum StringInterpolation {
7706 InterpolateValue { value_location: SrcSpan },
7707 SplitString { split_at: u32 },
7708}
7709
7710impl<'a> InterpolateString<'a> {
7711 pub fn new(
7712 module: &'a Module,
7713 line_numbers: &'a LineNumbers,
7714 params: &'a CodeActionParams,
7715 ) -> Self {
7716 Self {
7717 module,
7718 params,
7719 edits: TextEdits::new(line_numbers),
7720 string_interpolation: None,
7721 string_literal_position: StringLiteralPosition::Other,
7722 }
7723 }
7724
7725 pub fn code_actions(mut self) -> Vec<CodeAction> {
7726 self.visit_typed_module(&self.module.ast);
7727
7728 let Some((string_location, interpolation)) = self.string_interpolation else {
7729 return vec![];
7730 };
7731
7732 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
7733 self.edits.insert(string_location.start, "{ ".into());
7734 }
7735
7736 match interpolation {
7737 StringInterpolation::InterpolateValue { value_location } => {
7738 let name = self
7739 .module
7740 .code
7741 .get(value_location.start as usize..value_location.end as usize)
7742 .expect("invalid value range");
7743
7744 // We trust that the programmer has correctly selected the part
7745 // of the string they want to interpolate and simply "cut it out"
7746 // for them. In future, we could try and parse their selection to
7747 // see if it is a valid expression in Gleam.
7748 if value_location.start == string_location.start + 1 {
7749 self.edits
7750 .insert(string_location.start, format!("{name} <> "));
7751 } else if value_location.end == string_location.end - 1 {
7752 self.edits
7753 .insert(string_location.end, format!(" <> {name}"));
7754 } else {
7755 self.edits
7756 .insert(value_location.start, format!("\" <> {name} <> \""));
7757 }
7758 self.edits.delete(value_location);
7759 }
7760
7761 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
7762 self.edits.insert(split_at, "\" <> todo <> \"".into());
7763 }
7764
7765 StringInterpolation::SplitString { .. } => return vec![],
7766 };
7767
7768 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
7769 self.edits.insert(string_location.end, " }".into());
7770 }
7771
7772 let mut action = Vec::with_capacity(1);
7773 CodeActionBuilder::new("Interpolate string")
7774 .kind(CodeActionKind::REFACTOR_REWRITE)
7775 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7776 .preferred(false)
7777 .push_to(&mut action);
7778 action
7779 }
7780
7781 fn can_split_string_at(&self, at: u32) -> bool {
7782 self.string_interpolation
7783 .is_some_and(|(string_location, _)| {
7784 !(at <= string_location.start + 1 || at >= string_location.end - 1)
7785 })
7786 }
7787
7788 fn visit_literal_string(
7789 &mut self,
7790 string_location: SrcSpan,
7791 string_position: StringLiteralPosition,
7792 ) {
7793 // We can only interpolate/split a string if the cursor is somewhere
7794 // within its location, otherwise we skip it.
7795 let string_range = self.edits.src_span_to_lsp_range(string_location);
7796 if !within(self.params.range, string_range) {
7797 return;
7798 }
7799
7800 let selection @ SrcSpan { start, end } =
7801 self.edits.lsp_range_to_src_span(self.params.range);
7802
7803 // We can't interpolate/split if the double quotes delimiting the
7804 // string have been selected.
7805 if start == string_location.start || end == string_location.end {
7806 return;
7807 }
7808
7809 let name = self
7810 .module
7811 .code
7812 .get(start as usize..end as usize)
7813 .expect("invalid value range");
7814
7815 // TUI editors like Helix and Kakoune that use the selection-action edit
7816 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always
7817 // have something selected. For programmers using these editors, the
7818 // smallest selection possible is a 1-character selection. The best we can do
7819 // to provide parity with other editors is to consider a single-character SPACE
7820 // selection as an empty selection, as they most likely want to split the
7821 // string instead of interpolating a variable.
7822 let interpolation = if start == end || (end - start == 1 && name == " ") {
7823 StringInterpolation::SplitString { split_at: start }
7824 } else {
7825 StringInterpolation::InterpolateValue {
7826 value_location: selection,
7827 }
7828 };
7829 self.string_interpolation = Some((string_location, interpolation));
7830 self.string_literal_position = string_position;
7831 }
7832}
7833
7834impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
7835 fn visit_typed_expr_string(
7836 &mut self,
7837 location: &'ast SrcSpan,
7838 _type_: &'ast Arc<Type>,
7839 _value: &'ast EcoString,
7840 ) {
7841 self.visit_literal_string(*location, StringLiteralPosition::Other);
7842 }
7843
7844 fn visit_typed_expr_pipeline(
7845 &mut self,
7846 _location: &'ast SrcSpan,
7847 first_value: &'ast TypedPipelineAssignment,
7848 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
7849 finally: &'ast TypedExpr,
7850 _finally_kind: &'ast PipelineAssignmentKind,
7851 ) {
7852 if first_value.value.is_literal_string() {
7853 self.visit_literal_string(
7854 first_value.location,
7855 StringLiteralPosition::FirstPipelineStep,
7856 );
7857 } else {
7858 ast::visit::visit_typed_pipeline_assignment(self, first_value);
7859 }
7860
7861 assignments
7862 .iter()
7863 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
7864 self.visit_typed_expr(finally);
7865 }
7866}
7867
7868/// Code action to replace a `..` in a pattern with all the missing fields that
7869/// have not been explicitly provided; labelled ones are introduced with the
7870/// shorthand syntax.
7871///
7872/// ```gleam
7873/// pub type Pokemon {
7874/// Pokemon(Int, name: String, moves: List(String))
7875/// }
7876///
7877/// pub fn main() {
7878/// let Pokemon(..) = todo
7879/// // ^^ Cursor over the spread
7880/// }
7881/// ```
7882/// Would become
7883/// ```gleam
7884/// pub fn main() {
7885/// let Pokemon(int, name:, moves:) = todo
7886/// }
7887///
7888pub struct FillUnusedFields<'a> {
7889 module: &'a Module,
7890 params: &'a CodeActionParams,
7891 edits: TextEdits<'a>,
7892 data: Option<FillUnusedFieldsData>,
7893}
7894
7895pub struct FillUnusedFieldsData {
7896 /// All the missing positional and labelled fields.
7897 positional: Vec<Arc<Type>>,
7898 labelled: Vec<(EcoString, Arc<Type>)>,
7899 /// We need this in order to tell where the missing positional arguments
7900 /// should be inserted.
7901 first_labelled_argument_start: Option<u32>,
7902 /// The end of the final argument before the spread, if there's any.
7903 /// We'll use this to delete everything that comes after the final argument,
7904 /// after adding all the ignored fields.
7905 last_argument_end: Option<u32>,
7906 spread_location: SrcSpan,
7907}
7908
7909impl<'a> FillUnusedFields<'a> {
7910 pub fn new(
7911 module: &'a Module,
7912 line_numbers: &'a LineNumbers,
7913 params: &'a CodeActionParams,
7914 ) -> Self {
7915 Self {
7916 module,
7917 params,
7918 edits: TextEdits::new(line_numbers),
7919 data: None,
7920 }
7921 }
7922
7923 pub fn code_actions(mut self) -> Vec<CodeAction> {
7924 self.visit_typed_module(&self.module.ast);
7925
7926 let Some(FillUnusedFieldsData {
7927 positional,
7928 labelled,
7929 first_labelled_argument_start,
7930 last_argument_end,
7931 spread_location,
7932 }) = self.data
7933 else {
7934 return vec![];
7935 };
7936
7937 // Do not suggest this code action if there's no ignored fields at all.
7938 if positional.is_empty() && labelled.is_empty() {
7939 return vec![];
7940 };
7941
7942 // We add all the missing positional arguments before the first
7943 // labelled one (and so after all the already existing positional ones).
7944 if !positional.is_empty() {
7945 // We want to make sure that all positional args will have a name
7946 // that's different from any label. So we add those as already used
7947 // names.
7948 let mut names = NameGenerator::new();
7949 for (label, _) in labelled.iter() {
7950 names.add_used_name(label.clone());
7951 }
7952
7953 let positional_arguments = positional
7954 .iter()
7955 .map(|type_| names.generate_name_from_type(type_))
7956 .join(", ");
7957 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
7958
7959 // The positional arguments are going to be followed by some other
7960 // arguments if there's some already existing labelled args
7961 // (`last_argument_end.is_some`), of if we're adding those labelled args
7962 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
7963 // final positional argument we're adding to separate it from the ones that
7964 // are going to come after.
7965 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
7966 let positional_arguments = if has_arguments_after {
7967 format!("{positional_arguments}, ")
7968 } else {
7969 positional_arguments
7970 };
7971
7972 self.edits.insert(insert_at, positional_arguments);
7973 }
7974
7975 if !labelled.is_empty() {
7976 // If there's labelled arguments to add, we replace the existing spread
7977 // with the arguments to be added. This way commas and all should already
7978 // be correct.
7979 let labelled_arguments = labelled
7980 .iter()
7981 .map(|(label, _)| format!("{label}:"))
7982 .join(", ");
7983 self.edits.replace(spread_location, labelled_arguments);
7984 } else if let Some(delete_start) = last_argument_end {
7985 // However, if there's no labelled arguments to insert we still need
7986 // to delete the entire spread: we start deleting from the end of the
7987 // final argument, if there's one.
7988 // This way we also get rid of any comma separating the last argument
7989 // and the spread to be removed.
7990 self.edits
7991 .delete(SrcSpan::new(delete_start, spread_location.end))
7992 } else {
7993 // Otherwise we just delete the spread.
7994 self.edits.delete(spread_location)
7995 }
7996
7997 let mut action = Vec::with_capacity(1);
7998 CodeActionBuilder::new("Fill unused fields")
7999 .kind(CodeActionKind::REFACTOR_REWRITE)
8000 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8001 .preferred(false)
8002 .push_to(&mut action);
8003 action
8004 }
8005}
8006
8007impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
8008 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
8009 // We can only interpolate/split a string if the cursor is somewhere
8010 // within its location, otherwise we skip it.
8011 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
8012 if !within(self.params.range, pattern_range) {
8013 return;
8014 }
8015
8016 if let TypedPattern::Constructor {
8017 arguments,
8018 spread: Some(spread_location),
8019 ..
8020 } = pattern
8021 && let Some(PatternUnusedArguments {
8022 positional,
8023 labelled,
8024 }) = pattern.unused_arguments()
8025 {
8026 // If there's any unused argument that's being ignored we want to
8027 // suggest the code action.
8028 let first_labelled_argument_start = arguments
8029 .iter()
8030 .find(|arg| !arg.is_implicit() && arg.label.is_some())
8031 .map(|arg| arg.location.start);
8032
8033 let last_argument_end = arguments
8034 .iter()
8035 .rfind(|arg| !arg.is_implicit())
8036 .map(|arg| arg.location.end);
8037
8038 self.data = Some(FillUnusedFieldsData {
8039 positional,
8040 labelled,
8041 first_labelled_argument_start,
8042 last_argument_end,
8043 spread_location: *spread_location,
8044 });
8045 };
8046
8047 ast::visit::visit_typed_pattern(self, pattern);
8048 }
8049}
8050
8051/// Code action to remove an echo.
8052///
8053pub struct RemoveEchos<'a> {
8054 module: &'a Module,
8055 params: &'a CodeActionParams,
8056 edits: TextEdits<'a>,
8057 is_hovering_echo: bool,
8058 echo_spans_to_delete: Vec<SrcSpan>,
8059 // We need to keep a reference to the two latest pipeline assignments we
8060 // run into to properly delete an echo that's inside a pipeline.
8061 latest_pipe_step: Option<SrcSpan>,
8062 second_to_latest_pipe_step: Option<SrcSpan>,
8063}
8064
8065impl<'a> RemoveEchos<'a> {
8066 pub fn new(
8067 module: &'a Module,
8068 line_numbers: &'a LineNumbers,
8069 params: &'a CodeActionParams,
8070 ) -> Self {
8071 Self {
8072 module,
8073 params,
8074 edits: TextEdits::new(line_numbers),
8075 is_hovering_echo: false,
8076 echo_spans_to_delete: vec![],
8077 latest_pipe_step: None,
8078 second_to_latest_pipe_step: None,
8079 }
8080 }
8081
8082 pub fn code_actions(mut self) -> Vec<CodeAction> {
8083 self.visit_typed_module(&self.module.ast);
8084
8085 // We only want to trigger the action if we're over one of the echos in
8086 // the module
8087 if !self.is_hovering_echo {
8088 return vec![];
8089 };
8090
8091 for span in self.echo_spans_to_delete {
8092 self.edits.delete(span);
8093 }
8094
8095 let mut action = Vec::with_capacity(1);
8096 CodeActionBuilder::new("Remove all `echo`s from this module")
8097 .kind(CodeActionKind::REFACTOR_REWRITE)
8098 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8099 .preferred(false)
8100 .push_to(&mut action);
8101 action
8102 }
8103
8104 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
8105 for i in 0..statements.len() {
8106 let statement = statements
8107 .get(i)
8108 .expect("Statement must exist in iteration");
8109 let next_statement = statements.get(i + 1);
8110 let is_last = i == statements.len() - 1;
8111
8112 match statement {
8113 // We remove any echo that is used as a standalone statement used
8114 // to print a literal value.
8115 //
8116 // ```gleam
8117 // pub fn main() {
8118 // echo "I'm here"
8119 // do_something()
8120 // echo "Safe!"
8121 // do_something_else()
8122 // }
8123 // ```
8124 //
8125 // Here we want to remove not just the echo but also the literal
8126 // strings they're printing.
8127 //
8128 // It's safe to do this only if echo is not the last expression
8129 // in a function's block (otherwise we might change the function's
8130 // return type by removing the entire line) and the value being
8131 // printed is a literal expression.
8132 //
8133 ast::Statement::Expression(TypedExpr::Echo {
8134 location,
8135 expression,
8136 ..
8137 }) if !is_last
8138 && expression.as_ref().is_some_and(|expression| {
8139 expression.is_literal() || expression.is_var()
8140 }) =>
8141 {
8142 let echo_range = self.edits.src_span_to_lsp_range(*location);
8143 if within(self.params.range, echo_range) {
8144 self.is_hovering_echo = true;
8145 }
8146
8147 let end = next_statement
8148 .map(|next| {
8149 let echo_end = location.end;
8150 let next_start = next.location().start;
8151 // We want to remove everything until the start of the
8152 // following statement. However, we have to be careful not to
8153 // delete any comments. So if there's any comment between the
8154 // echo to remove and the next statement, we just delete until
8155 // the comment's start.
8156 self.module
8157 .extra
8158 .first_comment_between(echo_end, next_start)
8159 // For comments we record the start of their content, not of the `//`
8160 // so we're subtracting 2 here to not delete the `//` as well
8161 .map(|comment| comment.start - 2)
8162 .unwrap_or(next_start)
8163 })
8164 .unwrap_or(location.end);
8165
8166 self.echo_spans_to_delete.push(SrcSpan {
8167 start: location.start,
8168 end,
8169 });
8170 }
8171
8172 // Otherwise we visit the statement as usual.
8173 ast::Statement::Expression(_)
8174 | ast::Statement::Assignment(_)
8175 | ast::Statement::Use(_)
8176 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement),
8177 }
8178 }
8179 }
8180}
8181
8182impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
8183 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
8184 self.visit_function_statements(&fun.body);
8185 }
8186
8187 fn visit_typed_expr_fn(
8188 &mut self,
8189 _location: &'ast SrcSpan,
8190 _type_: &'ast Arc<Type>,
8191 _kind: &'ast FunctionLiteralKind,
8192 _arguments: &'ast [TypedArg],
8193 body: &'ast Vec1<TypedStatement>,
8194 _return_annotation: &'ast Option<ast::TypeAst>,
8195 ) {
8196 self.visit_function_statements(body);
8197 }
8198
8199 fn visit_typed_expr_echo(
8200 &mut self,
8201 location: &'ast SrcSpan,
8202 type_: &'ast Arc<Type>,
8203 expression: &'ast Option<Box<TypedExpr>>,
8204 message: &'ast Option<Box<TypedExpr>>,
8205 ) {
8206 // We also want to trigger the action if we're hovering over the expression
8207 // being printed. So we create a unique span starting from the start of echo
8208 // end ending at the end of the expression.
8209 //
8210 // ```
8211 // echo 1 + 2
8212 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
8213 // inside it, not just the keyword
8214 // ```
8215 //
8216 let echo_range = self.edits.src_span_to_lsp_range(*location);
8217 if within(self.params.range, echo_range) {
8218 self.is_hovering_echo = true;
8219 }
8220
8221 // We also want to remove the echo message!
8222 if message.is_some() {
8223 let start = expression
8224 .as_ref()
8225 .map(|expression| expression.location().end)
8226 .unwrap_or(location.start + 4);
8227
8228 self.echo_spans_to_delete
8229 .push(SrcSpan::new(start, location.end));
8230 }
8231
8232 if let Some(expression) = expression {
8233 // If there's an expression we delete everything we find until its
8234 // start (excluded).
8235 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
8236 self.echo_spans_to_delete.push(span_to_delete);
8237 } else {
8238 // Othwerise we know we're inside a pipeline, we take the closest step
8239 // that is not echo itself and delete everything from its end until the
8240 // end of the echo keyword:
8241 //
8242 // ```txt
8243 // wibble |> echo |> wobble
8244 // ^^^^^^^^ This span right here
8245 // ```
8246 let step_preceding_echo = self
8247 .latest_pipe_step
8248 .filter(|l| l != location)
8249 .or(self.second_to_latest_pipe_step);
8250 if let Some(step_preceding_echo) = step_preceding_echo {
8251 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
8252 self.echo_spans_to_delete.push(span_to_delete);
8253 }
8254 }
8255
8256 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message);
8257 }
8258
8259 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
8260 if self.latest_pipe_step.is_some() {
8261 self.second_to_latest_pipe_step = self.latest_pipe_step;
8262 }
8263 self.latest_pipe_step = Some(assignment.location);
8264 ast::visit::visit_typed_pipeline_assignment(self, assignment);
8265 }
8266}
8267
8268/// Code action to wrap assignment and case clause values in a block.
8269///
8270/// ```gleam
8271/// pub type PokemonType {
8272/// Fire
8273/// Water
8274/// }
8275///
8276/// pub fn main() {
8277/// let pokemon_type: PokemonType = todo
8278/// case pokemon_type {
8279/// Water -> soak()
8280/// ^^^^^^ Cursor over the spread
8281/// Fire -> burn()
8282/// }
8283/// }
8284/// ```
8285/// Becomes
8286/// ```gleam
8287/// pub type PokemonType {
8288/// Fire
8289/// Water
8290/// }
8291///
8292/// pub fn main() {
8293/// let pokemon_type: PokemonType = todo
8294/// case pokemon_type {
8295/// Water -> {
8296/// soak()
8297/// }
8298/// Fire -> burn()
8299/// }
8300/// }
8301/// ```
8302///
8303pub struct WrapInBlock<'a> {
8304 module: &'a Module,
8305 params: &'a CodeActionParams,
8306 edits: TextEdits<'a>,
8307 selected_expression: Option<SrcSpan>,
8308}
8309
8310impl<'a> WrapInBlock<'a> {
8311 pub fn new(
8312 module: &'a Module,
8313 line_numbers: &'a LineNumbers,
8314 params: &'a CodeActionParams,
8315 ) -> Self {
8316 Self {
8317 module,
8318 params,
8319 edits: TextEdits::new(line_numbers),
8320 selected_expression: None,
8321 }
8322 }
8323
8324 pub fn code_actions(mut self) -> Vec<CodeAction> {
8325 self.visit_typed_module(&self.module.ast);
8326
8327 let Some(expr_span) = self.selected_expression else {
8328 return vec![];
8329 };
8330
8331 let Some(expr_string) = self
8332 .module
8333 .code
8334 .get(expr_span.start as usize..(expr_span.end as usize + 1))
8335 else {
8336 return vec![];
8337 };
8338
8339 let range = self
8340 .edits
8341 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
8342
8343 let indent_size =
8344 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
8345
8346 let expr_indent_size = indent_size + 2;
8347
8348 let indent = " ".repeat(indent_size);
8349 let inner_indent = " ".repeat(expr_indent_size);
8350
8351 self.edits.replace(
8352 expr_span,
8353 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
8354 );
8355
8356 let mut action = Vec::with_capacity(1);
8357 CodeActionBuilder::new("Wrap in block")
8358 .kind(CodeActionKind::REFACTOR_EXTRACT)
8359 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8360 .preferred(false)
8361 .push_to(&mut action);
8362 action
8363 }
8364}
8365
8366impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
8367 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
8368 ast::visit::visit_typed_expr(self, &assignment.value);
8369 if !within(
8370 self.params.range,
8371 self.edits
8372 .src_span_to_lsp_range(assignment.value.location()),
8373 ) {
8374 return;
8375 }
8376 match &assignment.value {
8377 // To avoid wrapping the same expression in multiple, nested blocks.
8378 TypedExpr::Block { .. } => {}
8379 TypedExpr::RecordAccess { .. }
8380 | TypedExpr::PositionalAccess { .. }
8381 | TypedExpr::Int { .. }
8382 | TypedExpr::Float { .. }
8383 | TypedExpr::String { .. }
8384 | TypedExpr::Pipeline { .. }
8385 | TypedExpr::Var { .. }
8386 | TypedExpr::Fn { .. }
8387 | TypedExpr::List { .. }
8388 | TypedExpr::Call { .. }
8389 | TypedExpr::BinOp { .. }
8390 | TypedExpr::Case { .. }
8391 | TypedExpr::ModuleSelect { .. }
8392 | TypedExpr::Tuple { .. }
8393 | TypedExpr::TupleIndex { .. }
8394 | TypedExpr::Todo { .. }
8395 | TypedExpr::Panic { .. }
8396 | TypedExpr::Echo { .. }
8397 | TypedExpr::BitArray { .. }
8398 | TypedExpr::RecordUpdate { .. }
8399 | TypedExpr::NegateBool { .. }
8400 | TypedExpr::NegateInt { .. }
8401 | TypedExpr::Invalid { .. } => {
8402 self.selected_expression = Some(assignment.value.location());
8403 }
8404 };
8405 ast::visit::visit_typed_assignment(self, assignment);
8406 }
8407
8408 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
8409 ast::visit::visit_typed_clause(self, clause);
8410
8411 if !within(
8412 self.params.range,
8413 self.edits.src_span_to_lsp_range(clause.then.location()),
8414 ) {
8415 return;
8416 }
8417
8418 // To avoid wrapping the same expression in multiple, nested blocks.
8419 if !matches!(clause.then, TypedExpr::Block { .. }) {
8420 self.selected_expression = Some(clause.then.location());
8421 };
8422
8423 ast::visit::visit_typed_clause(self, clause);
8424 }
8425}
8426
8427/// Code action to fix wrong binary operators when the compiler can easily tell
8428/// what the correct alternative is.
8429///
8430/// ```gleam
8431/// 1 +. 2 // becomes 1 + 2
8432/// 1.0 + 2.3 // becomes 1.0 +. 2.3
8433/// ```
8434///
8435pub struct FixBinaryOperation<'a> {
8436 module: &'a Module,
8437 params: &'a CodeActionParams,
8438 edits: TextEdits<'a>,
8439 fix: Option<(SrcSpan, ast::BinOp)>,
8440}
8441
8442impl<'a> FixBinaryOperation<'a> {
8443 pub fn new(
8444 module: &'a Module,
8445 line_numbers: &'a LineNumbers,
8446 params: &'a CodeActionParams,
8447 ) -> Self {
8448 Self {
8449 module,
8450 params,
8451 edits: TextEdits::new(line_numbers),
8452 fix: None,
8453 }
8454 }
8455
8456 pub fn code_actions(mut self) -> Vec<CodeAction> {
8457 self.visit_typed_module(&self.module.ast);
8458
8459 let Some((location, replacement)) = self.fix else {
8460 return vec![];
8461 };
8462
8463 self.edits.replace(location, replacement.name().into());
8464
8465 let mut action = Vec::with_capacity(1);
8466 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
8467 .kind(CodeActionKind::REFACTOR_REWRITE)
8468 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8469 .preferred(true)
8470 .push_to(&mut action);
8471 action
8472 }
8473}
8474
8475impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
8476 fn visit_typed_expr_bin_op(
8477 &mut self,
8478 location: &'ast SrcSpan,
8479 type_: &'ast Arc<Type>,
8480 name: &'ast ast::BinOp,
8481 name_location: &'ast SrcSpan,
8482 left: &'ast TypedExpr,
8483 right: &'ast TypedExpr,
8484 ) {
8485 let binop_range = self.edits.src_span_to_lsp_range(*location);
8486 if !within(self.params.range, binop_range) {
8487 return;
8488 }
8489
8490 if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() {
8491 self.fix = name.float_equivalent().map(|fix| (*name_location, fix));
8492 } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() {
8493 self.fix = name.int_equivalent().map(|fix| (*name_location, fix))
8494 } else if *name == ast::BinOp::AddInt
8495 && left.type_().is_string()
8496 && right.type_().is_string()
8497 {
8498 self.fix = Some((*name_location, ast::BinOp::Concatenate))
8499 }
8500
8501 ast::visit::visit_typed_expr_bin_op(
8502 self,
8503 location,
8504 type_,
8505 name,
8506 name_location,
8507 left,
8508 right,
8509 );
8510 }
8511}
8512
8513/// Code action builder to automatically fix segments that have a value that's
8514/// guaranteed to overflow.
8515///
8516pub struct FixTruncatedBitArraySegment<'a> {
8517 module: &'a Module,
8518 params: &'a CodeActionParams,
8519 edits: TextEdits<'a>,
8520 truncation: Option<BitArraySegmentTruncation>,
8521}
8522
8523impl<'a> FixTruncatedBitArraySegment<'a> {
8524 pub fn new(
8525 module: &'a Module,
8526 line_numbers: &'a LineNumbers,
8527 params: &'a CodeActionParams,
8528 ) -> Self {
8529 Self {
8530 module,
8531 params,
8532 edits: TextEdits::new(line_numbers),
8533 truncation: None,
8534 }
8535 }
8536
8537 pub fn code_actions(mut self) -> Vec<CodeAction> {
8538 self.visit_typed_module(&self.module.ast);
8539
8540 let Some(truncation) = self.truncation else {
8541 return vec![];
8542 };
8543
8544 let replacement = truncation.truncated_into.to_string();
8545 self.edits
8546 .replace(truncation.value_location, replacement.clone());
8547
8548 let mut action = Vec::with_capacity(1);
8549 CodeActionBuilder::new(&format!("Replace with `{replacement}`"))
8550 .kind(CodeActionKind::REFACTOR_REWRITE)
8551 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8552 .preferred(true)
8553 .push_to(&mut action);
8554 action
8555 }
8556}
8557
8558impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
8559 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
8560 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
8561 if !within(self.params.range, segment_range) {
8562 return;
8563 }
8564
8565 if let Some(truncation) = segment.check_for_truncated_value() {
8566 self.truncation = Some(truncation);
8567 }
8568
8569 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
8570 }
8571}
8572
8573/// Code action builder to remove unused imports and values.
8574///
8575pub struct RemoveUnusedImports<'a> {
8576 module: &'a Module,
8577 params: &'a CodeActionParams,
8578 edits: TextEdits<'a>,
8579}
8580
8581#[derive(Debug)]
8582enum UnusedImport {
8583 ValueOrType(SrcSpan),
8584 Module(SrcSpan),
8585 ModuleAlias(SrcSpan),
8586}
8587
8588impl UnusedImport {
8589 fn location(&self) -> SrcSpan {
8590 match self {
8591 UnusedImport::ValueOrType(location)
8592 | UnusedImport::Module(location)
8593 | UnusedImport::ModuleAlias(location) => *location,
8594 }
8595 }
8596}
8597
8598impl<'a> RemoveUnusedImports<'a> {
8599 pub fn new(
8600 module: &'a Module,
8601 line_numbers: &'a LineNumbers,
8602 params: &'a CodeActionParams,
8603 ) -> Self {
8604 Self {
8605 module,
8606 params,
8607 edits: TextEdits::new(line_numbers),
8608 }
8609 }
8610
8611 /// Given an import location, returns a list of the spans of all the
8612 /// unqualified values it's importing. Sorted by SrcSpan location.
8613 ///
8614 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> {
8615 self.module
8616 .ast
8617 .definitions
8618 .imports
8619 .iter()
8620 .find(|import| import.location.contains(import_location.start))
8621 .map(|import| {
8622 let types = import.unqualified_types.iter().map(|type_| type_.location);
8623 let values = import.unqualified_values.iter().map(|value| value.location);
8624 types
8625 .chain(values)
8626 .sorted_by_key(|location| location.start)
8627 .collect_vec()
8628 })
8629 .unwrap_or_default()
8630 }
8631
8632 pub fn code_actions(mut self) -> Vec<CodeAction> {
8633 // If there's no import in the module then there can't be any unused
8634 // import to remove.
8635 if self.module.ast.definitions.imports.is_empty() {
8636 return vec![];
8637 }
8638
8639 let unused_imports = self
8640 .module
8641 .ast
8642 .type_info
8643 .warnings
8644 .iter()
8645 .filter_map(|warning| match warning {
8646 type_::Warning::UnusedImportedValue { location, .. } => {
8647 Some(UnusedImport::ValueOrType(*location))
8648 }
8649 type_::Warning::UnusedType {
8650 location,
8651 imported: true,
8652 ..
8653 } => Some(UnusedImport::ValueOrType(*location)),
8654 type_::Warning::UnusedImportedModule { location, .. } => {
8655 Some(UnusedImport::Module(*location))
8656 }
8657 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
8658 Some(UnusedImport::ModuleAlias(*location))
8659 }
8660 type_::Warning::Todo { .. }
8661 | type_::Warning::ImplicitlyDiscardedResult { .. }
8662 | type_::Warning::UnusedLiteral { .. }
8663 | type_::Warning::UnusedValue { .. }
8664 | type_::Warning::NoFieldsRecordUpdate { .. }
8665 | type_::Warning::AllFieldsRecordUpdate { .. }
8666 | type_::Warning::UnusedType { .. }
8667 | type_::Warning::UnusedConstructor { .. }
8668 | type_::Warning::UnusedPrivateModuleConstant { .. }
8669 | type_::Warning::UnusedPrivateFunction { .. }
8670 | type_::Warning::UnusedVariable { .. }
8671 | type_::Warning::UnnecessaryDoubleIntNegation { .. }
8672 | type_::Warning::UnnecessaryDoubleBoolNegation { .. }
8673 | type_::Warning::InefficientEmptyListCheck { .. }
8674 | type_::Warning::TransitiveDependencyImported { .. }
8675 | type_::Warning::DeprecatedItem { .. }
8676 | type_::Warning::UnreachableCasePattern { .. }
8677 | type_::Warning::UnusedDiscardPattern { .. }
8678 | type_::Warning::CaseMatchOnLiteralCollection { .. }
8679 | type_::Warning::CaseMatchOnLiteralValue { .. }
8680 | type_::Warning::OpaqueExternalType { .. }
8681 | type_::Warning::InternalTypeLeak { .. }
8682 | type_::Warning::RedundantAssertAssignment { .. }
8683 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. }
8684 | type_::Warning::TodoOrPanicUsedAsFunction { .. }
8685 | type_::Warning::UnreachableCodeAfterPanic { .. }
8686 | type_::Warning::RedundantPipeFunctionCapture { .. }
8687 | type_::Warning::FeatureRequiresHigherGleamVersion { .. }
8688 | type_::Warning::JavaScriptIntUnsafe { .. }
8689 | type_::Warning::AssertLiteralBool { .. }
8690 | type_::Warning::BitArraySegmentTruncatedValue { .. }
8691 | type_::Warning::ModuleImportedTwice { .. }
8692 | type_::Warning::TopLevelDefinitionShadowsImport { .. }
8693 | type_::Warning::RedundantComparison { .. }
8694 | type_::Warning::UnusedRecursiveArgument { .. }
8695 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. } => None,
8696 })
8697 .sorted_by_key(|import| import.location())
8698 .collect_vec();
8699
8700 // If the cursor is not over any of the unused imports then we don't offer
8701 // the code action.
8702 let hovering_unused_import = unused_imports.iter().any(|import| {
8703 let unused_range = self.edits.src_span_to_lsp_range(import.location());
8704 overlaps(self.params.range, unused_range)
8705 });
8706 if !hovering_unused_import {
8707 return vec![];
8708 }
8709
8710 // Otherwise we start removing all unused imports:
8711 for import in &unused_imports {
8712 match import {
8713 // When an entire module is unused we can delete its entire location
8714 // in the source code.
8715 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
8716 if self.edits.line_numbers.spans_entire_line(location) {
8717 // If the unused module spans over the entire line then
8718 // we also take care of removing the following newline
8719 // characther!
8720 self.edits.delete(SrcSpan {
8721 start: location.start,
8722 end: location.end + 1,
8723 })
8724 } else {
8725 self.edits.delete(*location)
8726 }
8727 }
8728
8729 // When removing unused imported values we have to be a bit more
8730 // careful: an unused value might be followed or preceded by a
8731 // comma that we also need to remove!
8732 UnusedImport::ValueOrType(location) => {
8733 let imported = self.imported_values(*location);
8734 let unused_index = imported.binary_search(location);
8735 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1);
8736 let next_value = unused_index
8737 .ok()
8738 .and_then(|value_index| imported.get(value_index + 1));
8739 let previous_value = unused_index.ok().and_then(|value_index| {
8740 value_index
8741 .checked_sub(1)
8742 .and_then(|previous_index| imported.get(previous_index))
8743 });
8744 let previous_is_unused = previous_value.is_some_and(|previous| {
8745 unused_imports
8746 .as_slice()
8747 .binary_search_by_key(previous, |import| import.location())
8748 .is_ok()
8749 });
8750
8751 match (previous_value, next_value) {
8752 // If there's a value following the unused import we need
8753 // to remove all characters until its start!
8754 //
8755 // ```gleam
8756 // import wibble.{unused, used}
8757 // // ^^^^^^^^^^^ We need to remove all of this!
8758 // ```
8759 //
8760 (_, Some(next_value)) => self.edits.delete(SrcSpan {
8761 start: location.start,
8762 end: next_value.start,
8763 }),
8764
8765 // If this unused import is the last of the unuqualified
8766 // list and is preceded by another used value then we
8767 // need to do some additional cleanup and remove all
8768 // characters starting from its end.
8769 // (If the previous one is unused as well it will take
8770 // care of removing all the extra space)
8771 //
8772 // ```gleam
8773 // import wibble.{used, unused}
8774 // // ^^^^^^^^^^^^ We need to remove all of this!
8775 // ```
8776 //
8777 (Some(previous_value), _) if is_last && !previous_is_unused => {
8778 self.edits.delete(SrcSpan {
8779 start: previous_value.end,
8780 end: location.end,
8781 })
8782 }
8783
8784 // In all other cases it means that this is the only
8785 // item in the import list. We can just remove it.
8786 //
8787 // ```gleam
8788 // import wibble.{unused}
8789 // // ^^^^^^ We remove this import, the formatter will already
8790 // // take care of removing the empty curly braces
8791 // ```
8792 //
8793 (_, _) => self.edits.delete(*location),
8794 }
8795 }
8796 }
8797 }
8798
8799 let mut action = Vec::with_capacity(1);
8800 CodeActionBuilder::new("Remove unused imports")
8801 .kind(CodeActionKind::REFACTOR_REWRITE)
8802 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8803 .preferred(true)
8804 .push_to(&mut action);
8805 action
8806 }
8807}
8808
8809/// Code action to remove a block wrapping a single expression.
8810///
8811pub struct RemoveBlock<'a> {
8812 module: &'a Module,
8813 params: &'a CodeActionParams,
8814 edits: TextEdits<'a>,
8815 block_span: Option<SrcSpan>,
8816 position: RemoveBlockPosition,
8817}
8818
8819#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
8820enum RemoveBlockPosition {
8821 InsideBinOp,
8822 OutsideBinOp,
8823}
8824
8825impl<'a> RemoveBlock<'a> {
8826 pub fn new(
8827 module: &'a Module,
8828 line_numbers: &'a LineNumbers,
8829 params: &'a CodeActionParams,
8830 ) -> Self {
8831 Self {
8832 module,
8833 params,
8834 edits: TextEdits::new(line_numbers),
8835 block_span: None,
8836 position: RemoveBlockPosition::OutsideBinOp,
8837 }
8838 }
8839
8840 pub fn code_actions(mut self) -> Vec<CodeAction> {
8841 self.visit_typed_module(&self.module.ast);
8842
8843 let Some(SrcSpan { start, end }) = self.block_span else {
8844 return vec![];
8845 };
8846
8847 self.edits.delete(SrcSpan::new(start, start + 1));
8848 self.edits.delete(SrcSpan::new(end - 1, end));
8849
8850 let mut action = Vec::with_capacity(1);
8851 CodeActionBuilder::new("Remove block")
8852 .kind(CodeActionKind::REFACTOR_REWRITE)
8853 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8854 .preferred(true)
8855 .push_to(&mut action);
8856 action
8857 }
8858}
8859
8860impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> {
8861 fn visit_typed_expr_bin_op(
8862 &mut self,
8863 _location: &'ast SrcSpan,
8864 _type_: &'ast Arc<Type>,
8865 _name: &'ast ast::BinOp,
8866 _name_location: &'ast SrcSpan,
8867 left: &'ast TypedExpr,
8868 right: &'ast TypedExpr,
8869 ) {
8870 let old_position = self.position;
8871 self.position = RemoveBlockPosition::InsideBinOp;
8872 ast::visit::visit_typed_expr(self, left);
8873 self.position = RemoveBlockPosition::InsideBinOp;
8874 ast::visit::visit_typed_expr(self, right);
8875 self.position = old_position;
8876 }
8877
8878 fn visit_typed_expr_block(
8879 &mut self,
8880 location: &'ast SrcSpan,
8881 statements: &'ast [TypedStatement],
8882 ) {
8883 let block_range = self.edits.src_span_to_lsp_range(*location);
8884 if !within(self.params.range, block_range) {
8885 return;
8886 }
8887
8888 match statements {
8889 [] | [_, _, ..] => (),
8890 [value] => match value {
8891 ast::Statement::Use(_)
8892 | ast::Statement::Assert(_)
8893 | ast::Statement::Assignment(_) => {
8894 ast::visit::visit_typed_expr_block(self, location, statements)
8895 }
8896
8897 ast::Statement::Expression(expr) => match expr {
8898 TypedExpr::Int { .. }
8899 | TypedExpr::Float { .. }
8900 | TypedExpr::String { .. }
8901 | TypedExpr::Block { .. }
8902 | TypedExpr::Var { .. }
8903 | TypedExpr::Fn { .. }
8904 | TypedExpr::List { .. }
8905 | TypedExpr::Call { .. }
8906 | TypedExpr::Case { .. }
8907 | TypedExpr::RecordAccess { .. }
8908 | TypedExpr::PositionalAccess { .. }
8909 | TypedExpr::ModuleSelect { .. }
8910 | TypedExpr::Tuple { .. }
8911 | TypedExpr::TupleIndex { .. }
8912 | TypedExpr::Todo { .. }
8913 | TypedExpr::Panic { .. }
8914 | TypedExpr::Echo { .. }
8915 | TypedExpr::BitArray { .. }
8916 | TypedExpr::RecordUpdate { .. }
8917 | TypedExpr::NegateBool { .. }
8918 | TypedExpr::NegateInt { .. }
8919 | TypedExpr::Invalid { .. } => {
8920 self.block_span = Some(*location);
8921 }
8922 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => {
8923 if self.position == RemoveBlockPosition::OutsideBinOp {
8924 self.block_span = Some(*location);
8925 }
8926 }
8927 },
8928 },
8929 }
8930
8931 ast::visit::visit_typed_expr_block(self, location, statements);
8932 }
8933}
8934
8935/// Code action to remove `opaque` from a private type.
8936///
8937pub struct RemovePrivateOpaque<'a> {
8938 module: &'a Module,
8939 params: &'a CodeActionParams,
8940 edits: TextEdits<'a>,
8941 opaque_span: Option<SrcSpan>,
8942}
8943
8944impl<'a> RemovePrivateOpaque<'a> {
8945 pub fn new(
8946 module: &'a Module,
8947 line_numbers: &'a LineNumbers,
8948 params: &'a CodeActionParams,
8949 ) -> Self {
8950 Self {
8951 module,
8952 params,
8953 edits: TextEdits::new(line_numbers),
8954 opaque_span: None,
8955 }
8956 }
8957
8958 pub fn code_actions(mut self) -> Vec<CodeAction> {
8959 self.visit_typed_module(&self.module.ast);
8960
8961 let Some(opaque_span) = self.opaque_span else {
8962 return vec![];
8963 };
8964
8965 self.edits.delete(opaque_span);
8966
8967 let mut action = Vec::with_capacity(1);
8968 CodeActionBuilder::new("Remove opaque from private type")
8969 .kind(CodeActionKind::QUICKFIX)
8970 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8971 .preferred(true)
8972 .push_to(&mut action);
8973 action
8974 }
8975}
8976
8977impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> {
8978 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
8979 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location);
8980 if !within(self.params.range, custom_type_range) {
8981 return;
8982 }
8983
8984 if custom_type.opaque && custom_type.publicity.is_private() {
8985 self.opaque_span = Some(SrcSpan {
8986 start: custom_type.location.start,
8987 end: custom_type.location.start + 7,
8988 })
8989 }
8990 }
8991}
8992
8993/// Code action to rewrite a case expression as part of an outer case expression
8994/// branch. For example:
8995///
8996/// ```gleam
8997/// case wibble {
8998/// Ok(a) -> case a {
8999/// 1 -> todo
9000/// _ -> todo
9001/// }
9002/// Error(_) -> todo
9003/// }
9004/// ```
9005///
9006/// Would become:
9007///
9008/// ```gleam
9009/// case wibble {
9010/// Ok(1) -> todo
9011/// Ok(_) -> todo
9012/// Error(_) -> todo
9013/// }
9014/// ```
9015///
9016pub struct CollapseNestedCase<'a> {
9017 module: &'a Module,
9018 params: &'a CodeActionParams,
9019 edits: TextEdits<'a>,
9020 collapsed: Option<Collapsed<'a>>,
9021}
9022
9023/// This holds all the needed data about the pattern to collapse.
9024/// We'll use this piece of code as an example:
9025/// ```gleam
9026/// case something {
9027/// User(username: _, NotAdmin) -> "Stranger!!"
9028/// User(username:, Admin) if wibble ->
9029/// case username { // <- We're collapsing this nested case
9030/// "Joe" -> "Hello, Joe!"
9031/// _ -> "I don't know you, " <> username
9032/// }
9033/// }
9034/// ```
9035///
9036struct Collapsed<'a> {
9037 /// This is the span covering the entire clause being collapsed:
9038 ///
9039 /// ```gleam
9040 /// case something {
9041 /// User(username: _, NotAdmin) -> "Stranger!!"
9042 /// User(username:, Admin) if wibble ->
9043 /// ┬ It goes all the way from here...
9044 /// ╭─╯
9045 /// │ case username {
9046 /// │ "Joe" -> "Hello, Joe!"
9047 /// │ _ -> "I don't know you, " <> username
9048 /// │ }
9049 /// │ ┬ ...to here!
9050 /// ╰───╯
9051 /// }
9052 /// ```
9053 ///
9054 outer_clause_span: SrcSpan,
9055
9056 /// The (optional) guard of the outer branch. In this exmaple it's this one:
9057 ///
9058 /// ```gleam
9059 /// case something {
9060 /// User(username: _, NotAdmin) -> "Stranger!!"
9061 /// User(username:, Admin) if wibble ->
9062 /// ┬────────
9063 /// ╰─ `outer_guard`
9064 /// case username {
9065 /// "Joe" -> "Hello, Joe!"
9066 /// _ -> "I don't know you, " <> username
9067 /// }
9068 /// }
9069 /// ```
9070 ///
9071 outer_guard: &'a Option<TypedClauseGuard>,
9072
9073 /// The pattern variable being matched on:
9074 ///
9075 /// ```gleam
9076 /// case something {
9077 /// User(username: _, NotAdmin) -> "Stranger!!"
9078 /// User(username:, Admin) if wibble ->
9079 /// ┬───────
9080 /// ╰─ `matched_variable`
9081 /// case username {
9082 /// "Joe" -> "Hello, Joe!"
9083 /// _ -> "I don't know you, " <> username
9084 /// }
9085 /// }
9086 /// ```
9087 ///
9088 matched_variable: BoundVariable,
9089
9090 /// The span covering the entire pattern that is bringing the matched
9091 /// variable in scope:
9092 ///
9093 /// ```gleam
9094 /// case something {
9095 /// User(username: _, NotAdmin) -> "Stranger!!"
9096 /// User(username:, Admin) if wibble ->
9097 /// ┬─────────────────────
9098 /// ╰─ `matched_pattern_span`
9099 /// case username {
9100 /// "Joe" -> "Hello, Joe!"
9101 /// _ -> "I don't know you, " <> username
9102 /// }
9103 /// }
9104 /// ```
9105 ///
9106 matched_pattern_span: SrcSpan,
9107
9108 /// The clauses matching on the `username` variable. In this case they are:
9109 /// ```gleam
9110 /// "Joe" -> "Hello, Joe!"
9111 /// _ -> "I don't know you, " <> username
9112 /// ```
9113 ///
9114 inner_clauses: &'a Vec<ast::TypedClause>,
9115}
9116
9117impl<'a> CollapseNestedCase<'a> {
9118 pub fn new(
9119 module: &'a Module,
9120 line_numbers: &'a LineNumbers,
9121 params: &'a CodeActionParams,
9122 ) -> Self {
9123 Self {
9124 module,
9125 params,
9126 edits: TextEdits::new(line_numbers),
9127 collapsed: None,
9128 }
9129 }
9130
9131 pub fn code_actions(mut self) -> Vec<CodeAction> {
9132 self.visit_typed_module(&self.module.ast);
9133
9134 let Some(Collapsed {
9135 outer_clause_span,
9136 outer_guard,
9137 ref matched_variable,
9138 matched_pattern_span,
9139 inner_clauses,
9140 }) = self.collapsed
9141 else {
9142 return vec![];
9143 };
9144
9145 // Now comes the tricky part: we need to replace the current pattern
9146 // that is bringing the variable into scope with many new patterns, one
9147 // for each of the inner clauses.
9148 //
9149 // Each time we will have to replace the matched variable with the
9150 // pattern used in the inner clause. Let's look at an example:
9151 //
9152 // ```gleam
9153 // Ok(a) -> case a {
9154 // 1 -> wibble
9155 // 2 | 3 -> wobble
9156 // _ -> woo
9157 // }
9158 // ```
9159 //
9160 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then
9161 // with `2` and `3`, and finally with `_`. Obtaining something like
9162 // this:
9163 //
9164 // ```gleam
9165 // Ok(1) -> wibble
9166 // Ok(2) | Ok(3) -> wobble
9167 // Ok(_) -> woo
9168 // ```
9169 //
9170 // Notice one key detail: since alternative patterns can't be nested we
9171 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`!
9172
9173 let pattern_text: String = code_at(self.module, matched_pattern_span).into();
9174 let matched_variable_span = matched_variable.location;
9175
9176 let pattern_with_variable = |mut new_content: String| {
9177 let mut new_pattern = pattern_text.clone();
9178
9179 match matched_variable {
9180 BoundVariable {
9181 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. },
9182 ..
9183 } => {
9184 let trimmed_contents = new_content.trim();
9185
9186 let pattern_is_literal_list =
9187 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]");
9188 let pattern_is_discard = trimmed_contents == "_";
9189
9190 let span_to_replace = match (
9191 &matched_variable.name,
9192 // We verify whether the pattern is compatible with the list prefix `..`.
9193 // For example, `..var` is valid syntax, but `..[]` and `.._` are not.
9194 pattern_is_literal_list || pattern_is_discard,
9195 ) {
9196 // We normally replace the selected variable with the pattern.
9197 (BoundVariableName::Regular { .. }, _) => matched_variable_span,
9198
9199 // If the selected pattern is not a list, we also replace it normally.
9200 (BoundVariableName::ListTail { .. }, false) => matched_variable_span,
9201 // If the pattern is a list to also remove the list tail prefix.
9202 (BoundVariableName::ListTail { tail_location, .. }, true) => {
9203 // When it's a list literal, we remove the surrounding brackets.
9204 let len = trimmed_contents.len();
9205 if let Some(slice) = new_content.trim().get(1..(len - 1)) {
9206 new_content = slice.to_string()
9207 };
9208
9209 *tail_location
9210 }
9211
9212 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(),
9213 };
9214
9215 let start_of_pattern =
9216 (span_to_replace.start - matched_pattern_span.start) as usize;
9217 let pattern_length = span_to_replace.len();
9218
9219 let end_of_pattern = start_of_pattern + pattern_length;
9220 let replaced_range = start_of_pattern..end_of_pattern;
9221
9222 new_pattern.replace_range(replaced_range, &new_content);
9223 }
9224
9225 BoundVariable {
9226 name: BoundVariableName::ShorthandLabel { .. },
9227 ..
9228 } => {
9229 // But if it's introduced using the shorthand syntax we can't
9230 // just replace it's location with the new pattern: we would be
9231 // removing the label!!
9232 // So we instead insert the pattern right after the label.
9233 new_pattern.insert_str(
9234 (matched_variable_span.end - matched_pattern_span.start) as usize,
9235 &format!(" {new_content}"),
9236 );
9237 }
9238 }
9239
9240 new_pattern
9241 };
9242
9243 let mut new_clauses = vec![];
9244 for clause in inner_clauses {
9245 // Here we take care of unrolling any alterantive patterns: for each
9246 // of the alternatives we build a new pattern and then join
9247 // everything together with ` | `.
9248
9249 let references_to_matched_variable =
9250 FindVariableReferences::new(matched_variable_span, matched_variable.name())
9251 .find(&clause.then);
9252
9253 let new_patterns = iter::once(&clause.pattern)
9254 .chain(&clause.alternative_patterns)
9255 .map(|patterns| {
9256 // If we've reached this point we've already made in the
9257 // traversal that the inner clause is matching on a single
9258 // subject. So this should be safe to expect!
9259 let pattern_location =
9260 patterns.first().expect("must have a pattern").location();
9261
9262 let mut pattern_code = code_at(self.module, pattern_location).to_string();
9263 if !references_to_matched_variable.is_empty() {
9264 pattern_code = format!("{pattern_code} as {}", matched_variable.name());
9265 };
9266 pattern_with_variable(pattern_code)
9267 })
9268 .join(" | ");
9269
9270 let clause_code = code_at(self.module, clause.then.location());
9271 let guard_code = match (outer_guard, &clause.guard) {
9272 (Some(outer), Some(inner)) => {
9273 let mut outer_code = code_at(self.module, outer.location()).to_string();
9274 let mut inner_code = code_at(self.module, inner.location()).to_string();
9275 if ast::BinOp::And.precedence() > outer.precedence() {
9276 outer_code = format!("{{ {outer_code} }}")
9277 }
9278 if ast::BinOp::And.precedence() > inner.precedence() {
9279 inner_code = format!("{{ {inner_code} }}")
9280 }
9281 format!(" if {outer_code} && {inner_code}")
9282 }
9283 (None, Some(guard)) | (Some(guard), None) => {
9284 format!(" if {}", code_at(self.module, guard.location()))
9285 }
9286 (None, None) => "".into(),
9287 };
9288
9289 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}"));
9290 }
9291
9292 let pattern_nesting = self
9293 .edits
9294 .src_span_to_lsp_range(outer_clause_span)
9295 .start
9296 .character;
9297 let indentation = " ".repeat(pattern_nesting as usize);
9298
9299 self.edits.replace(
9300 outer_clause_span,
9301 new_clauses.join(&format!("\n{indentation}")),
9302 );
9303
9304 let mut action = Vec::with_capacity(1);
9305 CodeActionBuilder::new("Collapse nested case")
9306 .kind(CodeActionKind::REFACTOR_REWRITE)
9307 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9308 .preferred(false)
9309 .push_to(&mut action);
9310 action
9311 }
9312
9313 /// If the clause can be flattened because it's matching on a single variable
9314 /// defined in it, this function will return the info needed by the language
9315 /// server to flatten that case.
9316 ///
9317 /// We can only flatten a case expression in a very specific case:
9318 /// - This pattern may be introducing multiple variables,
9319 /// - The expression following this branch must be a case, and
9320 /// - It must be matching on one of those variables
9321 ///
9322 /// For example:
9323 ///
9324 /// ```gleam
9325 /// Wibble(a, b, 1) -> case a { ... }
9326 /// Wibble(a, b, 1) -> case b { ... }
9327 /// ```
9328 ///
9329 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> {
9330 let ast::TypedClause {
9331 pattern,
9332 alternative_patterns,
9333 then,
9334 location,
9335 guard,
9336 } = clause;
9337
9338 if !alternative_patterns.is_empty() {
9339 return None;
9340 }
9341
9342 // The `then` clause must be a single case expression matching on a
9343 // single variable.
9344 let Some(TypedExpr::Case {
9345 subjects, clauses, ..
9346 }) = single_expression(then)
9347 else {
9348 return None;
9349 };
9350
9351 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else {
9352 return None;
9353 };
9354
9355 // That variable must be one the variables we brought into scope in this
9356 // branch.
9357 let variable = pattern
9358 .iter()
9359 .flat_map(|pattern| pattern.bound_variables())
9360 .find(|variable| variable.name() == *name)?;
9361
9362 // There's one last condition to trigger the code action: we must
9363 // actually be with the cursor over the pattern or the nested case
9364 // expression!
9365 //
9366 // ```gleam
9367 // case wibble {
9368 // Ok(a) -> case a {
9369 // //^^^^^^^^^^^^^^^ Anywhere over here!
9370 // }
9371 // }
9372 // ```
9373 //
9374 let first_pattern = pattern.first().expect("at least one pattern");
9375 let last_pattern = pattern.last().expect("at least one pattern");
9376 let pattern_location = first_pattern.location().merge(&last_pattern.location());
9377
9378 let last_inner_subject = subjects.last().expect("at least one subject");
9379 let trigger_location = pattern_location.merge(&last_inner_subject.location());
9380 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location);
9381
9382 if within(self.params.range, trigger_range) {
9383 Some(Collapsed {
9384 outer_clause_span: *location,
9385 outer_guard: guard,
9386 matched_variable: variable,
9387 matched_pattern_span: pattern_location,
9388 inner_clauses: clauses,
9389 })
9390 } else {
9391 None
9392 }
9393 }
9394}
9395
9396impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> {
9397 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
9398 if let Some(collapsed) = self.flatten_clause(clause) {
9399 self.collapsed = Some(collapsed);
9400
9401 // We're done, there's no need to keep exploring as we know the
9402 // cursor is over this pattern and it can't be over any other one!
9403 return;
9404 };
9405
9406 ast::visit::visit_typed_clause(self, clause);
9407 }
9408}
9409
9410/// If the expression is a single expression, or a block containing a single
9411/// expression, this function will return it.
9412/// But if the expression is a block with multiple statements, an assignment
9413/// of a use, this will return None.
9414///
9415fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
9416 match expression {
9417 // If a block has a single statement, we can flatten it into a
9418 // single expression if that one statement is an expression.
9419 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() {
9420 ast::Statement::Expression(expression) => single_expression(expression),
9421 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
9422 None
9423 }
9424 },
9425
9426 // If a block has multiple statements then it can't be flattened
9427 // into a single expression.
9428 TypedExpr::Block { .. } => None,
9429
9430 TypedExpr::Int { .. }
9431 | TypedExpr::Float { .. }
9432 | TypedExpr::String { .. }
9433 | TypedExpr::Pipeline { .. }
9434 | TypedExpr::Var { .. }
9435 | TypedExpr::Fn { .. }
9436 | TypedExpr::List { .. }
9437 | TypedExpr::Call { .. }
9438 | TypedExpr::BinOp { .. }
9439 | TypedExpr::Case { .. }
9440 | TypedExpr::RecordAccess { .. }
9441 | TypedExpr::PositionalAccess { .. }
9442 | TypedExpr::ModuleSelect { .. }
9443 | TypedExpr::Tuple { .. }
9444 | TypedExpr::TupleIndex { .. }
9445 | TypedExpr::Todo { .. }
9446 | TypedExpr::Panic { .. }
9447 | TypedExpr::Echo { .. }
9448 | TypedExpr::BitArray { .. }
9449 | TypedExpr::RecordUpdate { .. }
9450 | TypedExpr::NegateBool { .. }
9451 | TypedExpr::NegateInt { .. }
9452 | TypedExpr::Invalid { .. } => Some(expression),
9453 }
9454}
9455
9456/// Code action to remove unreachable clauses from a case expression.
9457///
9458pub struct RemoveUnreachableCaseClauses<'a> {
9459 module: &'a Module,
9460 params: &'a CodeActionParams,
9461 edits: TextEdits<'a>,
9462 /// The source location of the patterns of all the unreachable clauses in
9463 /// the current module.
9464 ///
9465 unreachable_clauses: HashSet<SrcSpan>,
9466 clauses_to_delete: Vec<SrcSpan>,
9467}
9468
9469impl<'a> RemoveUnreachableCaseClauses<'a> {
9470 pub fn new(
9471 module: &'a Module,
9472 line_numbers: &'a LineNumbers,
9473 params: &'a CodeActionParams,
9474 ) -> Self {
9475 let unreachable_clauses = module
9476 .ast
9477 .type_info
9478 .warnings
9479 .iter()
9480 .filter_map(|warning| {
9481 if let type_::Warning::UnreachableCasePattern { location, .. } = warning {
9482 Some(*location)
9483 } else {
9484 None
9485 }
9486 })
9487 .collect();
9488
9489 Self {
9490 unreachable_clauses,
9491 module,
9492 params,
9493 edits: TextEdits::new(line_numbers),
9494 clauses_to_delete: vec![],
9495 }
9496 }
9497
9498 pub fn code_actions(mut self) -> Vec<CodeAction> {
9499 self.visit_typed_module(&self.module.ast);
9500 if self.clauses_to_delete.is_empty() {
9501 return vec![];
9502 }
9503
9504 for branch in self.clauses_to_delete {
9505 self.edits.delete(branch);
9506 }
9507
9508 let mut action = Vec::with_capacity(1);
9509 CodeActionBuilder::new("Remove unreachable clauses")
9510 .kind(CodeActionKind::QUICKFIX)
9511 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9512 .preferred(true)
9513 .push_to(&mut action);
9514 action
9515 }
9516}
9517
9518impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> {
9519 fn visit_typed_expr_case(
9520 &mut self,
9521 location: &'ast SrcSpan,
9522 type_: &'ast Arc<Type>,
9523 subjects: &'ast [TypedExpr],
9524 clauses: &'ast [ast::TypedClause],
9525 compiled_case: &'ast CompiledCase,
9526 ) {
9527 // We're showing the code action only if we're within one of the
9528 // unreachable patterns. And the code action is going to remove all the
9529 // unreachable patterns for this case.
9530 let is_hovering_clause = clauses.iter().any(|clause| {
9531 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location());
9532 within(self.params.range, pattern_range)
9533 });
9534 if is_hovering_clause {
9535 self.clauses_to_delete = clauses
9536 .iter()
9537 .filter(|clause| {
9538 self.unreachable_clauses
9539 .contains(&clause.pattern_location())
9540 })
9541 .map(|clause| clause.location())
9542 .collect_vec();
9543 return;
9544 }
9545
9546 // If we're not hovering any of the clauses then we want to
9547 // keep visiting the case expression as the unreachable branch might be
9548 // in one of the nested cases.
9549 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
9550 }
9551}
9552
9553/// Code action to add labels to a constructor/call where all the labels where
9554/// omitted.
9555///
9556pub struct AddOmittedLabels<'a> {
9557 module: &'a Module,
9558 params: &'a CodeActionParams,
9559 edits: TextEdits<'a>,
9560 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>,
9561}
9562
9563struct CallArgumentWithOmittedLabel {
9564 location: SrcSpan,
9565
9566 /// If the argument has a label this will be the label we can use for it.
9567 ///
9568 omitted_label: Option<EcoString>,
9569
9570 /// If the argument is a variable that has the same name as the omitted label
9571 /// and could use the shorthand syntax.
9572 ///
9573 can_use_shorthand_syntax: bool,
9574}
9575
9576impl<'a> AddOmittedLabels<'a> {
9577 pub fn new(
9578 module: &'a Module,
9579 line_numbers: &'a LineNumbers,
9580 params: &'a CodeActionParams,
9581 ) -> Self {
9582 Self {
9583 module,
9584 params,
9585 edits: TextEdits::new(line_numbers),
9586 arguments_and_omitted_labels: None,
9587 }
9588 }
9589
9590 pub fn code_actions(mut self) -> Vec<CodeAction> {
9591 self.visit_typed_module(&self.module.ast);
9592
9593 let Some(call_arguments) = self.arguments_and_omitted_labels else {
9594 return vec![];
9595 };
9596
9597 for call_argument in call_arguments {
9598 let Some(label) = call_argument.omitted_label else {
9599 continue;
9600 };
9601 if call_argument.can_use_shorthand_syntax {
9602 self.edits.insert(call_argument.location.end, ":".into());
9603 } else {
9604 self.edits
9605 .insert(call_argument.location.start, format!("{label}: "))
9606 }
9607 }
9608
9609 let mut action = Vec::with_capacity(1);
9610 CodeActionBuilder::new("Add omitted labels")
9611 .kind(CodeActionKind::REFACTOR_REWRITE)
9612 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9613 .preferred(false)
9614 .push_to(&mut action);
9615 action
9616 }
9617}
9618
9619impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
9620 fn visit_typed_expr_call(
9621 &mut self,
9622 location: &'ast SrcSpan,
9623 type_: &'ast Arc<Type>,
9624 fun: &'ast TypedExpr,
9625 arguments: &'ast [TypedCallArg],
9626 ) {
9627 let called_function_range = self.edits.src_span_to_lsp_range(fun.location());
9628 if !within(self.params.range, called_function_range) {
9629 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
9630 return;
9631 }
9632
9633 let Some(field_map) = fun.field_map() else {
9634 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
9635 return;
9636 };
9637 let argument_index_to_label = field_map.indices_to_labels();
9638
9639 let mut omitted_labels = Vec::with_capacity(arguments.len());
9640 for (index, argument) in arguments.iter().enumerate() {
9641 // If the argument already has a label we don't want to add a label
9642 // for it, so we skip it.
9643 if let Some(label) = &argument.label {
9644 // Though, before skipping, we want to make sure that the label
9645 // is actually right for the function call. If it's not then we
9646 // give up on adding labels because there wouldn't be no way of
9647 // knowing which label to add.
9648 if !field_map.fields.contains_key(label) {
9649 return;
9650 } else {
9651 continue;
9652 }
9653 }
9654 // No labels for pipes, uses, etc!
9655 if argument.is_implicit() {
9656 continue;
9657 }
9658
9659 let label = argument_index_to_label
9660 .get(&(index as u32))
9661 .cloned()
9662 .cloned();
9663
9664 let can_use_shorthand_syntax = match (&label, &argument.value) {
9665 (Some(label), TypedExpr::Var { name, .. }) => name == label,
9666 (Some(_) | None, _) => false,
9667 };
9668
9669 omitted_labels.push(CallArgumentWithOmittedLabel {
9670 location: argument.location,
9671 omitted_label: label,
9672 can_use_shorthand_syntax,
9673 })
9674 }
9675 self.arguments_and_omitted_labels = Some(omitted_labels);
9676 }
9677}
9678
9679/// Code action to extract selected code into a separate function.
9680/// If a user selected a portion of code in a function, we offer a code action
9681/// to extract it into a new one. This can either be a single expression, such
9682/// as in the following example:
9683///
9684/// ```gleam
9685/// pub fn main() {
9686/// let value = {
9687/// // ^ User selects from here
9688/// ...
9689/// }
9690/// //^ Until here
9691/// }
9692/// ```
9693///
9694/// Here, we would extract the selected block expression. It could also be a
9695/// series of statements. For example:
9696///
9697/// ```gleam
9698/// pub fn main() {
9699/// let a = 1
9700/// //^ User selects from here
9701/// let b = 2
9702/// let c = a + b
9703/// // ^ Until here
9704///
9705/// do_more_things(c)
9706/// }
9707/// ```
9708///
9709/// Here, we want to extract the statements inside the user's selection.
9710///
9711pub struct ExtractFunction<'a> {
9712 module: &'a Module,
9713 params: &'a CodeActionParams,
9714 edits: TextEdits<'a>,
9715 function: Option<ExtractedFunction<'a>>,
9716 function_end_position: Option<u32>,
9717 /// Since the `visit_typed_statement` visitor function doesn't tell us when
9718 /// a statement is the last in a block or function, we need to track that
9719 /// manually.
9720 last_statement_location: Option<SrcSpan>,
9721 /// When visiting a pipeline step, this will hold the type of the value
9722 /// returned by the previous step (if any!)
9723 previous_pipeline_assignment_type: Option<Arc<Type>>,
9724}
9725
9726/// Information about a section of code we are extracting as a function.
9727#[derive(Debug)]
9728struct ExtractedFunction<'a> {
9729 /// A list of parameters which need to be passed to the extracted function.
9730 /// These are any variables used in the extracted code, which are defined
9731 /// outside of the extracted code.
9732 parameters: Vec<(EcoString, Arc<Type>)>,
9733 /// A list of values which need to be returned from the extracted function.
9734 /// These are the variables defined in the extracted code which are used
9735 /// outside of the extracted section.
9736 returned_variables: Vec<(EcoString, Arc<Type>)>,
9737 /// The piece of code to be extracted. This is either a single expression or
9738 /// a list of statements, as explained in the documentation of `ExtractFunction`
9739 value: ExtractedValue<'a>,
9740}
9741
9742impl<'a> ExtractedFunction<'a> {
9743 fn new(value: ExtractedValue<'a>) -> Self {
9744 Self {
9745 value,
9746 parameters: Vec::new(),
9747 returned_variables: Vec::new(),
9748 }
9749 }
9750
9751 fn location(&self) -> SrcSpan {
9752 match &self.value {
9753 ExtractedValue::Expression(expression) => expression.location(),
9754 ExtractedValue::Statements { location, .. }
9755 | ExtractedValue::PipelineSteps { location, .. } => *location,
9756 }
9757 }
9758
9759 /// If the extracted function is a series of pipeline steps, this adds to it
9760 /// the given pipeline step, otherwise leaving it unchanged.
9761 /// If the extracted function was indeed a pipeline, this will return `true`,
9762 /// otherwise it returns `false`.
9763 ///
9764 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) {
9765 if let ExtractedFunction {
9766 value:
9767 ExtractedValue::PipelineSteps {
9768 location,
9769 before_first: _,
9770 return_type,
9771 },
9772 ..
9773 } = self
9774 {
9775 // If we're extracting this pipeline and the final step is included
9776 // in the selection we want to add it to the extracted steps
9777 *return_type = step_type;
9778 *location = location.merge(&step_location);
9779 }
9780 }
9781}
9782
9783#[derive(Debug)]
9784enum ExtractedValue<'a> {
9785 Expression(&'a TypedExpr),
9786 Statements {
9787 location: SrcSpan,
9788 position: StatementPosition,
9789 },
9790 PipelineSteps {
9791 location: SrcSpan,
9792 /// The type of the value produced by the pipeline steps that will be
9793 /// piped into the extracted function. Could be none if the steps we're
9794 /// extracting include the first step, in that case there would be
9795 /// nothing that is fed into it.
9796 before_first: Option<Arc<Type>>,
9797 /// The type returned by the extracted steps.
9798 return_type: Arc<Type>,
9799 },
9800}
9801
9802impl ExtractedValue<'_> {
9803 fn location(&self) -> SrcSpan {
9804 match self {
9805 ExtractedValue::Expression(typed_expr) => typed_expr.location(),
9806 ExtractedValue::Statements { location, .. }
9807 | ExtractedValue::PipelineSteps { location, .. } => *location,
9808 }
9809 }
9810}
9811
9812/// When we are extracting multiple statements, there are two possible cases:
9813/// The first is if we are extracting statements in the middle of a function.
9814/// In this case, we will need to return some number of arguments, or `Nil`.
9815/// For example:
9816///
9817/// ```gleam
9818/// pub fn main() {
9819/// let message = "Hello!"
9820/// let log_message = "[INFO] " <> message
9821/// //^ Select from here
9822/// io.println(log_message)
9823/// // ^ Until here
9824///
9825/// do_some_more_things()
9826/// }
9827/// ```
9828///
9829/// Here, the extracted function doesn't bind any variables which we need
9830/// afterwards, it purely performs side effects. In this case we can just return
9831/// `Nil` from the new function.
9832///
9833/// However, consider the following:
9834///
9835/// ```gleam
9836/// pub fn main() {
9837/// let a = 1
9838/// let b = 2
9839/// //^ Select from here
9840/// a + b
9841/// // ^ Until here
9842/// }
9843/// ```
9844///
9845/// Here, despite us not needing any variables from the extracted code, there
9846/// is one key difference: the `a + b` expression is at the end of the function,
9847/// and so its value is returned from the entire function. This is known as the
9848/// "tail" position. In that case, we can't return `Nil` as that would make the
9849/// `main` function return `Nil` instead of the result of the addition. If we
9850/// extract the tail-position statement, we need to return that last value rather
9851/// than `Nil`.
9852///
9853#[derive(Debug)]
9854enum StatementPosition {
9855 Tail { type_: Arc<Type> },
9856 NotTail,
9857}
9858
9859impl<'a> ExtractFunction<'a> {
9860 pub fn new(
9861 module: &'a Module,
9862 line_numbers: &'a LineNumbers,
9863 params: &'a CodeActionParams,
9864 ) -> Self {
9865 Self {
9866 module,
9867 params,
9868 edits: TextEdits::new(line_numbers),
9869 function: None,
9870 function_end_position: None,
9871 last_statement_location: None,
9872 previous_pipeline_assignment_type: None,
9873 }
9874 }
9875
9876 pub fn code_actions(mut self) -> Vec<CodeAction> {
9877 // If no code is selected, then there is no function to extract and we
9878 // can return no code actions.
9879 if self.params.range.start == self.params.range.end {
9880 return Vec::new();
9881 }
9882
9883 self.visit_typed_module(&self.module.ast);
9884
9885 let Some(end) = self.function_end_position else {
9886 return Vec::new();
9887 };
9888
9889 // If nothing was found in the selected range, there is no code action.
9890 let Some(extracted) = self.function.take() else {
9891 return Vec::new();
9892 };
9893
9894 match extracted.value {
9895 // If we extract a block, it isn't very helpful to have the body of
9896 // the extracted function just be a single block expression, so
9897 // instead we extract the statements inside the block. For example,
9898 // the following code:
9899 //
9900 // ```gleam
9901 // pub fn main() {
9902 // let x = {
9903 // // ^ Select from here
9904 // let a = 1
9905 // let b = 2
9906 // a + b
9907 // }
9908 // //^ Until here
9909 // x
9910 // }
9911 // ```
9912 //
9913 // Would produce the following extracted function:
9914 //
9915 // ```gleam
9916 // fn function() {
9917 // let a = 1
9918 // let b = 2
9919 // a + b
9920 // }
9921 // ```
9922 //
9923 // Rather than:
9924 //
9925 // ```gleam
9926 // fn function() {
9927 // {
9928 // let a = 1
9929 // let b = 2
9930 // a + b
9931 // }
9932 // }
9933 // ```
9934 //
9935 ExtractedValue::Expression(TypedExpr::Block {
9936 statements,
9937 location: full_location,
9938 }) => {
9939 let location = statements
9940 .first()
9941 .location()
9942 .merge(&statements.last().location());
9943
9944 self.extract_code_in_tail_position(
9945 *full_location,
9946 location,
9947 statements.last().type_(),
9948 extracted.parameters,
9949 end,
9950 )
9951 }
9952 ExtractedValue::Expression(TypedExpr::Fn {
9953 type_,
9954 location: full_location,
9955 kind: FunctionLiteralKind::Anonymous { .. },
9956 arguments,
9957 body,
9958 ..
9959 }) => {
9960 let location = body.first().location().merge(&body.last().location());
9961 let return_type = type_.return_type().expect("Fn should have a return type");
9962
9963 if extracted.parameters.is_empty() {
9964 self.extract_anonymous_function(
9965 *full_location,
9966 location,
9967 arguments,
9968 return_type,
9969 end,
9970 )
9971 } else if arguments.len() == 1 {
9972 self.extract_anonymous_function_with_capture_hole(
9973 *full_location,
9974 location,
9975 arguments.first().expect("There is exactly one argument"),
9976 extracted.parameters,
9977 return_type,
9978 end,
9979 )
9980 } else {
9981 self.extract_anonymous_function_body(
9982 location,
9983 arguments,
9984 extracted.parameters,
9985 return_type,
9986 end,
9987 )
9988 }
9989 }
9990 ExtractedValue::Expression(expression) => {
9991 let expression_type = if let TypedExpr::Fn {
9992 type_,
9993 kind: FunctionLiteralKind::Use { .. },
9994 ..
9995 } = expression
9996 {
9997 type_.fn_types().expect("use callback to be a function").1
9998 } else {
9999 expression.type_()
10000 };
10001 self.extract_code_in_tail_position(
10002 expression.location(),
10003 expression.location(),
10004 expression_type,
10005 extracted.parameters,
10006 end,
10007 )
10008 }
10009 ExtractedValue::Statements {
10010 location,
10011 position: StatementPosition::NotTail,
10012 } => self.extract_statements(
10013 location,
10014 extracted.parameters,
10015 extracted.returned_variables,
10016 end,
10017 ),
10018 ExtractedValue::Statements {
10019 location,
10020 position: StatementPosition::Tail { type_ },
10021 } => self.extract_code_in_tail_position(
10022 location,
10023 location,
10024 type_,
10025 extracted.parameters,
10026 end,
10027 ),
10028 ExtractedValue::PipelineSteps {
10029 location,
10030 before_first,
10031 return_type,
10032 } => self.extract_pipeline_steps(
10033 location,
10034 end,
10035 extracted.parameters,
10036 before_first,
10037 return_type,
10038 ),
10039 }
10040
10041 let mut action = Vec::with_capacity(1);
10042 CodeActionBuilder::new("Extract function")
10043 .kind(CodeActionKind::REFACTOR_EXTRACT)
10044 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10045 .preferred(false)
10046 .push_to(&mut action);
10047 action
10048 }
10049
10050 /// Choose a suitable name for an extracted function to make sure it doesn't
10051 /// clash with existing functions defined in the module and cause an error.
10052 fn function_name(&self) -> EcoString {
10053 if !self.module.ast.type_info.values.contains_key("function") {
10054 return "function".into();
10055 }
10056
10057 let mut number = 2;
10058 loop {
10059 let name = eco_format!("function_{number}");
10060 if !self.module.ast.type_info.values.contains_key(&name) {
10061 return name;
10062 }
10063 number += 1;
10064 }
10065 }
10066
10067 /// For anonymous functions that do not capture any variables from an outer scope.
10068 /// Moves the function so it is defined at the module top-level instead,
10069 /// replacing the original literal with a reference to the new function.
10070 fn extract_anonymous_function(
10071 &mut self,
10072 location: SrcSpan,
10073 code_location: SrcSpan,
10074 arguments: &[TypedArg],
10075 return_type: Arc<Type>,
10076 function_end: u32,
10077 ) {
10078 // --- BEFORE
10079 // ```gleam
10080 // pub fn main() {
10081 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) })
10082 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
10083 // }
10084 // ```
10085 //
10086 // --- AFTER
10087 // ```gleam
10088 // pub fn main() {
10089 // list.each([1, 2, 3], function)
10090 // }
10091 //
10092 // fn function(x: Int) -> Nil {
10093 // io.println(int.to_string(x))
10094 // }
10095 // ```
10096
10097 let name = self.function_name();
10098 self.edits.replace(location, name.to_string());
10099
10100 let mut printer = Printer::new(&self.module.ast.names);
10101
10102 let return_type = printer.print_type(&return_type);
10103 let function_body = code_at(self.module, code_location);
10104 let mut name_generator = NameGenerator::new();
10105 let arguments = arguments
10106 .iter()
10107 .map(|arg| {
10108 if let Some(name) = arg.get_variable_name() {
10109 eco_format!("{name}: {}", printer.print_type(&arg.type_))
10110 } else {
10111 let name = name_generator.generate_name_from_type(&arg.type_);
10112 eco_format!("_{name}: {}", printer.print_type(&arg.type_))
10113 }
10114 })
10115 .join(", ");
10116
10117 let function = format!(
10118 "\n\nfn {name}({arguments}) -> {return_type} {{
10119 {function_body}
10120}}"
10121 );
10122 self.edits.insert(function_end, function);
10123 }
10124
10125 /// For anonymous functions that capture variables from an external scope
10126 /// but only expect a single argument.
10127 /// Uses function caputre syntax to provide a more concise refactoring than
10128 /// `extract_anonymous_function_body`.
10129 fn extract_anonymous_function_with_capture_hole(
10130 &mut self,
10131 location: SrcSpan,
10132 code_location: SrcSpan,
10133 argument: &TypedArg,
10134 extra_parameters: Vec<(EcoString, Arc<Type>)>,
10135 return_type: Arc<Type>,
10136 function_end: u32,
10137 ) {
10138 let name = self.function_name();
10139
10140 // --- BEFORE
10141 // ```gleam
10142 // pub fn main() {
10143 // let needle = 42
10144 // let haystack = [25, 81, 74, 42, 33]
10145 // list.filter(haystack, fn(x) { x == needle })
10146 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
10147 // }
10148 // ```
10149 //
10150 // --- AFTER
10151 // ```gleam
10152 // pub fn main() {
10153 // let needle = 42
10154 // let haystack = [25, 81, 74, 42, 33]
10155 // list.filter(haystack, function(_, needle))
10156 // }
10157 //
10158 // fn function(x: Int, needle: Int) -> Bool {
10159 // x == needle
10160 // }
10161 // ```
10162
10163 let call = format!(
10164 "{name}(_, {})",
10165 extra_parameters.iter().map(|(name, _)| name).join(", ")
10166 );
10167 self.edits.replace(location, call);
10168
10169 let mut printer = Printer::new(&self.module.ast.names);
10170
10171 // build up the code for the newly generated function
10172 let return_type = printer.print_type(&return_type);
10173 let function_body = code_at(self.module, code_location);
10174 let argument = if let Some(name) = argument.get_variable_name() {
10175 eco_format!("{name}: {}", printer.print_type(&argument.type_))
10176 } else {
10177 let name = NameGenerator::new().generate_name_from_type(&argument.type_);
10178 eco_format!("_{name}: {}", printer.print_type(&argument.type_))
10179 };
10180 let extra_parameters = extra_parameters
10181 .iter()
10182 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
10183 .join(", ");
10184
10185 let function = format!(
10186 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{
10187 {function_body}
10188}}"
10189 );
10190 self.edits.insert(function_end, function);
10191 }
10192
10193 /// For non-unary anonymous functions that capture variables from an external scope.
10194 /// Replaces just the _function body_ with a call to the newly generated function.
10195 fn extract_anonymous_function_body(
10196 &mut self,
10197 location: SrcSpan,
10198 arguments: &[TypedArg],
10199 extra_parameters: Vec<(EcoString, Arc<Type>)>,
10200 return_type: Arc<Type>,
10201 function_end: u32,
10202 ) {
10203 let name = self.function_name();
10204 // --- BEFORE
10205 // ```gleam
10206 // pub fn main() {
10207 // let factor = 2
10208 // list.fold([], 0, fn(acc, value) { acc + value * factor })
10209 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
10210 // }
10211 // ```
10212 //
10213 // --- AFTER
10214 // ```gleam
10215 // pub fn main() {
10216 // let factor = 2
10217 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) })
10218 // }
10219 //
10220 // fn function(acc: Int, value: Int, factor: Int) -> Int {
10221 // acc + value * factor
10222 // }
10223 // ```
10224
10225 // if the programmer has ignored an argument, the generated function
10226 // cannot take it as an parameter
10227 let arguments = arguments
10228 .iter()
10229 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_)))
10230 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_)))
10231 .collect::<Vec<(&EcoString, &Arc<Type>)>>();
10232
10233 let call = format!(
10234 "{name}({})",
10235 arguments.iter().map(|(name, _)| name).join(", ")
10236 );
10237 self.edits.replace(location, call);
10238
10239 let mut printer = Printer::new(&self.module.ast.names);
10240
10241 let return_type = printer.print_type(&return_type);
10242 let function_body = code_at(self.module, location);
10243 let arguments = arguments
10244 .iter()
10245 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
10246 .join(", ");
10247
10248 let function = format!(
10249 "\n\nfn {name}({arguments}) -> {return_type} {{
10250 {function_body}
10251}}"
10252 );
10253 self.edits.insert(function_end, function);
10254 }
10255
10256 /// Extracts code from the end of a function or block. This could either be
10257 /// a single expression, or multiple statements followed by a final expression.
10258 fn extract_code_in_tail_position(
10259 &mut self,
10260 location: SrcSpan,
10261 code_location: SrcSpan,
10262 type_: Arc<Type>,
10263 parameters: Vec<(EcoString, Arc<Type>)>,
10264 function_end: u32,
10265 ) {
10266 let expression_code = code_at(self.module, code_location);
10267
10268 let name = self.function_name();
10269 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
10270 let call = format!("{name}({arguments})");
10271
10272 // Since we are only extracting a single expression, we can just replace
10273 // it with the call and preserve all other semantics; only one value can
10274 // be returned from the expression, unlike when extracting multiple
10275 // statements.
10276 self.edits.replace(location, call);
10277
10278 let mut printer = Printer::new(&self.module.ast.names);
10279
10280 let parameters = parameters
10281 .iter()
10282 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
10283 .join(", ");
10284 let return_type = printer.print_type(&type_);
10285
10286 let function = format!(
10287 "\n\nfn {name}({parameters}) -> {return_type} {{
10288 {expression_code}
10289}}"
10290 );
10291
10292 self.edits.insert(function_end, function);
10293 }
10294
10295 fn extract_statements(
10296 &mut self,
10297 location: SrcSpan,
10298 parameters: Vec<(EcoString, Arc<Type>)>,
10299 returned_variables: Vec<(EcoString, Arc<Type>)>,
10300 function_end: u32,
10301 ) {
10302 let code = code_at(self.module, location);
10303
10304 let returns_anything = !returned_variables.is_empty();
10305
10306 // Here, we decide what value to return from the function. There are
10307 // three cases:
10308 // The first is when the extracted code is purely for side-effects, and
10309 // does not produce any values which are needed outside of the extracted
10310 // code. For example:
10311 //
10312 // ```gleam
10313 // pub fn main() {
10314 // let message = "Something important"
10315 // //^ Select from here
10316 // io.println("Something important")
10317 // io.println("Something else which is repeated")
10318 // // ^ Until here
10319 //
10320 // do_final_thing()
10321 // }
10322 // ```
10323 //
10324 // It doesn't make sense to return any values from this function, since
10325 // no values from the extract code are used afterwards, so we simply
10326 // return `Nil`.
10327 //
10328 // The next is when we need just a single value defined in the extracted
10329 // function, such as in this piece of code:
10330 //
10331 // ```gleam
10332 // pub fn main() {
10333 // let a = 10
10334 // //^ Select from here
10335 // let b = 20
10336 // let c = a + b
10337 // // ^ Until here
10338 //
10339 // echo c
10340 // }
10341 // ```
10342 //
10343 // Here, we can just return the single value, `c`.
10344 //
10345 // The last situation is when we need multiple defined values, such as
10346 // in the following code:
10347 //
10348 // ```gleam
10349 // pub fn main() {
10350 // let a = 10
10351 // //^ Select from here
10352 // let b = 20
10353 // let c = a + b
10354 // // ^ Until here
10355 //
10356 // echo a
10357 // echo b
10358 // echo c
10359 // }
10360 // ```
10361 //
10362 // In this case, we must return a tuple containing `a`, `b` and `c` in
10363 // order for the calling function to have access to the correct values.
10364 let (return_type, return_value) = match returned_variables.as_slice() {
10365 [] => (type_::nil(), "Nil".into()),
10366 [(name, type_)] => (type_.clone(), name.clone()),
10367 _ => {
10368 let values = returned_variables.iter().map(|(name, _)| name).join(", ");
10369 let type_ = type_::tuple(
10370 returned_variables
10371 .into_iter()
10372 .map(|(_, type_)| type_)
10373 .collect(),
10374 );
10375
10376 (type_, eco_format!("#({values})"))
10377 }
10378 };
10379
10380 let name = self.function_name();
10381 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
10382
10383 // If any values are returned from the extracted function, we need to
10384 // bind them so that they are accessible in the current scope.
10385 let call = if returns_anything {
10386 format!("let {return_value} = {name}({arguments})")
10387 } else {
10388 format!("{name}({arguments})")
10389 };
10390 self.edits.replace(location, call);
10391
10392 let mut printer = Printer::new(&self.module.ast.names);
10393
10394 let parameters = parameters
10395 .iter()
10396 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
10397 .join(", ");
10398
10399 let return_type = printer.print_type(&return_type);
10400
10401 let function = format!(
10402 "\n\nfn {name}({parameters}) -> {return_type} {{
10403 {code}
10404 {return_value}
10405}}"
10406 );
10407
10408 self.edits.insert(function_end, function);
10409 }
10410
10411 fn extract_pipeline_steps(
10412 &mut self,
10413 location: SrcSpan,
10414 function_end: u32,
10415 parameters: Vec<(EcoString, Arc<Type>)>,
10416 before_first: Option<Arc<Type>>,
10417 return_type: Arc<Type>,
10418 ) {
10419 let name = self.function_name();
10420 let code = code_at(self.module, location);
10421 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", ");
10422 let replacement = match before_first {
10423 Some(_) if parameters.is_empty() => format!("{name}"),
10424 Some(_) | None => format!("{name}({arguments})"),
10425 };
10426 self.edits.replace(location, replacement);
10427
10428 // When extracting something out of the middle of a pipeline the
10429 // function we produce will produce a single value as output but could
10430 // take multiple values as input:
10431 //
10432 // ```gleam
10433 // wibble
10434 // |> wobble(a) // extracting this
10435 // |> woo(b) //
10436 // |> something
10437 // ```
10438 //
10439 // It will take the type returned by `wibble`, `a`, and `b` as input,
10440 // and produce the value returned by `woo` as output:
10441 //
10442 // ```gleam
10443 // wibble
10444 // |> function(a, b)
10445 // |> something
10446 // ```
10447 //
10448 // If the steps extracted are at the beginning of the pipeline, then it
10449 // won't take that additional argument!
10450 //
10451 // ```gleam
10452 // wibble // extracting these
10453 // |> wobble(a) //
10454 // |> woo(b) //
10455 // |> something
10456 // ```
10457 //
10458 // Becomes:
10459 //
10460 // ```gleam
10461 // function(a, b)
10462 // |> something
10463 // ```
10464
10465 let mut type_printer = Printer::new(&self.module.ast.names);
10466 let return_type = type_printer.print_type(&return_type);
10467 let first_argument = before_first.map(|type_of_first_argument| {
10468 let mut generator = NameGenerator::new();
10469 for (name, _) in parameters.iter() {
10470 generator.add_used_name(name.clone());
10471 }
10472 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument);
10473 (first_argument_name, type_of_first_argument.clone())
10474 });
10475 let parameters = first_argument
10476 .clone()
10477 .into_iter()
10478 .chain(parameters)
10479 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_)))
10480 .join(", ");
10481
10482 let code = if let Some((first_argument_name, _)) = first_argument {
10483 format!("{first_argument_name}\n |> {code}")
10484 } else {
10485 format!("{}", code.trim_start_matches("|>"))
10486 };
10487 let function = format!(
10488 "\n\nfn {name}({parameters}) -> {return_type} {{
10489 {code}
10490}}"
10491 );
10492
10493 self.edits.insert(function_end, function);
10494 }
10495
10496 /// When a variable is referenced, we need to decide if we need to do anything
10497 /// to ensure that the reference is still valid after extracting a function.
10498 /// If the variable is defined outside the extracted function, but used inside
10499 /// it, then we need to add it as a parameter of the function. Similarly, if
10500 /// a variable is defined inside the extracted code, but used outside of it,
10501 /// we need to ensure that value is returned from the function so that it is
10502 /// accessible.
10503 fn register_referenced_variable(
10504 &mut self,
10505 name: &EcoString,
10506 type_: &Arc<Type>,
10507 location: SrcSpan,
10508 definition_location: SrcSpan,
10509 ) {
10510 let Some(extracted) = &mut self.function else {
10511 return;
10512 };
10513
10514 let extracted_location = extracted.location();
10515
10516 // If a variable defined outside the extracted code is referenced inside
10517 // it, we need to add it to the list of parameters.
10518 let variables = if extracted_location.contains_span(location)
10519 && !extracted_location.contains_span(definition_location)
10520 {
10521 &mut extracted.parameters
10522 // If a variable defined inside the extracted code is referenced outside
10523 // it, then we need to ensure that it is returned from the function.
10524 } else if extracted_location.contains_span(definition_location)
10525 && !extracted_location.contains_span(location)
10526 {
10527 &mut extracted.returned_variables
10528 } else {
10529 return;
10530 };
10531
10532 // If the variable has already been tracked, no need to register it again.
10533 // We use a `Vec` here rather than a `HashMap` because we want to ensure
10534 // the order of arguments is consistent; in this case it will be determined
10535 // by the order the variables are used. This isn't always desired, but it's
10536 // better than random order, and makes it easier to write tests too.
10537 // The cost of iterating the list here is minimal; it is unlikely that
10538 // a given function will ever have more than 10 or so parameters.
10539 if variables.iter().any(|(variable, _)| variable == name) {
10540 return;
10541 }
10542
10543 variables.push((name.clone(), type_.clone()));
10544 }
10545
10546 fn can_extract_expression(&self, expression: &TypedExpr) -> bool {
10547 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
10548 let selected_range = self.params.range;
10549
10550 // If the selected range doesn't touch the expression at all, then there
10551 // is no reason to extract it.
10552 if !overlaps(expression_range, selected_range) {
10553 return false;
10554 }
10555
10556 match expression {
10557 TypedExpr::Pipeline {
10558 first_value,
10559 finally,
10560 ..
10561 } => {
10562 // We can extract a pipeline as a whole only if the selection
10563 // spans all of its steps!
10564 let first_step = self.edits.src_span_to_lsp_range(first_value.location);
10565 let last_step = self.edits.src_span_to_lsp_range(finally.location());
10566 position_within(selected_range.start, first_step)
10567 && position_within(selected_range.end, last_step)
10568 }
10569
10570 TypedExpr::Int { .. }
10571 | TypedExpr::Float { .. }
10572 | TypedExpr::String { .. }
10573 | TypedExpr::Block { .. }
10574 | TypedExpr::Var { .. }
10575 | TypedExpr::Fn { .. }
10576 | TypedExpr::List { .. }
10577 | TypedExpr::Call { .. }
10578 | TypedExpr::BinOp { .. }
10579 | TypedExpr::Case { .. }
10580 | TypedExpr::RecordAccess { .. }
10581 | TypedExpr::PositionalAccess { .. }
10582 | TypedExpr::ModuleSelect { .. }
10583 | TypedExpr::Tuple { .. }
10584 | TypedExpr::TupleIndex { .. }
10585 | TypedExpr::Todo { .. }
10586 | TypedExpr::Panic { .. }
10587 | TypedExpr::Echo { .. }
10588 | TypedExpr::BitArray { .. }
10589 | TypedExpr::RecordUpdate { .. }
10590 | TypedExpr::NegateBool { .. }
10591 | TypedExpr::NegateInt { .. }
10592 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range),
10593 }
10594 }
10595
10596 fn can_extract_statement(&self, statement: &TypedStatement) -> bool {
10597 match statement {
10598 ast::Statement::Expression(expression) => self.can_extract_expression(expression),
10599
10600 // Determine whether the selected range falls completely within the
10601 // expression. For example:
10602 // ```gleam
10603 // pub fn main() {
10604 // let something = {
10605 // let a = 1
10606 // let b = 2
10607 // let c = a + b
10608 // //^ The user has selected from here
10609 // let d = a * b
10610 // c / d
10611 // // ^ Until here
10612 // }
10613 // }
10614 // ```
10615 //
10616 // Here, the selected range does overlap with the `let something`
10617 // statement; but we don't want to extract that whole statement! The
10618 // user only wanted to extract the statements inside the block. So if
10619 // the selected range falls completely within the expression, we ignore
10620 // it and traverse the tree further until we find exactly what the user
10621 // selected.
10622 //
10623 // If the selected range is completely within the expression, we don't
10624 // want to extract it.
10625 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
10626 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
10627 let selected_range = self.params.range;
10628
10629 // If the selected range doesn't touch the statement at all, then there
10630 // is no reason to extract it.
10631 if !overlaps(statement_range, selected_range) {
10632 return false;
10633 }
10634 !completely_within(selected_range, statement_range)
10635 }
10636 }
10637 }
10638}
10639
10640impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
10641 fn visit_typed_function(&mut self, function: &'ast TypedFunction) {
10642 let range = self.edits.src_span_to_lsp_range(function.full_location());
10643
10644 if within(self.params.range, range) {
10645 self.function_end_position = Some(function.end_position);
10646 self.last_statement_location = function.body.last().map(|last| last.location());
10647
10648 ast::visit::visit_typed_function(self, function);
10649 }
10650 }
10651
10652 fn visit_typed_expr_block(
10653 &mut self,
10654 location: &'ast SrcSpan,
10655 statements: &'ast [TypedStatement],
10656 ) {
10657 let last_statement_location = self.last_statement_location;
10658 self.last_statement_location = statements.last().map(|last| last.location());
10659
10660 ast::visit::visit_typed_expr_block(self, location, statements);
10661
10662 self.last_statement_location = last_statement_location;
10663 }
10664
10665 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
10666 // If we have already determined what code we want to extract, we don't
10667 // want to extract this instead. This expression would be inside the
10668 // piece of code we already are going to extract, leading to us
10669 // extracting just a single literal in any selection, which is of course
10670 // not desired.
10671 if self.function.is_none() {
10672 // If this expression is fully selected, we mark it as being extracted.
10673 if self.can_extract_expression(expression) {
10674 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
10675 expression,
10676 )));
10677 }
10678 }
10679 ast::visit::visit_typed_expr(self, expression);
10680 }
10681
10682 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
10683 let statement_location = statement.location();
10684
10685 if self.can_extract_statement(statement) {
10686 let is_in_tail_position =
10687 self.last_statement_location
10688 .is_some_and(|last_statement_location| {
10689 last_statement_location == statement_location
10690 });
10691
10692 // A use is always eating up the entire block, if we're extracting it,
10693 // it will be in tail position there and the extracted function should
10694 // return its returned value.
10695 let position = if statement.is_use() || is_in_tail_position {
10696 StatementPosition::Tail {
10697 type_: statement.type_(),
10698 }
10699 } else {
10700 StatementPosition::NotTail
10701 };
10702
10703 match &mut self.function {
10704 None => {
10705 self.function = match statement {
10706 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None,
10707 TypedStatement::Assert(_)
10708 | TypedStatement::Assignment(_)
10709 | TypedStatement::Expression(_)
10710 | TypedStatement::Use(_) => {
10711 Some(ExtractedFunction::new(ExtractedValue::Statements {
10712 location: statement_location,
10713 position,
10714 }))
10715 }
10716 }
10717 }
10718
10719 // If we have already chosen an expression to extract, that means
10720 // that this statement is within the already extracted expression,
10721 // so we don't want to extract this instead.
10722 Some(ExtractedFunction {
10723 value: ExtractedValue::Expression(_),
10724 ..
10725 }) => {}
10726 // If we are selecting multiple statements, this statement should
10727 // be included within that list, so we merge the spans to ensure
10728 // it is included.
10729 Some(ExtractedFunction {
10730 value:
10731 ExtractedValue::Statements {
10732 location,
10733 position: extracted_position,
10734 },
10735 ..
10736 }) => {
10737 *location = location.merge(&statement_location);
10738 *extracted_position = position;
10739 }
10740 Some(ExtractedFunction {
10741 value: value @ ExtractedValue::PipelineSteps { .. },
10742 ..
10743 }) => {
10744 // If we were extracting a pipeline, but end up selecting
10745 // some statement that is not part of it, then we go back to
10746 // selecting a batch of statements.
10747 *value = ExtractedValue::Statements {
10748 location: value.location(),
10749 position,
10750 }
10751 }
10752 }
10753 }
10754 ast::visit::visit_typed_statement(self, statement);
10755 }
10756
10757 fn visit_typed_expr_pipeline(
10758 &mut self,
10759 _location: &'ast SrcSpan,
10760 first_value: &'ast TypedPipelineAssignment,
10761 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
10762 finally: &'ast TypedExpr,
10763 _finally_kind: &'ast PipelineAssignmentKind,
10764 ) {
10765 self.previous_pipeline_assignment_type = None;
10766 self.visit_typed_pipeline_assignment(first_value);
10767
10768 self.previous_pipeline_assignment_type = Some(first_value.type_());
10769 for (assignment, _kind) in assignments {
10770 self.visit_typed_pipeline_assignment(assignment);
10771 self.previous_pipeline_assignment_type = Some(assignment.type_());
10772 }
10773
10774 // If we're selecting a pipeline and the selection ends on its final step
10775 // we want to include that as well into the extracted bit.
10776 let final_step_range = self.edits.src_span_to_lsp_range(finally.location());
10777 if let Some(extracted_function) = &mut self.function
10778 && position_within(self.params.range.end, final_step_range)
10779 {
10780 extracted_function.try_add_pipeline_step(finally.type_(), finally.location());
10781 };
10782
10783 self.visit_typed_expr(finally);
10784 self.previous_pipeline_assignment_type = None;
10785 }
10786
10787 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
10788 // In order to be extracted, a pipeline step must be overlapping with
10789 // the cursor selection!
10790 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
10791 if !overlaps(self.params.range, assignment_range) {
10792 return;
10793 }
10794
10795 match &mut self.function {
10796 None => {
10797 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps {
10798 location: assignment.location,
10799 before_first: self.previous_pipeline_assignment_type.clone(),
10800 return_type: assignment.type_(),
10801 }));
10802 }
10803 Some(extracted_function) => {
10804 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location)
10805 }
10806 }
10807 ast::visit::visit_typed_pipeline_assignment(self, assignment);
10808 }
10809
10810 fn visit_typed_expr_var(
10811 &mut self,
10812 location: &'ast SrcSpan,
10813 constructor: &'ast ValueConstructor,
10814 name: &'ast EcoString,
10815 ) {
10816 if let type_::ValueConstructorVariant::LocalVariable {
10817 location: definition_location,
10818 ..
10819 } = &constructor.variant
10820 {
10821 self.register_referenced_variable(
10822 name,
10823 &constructor.type_,
10824 *location,
10825 *definition_location,
10826 );
10827 }
10828 }
10829
10830 fn visit_typed_clause_guard_var(
10831 &mut self,
10832 location: &'ast SrcSpan,
10833 name: &'ast EcoString,
10834 type_: &'ast Arc<Type>,
10835 definition_location: &'ast SrcSpan,
10836 _origin: &'ast VariableOrigin,
10837 ) {
10838 self.register_referenced_variable(name, type_, *location, *definition_location);
10839 }
10840
10841 fn visit_typed_bit_array_size_variable(
10842 &mut self,
10843 location: &'ast SrcSpan,
10844 name: &'ast EcoString,
10845 constructor: &'ast Option<Box<ValueConstructor>>,
10846 type_: &'ast Arc<Type>,
10847 ) {
10848 let variant = match constructor {
10849 Some(constructor) => &constructor.variant,
10850 None => return,
10851 };
10852 if let type_::ValueConstructorVariant::LocalVariable {
10853 location: definition_location,
10854 ..
10855 } = variant
10856 {
10857 self.register_referenced_variable(name, type_, *location, *definition_location);
10858 }
10859 }
10860}
10861
10862/// Code action to merge two identical branches together.
10863///
10864pub struct MergeCaseBranches<'a> {
10865 module: &'a Module,
10866 params: &'a CodeActionParams,
10867 edits: TextEdits<'a>,
10868 /// These are the positions of the patterns of all the consecutive branches
10869 /// we've determined can be merged, for example if we're mergin the first
10870 /// two branches here:
10871 ///
10872 /// ```gleam
10873 /// case wibble {
10874 /// 1 -> todo
10875 /// // ^ this location here
10876 /// 20 -> todo
10877 /// // ^^ and this location here
10878 /// _ -> todo
10879 /// }
10880 /// ```
10881 ///
10882 /// We need those to delete all the space between each consecutive pattern,
10883 /// replacing it with the `|` for alternatives
10884 ///
10885 patterns_to_merge: Option<MergeableBranches>,
10886}
10887
10888struct MergeableBranches {
10889 /// The span of the body to keep when merging multiple branches. For
10890 /// example:
10891 ///
10892 /// ```gleam
10893 /// case n {
10894 /// // Imagine we're merging the first three branches together...
10895 /// 1 -> todo
10896 /// 2 -> n * 2
10897 /// // ^^^^^ This would be the location of the one body to keep
10898 /// 3 -> todo
10899 /// _ -> todo
10900 /// }
10901 /// ```
10902 ///
10903 body_to_keep: SrcSpan,
10904
10905 /// The location body of the last of the branches that are going to be
10906 /// merged; that is where we're going to place the code of the body to keep
10907 /// once the action is done. For example:
10908 ///
10909 /// ```gleam
10910 /// case n {
10911 /// // Imagine we're merging the first three branches together...
10912 /// 1 -> todo
10913 /// 2 -> n * 2
10914 /// 3 -> todo
10915 /// // ^^^^ This would be the location of the final body
10916 /// _ -> todo
10917 /// }
10918 /// ```
10919 ///
10920 final_body: SrcSpan,
10921
10922 /// The span of the patterns whose branches are going to be merged. For
10923 /// example:
10924 ///
10925 /// ```gleam
10926 /// case n {
10927 /// // Imagine we're merging the first three branches together...
10928 /// 1 -> todo
10929 /// // ^
10930 /// 2 -> n * 2
10931 /// // ^
10932 /// 3 -> todo
10933 /// // ^ These would be the locations of the patterns
10934 /// _ -> todo
10935 /// }
10936 /// ```
10937 ///
10938 patterns_to_merge: Vec<SrcSpan>,
10939}
10940
10941impl<'a> MergeCaseBranches<'a> {
10942 pub fn new(
10943 module: &'a Module,
10944 line_numbers: &'a LineNumbers,
10945 params: &'a CodeActionParams,
10946 ) -> Self {
10947 Self {
10948 module,
10949 params,
10950 edits: TextEdits::new(line_numbers),
10951 patterns_to_merge: None,
10952 }
10953 }
10954
10955 pub fn code_actions(mut self) -> Vec<CodeAction> {
10956 self.visit_typed_module(&self.module.ast);
10957
10958 let Some(mergeable_branches) = self.patterns_to_merge else {
10959 return vec![];
10960 };
10961
10962 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() {
10963 self.edits
10964 .replace(SrcSpan::new(one.end, next.start), " | ".into());
10965 }
10966
10967 self.edits.replace(
10968 mergeable_branches.final_body,
10969 code_at(self.module, mergeable_branches.body_to_keep).into(),
10970 );
10971
10972 let mut action = Vec::with_capacity(1);
10973 CodeActionBuilder::new("Merge case branches")
10974 .kind(CodeActionKind::REFACTOR_REWRITE)
10975 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10976 .preferred(false)
10977 .push_to(&mut action);
10978 action
10979 }
10980
10981 fn select_mergeable_branches(
10982 &self,
10983 clauses: &'a [ast::TypedClause],
10984 ) -> Option<MergeableBranches> {
10985 let mut clauses = clauses
10986 .iter()
10987 // We want to skip all the branches at the beginning of the case
10988 // expression that the cursor is not hovering over. For example:
10989 //
10990 // ```gleam
10991 // case wibble {
10992 // a -> 1 <- we want to skip this one here that is not selected
10993 // b -> 2
10994 // ^^^^ this is the selection
10995 // _ -> 3
10996 // ^^
10997 // }
10998 // ```
10999 .skip_while(|clause| {
11000 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
11001 !overlaps(self.params.range, clause_range)
11002 })
11003 // Then we only want to take the clauses that we're hovering over
11004 // with our selection (even partially!)
11005 // In the provious example they would be `b -> 2` and `_ -> 3`.
11006 .take_while(|clause| {
11007 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
11008 overlaps(self.params.range, clause_range)
11009 });
11010
11011 let first_hovered_clause = clauses.next()?;
11012
11013 // This is the clause we're comparing all the others with. We need to
11014 // make sure that all the clauses we're going to join can be merged with
11015 // this one.
11016 let mut reference_clause = first_hovered_clause;
11017 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()];
11018 let mut final_body = first_hovered_clause.then.location();
11019
11020 for clause in clauses {
11021 // As soon as we find a clause that can't be merged with the current
11022 // reference we know we're done looking for consecutive clauses to
11023 // merge.
11024 if !clauses_can_be_merged(reference_clause, clause) {
11025 break;
11026 }
11027
11028 clause_patterns_to_merge.push(clause.pattern_location());
11029 final_body = clause.then.location();
11030
11031 // If the current reference is a `todo` expression, we want to use
11032 // the newly found mergeable clause as the next reference. The
11033 // reference clause is the one whose body will be kept around, so if
11034 // we can we avoid keeping `todo`s
11035 if reference_clause.then.is_todo_with_no_message() {
11036 reference_clause = clause;
11037 }
11038 }
11039
11040 // We only offer the code action if we have found two or more clauses
11041 // to merge.
11042 if clause_patterns_to_merge.len() >= 2 {
11043 Some(MergeableBranches {
11044 final_body,
11045 body_to_keep: reference_clause.then.location(),
11046 patterns_to_merge: clause_patterns_to_merge,
11047 })
11048 } else {
11049 None
11050 }
11051 }
11052}
11053
11054fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool {
11055 // Two clauses cannot be merged if any of those has an if guard
11056 if one.guard.is_some() || other.guard.is_some() {
11057 return false;
11058 }
11059
11060 // Two clauses can only be merged if they define the same variables,
11061 // otherwise joining them would result in invalid code.
11062 let variables_one = one
11063 .bound_variables()
11064 .map(|variable| (variable.name(), variable.type_))
11065 .collect::<HashMap<_, _>>();
11066
11067 let variables_other = other
11068 .bound_variables()
11069 .map(|variable| (variable.name(), variable.type_))
11070 .collect::<HashMap<_, _>>();
11071
11072 for (name, type_) in variables_one.iter() {
11073 if let Some(type_other) = variables_other.get(name)
11074 && type_other.same_as(type_)
11075 {
11076 continue;
11077 }
11078
11079 // There's a variable that is not defined in the second branch but
11080 // is defined in the first one, or it's defined in the second branch
11081 // but it has an incompatible type.
11082 return false;
11083 }
11084
11085 for (name, _) in variables_other.iter() {
11086 if !variables_one.contains_key(name) {
11087 // There's some variables defined in the second branch that are not
11088 // defined in the first one, so they can't be merged!
11089 return false;
11090 }
11091 }
11092
11093 // Anything can be merged with a simple todo, or the two bodies must be
11094 // syntactically equal.
11095 one.then.is_todo_with_no_message()
11096 || other.then.is_todo_with_no_message()
11097 || one.then.syntactically_eq(&other.then)
11098}
11099
11100impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> {
11101 fn visit_typed_expr_case(
11102 &mut self,
11103 location: &'ast SrcSpan,
11104 type_: &'ast Arc<Type>,
11105 subjects: &'ast [TypedExpr],
11106 clauses: &'ast [ast::TypedClause],
11107 compiled_case: &'ast CompiledCase,
11108 ) {
11109 // We only trigger the code action if we are within a case expression,
11110 // otherwise there's no point in exploring the expression any further.
11111 let case_range = self.edits.src_span_to_lsp_range(*location);
11112 if !within(self.params.range, case_range) {
11113 return;
11114 }
11115
11116 if let result @ Some(_) = self.select_mergeable_branches(clauses) {
11117 self.patterns_to_merge = result
11118 }
11119
11120 // We still need to visit the case expression in case we want to apply
11121 // the code action to some case expression that is nested in one of its
11122 // branches!
11123 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
11124 }
11125}
11126
11127/// Code action to add a missing type parameter to custom types.
11128/// If a custom type is missing a type parameter, as it is the case
11129/// in the following example, this action will offer to add the
11130/// type parameter to the type definition.
11131///
11132/// Before:
11133/// ```gleam
11134/// type Wibble {
11135/// Wibble(field: t)
11136/// }
11137/// ```
11138///
11139/// After:
11140/// ```gleam
11141/// type Wibble(t) {
11142/// Wibble(field: t)
11143/// }
11144/// ```
11145///
11146pub struct AddMissingTypeParameter<'a> {
11147 module: &'a Module,
11148 params: &'a CodeActionParams,
11149 edits: TextEdits<'a>,
11150 /// The source location where the parameters should be defined.
11151 /// This might be a zero-length span if there are no parameters yet,
11152 /// or it might cover the already existing type parameter definitions.
11153 parameters_location: Option<SrcSpan>,
11154 /// If the type definition already had existing parameters before.
11155 has_existing_parameters: bool,
11156 /// The set of all type parameter names in the different variants of the type
11157 /// that are not already part of the type parameter definition on the type.
11158 missing_parameters: HashSet<EcoString>,
11159}
11160
11161impl<'a> AddMissingTypeParameter<'a> {
11162 pub fn new(
11163 module: &'a Module,
11164 line_numbers: &'a LineNumbers,
11165 params: &'a CodeActionParams,
11166 ) -> Self {
11167 Self {
11168 module,
11169 params,
11170 edits: TextEdits::new(line_numbers),
11171 parameters_location: None,
11172 has_existing_parameters: false,
11173 missing_parameters: HashSet::new(),
11174 }
11175 }
11176
11177 pub fn code_actions(mut self) -> Vec<CodeAction> {
11178 self.visit_typed_module(&self.module.ast);
11179
11180 let Some(type_parameters_location) = self.parameters_location else {
11181 return vec![];
11182 };
11183
11184 if self.missing_parameters.is_empty() {
11185 return vec![];
11186 }
11187
11188 let mut new_parameters = self.missing_parameters.iter().sorted().join(", ");
11189 if self.has_existing_parameters {
11190 let has_trailing_comma = self
11191 .module
11192 .extra
11193 .trailing_commas
11194 .iter()
11195 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma));
11196
11197 if !has_trailing_comma {
11198 new_parameters.insert_str(0, ", ");
11199 }
11200
11201 self.edits
11202 .insert(type_parameters_location.end - 1, new_parameters);
11203 } else {
11204 self.edits
11205 .insert(type_parameters_location.end, format!("({new_parameters})"));
11206 }
11207
11208 let mut action = Vec::with_capacity(1);
11209 CodeActionBuilder::new("Add missing type parameter")
11210 .kind(CodeActionKind::QUICKFIX)
11211 .changes(self.params.text_document.uri.clone(), self.edits.edits)
11212 .preferred(true)
11213 .push_to(&mut action);
11214 action
11215 }
11216}
11217
11218impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> {
11219 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
11220 let custom_type_range = self
11221 .edits
11222 .src_span_to_lsp_range(custom_type.full_location());
11223
11224 // Only continue, if the action was selected anywhere within the custom type definition.
11225 if !overlaps(self.params.range, custom_type_range) {
11226 return;
11227 }
11228
11229 self.parameters_location = Some(SrcSpan::new(
11230 custom_type.name_location.end,
11231 custom_type.location.end,
11232 ));
11233
11234 self.has_existing_parameters = !custom_type.typed_parameters.is_empty();
11235
11236 let existing_names: HashSet<_> = custom_type
11237 .parameters
11238 .iter()
11239 .map(|(_, name)| name)
11240 .collect();
11241
11242 // Collect the remaining type parameters from the variant constructors.
11243 for record in &custom_type.constructors {
11244 for argument in &record.arguments {
11245 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast
11246 && !existing_names.contains(name)
11247 {
11248 let _ = self.missing_parameters.insert(name.clone());
11249 }
11250 }
11251 }
11252 }
11253}
11254
11255/// Code action to replace a `_` with its actual type in an annotation.
11256///
11257/// Before:
11258/// ```gleam
11259/// fn wibble() -> Ok(_) { Ok(1) }
11260/// // ^ Trigger it here
11261/// ```
11262///
11263/// After:
11264/// ```gleam
11265/// fn wibble() -> Ok(Int) { Ok(1) }
11266/// ```
11267///
11268pub struct ReplaceUnderscoreWithType<'a> {
11269 module: &'a Module,
11270 params: &'a CodeActionParams,
11271 edits: TextEdits<'a>,
11272 hovered_hole: Option<HoveredHole>,
11273}
11274
11275struct HoveredHole {
11276 type_: Arc<Type>,
11277 location: SrcSpan,
11278}
11279
11280impl<'a> ReplaceUnderscoreWithType<'a> {
11281 pub fn new(
11282 module: &'a Module,
11283 line_numbers: &'a LineNumbers,
11284 params: &'a CodeActionParams,
11285 ) -> Self {
11286 Self {
11287 module,
11288 params,
11289 edits: TextEdits::new(line_numbers),
11290 hovered_hole: None,
11291 }
11292 }
11293
11294 pub fn code_actions(mut self) -> Vec<CodeAction> {
11295 self.visit_typed_module(&self.module.ast);
11296
11297 let mut action = Vec::with_capacity(1);
11298
11299 let Some(HoveredHole { type_, location }) = self.hovered_hole else {
11300 return vec![];
11301 };
11302 let mut printer = Printer::new(&self.module.ast.names);
11303 self.edits
11304 .replace(location, format!("{}", printer.print_type(&type_)));
11305
11306 CodeActionBuilder::new("Replace `_` with type")
11307 .kind(CodeActionKind::QUICKFIX)
11308 .changes(self.params.text_document.uri.clone(), self.edits.edits)
11309 .preferred(true)
11310 .push_to(&mut action);
11311 action
11312 }
11313}
11314
11315impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> {
11316 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) {
11317 // We never traverse a type annotation we're not hovering
11318 let node_location = self.edits.src_span_to_lsp_range(node.location());
11319 if !within(self.params.range, node_location) {
11320 return;
11321 }
11322 ast::visit::visit_type_ast(self, node, inferred_type);
11323 }
11324
11325 fn visit_type_ast_hole(
11326 &mut self,
11327 location: &'ast SrcSpan,
11328 _name: &'ast EcoString,
11329 type_: Option<Arc<Type>>,
11330 ) {
11331 if let Some(type_) = type_ {
11332 self.hovered_hole = Some(HoveredHole {
11333 type_,
11334 location: *location,
11335 })
11336 }
11337 }
11338}