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