Fork of daniellemaywood.uk/gleam — Wasm codegen work
452 kB
12945 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4use std::{collections::HashSet, iter, sync::Arc};
5
6use ecow::{EcoString, eco_format};
7use gleam_core::{
8 Error, STDLIB_PACKAGE_NAME,
9 analyse::Inferred,
10 ast::{
11 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable,
12 BoundVariableName, CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import,
13 InvalidExpression, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments,
14 PipelineAssignmentKind, Publicity, RecordConstructor, SrcSpan, TodoKind,
15 TypeAstConstructorName, TypedArg, TypedAssignment, TypedClauseGuard, TypedDefinitions,
16 TypedExpr, TypedFunction, TypedModuleConstant, TypedPattern, TypedPipelineAssignment,
17 TypedRecordConstructor, TypedStatement, TypedTailPattern, TypedUse, visit::Visit as _,
18 },
19 build::{Located, Module, Origin},
20 config::PackageConfig,
21 exhaustiveness::CompiledCase,
22 line_numbers::LineNumbers,
23 parse::{extra::ModuleExtra, lexer::string_to_keyword},
24 paths::ProjectPaths,
25 strings::to_snake_case,
26 type_::{
27 self, Error as TypeError, FieldMap, ModuleValueConstructor, Opaque, Type, TypeVar,
28 TypedCallArg, ValueConstructor,
29 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin},
30 printer::Printer,
31 },
32};
33use im::HashMap;
34use itertools::Itertools;
35use lsp_types::{
36 CodeAction, CodeActionKind, CodeActionParams, CreateFile, CreateFileOptions, DocumentChange,
37 Position, Range, TextEdit, Uri as Url,
38};
39use vec1::{Vec1, vec1};
40
41use crate::engine::{completely_within, position_within};
42
43use super::{
44 TextEdits,
45 compiler::LspProjectCompiler,
46 edits,
47 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
48 engine::{overlaps, within},
49 files::FileSystemProxy,
50 reference::{FindVariableReferences, VariableReferenceKind},
51 src_span_to_lsp_range, url_from_path,
52};
53
54#[derive(Debug)]
55pub struct CodeActionBuilder {
56 action: CodeAction,
57}
58
59impl CodeActionBuilder {
60 pub fn new(title: &str) -> Self {
61 Self {
62 action: CodeAction {
63 title: title.to_string(),
64 kind: None,
65 diagnostics: None,
66 edit: None,
67 command: None,
68 is_preferred: None,
69 disabled: None,
70 data: None,
71 tags: None,
72 },
73 }
74 }
75
76 pub fn kind(mut self, kind: CodeActionKind) -> Self {
77 self.action.kind = Some(kind);
78 self
79 }
80
81 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
82 let mut edit = self.action.edit.take().unwrap_or_default();
83 let mut changes = edit.changes.take().unwrap_or_default();
84 _ = changes.insert(uri, edits);
85
86 edit.changes = Some(changes);
87 self.action.edit = Some(edit);
88 self
89 }
90
91 pub fn document_changes(mut self, changes: Vec<DocumentChange>) -> Self {
92 let mut edit = self.action.edit.take().unwrap_or_default();
93
94 edit.document_changes = Some(changes);
95
96 self.action.edit = Some(edit);
97 self
98 }
99
100 pub fn preferred(mut self, is_preferred: bool) -> Self {
101 self.action.is_preferred = Some(is_preferred);
102 self
103 }
104
105 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
106 actions.push(self.action);
107 }
108}
109
110/// A small helper function to get the indentation at a given position.
111fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
112 let mut indent_size = 0;
113 let line_start = *line_numbers
114 .line_starts
115 .get(line as usize)
116 .expect("Line number should be valid");
117
118 let mut chars = code[line_start as usize..].chars();
119 while chars.next() == Some(' ') {
120 indent_size += 1;
121 }
122
123 indent_size
124}
125
126// Given a string and a position in it, if the position points to whitespace,
127// this function returns the next position which doesn't.
128fn next_nonwhitespace(string: &EcoString, position: u32) -> u32 {
129 let mut n = position;
130 let mut chars = string[position as usize..].chars();
131 while chars.next().is_some_and(char::is_whitespace) {
132 n += 1;
133 }
134 n
135}
136
137// Given a string and a position in it, if the position points after whitespace,
138// this function returns the previous position which doesn't.
139fn previous_nonwhitespace(string: &EcoString, position: u32) -> u32 {
140 let mut n = position;
141 let mut chars = string[..position as usize].chars();
142 while chars.next_back().is_some_and(char::is_whitespace) {
143 n -= 1;
144 }
145 n
146}
147
148/// Code action to remove literal tuples in case subjects, essentially making
149/// the elements of the tuples into the case's subjects.
150///
151/// The code action is only available for the i'th subject if:
152/// - it is a non-empty tuple, and
153/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
154///
155/// # Basic example:
156///
157/// The following case expression:
158///
159/// ```gleam
160/// case #(1, 2) {
161/// #(a, b) -> 0
162/// }
163/// ```
164///
165/// Becomes:
166///
167/// ```gleam
168/// case 1, 2 {
169/// a, b -> 0
170/// }
171/// ```
172///
173/// # Another example:
174///
175/// The following case expression does not produce any code action
176///
177/// ```gleam
178/// case #(1, 2) {
179/// a -> 0 // <- the pattern is not a tuple
180/// }
181/// ```
182pub struct RedundantTupleInCaseSubject<'a> {
183 edits: TextEdits<'a>,
184 code: &'a EcoString,
185 extra: &'a ModuleExtra,
186 params: &'a CodeActionParams,
187 module: &'a ast::TypedModule,
188 hovered: bool,
189}
190
191impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
192 fn visit_typed_expr_case(
193 &mut self,
194 location: &'ast SrcSpan,
195 type_: &'ast Arc<Type>,
196 subjects: &'ast [TypedExpr],
197 clauses: &'ast [ast::TypedClause],
198 compiled_case: &'ast CompiledCase,
199 ) {
200 for (subject_idx, subject) in subjects.iter().enumerate() {
201 let TypedExpr::Tuple {
202 location, elements, ..
203 } = subject
204 else {
205 continue;
206 };
207
208 // Ignore empty tuple
209 if elements.is_empty() {
210 continue;
211 }
212
213 // We cannot rewrite clauses whose i-th pattern is not a discard or
214 // tuples.
215 let all_ith_patterns_are_tuples_or_discards = clauses
216 .iter()
217 .map(|clause| clause.pattern.get(subject_idx))
218 .all(|pattern| {
219 matches!(
220 pattern,
221 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
222 )
223 });
224
225 if !all_ith_patterns_are_tuples_or_discards {
226 continue;
227 }
228
229 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
230
231 for clause in clauses {
232 match clause.pattern.get(subject_idx) {
233 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
234 *location,
235 elements.last().map(|element| element.location()),
236 ),
237 Some(Pattern::Discard { location, .. }) => {
238 self.discard_tuple_items(*location, elements.len())
239 }
240 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
241 }
242 }
243 let range = self.edits.src_span_to_lsp_range(*location);
244 self.hovered = self.hovered || overlaps(self.params.range, range);
245 }
246
247 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case)
248 }
249}
250
251impl<'a> RedundantTupleInCaseSubject<'a> {
252 pub fn new(
253 module: &'a Module,
254 line_numbers: &'a LineNumbers,
255 params: &'a CodeActionParams,
256 ) -> Self {
257 Self {
258 edits: TextEdits::new(line_numbers),
259 code: &module.code,
260 extra: &module.extra,
261 params,
262 module: &module.ast,
263 hovered: false,
264 }
265 }
266
267 pub fn code_actions(mut self) -> Vec<CodeAction> {
268 self.visit_typed_module(self.module);
269 if !self.hovered {
270 return vec![];
271 }
272
273 self.edits.edits.sort_by_key(|edit| edit.range.start);
274
275 let mut actions = vec![];
276 CodeActionBuilder::new("Remove redundant tuples")
277 .kind(CodeActionKind::RefactorRewrite)
278 .changes(self.params.text_document.uri.clone(), self.edits.edits)
279 .preferred(true)
280 .push_to(&mut actions);
281
282 actions
283 }
284
285 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
286 let tuple_code = self
287 .code
288 .get(location.start as usize..location.end as usize)
289 .expect("valid span");
290
291 // Delete `#`
292 self.edits
293 .delete(SrcSpan::new(location.start, location.start + 1));
294
295 // Delete `(`
296 let (lparen_offset, _) = tuple_code
297 .match_indices('(')
298 // Ignore in comments
299 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
300 .expect("`(` not found in tuple");
301
302 self.edits.delete(SrcSpan::new(
303 location.start + lparen_offset as u32,
304 location.start + lparen_offset as u32 + 1,
305 ));
306
307 // Delete trailing `,` (if applicable)
308 if let Some(last_elem_location) = last_elem_location {
309 // Get the code after the last element until the tuple's `)`
310 let code_after_last_elem = self
311 .code
312 .get(last_elem_location.end as usize..location.end as usize)
313 .expect("valid span");
314
315 if let Some((trailing_comma_offset, _)) = code_after_last_elem
316 .rmatch_indices(',')
317 // Ignore in comments
318 .find(|(i, _)| {
319 !self
320 .extra
321 .is_within_comment(last_elem_location.end + *i as u32)
322 })
323 {
324 self.edits.delete(SrcSpan::new(
325 last_elem_location.end + trailing_comma_offset as u32,
326 last_elem_location.end + trailing_comma_offset as u32 + 1,
327 ));
328 }
329 }
330
331 // Delete )
332 self.edits
333 .delete(SrcSpan::new(location.end - 1, location.end));
334 }
335
336 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
337 // Replace the old discard with multiple discard, one for each of the
338 // tuple items.
339 self.edits.replace(
340 discard_location,
341 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
342 )
343 }
344}
345
346/// Builder for code action to convert `let assert` into a case expression.
347///
348pub struct LetAssertToCase<'a> {
349 module: &'a Module,
350 params: &'a CodeActionParams,
351 actions: Vec<CodeAction>,
352 edits: TextEdits<'a>,
353}
354
355impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
356 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
357 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
358 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan {
359 start: assignment.location.start,
360 end: assignment.value.location().start,
361 });
362 self.visit_typed_expr(&assignment.value);
363
364 // Only offer the code action if the cursor is over the statement and
365 // to prevent weird behaviour when `let assert` statements are nested,
366 // we only check for the code action between the `let` and `=`.
367 if !(within(self.params.range, assignment_range)
368 && overlaps(self.params.range, assignment_start_range))
369 {
370 return;
371 }
372
373 // This pattern only applies to `let assert`
374 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
375 return;
376 };
377
378 // Get the source code for the tested expression
379 let location = assignment.value.location();
380 let expr = self
381 .module
382 .code
383 .get(location.start as usize..location.end as usize)
384 .expect("Location must be valid");
385
386 // Get the source code for the pattern
387 let pattern_location = assignment.pattern.location();
388 let pattern = self
389 .module
390 .code
391 .get(pattern_location.start as usize..pattern_location.end as usize)
392 .expect("Location must be valid");
393
394 let message = message.as_ref().map(|message| {
395 let location = message.location();
396 self.module
397 .code
398 .get(location.start as usize..location.end as usize)
399 .expect("Location must be valid")
400 });
401
402 let range = self.edits.src_span_to_lsp_range(assignment.location);
403
404 // Figure out which variables are assigned in the pattern
405 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
406
407 let assigned = match variables.len() {
408 0 => "_",
409 1 => variables.first().expect("Variables is length one"),
410 _ => &format!("#({})", variables.join(", ")),
411 };
412
413 let mut new_text = format!("let {assigned} = ");
414 let panic_message = if let Some(message) = message {
415 &format!("panic as {message}")
416 } else {
417 "panic"
418 };
419 let clauses = vec![
420 // The existing pattern
421 CaseClause {
422 pattern,
423 // `_` is not a valid expression, so if we are not
424 // binding any variables in the pattern, we simply return Nil.
425 expression: if assigned == "_" { "Nil" } else { assigned },
426 },
427 CaseClause {
428 pattern: "_",
429 expression: panic_message,
430 },
431 ];
432 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
433
434 let uri = &self.params.text_document.uri;
435
436 CodeActionBuilder::new("Convert to case")
437 .kind(CodeActionKind::RefactorRewrite)
438 .changes(uri.clone(), vec![TextEdit { range, new_text }])
439 .preferred(false)
440 .push_to(&mut self.actions);
441 }
442}
443
444impl<'a> LetAssertToCase<'a> {
445 pub fn new(
446 module: &'a Module,
447 line_numbers: &'a LineNumbers,
448 params: &'a CodeActionParams,
449 ) -> Self {
450 Self {
451 module,
452 params,
453 actions: Vec::new(),
454 edits: TextEdits::new(line_numbers),
455 }
456 }
457
458 pub fn code_actions(mut self) -> Vec<CodeAction> {
459 self.visit_typed_module(&self.module.ast);
460 self.actions
461 }
462}
463
464struct PatternVariableFinder {
465 pattern_variables: Vec<EcoString>,
466}
467
468impl PatternVariableFinder {
469 fn new() -> Self {
470 Self {
471 pattern_variables: Vec::new(),
472 }
473 }
474
475 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
476 let mut finder = Self::new();
477 finder.visit_typed_pattern(pattern);
478 finder.pattern_variables
479 }
480}
481
482impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
483 fn visit_typed_pattern_variable(
484 &mut self,
485 _location: &'ast SrcSpan,
486 name: &'ast EcoString,
487 _type: &'ast Arc<Type>,
488 _origin: &'ast VariableOrigin,
489 ) {
490 self.pattern_variables.push(name.clone());
491 }
492
493 fn visit_typed_pattern_assign(
494 &mut self,
495 location: &'ast SrcSpan,
496 name: &'ast EcoString,
497 pattern: &'ast TypedPattern,
498 ) {
499 self.pattern_variables.push(name.clone());
500 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
501 }
502
503 fn visit_typed_pattern_string_prefix(
504 &mut self,
505 _location: &'ast SrcSpan,
506 _left_location: &'ast SrcSpan,
507 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
508 _right_location: &'ast SrcSpan,
509 _left_side_string: &'ast EcoString,
510 right_side_assignment: &'ast AssignName,
511 ) {
512 if let Some((name, _)) = left_side_assignment {
513 self.pattern_variables.push(name.clone());
514 }
515 if let AssignName::Variable(name) = right_side_assignment {
516 self.pattern_variables.push(name.clone());
517 }
518 }
519}
520
521pub fn code_action_inexhaustive_let_to_case(
522 module: &Module,
523 line_numbers: &LineNumbers,
524 params: &CodeActionParams,
525 error: &Option<Error>,
526 actions: &mut Vec<CodeAction>,
527) {
528 let Some(errors) = type_errors_for_module(error, module) else {
529 return;
530 };
531
532 let inexhaustive_assignments = errors
533 .iter()
534 .filter_map(|error| {
535 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error {
536 Some((*location, missing))
537 } else {
538 None
539 }
540 })
541 .collect_vec();
542
543 if inexhaustive_assignments.is_empty() {
544 return;
545 }
546
547 for (location, missing) in inexhaustive_assignments {
548 let mut text_edits = TextEdits::new(line_numbers);
549
550 let range = text_edits.src_span_to_lsp_range(location);
551 if !within(params.range, range) {
552 continue;
553 }
554
555 let Some(Located::Statement(TypedStatement::Assignment(assignment))) =
556 module.find_node(location.start)
557 else {
558 continue;
559 };
560
561 let TypedAssignment {
562 value,
563 pattern,
564 kind: AssignmentKind::Let,
565 location,
566 compiled_case: _,
567 annotation: _,
568 } = assignment.as_ref()
569 else {
570 continue;
571 };
572
573 // Get the source code for the tested expression
574 let value_location = value.location();
575 let expr = module
576 .code
577 .get(value_location.start as usize..value_location.end as usize)
578 .expect("Location must be valid");
579
580 // Get the source code for the pattern
581 let pattern_location = pattern.location();
582 let pattern_code = module
583 .code
584 .get(pattern_location.start as usize..pattern_location.end as usize)
585 .expect("Location must be valid");
586
587 let range = text_edits.src_span_to_lsp_range(*location);
588
589 // Figure out which variables are assigned in the pattern
590 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
591
592 let assigned = match variables.len() {
593 0 => "_",
594 1 => variables.first().expect("Variables is length one"),
595 _ => &format!("#({})", variables.join(", ")),
596 };
597
598 let mut new_text = format!("let {assigned} = ");
599 print_case_expression(
600 range.start.character as usize,
601 expr,
602 iter::once(CaseClause {
603 pattern: pattern_code,
604 expression: if assigned == "_" { "Nil" } else { assigned },
605 })
606 .chain(missing.iter().map(|pattern| CaseClause {
607 pattern,
608 expression: "todo",
609 }))
610 .collect(),
611 &mut new_text,
612 );
613
614 let uri = ¶ms.text_document.uri;
615
616 text_edits.replace(*location, new_text);
617
618 CodeActionBuilder::new("Convert to case")
619 .kind(CodeActionKind::QuickFix)
620 .changes(uri.clone(), text_edits.edits)
621 .preferred(true)
622 .push_to(actions);
623 }
624}
625
626pub(crate) fn type_errors_for_module<'a>(
627 error: &'a Option<Error>,
628 module: &Module,
629) -> Option<&'a Vec1<type_::Error>> {
630 let Some(Error::Type {
631 skipped_modules: _,
632 failed_modules,
633 }) = error
634 else {
635 return None;
636 };
637 Some(&failed_modules.get(&module.name)?.errors)
638}
639
640struct CaseClause<'a> {
641 pattern: &'a str,
642 expression: &'a str,
643}
644
645fn print_case_expression(
646 indent_size: usize,
647 subject: &str,
648 clauses: Vec<CaseClause<'_>>,
649 buffer: &mut String,
650) {
651 let indent = " ".repeat(indent_size);
652
653 // Print the beginning of the expression
654 buffer.push_str("case ");
655 buffer.push_str(subject);
656 buffer.push_str(" {");
657
658 for clause in clauses.iter() {
659 // Print the newline and indentation for this clause
660 buffer.push('\n');
661 buffer.push_str(&indent);
662 // Indent this clause one level deeper than the case expression
663 buffer.push_str(" ");
664
665 // Print the clause
666 buffer.push_str(clause.pattern);
667 buffer.push_str(" -> ");
668 buffer.push_str(clause.expression);
669 }
670
671 // If there are no clauses to print, the closing brace should be
672 // on the same line as the opening one, with no space between.
673 if !clauses.is_empty() {
674 buffer.push('\n');
675 buffer.push_str(&indent);
676 }
677 buffer.push('}');
678}
679
680/// Builder for code action to apply the label shorthand syntax on arguments
681/// where the label has the same name as the variable.
682///
683pub struct UseLabelShorthandSyntax<'a> {
684 module: &'a Module,
685 params: &'a CodeActionParams,
686 edits: TextEdits<'a>,
687}
688
689impl<'a> UseLabelShorthandSyntax<'a> {
690 pub fn new(
691 module: &'a Module,
692 line_numbers: &'a LineNumbers,
693 params: &'a CodeActionParams,
694 ) -> Self {
695 Self {
696 module,
697 params,
698 edits: TextEdits::new(line_numbers),
699 }
700 }
701
702 pub fn code_actions(mut self) -> Vec<CodeAction> {
703 self.visit_typed_module(&self.module.ast);
704 if self.edits.edits.is_empty() {
705 return vec![];
706 }
707 let mut action = Vec::with_capacity(1);
708 CodeActionBuilder::new("Use label shorthand syntax")
709 .kind(CodeActionKind::Refactor)
710 .changes(self.params.text_document.uri.clone(), self.edits.edits)
711 .preferred(false)
712 .push_to(&mut action);
713 action
714 }
715}
716
717impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
718 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
719 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
720 let is_selected = overlaps(arg_range, self.params.range);
721
722 match arg {
723 CallArg {
724 label: Some(label),
725 value: TypedExpr::Var { name, location, .. },
726 ..
727 } if is_selected && !arg.uses_label_shorthand() && label == name => {
728 self.edits.delete(*location)
729 }
730 _ => (),
731 }
732
733 ast::visit::visit_typed_call_arg(self, arg)
734 }
735
736 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
737 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
738 let is_selected = overlaps(arg_range, self.params.range);
739
740 match arg {
741 CallArg {
742 label: Some(label),
743 value: TypedPattern::Variable { name, location, .. },
744 ..
745 } if is_selected && !arg.uses_label_shorthand() && label == name => {
746 self.edits.delete(*location)
747 }
748 _ => (),
749 }
750
751 ast::visit::visit_typed_pattern_call_arg(self, arg)
752 }
753}
754
755/// Builder for code action to apply the fill in the missing labelled arguments
756/// of the selected function call.
757///
758pub struct FillInMissingLabelledArgs<'a> {
759 module: &'a Module,
760 params: &'a CodeActionParams,
761 edits: TextEdits<'a>,
762 use_right_hand_side_location: Option<SrcSpan>,
763 selected_call: Option<SelectedCall<'a>>,
764 current_function: Option<&'a TypedFunction>,
765}
766
767struct SelectedCall<'a> {
768 location: SrcSpan,
769 field_map: &'a FieldMap,
770 arguments: Vec<CallArg<()>>,
771 kind: SelectedCallKind,
772 fun_type: Option<Arc<Type>>,
773 enclosing_function: Option<&'a TypedFunction>,
774}
775
776enum SelectedCallKind {
777 Value,
778 Pattern,
779}
780
781impl<'a> FillInMissingLabelledArgs<'a> {
782 pub fn new(
783 module: &'a Module,
784 line_numbers: &'a LineNumbers,
785 params: &'a CodeActionParams,
786 ) -> Self {
787 Self {
788 module,
789 params,
790 edits: TextEdits::new(line_numbers),
791 use_right_hand_side_location: None,
792 selected_call: None,
793 current_function: None,
794 }
795 }
796
797 pub fn code_actions(mut self) -> Vec<CodeAction> {
798 self.visit_typed_module(&self.module.ast);
799 let Some(SelectedCall {
800 location: call_location,
801 field_map,
802 arguments,
803 kind,
804 fun_type,
805 enclosing_function,
806 }) = self.selected_call
807 else {
808 return vec![];
809 };
810
811 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
812 let missing_labels = field_map.missing_labels(&arguments);
813
814 // If we're applying the code action to a use call, then we know
815 // that the last missing argument is going to be implicitly inserted
816 // by the compiler, so in that case we don't want to also add that
817 // last label to the completions.
818 let missing_labels = missing_labels.iter().peekable();
819 let mut missing_labels = if is_use_call {
820 missing_labels.dropping_back(1)
821 } else {
822 missing_labels
823 };
824
825 // If we couldn't find any missing label to insert we just return.
826 if missing_labels.peek().is_none() {
827 return vec![];
828 }
829
830 // A pattern could have been written with no parentheses at all!
831 // So we need to check for the last character to see if parentheses
832 // are there or not before filling the arguments in
833 let has_parentheses = ")"
834 == code_at(
835 self.module,
836 SrcSpan::new(call_location.end - 1, call_location.end),
837 );
838 let label_insertion_start = if has_parentheses {
839 // If it ends with a parentheses we'll need to start inserting
840 // right before the closing one...
841 call_location.end - 1
842 } else {
843 // ...otherwise we just append the result
844 call_location.end
845 };
846
847 // Now we need to figure out if there's a comma at the end of the
848 // arguments list:
849 //
850 // call(one, |)
851 // ^ Cursor here, with a comma behind
852 //
853 // call(one|)
854 // ^ Cursor here, no comma behind, we'll have to add one!
855 //
856 let has_comma_after_last_argument =
857 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) {
858 self.module
859 .code
860 .get(last_arg.location.end as usize..=label_insertion_start as usize)
861 .is_some_and(|text| text.contains(','))
862 } else {
863 false
864 };
865
866 let scope_variables =
867 ScopeVariableCollector::new(call_location.start, &self.module.ast.definitions);
868 let variables_in_scope = match enclosing_function {
869 Some(function) => scope_variables.collect_from_function(function),
870 None => scope_variables.variables,
871 };
872
873 let labels_list = missing_labels
874 .map(|label| {
875 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope)
876 })
877 .join(", ");
878
879 let has_no_explicit_arguments = arguments
880 .iter()
881 .filter(|arg| !arg.is_implicit())
882 .peekable()
883 .peek()
884 .is_none();
885
886 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
887 labels_list
888 } else {
889 format!(", {labels_list}")
890 };
891
892 let edit = if has_parentheses {
893 labels_list
894 } else {
895 // If the variant whose arguments we're filling in was written
896 // with no parentheses we need to add those as well to make it a
897 // valid constructor.
898 format!("({labels_list})")
899 };
900
901 self.edits.insert(label_insertion_start, edit);
902
903 let mut action = Vec::with_capacity(1);
904 CodeActionBuilder::new("Fill labels")
905 .kind(CodeActionKind::QuickFix)
906 .changes(self.params.text_document.uri.clone(), self.edits.edits)
907 .preferred(true)
908 .push_to(&mut action);
909 action
910 }
911
912 /// Formats a label for insertion. Uses `label:` syntax if there's a variable
913 /// in scope with matching name and type, otherwise uses `label: todo`.
914 fn format_label(
915 label: &EcoString,
916 kind: &SelectedCallKind,
917 fun_type: &Option<Arc<Type>>,
918 field_map: &FieldMap,
919 variables_in_scope: &HashMap<EcoString, Arc<Type>>,
920 ) -> String {
921 if matches!(kind, SelectedCallKind::Pattern) {
922 return format!("{label}:");
923 }
924
925 if let Some(var_type) = variables_in_scope.get(label) {
926 let expected_type = fun_type.as_ref().and_then(|ft| {
927 let (arg_types, _) = ft.fn_types()?;
928 let &index = field_map.fields.get(label)?;
929 arg_types.get(index as usize).cloned()
930 });
931
932 if let Some(expected) = expected_type
933 && expected.same_as(var_type)
934 {
935 return format!("{label}:");
936 }
937 }
938
939 format!("{label}: todo")
940 }
941
942 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
943 CallArg {
944 label: argument.label.clone(),
945 location: argument.location,
946 value: (),
947 implicit: argument.implicit,
948 }
949 }
950}
951
952impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
953 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
954 // We store the current function as the containing function while we traverse
955 // it, allowing handling nested functions correctly.
956 let previous_function = self.current_function;
957 self.current_function = Some(fun);
958 ast::visit::visit_typed_function(self, fun);
959 self.current_function = previous_function;
960 }
961
962 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
963 // If we're adding labels to a use call the correct location of the
964 // function we need to add labels to is `use_right_hand_side_location`.
965 // So we store it for when we're typing the use call.
966 let previous = self.use_right_hand_side_location;
967 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
968 ast::visit::visit_typed_use(self, use_);
969 self.use_right_hand_side_location = previous;
970 }
971
972 fn visit_typed_constant_record(
973 &mut self,
974 location: &'ast SrcSpan,
975 module: &'ast Option<(EcoString, SrcSpan)>,
976 name: &'ast EcoString,
977 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
978 type_: &'ast Arc<Type>,
979 field_map: &'ast Inferred<FieldMap>,
980 record_constructor: &'ast Option<Box<ValueConstructor>>,
981 ) {
982 let record_range = self.edits.src_span_to_lsp_range(*location);
983 if !within(self.params.range, record_range) {
984 return;
985 }
986
987 if let Some(arguments) = arguments
988 && let Inferred::Known(field_map) = field_map
989 {
990 self.selected_call = Some(SelectedCall {
991 location: *location,
992 field_map,
993 arguments: arguments.iter().map(Self::empty_argument).collect(),
994 kind: SelectedCallKind::Value,
995 fun_type: record_constructor
996 .as_ref()
997 .map(|constructor| constructor.type_.clone()),
998 enclosing_function: None,
999 })
1000 }
1001
1002 // We only want to take into account the innermost function call
1003 // containing the current selection so we can't stop at the first call
1004 // we find (the outermost one) and have to keep traversing it in case
1005 // we're inside a nested call.
1006 ast::visit::visit_typed_constant_record(
1007 self,
1008 location,
1009 module,
1010 name,
1011 arguments,
1012 type_,
1013 field_map,
1014 record_constructor,
1015 );
1016 }
1017
1018 fn visit_typed_expr_call(
1019 &mut self,
1020 location: &'ast SrcSpan,
1021 type_: &'ast Arc<Type>,
1022 fun: &'ast TypedExpr,
1023 arguments: &'ast [TypedCallArg],
1024 open_parenthesis: &'ast Option<u32>,
1025 ) {
1026 let call_range = self.edits.src_span_to_lsp_range(*location);
1027 if !within(self.params.range, call_range) {
1028 return;
1029 }
1030
1031 if let Some(field_map) = fun.field_map() {
1032 let location = self.use_right_hand_side_location.unwrap_or(*location);
1033 self.selected_call = Some(SelectedCall {
1034 location,
1035 field_map,
1036 arguments: arguments.iter().map(Self::empty_argument).collect(),
1037 kind: SelectedCallKind::Value,
1038 fun_type: Some(fun.type_()),
1039 enclosing_function: self.current_function,
1040 })
1041 }
1042
1043 // We only want to take into account the innermost function call
1044 // containing the current selection so we can't stop at the first call
1045 // we find (the outermost one) and have to keep traversing it in case
1046 // we're inside a nested call.
1047 let previous = self.use_right_hand_side_location;
1048 self.use_right_hand_side_location = None;
1049 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
1050 self.use_right_hand_side_location = previous;
1051 }
1052
1053 fn visit_typed_pattern_constructor(
1054 &mut self,
1055 location: &'ast SrcSpan,
1056 name_location: &'ast SrcSpan,
1057 name: &'ast EcoString,
1058 arguments: &'ast Vec<CallArg<TypedPattern>>,
1059 module: &'ast Option<(EcoString, SrcSpan)>,
1060 constructor: &'ast Inferred<type_::PatternConstructor>,
1061 spread: &'ast Option<SrcSpan>,
1062 type_: &'ast Arc<Type>,
1063 ) {
1064 let call_range = self.edits.src_span_to_lsp_range(*location);
1065 if !within(self.params.range, call_range) {
1066 return;
1067 }
1068
1069 if let Some(field_map) = constructor.field_map() {
1070 self.selected_call = Some(SelectedCall {
1071 location: *location,
1072 field_map,
1073 arguments: arguments.iter().map(Self::empty_argument).collect(),
1074 kind: SelectedCallKind::Pattern,
1075 fun_type: None,
1076 enclosing_function: self.current_function,
1077 })
1078 }
1079
1080 ast::visit::visit_typed_pattern_constructor(
1081 self,
1082 location,
1083 name_location,
1084 name,
1085 arguments,
1086 module,
1087 constructor,
1088 spread,
1089 type_,
1090 );
1091 }
1092}
1093
1094/// Collects variables that are in scope at a given cursor position.
1095struct ScopeVariableCollector {
1096 cursor: u32,
1097 pub variables: HashMap<EcoString, Arc<Type>>,
1098}
1099
1100impl ScopeVariableCollector {
1101 fn new(cursor: u32, definitions: &TypedDefinitions) -> Self {
1102 let TypedDefinitions { constants, .. } = definitions;
1103
1104 // When creating a collector, we add module constants to the variables
1105 // that are in scope.
1106 // These will always be available inside the body of any function we
1107 // might want to analyse (unless they are shadowed and replaced in the
1108 // map, that is)
1109 let variables = constants
1110 .iter()
1111 .map(|constant| (constant.name.clone(), constant.type_.clone()))
1112 .collect();
1113
1114 Self { cursor, variables }
1115 }
1116
1117 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> {
1118 for arg in &fun.arguments {
1119 if let Some(name) = arg.get_variable_name() {
1120 _ = self.variables.insert(name.clone(), arg.type_.clone());
1121 }
1122 }
1123
1124 for statement in &fun.body {
1125 self.visit_typed_statement(statement);
1126 }
1127
1128 self.variables
1129 }
1130}
1131
1132impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector {
1133 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
1134 // We need to consider variables defined only before the cursor
1135 if statement.location().start >= self.cursor {
1136 return;
1137 }
1138
1139 ast::visit::visit_typed_statement(self, statement);
1140 }
1141
1142 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1143 // if cursor is inside the assignment, we only visit the value expression,
1144 // because the variable being defined isn't in scope yet.
1145 if assignment.location.contains(self.cursor) {
1146 self.visit_typed_expr(&assignment.value);
1147 } else {
1148 self.visit_typed_pattern(&assignment.pattern);
1149 }
1150 }
1151
1152 fn visit_typed_expr_fn(
1153 &mut self,
1154 _location: &'ast SrcSpan,
1155 _type_: &'ast Arc<Type>,
1156 _kind: &'ast FunctionLiteralKind,
1157 _arguments: &'ast [TypedArg],
1158 _body: &'ast Vec1<TypedStatement>,
1159 _return_annotation: &'ast Option<ast::TypeAst>,
1160 ) {
1161 // We don't descend into nested functions, as their variables aren't in
1162 // our scope
1163 }
1164
1165 fn visit_typed_expr_block(
1166 &mut self,
1167 location: &'ast SrcSpan,
1168 statements: &'ast [TypedStatement],
1169 ) {
1170 if self.cursor >= location.end {
1171 return;
1172 }
1173 ast::visit::visit_typed_expr_block(self, location, statements);
1174 }
1175
1176 fn visit_typed_pattern_variable(
1177 &mut self,
1178 _location: &'ast SrcSpan,
1179 name: &'ast EcoString,
1180 type_: &'ast Arc<Type>,
1181 _origin: &'ast VariableOrigin,
1182 ) {
1183 if !name.starts_with('_') {
1184 _ = self.variables.insert(name.clone(), type_.clone());
1185 }
1186 }
1187
1188 fn visit_typed_pattern_assign(
1189 &mut self,
1190 _location: &'ast SrcSpan,
1191 name: &'ast EcoString,
1192 pattern: &'ast Pattern<Arc<Type>>,
1193 ) {
1194 self.visit_typed_pattern(pattern);
1195 if !name.starts_with('_') {
1196 _ = self.variables.insert(name.clone(), pattern.type_());
1197 }
1198 }
1199}
1200
1201struct MissingImport {
1202 location: SrcSpan,
1203 suggestions: Vec<ImportSuggestion>,
1204}
1205
1206struct ImportSuggestion {
1207 // The name to replace with, if the user made a typo
1208 name: EcoString,
1209 // The optional module to import, if suggesting an importable module
1210 import: Option<EcoString>,
1211}
1212
1213pub fn code_action_import_module(
1214 module: &Module,
1215 line_numbers: &LineNumbers,
1216 params: &CodeActionParams,
1217 error: &Option<Error>,
1218 actions: &mut Vec<CodeAction>,
1219) {
1220 let uri = ¶ms.text_document.uri;
1221 let Some(errors) = type_errors_for_module(error, module) else {
1222 return;
1223 };
1224
1225 let missing_imports = errors
1226 .into_iter()
1227 .filter_map(|error| {
1228 if let type_::Error::UnknownModule {
1229 location,
1230 suggestions,
1231 ..
1232 } = error
1233 {
1234 suggest_imports(*location, suggestions)
1235 } else {
1236 None
1237 }
1238 })
1239 .collect_vec();
1240
1241 if missing_imports.is_empty() {
1242 return;
1243 }
1244
1245 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
1246 let first_is_import = first_import_pos.is_some();
1247 let import_location = first_import_pos.unwrap_or_default();
1248
1249 let after_import_newlines =
1250 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
1251
1252 for missing_import in missing_imports {
1253 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
1254 if !overlaps(params.range, range) {
1255 continue;
1256 }
1257
1258 for suggestion in missing_import.suggestions {
1259 let mut edits = vec![TextEdit {
1260 range,
1261 new_text: suggestion.name.to_string(),
1262 }];
1263 if let Some(import) = &suggestion.import {
1264 edits.push(get_import_edit(
1265 import_location,
1266 import,
1267 &after_import_newlines,
1268 ))
1269 };
1270
1271 let title = match &suggestion.import {
1272 Some(import) => &format!("Import `{import}`"),
1273 _ => &format!("Did you mean `{}`", suggestion.name),
1274 };
1275
1276 CodeActionBuilder::new(title)
1277 .kind(CodeActionKind::QuickFix)
1278 .changes(uri.clone(), edits)
1279 .preferred(true)
1280 .push_to(actions);
1281 }
1282 }
1283}
1284
1285pub fn code_action_generate_type(
1286 module: &Module,
1287 line_numbers: &LineNumbers,
1288 params: &CodeActionParams,
1289 error: &Option<Error>,
1290 actions: &mut Vec<CodeAction>,
1291) {
1292 let uri = ¶ms.text_document.uri;
1293 let Some(errors) = type_errors_for_module(error, module) else {
1294 return;
1295 };
1296
1297 for error in errors {
1298 let type_::Error::UnknownType {
1299 location,
1300 name,
1301 parameter_names,
1302 ..
1303 } = error
1304 else {
1305 continue;
1306 };
1307
1308 let range = src_span_to_lsp_range(*location, line_numbers);
1309 if !within(params.range, range) {
1310 continue;
1311 }
1312
1313 // Insert the new type stub before the top-level definition that
1314 // contains the error, so it appears close to where it is used.
1315 let (insert_at, is_public) =
1316 definition_start_and_publicity_containing(&module.ast.definitions, location.start)
1317 .unwrap_or((module.code.len() as u32, false));
1318 let insert_range = src_span_to_lsp_range(
1319 SrcSpan {
1320 start: insert_at,
1321 end: insert_at,
1322 },
1323 line_numbers,
1324 );
1325
1326 let pub_prefix = if is_public { "pub " } else { "" };
1327 let new_text = if parameter_names.is_empty() {
1328 format!("{pub_prefix}type {name}\n\n")
1329 } else {
1330 let parameters = parameter_names.join(", ");
1331 format!("{pub_prefix}type {name}({parameters})\n\n")
1332 };
1333
1334 let edit = TextEdit {
1335 range: insert_range,
1336 new_text,
1337 };
1338
1339 CodeActionBuilder::new("Generate type")
1340 .kind(CodeActionKind::QuickFix)
1341 .changes(uri.clone(), vec![edit])
1342 .preferred(true)
1343 .push_to(actions);
1344 }
1345}
1346
1347/// Returns the source offset and publicity of the top-level definition that
1348/// contains `position`, so the caller can insert code before it.
1349fn definition_start_and_publicity_containing(
1350 definitions: &TypedDefinitions,
1351 position: u32,
1352) -> Option<(u32, bool)> {
1353 let functions = definitions
1354 .functions
1355 .iter()
1356 .map(|function| (function.full_location(), function.publicity.is_public()));
1357 let custom_types = definitions.custom_types.iter().map(|custom_type| {
1358 (
1359 custom_type.full_location(),
1360 custom_type.publicity.is_public(),
1361 )
1362 });
1363 let type_aliases = definitions
1364 .type_aliases
1365 .iter()
1366 .map(|type_alias| (type_alias.location, type_alias.publicity.is_public()));
1367 let constants = definitions
1368 .constants
1369 .iter()
1370 .map(|constant| (constant.location, constant.publicity.is_public()));
1371
1372 functions
1373 .chain(custom_types)
1374 .chain(type_aliases)
1375 .chain(constants)
1376 .filter(|(span, _)| span.start <= position && position <= span.end)
1377 .map(|(span, is_public)| (span.start, is_public))
1378 .next()
1379}
1380
1381fn suggest_imports(
1382 location: SrcSpan,
1383 importable_modules: &[ModuleSuggestion],
1384) -> Option<MissingImport> {
1385 let suggestions = importable_modules
1386 .iter()
1387 .map(|suggestion| {
1388 let imported_name = suggestion.last_name_component();
1389 match suggestion {
1390 ModuleSuggestion::Importable(name) => ImportSuggestion {
1391 name: imported_name.into(),
1392 import: Some(name.clone()),
1393 },
1394 ModuleSuggestion::Imported(_) => ImportSuggestion {
1395 name: imported_name.into(),
1396 import: None,
1397 },
1398 }
1399 })
1400 .collect_vec();
1401
1402 if suggestions.is_empty() {
1403 None
1404 } else {
1405 Some(MissingImport {
1406 location,
1407 suggestions,
1408 })
1409 }
1410}
1411
1412pub fn code_action_add_missing_patterns(
1413 module: &Module,
1414 line_numbers: &LineNumbers,
1415 params: &CodeActionParams,
1416 error: &Option<Error>,
1417 actions: &mut Vec<CodeAction>,
1418) {
1419 let uri = ¶ms.text_document.uri;
1420 let Some(errors) = type_errors_for_module(error, module) else {
1421 return;
1422 };
1423 let missing_patterns = errors
1424 .iter()
1425 .filter_map(|error| {
1426 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error {
1427 Some((*location, missing))
1428 } else {
1429 None
1430 }
1431 })
1432 .collect_vec();
1433
1434 if missing_patterns.is_empty() {
1435 return;
1436 }
1437
1438 for (location, missing) in missing_patterns {
1439 let mut edits = TextEdits::new(line_numbers);
1440 let range = edits.src_span_to_lsp_range(location);
1441 if !within(params.range, range) {
1442 continue;
1443 }
1444
1445 let Some(Located::Expression {
1446 expression: TypedExpr::Case {
1447 clauses, subjects, ..
1448 },
1449 ..
1450 }) = module.find_node(location.start)
1451 else {
1452 continue;
1453 };
1454
1455 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1456
1457 let indent = " ".repeat(indent_size);
1458
1459 // Insert the missing patterns just after the final clause, or just before
1460 // the closing brace if there are no clauses
1461
1462 let insert_at = clauses
1463 .last()
1464 .map(|clause| clause.location.end)
1465 .unwrap_or(location.end - 1);
1466
1467 for pattern in missing {
1468 let new_text = format!("\n{indent} {pattern} -> todo");
1469 edits.insert(insert_at, new_text);
1470 }
1471
1472 // Add a newline + indent after the last pattern if there are no clauses
1473 //
1474 // This improves the generated code for this case:
1475 // ```gleam
1476 // case True {}
1477 // ```
1478 // This produces:
1479 // ```gleam
1480 // case True {
1481 // True -> todo
1482 // False -> todo
1483 // }
1484 // ```
1485 // Instead of:
1486 // ```gleam
1487 // case True {
1488 // True -> todo
1489 // False -> todo}
1490 // ```
1491 //
1492 if clauses.is_empty() {
1493 let last_subject_location = subjects
1494 .last()
1495 .expect("Case expressions have at least one subject")
1496 .location()
1497 .end;
1498
1499 // Find the opening brace of the case expression
1500 let chars = module.code[last_subject_location as usize..].chars();
1501 let mut start_brace_location = last_subject_location;
1502 for char in chars {
1503 start_brace_location += 1;
1504 if char == '{' {
1505 break;
1506 }
1507 }
1508
1509 // We remove any blank spaces/lines between the end of the last
1510 // comment inside the case expression and the insertion point.
1511 // If there's no comments we remove all empty space in between the
1512 // two braces
1513 let deletion_start = module
1514 .extra
1515 .last_comment_between(start_brace_location, insert_at)
1516 .map(|src_span| src_span.end)
1517 .unwrap_or(start_brace_location);
1518
1519 edits.delete(SrcSpan::new(deletion_start, insert_at));
1520 edits.insert(insert_at, format!("\n{indent}"));
1521 }
1522
1523 CodeActionBuilder::new("Add missing patterns")
1524 .kind(CodeActionKind::QuickFix)
1525 .changes(uri.clone(), edits.edits)
1526 .preferred(true)
1527 .push_to(actions);
1528 }
1529}
1530
1531/// Builder for code action to add annotations to an assignment or function
1532///
1533pub struct AddAnnotations<'a> {
1534 module: &'a Module,
1535 params: &'a CodeActionParams,
1536 edits: TextEdits<'a>,
1537 printer: Printer<'a>,
1538}
1539
1540impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1541 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1542 self.visit_typed_expr(&assignment.value);
1543
1544 // We only offer this code action between `let` and `=`, because
1545 // otherwise it could lead to confusing behaviour if inside a block
1546 // which is part of a let binding.
1547 let pattern_location = assignment.pattern.location();
1548 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1549 let code_action_range = self.edits.src_span_to_lsp_range(location);
1550
1551 // Only offer the code action if the cursor is over the statement
1552 if !overlaps(code_action_range, self.params.range) {
1553 return;
1554 }
1555
1556 // We don't need to add an annotation if there already is one
1557 if assignment.annotation.is_some() {
1558 return;
1559 }
1560
1561 // Various expressions such as pipelines and `use` expressions generate assignments
1562 // internally. However, these cannot be annotated and so we don't offer a code action here.
1563 if matches!(assignment.kind, AssignmentKind::Generated) {
1564 return;
1565 }
1566
1567 self.edits.insert(
1568 pattern_location.end,
1569 format!(": {}", self.printer.print_type(&assignment.type_())),
1570 );
1571 }
1572
1573 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1574 // Since type variable names are local to definitions, any type variables
1575 // in other parts of the module shouldn't affect what we print for the
1576 // annotations of this constant.
1577 self.printer.clear_type_variables();
1578
1579 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1580
1581 // Only offer the code action if the cursor is over the statement
1582 if !overlaps(code_action_range, self.params.range) {
1583 return;
1584 }
1585
1586 // We don't need to add an annotation if there already is one
1587 if constant.annotation.is_some() {
1588 return;
1589 }
1590
1591 self.edits.insert(
1592 constant.name_location.end,
1593 format!(": {}", self.printer.print_type(&constant.type_)),
1594 );
1595 }
1596
1597 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1598 // Since type variable names are local to definitions, any type variables
1599 // in other parts of the module shouldn't affect what we print for the
1600 // annotations of this functions. The only variables which cannot clash
1601 // are ones defined in the signature of this function, which we register
1602 // when we visit the parameters of this function inside `collect_type_variables`.
1603 self.printer.clear_type_variables();
1604 collect_type_variables(&mut self.printer, fun);
1605
1606 ast::visit::visit_typed_function(self, fun);
1607
1608 let code_action_range = self.edits.src_span_to_lsp_range(
1609 fun.body_start
1610 .map(|body_start| SrcSpan {
1611 start: fun.location.start,
1612 end: body_start,
1613 })
1614 .unwrap_or(fun.location),
1615 );
1616
1617 // Only offer the code action if the cursor is over the statement
1618 if !overlaps(code_action_range, self.params.range) {
1619 return;
1620 }
1621
1622 // Annotate each argument separately
1623 for argument in fun.arguments.iter() {
1624 // Don't annotate the argument if it's already annotated
1625 if argument.annotation.is_some() {
1626 continue;
1627 }
1628
1629 self.edits.insert(
1630 argument.location.end,
1631 format!(": {}", self.printer.print_type(&argument.type_)),
1632 );
1633 }
1634
1635 // Annotate the return type if it isn't already annotated
1636 if fun.return_annotation.is_none() {
1637 self.edits.insert(
1638 fun.location.end,
1639 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1640 );
1641 }
1642 }
1643
1644 fn visit_typed_expr_fn(
1645 &mut self,
1646 location: &'ast SrcSpan,
1647 type_: &'ast Arc<Type>,
1648 kind: &'ast FunctionLiteralKind,
1649 arguments: &'ast [TypedArg],
1650 body: &'ast Vec1<TypedStatement>,
1651 return_annotation: &'ast Option<ast::TypeAst>,
1652 ) {
1653 ast::visit::visit_typed_expr_fn(
1654 self,
1655 location,
1656 type_,
1657 kind,
1658 arguments,
1659 body,
1660 return_annotation,
1661 );
1662
1663 // If the function doesn't have a head, we can't annotate it
1664 let location = match kind {
1665 // Function captures don't need any type annotations
1666 FunctionLiteralKind::Capture { .. } => return,
1667 FunctionLiteralKind::Anonymous { head, .. } => head,
1668 FunctionLiteralKind::Use { location } => location,
1669 };
1670
1671 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1672
1673 // Only offer the code action if the cursor is over the expression
1674 if !overlaps(code_action_range, self.params.range) {
1675 return;
1676 }
1677
1678 // Annotate each argument separately
1679 for argument in arguments.iter() {
1680 // Don't annotate the argument if it's already annotated
1681 if argument.annotation.is_some() {
1682 continue;
1683 }
1684
1685 self.edits.insert(
1686 argument.location.end,
1687 format!(": {}", self.printer.print_type(&argument.type_)),
1688 );
1689 }
1690
1691 // Annotate the return type if it isn't already annotated, and this is
1692 // an anonymous function.
1693 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1694 let return_type = &type_.return_type().expect("Type must be a function");
1695 let pretty_type = self.printer.print_type(return_type);
1696 self.edits
1697 .insert(location.end, format!(" -> {pretty_type}"));
1698 }
1699 }
1700}
1701
1702impl<'a> AddAnnotations<'a> {
1703 pub fn new(
1704 module: &'a Module,
1705 line_numbers: &'a LineNumbers,
1706 params: &'a CodeActionParams,
1707 ) -> Self {
1708 Self {
1709 module,
1710 params,
1711 edits: TextEdits::new(line_numbers),
1712 // We need to use the same printer for all the edits because otherwise
1713 // we could get duplicate type variable names.
1714 printer: Printer::new_without_type_variables(&module.ast.names),
1715 }
1716 }
1717
1718 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1719 self.visit_typed_module(&self.module.ast);
1720
1721 let uri = &self.params.text_document.uri;
1722
1723 let title = match self.edits.edits.len() {
1724 // We don't offer a code action if there is no action to perform
1725 0 => return,
1726 1 => "Add type annotation",
1727 _ => "Add type annotations",
1728 };
1729
1730 CodeActionBuilder::new(title)
1731 .kind(CodeActionKind::Refactor)
1732 .changes(uri.clone(), self.edits.edits)
1733 .preferred(false)
1734 .push_to(actions);
1735 }
1736}
1737
1738/// Code action to add type annotations to all top level definitions
1739///
1740pub struct AnnotateTopLevelDefinitions<'a> {
1741 module: &'a Module,
1742 params: &'a CodeActionParams,
1743 edits: TextEdits<'a>,
1744 is_hovering_definition_requiring_annotations: bool,
1745}
1746
1747impl<'a> AnnotateTopLevelDefinitions<'a> {
1748 pub fn new(
1749 module: &'a Module,
1750 line_numbers: &'a LineNumbers,
1751 params: &'a CodeActionParams,
1752 ) -> Self {
1753 Self {
1754 module,
1755 params,
1756 edits: TextEdits::new(line_numbers),
1757 is_hovering_definition_requiring_annotations: false,
1758 }
1759 }
1760
1761 pub fn code_actions(mut self) -> Vec<CodeAction> {
1762 self.visit_typed_module(&self.module.ast);
1763
1764 // We only want to trigger the action if we're over one of the definition
1765 // which is lacking some annotations in the module
1766 if !self.is_hovering_definition_requiring_annotations {
1767 return vec![];
1768 };
1769
1770 let mut action = Vec::with_capacity(1);
1771 CodeActionBuilder::new("Annotate all top level definitions")
1772 .kind(CodeActionKind::RefactorRewrite)
1773 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1774 .preferred(false)
1775 .push_to(&mut action);
1776 action
1777 }
1778}
1779
1780impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> {
1781 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1782 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1783
1784 // We don't need to add an annotation if there already is one
1785 if constant.annotation.is_some() {
1786 return;
1787 }
1788
1789 // We're hovering definition which needs some annotations
1790 if overlaps(code_action_range, self.params.range) {
1791 self.is_hovering_definition_requiring_annotations = true;
1792 }
1793
1794 self.edits.insert(
1795 constant.name_location.end,
1796 format!(
1797 ": {}",
1798 // Create new printer to ignore type variables from other definitions
1799 Printer::new_without_type_variables(&self.module.ast.names)
1800 .print_type(&constant.type_)
1801 ),
1802 );
1803 }
1804
1805 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1806 // Don't annotate already annotated arguments
1807 let arguments_to_annotate = fun
1808 .arguments
1809 .iter()
1810 .filter(|argument| argument.annotation.is_none())
1811 .collect::<Vec<_>>();
1812 let needs_return_annotation = fun.return_annotation.is_none();
1813
1814 if arguments_to_annotate.is_empty() && !needs_return_annotation {
1815 return;
1816 }
1817
1818 let code_action_range = self.edits.src_span_to_lsp_range(fun.location);
1819 if overlaps(code_action_range, self.params.range) {
1820 self.is_hovering_definition_requiring_annotations = true;
1821 }
1822
1823 // Create new printer to ignore type variables from other definitions
1824 let mut printer = Printer::new_without_type_variables(&self.module.ast.names);
1825 collect_type_variables(&mut printer, fun);
1826
1827 // Annotate each argument separately
1828 for argument in arguments_to_annotate {
1829 self.edits.insert(
1830 argument.location.end,
1831 format!(": {}", printer.print_type(&argument.type_)),
1832 );
1833 }
1834
1835 // Annotate the return type if it isn't already annotated
1836 if needs_return_annotation {
1837 self.edits.insert(
1838 fun.location.end,
1839 format!(" -> {}", printer.print_type(&fun.return_type)),
1840 );
1841 }
1842 }
1843}
1844
1845struct TypeVariableCollector<'a, 'b> {
1846 printer: &'a mut Printer<'b>,
1847}
1848
1849/// Collect type variables defined within a function and register them for a
1850/// `Printer`
1851fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) {
1852 TypeVariableCollector { printer }.visit_typed_function(function);
1853}
1854
1855impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> {
1856 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) {
1857 // Register this type variable so that we don't duplicate names when
1858 // adding annotations.
1859 self.printer.register_type_variable(name.clone());
1860 }
1861}
1862
1863pub struct QualifiedConstructor<'a> {
1864 import: &'a Import<EcoString>,
1865 used_name: EcoString,
1866 constructor: EcoString,
1867 layer: ast::Layer,
1868}
1869
1870impl QualifiedConstructor<'_> {
1871 fn constructor_import(&self) -> String {
1872 if self.layer.is_value() {
1873 self.constructor.to_string()
1874 } else {
1875 format!("type {}", self.constructor)
1876 }
1877 }
1878}
1879
1880pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1881 module: &'a Module,
1882 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1883 params: &'a CodeActionParams,
1884 line_numbers: &'a LineNumbers,
1885 qualified_constructor: Option<QualifiedConstructor<'a>>,
1886}
1887
1888impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1889 fn new(
1890 module: &'a Module,
1891 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1892 params: &'a CodeActionParams,
1893 line_numbers: &'a LineNumbers,
1894 ) -> Self {
1895 Self {
1896 module,
1897 compiler,
1898 params,
1899 line_numbers,
1900 qualified_constructor: None,
1901 }
1902 }
1903
1904 fn get_module_import(
1905 &self,
1906 module_name: &EcoString,
1907 constructor: &EcoString,
1908 layer: ast::Layer,
1909 ) -> Option<&'a Import<EcoString>> {
1910 let mut matching_import = None;
1911
1912 for import in &self.module.ast.definitions.imports {
1913 if import.used_name().as_deref() == Some(module_name)
1914 && let Some(module) = self.compiler.get_module_interface(&import.module)
1915 {
1916 // If the import is the one we're referring to, we see if the
1917 // referred module exports the type/value we are trying to
1918 // unqualify: we don't want to offer the action indiscriminately if
1919 // it would generate invalid code!
1920 let module_exports_constructor = match layer {
1921 ast::Layer::Value => module.get_importable_value(constructor).is_some(),
1922 ast::Layer::Type => module.get_importable_type(constructor).is_some(),
1923 };
1924 if module_exports_constructor {
1925 matching_import = Some(import);
1926 }
1927 } else {
1928 // If the import refers to another module we still want to check
1929 // if in its unqualified import list there is a name that's equal
1930 // to the one we're trying to unqualify. In this case we can't
1931 // offer the action as it would generate invalid code.
1932 //
1933 // For example:
1934 // ```gleam
1935 // import wibble.{Some}
1936 // import option
1937 //
1938 // pub fn something() {
1939 // option.Some(1)
1940 // ^^^^ We can't unqualify this because `Some` is already
1941 // imported unqualified from the `wibble` module
1942 // }
1943 // ```
1944 //
1945 let imported = match layer {
1946 ast::Layer::Value => &import.unqualified_values,
1947 ast::Layer::Type => &import.unqualified_types,
1948 };
1949 let constructor_already_imported_by_other_module = imported
1950 .iter()
1951 .any(|value| value.used_name() == constructor);
1952
1953 if constructor_already_imported_by_other_module {
1954 return None;
1955 }
1956 }
1957 }
1958
1959 matching_import
1960 }
1961}
1962
1963impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> {
1964 fn visit_type_ast_constructor(
1965 &mut self,
1966 location: &'ast SrcSpan,
1967 name: &'ast TypeAstConstructorName,
1968 arguments: &'ast [ast::TypeAst],
1969 arguments_types: Option<Vec<Arc<Type>>>,
1970 ) {
1971 let range = src_span_to_lsp_range(*location, self.line_numbers);
1972 if within(self.params.range, range)
1973 && let Some(module_alias) = name.module_name()
1974 && let Some(name) = name.name()
1975 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type)
1976 {
1977 self.qualified_constructor = Some(QualifiedConstructor {
1978 import,
1979 used_name: module_alias.clone(),
1980 constructor: name.clone(),
1981 layer: ast::Layer::Type,
1982 });
1983 }
1984 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
1985 }
1986
1987 fn visit_typed_expr_module_select(
1988 &mut self,
1989 location: &'ast SrcSpan,
1990 field_start: &'ast u32,
1991 type_: &'ast Arc<Type>,
1992 label: &'ast EcoString,
1993 module_name: &'ast EcoString,
1994 module_alias: &'ast EcoString,
1995 constructor: &'ast ModuleValueConstructor,
1996 ) {
1997 // When hovering over a Record Value Constructor, we want to expand the source span to
1998 // include the module name:
1999 // option.Some
2000 // ↑
2001 // This allows us to offer a code action when hovering over the module name.
2002 let range = src_span_to_lsp_range(*location, self.line_numbers);
2003 if within(self.params.range, range)
2004 && let ModuleValueConstructor::Record {
2005 name: constructor_name,
2006 ..
2007 } = constructor
2008 && let Some(import) =
2009 self.get_module_import(module_alias, constructor_name, ast::Layer::Value)
2010 {
2011 self.qualified_constructor = Some(QualifiedConstructor {
2012 import,
2013 used_name: module_alias.clone(),
2014 constructor: constructor_name.clone(),
2015 layer: ast::Layer::Value,
2016 });
2017 }
2018 ast::visit::visit_typed_expr_module_select(
2019 self,
2020 location,
2021 field_start,
2022 type_,
2023 label,
2024 module_name,
2025 module_alias,
2026 constructor,
2027 )
2028 }
2029
2030 fn visit_typed_pattern_constructor(
2031 &mut self,
2032 location: &'ast SrcSpan,
2033 name_location: &'ast SrcSpan,
2034 name: &'ast EcoString,
2035 arguments: &'ast Vec<CallArg<TypedPattern>>,
2036 module: &'ast Option<(EcoString, SrcSpan)>,
2037 constructor: &'ast Inferred<type_::PatternConstructor>,
2038 spread: &'ast Option<SrcSpan>,
2039 type_: &'ast Arc<Type>,
2040 ) {
2041 let range = src_span_to_lsp_range(*location, self.line_numbers);
2042 if within(self.params.range, range)
2043 && let Some((module_alias, _)) = module
2044 && let Inferred::Known(_) = constructor
2045 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2046 {
2047 self.qualified_constructor = Some(QualifiedConstructor {
2048 import,
2049 used_name: module_alias.clone(),
2050 constructor: name.clone(),
2051 layer: ast::Layer::Value,
2052 });
2053 }
2054 ast::visit::visit_typed_pattern_constructor(
2055 self,
2056 location,
2057 name_location,
2058 name,
2059 arguments,
2060 module,
2061 constructor,
2062 spread,
2063 type_,
2064 );
2065 }
2066
2067 fn visit_typed_constant_record(
2068 &mut self,
2069 location: &'ast SrcSpan,
2070 module: &'ast Option<(EcoString, SrcSpan)>,
2071 name: &'ast EcoString,
2072 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2073 type_: &'ast Arc<Type>,
2074 field_map: &'ast Inferred<FieldMap>,
2075 record_constructor: &'ast Option<Box<ValueConstructor>>,
2076 ) {
2077 let range = src_span_to_lsp_range(*location, self.line_numbers);
2078 if within(self.params.range, range)
2079 && let Some((module_alias, _)) = module
2080 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2081 {
2082 self.qualified_constructor = Some(QualifiedConstructor {
2083 import,
2084 used_name: module_alias.clone(),
2085 constructor: name.clone(),
2086 layer: ast::Layer::Value,
2087 });
2088 }
2089 ast::visit::visit_typed_constant_record(
2090 self,
2091 location,
2092 module,
2093 name,
2094 arguments,
2095 type_,
2096 field_map,
2097 record_constructor,
2098 );
2099 }
2100
2101 fn visit_typed_constant_var(
2102 &mut self,
2103 location: &'ast SrcSpan,
2104 module: &'ast Option<(EcoString, SrcSpan)>,
2105 name: &'ast EcoString,
2106 constructor: &'ast Option<Box<ValueConstructor>>,
2107 type_: &'ast Arc<Type>,
2108 ) {
2109 let range = src_span_to_lsp_range(*location, self.line_numbers);
2110 if within(self.params.range, range)
2111 && let Some((module_alias, _)) = module
2112 && let Some(constructor) = constructor
2113 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant
2114 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2115 {
2116 self.qualified_constructor = Some(QualifiedConstructor {
2117 import,
2118 used_name: module_alias.clone(),
2119 constructor: name.clone(),
2120 layer: ast::Layer::Value,
2121 });
2122 }
2123 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2124 }
2125}
2126
2127pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
2128 module: &'a Module,
2129 params: &'a CodeActionParams,
2130 edits: TextEdits<'a>,
2131 qualified_constructor: QualifiedConstructor<'a>,
2132}
2133
2134impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
2135 pub fn new(
2136 module: &'a Module,
2137 params: &'a CodeActionParams,
2138 line_numbers: &'a LineNumbers,
2139 qualified_constructor: QualifiedConstructor<'a>,
2140 ) -> Self {
2141 Self {
2142 module,
2143 params,
2144 edits: TextEdits::new(line_numbers),
2145 qualified_constructor,
2146 }
2147 }
2148
2149 pub fn code_actions(mut self) -> Vec<CodeAction> {
2150 self.visit_typed_module(&self.module.ast);
2151 if self.edits.edits.is_empty() {
2152 return vec![];
2153 }
2154 self.edit_import();
2155 let mut action = Vec::with_capacity(1);
2156 CodeActionBuilder::new(&format!(
2157 "Unqualify {}.{}",
2158 self.qualified_constructor.used_name, self.qualified_constructor.constructor
2159 ))
2160 .kind(CodeActionKind::Refactor)
2161 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2162 .preferred(false)
2163 .push_to(&mut action);
2164 action
2165 }
2166
2167 fn remove_module_qualifier(&mut self, location: SrcSpan) {
2168 self.edits.delete(SrcSpan {
2169 start: location.start,
2170 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
2171 })
2172 }
2173
2174 fn edit_import(&mut self) {
2175 let QualifiedConstructor {
2176 constructor,
2177 layer,
2178 import,
2179 ..
2180 } = &self.qualified_constructor;
2181 let is_imported = if layer.is_value() {
2182 import
2183 .unqualified_values
2184 .iter()
2185 .any(|value| value.used_name() == constructor)
2186 } else {
2187 import
2188 .unqualified_types
2189 .iter()
2190 .any(|type_| type_.used_name() == constructor)
2191 };
2192 if is_imported {
2193 return;
2194 }
2195 let (insert_pos, new_text) = edits::insert_unqualified_import(
2196 import,
2197 &self.module.code,
2198 self.qualified_constructor.constructor_import(),
2199 );
2200 let span = SrcSpan::new(insert_pos, insert_pos);
2201 self.edits.replace(span, new_text);
2202 }
2203}
2204
2205impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
2206 fn visit_type_ast_constructor(
2207 &mut self,
2208 location: &'ast SrcSpan,
2209 name: &'ast TypeAstConstructorName,
2210 arguments: &'ast [ast::TypeAst],
2211 arguments_types: Option<Vec<Arc<Type>>>,
2212 ) {
2213 if let Some(module_name) = name.module_name()
2214 && let Some(name) = name.name()
2215 {
2216 let QualifiedConstructor {
2217 used_name,
2218 constructor,
2219 layer,
2220 ..
2221 } = &self.qualified_constructor;
2222
2223 if !layer.is_value() && used_name == module_name && name == constructor {
2224 self.remove_module_qualifier(*location);
2225 }
2226 }
2227 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2228 }
2229
2230 fn visit_typed_expr_module_select(
2231 &mut self,
2232 location: &'ast SrcSpan,
2233 field_start: &'ast u32,
2234 type_: &'ast Arc<Type>,
2235 label: &'ast EcoString,
2236 module_name: &'ast EcoString,
2237 module_alias: &'ast EcoString,
2238 constructor: &'ast ModuleValueConstructor,
2239 ) {
2240 if let ModuleValueConstructor::Record { name, .. } = constructor {
2241 let QualifiedConstructor {
2242 used_name,
2243 constructor,
2244 layer,
2245 ..
2246 } = &self.qualified_constructor;
2247
2248 if layer.is_value() && used_name == module_alias && name == constructor {
2249 self.remove_module_qualifier(*location);
2250 }
2251 }
2252 ast::visit::visit_typed_expr_module_select(
2253 self,
2254 location,
2255 field_start,
2256 type_,
2257 label,
2258 module_name,
2259 module_alias,
2260 constructor,
2261 )
2262 }
2263
2264 fn visit_typed_pattern_constructor(
2265 &mut self,
2266 location: &'ast SrcSpan,
2267 name_location: &'ast SrcSpan,
2268 name: &'ast EcoString,
2269 arguments: &'ast Vec<CallArg<TypedPattern>>,
2270 module: &'ast Option<(EcoString, SrcSpan)>,
2271 constructor: &'ast Inferred<type_::PatternConstructor>,
2272 spread: &'ast Option<SrcSpan>,
2273 type_: &'ast Arc<Type>,
2274 ) {
2275 if let Some((module_alias, _)) = module
2276 && let Inferred::Known(_) = constructor
2277 {
2278 let QualifiedConstructor {
2279 used_name,
2280 constructor,
2281 layer,
2282 ..
2283 } = &self.qualified_constructor;
2284
2285 if layer.is_value() && used_name == module_alias && name == constructor {
2286 self.remove_module_qualifier(*location);
2287 }
2288 }
2289 ast::visit::visit_typed_pattern_constructor(
2290 self,
2291 location,
2292 name_location,
2293 name,
2294 arguments,
2295 module,
2296 constructor,
2297 spread,
2298 type_,
2299 );
2300 }
2301
2302 fn visit_typed_constant_record(
2303 &mut self,
2304 location: &'ast SrcSpan,
2305 module: &'ast Option<(EcoString, SrcSpan)>,
2306 name: &'ast EcoString,
2307 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2308 type_: &'ast Arc<Type>,
2309 field_map: &'ast Inferred<FieldMap>,
2310 record_constructor: &'ast Option<Box<ValueConstructor>>,
2311 ) {
2312 if let Some((module_alias, _)) = module {
2313 let QualifiedConstructor {
2314 used_name,
2315 constructor,
2316 layer,
2317 ..
2318 } = &self.qualified_constructor;
2319
2320 if layer.is_value() && used_name == module_alias && name == constructor {
2321 self.remove_module_qualifier(*location);
2322 }
2323 }
2324 ast::visit::visit_typed_constant_record(
2325 self,
2326 location,
2327 module,
2328 name,
2329 arguments,
2330 type_,
2331 field_map,
2332 record_constructor,
2333 );
2334 }
2335
2336 fn visit_typed_constant_var(
2337 &mut self,
2338 location: &'ast SrcSpan,
2339 module: &'ast Option<(EcoString, SrcSpan)>,
2340 name: &'ast EcoString,
2341 constructor: &'ast Option<Box<ValueConstructor>>,
2342 type_: &'ast Arc<Type>,
2343 ) {
2344 if let Some((module_alias, _)) = module {
2345 let QualifiedConstructor {
2346 used_name,
2347 constructor: wanted_constructor,
2348 layer,
2349 ..
2350 } = &self.qualified_constructor;
2351
2352 if layer.is_value() && used_name == module_alias && name == wanted_constructor {
2353 self.remove_module_qualifier(*location);
2354 }
2355 }
2356 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2357 }
2358}
2359
2360pub fn code_action_convert_qualified_constructor_to_unqualified<IO>(
2361 module: &Module,
2362 compiler: &LspProjectCompiler<FileSystemProxy<IO>>,
2363 line_numbers: &LineNumbers,
2364 params: &CodeActionParams,
2365 actions: &mut Vec<CodeAction>,
2366) {
2367 let mut first_pass =
2368 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers);
2369 first_pass.visit_typed_module(&module.ast);
2370 let Some(qualified_constructor) = first_pass.qualified_constructor else {
2371 return;
2372 };
2373 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
2374 module,
2375 params,
2376 line_numbers,
2377 qualified_constructor,
2378 );
2379 let new_actions = second_pass.code_actions();
2380 actions.extend(new_actions);
2381}
2382
2383struct UnqualifiedConstructor<'a> {
2384 module_name: EcoString,
2385 constructor: &'a ast::UnqualifiedImport,
2386 layer: ast::Layer,
2387}
2388
2389struct UnqualifiedToQualifiedImportFirstPass<'a> {
2390 module: &'a Module,
2391 params: &'a CodeActionParams,
2392 line_numbers: &'a LineNumbers,
2393 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
2394}
2395
2396impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
2397 fn new(
2398 module: &'a Module,
2399 params: &'a CodeActionParams,
2400 line_numbers: &'a LineNumbers,
2401 ) -> Self {
2402 Self {
2403 module,
2404 params,
2405 line_numbers,
2406 unqualified_constructor: None,
2407 }
2408 }
2409
2410 fn get_module_import_from_value_constructor(
2411 &mut self,
2412 module_name: &EcoString,
2413 constructor_name: &EcoString,
2414 ) {
2415 self.unqualified_constructor = self
2416 .module
2417 .ast
2418 .definitions
2419 .imports
2420 .iter()
2421 .filter(|import| import.module == *module_name)
2422 .find_map(|import| {
2423 import
2424 .unqualified_values
2425 .iter()
2426 .find(|value| value.used_name() == constructor_name)
2427 .and_then(|value| {
2428 Some(UnqualifiedConstructor {
2429 constructor: value,
2430 module_name: import.used_name()?,
2431 layer: ast::Layer::Value,
2432 })
2433 })
2434 })
2435 }
2436
2437 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
2438 self.unqualified_constructor =
2439 self.module
2440 .ast
2441 .definitions
2442 .imports
2443 .iter()
2444 .find_map(|import| {
2445 if let Some(ty) = import
2446 .unqualified_types
2447 .iter()
2448 .find(|ty| ty.used_name() == constructor_name)
2449 {
2450 return Some(UnqualifiedConstructor {
2451 constructor: ty,
2452 module_name: import.used_name()?,
2453 layer: ast::Layer::Type,
2454 });
2455 }
2456 None
2457 })
2458 }
2459}
2460
2461impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
2462 fn visit_type_ast_constructor(
2463 &mut self,
2464 location: &'ast SrcSpan,
2465 name: &'ast TypeAstConstructorName,
2466 arguments: &'ast [ast::TypeAst],
2467 arguments_types: Option<Vec<Arc<Type>>>,
2468 ) {
2469 if !name.is_qualified()
2470 && let Some(name) = name.name()
2471 && within(
2472 self.params.range,
2473 src_span_to_lsp_range(*location, self.line_numbers),
2474 )
2475 {
2476 self.get_module_import_from_type_constructor(name);
2477 }
2478
2479 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2480 }
2481
2482 fn visit_typed_expr_var(
2483 &mut self,
2484 location: &'ast SrcSpan,
2485 constructor: &'ast ValueConstructor,
2486 name: &'ast EcoString,
2487 ) {
2488 let range = src_span_to_lsp_range(*location, self.line_numbers);
2489 if within(self.params.range, range)
2490 && let Some(module_name) = match &constructor.variant {
2491 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2492 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2493 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2494
2495 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2496 }
2497 {
2498 self.get_module_import_from_value_constructor(module_name, name);
2499 }
2500 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2501 }
2502
2503 fn visit_typed_pattern_constructor(
2504 &mut self,
2505 location: &'ast SrcSpan,
2506 name_location: &'ast SrcSpan,
2507 name: &'ast EcoString,
2508 arguments: &'ast Vec<CallArg<TypedPattern>>,
2509 module: &'ast Option<(EcoString, SrcSpan)>,
2510 constructor: &'ast Inferred<type_::PatternConstructor>,
2511 spread: &'ast Option<SrcSpan>,
2512 type_: &'ast Arc<Type>,
2513 ) {
2514 if module.is_none()
2515 && within(
2516 self.params.range,
2517 src_span_to_lsp_range(*location, self.line_numbers),
2518 )
2519 && let Inferred::Known(constructor) = constructor
2520 {
2521 self.get_module_import_from_value_constructor(&constructor.module, name);
2522 }
2523
2524 ast::visit::visit_typed_pattern_constructor(
2525 self,
2526 location,
2527 name_location,
2528 name,
2529 arguments,
2530 module,
2531 constructor,
2532 spread,
2533 type_,
2534 );
2535 }
2536
2537 fn visit_typed_constant_record(
2538 &mut self,
2539 location: &'ast SrcSpan,
2540 module: &'ast Option<(EcoString, SrcSpan)>,
2541 name: &'ast EcoString,
2542 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2543 type_: &'ast Arc<Type>,
2544 field_map: &'ast Inferred<FieldMap>,
2545 record_constructor: &'ast Option<Box<ValueConstructor>>,
2546 ) {
2547 if module.is_none()
2548 && within(
2549 self.params.range,
2550 src_span_to_lsp_range(*location, self.line_numbers),
2551 )
2552 && let Some(record_constructor) = record_constructor
2553 && let Some(module_name) = match &record_constructor.variant {
2554 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2555 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2556 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2557
2558 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2559 }
2560 {
2561 self.get_module_import_from_value_constructor(module_name, name);
2562 }
2563 ast::visit::visit_typed_constant_record(
2564 self,
2565 location,
2566 module,
2567 name,
2568 arguments,
2569 type_,
2570 field_map,
2571 record_constructor,
2572 );
2573 }
2574
2575 fn visit_typed_constant_var(
2576 &mut self,
2577 location: &'ast SrcSpan,
2578 module: &'ast Option<(EcoString, SrcSpan)>,
2579 name: &'ast EcoString,
2580 constructor: &'ast Option<Box<ValueConstructor>>,
2581 type_: &'ast Arc<Type>,
2582 ) {
2583 if module.is_none()
2584 && within(
2585 self.params.range,
2586 src_span_to_lsp_range(*location, self.line_numbers),
2587 )
2588 && let Some(constructor) = constructor
2589 && let Some(module_name) = match &constructor.variant {
2590 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2591 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2592 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2593
2594 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2595 }
2596 {
2597 self.get_module_import_from_value_constructor(module_name, name);
2598 }
2599 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2600 }
2601}
2602
2603struct UnqualifiedToQualifiedImportSecondPass<'a> {
2604 module: &'a Module,
2605 params: &'a CodeActionParams,
2606 edits: TextEdits<'a>,
2607 unqualified_constructor: UnqualifiedConstructor<'a>,
2608}
2609
2610impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2611 pub fn new(
2612 module: &'a Module,
2613 params: &'a CodeActionParams,
2614 line_numbers: &'a LineNumbers,
2615 unqualified_constructor: UnqualifiedConstructor<'a>,
2616 ) -> Self {
2617 Self {
2618 module,
2619 params,
2620 edits: TextEdits::new(line_numbers),
2621 unqualified_constructor,
2622 }
2623 }
2624
2625 fn add_module_qualifier(&mut self, location: SrcSpan) {
2626 let src_span = SrcSpan::new(
2627 location.start,
2628 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
2629 );
2630
2631 self.edits.replace(
2632 src_span,
2633 format!(
2634 "{}.{}",
2635 self.unqualified_constructor.module_name,
2636 self.unqualified_constructor.constructor.name
2637 ),
2638 );
2639 }
2640
2641 pub fn code_actions(mut self) -> Vec<CodeAction> {
2642 self.visit_typed_module(&self.module.ast);
2643 if self.edits.edits.is_empty() {
2644 return vec![];
2645 }
2646 self.edit_import();
2647 let mut action = Vec::with_capacity(1);
2648 let UnqualifiedConstructor {
2649 module_name,
2650 constructor,
2651 ..
2652 } = self.unqualified_constructor;
2653 CodeActionBuilder::new(&format!(
2654 "Qualify {} as {}.{}",
2655 constructor.used_name(),
2656 module_name,
2657 constructor.name,
2658 ))
2659 .kind(CodeActionKind::Refactor)
2660 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2661 .preferred(false)
2662 .push_to(&mut action);
2663 action
2664 }
2665
2666 fn edit_import(&mut self) {
2667 let UnqualifiedConstructor {
2668 constructor:
2669 ast::UnqualifiedImport {
2670 location: constructor_import_span,
2671 ..
2672 },
2673 ..
2674 } = self.unqualified_constructor;
2675
2676 let mut last_char_pos = constructor_import_span.end as usize;
2677 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2678 last_char_pos += 1;
2679 }
2680 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2681 last_char_pos += 1;
2682 }
2683 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2684 last_char_pos += 1;
2685 }
2686
2687 self.edits.delete(SrcSpan::new(
2688 constructor_import_span.start,
2689 last_char_pos as u32,
2690 ));
2691 }
2692}
2693
2694impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2695 fn visit_type_ast_constructor(
2696 &mut self,
2697 location: &'ast SrcSpan,
2698 name: &'ast TypeAstConstructorName,
2699 arguments: &'ast [ast::TypeAst],
2700 arguments_types: Option<Vec<Arc<Type>>>,
2701 ) {
2702 if !name.is_qualified()
2703 && let Some(name) = name.name()
2704 {
2705 let UnqualifiedConstructor {
2706 constructor, layer, ..
2707 } = &self.unqualified_constructor;
2708 if !layer.is_value() && constructor.used_name() == name {
2709 self.add_module_qualifier(*location);
2710 }
2711 }
2712 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2713 }
2714
2715 fn visit_typed_expr_var(
2716 &mut self,
2717 location: &'ast SrcSpan,
2718 constructor: &'ast ValueConstructor,
2719 name: &'ast EcoString,
2720 ) {
2721 let UnqualifiedConstructor {
2722 constructor: wanted_constructor,
2723 layer,
2724 ..
2725 } = &self.unqualified_constructor;
2726
2727 if layer.is_value()
2728 && wanted_constructor.used_name() == name
2729 && !constructor.is_local_variable()
2730 {
2731 self.add_module_qualifier(*location);
2732 }
2733 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2734 }
2735
2736 fn visit_typed_pattern_constructor(
2737 &mut self,
2738 location: &'ast SrcSpan,
2739 name_location: &'ast SrcSpan,
2740 name: &'ast EcoString,
2741 arguments: &'ast Vec<CallArg<TypedPattern>>,
2742 module: &'ast Option<(EcoString, SrcSpan)>,
2743 constructor: &'ast Inferred<type_::PatternConstructor>,
2744 spread: &'ast Option<SrcSpan>,
2745 type_: &'ast Arc<Type>,
2746 ) {
2747 if module.is_none() {
2748 let UnqualifiedConstructor {
2749 constructor: wanted_constructor,
2750 layer,
2751 ..
2752 } = &self.unqualified_constructor;
2753 if layer.is_value() && wanted_constructor.used_name() == name {
2754 self.add_module_qualifier(*location);
2755 }
2756 }
2757 ast::visit::visit_typed_pattern_constructor(
2758 self,
2759 location,
2760 name_location,
2761 name,
2762 arguments,
2763 module,
2764 constructor,
2765 spread,
2766 type_,
2767 );
2768 }
2769
2770 fn visit_typed_constant_record(
2771 &mut self,
2772 location: &'ast SrcSpan,
2773 module: &'ast Option<(EcoString, SrcSpan)>,
2774 name: &'ast EcoString,
2775 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2776 type_: &'ast Arc<Type>,
2777 field_map: &'ast Inferred<FieldMap>,
2778 record_constructor: &'ast Option<Box<ValueConstructor>>,
2779 ) {
2780 if module.is_none() {
2781 let UnqualifiedConstructor {
2782 constructor: wanted_constructor,
2783 layer,
2784 ..
2785 } = &self.unqualified_constructor;
2786 if layer.is_value() && wanted_constructor.used_name() == name {
2787 self.add_module_qualifier(*location);
2788 }
2789 }
2790 ast::visit::visit_typed_constant_record(
2791 self,
2792 location,
2793 module,
2794 name,
2795 arguments,
2796 type_,
2797 field_map,
2798 record_constructor,
2799 );
2800 }
2801
2802 fn visit_typed_constant_var(
2803 &mut self,
2804 location: &'ast SrcSpan,
2805 module: &'ast Option<(EcoString, SrcSpan)>,
2806 name: &'ast EcoString,
2807 constructor: &'ast Option<Box<ValueConstructor>>,
2808 type_: &'ast Arc<Type>,
2809 ) {
2810 if module.is_none() {
2811 let UnqualifiedConstructor {
2812 constructor: wanted_constructor,
2813 layer,
2814 ..
2815 } = &self.unqualified_constructor;
2816 if layer.is_value() && wanted_constructor.used_name() == name {
2817 self.add_module_qualifier(*location);
2818 }
2819 }
2820 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2821 }
2822}
2823
2824pub fn code_action_convert_unqualified_constructor_to_qualified(
2825 module: &Module,
2826 line_numbers: &LineNumbers,
2827 params: &CodeActionParams,
2828 actions: &mut Vec<CodeAction>,
2829) {
2830 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2831 first_pass.visit_typed_module(&module.ast);
2832 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2833 return;
2834 };
2835 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2836 module,
2837 params,
2838 line_numbers,
2839 unqualified_constructor,
2840 );
2841 let new_actions = second_pass.code_actions();
2842 actions.extend(new_actions);
2843}
2844
2845/// Builder for code action to apply the convert from use action, turning a use
2846/// expression into a regular function call.
2847///
2848pub struct ConvertFromUse<'a> {
2849 module: &'a Module,
2850 params: &'a CodeActionParams,
2851 edits: TextEdits<'a>,
2852 selected_use: Option<&'a TypedUse>,
2853}
2854
2855impl<'a> ConvertFromUse<'a> {
2856 pub fn new(
2857 module: &'a Module,
2858 line_numbers: &'a LineNumbers,
2859 params: &'a CodeActionParams,
2860 ) -> Self {
2861 Self {
2862 module,
2863 params,
2864 edits: TextEdits::new(line_numbers),
2865 selected_use: None,
2866 }
2867 }
2868
2869 pub fn code_actions(mut self) -> Vec<CodeAction> {
2870 self.visit_typed_module(&self.module.ast);
2871
2872 let Some(use_) = self.selected_use else {
2873 return vec![];
2874 };
2875
2876 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else {
2877 return vec![];
2878 };
2879
2880 // If the use callback we're desugaring is using labels, that means we
2881 // have to add the last argument's label when writing the callback;
2882 // otherwise, it would result in invalid code.
2883 //
2884 // use acc, item <- list.fold(over: list, from: 1)
2885 // todo
2886 //
2887 // Needs to be rewritten as:
2888 //
2889 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2890 // ^^^^^ We cannot forget to add this label back!
2891 //
2892 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) {
2893 fun.field_map()
2894 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned())
2895 .map(|label| eco_format!("{label}: "))
2896 .unwrap_or(EcoString::from(""))
2897 } else {
2898 EcoString::from("")
2899 };
2900
2901 // The use callback is not necessarily the last argument. If you have
2902 // the following function: `wibble(a a, b b) { todo }`
2903 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2904 // is going to be the use callback, not the last one!
2905 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback());
2906 let Some(CallArg {
2907 implicit: Some(ImplicitCallArgOrigin::Use),
2908 value: TypedExpr::Fn { body, type_, .. },
2909 ..
2910 }) = use_callback
2911 else {
2912 return vec![];
2913 };
2914
2915 // If there's arguments on the left hand side of the function we extract
2916 // those so we can paste them back as the anonymous function arguments.
2917 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2918 let assignments_range =
2919 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2920 self.module
2921 .code
2922 .get(assignments_range)
2923 .expect("use assignments")
2924 } else {
2925 ""
2926 };
2927
2928 // We first delete everything on the left hand side of use and the use
2929 // arrow.
2930 self.edits.delete(SrcSpan {
2931 start: use_.location.start,
2932 end: use_.right_hand_side_location.start,
2933 });
2934
2935 let use_line_end = use_.right_hand_side_location.end;
2936 let use_rhs_function_has_some_explicit_arguments = arguments
2937 .iter()
2938 .filter(|argument| !argument.is_use_implicit_callback())
2939 .peekable()
2940 .peek()
2941 .is_some();
2942
2943 let use_rhs_function_ends_with_closed_parentheses = self
2944 .module
2945 .code
2946 .get(use_line_end as usize - 1..use_line_end as usize)
2947 == Some(")");
2948
2949 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit());
2950 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2951
2952 // This is the piece of code between the end of the last argument and
2953 // the end of the use_expression:
2954 //
2955 // use <- wibble(a, b, )
2956 // ^^^^^ This piece right here, from `,` included
2957 // up to `)` excluded.
2958 //
2959 let text_after_last_argument = self
2960 .module
2961 .code
2962 .get(last_arg_end as usize..use_line_end as usize - 1);
2963 let use_rhs_has_comma_after_last_argument =
2964 text_after_last_argument.is_some_and(|code| code.contains(','));
2965 let needs_space_before_callback =
2966 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2967
2968 if use_rhs_function_ends_with_closed_parentheses {
2969 // If the function on the right hand side of use ends with a closed
2970 // parentheses then we have to remove it and add it later at the end
2971 // of the anonymous function we're inserting.
2972 //
2973 // use <- wibble()
2974 // ^ To add the fn() we need to first remove this
2975 //
2976 // So here we write over the last closed parentheses to remove it.
2977 let callback_start = format!("{callback_label}fn({assignments}) {{");
2978 self.edits.replace(
2979 SrcSpan {
2980 start: use_line_end - 1,
2981 end: use_line_end,
2982 },
2983 // If the function on the rhs of use has other orguments besides
2984 // the implicit fn expression then we need to put a comma after
2985 // the last argument.
2986 if use_rhs_function_has_some_explicit_arguments
2987 && !use_rhs_has_comma_after_last_argument
2988 {
2989 format!(", {callback_start}")
2990 } else if needs_space_before_callback {
2991 format!(" {callback_start}")
2992 } else {
2993 callback_start
2994 },
2995 )
2996 } else {
2997 // On the other hand, if the function on the right hand side doesn't
2998 // end with a closed parenthese then we have to manually add it.
2999 //
3000 // use <- wibble
3001 // ^ No parentheses
3002 //
3003 self.edits
3004 .insert(use_line_end, format!("(fn({assignments}) {{"))
3005 };
3006
3007 // Then we have to increase indentation for all the lines of the use
3008 // body.
3009 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
3010 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
3011
3012 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
3013 self.edits.edits.push(TextEdit {
3014 range: Range {
3015 start: Position { line, character: 0 },
3016 end: Position { line, character: 0 },
3017 },
3018 new_text: " ".to_string(),
3019 })
3020 }
3021
3022 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
3023 self.edits.insert(
3024 use_.call.location().end,
3025 format!("\n{final_line_indentation}}})"),
3026 );
3027
3028 let mut action = Vec::with_capacity(1);
3029 CodeActionBuilder::new("Convert from `use`")
3030 .kind(CodeActionKind::RefactorRewrite)
3031 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3032 .preferred(false)
3033 .push_to(&mut action);
3034 action
3035 }
3036}
3037
3038impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
3039 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3040 let use_range = self.edits.src_span_to_lsp_range(use_.location);
3041
3042 // If the use expression is using patterns that are not just variable
3043 // assignments then we can't automatically rewrite it as it would result
3044 // in a syntax error as we can't pattern match in an anonymous function
3045 // head.
3046 // At the same time we can't safely add bindings inside the anonymous
3047 // function body by picking placeholder names as we'd risk shadowing
3048 // variables coming from the outer scope.
3049 // So we just skip those use expressions we can't safely rewrite!
3050 if within(self.params.range, use_range)
3051 && use_
3052 .assignments
3053 .iter()
3054 .all(|assignment| assignment.pattern.is_variable())
3055 {
3056 self.selected_use = Some(use_);
3057 }
3058
3059 // We still want to visit the use expression so that we always end up
3060 // picking the innermost, most relevant use under the cursor.
3061 self.visit_typed_expr(&use_.call);
3062 }
3063}
3064
3065/// Builder for code action to apply the convert to use action.
3066///
3067pub struct ConvertToUse<'a> {
3068 module: &'a Module,
3069 params: &'a CodeActionParams,
3070 edits: TextEdits<'a>,
3071 selected_call: Option<CallLocations>,
3072}
3073
3074/// All the locations we'll need to transform a function call into a use
3075/// expression.
3076///
3077struct CallLocations {
3078 call_span: SrcSpan,
3079 called_function_span: SrcSpan,
3080 callback_arguments_span: Option<SrcSpan>,
3081 arg_before_callback_span: Option<SrcSpan>,
3082 callback_body_span: SrcSpan,
3083}
3084
3085impl<'a> ConvertToUse<'a> {
3086 pub fn new(
3087 module: &'a Module,
3088 line_numbers: &'a LineNumbers,
3089 params: &'a CodeActionParams,
3090 ) -> Self {
3091 Self {
3092 module,
3093 params,
3094 edits: TextEdits::new(line_numbers),
3095 selected_call: None,
3096 }
3097 }
3098
3099 pub fn code_actions(mut self) -> Vec<CodeAction> {
3100 self.visit_typed_module(&self.module.ast);
3101
3102 let Some(CallLocations {
3103 call_span,
3104 called_function_span,
3105 callback_arguments_span,
3106 arg_before_callback_span,
3107 callback_body_span,
3108 }) = self.selected_call
3109 else {
3110 return vec![];
3111 };
3112
3113 // This is the nesting level of the `use` keyword we've inserted, we
3114 // want to move the entire body of the anonymous function to this level.
3115 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
3116 let indentation = " ".repeat(use_nesting_level as usize);
3117
3118 // First we move the callback arguments to the left hand side of the
3119 // call and add the `use` keyword.
3120 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span {
3121 let arguments_start = arguments_location.start as usize;
3122 let arguments_end = arguments_location.end as usize;
3123 let arguments_text = self
3124 .module
3125 .code
3126 .get(arguments_start..arguments_end)
3127 .expect("fn args");
3128 format!("use {arguments_text} <- ")
3129 } else {
3130 "use <- ".into()
3131 };
3132
3133 self.edits.insert(call_span.start, left_hand_side_text);
3134
3135 match arg_before_callback_span {
3136 // If the function call has no other arguments besides the callback then
3137 // we just have to remove the `fn(...) {` part.
3138 //
3139 // wibble(fn(...) { ... })
3140 // ^^^^^^^^^^ This goes from the end of the called function
3141 // To the start of the first thing in the anonymous
3142 // function's body.
3143 //
3144 None => self.edits.replace(
3145 SrcSpan::new(called_function_span.end, callback_body_span.start),
3146 format!("\n{indentation}"),
3147 ),
3148 // If it has other arguments we'll have to remove those and add a closed
3149 // parentheses too:
3150 //
3151 // wibble(1, 2, fn(...) { ... })
3152 // ^^^^^^^^^^^ We have to replace this with a `)`, it
3153 // goes from the end of the second-to-last
3154 // argument to the start of the first thing
3155 // in the anonymous function's body.
3156 //
3157 Some(arg_before_callback) => self.edits.replace(
3158 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
3159 format!(")\n{indentation}"),
3160 ),
3161 };
3162
3163 // Then we have to remove two spaces of indentation from each line of
3164 // the callback function's body.
3165 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
3166 for line in body_range.start.line + 1..=body_range.end.line {
3167 self.edits.delete_range(Range::new(
3168 Position { line, character: 0 },
3169 Position { line, character: 2 },
3170 ))
3171 }
3172
3173 // Then we have to remove the anonymous fn closing `}` and the call's
3174 // closing `)`.
3175 self.edits
3176 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
3177
3178 let mut action = Vec::with_capacity(1);
3179 CodeActionBuilder::new("Convert to `use`")
3180 .kind(CodeActionKind::RefactorRewrite)
3181 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3182 .preferred(false)
3183 .push_to(&mut action);
3184 action
3185 }
3186}
3187
3188impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
3189 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3190 // The cursor has to be inside the last statement of the function to
3191 // offer the code action.
3192 if let Some(last) = &fun.body.last()
3193 && within(
3194 self.params.range,
3195 self.edits.src_span_to_lsp_range(last.location()),
3196 )
3197 && let Some(call_data) = turn_statement_into_use(last)
3198 {
3199 self.selected_call = Some(call_data);
3200 }
3201
3202 ast::visit::visit_typed_function(self, fun)
3203 }
3204
3205 fn visit_typed_expr_fn(
3206 &mut self,
3207 location: &'ast SrcSpan,
3208 type_: &'ast Arc<Type>,
3209 kind: &'ast FunctionLiteralKind,
3210 arguments: &'ast [TypedArg],
3211 body: &'ast Vec1<TypedStatement>,
3212 return_annotation: &'ast Option<ast::TypeAst>,
3213 ) {
3214 // The cursor has to be inside the last statement of the body to
3215 // offer the code action.
3216 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
3217 if within(self.params.range, last_statement_range)
3218 && let Some(call_data) = turn_statement_into_use(body.last())
3219 {
3220 self.selected_call = Some(call_data);
3221 }
3222
3223 ast::visit::visit_typed_expr_fn(
3224 self,
3225 location,
3226 type_,
3227 kind,
3228 arguments,
3229 body,
3230 return_annotation,
3231 );
3232 }
3233
3234 fn visit_typed_expr_block(
3235 &mut self,
3236 location: &'ast SrcSpan,
3237 statements: &'ast [TypedStatement],
3238 ) {
3239 let Some(last_statement) = statements.last() else {
3240 return;
3241 };
3242
3243 // The cursor has to be inside the last statement of the block to offer
3244 // the code action.
3245 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
3246 if within(self.params.range, statement_range) {
3247 // Only the last statement of a block can be turned into a use!
3248 if let Some(selected_call) = turn_statement_into_use(last_statement) {
3249 self.selected_call = Some(selected_call)
3250 }
3251 }
3252
3253 ast::visit::visit_typed_expr_block(self, location, statements);
3254 }
3255}
3256
3257fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
3258 match statement {
3259 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
3260 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
3261 }
3262}
3263
3264fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
3265 let TypedExpr::Call {
3266 arguments,
3267 location: call_span,
3268 fun: called_function,
3269 ..
3270 } = expr
3271 else {
3272 return None;
3273 };
3274
3275 // The function arguments in the ast are reordered using function's field map.
3276 // This means that in the `args` array they might not appear in the same order
3277 // in which they are written by the user. Since the rest of the code relies
3278 // on their order in the written code we first have to sort them by their
3279 // source position.
3280 let arguments = arguments
3281 .iter()
3282 .sorted_by_key(|argument| argument.location.start)
3283 .collect_vec();
3284
3285 let CallArg {
3286 value: last_arg,
3287 implicit: None,
3288 ..
3289 } = arguments.last()?
3290 else {
3291 return None;
3292 };
3293
3294 let TypedExpr::Fn {
3295 arguments: callback_arguments,
3296 body,
3297 ..
3298 } = last_arg
3299 else {
3300 return None;
3301 };
3302
3303 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) {
3304 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
3305 _ => None,
3306 };
3307
3308 let arg_before_callback_span = if arguments.len() >= 2 {
3309 arguments
3310 .get(arguments.len() - 2)
3311 .map(|call_arg| call_arg.location)
3312 } else {
3313 None
3314 };
3315
3316 let callback_body_span = body.first().location().merge(&body.last().last_location());
3317
3318 Some(CallLocations {
3319 call_span: *call_span,
3320 called_function_span: called_function.location(),
3321 callback_arguments_span,
3322 arg_before_callback_span,
3323 callback_body_span,
3324 })
3325}
3326
3327/// Builder for code action to extract expression into a variable.
3328/// The action will wrap the expression in a block if needed in the appropriate scope.
3329///
3330/// For using the code action on the following selection:
3331///
3332/// ```gleam
3333/// fn void() {
3334/// case result {
3335/// Ok(value) -> 2 * value + 1
3336/// // ^^^^^^^^^
3337/// Error(_) -> panic
3338/// }
3339/// }
3340/// ```
3341///
3342/// Will result:
3343///
3344/// ```gleam
3345/// fn void() {
3346/// case result {
3347/// Ok(value) -> {
3348/// let int = 2 * value
3349/// int + 1
3350/// }
3351/// Error(_) -> panic
3352/// }
3353/// }
3354/// ```
3355pub struct ExtractVariable<'a> {
3356 module: &'a Module,
3357 params: &'a CodeActionParams,
3358 edits: TextEdits<'a>,
3359 position: Option<ExtractVariablePosition>,
3360 selected_expression: Option<ExtractedToVariable>,
3361 statement_before_selected_expression: Option<SrcSpan>,
3362 latest_statement: Option<SrcSpan>,
3363 to_be_wrapped: bool,
3364 name_generator: NameGenerator,
3365}
3366
3367pub enum ExtractedToVariable {
3368 Expression { location: SrcSpan, name: EcoString },
3369 StartOfPipeline { location: SrcSpan, name: EcoString },
3370}
3371
3372/// The Position of the selected code
3373#[derive(PartialEq, Eq, Copy, Clone, Debug)]
3374enum ExtractVariablePosition {
3375 InsideCaptureBody,
3376 /// Full statements (i.e. assignments, `use`s, and simple expressions).
3377 TopLevelStatement,
3378 /// The call on the right hand side of a pipe `|>`.
3379 PipelineCall,
3380 /// The right hand side of the `->` in a case expression.
3381 InsideCaseClause,
3382 /// A call argument. This can also be a `use` callback.
3383 CallArg,
3384}
3385
3386impl<'a> ExtractVariable<'a> {
3387 pub fn new(
3388 module: &'a Module,
3389 line_numbers: &'a LineNumbers,
3390 params: &'a CodeActionParams,
3391 ) -> Self {
3392 Self {
3393 module,
3394 params,
3395 edits: TextEdits::new(line_numbers),
3396 position: None,
3397 selected_expression: None,
3398 statement_before_selected_expression: None,
3399 latest_statement: None,
3400 to_be_wrapped: false,
3401 name_generator: NameGenerator::new(),
3402 }
3403 }
3404
3405 pub fn code_actions(mut self) -> Vec<CodeAction> {
3406 self.visit_typed_module(&self.module.ast);
3407
3408 let (Some(extracted_value), Some(insert_location)) = (
3409 self.selected_expression,
3410 self.statement_before_selected_expression,
3411 ) else {
3412 return vec![];
3413 };
3414
3415 let variable_name = match &extracted_value {
3416 ExtractedToVariable::Expression { name, .. }
3417 | ExtractedToVariable::StartOfPipeline { name, .. } => name,
3418 };
3419 let expression_span = match &extracted_value {
3420 ExtractedToVariable::Expression { location, .. }
3421 | ExtractedToVariable::StartOfPipeline { location, .. } => location,
3422 };
3423
3424 let content = self
3425 .module
3426 .code
3427 .get(expression_span.start as usize..expression_span.end as usize)
3428 .expect("selected expression");
3429
3430 let range = self.edits.src_span_to_lsp_range(insert_location);
3431
3432 let indent_size =
3433 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3434
3435 let mut indent = " ".repeat(indent_size);
3436
3437 // We insert the variable declaration
3438 // Wrap in a block if needed
3439 let mut insertion = match extracted_value {
3440 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"),
3441 ExtractedToVariable::StartOfPipeline { .. } => {
3442 format!("let {variable_name} =\n{indent} {content}\n")
3443 }
3444 };
3445
3446 if self.to_be_wrapped {
3447 let line_end = self
3448 .edits
3449 .line_numbers
3450 .line_starts
3451 .get((range.end.line + 1) as usize)
3452 .expect("Line number should be valid");
3453
3454 self.edits.insert(*line_end, format!("{indent}}}\n"));
3455 indent += " ";
3456 insertion = format!("{{\n{indent}{insertion}");
3457 };
3458
3459 self.edits
3460 .insert(insert_location.start, format!("{insertion}\n{indent}"));
3461
3462 self.edits
3463 .replace(*expression_span, String::from(variable_name));
3464
3465 let mut action = Vec::with_capacity(1);
3466 CodeActionBuilder::new("Extract variable")
3467 .kind(CodeActionKind::RefactorExtract)
3468 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3469 .preferred(false)
3470 .push_to(&mut action);
3471 action
3472 }
3473
3474 fn inside_new_scope<F>(&mut self, fun: F)
3475 where
3476 F: Fn(&mut Self),
3477 {
3478 let names = self.name_generator.clone();
3479 fun(self);
3480 self.name_generator = names;
3481 }
3482
3483 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString {
3484 let name = self.name_generator.generate_name_from_type(&type_);
3485 // When the generator generates a name, it rightfully inserts it in the
3486 // current scope so that it cannot be used again.
3487 // However, in our case it's not what we want: the name we're generating
3488 // is a candidate for what we might use for a single variable, at the
3489 // end of the whole process we're gonna pick just a single name.
3490 // If we were to insert this name into scope, that means that all the
3491 // other candidates would have a suffix `int_2`, `int_3`, ...
3492 // When we finally pick one it would be strange if the picked name had
3493 // a suffix but no `int`, `int_1`, ... were in scope!
3494 let _ = self.name_generator.used_names.remove(&name);
3495 name
3496 }
3497
3498 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
3499 where
3500 F: Fn(&mut Self),
3501 {
3502 self.at_optional_position(Some(position), fun);
3503 }
3504
3505 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
3506 where
3507 F: Fn(&mut Self),
3508 {
3509 let previous_statement = self.latest_statement;
3510 let previous_position = self.position;
3511 self.position = position;
3512 fun(self);
3513 self.position = previous_position;
3514 self.latest_statement = previous_statement;
3515 }
3516}
3517
3518impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
3519 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
3520 let range = self.edits.src_span_to_lsp_range(statement.location());
3521 if !within(self.params.range, range) {
3522 self.latest_statement = Some(statement.location());
3523 ast::visit::visit_typed_statement(self, statement);
3524 return;
3525 }
3526
3527 match self.position {
3528 // A capture body is comprised of just a single expression statement
3529 // that is inserted by the compiler, we don't really want to put
3530 // anything before that; so in this case we avoid tracking it.
3531 Some(ExtractVariablePosition::InsideCaptureBody) => {}
3532 Some(ExtractVariablePosition::PipelineCall) => {
3533 // Insert above the pipeline start
3534 self.latest_statement = Some(statement.location());
3535 }
3536 _ => {
3537 // Insert below the previous statement
3538 self.latest_statement = Some(statement.location());
3539 self.statement_before_selected_expression = self.latest_statement;
3540 }
3541 }
3542
3543 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3544 ast::visit::visit_typed_statement(this, statement);
3545 });
3546 }
3547
3548 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3549 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3550 start: fun.location.start,
3551 end: fun.end_position,
3552 });
3553
3554 if !within(self.params.range, fun_range) {
3555 return;
3556 }
3557
3558 // We reset the name generator to purge the variable names from other
3559 // scopes.
3560 // We then reserve the names already used by top level definitions.
3561 self.name_generator = NameGenerator::new();
3562 self.name_generator
3563 .reserve_module_value_names(&self.module.ast.definitions);
3564
3565 ast::visit::visit_typed_function(self, fun);
3566 }
3567
3568 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3569 if let Pattern::Variable { name, .. } = &assignment.pattern {
3570 self.name_generator.add_used_name(name.clone())
3571 };
3572 ast::visit::visit_typed_assignment(self, assignment);
3573 }
3574
3575 fn visit_typed_expr_pipeline(
3576 &mut self,
3577 location: &'ast SrcSpan,
3578 first_value: &'ast TypedPipelineAssignment,
3579 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
3580 finally: &'ast TypedExpr,
3581 finally_kind: &'ast PipelineAssignmentKind,
3582 ) {
3583 let expr_range = self.edits.src_span_to_lsp_range(*location);
3584 if !within(self.params.range, expr_range) {
3585 ast::visit::visit_typed_expr_pipeline(
3586 self,
3587 location,
3588 first_value,
3589 assignments,
3590 finally,
3591 finally_kind,
3592 );
3593 return;
3594 };
3595
3596 // Visiting a pipeline requires a bit of care, we don't want to extract
3597 // intermediate steps as variables (those are function calls)!
3598 // So we start by checking if the selected section contains multiple
3599 // steps including the first one: in that case we can extract all those
3600 // steps as a single variable.
3601 let selection = self.edits.lsp_range_to_src_span(self.params.range);
3602 let is_inside_first_step = first_value.location.contains(selection.start);
3603 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| {
3604 if assignment.location.contains(selection.end) {
3605 Some(assignment)
3606 } else {
3607 None
3608 }
3609 });
3610
3611 if let Some(last) = last_included_step
3612 && is_inside_first_step
3613 {
3614 let location = first_value.location.merge(&last.value.location());
3615 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline {
3616 location,
3617 name: self.generate_candidate_name(last.type_()),
3618 });
3619 return;
3620 }
3621
3622 // Otherwise we visit all the steps individually to see if there's
3623 // something _inside_ a step that might be extracted.
3624 let all_assignments =
3625 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
3626 for assignment in all_assignments {
3627 // With the position as "PipelineCall" we know we can't extract the
3628 // pipeline step itself!
3629 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3630 this.visit_typed_pipeline_assignment(assignment);
3631 });
3632 }
3633
3634 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3635 this.visit_typed_expr(finally)
3636 });
3637 }
3638
3639 fn visit_typed_expr_call(
3640 &mut self,
3641 location: &'ast SrcSpan,
3642 type_: &'ast Arc<Type>,
3643 fun: &'ast TypedExpr,
3644 arguments: &'ast [TypedCallArg],
3645 open_parenthesis: &'ast Option<u32>,
3646 ) {
3647 // Function calls need some extra care. If we're inspecting a record
3648 // call like this one: `Wibble(wobble, woo)` and the cursor is over the
3649 // constructor itself we never want to allow extracting it, or it would
3650 // result in the following code:
3651 //
3652 // ```gleam
3653 // Wibble(wobble, woo)
3654 // // ^^ Cursor here
3655 //
3656 // let wibble = Wibble
3657 // wibble(wobble, woo)
3658 // // That's a bit silly!
3659 // ```
3660 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
3661 if within(self.params.range, fun_range) && fun.is_record_constructor_function() {
3662 return;
3663 }
3664
3665 // Otherwise we just keep visiting like usual, no special handling is
3666 // required.
3667 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
3668 }
3669
3670 fn visit_typed_expr_record_update(
3671 &mut self,
3672 location: &'ast SrcSpan,
3673 spread_start: &'ast u32,
3674 type_: &'ast Arc<Type>,
3675 updated_record: &'ast TypedExpr,
3676 updated_record_assigned_name: &'ast Option<EcoString>,
3677 constructor: &'ast TypedExpr,
3678 arguments: &'ast [TypedCallArg],
3679 ) {
3680 // Record updates need some extra care. If we're inspecting a record
3681 // update like this one: `Wibble(..wobble, woo)` and the cursor is over
3682 // the constructor itself we never want to allow extracting it, or it
3683 // would result in the following code:
3684 //
3685 // ```gleam
3686 // Wibble(..wobble, woo)
3687 // // ^^ Cursor here
3688 //
3689 // let wibble = Wibble
3690 // wibble(..wobble, woo)
3691 // // That's a bit silly!
3692 // ```
3693 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location());
3694 if within(self.params.range, constructor_range)
3695 && constructor.is_record_constructor_function()
3696 {
3697 return;
3698 }
3699
3700 // Otherwise we just keep visiting like usual, no special handling is
3701 // required.
3702 ast::visit::visit_typed_expr_record_update(
3703 self,
3704 location,
3705 spread_start,
3706 type_,
3707 updated_record,
3708 updated_record_assigned_name,
3709 constructor,
3710 arguments,
3711 );
3712 }
3713
3714 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3715 let expr_location = expr.location();
3716 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3717 if !within(self.params.range, expr_range) {
3718 ast::visit::visit_typed_expr(self, expr);
3719 return;
3720 }
3721
3722 // If the expression is a top level statement we don't want to extract
3723 // it into a variable. It would mean we would turn this:
3724 //
3725 // ```gleam
3726 // pub fn main() {
3727 // let wibble = 1
3728 // // ^ cursor here
3729 // }
3730 //
3731 // // into:
3732 //
3733 // pub fn main() {
3734 // let int = 1
3735 // let wibble = int
3736 // }
3737 // ```
3738 //
3739 // Not all that useful!
3740 //
3741 match self.position {
3742 Some(
3743 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
3744 ) => {
3745 self.at_optional_position(None, |this| {
3746 ast::visit::visit_typed_expr(this, expr);
3747 });
3748 return;
3749 }
3750 Some(
3751 ExtractVariablePosition::InsideCaptureBody
3752 | ExtractVariablePosition::InsideCaseClause
3753 | ExtractVariablePosition::CallArg,
3754 )
3755 | None => {}
3756 }
3757
3758 match expr {
3759 TypedExpr::Fn {
3760 kind: FunctionLiteralKind::Anonymous { .. },
3761 ..
3762 } => {
3763 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3764 ast::visit::visit_typed_expr(this, expr);
3765 });
3766 return;
3767 }
3768
3769 TypedExpr::Int { location, .. }
3770 | TypedExpr::Float { location, .. }
3771 | TypedExpr::String { location, .. }
3772 | TypedExpr::Pipeline { location, .. }
3773 | TypedExpr::Fn { location, .. }
3774 | TypedExpr::Todo { location, .. }
3775 | TypedExpr::List { location, .. }
3776 | TypedExpr::Call { location, .. }
3777 | TypedExpr::BinOp { location, .. }
3778 | TypedExpr::Case { location, .. }
3779 | TypedExpr::RecordAccess { location, .. }
3780 | TypedExpr::Tuple { location, .. }
3781 | TypedExpr::TupleIndex { location, .. }
3782 | TypedExpr::BitArray { location, .. }
3783 | TypedExpr::RecordUpdate { location, .. }
3784 | TypedExpr::NegateBool { location, .. }
3785 | TypedExpr::NegateInt { location, .. }
3786 // It generally makes no sense to extract variables, the only
3787 // exception is for records with no fields (like `True` and
3788 // `False`): in the AST those are `Var`s with a `Record`
3789 // constructor. Extracting them is allowed!
3790 | TypedExpr::Var {
3791 constructor:
3792 ValueConstructor {
3793 variant: type_::ValueConstructorVariant::Record { .. },
3794 ..
3795 },
3796 location,
3797 ..
3798 } => {
3799 if let Some(ExtractVariablePosition::CallArg) = self.position {
3800 // Don't update latest statement, we don't want to insert the extracted
3801 // variable inside the parenthesis where the call argument is located.
3802 } else {
3803 self.statement_before_selected_expression = self.latest_statement;
3804 };
3805 self.selected_expression = Some(ExtractedToVariable::Expression {
3806 location: *location,
3807 name: self.generate_candidate_name(expr.type_()),
3808 });
3809 }
3810
3811 // Expressions that don't make sense to extract
3812 TypedExpr::Panic { .. }
3813 | TypedExpr::Echo { .. }
3814 | TypedExpr::Block { .. }
3815 | TypedExpr::ModuleSelect { .. }
3816 | TypedExpr::Invalid { .. }
3817 | TypedExpr::PositionalAccess { .. }
3818 | TypedExpr::Var { .. } => (),
3819 }
3820
3821 ast::visit::visit_typed_expr(self, expr);
3822 }
3823
3824 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3825 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3826 if !within(self.params.range, range) {
3827 ast::visit::visit_typed_use(self, use_);
3828 return;
3829 }
3830
3831 // Insert code under the `use`
3832 self.statement_before_selected_expression = Some(use_.call.location());
3833 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3834 ast::visit::visit_typed_use(this, use_);
3835 });
3836 }
3837
3838 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3839 let range = self.edits.src_span_to_lsp_range(clause.location());
3840 if !within(self.params.range, range) {
3841 self.inside_new_scope(|this| {
3842 ast::visit::visit_typed_clause(this, clause);
3843 });
3844 return;
3845 }
3846
3847 // Insert code after the `->`
3848 self.latest_statement = Some(clause.then.location());
3849 self.to_be_wrapped = true;
3850 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3851 this.inside_new_scope(|this| {
3852 ast::visit::visit_typed_clause(this, clause);
3853 });
3854 });
3855 }
3856
3857 fn visit_typed_expr_block(
3858 &mut self,
3859 location: &'ast SrcSpan,
3860 statements: &'ast [TypedStatement],
3861 ) {
3862 let range = self.edits.src_span_to_lsp_range(*location);
3863 if !within(self.params.range, range) {
3864 self.inside_new_scope(|this| {
3865 ast::visit::visit_typed_expr_block(this, location, statements);
3866 });
3867 return;
3868 }
3869
3870 // Don't extract block as variable
3871 let mut position = self.position;
3872 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3873 position = None;
3874 self.to_be_wrapped = false;
3875 }
3876
3877 self.at_optional_position(position, |this| {
3878 this.inside_new_scope(|this| {
3879 ast::visit::visit_typed_expr_block(this, location, statements);
3880 });
3881 });
3882 }
3883
3884 fn visit_typed_expr_fn(
3885 &mut self,
3886 location: &'ast SrcSpan,
3887 type_: &'ast Arc<Type>,
3888 kind: &'ast FunctionLiteralKind,
3889 arguments: &'ast [TypedArg],
3890 body: &'ast Vec1<TypedStatement>,
3891 return_annotation: &'ast Option<ast::TypeAst>,
3892 ) {
3893 let range = self.edits.src_span_to_lsp_range(*location);
3894 if !within(self.params.range, range) {
3895 self.inside_new_scope(|this| {
3896 ast::visit::visit_typed_expr_fn(
3897 this,
3898 location,
3899 type_,
3900 kind,
3901 arguments,
3902 body,
3903 return_annotation,
3904 );
3905 });
3906 return;
3907 }
3908
3909 let position = match kind {
3910 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3911 // just a single expression statement. When visiting we must record
3912 // we're inside a capture body.
3913 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3914 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3915 FunctionLiteralKind::Anonymous { .. } => self.position,
3916 };
3917
3918 self.at_optional_position(position, |this| {
3919 this.inside_new_scope(|this| {
3920 ast::visit::visit_typed_expr_fn(
3921 this,
3922 location,
3923 type_,
3924 kind,
3925 arguments,
3926 body,
3927 return_annotation,
3928 );
3929 });
3930 });
3931 }
3932
3933 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3934 let range = self.edits.src_span_to_lsp_range(arg.location);
3935 if !within(self.params.range, range) {
3936 ast::visit::visit_typed_call_arg(self, arg);
3937 return;
3938 }
3939
3940 // An implicit record update arg in inserted by the compiler, we don't
3941 // want folks to interact with this since it doesn't translate to
3942 // anything in the source code despite having a default position.
3943 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit {
3944 return;
3945 }
3946
3947 let position = if arg.is_use_implicit_callback() {
3948 Some(ExtractVariablePosition::TopLevelStatement)
3949 } else {
3950 Some(ExtractVariablePosition::CallArg)
3951 };
3952
3953 self.at_optional_position(position, |this| {
3954 ast::visit::visit_typed_call_arg(this, arg);
3955 });
3956 }
3957
3958 // We don't want to offer the action if the cursor is over some invalid
3959 // piece of code.
3960 fn visit_typed_expr_invalid(
3961 &mut self,
3962 location: &'ast SrcSpan,
3963 _type_: &'ast Arc<Type>,
3964 _extra_information: &'ast Option<InvalidExpression>,
3965 ) {
3966 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3967 if within(self.params.range, invalid_range) {
3968 self.selected_expression = None;
3969 }
3970 }
3971}
3972
3973/// Builder for code action to convert a literal use into a const.
3974///
3975/// For using the code action on each of the following lines:
3976///
3977/// ```gleam
3978/// fn void() {
3979/// let var = [1, 2, 3]
3980/// let res = function("Statement", var)
3981/// }
3982/// ```
3983///
3984/// Both value literals will become:
3985///
3986/// ```gleam
3987/// const var = [1, 2, 3]
3988/// const string = "Statement"
3989///
3990/// fn void() {
3991/// let res = function(string, var)
3992/// }
3993/// ```
3994pub struct ExtractConstant<'a> {
3995 module: &'a Module,
3996 params: &'a CodeActionParams,
3997 edits: TextEdits<'a>,
3998 /// The whole selected expression
3999 selected_expression: Option<SrcSpan>,
4000 /// The location of the start of the function containing the expression.
4001 /// It includes function's documentation as well
4002 container_function_start: Option<u32>,
4003 /// The variant of the extractable expression being extracted (if any)
4004 variant_of_extractable: Option<ExtractableToConstant>,
4005 /// The name of the newly created constant
4006 name_to_use: Option<EcoString>,
4007 /// The right hand side expression of the newly created constant
4008 value_to_use: Option<EcoString>,
4009}
4010
4011/// Used when an expression can be extracted to a constant
4012enum ExtractableToConstant {
4013 /// Used for collections and operator uses. This means that elements
4014 /// inside, are also extractable as constants.
4015 ComposedValue,
4016 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
4017 /// and type variants (not records).
4018 SingleValue,
4019 /// Used for whole variable assignments. If the right hand side of the
4020 /// expression can be extracted, the whole expression extracted and use the
4021 /// local variable as a constant.
4022 Assignment,
4023}
4024
4025fn can_be_constant(
4026 module: &Module,
4027 expr: &TypedExpr,
4028 module_constants: Option<&HashSet<&EcoString>>,
4029) -> bool {
4030 // We pass the `module_constants` on recursion to not compute them each time
4031 let module_constants = match module_constants {
4032 Some(module_constants) => module_constants,
4033 None => &module
4034 .ast
4035 .definitions
4036 .constants
4037 .iter()
4038 .map(|constant| &constant.name)
4039 .collect(),
4040 };
4041
4042 match expr {
4043 // Attempt to extract whole list as long as it's comprised of only literals
4044 TypedExpr::List { elements, tail, .. } => {
4045 elements
4046 .iter()
4047 .all(|element| can_be_constant(module, element, Some(module_constants)))
4048 && tail.is_none()
4049 }
4050
4051 // Attempt to extract whole bit array as long as it's made up of literals
4052 TypedExpr::BitArray { segments, .. } => {
4053 segments
4054 .iter()
4055 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants)))
4056 && segments.iter().all(|segment| {
4057 segment.options.iter().all(|option| match option {
4058 ast::BitArrayOption::Size { value, .. } => {
4059 can_be_constant(module, value, Some(module_constants))
4060 }
4061
4062 ast::BitArrayOption::Bytes { .. }
4063 | ast::BitArrayOption::Int { .. }
4064 | ast::BitArrayOption::Float { .. }
4065 | ast::BitArrayOption::Bits { .. }
4066 | ast::BitArrayOption::Utf8 { .. }
4067 | ast::BitArrayOption::Utf16 { .. }
4068 | ast::BitArrayOption::Utf32 { .. }
4069 | ast::BitArrayOption::Utf8Codepoint { .. }
4070 | ast::BitArrayOption::Utf16Codepoint { .. }
4071 | ast::BitArrayOption::Utf32Codepoint { .. }
4072 | ast::BitArrayOption::Signed { .. }
4073 | ast::BitArrayOption::Unsigned { .. }
4074 | ast::BitArrayOption::Big { .. }
4075 | ast::BitArrayOption::Little { .. }
4076 | ast::BitArrayOption::Native { .. }
4077 | ast::BitArrayOption::Unit { .. } => true,
4078 })
4079 })
4080 }
4081
4082 // Attempt to extract whole tuple as long as it's comprised of only literals
4083 TypedExpr::Tuple { elements, .. } => elements
4084 .iter()
4085 .all(|element| can_be_constant(module, element, Some(module_constants))),
4086
4087 // Extract literals directly
4088 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
4089
4090 // Extract non-record types directly
4091 TypedExpr::Var {
4092 constructor, name, ..
4093 } => {
4094 matches!(
4095 constructor.variant,
4096 type_::ValueConstructorVariant::Record { arity: 0, .. }
4097 ) || module_constants.contains(name)
4098 }
4099
4100 // Extract record types as long as arguments can be constant
4101 TypedExpr::Call { arguments, fun, .. } => {
4102 fun.is_record_literal()
4103 && arguments
4104 .iter()
4105 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants)))
4106 }
4107
4108 // Extract concat binary operation if both sides can be constants
4109 TypedExpr::BinOp {
4110 operator,
4111 left,
4112 right,
4113 ..
4114 } => {
4115 matches!(operator, ast::BinOp::Concatenate)
4116 && can_be_constant(module, left, Some(module_constants))
4117 && can_be_constant(module, right, Some(module_constants))
4118 }
4119
4120 TypedExpr::Block { .. }
4121 | TypedExpr::Pipeline { .. }
4122 | TypedExpr::Fn { .. }
4123 | TypedExpr::Case { .. }
4124 | TypedExpr::RecordAccess { .. }
4125 | TypedExpr::PositionalAccess { .. }
4126 | TypedExpr::ModuleSelect { .. }
4127 | TypedExpr::TupleIndex { .. }
4128 | TypedExpr::Todo { .. }
4129 | TypedExpr::Panic { .. }
4130 | TypedExpr::Echo { .. }
4131 | TypedExpr::RecordUpdate { .. }
4132 | TypedExpr::NegateBool { .. }
4133 | TypedExpr::NegateInt { .. }
4134 | TypedExpr::Invalid { .. } => false,
4135 }
4136}
4137
4138/// Takes the list of already existing constants and functions and creates a
4139/// name that doesn't conflict with them
4140///
4141fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
4142 let mut name_generator = NameGenerator::new();
4143 name_generator.reserve_module_value_names(&module.ast.definitions);
4144 name_generator.generate_name_from_type(&expr.type_())
4145}
4146
4147/// Converts the source start position of a documentation comment's contents into
4148/// the position of the leading slash in its marker ('///').
4149fn get_doc_marker_position(content_pos: u32) -> u32 {
4150 content_pos.saturating_sub(3)
4151}
4152
4153impl<'a> ExtractConstant<'a> {
4154 pub fn new(
4155 module: &'a Module,
4156 line_numbers: &'a LineNumbers,
4157 params: &'a CodeActionParams,
4158 ) -> Self {
4159 Self {
4160 module,
4161 params,
4162 edits: TextEdits::new(line_numbers),
4163 selected_expression: None,
4164 container_function_start: None,
4165 variant_of_extractable: None,
4166 name_to_use: None,
4167 value_to_use: None,
4168 }
4169 }
4170
4171 pub fn code_actions(mut self) -> Vec<CodeAction> {
4172 self.visit_typed_module(&self.module.ast);
4173
4174 let (
4175 Some(expr_span),
4176 Some(function_start),
4177 Some(type_of_extractable),
4178 Some(new_const_name),
4179 Some(const_value),
4180 ) = (
4181 self.selected_expression,
4182 self.container_function_start,
4183 self.variant_of_extractable,
4184 self.name_to_use,
4185 self.value_to_use,
4186 )
4187 else {
4188 return vec![];
4189 };
4190
4191 // We insert the constant declaration
4192 self.edits.insert(
4193 function_start,
4194 format!("const {new_const_name} = {const_value}\n\n"),
4195 );
4196
4197 // We remove or replace the selected expression
4198 match type_of_extractable {
4199 // The whole expression is deleted for assignments
4200 ExtractableToConstant::Assignment => {
4201 let range = self
4202 .edits
4203 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
4204
4205 let indent_size =
4206 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
4207
4208 let expr_span_with_new_line = SrcSpan {
4209 // We remove leading indentation + 1 to remove the newline with it
4210 start: expr_span.start - (indent_size as u32 + 1),
4211 end: expr_span.end,
4212 };
4213 self.edits.delete(expr_span_with_new_line);
4214 }
4215
4216 // Only right hand side is replaced for collection or values
4217 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
4218 self.edits.replace(expr_span, String::from(new_const_name));
4219 }
4220 }
4221
4222 let mut action = Vec::with_capacity(1);
4223 CodeActionBuilder::new("Extract constant")
4224 .kind(CodeActionKind::RefactorExtract)
4225 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4226 .preferred(false)
4227 .push_to(&mut action);
4228 action
4229 }
4230}
4231
4232impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
4233 /// To get the position of the function containing the value or assignment
4234 /// to extract
4235 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
4236 let fun_location = fun.location;
4237 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
4238 start: fun_location.start,
4239 end: fun.end_position,
4240 });
4241
4242 if !within(self.params.range, fun_range) {
4243 return;
4244 }
4245
4246 // Here we need to get position of the function, starting from the leading slash in the
4247 // documentation comment's marker ('///'), not from comment's content (of which
4248 // we have the position), so we must convert the content start position
4249 // to the leading slash's position.
4250 self.container_function_start = Some(
4251 fun.documentation
4252 .as_ref()
4253 .map(|(doc_start, _)| get_doc_marker_position(*doc_start))
4254 .unwrap_or(fun_location.start),
4255 );
4256
4257 ast::visit::visit_typed_function(self, fun);
4258 }
4259
4260 /// To extract the whole assignment
4261 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
4262 let expr_location = assignment.location;
4263
4264 // We only offer this code action for extracting the whole assignment
4265 // between `let` and `=`.
4266 let pattern_location = assignment.pattern.location();
4267 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
4268 let code_action_range = self.edits.src_span_to_lsp_range(location);
4269
4270 if !within(self.params.range, code_action_range) {
4271 ast::visit::visit_typed_assignment(self, assignment);
4272 return;
4273 }
4274
4275 // Has to be variable because patterns can't be constants.
4276 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
4277 {
4278 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
4279 self.selected_expression = Some(expr_location);
4280 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern {
4281 Some(name.clone())
4282 } else {
4283 None
4284 };
4285 self.value_to_use = Some(EcoString::from(
4286 self.module
4287 .code
4288 .get(
4289 (assignment.value.location().start as usize)
4290 ..(assignment.location.end as usize),
4291 )
4292 .expect("selected expression"),
4293 ));
4294 }
4295 }
4296
4297 /// To extract only the literal
4298 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
4299 let expr_location = expr.location();
4300 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
4301
4302 if !within(self.params.range, expr_range) {
4303 ast::visit::visit_typed_expr(self, expr);
4304 return;
4305 }
4306
4307 // Keep going down recursively if:
4308 // - It's no extractable has been found yet (`None`).
4309 // - It's a collection, which may or may not contain a value that can
4310 // be extracted.
4311 // - It's a binary operator, which may or may not operate on
4312 // extractable values.
4313 if matches!(
4314 self.variant_of_extractable,
4315 None | Some(ExtractableToConstant::ComposedValue)
4316 ) && can_be_constant(self.module, expr, None)
4317 {
4318 self.variant_of_extractable = match expr {
4319 TypedExpr::Var { .. }
4320 | TypedExpr::Int { .. }
4321 | TypedExpr::Float { .. }
4322 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
4323
4324 TypedExpr::List { .. }
4325 | TypedExpr::Tuple { .. }
4326 | TypedExpr::BitArray { .. }
4327 | TypedExpr::BinOp { .. }
4328 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
4329
4330 TypedExpr::Block { .. }
4331 | TypedExpr::Pipeline { .. }
4332 | TypedExpr::Fn { .. }
4333 | TypedExpr::Case { .. }
4334 | TypedExpr::RecordAccess { .. }
4335 | TypedExpr::PositionalAccess { .. }
4336 | TypedExpr::ModuleSelect { .. }
4337 | TypedExpr::TupleIndex { .. }
4338 | TypedExpr::Todo { .. }
4339 | TypedExpr::Panic { .. }
4340 | TypedExpr::Echo { .. }
4341 | TypedExpr::RecordUpdate { .. }
4342 | TypedExpr::NegateBool { .. }
4343 | TypedExpr::NegateInt { .. }
4344 | TypedExpr::Invalid { .. } => None,
4345 };
4346
4347 self.selected_expression = Some(expr_location);
4348 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
4349 self.value_to_use = Some(EcoString::from(
4350 self.module
4351 .code
4352 .get((expr_location.start as usize)..(expr_location.end as usize))
4353 .expect("selected expression"),
4354 ));
4355 }
4356
4357 ast::visit::visit_typed_expr(self, expr);
4358 }
4359}
4360
4361/// Builder for code action to apply the "expand function capture" action.
4362///
4363pub struct ExpandFunctionCapture<'a> {
4364 module: &'a Module,
4365 params: &'a CodeActionParams,
4366 edits: TextEdits<'a>,
4367 function_capture_data: Option<FunctionCaptureData>,
4368}
4369
4370pub struct FunctionCaptureData {
4371 function_span: SrcSpan,
4372 hole_span: SrcSpan,
4373 hole_type: Arc<Type>,
4374 reserved_names: VariablesNames,
4375}
4376
4377impl<'a> ExpandFunctionCapture<'a> {
4378 pub fn new(
4379 module: &'a Module,
4380 line_numbers: &'a LineNumbers,
4381 params: &'a CodeActionParams,
4382 ) -> Self {
4383 Self {
4384 module,
4385 params,
4386 edits: TextEdits::new(line_numbers),
4387 function_capture_data: None,
4388 }
4389 }
4390
4391 pub fn code_actions(mut self) -> Vec<CodeAction> {
4392 self.visit_typed_module(&self.module.ast);
4393
4394 let Some(FunctionCaptureData {
4395 function_span,
4396 hole_span,
4397 hole_type,
4398 reserved_names,
4399 }) = self.function_capture_data
4400 else {
4401 return vec![];
4402 };
4403
4404 let mut name_generator = NameGenerator::new();
4405 name_generator.reserve_variable_names(reserved_names);
4406 let name = name_generator.generate_name_from_type(&hole_type);
4407
4408 self.edits.replace(hole_span, name.clone().into());
4409 self.edits.insert(function_span.end, " }".into());
4410 self.edits
4411 .insert(function_span.start, format!("fn({name}) {{ "));
4412
4413 let mut action = Vec::with_capacity(1);
4414 CodeActionBuilder::new("Expand function capture")
4415 .kind(CodeActionKind::RefactorRewrite)
4416 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4417 .preferred(false)
4418 .push_to(&mut action);
4419 action
4420 }
4421}
4422
4423impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
4424 fn visit_typed_expr_fn(
4425 &mut self,
4426 location: &'ast SrcSpan,
4427 type_: &'ast Arc<Type>,
4428 kind: &'ast FunctionLiteralKind,
4429 arguments: &'ast [TypedArg],
4430 body: &'ast Vec1<TypedStatement>,
4431 return_annotation: &'ast Option<ast::TypeAst>,
4432 ) {
4433 let fn_range = self.edits.src_span_to_lsp_range(*location);
4434 if within(self.params.range, fn_range)
4435 && kind.is_capture()
4436 && let [argument] = arguments
4437 {
4438 self.function_capture_data = Some(FunctionCaptureData {
4439 function_span: *location,
4440 hole_span: argument.location,
4441 hole_type: argument.type_.clone(),
4442 reserved_names: VariablesNames::from_statements(body),
4443 });
4444 }
4445
4446 ast::visit::visit_typed_expr_fn(
4447 self,
4448 location,
4449 type_,
4450 kind,
4451 arguments,
4452 body,
4453 return_annotation,
4454 )
4455 }
4456}
4457
4458/// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names].
4459struct VariablesNames {
4460 names: HashSet<EcoString>,
4461}
4462
4463impl VariablesNames {
4464 /// Creates a `VariableNames` by collecting all variables used in a list of statements.
4465 fn from_statements(statements: &[TypedStatement]) -> Self {
4466 let mut variables = Self {
4467 names: HashSet::new(),
4468 };
4469
4470 for statement in statements {
4471 variables.visit_typed_statement(statement);
4472 }
4473 variables
4474 }
4475
4476 /// Creates a `VariableNames` by collecting all variables used within an expression.
4477 fn from_expression(expression: &TypedExpr) -> Self {
4478 let mut variables = Self {
4479 names: HashSet::new(),
4480 };
4481
4482 variables.visit_typed_expr(expression);
4483 variables
4484 }
4485}
4486
4487impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
4488 fn visit_typed_expr_var(
4489 &mut self,
4490 _location: &'ast SrcSpan,
4491 _constructor: &'ast ValueConstructor,
4492 name: &'ast EcoString,
4493 ) {
4494 let _ = self.names.insert(name.clone());
4495 }
4496}
4497
4498/// Builder for code action to apply the "generate dynamic decoder action.
4499///
4500pub struct GenerateDynamicDecoder<'a, IO> {
4501 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4502 module: &'a Module,
4503 params: &'a CodeActionParams,
4504 edits: TextEdits<'a>,
4505 printer: Printer<'a>,
4506 actions: &'a mut Vec<CodeAction>,
4507}
4508
4509const DECODE_MODULE: &str = "gleam/dynamic/decode";
4510
4511impl<'a, IO> GenerateDynamicDecoder<'a, IO> {
4512 pub fn new(
4513 module: &'a Module,
4514 line_numbers: &'a LineNumbers,
4515 params: &'a CodeActionParams,
4516 actions: &'a mut Vec<CodeAction>,
4517 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4518 ) -> Self {
4519 // Since we are generating a new function, type variables from other
4520 // functions and constants are irrelevant to the types we print.
4521 let printer = Printer::new_without_type_variables(&module.ast.names);
4522 Self {
4523 module,
4524 params,
4525 edits: TextEdits::new(line_numbers),
4526 printer,
4527 actions,
4528 compiler,
4529 }
4530 }
4531
4532 pub fn code_actions(&mut self) {
4533 self.visit_typed_module(&self.module.ast);
4534 }
4535
4536 fn custom_type_decoder_body(
4537 &mut self,
4538 custom_type: &CustomType<Arc<Type>>,
4539 ) -> Option<EcoString> {
4540 // We cannot generate a decoder for an external type with no constructors!
4541 let constructors_size = custom_type.constructors.len();
4542 let (first, rest) = custom_type.constructors.split_first()?;
4543 let mode = EncodingMode::for_custom_type(custom_type);
4544
4545 // We generate a decoder for a type with a single constructor: it does not
4546 // require pattern matching on a tag as there's no variants to tell apart.
4547 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
4548 return self.constructor_decoder(mode, custom_type, first, 0);
4549 }
4550
4551 // Otherwise we need to generate a decoder that has to tell apart different
4552 // variants, depending on the mode we might have to decode a type field or
4553 // plain strings!
4554 let module = self.printer.print_module(DECODE_MODULE);
4555 let discriminant = if mode == EncodingMode::PlainString {
4556 eco_format!("use variant <- {module}.then({module}.string)")
4557 } else {
4558 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
4559 };
4560
4561 let mut clauses = Vec::with_capacity(constructors_size);
4562 for constructor in iter::once(first).chain(rest) {
4563 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
4564 let name = to_snake_case(&constructor.name);
4565 clauses.push(eco_format!(r#" "{name}" -> {body}"#));
4566 }
4567
4568 let failure_clause = self.failure_clause(custom_type);
4569
4570 let cases = clauses.join("\n");
4571 Some(eco_format!(
4572 r#"{{
4573 {discriminant}
4574 case variant {{
4575{cases}
4576{failure_clause}
4577 }}
4578}}"#,
4579 ))
4580 }
4581
4582 fn constructor_decoder(
4583 &mut self,
4584 mode: EncodingMode,
4585 custom_type: &ast::TypedCustomType,
4586 constructor: &TypedRecordConstructor,
4587 nesting: usize,
4588 ) -> Option<EcoString> {
4589 let decode_module = self.printer.print_module(DECODE_MODULE);
4590 let constructor_name = &constructor.name;
4591
4592 // If the constructor was encoded as a plain string with no additional
4593 // fields it means there's nothing else to decode and we can just
4594 // succeed.
4595 if mode == EncodingMode::PlainString {
4596 return Some(eco_format!("{decode_module}.success({constructor_name})"));
4597 }
4598
4599 // Otherwise we have to decode all the constructor fields to build it.
4600 let mut fields = Vec::with_capacity(constructor.arguments.len());
4601 for argument in constructor.arguments.iter() {
4602 let (_, name) = argument.label.as_ref()?;
4603 let field = RecordField {
4604 label: RecordLabel::Labeled(name),
4605 type_: &argument.type_,
4606 };
4607 fields.push(field);
4608 }
4609
4610 let mut decoder_printer = DecoderPrinter::new(
4611 &mut self.printer,
4612 custom_type.name.clone(),
4613 self.module.name.clone(),
4614 self.compiler,
4615 );
4616
4617 let decoders = fields
4618 .iter()
4619 .map(|field| decoder_printer.decode_field(field, nesting + 2))
4620 .join("\n");
4621
4622 let indent = " ".repeat(nesting);
4623
4624 Some(if decoders.is_empty() {
4625 eco_format!("{decode_module}.success({constructor_name})")
4626 } else {
4627 let field_names = fields
4628 .iter()
4629 .map(|field| format!("{}:", field.label.variable_name()))
4630 .join(", ");
4631
4632 eco_format!(
4633 "{{
4634{decoders}
4635{indent} {decode_module}.success({constructor_name}({field_names}))
4636{indent}}}",
4637 )
4638 })
4639 }
4640
4641 /// Generates the failure/catch-all clause in a decoder function for a
4642 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()`
4643 /// clause executed when the `type` field does not match any of the known variants.
4644 ///
4645 /// # Arguments
4646 /// * `custom_type` - The root type we are printing a decoder for
4647 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString {
4648 let decode_module = self.printer.print_module(DECODE_MODULE);
4649 let type_name = &custom_type.name;
4650
4651 let mut decoder_printer = DecoderPrinter::new(
4652 &mut self.printer,
4653 type_name.clone(),
4654 self.module.name.clone(),
4655 self.compiler,
4656 );
4657
4658 // The construction of the zero value might necessitate importing
4659 // modules that aren't currently in scope. We keep track of them here.
4660 let mut modules_to_import = HashSet::new();
4661 // We also need to keep track of the path we are tracing through the types
4662 // to make sure we don't get stuck in an infinite loop building a recursive
4663 // type.
4664 let mut path = vec![];
4665
4666 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type(
4667 &self.module.name,
4668 type_name,
4669 &mut path,
4670 &mut modules_to_import,
4671 ) {
4672 for module_name in modules_to_import {
4673 maybe_import(&mut self.edits, self.module, &module_name);
4674 }
4675 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,)
4676 } else {
4677 eco_format!(
4678 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"#
4679 )
4680 }
4681 }
4682}
4683
4684impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> {
4685 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4686 let range = self
4687 .edits
4688 .src_span_to_lsp_range(custom_type.full_location());
4689 if !within(self.params.range, range) {
4690 return;
4691 }
4692
4693 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
4694 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
4695 return;
4696 };
4697
4698 let parameters = match custom_type.parameters.len() {
4699 0 => EcoString::new(),
4700 _ => eco_format!(
4701 "({})",
4702 custom_type
4703 .parameters
4704 .iter()
4705 .map(|(_, name)| name)
4706 .join(", ")
4707 ),
4708 };
4709
4710 let decoder_type = self.printer.print_type(&Type::Named {
4711 publicity: Publicity::Public,
4712 package: STDLIB_PACKAGE_NAME.into(),
4713 module: DECODE_MODULE.into(),
4714 name: "Decoder".into(),
4715 arguments: vec![],
4716 inferred_variant: None,
4717 });
4718
4719 let function = format!(
4720 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
4721 type_name = custom_type.name,
4722 );
4723
4724 self.edits.insert(custom_type.end_position, function);
4725 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
4726
4727 CodeActionBuilder::new("Generate dynamic decoder")
4728 .kind(CodeActionKind::Refactor)
4729 .preferred(false)
4730 .changes(
4731 self.params.text_document.uri.clone(),
4732 std::mem::take(&mut self.edits.edits),
4733 )
4734 .push_to(self.actions);
4735 }
4736}
4737
4738/// If `module_name` is not already imported inside `module`, adds an edit to
4739/// add that import.
4740/// This function also makes sure not to import a module in itself.
4741///
4742fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
4743 if module.ast.names.is_imported(module_name) || module.name == module_name {
4744 return;
4745 }
4746
4747 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
4748 let first_is_import = first_import_pos.is_some();
4749 let import_location = first_import_pos.unwrap_or_default();
4750 let after_import_newlines = add_newlines_after_import(
4751 import_location,
4752 first_is_import,
4753 edits.line_numbers,
4754 &module.code,
4755 );
4756
4757 edits.edits.push(get_import_edit(
4758 import_location,
4759 module_name,
4760 &after_import_newlines,
4761 ));
4762}
4763
4764struct DecoderPrinter<'a, 'b, IO> {
4765 printer: &'a mut Printer<'b>,
4766 /// The name of the root type we are printing a decoder for
4767 type_name: EcoString,
4768 /// The module name of the root type we are printing a decoder for
4769 type_module: EcoString,
4770 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4771}
4772
4773const DYNAMIC_MODULE: &str = "gleam/dynamic";
4774const DICT_MODULE: &str = "gleam/dict";
4775const OPTION_MODULE: &str = "gleam/option";
4776
4777struct RecordField<'a> {
4778 label: RecordLabel<'a>,
4779 type_: &'a Type,
4780}
4781
4782enum RecordLabel<'a> {
4783 Labeled(&'a str),
4784 Unlabeled(usize),
4785}
4786
4787impl RecordLabel<'_> {
4788 fn field_key(&self) -> EcoString {
4789 match self {
4790 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
4791 RecordLabel::Unlabeled(index) => {
4792 eco_format!("{index}")
4793 }
4794 }
4795 }
4796
4797 fn variable_name(&self) -> EcoString {
4798 match self {
4799 RecordLabel::Labeled(label) => (*label).into(),
4800 &RecordLabel::Unlabeled(mut index) => {
4801 let mut characters = Vec::new();
4802 let alphabet_length = 26;
4803 let alphabet_offset = b'a';
4804 loop {
4805 let alphabet_index = (index % alphabet_length) as u8;
4806 characters.push((alphabet_offset + alphabet_index) as char);
4807 index /= alphabet_length;
4808
4809 if index == 0 {
4810 break;
4811 }
4812 index -= 1;
4813 }
4814 characters.into_iter().rev().collect()
4815 }
4816 }
4817 }
4818}
4819
4820impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> {
4821 fn new(
4822 printer: &'a mut Printer<'b>,
4823 type_name: EcoString,
4824 type_module: EcoString,
4825 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4826 ) -> Self {
4827 Self {
4828 type_name,
4829 type_module,
4830 printer,
4831 compiler,
4832 }
4833 }
4834
4835 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
4836 let module_name = self.printer.print_module(DECODE_MODULE);
4837 if type_.is_bit_array() {
4838 eco_format!("{module_name}.bit_array")
4839 } else if type_.is_bool() {
4840 eco_format!("{module_name}.bool")
4841 } else if type_.is_float() {
4842 eco_format!("{module_name}.float")
4843 } else if type_.is_int() {
4844 eco_format!("{module_name}.int")
4845 } else if type_.is_string() {
4846 eco_format!("{module_name}.string")
4847 } else if type_.is_nil() {
4848 eco_format!("{module_name}.success(Nil)")
4849 } else {
4850 match type_.tuple_types() {
4851 Some(types) => {
4852 let fields = types
4853 .iter()
4854 .enumerate()
4855 .map(|(index, type_)| RecordField {
4856 type_,
4857 label: RecordLabel::Unlabeled(index),
4858 })
4859 .collect_vec();
4860 let decoders = fields
4861 .iter()
4862 .map(|field| self.decode_field(field, indent + 2))
4863 .join("\n");
4864 let mut field_names = fields.iter().map(|field| field.label.variable_name());
4865
4866 eco_format!(
4867 "{{
4868{decoders}
4869
4870{indent} {module_name}.success(#({fields}))
4871{indent}}}",
4872 fields = field_names.join(", "),
4873 indent = " ".repeat(indent)
4874 )
4875 }
4876 _ => {
4877 let type_information = type_.named_type_information();
4878 let type_information =
4879 type_information.as_ref().map(|(module, name, arguments)| {
4880 (module.as_str(), name.as_str(), arguments.as_slice())
4881 });
4882
4883 match type_information {
4884 Some(("gleam/dynamic", "Dynamic", _)) => {
4885 eco_format!("{module_name}.dynamic")
4886 }
4887 Some(("gleam", "List", [element])) => {
4888 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
4889 }
4890 Some(("gleam/option", "Option", [some])) => {
4891 eco_format!(
4892 "{module_name}.optional({})",
4893 self.decoder_for(some, indent)
4894 )
4895 }
4896 Some(("gleam/dict", "Dict", [key, value])) => {
4897 eco_format!(
4898 "{module_name}.dict({}, {})",
4899 self.decoder_for(key, indent),
4900 self.decoder_for(value, indent)
4901 )
4902 }
4903 Some((module, name, _))
4904 if module == self.type_module && name == self.type_name =>
4905 {
4906 eco_format!("{}_decoder()", to_snake_case(name))
4907 }
4908 _ => eco_format!(
4909 r#"todo as "Decoder for {}""#,
4910 self.printer.print_type(type_)
4911 ),
4912 }
4913 }
4914 }
4915 }
4916 }
4917
4918 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4919 let decoder = self.decoder_for(field.type_, indent);
4920
4921 eco_format!(
4922 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4923 indent = " ".repeat(indent),
4924 variable = field.label.variable_name(),
4925 field = field.label.field_key(),
4926 module = self.printer.print_module(DECODE_MODULE)
4927 )
4928 }
4929
4930 /// Performs best-effort generation of zero values for a given type.
4931 /// Will succeed for all prelude types and most stdlib types.
4932 /// For specifics on how custom user-defined types are handled, see
4933 /// `zero_value_for_custom_type`.
4934 fn zero_value_for_type(
4935 &mut self,
4936 type_: &Type,
4937 // Keeps track of types visited already to ensure we don't end up in an infinite
4938 // loop due to recursive types
4939 path: &mut Vec<(EcoString, EcoString)>,
4940 modules_to_import: &mut HashSet<EcoString>,
4941 ) -> Option<EcoString> {
4942 if type_.is_bit_array() {
4943 return Some("<<>>".into());
4944 }
4945 if type_.is_bool() {
4946 return Some("False".into());
4947 }
4948 if type_.is_float() {
4949 return Some("0.0".into());
4950 }
4951 if type_.is_int() {
4952 return Some("0".into());
4953 }
4954 if type_.is_string() {
4955 return Some("\"\"".into());
4956 }
4957 if type_.is_list() {
4958 return Some("[]".into());
4959 }
4960 if type_.is_nil() {
4961 return Some("Nil".into());
4962 }
4963
4964 if let Some(types) = type_.tuple_types() {
4965 // We try generating zero values for the tuple members and exit early if any of them fail.
4966 let field_zeroes = types
4967 .iter()
4968 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import))
4969 .collect_vec();
4970
4971 if field_zeroes.len() < types.len() {
4972 return None;
4973 } else {
4974 return Some(eco_format!("#({})", field_zeroes.iter().join(", ")));
4975 }
4976 };
4977
4978 let (module, name, _) = type_.named_type_information()?;
4979 match (module.as_str(), name.as_str()) {
4980 (OPTION_MODULE, "Option") => {
4981 let _ = modules_to_import.insert(OPTION_MODULE.into());
4982 Some(eco_format!(
4983 "{}.None",
4984 self.printer.print_module(OPTION_MODULE)
4985 ))
4986 }
4987
4988 (DYNAMIC_MODULE, "Dynamic") => {
4989 let _ = modules_to_import.insert(DYNAMIC_MODULE.into());
4990 Some(eco_format!(
4991 "{}.nil()",
4992 self.printer.print_module(DYNAMIC_MODULE)
4993 ))
4994 }
4995
4996 (DICT_MODULE, "Dict") => {
4997 let _ = modules_to_import.insert(DICT_MODULE.into());
4998 Some(eco_format!(
4999 "{}.new()",
5000 self.printer.print_module(DICT_MODULE)
5001 ))
5002 }
5003
5004 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import),
5005 }
5006 }
5007
5008 /// Best-effort zero value generation for user-defined types in the
5009 /// current package.
5010 fn zero_value_for_custom_type(
5011 &mut self,
5012 custom_type_module: &EcoString,
5013 custom_type_name: &EcoString,
5014 path: &mut Vec<(EcoString, EcoString)>,
5015 modules_to_import: &mut HashSet<EcoString>,
5016 ) -> Option<EcoString> {
5017 // First we check that we have not already visited this type before to
5018 // avoid cycles.
5019 let already_seen_type = path.iter().any(|(path_module, path_type_name)| {
5020 path_module == custom_type_module && path_type_name == custom_type_name
5021 });
5022
5023 if already_seen_type {
5024 return None;
5025 };
5026
5027 let type_is_inside_current_module = &self.type_module == custom_type_module;
5028
5029 let current_module_interface = self
5030 .compiler
5031 .modules
5032 .get(&self.type_module)
5033 .map(|module| &module.ast.type_info)?;
5034
5035 let type_module_interface = if !type_is_inside_current_module {
5036 self.compiler.get_module_interface(custom_type_module)?
5037 } else {
5038 current_module_interface
5039 };
5040
5041 // We only try and generate zero values for user-defined types in the current
5042 // package. If you are expanding the scope of this functionality to
5043 // remove this limitation, make sure to check for internal modules.
5044 if current_module_interface.package != type_module_interface.package {
5045 return None;
5046 }
5047
5048 let constructors = type_module_interface
5049 .types_value_constructors
5050 .get(custom_type_name)?;
5051
5052 // Opaque types cannot be constructed outside the module they were defined in,
5053 // so we will be unable to produce a zero value.
5054 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque {
5055 return None;
5056 }
5057
5058 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor
5059 // to construct our zero value to reduce visual noise, but this might not always
5060 // be possible. So we check all constructors in increasing order of size to
5061 // find the first one that succeeds, and then short circuit.
5062 constructors
5063 .variants
5064 .iter()
5065 .sorted_by_key(|v| v.parameters.len())
5066 .find_map(|zero_constructor| {
5067 self.zero_value_for_custom_type_constructor(
5068 custom_type_module,
5069 custom_type_name,
5070 zero_constructor,
5071 type_is_inside_current_module,
5072 path,
5073 modules_to_import,
5074 )
5075 })
5076 }
5077
5078 /// Attempts to construct a zero value for one specific constructor
5079 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs
5080 /// _important checks on type visibility that this method does NOT_.
5081 /// This is a helper method extracted for readability.
5082 fn zero_value_for_custom_type_constructor(
5083 &mut self,
5084 custom_type_module: &EcoString,
5085 custom_type_name: &EcoString,
5086 zero_constructor: &type_::TypeValueConstructor,
5087 type_is_inside_current_module: bool,
5088 path: &mut Vec<(EcoString, EcoString)>,
5089 modules_to_import: &mut HashSet<EcoString>,
5090 ) -> Option<EcoString> {
5091 path.push((custom_type_module.clone(), custom_type_name.clone()));
5092
5093 // Try to generate zero values for all fields in the constructor
5094 let zero_params = zero_constructor
5095 .parameters
5096 .iter()
5097 .map_while(|parameter| {
5098 let zero = self.zero_value_for_type(¶meter.type_, path, modules_to_import)?;
5099
5100 if let Some(label) = ¶meter.label {
5101 Some(eco_format!("{label}: {zero}"))
5102 } else {
5103 Some(zero)
5104 }
5105 })
5106 .collect_vec();
5107
5108 // We need to make sure to clean up the path once we've finished
5109 // "visiting" this type.
5110 let _ = path.pop();
5111
5112 // Only proceed if we were able to construct every field successfully
5113 if zero_params.len() < zero_constructor.parameters.len() {
5114 return None;
5115 };
5116
5117 let zero_constructor = if !type_is_inside_current_module {
5118 // Type constructors from other modules need to be qualified appropriately,
5119 // and they might need to be brought into scope.
5120 let _ = modules_to_import.insert(custom_type_module.clone());
5121 eco_format!(
5122 "{}.{}",
5123 self.printer.print_module(custom_type_module),
5124 zero_constructor.name
5125 )
5126 } else {
5127 eco_format!("{}", zero_constructor.name)
5128 };
5129
5130 if zero_params.is_empty() {
5131 Some(eco_format!("{zero_constructor}"))
5132 } else {
5133 let zero_args = zero_params.iter().join(", ");
5134 Some(eco_format!("{zero_constructor}({zero_args})"))
5135 }
5136 }
5137}
5138
5139/// Builder for code action to apply the "Generate to-JSON function" action.
5140///
5141pub struct GenerateJsonEncoder<'a> {
5142 module: &'a Module,
5143 params: &'a CodeActionParams,
5144 edits: TextEdits<'a>,
5145 printer: Printer<'a>,
5146 actions: &'a mut Vec<CodeAction>,
5147 config: &'a PackageConfig,
5148}
5149
5150const JSON_MODULE: &str = "gleam/json";
5151const JSON_PACKAGE_NAME: &str = "gleam_json";
5152
5153#[derive(Eq, PartialEq, Copy, Clone)]
5154enum EncodingMode {
5155 PlainString,
5156 ObjectWithTypeTag,
5157 ObjectWithNoTypeTag,
5158}
5159
5160impl EncodingMode {
5161 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
5162 match type_.constructors.as_slice() {
5163 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
5164 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
5165 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
5166 EncodingMode::PlainString
5167 }
5168 _constructors => EncodingMode::ObjectWithTypeTag,
5169 }
5170 }
5171}
5172
5173impl<'a> GenerateJsonEncoder<'a> {
5174 pub fn new(
5175 module: &'a Module,
5176 line_numbers: &'a LineNumbers,
5177 params: &'a CodeActionParams,
5178 actions: &'a mut Vec<CodeAction>,
5179 config: &'a PackageConfig,
5180 ) -> Self {
5181 // Since we are generating a new function, type variables from other
5182 // functions and constants are irrelevant to the types we print.
5183 let printer = Printer::new_without_type_variables(&module.ast.names);
5184 Self {
5185 module,
5186 params,
5187 edits: TextEdits::new(line_numbers),
5188 printer,
5189 actions,
5190 config,
5191 }
5192 }
5193
5194 pub fn code_actions(&mut self) {
5195 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
5196 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
5197 {
5198 self.visit_typed_module(&self.module.ast);
5199 }
5200 }
5201
5202 fn custom_type_encoder_body(
5203 &mut self,
5204 record_name: EcoString,
5205 custom_type: &CustomType<Arc<Type>>,
5206 ) -> Option<EcoString> {
5207 // We cannot generate a decoder for an external type with no constructors!
5208 let constructors_size = custom_type.constructors.len();
5209 let (first, rest) = custom_type.constructors.split_first()?;
5210 let mode = EncodingMode::for_custom_type(custom_type);
5211
5212 // We generate an encoder for a type with a single constructor: it does not
5213 // require pattern matching on the argument as we can access all its fields
5214 // with the usual record access syntax.
5215 if rest.is_empty() {
5216 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
5217 let unpacking = if first.arguments.is_empty() {
5218 ""
5219 } else {
5220 &eco_format!(
5221 "let {name}({fields}:) = {record_name}\n ",
5222 name = first.name,
5223 fields = first
5224 .arguments
5225 .iter()
5226 .filter_map(|argument| {
5227 argument.label.as_ref().map(|(_location, label)| label)
5228 })
5229 .join(":, ")
5230 )
5231 };
5232 return Some(eco_format!("{unpacking}{encoder}"));
5233 }
5234
5235 // Otherwise we generate an encoder for a type with multiple constructors:
5236 // it will need to pattern match on the various constructors and encode each
5237 // one separately.
5238 let mut clauses = Vec::with_capacity(constructors_size);
5239 for constructor in iter::once(first).chain(rest) {
5240 let RecordConstructor { name, .. } = constructor;
5241 let encoder =
5242 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
5243 if constructor.arguments.is_empty() {
5244 clauses.push(eco_format!(" {name} -> {encoder}"));
5245 } else {
5246 let unpacking = constructor
5247 .arguments
5248 .iter()
5249 .filter_map(|argument| {
5250 argument.label.as_ref().map(|(_location, label)| {
5251 if is_nil_like(&argument.type_) {
5252 eco_format!("{}: _", label)
5253 } else {
5254 eco_format!("{}:", label)
5255 }
5256 })
5257 })
5258 .join(", ");
5259 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}"));
5260 }
5261 }
5262
5263 let clauses = clauses.join("\n");
5264 Some(eco_format!(
5265 "case {record_name} {{
5266{clauses}
5267 }}",
5268 ))
5269 }
5270
5271 fn constructor_encoder(
5272 &mut self,
5273 mode: EncodingMode,
5274 constructor: &TypedRecordConstructor,
5275 type_name: EcoString,
5276 nesting: usize,
5277 ) -> Option<EcoString> {
5278 let json_module = self.printer.print_module(JSON_MODULE);
5279 let tag = to_snake_case(&constructor.name);
5280 let indent = " ".repeat(nesting);
5281
5282 // If the variant is encoded as a simple json string we just call the
5283 // `json.string` with the variant tag as an argument.
5284 if mode == EncodingMode::PlainString {
5285 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
5286 }
5287
5288 // Otherwise we turn it into an object with a `type` tag field.
5289 let mut encoder_printer =
5290 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone());
5291
5292 // These are the fields of the json object to encode.
5293 let mut fields = Vec::with_capacity(constructor.arguments.len());
5294 if mode == EncodingMode::ObjectWithTypeTag {
5295 // Any needed type tag is always going to be the first field in the object
5296 fields.push(eco_format!(
5297 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
5298 ));
5299 }
5300
5301 for argument in constructor.arguments.iter() {
5302 let (_, label) = argument.label.as_ref()?;
5303 let field = RecordField {
5304 label: RecordLabel::Labeled(label),
5305 type_: &argument.type_,
5306 };
5307 let encoder = encoder_printer.encode_field(&field, nesting + 2);
5308 fields.push(encoder);
5309 }
5310
5311 let fields = fields.join(",\n");
5312 Some(eco_format!(
5313 "{json_module}.object([
5314{fields},
5315{indent}])"
5316 ))
5317 }
5318}
5319
5320/// When generating an encoder, we need to know when we can ignore the fields
5321/// destructured from a constructor.
5322/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil`
5323/// types, then we will never need to use the field in our encoder function.
5324fn is_nil_like(type_: &Type) -> bool {
5325 if type_.is_nil() {
5326 return true;
5327 }
5328
5329 match type_.tuple_types() {
5330 Some(types) => types.iter().all(|type_| is_nil_like(type_)),
5331 _ => false,
5332 }
5333}
5334
5335impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
5336 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
5337 let range = self
5338 .edits
5339 .src_span_to_lsp_range(custom_type.full_location());
5340 if !within(self.params.range, range) {
5341 return;
5342 }
5343
5344 let record_name = to_snake_case(&custom_type.name);
5345 let name = eco_format!("{record_name}_to_json");
5346 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
5347 return;
5348 };
5349
5350 let json_type = self.printer.print_type(&Type::Named {
5351 publicity: Publicity::Public,
5352 package: JSON_PACKAGE_NAME.into(),
5353 module: JSON_MODULE.into(),
5354 name: "Json".into(),
5355 arguments: vec![],
5356 inferred_variant: None,
5357 });
5358
5359 let type_ = if custom_type.parameters.is_empty() {
5360 custom_type.name.clone()
5361 } else {
5362 let parameters = custom_type
5363 .parameters
5364 .iter()
5365 .map(|(_, name)| name)
5366 .join(", ");
5367 eco_format!("{}({})", custom_type.name, parameters)
5368 };
5369
5370 let function = format!(
5371 "
5372
5373fn {name}({record_name}: {type_}) -> {json_type} {{
5374 {encoder}
5375}}",
5376 );
5377
5378 self.edits.insert(custom_type.end_position, function);
5379 maybe_import(&mut self.edits, self.module, JSON_MODULE);
5380
5381 CodeActionBuilder::new("Generate to-JSON function")
5382 .kind(CodeActionKind::Refactor)
5383 .preferred(false)
5384 .changes(
5385 self.params.text_document.uri.clone(),
5386 std::mem::take(&mut self.edits.edits),
5387 )
5388 .push_to(self.actions);
5389 }
5390}
5391
5392struct JsonEncoderPrinter<'a, 'b> {
5393 printer: &'a mut Printer<'b>,
5394 /// The name of the root type we are printing an encoder for
5395 type_name: EcoString,
5396 /// The module name of the root type we are printing an encoder for
5397 type_module: EcoString,
5398}
5399
5400impl<'a, 'b> JsonEncoderPrinter<'a, 'b> {
5401 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self {
5402 Self {
5403 type_name,
5404 type_module,
5405 printer,
5406 }
5407 }
5408
5409 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
5410 let module_name = self.printer.print_module(JSON_MODULE);
5411 let is_capture = encoded_value == "_";
5412 let maybe_capture = |mut function: EcoString| {
5413 if is_capture {
5414 function
5415 } else {
5416 function.push('(');
5417 function.push_str(encoded_value);
5418 function.push(')');
5419 function
5420 }
5421 };
5422
5423 if type_.is_bool() {
5424 maybe_capture(eco_format!("{module_name}.bool"))
5425 } else if type_.is_float() {
5426 maybe_capture(eco_format!("{module_name}.float"))
5427 } else if type_.is_int() {
5428 maybe_capture(eco_format!("{module_name}.int"))
5429 } else if type_.is_string() {
5430 maybe_capture(eco_format!("{module_name}.string"))
5431 } else if type_.is_nil() {
5432 if is_capture {
5433 eco_format!("fn(_) {{ {module_name}.null() }}")
5434 } else {
5435 eco_format!("{module_name}.null()")
5436 }
5437 } else {
5438 match type_.tuple_types() {
5439 Some(types) => {
5440 let (tuple, new_indent) = if is_capture {
5441 ("value", indent + 4)
5442 } else {
5443 (encoded_value, indent + 2)
5444 };
5445
5446 // We need to iterate over all of the tuple's fields
5447 // to obtain an encoder for each one, so we reuse the
5448 // iteration to check whether this tuple can be ignored
5449 // in the encoder without calling `is_nil_like`.
5450 let mut encoders = Vec::new();
5451 let all_values_are_nil = types.iter().enumerate().fold(
5452 true,
5453 |all_values_are_nil, (index, type_)| {
5454 encoders.push(self.encoder_for(
5455 &format!("{tuple}.{index}"),
5456 type_,
5457 new_indent,
5458 ));
5459 all_values_are_nil && is_nil_like(type_)
5460 },
5461 );
5462
5463 if is_capture {
5464 eco_format!(
5465 "fn({value}) {{
5466{indent} {module_name}.preprocessed_array([
5467{indent} {encoders},
5468{indent} ])
5469{indent}}}",
5470 value = if all_values_are_nil { "_" } else { "value" },
5471 indent = " ".repeat(indent),
5472 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5473 )
5474 } else {
5475 eco_format!(
5476 "{module_name}.preprocessed_array([
5477{indent} {encoders},
5478{indent}])",
5479 indent = " ".repeat(indent),
5480 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5481 )
5482 }
5483 }
5484 _ => {
5485 let type_information = type_.named_type_information();
5486 let type_information: Option<(&str, &str, &[Arc<Type>])> =
5487 type_information.as_ref().map(|(module, name, arguments)| {
5488 (module.as_str(), name.as_str(), arguments.as_slice())
5489 });
5490
5491 match type_information {
5492 Some(("gleam", "List", [element])) => {
5493 eco_format!(
5494 "{module_name}.array({encoded_value}, {map_function})",
5495 map_function = self.encoder_for("_", element, indent)
5496 )
5497 }
5498 Some(("gleam/option", "Option", [some])) => {
5499 eco_format!(
5500 "case {encoded_value} {{
5501{indent} {none} -> {module_name}.null()
5502{indent} {some}({value}) -> {encoder}
5503{indent}}}",
5504 indent = " ".repeat(indent),
5505 none = self
5506 .printer
5507 .print_constructor(&"gleam/option".into(), &"None".into()),
5508 some = self
5509 .printer
5510 .print_constructor(&"gleam/option".into(), &"Some".into()),
5511 value = if is_nil_like(some) { "_" } else { "value" },
5512 encoder = self.encoder_for("value", some, indent + 2)
5513 )
5514 }
5515 Some(("gleam/dict", "Dict", [key, value])) => {
5516 let stringify_function = match key
5517 .named_type_information()
5518 .as_ref()
5519 .map(|(module, name, arguments)| {
5520 (module.as_str(), name.as_str(), arguments.as_slice())
5521 }) {
5522 Some(("gleam", "String", [])) => "fn(string) { string }",
5523 _ => &format!(
5524 r#"todo as "Function to stringify {}""#,
5525 self.printer.print_type(key)
5526 ),
5527 };
5528 eco_format!(
5529 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
5530 self.encoder_for("_", value, indent)
5531 )
5532 }
5533 Some((module, name, _))
5534 if module == self.type_module && name == self.type_name =>
5535 {
5536 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
5537 }
5538 _ => eco_format!(
5539 r#"todo as "Encoder for {}""#,
5540 self.printer.print_type(type_)
5541 ),
5542 }
5543 }
5544 }
5545 }
5546 }
5547
5548 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
5549 let field_name = field.label.variable_name();
5550 let encoder = self.encoder_for(&field_name, field.type_, indent);
5551
5552 eco_format!(
5553 r#"{indent}#("{field_name}", {encoder})"#,
5554 indent = " ".repeat(indent),
5555 )
5556 }
5557}
5558
5559/// Builder for code action to pattern match on things like (anonymous) function
5560/// arguments or variables.
5561/// For example:
5562///
5563/// ```gleam
5564/// pub fn wibble(arg: #(key, value)) {
5565/// // ^ [pattern match on argument]
5566/// }
5567///
5568/// // Generates
5569///
5570/// pub fn wibble(arg: #(key, value)) {
5571/// let #(value_0, value_1) = arg
5572/// }
5573/// ```
5574///
5575/// Another example with variables:
5576///
5577/// ```gleam
5578/// pub fn main() {
5579/// let pair = #(1, 3)
5580/// // ^ [pattern match on value]
5581/// }
5582///
5583/// // Generates
5584///
5585/// pub fn main() {
5586/// let pair = #(1, 3)
5587/// let #(value_0, value_1) = pair
5588/// }
5589/// ```
5590///
5591pub struct PatternMatchOnValue<'a, A> {
5592 module: &'a Module,
5593 params: &'a CodeActionParams,
5594 compiler: &'a LspProjectCompiler<A>,
5595 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>,
5596 selected_value: Option<PatternMatchedValue<'a>>,
5597 edits: TextEdits<'a>,
5598}
5599
5600/// A value we might want to pattern match on.
5601/// Each variant will also contain all the info needed to know how to properly
5602/// print and format the corresponding pattern matching code; that's why you'll
5603/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
5604///
5605#[derive(Clone)]
5606pub enum PatternMatchedValue<'a> {
5607 /// A statement we can match on. For example, function calls, record
5608 /// and tuple accesses:
5609 ///
5610 /// ```gleam
5611 /// pub fn wibble() {
5612 /// wobble(1, 2)
5613 /// //^^^^ This
5614 /// }
5615 /// ```
5616 ///
5617 Statement {
5618 /// The span covering the entire statement:
5619 /// ```gleam
5620 /// wobble(1, 2)
5621 /// //^^^^^^^^^^^^ This
5622 /// ```
5623 location: SrcSpan,
5624 /// The statement's type defining what we will be pattern matching
5625 /// on.
5626 type_: Arc<Type>,
5627 },
5628 FunctionArgument {
5629 /// The argument being pattern matched on.
5630 ///
5631 arg: &'a TypedArg,
5632 /// The first statement inside the function body. Used to correctly
5633 /// position the inserted pattern matching.
5634 ///
5635 first_statement: &'a TypedStatement,
5636 /// The range of the entire function holding the argument.
5637 ///
5638 function_range: Range,
5639 },
5640 LetVariable {
5641 variable_name: &'a EcoString,
5642 variable_type: Arc<Type>,
5643 /// The location of the entire let assignment the variable is part of,
5644 /// so that we can add the pattern matching _after_ it.
5645 ///
5646 assignment_location: SrcSpan,
5647 },
5648 /// A variable that is bound in a case branch's pattern. For example:
5649 /// ```gleam
5650 /// case wibble {
5651 /// wobble -> 1
5652 /// // ^^^^^ This!
5653 /// }
5654 /// ```
5655 ///
5656 ClausePatternVariable {
5657 variable_type: Arc<Type>,
5658 variable_location: PatternLocation,
5659 clause_location: SrcSpan,
5660 /// All the names in the clause that are already taken by pattern variables.
5661 /// We need this to avoid generating invalid code were two pattern variables
5662 /// have the same name.
5663 ///
5664 /// For example:
5665 ///
5666 /// ```gleam
5667 /// case wibble {
5668 /// [first, ..rest] -> todo
5669 /// ^^^^^ When expanding `first` we can't add any variable pattern
5670 /// called `rest` as it would clash with the `rest` tail that is
5671 /// already there.
5672 /// }
5673 /// ```
5674 ///
5675 bound_variables: Vec<BoundVariable>,
5676 },
5677 UseVariable {
5678 variable_name: &'a EcoString,
5679 variable_type: Arc<Type>,
5680 /// The location of the entire use expression the variable is part of,
5681 /// so that we can add the pattern matching _after_ it.
5682 ///
5683 use_location: SrcSpan,
5684 },
5685}
5686
5687#[derive(Clone)]
5688pub enum PatternLocation {
5689 /// Any pattern that doesn't need any special handling.
5690 ///
5691 Regular { location: SrcSpan },
5692 /// List tails need some care to not generate invalid syntax when pattern
5693 /// matched on in case expressions.
5694 ///
5695 ListTail {
5696 /// This location covers the entire list tail pattern, including the `..`
5697 location: SrcSpan,
5698 },
5699 /// When the pattern being matched is a discard. For example:
5700 /// ```gleam
5701 /// case wibble {
5702 /// Ok(_) -> todo
5703 /// // ^ Hovering this!
5704 /// }
5705 /// ```
5706 Discard { location: SrcSpan },
5707}
5708
5709impl PatternLocation {
5710 fn regular(location: SrcSpan) -> Self {
5711 Self::Regular { location }
5712 }
5713
5714 fn is_discard(&self) -> bool {
5715 match self {
5716 PatternLocation::Regular { .. } | PatternLocation::ListTail { .. } => false,
5717 PatternLocation::Discard { .. } => true,
5718 }
5719 }
5720}
5721
5722impl<'a, IO> PatternMatchOnValue<'a, IO> {
5723 pub fn new(
5724 module: &'a Module,
5725 line_numbers: &'a LineNumbers,
5726 params: &'a CodeActionParams,
5727 compiler: &'a LspProjectCompiler<IO>,
5728 ) -> Self {
5729 Self {
5730 module,
5731 params,
5732 compiler,
5733 selected_value: None,
5734 pattern_variable_under_cursor: None,
5735 edits: TextEdits::new(line_numbers),
5736 }
5737 }
5738
5739 pub fn code_actions(mut self) -> Vec<CodeAction> {
5740 self.visit_typed_module(&self.module.ast);
5741
5742 let action_title = match self.selected_value.clone() {
5743 Some(PatternMatchedValue::FunctionArgument {
5744 arg,
5745 first_statement: function_body,
5746 function_range,
5747 }) => {
5748 self.match_on_function_argument(arg, function_body, function_range);
5749 "Pattern match on argument"
5750 }
5751 Some(
5752 PatternMatchedValue::LetVariable {
5753 variable_name,
5754 variable_type,
5755 assignment_location: location,
5756 }
5757 | PatternMatchedValue::UseVariable {
5758 variable_name,
5759 variable_type,
5760 use_location: location,
5761 },
5762 ) => {
5763 self.match_on_let_variable(variable_name, variable_type, location);
5764 "Pattern match on variable"
5765 }
5766
5767 Some(PatternMatchedValue::Statement { location, type_ }) => {
5768 self.match_on_statement(location, type_);
5769 "Pattern match on value"
5770 }
5771
5772 Some(PatternMatchedValue::ClausePatternVariable {
5773 variable_type,
5774 variable_location,
5775 clause_location,
5776 bound_variables,
5777 }) => {
5778 let title = if variable_location.is_discard() {
5779 "Pattern match on value"
5780 } else {
5781 "Pattern match on variable"
5782 };
5783
5784 self.match_on_clause_variable(
5785 variable_type,
5786 variable_location,
5787 clause_location,
5788 &bound_variables,
5789 );
5790
5791 title
5792 }
5793
5794 None => return vec![],
5795 };
5796
5797 if self.edits.edits.is_empty() {
5798 return vec![];
5799 }
5800
5801 let mut action = Vec::with_capacity(1);
5802 CodeActionBuilder::new(action_title)
5803 .kind(CodeActionKind::RefactorRewrite)
5804 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5805 .preferred(false)
5806 .push_to(&mut action);
5807 action
5808 }
5809
5810 fn match_on_function_argument(
5811 &mut self,
5812 arg: &TypedArg,
5813 first_statement: &TypedStatement,
5814 function_range: Range,
5815 ) {
5816 let Some(arg_name) = arg.get_variable_name() else {
5817 return;
5818 };
5819
5820 let Some(patterns) =
5821 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new())
5822 else {
5823 return;
5824 };
5825
5826 let first_statement_location = first_statement.location();
5827 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
5828
5829 // If we're trying to insert the pattern matching on the same
5830 // line as the one where the function is defined we will want to
5831 // put it on a new line instead. So in that case the nesting will
5832 // be the default 2 spaces.
5833 let needs_newline = function_range.start.line == first_statement_range.start.line;
5834 let nesting = if needs_newline {
5835 String::from(" ")
5836 } else {
5837 " ".repeat(first_statement_range.start.character as usize)
5838 };
5839
5840 let pattern_matching = if patterns.len() == 1 {
5841 let pattern = patterns.first();
5842 format!("let {pattern} = {arg_name}")
5843 } else {
5844 let patterns = patterns
5845 .iter()
5846 .map(|p| format!(" {nesting}{p} -> todo"))
5847 .join("\n");
5848 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
5849 };
5850
5851 let pattern_matching = if needs_newline {
5852 format!("\n{nesting}{pattern_matching}")
5853 } else {
5854 pattern_matching
5855 };
5856
5857 let has_empty_body = match first_statement {
5858 ast::Statement::Expression(TypedExpr::Todo {
5859 kind: TodoKind::EmptyFunction { .. },
5860 ..
5861 }) => true,
5862 ast::Statement::Expression(_)
5863 | ast::Statement::Assignment(_)
5864 | ast::Statement::Use(_)
5865 | ast::Statement::Assert(_) => false,
5866 };
5867
5868 // If the pattern matching is added to a function with an empty
5869 // body then we do not add any nesting after it, or we would be
5870 // increasing the nesting of the closing `}`!
5871 let pattern_matching = if has_empty_body {
5872 format!("{pattern_matching}\n")
5873 } else {
5874 format!("{pattern_matching}\n{nesting}")
5875 };
5876
5877 self.edits
5878 .insert(first_statement_location.start, pattern_matching);
5879 }
5880
5881 fn match_on_let_variable(
5882 &mut self,
5883 variable_name: &EcoString,
5884 variable_type: Arc<Type>,
5885 assignment_location: SrcSpan,
5886 ) {
5887 let Some(patterns) =
5888 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new())
5889 else {
5890 return;
5891 };
5892
5893 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
5894 let nesting = " ".repeat(assignment_range.start.character as usize);
5895 let pattern_matching = if patterns.len() == 1 {
5896 let pattern = patterns.first();
5897 format!("let {pattern} = {variable_name}")
5898 } else {
5899 let patterns = patterns
5900 .iter()
5901 .map(|p| format!(" {nesting}{p} -> todo"))
5902 .join("\n");
5903 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
5904 };
5905
5906 self.edits.insert(
5907 assignment_location.end,
5908 format!("\n{nesting}{pattern_matching}"),
5909 );
5910 }
5911
5912 fn match_on_statement(&mut self, statement_location: SrcSpan, type_: Arc<Type>) {
5913 let Some(patterns) =
5914 self.type_to_destructure_patterns(type_.as_ref(), &mut NameGenerator::new())
5915 else {
5916 return;
5917 };
5918
5919 if patterns.len() == 1 {
5920 let pattern = patterns.first();
5921 self.edits
5922 .insert(statement_location.start, format!("let {pattern} = "));
5923 } else {
5924 let statement_range = self.edits.src_span_to_lsp_range(statement_location);
5925 let nesting = " ".repeat(statement_range.start.character as usize);
5926 let patterns = patterns
5927 .iter()
5928 .map(|p| format!(" {nesting}{p} -> todo"))
5929 .join("\n");
5930 self.edits.insert(statement_location.start, "case ".into());
5931 self.edits.insert(
5932 statement_location.end,
5933 format!(" {{\n{patterns}\n{nesting}}}"),
5934 )
5935 }
5936 }
5937
5938 fn match_on_clause_variable(
5939 &mut self,
5940 variable_type: Arc<Type>,
5941 variable_location: PatternLocation,
5942 clause_location: SrcSpan,
5943 bound_variables: &[BoundVariable],
5944 ) {
5945 let mut names = NameGenerator::new();
5946 names.reserve_bound_variables(bound_variables);
5947
5948 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) {
5949 // Here we're dealing with a special case: if someone wants to expand the tail
5950 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`.
5951 // That would result in invalid syntax. So we have to generate list patterns
5952 // that have no square brackets.
5953 let first = names.rename_to_avoid_shadowing("first".into());
5954 let rest = names.rename_to_avoid_shadowing("rest".into());
5955 vec1!["".into(), eco_format!("{first}, ..{rest}")]
5956 } else if let Some(patterns) =
5957 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names)
5958 {
5959 patterns
5960 } else {
5961 return;
5962 };
5963
5964 let clause_range = self.edits.src_span_to_lsp_range(clause_location);
5965 let nesting = " ".repeat(clause_range.start.character as usize);
5966
5967 let variable_location = match variable_location {
5968 PatternLocation::Regular { location }
5969 | PatternLocation::ListTail { location }
5970 | PatternLocation::Discard { location } => location,
5971 };
5972
5973 let variable_start = (variable_location.start - clause_location.start) as usize;
5974 let variable_end = variable_start + variable_location.len();
5975
5976 let clause_code = code_at(self.module, clause_location);
5977 let patterns = patterns
5978 .iter()
5979 .map(|pattern| {
5980 let mut clause_code = clause_code.to_string();
5981 // If we're replacing a variable that's using the shorthand
5982 // syntax we want to add a space to separate it from the
5983 // preceding `:`.
5984 let pattern = if variable_start == variable_end {
5985 &eco_format!(" {pattern}")
5986 } else {
5987 pattern
5988 };
5989
5990 clause_code.replace_range(variable_start..variable_end, pattern);
5991 clause_code
5992 })
5993 .join(&format!("\n{nesting}"));
5994
5995 self.edits.replace(clause_location, patterns);
5996 }
5997
5998 /// Will produce a pattern that can be used on the left hand side of a let
5999 /// assignment to destructure a value of the given type. For example given
6000 /// this type:
6001 ///
6002 /// ```gleam
6003 /// pub type Wibble {
6004 /// Wobble(Int, label: String)
6005 /// }
6006 /// ```
6007 ///
6008 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
6009 /// The pattern will use the correct qualified/unqualified name for the
6010 /// constructor if it comes from another package.
6011 ///
6012 /// The function will only produce a list of patterns that can be used from
6013 /// the current module. So if the type comes from another module it must be
6014 /// public! Otherwise this function will return an empty vec.
6015 ///
6016 fn type_to_destructure_patterns(
6017 &mut self,
6018 type_: &Type,
6019 names: &mut NameGenerator,
6020 ) -> Option<Vec1<EcoString>> {
6021 match type_ {
6022 Type::Fn { .. } => None,
6023
6024 // Pattern matchin on `Nil` is not all that useful.
6025 Type::Named { .. } if type_.is_nil() => None,
6026
6027 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names),
6028
6029 // We special case lists, they don't have "regular" constructors
6030 // like other types. Instead we always add the two clauses covering
6031 // the empty and non empty list.
6032 Type::Named { .. } if type_.is_list() => {
6033 let first = names.rename_to_avoid_shadowing("first".into());
6034 let rest = names.rename_to_avoid_shadowing("rest".into());
6035 Some(vec1![
6036 EcoString::from("[]"),
6037 eco_format!("[{first}, ..{rest}]")
6038 ])
6039 }
6040
6041 Type::Named {
6042 module: type_module,
6043 name: type_name,
6044 ..
6045 } => {
6046 let mut patterns = vec![];
6047 let constructors =
6048 get_type_constructors(self.compiler, &self.module.name, type_module, type_name);
6049 for constructor in constructors {
6050 let names_before = names.clone();
6051 if let Some(pattern) =
6052 self.record_constructor_to_destructure_pattern(constructor, names)
6053 {
6054 patterns.push(pattern);
6055 }
6056 *names = names_before;
6057 }
6058
6059 Vec1::try_from_vec(patterns).ok()
6060 }
6061
6062 // We don't want to suggest this action for empty tuple as it
6063 // doesn't make a lot of sense to match on those.
6064 Type::Tuple { elements } if elements.is_empty() => None,
6065 Type::Tuple { elements } => {
6066 let elements = elements
6067 .iter()
6068 .map(|element| names.generate_name_from_type(element))
6069 .join(", ");
6070 Some(vec1![eco_format!("#({elements})")])
6071 }
6072 }
6073 }
6074
6075 fn type_var_to_destructure_patterns(
6076 &mut self,
6077 type_var: &TypeVar,
6078 names: &mut NameGenerator,
6079 ) -> Option<Vec1<EcoString>> {
6080 match type_var {
6081 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
6082 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names),
6083 }
6084 }
6085
6086 /// Given the value constructor of a record, returns a string with the
6087 /// pattern used to match on that specific variant.
6088 ///
6089 /// Note how:
6090 /// - If the constructor is internal to another module or comes from another
6091 /// module, then this returns `None` since one cannot pattern match on it.
6092 /// - If the provided `ValueConstructor` is not a record constructor this
6093 /// will return `None`.
6094 ///
6095 fn record_constructor_to_destructure_pattern(
6096 &self,
6097 constructor: &ValueConstructor,
6098 names: &mut NameGenerator,
6099 ) -> Option<EcoString> {
6100 let type_::ValueConstructorVariant::Record {
6101 name: constructor_name,
6102 arity: constructor_arity,
6103 module: constructor_module,
6104 field_map,
6105 ..
6106 } = &constructor.variant
6107 else {
6108 // The constructor should always be a record, in case it's not
6109 // there's not much we can do and just fail.
6110 return None;
6111 };
6112
6113 // Since the constructor is a record constructor we know that its type
6114 // is either `Named` or a `Fn` type, in either case we have to get the
6115 // arguments types out of it.
6116 let Some(arguments_types) = constructor
6117 .type_
6118 .fn_types()
6119 .map(|(arguments_types, _return)| arguments_types)
6120 .or_else(|| constructor.type_.constructor_types())
6121 else {
6122 // This should never happen but just in case we don't want to unwrap
6123 // and panic.
6124 return None;
6125 };
6126
6127 let index_to_label = match field_map {
6128 None => HashMap::new(),
6129 Some(field_map) => {
6130 names.reserve_all_labels(field_map);
6131
6132 field_map
6133 .fields
6134 .iter()
6135 .map(|(label, index)| (index, label))
6136 .collect::<HashMap<_, _>>()
6137 }
6138 };
6139
6140 let mut pattern =
6141 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
6142
6143 if *constructor_arity == 0 {
6144 return Some(pattern);
6145 }
6146
6147 pattern.push('(');
6148 let arguments = (0..*constructor_arity as u32)
6149 .map(|i| match index_to_label.get(&i) {
6150 Some(label) => eco_format!("{label}:"),
6151 None => match arguments_types.get(i as usize) {
6152 None => names.rename_to_avoid_shadowing(EcoString::from("value")),
6153 Some(type_) => names.generate_name_from_type(type_),
6154 },
6155 })
6156 .join(", ");
6157
6158 pattern.push_str(&arguments);
6159 pattern.push(')');
6160 Some(pattern)
6161 }
6162}
6163
6164fn code_at(module: &Module, span: SrcSpan) -> &str {
6165 module
6166 .code
6167 .get(span.start as usize..span.end as usize)
6168 .expect("code location must be valid")
6169}
6170
6171impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> {
6172 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6173 // If we're not inside the function there's no point in exploring its
6174 // ast further.
6175 let function_span = SrcSpan {
6176 start: fun.location.start,
6177 end: fun.end_position,
6178 };
6179 let function_range = self.edits.src_span_to_lsp_range(function_span);
6180 if !within(self.params.range, function_range) {
6181 return;
6182 }
6183
6184 for arg in &fun.arguments {
6185 // If the cursor is placed on one of the arguments, then we can try
6186 // and generate code for that one.
6187 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
6188 if within(self.params.range, arg_range)
6189 && let Some(first_statement) = fun.body.first()
6190 {
6191 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
6192 arg,
6193 first_statement,
6194 function_range,
6195 });
6196 return;
6197 }
6198 }
6199
6200 // If the cursor is not on any of the function arguments then we keep
6201 // exploring the function body as we might want to destructure the
6202 // argument of an expression function!
6203 ast::visit::visit_typed_function(self, fun);
6204 }
6205
6206 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
6207 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
6208 if !within(self.params.range, statement_range) {
6209 return;
6210 }
6211
6212 ast::visit::visit_typed_statement(self, statement);
6213 match statement {
6214 // If we haven't found any more specific selected expression, then
6215 // we are going to select the statement to pattern match on (if it
6216 // can be meaningfully pattern matched on).
6217 ast::Statement::Expression(
6218 TypedExpr::Call { type_, .. }
6219 | TypedExpr::ModuleSelect { type_, .. }
6220 | TypedExpr::RecordAccess { type_, .. }
6221 | TypedExpr::TupleIndex { type_, .. },
6222 ) if self.selected_value.is_none() => {
6223 self.selected_value = Some(PatternMatchedValue::Statement {
6224 location: statement.location(),
6225 type_: type_.clone(),
6226 })
6227 }
6228
6229 ast::Statement::Expression(_)
6230 | ast::Statement::Assignment(_)
6231 | ast::Statement::Use(_)
6232 | ast::Statement::Assert(_) => (),
6233 }
6234 }
6235
6236 fn visit_typed_expr_fn(
6237 &mut self,
6238 location: &'ast SrcSpan,
6239 type_: &'ast Arc<Type>,
6240 kind: &'ast FunctionLiteralKind,
6241 arguments: &'ast [TypedArg],
6242 body: &'ast Vec1<TypedStatement>,
6243 return_annotation: &'ast Option<ast::TypeAst>,
6244 ) {
6245 // If we're not inside the function there's no point in exploring its
6246 // ast further.
6247 let function_range = self.edits.src_span_to_lsp_range(*location);
6248 if !within(self.params.range, function_range) {
6249 return;
6250 }
6251
6252 for argument in arguments {
6253 // If the cursor is placed on one of the arguments, then we can try
6254 // and generate code for that one.
6255 let arg_range = self.edits.src_span_to_lsp_range(argument.location);
6256 if within(self.params.range, arg_range) {
6257 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
6258 arg: argument,
6259 first_statement: body.first(),
6260 function_range,
6261 });
6262 return;
6263 }
6264 }
6265
6266 // If the cursor is not on any of the function arguments then we keep
6267 // exploring the function body as we might want to destructure the
6268 // argument of an expression function!
6269 ast::visit::visit_typed_expr_fn(
6270 self,
6271 location,
6272 type_,
6273 kind,
6274 arguments,
6275 body,
6276 return_annotation,
6277 );
6278 }
6279
6280 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
6281 // If we're not inside the assignment there's no point in exploring its
6282 // ast further.
6283 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
6284 if !within(self.params.range, assignment_range) {
6285 return;
6286 }
6287
6288 ast::visit::visit_typed_assignment(self, assignment);
6289 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor
6290 // We must make sure that no other value was selected while visiting
6291 // this. If it were `Some` that means that we have found _another_
6292 // variable to match on inside the assignmemt itself. For example:
6293 // ```gleam
6294 // let a = {
6295 // let b = todo
6296 // // ^ We're matching on this, not the outer one!
6297 // }
6298 // ```
6299 && self.selected_value.is_none()
6300 {
6301 self.selected_value = Some(PatternMatchedValue::LetVariable {
6302 variable_name: name,
6303 variable_type: type_.clone(),
6304 assignment_location: assignment.location,
6305 });
6306 }
6307 }
6308
6309 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
6310 // If we're not inside the clause there's no point in exploring its
6311 // ast further.
6312 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
6313 if !within(self.params.range, clause_range) {
6314 return;
6315 }
6316
6317 for pattern in clause.pattern.iter() {
6318 self.visit_typed_pattern(pattern);
6319 }
6320 for patterns in clause.alternative_patterns.iter() {
6321 for pattern in patterns {
6322 self.visit_typed_pattern(pattern);
6323 }
6324 }
6325
6326 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() {
6327 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable {
6328 variable_type: type_,
6329 variable_location,
6330 clause_location: clause.location(),
6331 bound_variables: clause.bound_variables().collect_vec(),
6332 });
6333 } else {
6334 self.visit_typed_expr(&clause.then);
6335 }
6336 }
6337
6338 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
6339 if let Some(assignments) = use_.callback_arguments() {
6340 for variable in assignments {
6341 let ast::Arg {
6342 names: ArgNames::Named { name, .. },
6343 location: variable_location,
6344 type_,
6345 ..
6346 } = variable
6347 else {
6348 continue;
6349 };
6350
6351 // If we use a pattern in a use assignment, that will end up
6352 // being called `_use` something. We don't want to offer the
6353 // action when hovering a pattern so we ignore those.
6354 if name.starts_with("_use") {
6355 continue;
6356 }
6357
6358 let variable_range = self.edits.src_span_to_lsp_range(*variable_location);
6359 if within(self.params.range, variable_range) {
6360 self.selected_value = Some(PatternMatchedValue::UseVariable {
6361 variable_name: name,
6362 variable_type: type_.clone(),
6363 use_location: use_.location,
6364 });
6365 // If we've found the variable to pattern match on, there's no
6366 // point in keeping traversing the AST.
6367 return;
6368 }
6369 }
6370 }
6371
6372 ast::visit::visit_typed_use(self, use_);
6373 }
6374
6375 fn visit_typed_pattern_discard(
6376 &mut self,
6377 location: &'ast SrcSpan,
6378 name: &'ast EcoString,
6379 type_: &'ast Arc<Type>,
6380 ) {
6381 if within(
6382 self.params.range,
6383 self.edits.src_span_to_lsp_range(*location),
6384 ) {
6385 let location = PatternLocation::Discard {
6386 location: *location,
6387 };
6388 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
6389 }
6390 }
6391
6392 fn visit_typed_pattern_variable(
6393 &mut self,
6394 location: &'ast SrcSpan,
6395 name: &'ast EcoString,
6396 type_: &'ast Arc<Type>,
6397 _origin: &'ast VariableOrigin,
6398 ) {
6399 if within(
6400 self.params.range,
6401 self.edits.src_span_to_lsp_range(*location),
6402 ) {
6403 let location = PatternLocation::regular(*location);
6404 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
6405 }
6406 }
6407
6408 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
6409 if let Some(name) = arg.label_shorthand_name()
6410 && within(
6411 self.params.range,
6412 self.edits.src_span_to_lsp_range(arg.location),
6413 )
6414 {
6415 let location = PatternLocation::regular(SrcSpan {
6416 start: arg.location.end,
6417 end: arg.location.end,
6418 });
6419 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_()));
6420 return;
6421 }
6422
6423 ast::visit::visit_typed_pattern_call_arg(self, arg);
6424 }
6425
6426 fn visit_typed_pattern_string_prefix(
6427 &mut self,
6428 _location: &'ast SrcSpan,
6429 _left_location: &'ast SrcSpan,
6430 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
6431 right_location: &'ast SrcSpan,
6432 _left_side_string: &'ast EcoString,
6433 right_side_assignment: &'ast AssignName,
6434 ) {
6435 if let Some((name, location)) = left_side_assignment
6436 && within(
6437 self.params.range,
6438 self.edits.src_span_to_lsp_range(*location),
6439 )
6440 {
6441 let location = PatternLocation::regular(*location);
6442 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6443 } else if let AssignName::Variable(name) = right_side_assignment
6444 && within(
6445 self.params.range,
6446 self.edits.src_span_to_lsp_range(*right_location),
6447 )
6448 {
6449 let location = PatternLocation::regular(*right_location);
6450 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6451 }
6452 }
6453
6454 fn visit_typed_pattern_list(
6455 &mut self,
6456 location: &'ast SrcSpan,
6457 elements: &'ast Vec<TypedPattern>,
6458 tail: &'ast Option<Box<TypedTailPattern>>,
6459 type_: &'ast Arc<Type>,
6460 ) {
6461 let (name, tail_location, tail_type) = if let Some(tail) = tail
6462 && let Pattern::Variable { name, type_, .. } = &tail.pattern
6463 {
6464 (name, tail.location, type_)
6465 } else {
6466 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6467 return;
6468 };
6469
6470 let tail_range = self.edits.src_span_to_lsp_range(tail_location);
6471 if !within(self.params.range, tail_range) {
6472 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6473 return;
6474 }
6475
6476 let location = PatternLocation::ListTail {
6477 location: tail_location,
6478 };
6479 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone()))
6480 }
6481}
6482
6483/// Given a type and its module, returns a list of its *importable*
6484/// constructors.
6485///
6486/// Since this focuses just on importable constructors, if either the module or
6487/// the type are internal the returned array will be empty!
6488///
6489fn get_type_constructors<'a, 'b, IO>(
6490 compiler: &'a LspProjectCompiler<IO>,
6491 current_module: &'b EcoString,
6492 type_module: &'b EcoString,
6493 type_name: &'b EcoString,
6494) -> Vec<&'a ValueConstructor> {
6495 let type_is_inside_current_module = current_module == type_module;
6496 let module_interface = if !type_is_inside_current_module {
6497 // If the type is outside of the module we're in, we can only pattern
6498 // match on it if the module can be imported.
6499 // The `get_module_interface` already takes care of making this check.
6500 compiler.get_module_interface(type_module)
6501 } else {
6502 // However, if the type is defined in the module we're in, we can always
6503 // pattern match on it. So we get the current module's interface.
6504 compiler
6505 .modules
6506 .get(current_module)
6507 .map(|module| &module.ast.type_info)
6508 };
6509
6510 let Some(module_interface) = module_interface else {
6511 return vec![];
6512 };
6513
6514 // If the type is in an internal module that is not the current one, we
6515 // cannot use its constructors!
6516 if !type_is_inside_current_module && module_interface.is_internal {
6517 return vec![];
6518 }
6519
6520 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
6521 return vec![];
6522 };
6523
6524 constructors
6525 .variants
6526 .iter()
6527 .filter_map(|variant| {
6528 let constructor = module_interface.values.get(&variant.name)?;
6529 if type_is_inside_current_module || constructor.publicity.is_public() {
6530 Some(constructor)
6531 } else {
6532 None
6533 }
6534 })
6535 .collect_vec()
6536}
6537
6538/// Returns a pretty printed record constructor name, the way it would be used
6539/// inside the given `module` (with the correct name and qualification).
6540///
6541/// If the constructor cannot be used inside the module because it's not
6542/// imported, then this function will return `None`.
6543///
6544fn pretty_constructor_name(
6545 module: &Module,
6546 constructor_module: &EcoString,
6547 constructor_name: &EcoString,
6548) -> Option<EcoString> {
6549 match module
6550 .ast
6551 .names
6552 .named_constructor(constructor_module, constructor_name)
6553 {
6554 type_::printer::NameContextInformation::Unimported(_, _) => None,
6555 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
6556 Some(eco_format!("{constructor_name}"))
6557 }
6558 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
6559 Some(eco_format!("{module_name}.{constructor_name}"))
6560 }
6561 }
6562}
6563
6564/// Builder for the "generate function" code action.
6565/// Whenever someone hovers an invalid expression that is inferred to have a
6566/// function type the language server can generate a function definition for it.
6567/// For example:
6568///
6569/// ```gleam
6570/// pub fn main() {
6571/// wibble(1, 2, "hello")
6572/// // ^ [generate function]
6573/// }
6574/// ```
6575///
6576/// Will generate the following definition:
6577///
6578/// ```gleam
6579/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
6580/// todo
6581/// }
6582/// ```
6583///
6584pub struct GenerateFunction<'a> {
6585 module: &'a Module,
6586 modules: &'a std::collections::HashMap<EcoString, Module>,
6587 params: &'a CodeActionParams,
6588 edits: TextEdits<'a>,
6589 last_visited_definition_end: Option<u32>,
6590 function_to_generate: Option<FunctionToGenerate<'a>>,
6591}
6592
6593struct FunctionToGenerate<'a> {
6594 module: Option<&'a str>,
6595 name: &'a str,
6596 arguments_types: Vec<Arc<Type>>,
6597
6598 /// The arguments actually supplied as input to the function, if any.
6599 /// A function to generate might as well be just a name passed as an argument
6600 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
6601 /// have any actual arguments!
6602 given_arguments: Option<&'a [TypedCallArg]>,
6603 return_type: Arc<Type>,
6604 previous_definition_end: Option<u32>,
6605}
6606
6607impl<'a> GenerateFunction<'a> {
6608 pub fn new(
6609 module: &'a Module,
6610 modules: &'a std::collections::HashMap<EcoString, Module>,
6611 line_numbers: &'a LineNumbers,
6612 params: &'a CodeActionParams,
6613 ) -> Self {
6614 Self {
6615 module,
6616 modules,
6617 params,
6618 edits: TextEdits::new(line_numbers),
6619 last_visited_definition_end: None,
6620 function_to_generate: None,
6621 }
6622 }
6623
6624 pub fn code_actions(mut self) -> Vec<CodeAction> {
6625 self.visit_typed_module(&self.module.ast);
6626
6627 let Some(
6628 function_to_generate @ FunctionToGenerate {
6629 module,
6630 previous_definition_end: Some(insert_at),
6631 ..
6632 },
6633 ) = self.function_to_generate.take()
6634 else {
6635 return vec![];
6636 };
6637
6638 if let Some(module) = module {
6639 if let Some(module) = self.modules.get(module) {
6640 let insert_at = module.code.len() as u32;
6641 self.code_action_for_module(
6642 module,
6643 Publicity::Public,
6644 function_to_generate,
6645 insert_at,
6646 )
6647 } else {
6648 Vec::new()
6649 }
6650 } else {
6651 let module = self.module;
6652 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at)
6653 }
6654 }
6655
6656 fn code_action_for_module(
6657 mut self,
6658 module: &'a Module,
6659 publicity: Publicity,
6660 function_to_generate: FunctionToGenerate<'a>,
6661 insert_at: u32,
6662 ) -> Vec<CodeAction> {
6663 let FunctionToGenerate {
6664 name,
6665 arguments_types,
6666 given_arguments,
6667 return_type,
6668 ..
6669 } = function_to_generate;
6670
6671 // This might be triggered on variants as well, in that case we don't
6672 // want to offer this action. The "generate variant" action will be
6673 // offered instead.
6674 if !is_valid_lowercase_name(name) {
6675 return vec![];
6676 }
6677
6678 // Labels do not share the same namespace as argument so we use two
6679 // separate generators to avoid renaming a label in case it shares a
6680 // name with an argument.
6681 let mut label_names = NameGenerator::new();
6682 let mut argument_names = NameGenerator::new();
6683
6684 // Since we are generating a new function, type variables from other
6685 // functions and constants are irrelevant to the types we print.
6686 let mut printer = Printer::new_without_type_variables(&module.ast.names);
6687 let arguments = arguments_types
6688 .iter()
6689 .enumerate()
6690 .map(|(index, argument_type)| {
6691 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
6692 let (label, name) =
6693 argument_names.generate_label_and_name(call_argument, argument_type);
6694 let pretty_type = printer.print_type(argument_type);
6695 if let Some(label) = label {
6696 let label = label_names.rename_to_avoid_shadowing(label);
6697 format!("{label} {name}: {pretty_type}")
6698 } else {
6699 format!("{name}: {pretty_type}")
6700 }
6701 })
6702 .join(", ");
6703
6704 let return_type = printer.print_type(&return_type);
6705
6706 let publicity = if publicity.is_public() { "pub " } else { "" };
6707
6708 // Make sure we use the line number information of the module we are
6709 // editing, which might not be the module where the code action is
6710 // triggered.
6711 self.edits.line_numbers = &module.ast.type_info.line_numbers;
6712 self.edits.insert(
6713 insert_at,
6714 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
6715 );
6716
6717 let Some(uri) = url_from_path(module.input_path.as_str()) else {
6718 return Vec::new();
6719 };
6720 let mut action = Vec::with_capacity(1);
6721 CodeActionBuilder::new("Generate function")
6722 .kind(CodeActionKind::QuickFix)
6723 .changes(uri, self.edits.edits)
6724 .preferred(true)
6725 .push_to(&mut action);
6726 action
6727 }
6728
6729 fn try_save_function_to_generate(
6730 &mut self,
6731 name: &'a EcoString,
6732 function_type: &Arc<Type>,
6733 given_arguments: Option<&'a [TypedCallArg]>,
6734 ) {
6735 match function_type.fn_types() {
6736 None => {}
6737 Some((arguments_types, return_type)) => {
6738 self.function_to_generate = Some(FunctionToGenerate {
6739 name,
6740 arguments_types,
6741 given_arguments,
6742 return_type,
6743 previous_definition_end: self.last_visited_definition_end,
6744 module: None,
6745 })
6746 }
6747 }
6748 }
6749
6750 fn try_save_function_from_other_module(
6751 &mut self,
6752 module: &'a str,
6753 name: &'a str,
6754 function_type: &Arc<Type>,
6755 given_arguments: Option<&'a [TypedCallArg]>,
6756 ) {
6757 if let Some((arguments_types, return_type)) = function_type.fn_types()
6758 && is_valid_lowercase_name(name)
6759 {
6760 self.function_to_generate = Some(FunctionToGenerate {
6761 name,
6762 arguments_types,
6763 given_arguments,
6764 return_type,
6765 previous_definition_end: self.last_visited_definition_end,
6766 module: Some(module),
6767 })
6768 }
6769 }
6770}
6771
6772impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
6773 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6774 self.last_visited_definition_end = Some(fun.end_position);
6775 ast::visit::visit_typed_function(self, fun);
6776 }
6777
6778 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
6779 self.last_visited_definition_end = Some(constant.value.location().end);
6780 ast::visit::visit_typed_module_constant(self, constant);
6781 }
6782
6783 fn visit_typed_expr_invalid(
6784 &mut self,
6785 location: &'ast SrcSpan,
6786 type_: &'ast Arc<Type>,
6787 extra_information: &'ast Option<InvalidExpression>,
6788 ) {
6789 let invalid_range = self.edits.src_span_to_lsp_range(*location);
6790 if within(self.params.range, invalid_range) {
6791 match extra_information {
6792 Some(InvalidExpression::ModuleSelect { module_name, label }) => {
6793 self.try_save_function_from_other_module(module_name, label, type_, None)
6794 }
6795 Some(InvalidExpression::UnknownVariable { name }) => {
6796 self.try_save_function_to_generate(name, type_, None)
6797 }
6798 None => {}
6799 }
6800 }
6801
6802 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
6803 }
6804
6805 fn visit_typed_constant_invalid(
6806 &mut self,
6807 location: &'ast SrcSpan,
6808 type_: &'ast Arc<Type>,
6809 extra_information: &'ast Option<InvalidExpression>,
6810 ) {
6811 let constant_range = self.edits.src_span_to_lsp_range(*location);
6812 if let Some(extra_information) = extra_information
6813 && within(self.params.range, constant_range)
6814 {
6815 match extra_information {
6816 InvalidExpression::ModuleSelect { module_name, label } => {
6817 self.try_save_function_from_other_module(module_name, label, type_, None)
6818 }
6819 InvalidExpression::UnknownVariable { name } => {
6820 self.try_save_function_to_generate(name, type_, None)
6821 }
6822 }
6823 }
6824 }
6825
6826 fn visit_typed_expr_call(
6827 &mut self,
6828 location: &'ast SrcSpan,
6829 type_: &'ast Arc<Type>,
6830 fun: &'ast TypedExpr,
6831 arguments: &'ast [TypedCallArg],
6832 argument_parentheses: &'ast Option<u32>,
6833 ) {
6834 // If the function being called is invalid we need to generate a
6835 // function that has the proper labels.
6836 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
6837
6838 if within(self.params.range, fun_range) {
6839 if !labels_are_correct(arguments) {
6840 return;
6841 }
6842
6843 match fun {
6844 TypedExpr::Invalid {
6845 type_,
6846 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }),
6847 location: _,
6848 } => {
6849 return self.try_save_function_from_other_module(
6850 module_name,
6851 label,
6852 type_,
6853 Some(arguments),
6854 );
6855 }
6856 TypedExpr::Invalid {
6857 type_,
6858 extra_information: Some(InvalidExpression::UnknownVariable { name }),
6859 location: _,
6860 } => {
6861 return self.try_save_function_to_generate(name, type_, Some(arguments));
6862 }
6863 TypedExpr::Int { .. }
6864 | TypedExpr::Float { .. }
6865 | TypedExpr::String { .. }
6866 | TypedExpr::Block { .. }
6867 | TypedExpr::Pipeline { .. }
6868 | TypedExpr::Var { .. }
6869 | TypedExpr::Fn { .. }
6870 | TypedExpr::List { .. }
6871 | TypedExpr::Call { .. }
6872 | TypedExpr::BinOp { .. }
6873 | TypedExpr::Case { .. }
6874 | TypedExpr::RecordAccess { .. }
6875 | TypedExpr::PositionalAccess { .. }
6876 | TypedExpr::ModuleSelect { .. }
6877 | TypedExpr::Tuple { .. }
6878 | TypedExpr::TupleIndex { .. }
6879 | TypedExpr::Todo { .. }
6880 | TypedExpr::Panic { .. }
6881 | TypedExpr::Echo { .. }
6882 | TypedExpr::BitArray { .. }
6883 | TypedExpr::RecordUpdate { .. }
6884 | TypedExpr::NegateBool { .. }
6885 | TypedExpr::NegateInt { .. }
6886 | TypedExpr::Invalid { .. } => {}
6887 }
6888 }
6889 ast::visit::visit_typed_expr_call(
6890 self,
6891 location,
6892 type_,
6893 fun,
6894 arguments,
6895 argument_parentheses,
6896 );
6897 }
6898}
6899
6900/// Builder for the "generate variant" code action. This will generate a variant
6901/// for a type if it can tell the type it should come from. It will work with
6902/// non-existing variants both used as expressions
6903///
6904/// ```gleam
6905/// let a = IDoNotExist(1)
6906/// // ^^^^^^^^^^^ It would generate this variant here
6907/// ```
6908///
6909/// And as patterns:
6910///
6911/// ```gleam
6912/// let assert IDoNotExist(1) = todo
6913/// ^^^^^^^^^^^ It would generate this variant here
6914/// ```
6915///
6916pub struct GenerateVariant<'a, IO> {
6917 module: &'a Module,
6918 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
6919 params: &'a CodeActionParams,
6920 line_numbers: &'a LineNumbers,
6921 variant_to_generate: Option<VariantToGenerate<'a>>,
6922}
6923
6924struct VariantToGenerate<'a> {
6925 name: &'a str,
6926
6927 arguments_types: Vec<Arc<Type>>,
6928
6929 /// The start of the variant where the code action was triggered.
6930 /// For example:
6931 ///
6932 /// ```gleam
6933 /// Wobble
6934 /// ^ Trigger here to generate `Wobble`
6935 /// ^ The start is here!
6936 /// ```
6937 variant_start: u32,
6938
6939 /// If the variant where we triggered the code action is already qualified.
6940 /// For example:
6941 ///
6942 /// ```gleam
6943 /// wibble.Wobble // -> true
6944 /// Wobble // -> false
6945 /// ```
6946 ///
6947 is_qualified: bool,
6948
6949 /// Where the custom type to add this variant to ends.
6950 ///
6951 end_position: u32,
6952
6953 /// The already existing constructors of the custom type this new variant is
6954 /// going to be added to.
6955 ///
6956 constructors: &'a [RecordConstructor<Arc<Type>>],
6957
6958 /// Wether the type we're adding the variant to is written with braces or
6959 /// not. We need this information to add braces when missing.
6960 ///
6961 type_braces: TypeBraces,
6962
6963 /// The module this variant will be added to.
6964 ///
6965 module_name: EcoString,
6966
6967 /// The arguments actually supplied as input to the variant, if any.
6968 /// A variant to generate might as well be just a name passed as an argument
6969 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
6970 /// have any actual arguments!
6971 ///
6972 given_arguments: Option<Arguments<'a>>,
6973}
6974
6975#[derive(Debug, Clone, Copy)]
6976enum TypeBraces {
6977 /// If the type is written like this: `pub type Wibble`
6978 HasBraces,
6979 /// If the type is written like this: `pub type Wibble {}`
6980 NoBraces,
6981}
6982
6983/// The arguments to an invalid call or pattern we can use to generate a variant.
6984///
6985enum Arguments<'a> {
6986 /// These are the arguments provided to the invalid variant constructor
6987 /// when it's used as a function: `let a = Wibble(1, 2)`.
6988 ///
6989 Expressions(&'a [TypedCallArg]),
6990 /// These are the arguments provided to the invalid variant constructor when
6991 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
6992 ///
6993 Patterns(&'a [CallArg<TypedPattern>]),
6994}
6995
6996/// An invalid variant might be used both as a pattern in a case expression or
6997/// as a regular value in an expression. We want to generate the variant in both
6998/// cases, so we use this enum to tell apart the two cases and be able to reuse
6999/// most of the code for both as they are very similar.
7000///
7001enum Argument<'a> {
7002 Expression(&'a TypedCallArg),
7003 Pattern(&'a CallArg<TypedPattern>),
7004}
7005
7006impl<'a> Arguments<'a> {
7007 fn get(&self, index: usize) -> Option<Argument<'a>> {
7008 match self {
7009 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern),
7010 Arguments::Expressions(call_arguments) => {
7011 call_arguments.get(index).map(Argument::Expression)
7012 }
7013 }
7014 }
7015
7016 fn types(&self) -> Vec<Arc<Type>> {
7017 match self {
7018 Arguments::Expressions(call_arguments) => call_arguments
7019 .iter()
7020 .map(|argument| argument.value.type_())
7021 .collect_vec(),
7022
7023 Arguments::Patterns(call_arguments) => call_arguments
7024 .iter()
7025 .map(|argument| argument.value.type_())
7026 .collect_vec(),
7027 }
7028 }
7029}
7030
7031impl Argument<'_> {
7032 fn label(&self) -> Option<EcoString> {
7033 match self {
7034 Argument::Expression(call_arg) => call_arg.label.clone(),
7035 Argument::Pattern(call_arg) => call_arg.label.clone(),
7036 }
7037 }
7038}
7039
7040enum GenerateVariantEdits<'a> {
7041 GenerateInCurrentModule {
7042 current_module_edits: TextEdits<'a>,
7043 },
7044 GenerateInDifferentModule {
7045 current_module_edits: TextEdits<'a>,
7046 variant_module_edits: TextEdits<'a>,
7047 },
7048}
7049
7050impl<'a, IO> GenerateVariant<'a, IO> {
7051 pub fn new(
7052 module: &'a Module,
7053 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
7054 line_numbers: &'a LineNumbers,
7055 params: &'a CodeActionParams,
7056 ) -> Self {
7057 Self {
7058 module,
7059 params,
7060 compiler,
7061 line_numbers,
7062 variant_to_generate: None,
7063 }
7064 }
7065
7066 pub fn code_actions(mut self) -> Vec<CodeAction> {
7067 self.visit_typed_module(&self.module.ast);
7068
7069 let Some(VariantToGenerate {
7070 name,
7071 constructors,
7072 arguments_types,
7073 given_arguments,
7074 module_name,
7075 end_position,
7076 type_braces,
7077 variant_start,
7078 is_qualified,
7079 }) = &self.variant_to_generate
7080 else {
7081 return vec![];
7082 };
7083
7084 // Now we need to figure out if we're going to have to edit just the current
7085 // module (because the variant will be added to a type that is defined there),
7086 // or if we'll have to edit both the current module (to import the newly
7087 // generated variant) and a different module (where the variant definition
7088 // is going to end up).
7089 let current_module_line_numbers = LineNumbers::new(&self.module.code);
7090 let current_module_edits = TextEdits::new(¤t_module_line_numbers);
7091 let Some(variant_module) = self.compiler.modules.get(module_name) else {
7092 return vec![];
7093 };
7094 let variant_module_line_numbers = LineNumbers::new(&variant_module.code);
7095 let variant_module_edits = TextEdits::new(&variant_module_line_numbers);
7096
7097 let mut edits = if *module_name == self.module.name {
7098 GenerateVariantEdits::GenerateInCurrentModule {
7099 current_module_edits,
7100 }
7101 } else {
7102 GenerateVariantEdits::GenerateInDifferentModule {
7103 current_module_edits,
7104 variant_module_edits,
7105 }
7106 };
7107
7108 self.edits_to_create_variant(
7109 &mut edits,
7110 name,
7111 arguments_types,
7112 given_arguments,
7113 *end_position,
7114 *type_braces,
7115 );
7116 // If the variant is qualified already we don't have to do anything,
7117 // otherwise we need to import it in the current module.
7118 if !is_qualified {
7119 self.edits_to_import_variant(
7120 &mut edits,
7121 module_name,
7122 name,
7123 *variant_start,
7124 self.module,
7125 constructors,
7126 );
7127 }
7128
7129 let mut builder = CodeActionBuilder::new("Generate variant")
7130 .kind(CodeActionKind::QuickFix)
7131 .preferred(true);
7132
7133 match edits {
7134 GenerateVariantEdits::GenerateInCurrentModule {
7135 current_module_edits,
7136 } => {
7137 builder = builder.changes(
7138 self.params.text_document.uri.clone(),
7139 current_module_edits.edits,
7140 )
7141 }
7142 GenerateVariantEdits::GenerateInDifferentModule {
7143 current_module_edits,
7144 variant_module_edits,
7145 } => {
7146 let Some(variant_module_path) = url_from_path(variant_module.input_path.as_str())
7147 else {
7148 return vec![];
7149 };
7150
7151 if !current_module_edits.edits.is_empty() {
7152 builder = builder.changes(
7153 self.params.text_document.uri.clone(),
7154 current_module_edits.edits,
7155 );
7156 }
7157
7158 builder = builder.changes(variant_module_path, variant_module_edits.edits)
7159 }
7160 };
7161
7162 let mut action = Vec::with_capacity(1);
7163 builder.push_to(&mut action);
7164 action
7165 }
7166
7167 /// Returns the edits needed to add this new variant to the given module.
7168 /// It also returns the uri of the module the edits should be applied to.
7169 ///
7170 fn edits_to_create_variant(
7171 &self,
7172 edits: &mut GenerateVariantEdits<'_>,
7173 variant_name: &str,
7174 arguments_types: &[Arc<Type>],
7175 given_arguments: &Option<Arguments<'_>>,
7176 end_position: u32,
7177 type_braces: TypeBraces,
7178 ) {
7179 let mut label_names = NameGenerator::new();
7180 let mut printer = Printer::new(&self.module.ast.names);
7181 let arguments = arguments_types
7182 .iter()
7183 .enumerate()
7184 .map(|(index, argument_type)| {
7185 let label = given_arguments
7186 .as_ref()
7187 .and_then(|arguments| arguments.get(index)?.label())
7188 .map(|label| label_names.rename_to_avoid_shadowing(label));
7189
7190 let pretty_type = printer.print_type(argument_type);
7191 if let Some(arg_label) = label {
7192 format!("{arg_label}: {pretty_type}")
7193 } else {
7194 format!("{pretty_type}")
7195 }
7196 })
7197 .join(", ");
7198
7199 let variant = if arguments.is_empty() {
7200 variant_name.to_string()
7201 } else {
7202 format!("{variant_name}({arguments})")
7203 };
7204
7205 let (new_text, insert_at) = match type_braces {
7206 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
7207 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
7208 };
7209
7210 match edits {
7211 GenerateVariantEdits::GenerateInCurrentModule {
7212 current_module_edits,
7213 } => current_module_edits.insert(insert_at, new_text),
7214 GenerateVariantEdits::GenerateInDifferentModule {
7215 variant_module_edits,
7216 ..
7217 } => variant_module_edits.insert(insert_at, new_text),
7218 }
7219 }
7220
7221 fn try_save_variant_to_generate(
7222 &mut self,
7223 is_qualified: bool,
7224 function_name_location: SrcSpan,
7225 function_type: &Arc<Type>,
7226 given_arguments: Option<Arguments<'a>>,
7227 ) {
7228 let variant_to_generate = self.variant_to_generate(
7229 is_qualified,
7230 function_name_location,
7231 function_type,
7232 given_arguments,
7233 );
7234 if variant_to_generate.is_some() {
7235 self.variant_to_generate = variant_to_generate;
7236 }
7237 }
7238
7239 fn variant_to_generate(
7240 &mut self,
7241 is_qualified: bool,
7242 function_name_location: SrcSpan,
7243 type_: &Arc<Type>,
7244 given_arguments: Option<Arguments<'a>>,
7245 ) -> Option<VariantToGenerate<'a>> {
7246 let name = code_at(self.module, function_name_location);
7247 if !is_valid_uppercase_name(name) {
7248 return None;
7249 }
7250
7251 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
7252 (Some(result), _) => result,
7253 (None, Some(arguments)) => (arguments.types(), type_.clone()),
7254 (None, None) => (vec![], type_.clone()),
7255 };
7256
7257 let (module_name, type_name, _) = custom_type.named_type_information()?;
7258 let module = self.compiler.modules.get(&module_name)?;
7259 let (end_position, type_braces, constructors) =
7260 (module.ast.definitions.custom_types.iter())
7261 .filter(|custom_type| custom_type.name == type_name)
7262 .find_map(|custom_type| {
7263 // If there's already a variant with this name then we definitely
7264 // don't want to generate a new variant with the same name!
7265 let variant_with_this_name_already_exists = custom_type
7266 .constructors
7267 .iter()
7268 .map(|constructor| &constructor.name)
7269 .any(|existing_constructor_name| existing_constructor_name == name);
7270 if variant_with_this_name_already_exists {
7271 return None;
7272 }
7273 let type_braces = if custom_type.end_position == custom_type.location.end {
7274 TypeBraces::NoBraces
7275 } else {
7276 TypeBraces::HasBraces
7277 };
7278 Some((
7279 custom_type.end_position,
7280 type_braces,
7281 &custom_type.constructors,
7282 ))
7283 })?;
7284
7285 Some(VariantToGenerate {
7286 name,
7287 is_qualified,
7288 constructors,
7289 arguments_types,
7290 given_arguments,
7291 module_name,
7292 end_position,
7293 type_braces,
7294 variant_start: function_name_location.start,
7295 })
7296 }
7297
7298 /// If the variant is generated in a module different from the current one,
7299 /// this will add the edits needed to correctly import the variant so that
7300 /// it's readily available.
7301 /// It will also respect the developer's choice of how variants for the type
7302 /// are imported:
7303 ///
7304 /// ```diff
7305 /// - import wibble.{ Wibble }
7306 /// + import wibble.{ Wibble, Wobble }
7307 /// // If generating `Wobble`, and other variants of that type are
7308 /// // unqualified already the new variant is imported unqualified as well.
7309 /// ```
7310 ///
7311 /// ```diff
7312 /// import wibble
7313 ///
7314 /// pub fn main() {
7315 /// - let assert Wobble = todo
7316 /// + let assert wibble.Wobble = todo
7317 /// }
7318 /// // If no variant is used in an unqualified manner, than the variant
7319 /// // that triggered the generation is also qualified!
7320 /// ```
7321 fn edits_to_import_variant(
7322 &self,
7323 edits: &mut GenerateVariantEdits<'_>,
7324 variant_module_name: &str,
7325 variant_name: &str,
7326 variant_start: u32,
7327 module: &'a Module,
7328 constructors: &[RecordConstructor<Arc<Type>>],
7329 ) {
7330 let GenerateVariantEdits::GenerateInDifferentModule {
7331 current_module_edits,
7332 ..
7333 } = edits
7334 else {
7335 // If the variant is added to the current module, then no further
7336 // edits are needed. The variant is already available in the current
7337 // module!
7338 return;
7339 };
7340
7341 let constructors_names: HashSet<_> = constructors
7342 .iter()
7343 .map(|constructor| &constructor.name)
7344 .collect();
7345
7346 // We start by getting the import for the module where the variant
7347 // is going to be added...
7348 let Some(variant_module_import) = module
7349 .ast
7350 .definitions
7351 .imports
7352 .iter()
7353 .find(|import_| import_.module == variant_module_name)
7354 else {
7355 return;
7356 };
7357 // ...and then check if any of the variants of the type where the variant
7358 // is going to be added have already been imported in an unqualified way.
7359 let constructors_for_this_type_are_unqualified = variant_module_import
7360 .unqualified_values
7361 .iter()
7362 .any(|value| constructors_names.contains(&value.name));
7363
7364 if constructors_for_this_type_are_unqualified {
7365 // We need to add an unqualified import!
7366 let (insert_positions, new_text) = edits::insert_unqualified_import(
7367 variant_module_import,
7368 &self.module.code,
7369 variant_name.into(),
7370 );
7371 current_module_edits.insert(insert_positions, new_text);
7372 } else {
7373 // We need to qualify the variant that triggered the code action!
7374 current_module_edits.insert(variant_start, format!("{variant_module_name}."))
7375 }
7376 }
7377}
7378
7379impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> {
7380 fn visit_typed_expr_invalid(
7381 &mut self,
7382 location: &'ast SrcSpan,
7383 type_: &'ast Arc<Type>,
7384 extra_information: &'ast Option<InvalidExpression>,
7385 ) {
7386 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
7387 if within(self.params.range, invalid_range) {
7388 self.try_save_variant_to_generate(false, *location, type_, None);
7389 }
7390 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
7391 }
7392
7393 fn visit_typed_expr_call(
7394 &mut self,
7395 location: &'ast SrcSpan,
7396 type_: &'ast Arc<Type>,
7397 fun: &'ast TypedExpr,
7398 arguments: &'ast [TypedCallArg],
7399 open_parenthesis: &'ast Option<u32>,
7400 ) {
7401 // If the function being called is invalid we need to generate a
7402 // function that has the proper labels.
7403 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
7404 if within(self.params.range, fun_range) && fun.is_invalid() {
7405 if labels_are_correct(arguments) {
7406 self.try_save_variant_to_generate(
7407 fun.is_module_select(),
7408 fun.location(),
7409 &fun.type_(),
7410 Some(Arguments::Expressions(arguments)),
7411 );
7412 }
7413 } else {
7414 ast::visit::visit_typed_expr_call(
7415 self,
7416 location,
7417 type_,
7418 fun,
7419 arguments,
7420 open_parenthesis,
7421 );
7422 }
7423 }
7424
7425 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
7426 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
7427 if within(self.params.range, invalid_range) {
7428 self.try_save_variant_to_generate(false, *location, type_, None);
7429 }
7430 ast::visit::visit_typed_pattern_invalid(self, location, type_);
7431 }
7432
7433 fn visit_typed_pattern_constructor(
7434 &mut self,
7435 location: &'ast SrcSpan,
7436 name_location: &'ast SrcSpan,
7437 name: &'ast EcoString,
7438 arguments: &'ast Vec<CallArg<TypedPattern>>,
7439 module: &'ast Option<(EcoString, SrcSpan)>,
7440 constructor: &'ast Inferred<type_::PatternConstructor>,
7441 spread: &'ast Option<SrcSpan>,
7442 type_: &'ast Arc<Type>,
7443 ) {
7444 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
7445 if within(self.params.range, pattern_range) {
7446 if labels_are_correct(arguments) {
7447 self.try_save_variant_to_generate(
7448 module.is_some(),
7449 *name_location,
7450 type_,
7451 Some(Arguments::Patterns(arguments)),
7452 );
7453 }
7454 } else {
7455 ast::visit::visit_typed_pattern_constructor(
7456 self,
7457 location,
7458 name_location,
7459 name,
7460 arguments,
7461 module,
7462 constructor,
7463 spread,
7464 type_,
7465 );
7466 }
7467 }
7468}
7469
7470#[must_use]
7471/// Checks the labels in the given arguments are correct: that is there's no
7472/// duplicate labels and all labelled arguments come after the unlabelled ones.
7473fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool {
7474 let mut labelled_arg_found = false;
7475 let mut used_labels = HashSet::new();
7476
7477 for argument in arguments {
7478 match &argument.label {
7479 // Labels are invalid if there's duplicate ones or if an unlabelled
7480 // argument comes after a labelled one.
7481 Some(label) if used_labels.contains(label) => return false,
7482 None if labelled_arg_found => return false,
7483 // Otherwise we just add the label to the used ones.
7484 Some(label) => {
7485 labelled_arg_found = true;
7486 let _ = used_labels.insert(label);
7487 }
7488 None => {}
7489 }
7490 }
7491
7492 true
7493}
7494
7495#[derive(Clone)]
7496struct NameGenerator {
7497 used_names: im::HashSet<EcoString>,
7498}
7499
7500impl NameGenerator {
7501 pub fn new() -> Self {
7502 NameGenerator {
7503 used_names: im::HashSet::new(),
7504 }
7505 }
7506
7507 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
7508 let mut i = 1;
7509 let mut candidate_name = base.clone();
7510
7511 loop {
7512 if self.used_names.contains(&candidate_name) {
7513 i += 1;
7514 candidate_name = eco_format!("{base}_{i}");
7515 } else {
7516 let _ = self.used_names.insert(candidate_name.clone());
7517 return candidate_name;
7518 }
7519 }
7520 }
7521
7522 /// Given an argument type and the actual call argument (if any), comes up
7523 /// with a label and a name to use for that argument when generating a
7524 /// function.
7525 ///
7526 pub fn generate_label_and_name(
7527 &mut self,
7528 call_argument: Option<&CallArg<TypedExpr>>,
7529 argument_type: &Arc<Type>,
7530 ) -> (Option<EcoString>, EcoString) {
7531 let label = call_argument.and_then(|argument| argument.label.clone());
7532 let argument_name = call_argument
7533 // We always favour a name derived from the expression (for example if
7534 // the argument is a variable)
7535 .and_then(|argument| self.generate_name_from_expression(&argument.value))
7536 // If we don't have such a name and there's a label we use that name.
7537 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
7538 // If all else fails we fallback to using a name derived from the
7539 // argument's type.
7540 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
7541
7542 (label, argument_name)
7543 }
7544
7545 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
7546 let type_to_base_name = |type_: &Arc<Type>| {
7547 type_
7548 .named_type_name()
7549 .map(|(_type_module, type_name)| to_snake_case(&type_name))
7550 .filter(|name| is_valid_lowercase_name(name))
7551 .unwrap_or(EcoString::from("value"))
7552 };
7553
7554 let base_name = match type_.list_type() {
7555 None => type_to_base_name(type_),
7556 // If we're coming up with a name for a list we want to use the
7557 // plural form for the name of the inner type. For example:
7558 // `List(Pokemon)` should generate `pokemons`.
7559 Some(inner_type) => {
7560 let base_name = type_to_base_name(&inner_type);
7561 // If the inner type name already ends in "s" we leave it as it
7562 // is, or it would look funny.
7563 if base_name.ends_with('s') {
7564 base_name
7565 } else {
7566 eco_format!("{base_name}s")
7567 }
7568 }
7569 };
7570
7571 self.rename_to_avoid_shadowing(base_name)
7572 }
7573
7574 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
7575 match expression {
7576 // If the argument is a record, we can't use it as an argument name.
7577 // Similarly, we don't want to base the variable name off a
7578 // compiler-generated variable like `_pipe`.
7579 TypedExpr::Var {
7580 name, constructor, ..
7581 } if !constructor.variant.is_record()
7582 && !constructor.variant.is_generated_variable() =>
7583 {
7584 Some(self.rename_to_avoid_shadowing(name.clone()))
7585 }
7586
7587 // If the argument is a record access, we generate a name from the
7588 // label used.
7589 // For example if we have `wibble.id` we would end up picking `id`.
7590 TypedExpr::RecordAccess { label, .. } => {
7591 Some(self.rename_to_avoid_shadowing(label.clone()))
7592 }
7593
7594 TypedExpr::Int { .. }
7595 | TypedExpr::Float { .. }
7596 | TypedExpr::String { .. }
7597 | TypedExpr::Block { .. }
7598 | TypedExpr::Pipeline { .. }
7599 | TypedExpr::Var { .. }
7600 | TypedExpr::Fn { .. }
7601 | TypedExpr::List { .. }
7602 | TypedExpr::Call { .. }
7603 | TypedExpr::BinOp { .. }
7604 | TypedExpr::Case { .. }
7605 | TypedExpr::PositionalAccess { .. }
7606 | TypedExpr::ModuleSelect { .. }
7607 | TypedExpr::Tuple { .. }
7608 | TypedExpr::TupleIndex { .. }
7609 | TypedExpr::Todo { .. }
7610 | TypedExpr::Panic { .. }
7611 | TypedExpr::Echo { .. }
7612 | TypedExpr::BitArray { .. }
7613 | TypedExpr::RecordUpdate { .. }
7614 | TypedExpr::NegateBool { .. }
7615 | TypedExpr::NegateInt { .. }
7616 | TypedExpr::Invalid { .. } => None,
7617 }
7618 }
7619
7620 /// Given some typed definitions this reserves all the value names defined
7621 /// by all the top level definitions. That is: all function names, constant
7622 /// names, and imported modules names.
7623 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) {
7624 for constant in &definitions.constants {
7625 self.add_used_name(constant.name.clone());
7626 }
7627
7628 for function in &definitions.functions {
7629 if let Some((_, name)) = &function.name {
7630 self.add_used_name(name.clone());
7631 }
7632 }
7633
7634 for import in &definitions.imports {
7635 let module_name = match &import.used_name() {
7636 Some(used_name) => used_name.clone(),
7637 None => import.module.clone(),
7638 };
7639 self.add_used_name(module_name);
7640 }
7641 }
7642
7643 pub fn add_used_name(&mut self, name: EcoString) {
7644 let _ = self.used_names.insert(name);
7645 }
7646
7647 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
7648 field_map
7649 .fields
7650 .iter()
7651 .for_each(|(label, _)| self.add_used_name(label.clone()));
7652 }
7653
7654 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
7655 variable_names
7656 .names
7657 .iter()
7658 .for_each(|name| self.add_used_name(name.clone()));
7659 }
7660
7661 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) {
7662 for variable in bound_variables {
7663 self.add_used_name(variable.name());
7664 }
7665 }
7666}
7667
7668#[must_use]
7669fn is_valid_lowercase_name(name: &str) -> bool {
7670 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
7671 return false;
7672 }
7673
7674 for char in name.chars() {
7675 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
7676 if !is_valid_char {
7677 return false;
7678 }
7679 }
7680
7681 string_to_keyword(name).is_none()
7682}
7683
7684#[must_use]
7685fn is_valid_uppercase_name(name: &str) -> bool {
7686 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
7687 return false;
7688 }
7689
7690 for char in name.chars() {
7691 if !char.is_ascii_alphanumeric() {
7692 return false;
7693 }
7694 }
7695
7696 true
7697}
7698
7699/// Code action to rewrite a single-step pipeline into a regular function call.
7700/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
7701///
7702pub struct ConvertToFunctionCall<'a> {
7703 module: &'a Module,
7704 params: &'a CodeActionParams,
7705 edits: TextEdits<'a>,
7706 locations: Option<ConvertToFunctionCallLocations>,
7707}
7708
7709/// All the different locations the "Convert to function call" code action needs
7710/// to properly rewrite a pipeline into a function call.
7711///
7712struct ConvertToFunctionCallLocations {
7713 /// This is the location of the value being piped into a call.
7714 ///
7715 /// ```gleam
7716 /// [1, 2, 3] |> list.length
7717 /// // ^^^^^^^^^ This one here
7718 /// ```
7719 ///
7720 first_value: SrcSpan,
7721
7722 /// This is the location of the call the value is being piped into.
7723 ///
7724 /// ```gleam
7725 /// [1, 2, 3] |> list.length
7726 /// // ^^^^^^^^^^^ This one here
7727 /// ```
7728 ///
7729 call: SrcSpan,
7730
7731 /// This is the kind of desugaring that is taking place when piping
7732 /// `first_value` into `call`.
7733 ///
7734 call_kind: PipelineAssignmentKind,
7735}
7736
7737impl<'a> ConvertToFunctionCall<'a> {
7738 pub fn new(
7739 module: &'a Module,
7740 line_numbers: &'a LineNumbers,
7741 params: &'a CodeActionParams,
7742 ) -> Self {
7743 Self {
7744 module,
7745 params,
7746 edits: TextEdits::new(line_numbers),
7747 locations: None,
7748 }
7749 }
7750
7751 pub fn code_actions(mut self) -> Vec<CodeAction> {
7752 self.visit_typed_module(&self.module.ast);
7753
7754 // If we couldn't find a pipeline to rewrite we don't return any action.
7755 let Some(ConvertToFunctionCallLocations {
7756 first_value,
7757 call,
7758 call_kind,
7759 }) = self.locations
7760 else {
7761 return vec![];
7762 };
7763
7764 // We first delete the first value of the pipeline as it's going to be
7765 // inlined as a function call argument.
7766 self.edits.delete(SrcSpan {
7767 start: first_value.start,
7768 end: call.start,
7769 });
7770
7771 // Then we have to insert the piped value in the appropriate position.
7772 // This will change based on how the pipeline is being desugared, we
7773 // know this thanks to the `call_kind`
7774 let first_value_text = self
7775 .module
7776 .code
7777 .get(first_value.start as usize..first_value.end as usize)
7778 .expect("invalid code span")
7779 .to_string();
7780
7781 match call_kind {
7782 // When piping into a `_` we replace the hole with the piped value:
7783 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
7784 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
7785
7786 // When piping is desguared as a function call we need to add the
7787 // missing parentheses:
7788 // `[1, 2] |> length` becomes `length([1, 2])`
7789 PipelineAssignmentKind::FunctionCall => {
7790 self.edits.insert(call.end, format!("({first_value_text})"))
7791 }
7792
7793 // When the piped value is inserted as the first argument there's two
7794 // possible scenarios:
7795 // - there's a second argument as well: in that case we insert it
7796 // before the second arg and add a comma
7797 // - there's no other argument: `[1, 2] |> length()` becomes
7798 // `length([1, 2])`, we insert the value between the empty
7799 // parentheses
7800 PipelineAssignmentKind::FirstArgument {
7801 second_argument: Some(SrcSpan { start, .. }),
7802 } => self.edits.insert(start, format!("{first_value_text}, ")),
7803 PipelineAssignmentKind::FirstArgument {
7804 second_argument: None,
7805 } => self.edits.insert(call.end - 1, first_value_text),
7806
7807 // When the value is piped into an echo, to rewrite the pipeline we
7808 // have to insert the value after the `echo` with no parentheses:
7809 // `a |> echo` is rewritten as `echo a`.
7810 PipelineAssignmentKind::Echo => {
7811 self.edits.insert(call.end, format!(" {first_value_text}"))
7812 }
7813 }
7814
7815 let mut action = Vec::with_capacity(1);
7816 CodeActionBuilder::new("Convert to function call")
7817 .kind(CodeActionKind::RefactorRewrite)
7818 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7819 .preferred(false)
7820 .push_to(&mut action);
7821 action
7822 }
7823}
7824
7825impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
7826 fn visit_typed_expr_pipeline(
7827 &mut self,
7828 location: &'ast SrcSpan,
7829 first_value: &'ast TypedPipelineAssignment,
7830 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
7831 finally: &'ast TypedExpr,
7832 finally_kind: &'ast PipelineAssignmentKind,
7833 ) {
7834 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
7835 if within(self.params.range, pipeline_range) {
7836 // Add final assignment to the list
7837 let all_assignments = assignments
7838 .iter()
7839 .map(|(call, kind)| (call.location, *kind))
7840 .chain(iter::once((finally.location(), *finally_kind)));
7841 let mut call_information = None;
7842 // Span, containing content that will be extracted as call argument
7843 let mut accumulated_span = first_value.location;
7844
7845 for (current_location, current_kind) in all_assignments {
7846 if within(
7847 self.params.range,
7848 self.edits.src_span_to_lsp_range(current_location),
7849 ) {
7850 call_information = Some((accumulated_span, current_location, current_kind));
7851 break;
7852 }
7853 accumulated_span = accumulated_span.merge(¤t_location);
7854 }
7855
7856 let (final_prev, final_call, final_kind) = match call_information {
7857 Some(triple) => triple,
7858
7859 // Here two situations are possible:
7860 // - If the cursor is not on any of the assignments (for example, it is on first value), then we need
7861 // to use that first value as accumulated location, and the first call location and kind
7862 // - If there are no assignments, then we are dealing with a single
7863 // step pipeline and the call is `finally`
7864 None => {
7865 let (call, call_kind) = &assignments
7866 .first()
7867 .map(|(call, call_kind)| (call.location, *call_kind))
7868 .unwrap_or_else(|| (finally.location(), *finally_kind));
7869 (first_value.location, *call, *call_kind)
7870 }
7871 };
7872
7873 self.locations = Some(ConvertToFunctionCallLocations {
7874 first_value: final_prev,
7875 call: final_call,
7876 call_kind: final_kind,
7877 });
7878
7879 ast::visit::visit_typed_expr_pipeline(
7880 self,
7881 location,
7882 first_value,
7883 assignments,
7884 finally,
7885 finally_kind,
7886 );
7887 }
7888 }
7889}
7890
7891/// Builder for code action to inline a variable.
7892///
7893pub struct InlineVariable<'a> {
7894 module: &'a Module,
7895 params: &'a CodeActionParams,
7896 edits: TextEdits<'a>,
7897 actions: Vec<CodeAction>,
7898}
7899
7900impl<'a> InlineVariable<'a> {
7901 pub fn new(
7902 module: &'a Module,
7903 line_numbers: &'a LineNumbers,
7904 params: &'a CodeActionParams,
7905 ) -> Self {
7906 Self {
7907 module,
7908 params,
7909 edits: TextEdits::new(line_numbers),
7910 actions: Vec::new(),
7911 }
7912 }
7913
7914 pub fn code_actions(mut self) -> Vec<CodeAction> {
7915 self.visit_typed_module(&self.module.ast);
7916
7917 self.actions
7918 }
7919
7920 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) {
7921 let references =
7922 FindVariableReferences::new(location, name).find_in_module(&self.module.ast);
7923 let reference = if references.len() == 1 {
7924 references
7925 .into_iter()
7926 .next()
7927 .expect("References has length 1")
7928 } else {
7929 return;
7930 };
7931
7932 let Some(ast::Statement::Assignment(assignment)) =
7933 self.module.ast.find_statement(location.start)
7934 else {
7935 return;
7936 };
7937
7938 // If the assignment does not simple bind a variable, for example:
7939 // ```gleam
7940 // let #(first, second, third)
7941 // io.println(first)
7942 // // ^ Inline here
7943 // ```
7944 // We can't inline it.
7945 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
7946 return;
7947 }
7948
7949 // If the assignment was generated by the compiler, it doesn't have a
7950 // syntactical representation, so we can't inline it.
7951 if matches!(assignment.kind, AssignmentKind::Generated) {
7952 return;
7953 }
7954
7955 let value_location = assignment.value.location();
7956 let value = self
7957 .module
7958 .code
7959 .get(value_location.start as usize..value_location.end as usize)
7960 .expect("Span is valid");
7961
7962 match reference.kind {
7963 VariableReferenceKind::Variable => {
7964 self.edits.replace(reference.location, value.into());
7965 }
7966 VariableReferenceKind::LabelShorthand => {
7967 self.edits
7968 .insert(reference.location.end, format!(" {value}"));
7969 }
7970 }
7971
7972 let mut location = assignment.location;
7973 location.end = next_nonwhitespace(&self.module.code, location.end);
7974
7975 self.edits.delete(location);
7976
7977 CodeActionBuilder::new("Inline variable")
7978 .kind(CodeActionKind::RefactorInline)
7979 .changes(
7980 self.params.text_document.uri.clone(),
7981 std::mem::take(&mut self.edits.edits),
7982 )
7983 .preferred(false)
7984 .push_to(&mut self.actions);
7985 }
7986}
7987
7988impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
7989 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
7990 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else {
7991 ast::visit::visit_typed_assignment(self, assignment);
7992 return;
7993 };
7994
7995 // We special case assignment variables because we want to trigger the
7996 // code action also if we're over the let keyword:
7997 //
7998 // ```gleam
7999 // let wibble = 11
8000 // // ^^^^^^^^^^ Here!
8001 // ```
8002 //
8003 let assignment_range = self
8004 .edits
8005 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end));
8006 if !within(self.params.range, assignment_range) {
8007 ast::visit::visit_typed_assignment(self, assignment);
8008 return;
8009 }
8010
8011 self.maybe_inline(*location, name.clone());
8012 }
8013
8014 fn visit_typed_expr_var(
8015 &mut self,
8016 location: &'ast SrcSpan,
8017 constructor: &'ast ValueConstructor,
8018 name: &'ast EcoString,
8019 ) {
8020 let range = self.edits.src_span_to_lsp_range(*location);
8021
8022 if !within(self.params.range, range) {
8023 return;
8024 }
8025
8026 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
8027 &constructor.variant
8028 else {
8029 return;
8030 };
8031
8032 // We can only inline variables assigned by `let` statements, as it
8033 //doesn't make sense to do so with any other kind of variable.
8034 match origin.declaration {
8035 VariableDeclaration::LetPattern => {}
8036 VariableDeclaration::UsePattern
8037 | VariableDeclaration::ClausePattern
8038 | VariableDeclaration::FunctionParameter { .. }
8039 | VariableDeclaration::Generated => return,
8040 }
8041
8042 self.maybe_inline(*location, name.clone());
8043 }
8044
8045 fn visit_typed_pattern_variable(
8046 &mut self,
8047 location: &'ast SrcSpan,
8048 name: &'ast EcoString,
8049 _type: &'ast Arc<Type>,
8050 origin: &'ast VariableOrigin,
8051 ) {
8052 // We can only inline variables assigned by `let` statements, as it
8053 //doesn't make sense to do so with any other kind of variable.
8054 match origin.declaration {
8055 VariableDeclaration::LetPattern => {}
8056 VariableDeclaration::UsePattern
8057 | VariableDeclaration::ClausePattern
8058 | VariableDeclaration::FunctionParameter { .. }
8059 | VariableDeclaration::Generated => return,
8060 }
8061
8062 let range = self.edits.src_span_to_lsp_range(*location);
8063
8064 if !within(self.params.range, range) {
8065 return;
8066 }
8067
8068 self.maybe_inline(*location, name.clone());
8069 }
8070}
8071
8072/// Builder for the "convert to pipe" code action.
8073///
8074/// ```gleam
8075/// pub fn main() {
8076/// wibble(wobble, woo)
8077/// // ^ [convert to pipe]
8078/// }
8079/// ```
8080///
8081/// Will turn the code into the following pipeline:
8082///
8083/// ```gleam
8084/// pub fn main() {
8085/// wobble |> wibble(woo)
8086/// }
8087/// ```
8088///
8089pub struct ConvertToPipe<'a> {
8090 module: &'a Module,
8091 params: &'a CodeActionParams,
8092 edits: TextEdits<'a>,
8093 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
8094 visited_item: VisitedItem,
8095}
8096
8097pub enum VisitedItem {
8098 RegularExpression,
8099 UseRightHandSide,
8100 PipelineFinalStep,
8101}
8102
8103/// Holds all the data needed by the "convert to pipe" code action to properly
8104/// rewrite a call into a pipe. Here's what each span means:
8105///
8106/// ```gleam
8107/// wibble(wobb|le, woo)
8108/// // ^^^^^^^^^^^^^^^^^^^^ call
8109/// // ^^^^^^ called
8110/// // ^^^^^^^ arg
8111/// // ^^^ next arg
8112/// ```
8113///
8114/// In this example `position` is 0, since the cursor is over the first
8115/// argument.
8116///
8117pub struct ConvertToPipeArg<'a> {
8118 /// The span of the called function.
8119 called: SrcSpan,
8120 /// The span of the entire function call.
8121 call: SrcSpan,
8122 /// The position (0-based) of the argument.
8123 position: usize,
8124 /// The argument we have to pipe.
8125 arg: &'a TypedCallArg,
8126 /// The span of the argument following the one we have to pipe, if there's
8127 /// any.
8128 next_arg: Option<SrcSpan>,
8129}
8130
8131impl<'a> ConvertToPipe<'a> {
8132 pub fn new(
8133 module: &'a Module,
8134 line_numbers: &'a LineNumbers,
8135 params: &'a CodeActionParams,
8136 ) -> Self {
8137 Self {
8138 module,
8139 params,
8140 edits: TextEdits::new(line_numbers),
8141 visited_item: VisitedItem::RegularExpression,
8142 argument_to_pipe: None,
8143 }
8144 }
8145
8146 pub fn code_actions(mut self) -> Vec<CodeAction> {
8147 self.visit_typed_module(&self.module.ast);
8148
8149 let Some(ConvertToPipeArg {
8150 called,
8151 call,
8152 position,
8153 arg,
8154 next_arg,
8155 }) = self.argument_to_pipe
8156 else {
8157 return vec![];
8158 };
8159
8160 let arg_location = if arg.uses_label_shorthand() {
8161 SrcSpan {
8162 start: arg.location.start,
8163 end: arg.location.end - 1,
8164 }
8165 } else if arg.label.is_some() {
8166 arg.value.location()
8167 } else {
8168 arg.location
8169 };
8170
8171 let arg_text = code_at(self.module, arg_location);
8172 // If the expression being piped is a binary operation with
8173 // precedence lower than pipes then we have to wrap it in curly
8174 // braces to not mess with the order of operations.
8175 let arg_text = if let TypedExpr::BinOp { operator, .. } = arg.value
8176 && operator.precedence() < PIPE_PRECEDENCE
8177 {
8178 &format!("{{ {arg_text} }}")
8179 } else {
8180 arg_text
8181 };
8182
8183 match next_arg {
8184 // When extracting an argument we never want to remove any explicit
8185 // label that was written down, so in case it is labelled (be it a
8186 // shorthand or not) we'll always replace the value with a `_`
8187 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
8188 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
8189
8190 // Now we can deal with unlabelled arguments:
8191 // If we're removing the first argument and there's other arguments
8192 // after it, we need to delete the comma that was separating the
8193 // two.
8194 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
8195 start: arg.location.start,
8196 end: next_arg.start,
8197 }),
8198 // Otherwise, if we're deleting the first argument and there's
8199 // no other arguments following it, we remove the call's
8200 // parentheses.
8201 None if position == 0 => self.edits.delete(SrcSpan {
8202 start: called.end,
8203 end: call.end,
8204 }),
8205 // In all other cases we're piping something that is not the first
8206 // argument so we just replace it with an `_`.
8207 _ => self.edits.replace(arg.location, "_".into()),
8208 };
8209
8210 // Finally we can add the argument that was removed as the first step
8211 // of the newly defined pipeline.
8212 self.edits.insert(call.start, format!("{arg_text} |> "));
8213
8214 let mut action = Vec::with_capacity(1);
8215 CodeActionBuilder::new("Convert to pipe")
8216 .kind(CodeActionKind::RefactorRewrite)
8217 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8218 .preferred(false)
8219 .push_to(&mut action);
8220 action
8221 }
8222}
8223
8224impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
8225 fn visit_typed_expr_call(
8226 &mut self,
8227 location: &'ast SrcSpan,
8228 _type_: &'ast Arc<Type>,
8229 fun: &'ast TypedExpr,
8230 arguments: &'ast [TypedCallArg],
8231 _open_parenthesis: &'ast Option<u32>,
8232 ) {
8233 if arguments.iter().any(|arg| arg.is_capture_hole()) {
8234 return;
8235 }
8236
8237 // If we're visiting the typed function produced by typing a use, we
8238 // skip the thing itself and only visit its arguments and called
8239 // function, that is the body of the use.
8240 match self.visited_item {
8241 VisitedItem::RegularExpression => (),
8242 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => {
8243 self.visited_item = VisitedItem::RegularExpression;
8244 ast::visit::visit_typed_expr(self, fun);
8245 arguments
8246 .iter()
8247 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
8248 return;
8249 }
8250 }
8251
8252 // We only visit a call if the cursor is somewhere within its location,
8253 // otherwise we skip it entirely.
8254 let call_range = self.edits.src_span_to_lsp_range(*location);
8255 if !within(self.params.range, call_range) {
8256 return;
8257 }
8258
8259 // If the cursor is over any of the arguments then we'll use that as
8260 // the one to extract.
8261 // Otherwise the cursor must be over the called function, in that case
8262 // we extract the first argument (if there's one):
8263 //
8264 // ```gleam
8265 // wibble(wobble, woo)
8266 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
8267 // // ^^^ pipe the second argument if I'm here
8268 // ```
8269 let argument_to_pipe = arguments
8270 .iter()
8271 .enumerate()
8272 .find_map(|(position, arg)| {
8273 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
8274 if within(self.params.range, arg_range) {
8275 Some((position, arg))
8276 } else {
8277 None
8278 }
8279 })
8280 .or_else(|| arguments.first().map(|argument| (0, argument)));
8281
8282 // If we're not hovering over any of the arguments _or_ there's no
8283 // argument to extract at all we just return, there's nothing we can do
8284 // on this call or any of its arguments (since we've determined the
8285 // cursor is not over any of those).
8286 let Some((position, arg)) = argument_to_pipe else {
8287 return;
8288 };
8289
8290 self.argument_to_pipe = Some(ConvertToPipeArg {
8291 called: fun.location(),
8292 call: *location,
8293 position,
8294 arg,
8295 next_arg: arguments
8296 .get(position + 1)
8297 .map(|argument| argument.location),
8298 });
8299
8300 // We still want to visit the arguments so that if we're hovering a
8301 // nested pipeline, that's going to be the one we transform:
8302 //
8303 // ```gleam
8304 // wibble(Wobble(
8305 // field: call(other(last(1)))
8306 // // ^^^^ We want to convert this one if we hover over it,
8307 // // not the outer `wibble(Wobble(...))` call
8308 // ))
8309 // ```
8310 //
8311 for argument in arguments {
8312 ast::visit::visit_typed_call_arg(self, argument);
8313 }
8314 }
8315
8316 fn visit_typed_expr_pipeline(
8317 &mut self,
8318 _location: &'ast SrcSpan,
8319 first_value: &'ast TypedPipelineAssignment,
8320 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
8321 finally: &'ast TypedExpr,
8322 _finally_kind: &'ast PipelineAssignmentKind,
8323 ) {
8324 // We can only apply the action on the first step of a pipeline, so we
8325 // visit just that one and skip all the others.
8326 ast::visit::visit_typed_pipeline_assignment(self, first_value);
8327 self.visited_item = VisitedItem::PipelineFinalStep;
8328 ast::visit::visit_typed_expr(self, finally);
8329 }
8330
8331 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
8332 self.visited_item = VisitedItem::UseRightHandSide;
8333 ast::visit::visit_typed_use(self, use_);
8334 }
8335}
8336
8337/// Code action to interpolate a string. If the cursor is inside the string
8338/// (not selecting anything) the language server will offer to split it:
8339///
8340/// ```gleam
8341/// "wibble | wobble"
8342/// // ^ [Split string]
8343/// // Will produce the following
8344/// "wibble " <> todo <> " wobble"
8345/// ```
8346///
8347/// If the cursor is selecting an entire valid gleam name, then the language
8348/// server will offer to interpolate it as a variable:
8349///
8350/// ```gleam
8351/// "wibble wobble woo"
8352/// // ^^^^^^ [Interpolate variable]
8353/// // Will produce the following
8354/// "wibble " <> wobble <> " woo"
8355/// ```
8356///
8357/// > Note: the cursor won't end up right after the inserted variable/todo.
8358/// > that's a bit annoying, but in a future LSP version we will be able to
8359/// > isnert tab stops to allow one to jump to the newly added variable/todo.
8360///
8361pub struct InterpolateString<'a> {
8362 module: &'a Module,
8363 params: &'a CodeActionParams,
8364 edits: TextEdits<'a>,
8365 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
8366 string_literal_position: StringLiteralPosition,
8367}
8368
8369#[derive(Debug, Clone, Copy, Eq, PartialEq)]
8370pub enum StringLiteralPosition {
8371 FirstPipelineStep,
8372 Other,
8373}
8374
8375#[derive(Clone, Copy)]
8376enum StringInterpolation {
8377 InterpolateValue { value_location: SrcSpan },
8378 SplitString { split_at: u32 },
8379}
8380
8381impl<'a> InterpolateString<'a> {
8382 pub fn new(
8383 module: &'a Module,
8384 line_numbers: &'a LineNumbers,
8385 params: &'a CodeActionParams,
8386 ) -> Self {
8387 Self {
8388 module,
8389 params,
8390 edits: TextEdits::new(line_numbers),
8391 string_interpolation: None,
8392 string_literal_position: StringLiteralPosition::Other,
8393 }
8394 }
8395
8396 pub fn code_actions(mut self) -> Vec<CodeAction> {
8397 self.visit_typed_module(&self.module.ast);
8398
8399 let Some((string_location, interpolation)) = self.string_interpolation else {
8400 return vec![];
8401 };
8402
8403 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
8404 self.edits.insert(string_location.start, "{ ".into());
8405 }
8406
8407 match interpolation {
8408 StringInterpolation::InterpolateValue { value_location } => {
8409 let name = self
8410 .module
8411 .code
8412 .get(value_location.start as usize..value_location.end as usize)
8413 .expect("invalid value range");
8414
8415 // We trust that the programmer has correctly selected the part
8416 // of the string they want to interpolate and simply "cut it out"
8417 // for them. In future, we could try and parse their selection to
8418 // see if it is a valid expression in Gleam.
8419 if value_location.start == string_location.start + 1 {
8420 self.edits
8421 .insert(string_location.start, format!("{name} <> "));
8422 } else if value_location.end == string_location.end - 1 {
8423 self.edits
8424 .insert(string_location.end, format!(" <> {name}"));
8425 } else {
8426 self.edits
8427 .insert(value_location.start, format!("\" <> {name} <> \""));
8428 }
8429 self.edits.delete(value_location);
8430 }
8431
8432 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
8433 self.edits.insert(split_at, "\" <> todo <> \"".into());
8434 }
8435
8436 StringInterpolation::SplitString { .. } => return vec![],
8437 };
8438
8439 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
8440 self.edits.insert(string_location.end, " }".into());
8441 }
8442
8443 let mut action = Vec::with_capacity(1);
8444 CodeActionBuilder::new("Interpolate string")
8445 .kind(CodeActionKind::RefactorRewrite)
8446 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8447 .preferred(false)
8448 .push_to(&mut action);
8449 action
8450 }
8451
8452 fn can_split_string_at(&self, at: u32) -> bool {
8453 self.string_interpolation
8454 .is_some_and(|(string_location, _)| {
8455 !(at <= string_location.start + 1 || at >= string_location.end - 1)
8456 })
8457 }
8458
8459 fn visit_literal_string(
8460 &mut self,
8461 string_location: SrcSpan,
8462 string_position: StringLiteralPosition,
8463 ) {
8464 // We can only interpolate/split a string if the cursor is somewhere
8465 // within its location, otherwise we skip it.
8466 let string_range = self.edits.src_span_to_lsp_range(string_location);
8467 if !within(self.params.range, string_range) {
8468 return;
8469 }
8470
8471 let selection @ SrcSpan { start, end } =
8472 self.edits.lsp_range_to_src_span(self.params.range);
8473
8474 // We can't interpolate/split if the double quotes delimiting the
8475 // string have been selected.
8476 if start == string_location.start || end == string_location.end {
8477 return;
8478 }
8479
8480 let name = self
8481 .module
8482 .code
8483 .get(start as usize..end as usize)
8484 .expect("invalid value range");
8485
8486 // TUI editors like Helix and Kakoune that use the selection-action edit
8487 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always
8488 // have something selected. For programmers using these editors, the
8489 // smallest selection possible is a 1-character selection. The best we can do
8490 // to provide parity with other editors is to consider a single-character SPACE
8491 // selection as an empty selection, as they most likely want to split the
8492 // string instead of interpolating a variable.
8493 let interpolation = if start == end || (end - start == 1 && name == " ") {
8494 StringInterpolation::SplitString { split_at: start }
8495 } else {
8496 StringInterpolation::InterpolateValue {
8497 value_location: selection,
8498 }
8499 };
8500 self.string_interpolation = Some((string_location, interpolation));
8501 self.string_literal_position = string_position;
8502 }
8503}
8504
8505impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
8506 fn visit_typed_expr_string(
8507 &mut self,
8508 location: &'ast SrcSpan,
8509 _type_: &'ast Arc<Type>,
8510 _value: &'ast EcoString,
8511 ) {
8512 self.visit_literal_string(*location, StringLiteralPosition::Other);
8513 }
8514
8515 fn visit_typed_expr_pipeline(
8516 &mut self,
8517 _location: &'ast SrcSpan,
8518 first_value: &'ast TypedPipelineAssignment,
8519 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
8520 finally: &'ast TypedExpr,
8521 _finally_kind: &'ast PipelineAssignmentKind,
8522 ) {
8523 if first_value.value.is_literal_string() {
8524 self.visit_literal_string(
8525 first_value.location,
8526 StringLiteralPosition::FirstPipelineStep,
8527 );
8528 } else {
8529 ast::visit::visit_typed_pipeline_assignment(self, first_value);
8530 }
8531
8532 assignments
8533 .iter()
8534 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
8535 self.visit_typed_expr(finally);
8536 }
8537}
8538
8539/// Code action to replace a `..` in a pattern with all the missing fields that
8540/// have not been explicitly provided; labelled ones are introduced with the
8541/// shorthand syntax.
8542///
8543/// ```gleam
8544/// pub type Pokemon {
8545/// Pokemon(Int, name: String, moves: List(String))
8546/// }
8547///
8548/// pub fn main() {
8549/// let Pokemon(..) = todo
8550/// // ^^ Cursor over the spread
8551/// }
8552/// ```
8553/// Would become
8554/// ```gleam
8555/// pub fn main() {
8556/// let Pokemon(int, name:, moves:) = todo
8557/// }
8558///
8559pub struct FillUnusedFields<'a> {
8560 module: &'a Module,
8561 params: &'a CodeActionParams,
8562 edits: TextEdits<'a>,
8563 data: Option<FillUnusedFieldsData>,
8564}
8565
8566pub struct FillUnusedFieldsData {
8567 /// All the missing positional and labelled fields.
8568 positional: Vec<Arc<Type>>,
8569 labelled: Vec<(EcoString, Arc<Type>)>,
8570 /// We need this in order to tell where the missing positional arguments
8571 /// should be inserted.
8572 first_labelled_argument_start: Option<u32>,
8573 /// The end of the final argument before the spread, if there's any.
8574 /// We'll use this to delete everything that comes after the final argument,
8575 /// after adding all the ignored fields.
8576 last_argument_end: Option<u32>,
8577 spread_location: SrcSpan,
8578}
8579
8580impl<'a> FillUnusedFields<'a> {
8581 pub fn new(
8582 module: &'a Module,
8583 line_numbers: &'a LineNumbers,
8584 params: &'a CodeActionParams,
8585 ) -> Self {
8586 Self {
8587 module,
8588 params,
8589 edits: TextEdits::new(line_numbers),
8590 data: None,
8591 }
8592 }
8593
8594 pub fn code_actions(mut self) -> Vec<CodeAction> {
8595 self.visit_typed_module(&self.module.ast);
8596
8597 let Some(FillUnusedFieldsData {
8598 positional,
8599 labelled,
8600 first_labelled_argument_start,
8601 last_argument_end,
8602 spread_location,
8603 }) = self.data
8604 else {
8605 return vec![];
8606 };
8607
8608 // Do not suggest this code action if there's no ignored fields at all.
8609 if positional.is_empty() && labelled.is_empty() {
8610 return vec![];
8611 };
8612
8613 // We add all the missing positional arguments before the first
8614 // labelled one (and so after all the already existing positional ones).
8615 if !positional.is_empty() {
8616 // We want to make sure that all positional args will have a name
8617 // that's different from any label. So we add those as already used
8618 // names.
8619 let mut names = NameGenerator::new();
8620 for (label, _) in labelled.iter() {
8621 names.add_used_name(label.clone());
8622 }
8623
8624 let positional_arguments = positional
8625 .iter()
8626 .map(|type_| names.generate_name_from_type(type_))
8627 .join(", ");
8628 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
8629
8630 // The positional arguments are going to be followed by some other
8631 // arguments if there's some already existing labelled args
8632 // (`last_argument_end.is_some`), of if we're adding those labelled args
8633 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
8634 // final positional argument we're adding to separate it from the ones that
8635 // are going to come after.
8636 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
8637 let positional_arguments = if has_arguments_after {
8638 format!("{positional_arguments}, ")
8639 } else {
8640 positional_arguments
8641 };
8642
8643 self.edits.insert(insert_at, positional_arguments);
8644 }
8645
8646 if !labelled.is_empty() {
8647 // If there's labelled arguments to add, we replace the existing spread
8648 // with the arguments to be added. This way commas and all should already
8649 // be correct.
8650 let labelled_arguments = labelled
8651 .iter()
8652 .map(|(label, _)| format!("{label}:"))
8653 .join(", ");
8654 self.edits.replace(spread_location, labelled_arguments);
8655 } else if let Some(delete_start) = last_argument_end {
8656 // However, if there's no labelled arguments to insert we still need
8657 // to delete the entire spread: we start deleting from the end of the
8658 // final argument, if there's one.
8659 // This way we also get rid of any comma separating the last argument
8660 // and the spread to be removed.
8661 self.edits
8662 .delete(SrcSpan::new(delete_start, spread_location.end))
8663 } else {
8664 // Otherwise we just delete the spread.
8665 self.edits.delete(spread_location)
8666 }
8667
8668 let mut action = Vec::with_capacity(1);
8669 CodeActionBuilder::new("Fill unused fields")
8670 .kind(CodeActionKind::RefactorRewrite)
8671 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8672 .preferred(false)
8673 .push_to(&mut action);
8674 action
8675 }
8676}
8677
8678impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
8679 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
8680 // We can only interpolate/split a string if the cursor is somewhere
8681 // within its location, otherwise we skip it.
8682 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
8683 if !within(self.params.range, pattern_range) {
8684 return;
8685 }
8686
8687 if let TypedPattern::Constructor {
8688 arguments,
8689 spread: Some(spread_location),
8690 ..
8691 } = pattern
8692 && let Some(PatternUnusedArguments {
8693 positional,
8694 labelled,
8695 }) = pattern.unused_arguments()
8696 {
8697 // If there's any unused argument that's being ignored we want to
8698 // suggest the code action.
8699 let first_labelled_argument_start = arguments
8700 .iter()
8701 .find(|arg| !arg.is_implicit() && arg.label.is_some())
8702 .map(|arg| arg.location.start);
8703
8704 let last_argument_end = arguments
8705 .iter()
8706 .rfind(|arg| !arg.is_implicit())
8707 .map(|arg| arg.location.end);
8708
8709 self.data = Some(FillUnusedFieldsData {
8710 positional,
8711 labelled,
8712 first_labelled_argument_start,
8713 last_argument_end,
8714 spread_location: *spread_location,
8715 });
8716 };
8717
8718 ast::visit::visit_typed_pattern(self, pattern);
8719 }
8720}
8721
8722/// Code action to remove an echo.
8723///
8724pub struct RemoveEchos<'a> {
8725 module: &'a Module,
8726 params: &'a CodeActionParams,
8727 edits: TextEdits<'a>,
8728 is_hovering_echo: bool,
8729 echo_spans_to_delete: Vec<SrcSpan>,
8730 // We need to keep a reference to the two latest pipeline assignments we
8731 // run into to properly delete an echo that's inside a pipeline.
8732 latest_pipe_step: Option<SrcSpan>,
8733 second_to_latest_pipe_step: Option<SrcSpan>,
8734}
8735
8736impl<'a> RemoveEchos<'a> {
8737 pub fn new(
8738 module: &'a Module,
8739 line_numbers: &'a LineNumbers,
8740 params: &'a CodeActionParams,
8741 ) -> Self {
8742 Self {
8743 module,
8744 params,
8745 edits: TextEdits::new(line_numbers),
8746 is_hovering_echo: false,
8747 echo_spans_to_delete: vec![],
8748 latest_pipe_step: None,
8749 second_to_latest_pipe_step: None,
8750 }
8751 }
8752
8753 pub fn code_actions(mut self) -> Vec<CodeAction> {
8754 self.visit_typed_module(&self.module.ast);
8755
8756 // We only want to trigger the action if we're over one of the echos in
8757 // the module
8758 if !self.is_hovering_echo {
8759 return vec![];
8760 };
8761
8762 for span in self.echo_spans_to_delete {
8763 self.edits.delete(span);
8764 }
8765
8766 let mut action = Vec::with_capacity(1);
8767 CodeActionBuilder::new("Remove all `echo`s from this module")
8768 .kind(CodeActionKind::RefactorRewrite)
8769 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8770 .preferred(false)
8771 .push_to(&mut action);
8772 action
8773 }
8774
8775 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
8776 for i in 0..statements.len() {
8777 let statement = statements
8778 .get(i)
8779 .expect("Statement must exist in iteration");
8780 let next_statement = statements.get(i + 1);
8781 let is_last = i == statements.len() - 1;
8782
8783 match statement {
8784 // We remove any echo that is used as a standalone statement used
8785 // to print a literal value.
8786 //
8787 // ```gleam
8788 // pub fn main() {
8789 // echo "I'm here"
8790 // do_something()
8791 // echo "Safe!"
8792 // do_something_else()
8793 // }
8794 // ```
8795 //
8796 // Here we want to remove not just the echo but also the literal
8797 // strings they're printing.
8798 //
8799 // It's safe to do this only if echo is not the last expression
8800 // in a function's block (otherwise we might change the function's
8801 // return type by removing the entire line) and the value being
8802 // printed is a literal expression.
8803 //
8804 ast::Statement::Expression(TypedExpr::Echo {
8805 location,
8806 expression,
8807 ..
8808 }) if !is_last
8809 && expression.as_ref().is_some_and(|expression| {
8810 expression.is_literal() || expression.is_var()
8811 }) =>
8812 {
8813 let echo_range = self.edits.src_span_to_lsp_range(*location);
8814 if within(self.params.range, echo_range) {
8815 self.is_hovering_echo = true;
8816 }
8817
8818 let end = next_statement
8819 .map(|next| {
8820 let echo_end = location.end;
8821 let next_start = next.location().start;
8822 // We want to remove everything until the start of the
8823 // following statement. However, we have to be careful not to
8824 // delete any comments. So if there's any comment between the
8825 // echo to remove and the next statement, we just delete until
8826 // the comment's start.
8827 self.module
8828 .extra
8829 .first_comment_between(echo_end, next_start)
8830 // For comments we record the start of their content, not of the `//`
8831 // so we're subtracting 2 here to not delete the `//` as well
8832 .map(|comment| comment.start - 2)
8833 .unwrap_or(next_start)
8834 })
8835 .unwrap_or(location.end);
8836
8837 self.echo_spans_to_delete.push(SrcSpan {
8838 start: location.start,
8839 end,
8840 });
8841 }
8842
8843 // Otherwise we visit the statement as usual.
8844 ast::Statement::Expression(_)
8845 | ast::Statement::Assignment(_)
8846 | ast::Statement::Use(_)
8847 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement),
8848 }
8849 }
8850 }
8851}
8852
8853impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
8854 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
8855 self.visit_function_statements(&fun.body);
8856 }
8857
8858 fn visit_typed_expr_fn(
8859 &mut self,
8860 _location: &'ast SrcSpan,
8861 _type_: &'ast Arc<Type>,
8862 _kind: &'ast FunctionLiteralKind,
8863 _arguments: &'ast [TypedArg],
8864 body: &'ast Vec1<TypedStatement>,
8865 _return_annotation: &'ast Option<ast::TypeAst>,
8866 ) {
8867 self.visit_function_statements(body);
8868 }
8869
8870 fn visit_typed_expr_echo(
8871 &mut self,
8872 location: &'ast SrcSpan,
8873 type_: &'ast Arc<Type>,
8874 expression: &'ast Option<Box<TypedExpr>>,
8875 message: &'ast Option<Box<TypedExpr>>,
8876 ) {
8877 // We also want to trigger the action if we're hovering over the expression
8878 // being printed. So we create a unique span starting from the start of echo
8879 // end ending at the end of the expression.
8880 //
8881 // ```
8882 // echo 1 + 2
8883 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
8884 // inside it, not just the keyword
8885 // ```
8886 //
8887 let echo_range = self.edits.src_span_to_lsp_range(*location);
8888 if within(self.params.range, echo_range) {
8889 self.is_hovering_echo = true;
8890 }
8891
8892 // We also want to remove the echo message!
8893 if message.is_some() {
8894 let start = expression
8895 .as_ref()
8896 .map(|expression| expression.location().end)
8897 .unwrap_or(location.start + 4);
8898
8899 self.echo_spans_to_delete
8900 .push(SrcSpan::new(start, location.end));
8901 }
8902
8903 if let Some(expression) = expression {
8904 // If there's an expression we delete everything we find until its
8905 // start (excluded).
8906 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
8907 self.echo_spans_to_delete.push(span_to_delete);
8908 } else {
8909 // Othwerise we know we're inside a pipeline, we take the closest step
8910 // that is not echo itself and delete everything from its end until the
8911 // end of the echo keyword:
8912 //
8913 // ```txt
8914 // wibble |> echo |> wobble
8915 // ^^^^^^^^ This span right here
8916 // ```
8917 let step_preceding_echo = self
8918 .latest_pipe_step
8919 .filter(|l| l != location)
8920 .or(self.second_to_latest_pipe_step);
8921 if let Some(step_preceding_echo) = step_preceding_echo {
8922 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
8923 self.echo_spans_to_delete.push(span_to_delete);
8924 }
8925 }
8926
8927 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message);
8928 }
8929
8930 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
8931 if self.latest_pipe_step.is_some() {
8932 self.second_to_latest_pipe_step = self.latest_pipe_step;
8933 }
8934 self.latest_pipe_step = Some(assignment.location);
8935 ast::visit::visit_typed_pipeline_assignment(self, assignment);
8936 }
8937}
8938
8939/// Code action to wrap assignment and case clause values in a block.
8940///
8941/// ```gleam
8942/// pub type PokemonType {
8943/// Fire
8944/// Water
8945/// }
8946///
8947/// pub fn main() {
8948/// let pokemon_type: PokemonType = todo
8949/// case pokemon_type {
8950/// Water -> soak()
8951/// ^^^^^^ Cursor over the spread
8952/// Fire -> burn()
8953/// }
8954/// }
8955/// ```
8956/// Becomes
8957/// ```gleam
8958/// pub type PokemonType {
8959/// Fire
8960/// Water
8961/// }
8962///
8963/// pub fn main() {
8964/// let pokemon_type: PokemonType = todo
8965/// case pokemon_type {
8966/// Water -> {
8967/// soak()
8968/// }
8969/// Fire -> burn()
8970/// }
8971/// }
8972/// ```
8973///
8974pub struct WrapInBlock<'a> {
8975 module: &'a Module,
8976 params: &'a CodeActionParams,
8977 edits: TextEdits<'a>,
8978 selected_expression: Option<SrcSpan>,
8979}
8980
8981impl<'a> WrapInBlock<'a> {
8982 pub fn new(
8983 module: &'a Module,
8984 line_numbers: &'a LineNumbers,
8985 params: &'a CodeActionParams,
8986 ) -> Self {
8987 Self {
8988 module,
8989 params,
8990 edits: TextEdits::new(line_numbers),
8991 selected_expression: None,
8992 }
8993 }
8994
8995 pub fn code_actions(mut self) -> Vec<CodeAction> {
8996 self.visit_typed_module(&self.module.ast);
8997
8998 let Some(expr_span) = self.selected_expression else {
8999 return vec![];
9000 };
9001
9002 let Some(expr_string) = self
9003 .module
9004 .code
9005 .get(expr_span.start as usize..(expr_span.end as usize + 1))
9006 else {
9007 return vec![];
9008 };
9009
9010 let range = self
9011 .edits
9012 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
9013
9014 let indent_size =
9015 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
9016
9017 let expr_indent_size = indent_size + 2;
9018
9019 let indent = " ".repeat(indent_size);
9020 let inner_indent = " ".repeat(expr_indent_size);
9021
9022 self.edits.replace(
9023 expr_span,
9024 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
9025 );
9026
9027 let mut action = Vec::with_capacity(1);
9028 CodeActionBuilder::new("Wrap in block")
9029 .kind(CodeActionKind::RefactorExtract)
9030 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9031 .preferred(false)
9032 .push_to(&mut action);
9033 action
9034 }
9035}
9036
9037impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
9038 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
9039 ast::visit::visit_typed_expr(self, &assignment.value);
9040 if !within(
9041 self.params.range,
9042 self.edits
9043 .src_span_to_lsp_range(assignment.value.location()),
9044 ) {
9045 return;
9046 }
9047 match &assignment.value {
9048 // To avoid wrapping the same expression in multiple, nested blocks.
9049 TypedExpr::Block { .. } => {}
9050 TypedExpr::RecordAccess { .. }
9051 | TypedExpr::PositionalAccess { .. }
9052 | TypedExpr::Int { .. }
9053 | TypedExpr::Float { .. }
9054 | TypedExpr::String { .. }
9055 | TypedExpr::Pipeline { .. }
9056 | TypedExpr::Var { .. }
9057 | TypedExpr::Fn { .. }
9058 | TypedExpr::List { .. }
9059 | TypedExpr::Call { .. }
9060 | TypedExpr::BinOp { .. }
9061 | TypedExpr::Case { .. }
9062 | TypedExpr::ModuleSelect { .. }
9063 | TypedExpr::Tuple { .. }
9064 | TypedExpr::TupleIndex { .. }
9065 | TypedExpr::Todo { .. }
9066 | TypedExpr::Panic { .. }
9067 | TypedExpr::Echo { .. }
9068 | TypedExpr::BitArray { .. }
9069 | TypedExpr::RecordUpdate { .. }
9070 | TypedExpr::NegateBool { .. }
9071 | TypedExpr::NegateInt { .. }
9072 | TypedExpr::Invalid { .. } => {
9073 self.selected_expression = Some(assignment.value.location());
9074 }
9075 };
9076 ast::visit::visit_typed_assignment(self, assignment);
9077 }
9078
9079 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
9080 ast::visit::visit_typed_clause(self, clause);
9081
9082 if !within(
9083 self.params.range,
9084 self.edits.src_span_to_lsp_range(clause.then.location()),
9085 ) {
9086 return;
9087 }
9088
9089 // To avoid wrapping the same expression in multiple, nested blocks.
9090 if !matches!(clause.then, TypedExpr::Block { .. }) {
9091 self.selected_expression = Some(clause.then.location());
9092 };
9093
9094 ast::visit::visit_typed_clause(self, clause);
9095 }
9096}
9097
9098/// Code action to fix wrong binary operators when the compiler can easily tell
9099/// what the correct alternative is.
9100///
9101/// ```gleam
9102/// 1 +. 2 // becomes 1 + 2
9103/// 1.0 + 2.3 // becomes 1.0 +. 2.3
9104/// ```
9105///
9106pub struct FixBinaryOperation<'a> {
9107 module: &'a Module,
9108 params: &'a CodeActionParams,
9109 edits: TextEdits<'a>,
9110 fix: Option<(SrcSpan, ast::BinOp)>,
9111}
9112
9113impl<'a> FixBinaryOperation<'a> {
9114 pub fn new(
9115 module: &'a Module,
9116 line_numbers: &'a LineNumbers,
9117 params: &'a CodeActionParams,
9118 ) -> Self {
9119 Self {
9120 module,
9121 params,
9122 edits: TextEdits::new(line_numbers),
9123 fix: None,
9124 }
9125 }
9126
9127 pub fn code_actions(mut self) -> Vec<CodeAction> {
9128 self.visit_typed_module(&self.module.ast);
9129
9130 let Some((location, replacement)) = self.fix else {
9131 return vec![];
9132 };
9133
9134 self.edits.replace(location, replacement.name().into());
9135
9136 let mut action = Vec::with_capacity(1);
9137 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
9138 .kind(CodeActionKind::RefactorRewrite)
9139 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9140 .preferred(true)
9141 .push_to(&mut action);
9142 action
9143 }
9144
9145 fn try_fix(
9146 &mut self,
9147 left: Arc<Type>,
9148 right: Arc<Type>,
9149 operator: ast::BinOp,
9150 operator_start: u32,
9151 ) {
9152 let operator_location = SrcSpan::new(operator_start, operator_start + operator.size());
9153 if operator.is_int_operator() && left.is_float() && right.is_float() {
9154 self.fix = operator
9155 .float_equivalent()
9156 .map(|fix| (operator_location, fix));
9157 } else if operator.is_float_operator() && left.is_int() && right.is_int() {
9158 self.fix = operator
9159 .int_equivalent()
9160 .map(|fix| (operator_location, fix))
9161 } else if operator == ast::BinOp::AddInt && left.is_string() && right.is_string() {
9162 self.fix = Some((operator_location, ast::BinOp::Concatenate))
9163 }
9164 }
9165}
9166
9167impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
9168 fn visit_typed_expr_case(
9169 &mut self,
9170 location: &'ast SrcSpan,
9171 type_: &'ast Arc<Type>,
9172 subjects: &'ast [TypedExpr],
9173 clauses: &'ast [ast::TypedClause],
9174 compiled_case: &'ast CompiledCase,
9175 ) {
9176 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
9177 }
9178 fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) {
9179 ast::visit::visit_typed_clause_guard(self, guard);
9180 }
9181
9182 fn visit_typed_clause_guard_bin_op(
9183 &mut self,
9184 left: &'ast TypedClauseGuard,
9185 right: &'ast TypedClauseGuard,
9186 operator: &'ast ast::BinOp,
9187 operator_start: &'ast u32,
9188 location: &'ast SrcSpan,
9189 ) {
9190 let binop_range = self.edits.src_span_to_lsp_range(*location);
9191 if !within(self.params.range, binop_range) {
9192 return;
9193 }
9194
9195 self.try_fix(left.type_(), right.type_(), *operator, *operator_start);
9196
9197 ast::visit::visit_typed_clause_guard_bin_op(
9198 self,
9199 left,
9200 right,
9201 operator,
9202 operator_start,
9203 location,
9204 );
9205 }
9206
9207 fn visit_typed_expr_bin_op(
9208 &mut self,
9209 location: &'ast SrcSpan,
9210 type_: &'ast Arc<Type>,
9211 operator: &'ast ast::BinOp,
9212 operator_start: &'ast u32,
9213 left: &'ast TypedExpr,
9214 right: &'ast TypedExpr,
9215 ) {
9216 let binop_range = self.edits.src_span_to_lsp_range(*location);
9217 if !within(self.params.range, binop_range) {
9218 return;
9219 }
9220
9221 self.try_fix(left.type_(), right.type_(), *operator, *operator_start);
9222
9223 ast::visit::visit_typed_expr_bin_op(
9224 self,
9225 location,
9226 type_,
9227 operator,
9228 operator_start,
9229 left,
9230 right,
9231 );
9232 }
9233}
9234
9235/// Code action builder to automatically fix segments that have a value that's
9236/// guaranteed to overflow.
9237///
9238pub struct FixTruncatedBitArraySegment<'a> {
9239 module: &'a Module,
9240 params: &'a CodeActionParams,
9241 edits: TextEdits<'a>,
9242 truncation: Option<BitArraySegmentTruncation>,
9243}
9244
9245impl<'a> FixTruncatedBitArraySegment<'a> {
9246 pub fn new(
9247 module: &'a Module,
9248 line_numbers: &'a LineNumbers,
9249 params: &'a CodeActionParams,
9250 ) -> Self {
9251 Self {
9252 module,
9253 params,
9254 edits: TextEdits::new(line_numbers),
9255 truncation: None,
9256 }
9257 }
9258
9259 pub fn code_actions(mut self) -> Vec<CodeAction> {
9260 self.visit_typed_module(&self.module.ast);
9261
9262 let Some(truncation) = self.truncation else {
9263 return vec![];
9264 };
9265
9266 let replacement = truncation.truncated_into.to_string();
9267 self.edits
9268 .replace(truncation.value_location, replacement.clone());
9269
9270 let mut action = Vec::with_capacity(1);
9271 CodeActionBuilder::new(&format!("Replace with `{replacement}`"))
9272 .kind(CodeActionKind::RefactorRewrite)
9273 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9274 .preferred(true)
9275 .push_to(&mut action);
9276 action
9277 }
9278}
9279
9280impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
9281 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
9282 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
9283 if !within(self.params.range, segment_range) {
9284 return;
9285 }
9286
9287 if let Some(truncation) = segment.check_for_truncated_value() {
9288 self.truncation = Some(truncation);
9289 }
9290
9291 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
9292 }
9293}
9294
9295/// Code action builder to remove unused imports and values.
9296///
9297pub struct RemoveUnusedImports<'a> {
9298 module: &'a Module,
9299 params: &'a CodeActionParams,
9300 edits: TextEdits<'a>,
9301}
9302
9303#[derive(Debug)]
9304enum UnusedImport {
9305 ValueOrType(SrcSpan),
9306 Module(SrcSpan),
9307 ModuleAlias(SrcSpan),
9308}
9309
9310impl UnusedImport {
9311 fn location(&self) -> SrcSpan {
9312 match self {
9313 UnusedImport::ValueOrType(location)
9314 | UnusedImport::Module(location)
9315 | UnusedImport::ModuleAlias(location) => *location,
9316 }
9317 }
9318}
9319
9320impl<'a> RemoveUnusedImports<'a> {
9321 pub fn new(
9322 module: &'a Module,
9323 line_numbers: &'a LineNumbers,
9324 params: &'a CodeActionParams,
9325 ) -> Self {
9326 Self {
9327 module,
9328 params,
9329 edits: TextEdits::new(line_numbers),
9330 }
9331 }
9332
9333 /// Given an import location, returns a list of the spans of all the
9334 /// unqualified values it's importing. Sorted by SrcSpan location.
9335 ///
9336 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> {
9337 self.module
9338 .ast
9339 .definitions
9340 .imports
9341 .iter()
9342 .find(|import| import.location.contains(import_location.start))
9343 .map(|import| {
9344 let types = import.unqualified_types.iter().map(|type_| type_.location);
9345 let values = import.unqualified_values.iter().map(|value| value.location);
9346 types
9347 .chain(values)
9348 .sorted_by_key(|location| location.start)
9349 .collect_vec()
9350 })
9351 .unwrap_or_default()
9352 }
9353
9354 pub fn code_actions(mut self) -> Vec<CodeAction> {
9355 // If there's no import in the module then there can't be any unused
9356 // import to remove.
9357 if self.module.ast.definitions.imports.is_empty() {
9358 return vec![];
9359 }
9360
9361 let unused_imports = self
9362 .module
9363 .ast
9364 .type_info
9365 .warnings
9366 .iter()
9367 .filter_map(|warning| match warning {
9368 type_::Warning::UnusedImportedValue { location, .. } => {
9369 Some(UnusedImport::ValueOrType(*location))
9370 }
9371 type_::Warning::UnusedType {
9372 location,
9373 imported: true,
9374 ..
9375 } => Some(UnusedImport::ValueOrType(*location)),
9376 type_::Warning::UnusedImportedModule { location, .. } => {
9377 Some(UnusedImport::Module(*location))
9378 }
9379 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
9380 Some(UnusedImport::ModuleAlias(*location))
9381 }
9382 type_::Warning::Todo { .. }
9383 | type_::Warning::ImplicitlyDiscardedResult { .. }
9384 | type_::Warning::UnusedLiteral { .. }
9385 | type_::Warning::UnusedValue { .. }
9386 | type_::Warning::NoFieldsRecordUpdate { .. }
9387 | type_::Warning::AllFieldsRecordUpdate { .. }
9388 | type_::Warning::UnusedType { .. }
9389 | type_::Warning::UnusedConstructor { .. }
9390 | type_::Warning::UnusedPrivateModuleConstant { .. }
9391 | type_::Warning::UnusedPrivateFunction { .. }
9392 | type_::Warning::UnusedVariable { .. }
9393 | type_::Warning::UnnecessaryDoubleIntNegation { .. }
9394 | type_::Warning::UnnecessaryDoubleBoolNegation { .. }
9395 | type_::Warning::InefficientEmptyListCheck { .. }
9396 | type_::Warning::TransitiveDependencyImported { .. }
9397 | type_::Warning::DeprecatedItem { .. }
9398 | type_::Warning::UnreachableCasePattern { .. }
9399 | type_::Warning::UnusedDiscardPattern { .. }
9400 | type_::Warning::CaseMatchOnLiteralCollection { .. }
9401 | type_::Warning::CaseMatchOnLiteralValue { .. }
9402 | type_::Warning::OpaqueExternalType { .. }
9403 | type_::Warning::RedundantAssertAssignment { .. }
9404 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. }
9405 | type_::Warning::TodoOrPanicUsedAsFunction { .. }
9406 | type_::Warning::UnreachableCodeAfterPanic { .. }
9407 | type_::Warning::RedundantPipeFunctionCapture { .. }
9408 | type_::Warning::FeatureRequiresHigherGleamVersion { .. }
9409 | type_::Warning::JavaScriptIntUnsafe { .. }
9410 | type_::Warning::AssertLiteralBool { .. }
9411 | type_::Warning::BitArraySegmentTruncatedValue { .. }
9412 | type_::Warning::ModuleImportedTwice { .. }
9413 | type_::Warning::TopLevelDefinitionShadowsImport { .. }
9414 | type_::Warning::RedundantComparison { .. }
9415 | type_::Warning::UnusedRecursiveArgument { .. }
9416 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. } => None,
9417 })
9418 .sorted_by_key(|import| import.location())
9419 .collect_vec();
9420
9421 // If the cursor is not over any of the unused imports then we don't offer
9422 // the code action.
9423 let hovering_unused_import = unused_imports.iter().any(|import| {
9424 let unused_range = self.edits.src_span_to_lsp_range(import.location());
9425 overlaps(self.params.range, unused_range)
9426 });
9427 if !hovering_unused_import {
9428 return vec![];
9429 }
9430
9431 // Otherwise we start removing all unused imports:
9432 for import in &unused_imports {
9433 match import {
9434 // When an entire module is unused we can delete its entire location
9435 // in the source code.
9436 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
9437 if self.edits.line_numbers.spans_entire_line(location) {
9438 // If the unused module spans over the entire line then
9439 // we also take care of removing the following newline
9440 // characther!
9441 self.edits.delete(SrcSpan {
9442 start: location.start,
9443 end: location.end + 1,
9444 })
9445 } else {
9446 self.edits.delete(*location)
9447 }
9448 }
9449
9450 // When removing unused imported values we have to be a bit more
9451 // careful: an unused value might be followed or preceded by a
9452 // comma that we also need to remove!
9453 UnusedImport::ValueOrType(location) => {
9454 let imported = self.imported_values(*location);
9455 let unused_index = imported.binary_search(location);
9456 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1);
9457 let next_value = unused_index
9458 .ok()
9459 .and_then(|value_index| imported.get(value_index + 1));
9460 let previous_value = unused_index.ok().and_then(|value_index| {
9461 value_index
9462 .checked_sub(1)
9463 .and_then(|previous_index| imported.get(previous_index))
9464 });
9465 let previous_is_unused = previous_value.is_some_and(|previous| {
9466 unused_imports
9467 .as_slice()
9468 .binary_search_by_key(previous, |import| import.location())
9469 .is_ok()
9470 });
9471
9472 match (previous_value, next_value) {
9473 // If there's a value following the unused import we need
9474 // to remove all characters until its start!
9475 //
9476 // ```gleam
9477 // import wibble.{unused, used}
9478 // // ^^^^^^^^^^^ We need to remove all of this!
9479 // ```
9480 //
9481 (_, Some(next_value)) => self.edits.delete(SrcSpan {
9482 start: location.start,
9483 end: next_value.start,
9484 }),
9485
9486 // If this unused import is the last of the unuqualified
9487 // list and is preceded by another used value then we
9488 // need to do some additional cleanup and remove all
9489 // characters starting from its end.
9490 // (If the previous one is unused as well it will take
9491 // care of removing all the extra space)
9492 //
9493 // ```gleam
9494 // import wibble.{used, unused}
9495 // // ^^^^^^^^^^^^ We need to remove all of this!
9496 // ```
9497 //
9498 (Some(previous_value), _) if is_last && !previous_is_unused => {
9499 self.edits.delete(SrcSpan {
9500 start: previous_value.end,
9501 end: location.end,
9502 })
9503 }
9504
9505 // In all other cases it means that this is the only
9506 // item in the import list. We can just remove it.
9507 //
9508 // ```gleam
9509 // import wibble.{unused}
9510 // // ^^^^^^ We remove this import, the formatter will already
9511 // // take care of removing the empty curly braces
9512 // ```
9513 //
9514 (_, _) => self.edits.delete(*location),
9515 }
9516 }
9517 }
9518 }
9519
9520 let mut action = Vec::with_capacity(1);
9521 CodeActionBuilder::new("Remove unused imports")
9522 .kind(CodeActionKind::RefactorRewrite)
9523 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9524 .preferred(true)
9525 .push_to(&mut action);
9526 action
9527 }
9528}
9529
9530/// Code action to remove a block wrapping a single expression.
9531///
9532pub struct RemoveBlock<'a> {
9533 module: &'a Module,
9534 params: &'a CodeActionParams,
9535 edits: TextEdits<'a>,
9536 block_span: Option<SrcSpan>,
9537 position: RemoveBlockPosition,
9538}
9539
9540#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
9541enum RemoveBlockPosition {
9542 InsideBinOp,
9543 OutsideBinOp,
9544}
9545
9546impl<'a> RemoveBlock<'a> {
9547 pub fn new(
9548 module: &'a Module,
9549 line_numbers: &'a LineNumbers,
9550 params: &'a CodeActionParams,
9551 ) -> Self {
9552 Self {
9553 module,
9554 params,
9555 edits: TextEdits::new(line_numbers),
9556 block_span: None,
9557 position: RemoveBlockPosition::OutsideBinOp,
9558 }
9559 }
9560
9561 pub fn code_actions(mut self) -> Vec<CodeAction> {
9562 self.visit_typed_module(&self.module.ast);
9563
9564 let Some(SrcSpan { start, end }) = self.block_span else {
9565 return vec![];
9566 };
9567
9568 self.edits.delete(SrcSpan::new(start, start + 1));
9569 self.edits.delete(SrcSpan::new(end - 1, end));
9570
9571 let mut action = Vec::with_capacity(1);
9572 CodeActionBuilder::new("Remove block")
9573 .kind(CodeActionKind::RefactorRewrite)
9574 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9575 .preferred(true)
9576 .push_to(&mut action);
9577 action
9578 }
9579}
9580
9581impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> {
9582 fn visit_typed_expr_bin_op(
9583 &mut self,
9584 _location: &'ast SrcSpan,
9585 _type_: &'ast Arc<Type>,
9586 _operator: &'ast ast::BinOp,
9587 _operator_start: &'ast u32,
9588 left: &'ast TypedExpr,
9589 right: &'ast TypedExpr,
9590 ) {
9591 let old_position = self.position;
9592 self.position = RemoveBlockPosition::InsideBinOp;
9593 ast::visit::visit_typed_expr(self, left);
9594 self.position = RemoveBlockPosition::InsideBinOp;
9595 ast::visit::visit_typed_expr(self, right);
9596 self.position = old_position;
9597 }
9598
9599 fn visit_typed_expr_block(
9600 &mut self,
9601 location: &'ast SrcSpan,
9602 statements: &'ast [TypedStatement],
9603 ) {
9604 let block_range = self.edits.src_span_to_lsp_range(*location);
9605 if !within(self.params.range, block_range) {
9606 return;
9607 }
9608
9609 match statements {
9610 [] | [_, _, ..] => (),
9611 [value] => match value {
9612 ast::Statement::Use(_)
9613 | ast::Statement::Assert(_)
9614 | ast::Statement::Assignment(_) => {
9615 ast::visit::visit_typed_expr_block(self, location, statements)
9616 }
9617
9618 ast::Statement::Expression(expr) => match expr {
9619 TypedExpr::Int { .. }
9620 | TypedExpr::Float { .. }
9621 | TypedExpr::String { .. }
9622 | TypedExpr::Block { .. }
9623 | TypedExpr::Var { .. }
9624 | TypedExpr::Fn { .. }
9625 | TypedExpr::List { .. }
9626 | TypedExpr::Call { .. }
9627 | TypedExpr::Case { .. }
9628 | TypedExpr::RecordAccess { .. }
9629 | TypedExpr::PositionalAccess { .. }
9630 | TypedExpr::ModuleSelect { .. }
9631 | TypedExpr::Tuple { .. }
9632 | TypedExpr::TupleIndex { .. }
9633 | TypedExpr::Todo { .. }
9634 | TypedExpr::Panic { .. }
9635 | TypedExpr::Echo { .. }
9636 | TypedExpr::BitArray { .. }
9637 | TypedExpr::RecordUpdate { .. }
9638 | TypedExpr::NegateBool { .. }
9639 | TypedExpr::NegateInt { .. }
9640 | TypedExpr::Invalid { .. } => {
9641 self.block_span = Some(*location);
9642 }
9643 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => {
9644 if self.position == RemoveBlockPosition::OutsideBinOp {
9645 self.block_span = Some(*location);
9646 }
9647 }
9648 },
9649 },
9650 }
9651
9652 ast::visit::visit_typed_expr_block(self, location, statements);
9653 }
9654}
9655
9656/// Code action to remove `opaque` from a private type.
9657///
9658pub struct RemovePrivateOpaque<'a> {
9659 module: &'a Module,
9660 params: &'a CodeActionParams,
9661 edits: TextEdits<'a>,
9662 opaque_span: Option<SrcSpan>,
9663}
9664
9665impl<'a> RemovePrivateOpaque<'a> {
9666 pub fn new(
9667 module: &'a Module,
9668 line_numbers: &'a LineNumbers,
9669 params: &'a CodeActionParams,
9670 ) -> Self {
9671 Self {
9672 module,
9673 params,
9674 edits: TextEdits::new(line_numbers),
9675 opaque_span: None,
9676 }
9677 }
9678
9679 pub fn code_actions(mut self) -> Vec<CodeAction> {
9680 self.visit_typed_module(&self.module.ast);
9681
9682 let Some(opaque_span) = self.opaque_span else {
9683 return vec![];
9684 };
9685
9686 self.edits.delete(opaque_span);
9687
9688 let mut action = Vec::with_capacity(1);
9689 CodeActionBuilder::new("Remove opaque from private type")
9690 .kind(CodeActionKind::QuickFix)
9691 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9692 .preferred(true)
9693 .push_to(&mut action);
9694 action
9695 }
9696}
9697
9698impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> {
9699 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
9700 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location);
9701 if !within(self.params.range, custom_type_range) {
9702 return;
9703 }
9704
9705 if custom_type.opaque && custom_type.publicity.is_private() {
9706 self.opaque_span = Some(SrcSpan {
9707 start: custom_type.location.start,
9708 end: custom_type.location.start + 7,
9709 })
9710 }
9711 }
9712}
9713
9714/// Code action to rewrite a case expression as part of an outer case expression
9715/// branch. For example:
9716///
9717/// ```gleam
9718/// case wibble {
9719/// Ok(a) -> case a {
9720/// 1 -> todo
9721/// _ -> todo
9722/// }
9723/// Error(_) -> todo
9724/// }
9725/// ```
9726///
9727/// Would become:
9728///
9729/// ```gleam
9730/// case wibble {
9731/// Ok(1) -> todo
9732/// Ok(_) -> todo
9733/// Error(_) -> todo
9734/// }
9735/// ```
9736///
9737pub struct CollapseNestedCase<'a> {
9738 module: &'a Module,
9739 params: &'a CodeActionParams,
9740 edits: TextEdits<'a>,
9741 collapsed: Option<Collapsed<'a>>,
9742}
9743
9744/// This holds all the needed data about the pattern to collapse.
9745/// We'll use this piece of code as an example:
9746/// ```gleam
9747/// case something {
9748/// User(username: _, NotAdmin) -> "Stranger!!"
9749/// User(username:, Admin) if wibble ->
9750/// case username { // <- We're collapsing this nested case
9751/// "Joe" -> "Hello, Joe!"
9752/// _ -> "I don't know you, " <> username
9753/// }
9754/// }
9755/// ```
9756///
9757struct Collapsed<'a> {
9758 /// This is the span covering the entire clause being collapsed:
9759 ///
9760 /// ```gleam
9761 /// case something {
9762 /// User(username: _, NotAdmin) -> "Stranger!!"
9763 /// User(username:, Admin) if wibble ->
9764 /// ┬ It goes all the way from here...
9765 /// ╭─╯
9766 /// │ case username {
9767 /// │ "Joe" -> "Hello, Joe!"
9768 /// │ _ -> "I don't know you, " <> username
9769 /// │ }
9770 /// │ ┬ ...to here!
9771 /// ╰───╯
9772 /// }
9773 /// ```
9774 ///
9775 outer_clause_span: SrcSpan,
9776
9777 /// The (optional) guard of the outer branch. In this exmaple it's this one:
9778 ///
9779 /// ```gleam
9780 /// case something {
9781 /// User(username: _, NotAdmin) -> "Stranger!!"
9782 /// User(username:, Admin) if wibble ->
9783 /// ┬────────
9784 /// ╰─ `outer_guard`
9785 /// case username {
9786 /// "Joe" -> "Hello, Joe!"
9787 /// _ -> "I don't know you, " <> username
9788 /// }
9789 /// }
9790 /// ```
9791 ///
9792 outer_guard: &'a Option<TypedClauseGuard>,
9793
9794 /// The pattern variable being matched on:
9795 ///
9796 /// ```gleam
9797 /// case something {
9798 /// User(username: _, NotAdmin) -> "Stranger!!"
9799 /// User(username:, Admin) if wibble ->
9800 /// ┬───────
9801 /// ╰─ `matched_variable`
9802 /// case username {
9803 /// "Joe" -> "Hello, Joe!"
9804 /// _ -> "I don't know you, " <> username
9805 /// }
9806 /// }
9807 /// ```
9808 ///
9809 matched_variable: BoundVariable,
9810
9811 /// The span covering the entire pattern that is bringing the matched
9812 /// variable in scope:
9813 ///
9814 /// ```gleam
9815 /// case something {
9816 /// User(username: _, NotAdmin) -> "Stranger!!"
9817 /// User(username:, Admin) if wibble ->
9818 /// ┬─────────────────────
9819 /// ╰─ `matched_pattern_span`
9820 /// case username {
9821 /// "Joe" -> "Hello, Joe!"
9822 /// _ -> "I don't know you, " <> username
9823 /// }
9824 /// }
9825 /// ```
9826 ///
9827 matched_pattern_span: SrcSpan,
9828
9829 /// The clauses matching on the `username` variable. In this case they are:
9830 /// ```gleam
9831 /// "Joe" -> "Hello, Joe!"
9832 /// _ -> "I don't know you, " <> username
9833 /// ```
9834 ///
9835 inner_clauses: &'a Vec<ast::TypedClause>,
9836}
9837
9838impl<'a> CollapseNestedCase<'a> {
9839 pub fn new(
9840 module: &'a Module,
9841 line_numbers: &'a LineNumbers,
9842 params: &'a CodeActionParams,
9843 ) -> Self {
9844 Self {
9845 module,
9846 params,
9847 edits: TextEdits::new(line_numbers),
9848 collapsed: None,
9849 }
9850 }
9851
9852 pub fn code_actions(mut self) -> Vec<CodeAction> {
9853 self.visit_typed_module(&self.module.ast);
9854
9855 let Some(Collapsed {
9856 outer_clause_span,
9857 outer_guard,
9858 ref matched_variable,
9859 matched_pattern_span,
9860 inner_clauses,
9861 }) = self.collapsed
9862 else {
9863 return vec![];
9864 };
9865
9866 // Now comes the tricky part: we need to replace the current pattern
9867 // that is bringing the variable into scope with many new patterns, one
9868 // for each of the inner clauses.
9869 //
9870 // Each time we will have to replace the matched variable with the
9871 // pattern used in the inner clause. Let's look at an example:
9872 //
9873 // ```gleam
9874 // Ok(a) -> case a {
9875 // 1 -> wibble
9876 // 2 | 3 -> wobble
9877 // _ -> woo
9878 // }
9879 // ```
9880 //
9881 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then
9882 // with `2` and `3`, and finally with `_`. Obtaining something like
9883 // this:
9884 //
9885 // ```gleam
9886 // Ok(1) -> wibble
9887 // Ok(2) | Ok(3) -> wobble
9888 // Ok(_) -> woo
9889 // ```
9890 //
9891 // Notice one key detail: since alternative patterns can't be nested we
9892 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`!
9893
9894 let pattern_text: String = code_at(self.module, matched_pattern_span).into();
9895 let matched_variable_span = matched_variable.location;
9896
9897 let pattern_with_variable = |mut new_content: String| {
9898 let mut new_pattern = pattern_text.clone();
9899
9900 match matched_variable {
9901 BoundVariable {
9902 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. },
9903 ..
9904 } => {
9905 let trimmed_contents = new_content.trim();
9906
9907 let pattern_is_literal_list =
9908 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]");
9909 let pattern_is_discard = trimmed_contents == "_";
9910
9911 let span_to_replace = match (
9912 &matched_variable.name,
9913 // We verify whether the pattern is compatible with the list prefix `..`.
9914 // For example, `..var` is valid syntax, but `..[]` and `.._` are not.
9915 pattern_is_literal_list || pattern_is_discard,
9916 ) {
9917 // We normally replace the selected variable with the pattern.
9918 (BoundVariableName::Regular { .. }, _) => matched_variable_span,
9919
9920 // If the selected pattern is not a list, we also replace it normally.
9921 (BoundVariableName::ListTail { .. }, false) => matched_variable_span,
9922 // If the pattern is a list to also remove the list tail prefix.
9923 (BoundVariableName::ListTail { tail_location, .. }, true) => {
9924 // When it's a list literal, we remove the surrounding brackets.
9925 let len = trimmed_contents.len();
9926 if let Some(slice) = new_content.trim().get(1..(len - 1)) {
9927 new_content = slice.to_string()
9928 };
9929
9930 *tail_location
9931 }
9932
9933 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(),
9934 };
9935
9936 let start_of_pattern =
9937 (span_to_replace.start - matched_pattern_span.start) as usize;
9938 let pattern_length = span_to_replace.len();
9939
9940 let end_of_pattern = start_of_pattern + pattern_length;
9941 let replaced_range = start_of_pattern..end_of_pattern;
9942
9943 new_pattern.replace_range(replaced_range, &new_content);
9944 }
9945
9946 BoundVariable {
9947 name: BoundVariableName::ShorthandLabel { .. },
9948 ..
9949 } => {
9950 // But if it's introduced using the shorthand syntax we can't
9951 // just replace it's location with the new pattern: we would be
9952 // removing the label!!
9953 // So we instead insert the pattern right after the label.
9954 new_pattern.insert_str(
9955 (matched_variable_span.end - matched_pattern_span.start) as usize,
9956 &format!(" {new_content}"),
9957 );
9958 }
9959 }
9960
9961 new_pattern
9962 };
9963
9964 let mut new_clauses = vec![];
9965 for clause in inner_clauses {
9966 // Here we take care of unrolling any alterantive patterns: for each
9967 // of the alternatives we build a new pattern and then join
9968 // everything together with ` | `.
9969
9970 let references_to_matched_variable =
9971 FindVariableReferences::new(matched_variable_span, matched_variable.name())
9972 .find(&clause.then);
9973
9974 let new_patterns = iter::once(&clause.pattern)
9975 .chain(&clause.alternative_patterns)
9976 .map(|patterns| {
9977 // If we've reached this point we've already made in the
9978 // traversal that the inner clause is matching on a single
9979 // subject. So this should be safe to expect!
9980 let pattern_location =
9981 patterns.first().expect("must have a pattern").location();
9982
9983 let mut pattern_code = code_at(self.module, pattern_location).to_string();
9984 if !references_to_matched_variable.is_empty() {
9985 pattern_code = format!("{pattern_code} as {}", matched_variable.name());
9986 };
9987 pattern_with_variable(pattern_code)
9988 })
9989 .join(" | ");
9990
9991 let clause_code = code_at(self.module, clause.then.location());
9992 let guard_code = match (outer_guard, &clause.guard) {
9993 (Some(outer), Some(inner)) => {
9994 let mut outer_code = code_at(self.module, outer.location()).to_string();
9995 let mut inner_code = code_at(self.module, inner.location()).to_string();
9996 if ast::BinOp::And.precedence() > outer.precedence() {
9997 outer_code = format!("{{ {outer_code} }}")
9998 }
9999 if ast::BinOp::And.precedence() > inner.precedence() {
10000 inner_code = format!("{{ {inner_code} }}")
10001 }
10002 format!(" if {outer_code} && {inner_code}")
10003 }
10004 (None, Some(guard)) | (Some(guard), None) => {
10005 format!(" if {}", code_at(self.module, guard.location()))
10006 }
10007 (None, None) => "".into(),
10008 };
10009
10010 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}"));
10011 }
10012
10013 let pattern_nesting = self
10014 .edits
10015 .src_span_to_lsp_range(outer_clause_span)
10016 .start
10017 .character;
10018 let indentation = " ".repeat(pattern_nesting as usize);
10019
10020 self.edits.replace(
10021 outer_clause_span,
10022 new_clauses.join(&format!("\n{indentation}")),
10023 );
10024
10025 let mut action = Vec::with_capacity(1);
10026 CodeActionBuilder::new("Collapse nested case")
10027 .kind(CodeActionKind::RefactorRewrite)
10028 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10029 .preferred(false)
10030 .push_to(&mut action);
10031 action
10032 }
10033
10034 /// If the clause can be flattened because it's matching on a single variable
10035 /// defined in it, this function will return the info needed by the language
10036 /// server to flatten that case.
10037 ///
10038 /// We can only flatten a case expression in a very specific case:
10039 /// - This pattern may be introducing multiple variables,
10040 /// - The expression following this branch must be a case, and
10041 /// - It must be matching on one of those variables
10042 ///
10043 /// For example:
10044 ///
10045 /// ```gleam
10046 /// Wibble(a, b, 1) -> case a { ... }
10047 /// Wibble(a, b, 1) -> case b { ... }
10048 /// ```
10049 ///
10050 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> {
10051 let ast::TypedClause {
10052 pattern,
10053 alternative_patterns,
10054 then,
10055 location,
10056 guard,
10057 } = clause;
10058
10059 if !alternative_patterns.is_empty() {
10060 return None;
10061 }
10062
10063 // The `then` clause must be a single case expression matching on a
10064 // single variable.
10065 let Some(TypedExpr::Case {
10066 subjects, clauses, ..
10067 }) = single_expression(then)
10068 else {
10069 return None;
10070 };
10071
10072 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else {
10073 return None;
10074 };
10075
10076 // That variable must be one the variables we brought into scope in this
10077 // branch.
10078 let variable = pattern
10079 .iter()
10080 .flat_map(|pattern| pattern.bound_variables())
10081 .find(|variable| variable.name() == *name)?;
10082
10083 // There's one last condition to trigger the code action: we must
10084 // actually be with the cursor over the pattern or the nested case
10085 // expression!
10086 //
10087 // ```gleam
10088 // case wibble {
10089 // Ok(a) -> case a {
10090 // //^^^^^^^^^^^^^^^ Anywhere over here!
10091 // }
10092 // }
10093 // ```
10094 //
10095 let first_pattern = pattern.first().expect("at least one pattern");
10096 let last_pattern = pattern.last().expect("at least one pattern");
10097 let pattern_location = first_pattern.location().merge(&last_pattern.location());
10098
10099 let last_inner_subject = subjects.last().expect("at least one subject");
10100 let trigger_location = pattern_location.merge(&last_inner_subject.location());
10101 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location);
10102
10103 if within(self.params.range, trigger_range) {
10104 Some(Collapsed {
10105 outer_clause_span: *location,
10106 outer_guard: guard,
10107 matched_variable: variable,
10108 matched_pattern_span: pattern_location,
10109 inner_clauses: clauses,
10110 })
10111 } else {
10112 None
10113 }
10114 }
10115}
10116
10117impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> {
10118 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
10119 if let Some(collapsed) = self.flatten_clause(clause) {
10120 self.collapsed = Some(collapsed);
10121
10122 // We're done, there's no need to keep exploring as we know the
10123 // cursor is over this pattern and it can't be over any other one!
10124 return;
10125 };
10126
10127 ast::visit::visit_typed_clause(self, clause);
10128 }
10129}
10130
10131/// If the expression is a single expression, or a block containing a single
10132/// expression, this function will return it.
10133/// But if the expression is a block with multiple statements, an assignment
10134/// of a use, this will return None.
10135///
10136fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
10137 match expression {
10138 // If a block has a single statement, we can flatten it into a
10139 // single expression if that one statement is an expression.
10140 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() {
10141 ast::Statement::Expression(expression) => single_expression(expression),
10142 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
10143 None
10144 }
10145 },
10146
10147 // If a block has multiple statements then it can't be flattened
10148 // into a single expression.
10149 TypedExpr::Block { .. } => None,
10150
10151 TypedExpr::Int { .. }
10152 | TypedExpr::Float { .. }
10153 | TypedExpr::String { .. }
10154 | TypedExpr::Pipeline { .. }
10155 | TypedExpr::Var { .. }
10156 | TypedExpr::Fn { .. }
10157 | TypedExpr::List { .. }
10158 | TypedExpr::Call { .. }
10159 | TypedExpr::BinOp { .. }
10160 | TypedExpr::Case { .. }
10161 | TypedExpr::RecordAccess { .. }
10162 | TypedExpr::PositionalAccess { .. }
10163 | TypedExpr::ModuleSelect { .. }
10164 | TypedExpr::Tuple { .. }
10165 | TypedExpr::TupleIndex { .. }
10166 | TypedExpr::Todo { .. }
10167 | TypedExpr::Panic { .. }
10168 | TypedExpr::Echo { .. }
10169 | TypedExpr::BitArray { .. }
10170 | TypedExpr::RecordUpdate { .. }
10171 | TypedExpr::NegateBool { .. }
10172 | TypedExpr::NegateInt { .. }
10173 | TypedExpr::Invalid { .. } => Some(expression),
10174 }
10175}
10176
10177/// Code action to remove unreachable clauses from a case expression.
10178///
10179pub struct RemoveUnreachableCaseClauses<'a> {
10180 module: &'a Module,
10181 params: &'a CodeActionParams,
10182 edits: TextEdits<'a>,
10183 /// The source location of the patterns of all the unreachable clauses in
10184 /// the current module.
10185 ///
10186 unreachable_clauses: HashSet<SrcSpan>,
10187 clauses_to_delete: Vec<SrcSpan>,
10188}
10189
10190impl<'a> RemoveUnreachableCaseClauses<'a> {
10191 pub fn new(
10192 module: &'a Module,
10193 line_numbers: &'a LineNumbers,
10194 params: &'a CodeActionParams,
10195 ) -> Self {
10196 let unreachable_clauses = module
10197 .ast
10198 .type_info
10199 .warnings
10200 .iter()
10201 .filter_map(|warning| {
10202 if let type_::Warning::UnreachableCasePattern { location, .. } = warning {
10203 Some(*location)
10204 } else {
10205 None
10206 }
10207 })
10208 .collect();
10209
10210 Self {
10211 unreachable_clauses,
10212 module,
10213 params,
10214 edits: TextEdits::new(line_numbers),
10215 clauses_to_delete: vec![],
10216 }
10217 }
10218
10219 pub fn code_actions(mut self) -> Vec<CodeAction> {
10220 self.visit_typed_module(&self.module.ast);
10221 if self.clauses_to_delete.is_empty() {
10222 return vec![];
10223 }
10224
10225 for branch in self.clauses_to_delete {
10226 self.edits.delete(branch);
10227 }
10228
10229 let mut action = Vec::with_capacity(1);
10230 CodeActionBuilder::new("Remove unreachable clauses")
10231 .kind(CodeActionKind::QuickFix)
10232 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10233 .preferred(true)
10234 .push_to(&mut action);
10235 action
10236 }
10237}
10238
10239impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> {
10240 fn visit_typed_expr_case(
10241 &mut self,
10242 location: &'ast SrcSpan,
10243 type_: &'ast Arc<Type>,
10244 subjects: &'ast [TypedExpr],
10245 clauses: &'ast [ast::TypedClause],
10246 compiled_case: &'ast CompiledCase,
10247 ) {
10248 // We're showing the code action only if we're within one of the
10249 // unreachable patterns. And the code action is going to remove all the
10250 // unreachable patterns for this case.
10251 let is_hovering_clause = clauses.iter().any(|clause| {
10252 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location());
10253 within(self.params.range, pattern_range)
10254 });
10255
10256 // If we're not hovering any of the clauses then we want to
10257 // keep visiting the case expression as the unreachable branch might be
10258 // in one of the nested cases.
10259 if !is_hovering_clause {
10260 ast::visit::visit_typed_expr_case(
10261 self,
10262 location,
10263 type_,
10264 subjects,
10265 clauses,
10266 compiled_case,
10267 );
10268 return;
10269 }
10270
10271 for clause in clauses {
10272 let mut all_patterns_are_unreachable = true;
10273 let mut unreachable_patterns = vec![];
10274 let mut previous_pattern_end = None;
10275 let mut all_previous_patterns_were_deleted = true;
10276
10277 for pattern in clause.patterns() {
10278 let pattern_location = multi_pattern_location(pattern);
10279 if self.unreachable_clauses.contains(&pattern_location) {
10280 // If an alternative is unreachable we want to delete
10281 // everything from the end of the previous alternative to
10282 // the start of this one.
10283 //
10284 // ```gleam
10285 // Error(_) | Error(_) | Ok(_)
10286 // // ^^^^^^^^^^^ We want to delete all of this
10287 // ```
10288 unreachable_patterns.push(
10289 previous_pattern_end.map_or(pattern_location, |previous_end| {
10290 SrcSpan::new(previous_end, pattern_location.end)
10291 }),
10292 );
10293 } else {
10294 // If all the previous alternatives have been deleted and
10295 // this one is reachable there's some final cleanup we need
10296 // to take care of:
10297 //
10298 // ```gleam
10299 // Ok(_) | Ok(_) | Error(_) -> todo
10300 // //^^^^^^^^^^^ All of this has been deleted, but there's
10301 // // that last vertical bar that needs to be
10302 // // taken care of!
10303 // ```
10304 //
10305
10306 if all_previous_patterns_were_deleted && let Some(end) = previous_pattern_end {
10307 self.clauses_to_delete
10308 .push(SrcSpan::new(end, pattern_location.start));
10309 }
10310
10311 all_previous_patterns_were_deleted = false;
10312 all_patterns_are_unreachable = false;
10313 }
10314
10315 previous_pattern_end = pattern.last().map(|pattern| pattern.location().end);
10316 }
10317
10318 if all_patterns_are_unreachable {
10319 // If all the patterns of the clause are unreachable then we
10320 // want to delete the entire branch:
10321 //
10322 // ```gleam
10323 // case a, b {
10324 // _, _ -> todo
10325 // Ok(_), Ok(_) | Error(_), Error(_) -> todo
10326 // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10327 // // we want the entire branch to be deleted!
10328 // }
10329 // ```
10330 self.clauses_to_delete.push(clause.location())
10331 } else {
10332 // If only some of the variants are unreachable but not all
10333 // we want to delete just those.
10334 // case a, b {
10335 // 1, 2 | 1, 2 -> todo
10336 // // ^^^^ just this one should be deleted
10337 // }
10338 self.clauses_to_delete.extend(&unreachable_patterns);
10339 }
10340 }
10341 }
10342}
10343
10344/// Given a pattern with possibly many subjects, this returns the location
10345/// spanning the whole thing.
10346fn multi_pattern_location(alternative: &[Pattern<Arc<Type>>]) -> SrcSpan {
10347 let start = alternative
10348 .first()
10349 .map(|pattern| pattern.location().start)
10350 .unwrap_or_default();
10351 let end = alternative
10352 .last()
10353 .map(|pattern| pattern.location().end)
10354 .unwrap_or_default();
10355
10356 SrcSpan::new(start, end)
10357}
10358
10359/// Code action to remove a record update when all of its fields have been
10360/// provided already:
10361///
10362/// ```gleam
10363/// pub type Wibble { Wibble(one: Int, two: Int) }
10364///
10365/// wibble(..wibble, one:, two:)
10366/// // ^^^^^^^^ This is not needed and raises a warning!
10367/// ```
10368///
10369pub struct RemoveRedundantRecordUpdate<'a> {
10370 module: &'a Module,
10371 params: &'a CodeActionParams,
10372 edits: TextEdits<'a>,
10373}
10374
10375impl<'a> RemoveRedundantRecordUpdate<'a> {
10376 pub fn new(
10377 module: &'a Module,
10378 line_numbers: &'a LineNumbers,
10379 params: &'a CodeActionParams,
10380 ) -> Self {
10381 Self {
10382 module,
10383 params,
10384 edits: TextEdits::new(line_numbers),
10385 }
10386 }
10387
10388 pub fn code_actions(mut self) -> Vec<CodeAction> {
10389 let spread_to_remove = self
10390 .module
10391 .ast
10392 .type_info
10393 .warnings
10394 .iter()
10395 .find_map(|warning| {
10396 if let type_::Warning::AllFieldsRecordUpdate {
10397 location,
10398 record_location,
10399 } = warning
10400 && within(
10401 self.params.range,
10402 self.edits.src_span_to_lsp_range(*location),
10403 )
10404 {
10405 Some(*record_location)
10406 } else {
10407 None
10408 }
10409 });
10410
10411 let Some(spread_to_remove) = spread_to_remove else {
10412 return vec![];
10413 };
10414 self.edits.delete(spread_to_remove);
10415
10416 let mut action = Vec::with_capacity(1);
10417 CodeActionBuilder::new("Remove redundant record update")
10418 .kind(CodeActionKind::QuickFix)
10419 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10420 .preferred(true)
10421 .push_to(&mut action);
10422 action
10423 }
10424}
10425
10426/// Code action to add labels to a constructor/call where all the labels where
10427/// omitted.
10428///
10429pub struct AddOmittedLabels<'a> {
10430 module: &'a Module,
10431 params: &'a CodeActionParams,
10432 edits: TextEdits<'a>,
10433 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>,
10434}
10435
10436struct CallArgumentWithOmittedLabel {
10437 location: SrcSpan,
10438
10439 /// If the argument has a label this will be the label we can use for it.
10440 ///
10441 omitted_label: Option<EcoString>,
10442
10443 /// If the argument is a variable that has the same name as the omitted label
10444 /// and could use the shorthand syntax.
10445 ///
10446 can_use_shorthand_syntax: bool,
10447}
10448
10449impl<'a> AddOmittedLabels<'a> {
10450 pub fn new(
10451 module: &'a Module,
10452 line_numbers: &'a LineNumbers,
10453 params: &'a CodeActionParams,
10454 ) -> Self {
10455 Self {
10456 module,
10457 params,
10458 edits: TextEdits::new(line_numbers),
10459 arguments_and_omitted_labels: None,
10460 }
10461 }
10462
10463 pub fn code_actions(mut self) -> Vec<CodeAction> {
10464 self.visit_typed_module(&self.module.ast);
10465
10466 let Some(call_arguments) = self.arguments_and_omitted_labels else {
10467 return vec![];
10468 };
10469
10470 for call_argument in call_arguments {
10471 let Some(label) = call_argument.omitted_label else {
10472 continue;
10473 };
10474 if call_argument.can_use_shorthand_syntax {
10475 self.edits.insert(call_argument.location.end, ":".into());
10476 } else {
10477 self.edits
10478 .insert(call_argument.location.start, format!("{label}: "))
10479 }
10480 }
10481
10482 let mut action = Vec::with_capacity(1);
10483 CodeActionBuilder::new("Add omitted labels")
10484 .kind(CodeActionKind::RefactorRewrite)
10485 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10486 .preferred(false)
10487 .push_to(&mut action);
10488 action
10489 }
10490}
10491
10492impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
10493 fn visit_typed_expr_call(
10494 &mut self,
10495 location: &'ast SrcSpan,
10496 type_: &'ast Arc<Type>,
10497 fun: &'ast TypedExpr,
10498 arguments: &'ast [TypedCallArg],
10499 open_parenthesis: &'ast Option<u32>,
10500 ) {
10501 let called_function_range = self.edits.src_span_to_lsp_range(fun.location());
10502 if !within(self.params.range, called_function_range) {
10503 ast::visit::visit_typed_expr_call(
10504 self,
10505 location,
10506 type_,
10507 fun,
10508 arguments,
10509 open_parenthesis,
10510 );
10511 return;
10512 }
10513
10514 let Some(field_map) = fun.field_map() else {
10515 ast::visit::visit_typed_expr_call(
10516 self,
10517 location,
10518 type_,
10519 fun,
10520 arguments,
10521 open_parenthesis,
10522 );
10523 return;
10524 };
10525 let argument_index_to_label = field_map.indices_to_labels();
10526
10527 let mut omitted_labels = Vec::with_capacity(arguments.len());
10528 for (index, argument) in arguments.iter().enumerate() {
10529 // If the argument already has a label we don't want to add a label
10530 // for it, so we skip it.
10531 if let Some(label) = &argument.label {
10532 // Though, before skipping, we want to make sure that the label
10533 // is actually right for the function call. If it's not then we
10534 // give up on adding labels because there wouldn't be no way of
10535 // knowing which label to add.
10536 if !field_map.fields.contains_key(label) {
10537 return;
10538 } else {
10539 continue;
10540 }
10541 }
10542 // No labels for pipes, uses, etc!
10543 if argument.is_implicit() {
10544 continue;
10545 }
10546
10547 let label = argument_index_to_label
10548 .get(&(index as u32))
10549 .cloned()
10550 .cloned();
10551
10552 let can_use_shorthand_syntax = match (&label, &argument.value) {
10553 (Some(label), TypedExpr::Var { name, .. }) => name == label,
10554 (Some(_) | None, _) => false,
10555 };
10556
10557 omitted_labels.push(CallArgumentWithOmittedLabel {
10558 location: argument.location,
10559 omitted_label: label,
10560 can_use_shorthand_syntax,
10561 })
10562 }
10563 self.arguments_and_omitted_labels = Some(omitted_labels);
10564 }
10565}
10566
10567/// Code action to extract selected code into a separate function.
10568/// If a user selected a portion of code in a function, we offer a code action
10569/// to extract it into a new one. This can either be a single expression, such
10570/// as in the following example:
10571///
10572/// ```gleam
10573/// pub fn main() {
10574/// let value = {
10575/// // ^ User selects from here
10576/// ...
10577/// }
10578/// //^ Until here
10579/// }
10580/// ```
10581///
10582/// Here, we would extract the selected block expression. It could also be a
10583/// series of statements. For example:
10584///
10585/// ```gleam
10586/// pub fn main() {
10587/// let a = 1
10588/// //^ User selects from here
10589/// let b = 2
10590/// let c = a + b
10591/// // ^ Until here
10592///
10593/// do_more_things(c)
10594/// }
10595/// ```
10596///
10597/// Here, we want to extract the statements inside the user's selection.
10598///
10599pub struct ExtractFunction<'a> {
10600 module: &'a Module,
10601 params: &'a CodeActionParams,
10602 edits: TextEdits<'a>,
10603 function: Option<ExtractedFunction<'a>>,
10604 function_end_position: Option<u32>,
10605 /// Since the `visit_typed_statement` visitor function doesn't tell us when
10606 /// a statement is the last in a block or function, we need to track that
10607 /// manually.
10608 last_statement_location: Option<SrcSpan>,
10609 /// When visiting a pipeline step, this will hold the type of the value
10610 /// returned by the previous step (if any!)
10611 previous_pipeline_assignment_type: Option<Arc<Type>>,
10612}
10613
10614/// Information about a section of code we are extracting as a function.
10615#[derive(Debug)]
10616struct ExtractedFunction<'a> {
10617 /// A list of parameters which need to be passed to the extracted function.
10618 /// These are any variables used in the extracted code, which are defined
10619 /// outside of the extracted code.
10620 parameters: Vec<(EcoString, Arc<Type>)>,
10621 /// A list of values which need to be returned from the extracted function.
10622 /// These are the variables defined in the extracted code which are used
10623 /// outside of the extracted section.
10624 returned_variables: Vec<(EcoString, Arc<Type>)>,
10625 /// The piece of code to be extracted. This is either a single expression or
10626 /// a list of statements, as explained in the documentation of `ExtractFunction`
10627 value: ExtractedValue<'a>,
10628}
10629
10630impl<'a> ExtractedFunction<'a> {
10631 fn new(value: ExtractedValue<'a>) -> Self {
10632 Self {
10633 value,
10634 parameters: Vec::new(),
10635 returned_variables: Vec::new(),
10636 }
10637 }
10638
10639 fn location(&self) -> SrcSpan {
10640 match &self.value {
10641 ExtractedValue::Expression(expression) => expression.location(),
10642 ExtractedValue::Statements { location, .. }
10643 | ExtractedValue::Use { location, .. }
10644 | ExtractedValue::PipelineSteps { location, .. } => *location,
10645 }
10646 }
10647
10648 /// If the extracted function is a series of pipeline steps, this adds to it
10649 /// the given pipeline step, otherwise leaving it unchanged.
10650 /// If the extracted function was indeed a pipeline, this will return `true`,
10651 /// otherwise it returns `false`.
10652 ///
10653 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) {
10654 if let ExtractedFunction {
10655 value:
10656 ExtractedValue::PipelineSteps {
10657 location,
10658 before_first: _,
10659 return_type,
10660 },
10661 ..
10662 } = self
10663 {
10664 // If we're extracting this pipeline and the final step is included
10665 // in the selection we want to add it to the extracted steps
10666 *return_type = step_type;
10667 *location = location.merge(&step_location);
10668 }
10669 }
10670}
10671
10672#[derive(Debug)]
10673enum ExtractedValue<'a> {
10674 Expression(&'a TypedExpr),
10675 Statements {
10676 location: SrcSpan,
10677 position: StatementPosition,
10678 },
10679 /// We're extracting a single use statement. We need this special case to
10680 /// properly handle the statements inside of them.
10681 Use {
10682 /// This is the location of the entire use block, including the
10683 /// statements in it.
10684 location: SrcSpan,
10685 /// This is the location of the expression on the right hand side of the use
10686 /// arrow.
10687 ///
10688 /// ```gleam
10689 /// use a <- result.try(result)
10690 /// ^^^^^^^^^^^^^^^^^^
10691 /// ```
10692 ///
10693 use_line_location: SrcSpan,
10694 type_: Arc<Type>,
10695 },
10696 PipelineSteps {
10697 location: SrcSpan,
10698 /// The type of the value produced by the pipeline steps that will be
10699 /// piped into the extracted function. Could be none if the steps we're
10700 /// extracting include the first step, in that case there would be
10701 /// nothing that is fed into it.
10702 before_first: Option<Arc<Type>>,
10703 /// The type returned by the extracted steps.
10704 return_type: Arc<Type>,
10705 },
10706}
10707
10708impl ExtractedValue<'_> {
10709 fn location(&self) -> SrcSpan {
10710 match self {
10711 ExtractedValue::Expression(typed_expr) => typed_expr.location(),
10712 ExtractedValue::Statements { location, .. }
10713 | ExtractedValue::PipelineSteps { location, .. }
10714 | ExtractedValue::Use { location, .. } => *location,
10715 }
10716 }
10717}
10718
10719/// When we are extracting multiple statements, there are two possible cases:
10720/// The first is if we are extracting statements in the middle of a function.
10721/// In this case, we will need to return some number of arguments, or `Nil`.
10722/// For example:
10723///
10724/// ```gleam
10725/// pub fn main() {
10726/// let message = "Hello!"
10727/// let log_message = "[INFO] " <> message
10728/// //^ Select from here
10729/// io.println(log_message)
10730/// // ^ Until here
10731///
10732/// do_some_more_things()
10733/// }
10734/// ```
10735///
10736/// Here, the extracted function doesn't bind any variables which we need
10737/// afterwards, it purely performs side effects. In this case we can just return
10738/// `Nil` from the new function.
10739///
10740/// However, consider the following:
10741///
10742/// ```gleam
10743/// pub fn main() {
10744/// let a = 1
10745/// let b = 2
10746/// //^ Select from here
10747/// a + b
10748/// // ^ Until here
10749/// }
10750/// ```
10751///
10752/// Here, despite us not needing any variables from the extracted code, there
10753/// is one key difference: the `a + b` expression is at the end of the function,
10754/// and so its value is returned from the entire function. This is known as the
10755/// "tail" position. In that case, we can't return `Nil` as that would make the
10756/// `main` function return `Nil` instead of the result of the addition. If we
10757/// extract the tail-position statement, we need to return that last value rather
10758/// than `Nil`.
10759///
10760#[derive(Debug)]
10761enum StatementPosition {
10762 Tail { type_: Arc<Type> },
10763 NotTail,
10764}
10765
10766impl<'a> ExtractFunction<'a> {
10767 pub fn new(
10768 module: &'a Module,
10769 line_numbers: &'a LineNumbers,
10770 params: &'a CodeActionParams,
10771 ) -> Self {
10772 Self {
10773 module,
10774 params,
10775 edits: TextEdits::new(line_numbers),
10776 function: None,
10777 function_end_position: None,
10778 last_statement_location: None,
10779 previous_pipeline_assignment_type: None,
10780 }
10781 }
10782
10783 pub fn code_actions(mut self) -> Vec<CodeAction> {
10784 // If no code is selected, then there is no function to extract and we
10785 // can return no code actions.
10786 if self.params.range.start == self.params.range.end {
10787 return Vec::new();
10788 }
10789
10790 self.visit_typed_module(&self.module.ast);
10791
10792 let Some(end) = self.function_end_position else {
10793 return Vec::new();
10794 };
10795
10796 // If nothing was found in the selected range, there is no code action.
10797 let Some(extracted) = self.function.take() else {
10798 return Vec::new();
10799 };
10800
10801 match extracted.value {
10802 // If we extract a block, it isn't very helpful to have the body of
10803 // the extracted function just be a single block expression, so
10804 // instead we extract the statements inside the block. For example,
10805 // the following code:
10806 //
10807 // ```gleam
10808 // pub fn main() {
10809 // let x = {
10810 // // ^ Select from here
10811 // let a = 1
10812 // let b = 2
10813 // a + b
10814 // }
10815 // //^ Until here
10816 // x
10817 // }
10818 // ```
10819 //
10820 // Would produce the following extracted function:
10821 //
10822 // ```gleam
10823 // fn function() {
10824 // let a = 1
10825 // let b = 2
10826 // a + b
10827 // }
10828 // ```
10829 //
10830 // Rather than:
10831 //
10832 // ```gleam
10833 // fn function() {
10834 // {
10835 // let a = 1
10836 // let b = 2
10837 // a + b
10838 // }
10839 // }
10840 // ```
10841 //
10842 ExtractedValue::Expression(TypedExpr::Block {
10843 statements,
10844 location: full_location,
10845 }) => {
10846 let location = statements
10847 .first()
10848 .location()
10849 .merge(&statements.last().location());
10850
10851 self.extract_code_in_tail_position(
10852 *full_location,
10853 location,
10854 statements.last().type_(),
10855 extracted.parameters,
10856 end,
10857 )
10858 }
10859 ExtractedValue::Expression(TypedExpr::Fn {
10860 type_,
10861 location: full_location,
10862 kind: FunctionLiteralKind::Anonymous { .. },
10863 arguments,
10864 body,
10865 ..
10866 }) => {
10867 let location = body.first().location().merge(&body.last().location());
10868 let return_type = type_.return_type().expect("Fn should have a return type");
10869
10870 if extracted.parameters.is_empty() {
10871 self.extract_anonymous_function(
10872 *full_location,
10873 location,
10874 arguments,
10875 return_type,
10876 end,
10877 )
10878 } else if arguments.len() == 1 {
10879 self.extract_anonymous_function_with_capture_hole(
10880 *full_location,
10881 location,
10882 arguments.first().expect("There is exactly one argument"),
10883 extracted.parameters,
10884 return_type,
10885 end,
10886 )
10887 } else {
10888 self.extract_anonymous_function_body(
10889 location,
10890 arguments,
10891 extracted.parameters,
10892 return_type,
10893 end,
10894 )
10895 }
10896 }
10897 ExtractedValue::Expression(expression) => {
10898 let expression_type = if let TypedExpr::Fn {
10899 type_,
10900 kind: FunctionLiteralKind::Use { .. },
10901 ..
10902 } = expression
10903 {
10904 type_.fn_types().expect("use callback to be a function").1
10905 } else {
10906 expression.type_()
10907 };
10908 self.extract_code_in_tail_position(
10909 expression.location(),
10910 expression.location(),
10911 expression_type,
10912 extracted.parameters,
10913 end,
10914 )
10915 }
10916 ExtractedValue::Statements {
10917 location,
10918 position: StatementPosition::NotTail,
10919 } => self.extract_statements(
10920 location,
10921 extracted.parameters,
10922 extracted.returned_variables,
10923 end,
10924 ),
10925
10926 ExtractedValue::Use {
10927 location,
10928 use_line_location: _,
10929 type_,
10930 }
10931 | ExtractedValue::Statements {
10932 location,
10933 position: StatementPosition::Tail { type_ },
10934 } => self.extract_code_in_tail_position(
10935 location,
10936 location,
10937 type_,
10938 extracted.parameters,
10939 end,
10940 ),
10941 ExtractedValue::PipelineSteps {
10942 location,
10943 before_first,
10944 return_type,
10945 } => self.extract_pipeline_steps(
10946 location,
10947 end,
10948 extracted.parameters,
10949 before_first,
10950 return_type,
10951 ),
10952 }
10953
10954 let mut action = Vec::with_capacity(1);
10955 CodeActionBuilder::new("Extract function")
10956 .kind(CodeActionKind::RefactorExtract)
10957 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10958 .preferred(false)
10959 .push_to(&mut action);
10960 action
10961 }
10962
10963 /// Choose a suitable name for an extracted function to make sure it doesn't
10964 /// clash with existing functions defined in the module and cause an error.
10965 fn function_name(&self) -> EcoString {
10966 if !self.module.ast.type_info.values.contains_key("function") {
10967 return "function".into();
10968 }
10969
10970 let mut number = 2;
10971 loop {
10972 let name = eco_format!("function_{number}");
10973 if !self.module.ast.type_info.values.contains_key(&name) {
10974 return name;
10975 }
10976 number += 1;
10977 }
10978 }
10979
10980 /// For anonymous functions that do not capture any variables from an outer scope.
10981 /// Moves the function so it is defined at the module top-level instead,
10982 /// replacing the original literal with a reference to the new function.
10983 fn extract_anonymous_function(
10984 &mut self,
10985 location: SrcSpan,
10986 code_location: SrcSpan,
10987 arguments: &[TypedArg],
10988 return_type: Arc<Type>,
10989 function_end: u32,
10990 ) {
10991 // --- BEFORE
10992 // ```gleam
10993 // pub fn main() {
10994 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) })
10995 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
10996 // }
10997 // ```
10998 //
10999 // --- AFTER
11000 // ```gleam
11001 // pub fn main() {
11002 // list.each([1, 2, 3], function)
11003 // }
11004 //
11005 // fn function(x: Int) -> Nil {
11006 // io.println(int.to_string(x))
11007 // }
11008 // ```
11009
11010 let name = self.function_name();
11011 self.edits.replace(location, name.to_string());
11012
11013 let mut printer = Printer::new(&self.module.ast.names);
11014
11015 let return_type = printer.print_type(&return_type);
11016 let function_body = code_at(self.module, code_location);
11017 let mut name_generator = NameGenerator::new();
11018 let arguments = arguments
11019 .iter()
11020 .map(|arg| {
11021 if let Some(name) = arg.get_variable_name() {
11022 eco_format!("{name}: {}", printer.print_type(&arg.type_))
11023 } else {
11024 let name = name_generator.generate_name_from_type(&arg.type_);
11025 eco_format!("_{name}: {}", printer.print_type(&arg.type_))
11026 }
11027 })
11028 .join(", ");
11029
11030 let function = format!(
11031 "\n\nfn {name}({arguments}) -> {return_type} {{
11032 {function_body}
11033}}"
11034 );
11035 self.edits.insert(function_end, function);
11036 }
11037
11038 /// For anonymous functions that capture variables from an external scope
11039 /// but only expect a single argument.
11040 /// Uses function caputre syntax to provide a more concise refactoring than
11041 /// `extract_anonymous_function_body`.
11042 fn extract_anonymous_function_with_capture_hole(
11043 &mut self,
11044 location: SrcSpan,
11045 code_location: SrcSpan,
11046 argument: &TypedArg,
11047 extra_parameters: Vec<(EcoString, Arc<Type>)>,
11048 return_type: Arc<Type>,
11049 function_end: u32,
11050 ) {
11051 let name = self.function_name();
11052
11053 // --- BEFORE
11054 // ```gleam
11055 // pub fn main() {
11056 // let needle = 42
11057 // let haystack = [25, 81, 74, 42, 33]
11058 // list.filter(haystack, fn(x) { x == needle })
11059 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
11060 // }
11061 // ```
11062 //
11063 // --- AFTER
11064 // ```gleam
11065 // pub fn main() {
11066 // let needle = 42
11067 // let haystack = [25, 81, 74, 42, 33]
11068 // list.filter(haystack, function(_, needle))
11069 // }
11070 //
11071 // fn function(x: Int, needle: Int) -> Bool {
11072 // x == needle
11073 // }
11074 // ```
11075
11076 let call = format!(
11077 "{name}(_, {})",
11078 extra_parameters.iter().map(|(name, _)| name).join(", ")
11079 );
11080 self.edits.replace(location, call);
11081
11082 let mut printer = Printer::new(&self.module.ast.names);
11083
11084 // build up the code for the newly generated function
11085 let return_type = printer.print_type(&return_type);
11086 let function_body = code_at(self.module, code_location);
11087 let argument = if let Some(name) = argument.get_variable_name() {
11088 eco_format!("{name}: {}", printer.print_type(&argument.type_))
11089 } else {
11090 let name = NameGenerator::new().generate_name_from_type(&argument.type_);
11091 eco_format!("_{name}: {}", printer.print_type(&argument.type_))
11092 };
11093 let extra_parameters = extra_parameters
11094 .iter()
11095 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11096 .join(", ");
11097
11098 let function = format!(
11099 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{
11100 {function_body}
11101}}"
11102 );
11103 self.edits.insert(function_end, function);
11104 }
11105
11106 /// For non-unary anonymous functions that capture variables from an external scope.
11107 /// Replaces just the _function body_ with a call to the newly generated function.
11108 fn extract_anonymous_function_body(
11109 &mut self,
11110 location: SrcSpan,
11111 arguments: &[TypedArg],
11112 extra_parameters: Vec<(EcoString, Arc<Type>)>,
11113 return_type: Arc<Type>,
11114 function_end: u32,
11115 ) {
11116 let name = self.function_name();
11117 // --- BEFORE
11118 // ```gleam
11119 // pub fn main() {
11120 // let factor = 2
11121 // list.fold([], 0, fn(acc, value) { acc + value * factor })
11122 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
11123 // }
11124 // ```
11125 //
11126 // --- AFTER
11127 // ```gleam
11128 // pub fn main() {
11129 // let factor = 2
11130 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) })
11131 // }
11132 //
11133 // fn function(acc: Int, value: Int, factor: Int) -> Int {
11134 // acc + value * factor
11135 // }
11136 // ```
11137
11138 // if the programmer has ignored an argument, the generated function
11139 // cannot take it as an parameter
11140 let arguments = arguments
11141 .iter()
11142 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_)))
11143 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_)))
11144 .collect::<Vec<(&EcoString, &Arc<Type>)>>();
11145
11146 let call = format!(
11147 "{name}({})",
11148 arguments.iter().map(|(name, _)| name).join(", ")
11149 );
11150 self.edits.replace(location, call);
11151
11152 let mut printer = Printer::new(&self.module.ast.names);
11153
11154 let return_type = printer.print_type(&return_type);
11155 let function_body = code_at(self.module, location);
11156 let arguments = arguments
11157 .iter()
11158 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11159 .join(", ");
11160
11161 let function = format!(
11162 "\n\nfn {name}({arguments}) -> {return_type} {{
11163 {function_body}
11164}}"
11165 );
11166 self.edits.insert(function_end, function);
11167 }
11168
11169 /// Extracts code from the end of a function or block. This could either be
11170 /// a single expression, or multiple statements followed by a final expression.
11171 fn extract_code_in_tail_position(
11172 &mut self,
11173 location: SrcSpan,
11174 code_location: SrcSpan,
11175 type_: Arc<Type>,
11176 parameters: Vec<(EcoString, Arc<Type>)>,
11177 function_end: u32,
11178 ) {
11179 let expression_code = code_at(self.module, code_location);
11180
11181 let name = self.function_name();
11182 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
11183 let call = format!("{name}({arguments})");
11184
11185 // Since we are only extracting a single expression, we can just replace
11186 // it with the call and preserve all other semantics; only one value can
11187 // be returned from the expression, unlike when extracting multiple
11188 // statements.
11189 self.edits.replace(location, call);
11190
11191 let mut printer = Printer::new(&self.module.ast.names);
11192
11193 let parameters = parameters
11194 .iter()
11195 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11196 .join(", ");
11197 let return_type = printer.print_type(&type_);
11198
11199 let function = format!(
11200 "\n\nfn {name}({parameters}) -> {return_type} {{
11201 {expression_code}
11202}}"
11203 );
11204
11205 self.edits.insert(function_end, function);
11206 }
11207
11208 fn extract_statements(
11209 &mut self,
11210 location: SrcSpan,
11211 parameters: Vec<(EcoString, Arc<Type>)>,
11212 returned_variables: Vec<(EcoString, Arc<Type>)>,
11213 function_end: u32,
11214 ) {
11215 let code = code_at(self.module, location);
11216
11217 let returns_anything = !returned_variables.is_empty();
11218
11219 // Here, we decide what value to return from the function. There are
11220 // three cases:
11221 // The first is when the extracted code is purely for side-effects, and
11222 // does not produce any values which are needed outside of the extracted
11223 // code. For example:
11224 //
11225 // ```gleam
11226 // pub fn main() {
11227 // let message = "Something important"
11228 // //^ Select from here
11229 // io.println("Something important")
11230 // io.println("Something else which is repeated")
11231 // // ^ Until here
11232 //
11233 // do_final_thing()
11234 // }
11235 // ```
11236 //
11237 // It doesn't make sense to return any values from this function, since
11238 // no values from the extract code are used afterwards, so we simply
11239 // return `Nil`.
11240 //
11241 // The next is when we need just a single value defined in the extracted
11242 // function, such as in this piece of code:
11243 //
11244 // ```gleam
11245 // pub fn main() {
11246 // let a = 10
11247 // //^ Select from here
11248 // let b = 20
11249 // let c = a + b
11250 // // ^ Until here
11251 //
11252 // echo c
11253 // }
11254 // ```
11255 //
11256 // Here, we can just return the single value, `c`.
11257 //
11258 // The last situation is when we need multiple defined values, such as
11259 // in the following code:
11260 //
11261 // ```gleam
11262 // pub fn main() {
11263 // let a = 10
11264 // //^ Select from here
11265 // let b = 20
11266 // let c = a + b
11267 // // ^ Until here
11268 //
11269 // echo a
11270 // echo b
11271 // echo c
11272 // }
11273 // ```
11274 //
11275 // In this case, we must return a tuple containing `a`, `b` and `c` in
11276 // order for the calling function to have access to the correct values.
11277 let (return_type, return_value) = match returned_variables.as_slice() {
11278 [] => (type_::nil(), "Nil".into()),
11279 [(name, type_)] => (type_.clone(), name.clone()),
11280 _ => {
11281 let values = returned_variables.iter().map(|(name, _)| name).join(", ");
11282 let type_ = type_::tuple(
11283 returned_variables
11284 .into_iter()
11285 .map(|(_, type_)| type_)
11286 .collect(),
11287 );
11288
11289 (type_, eco_format!("#({values})"))
11290 }
11291 };
11292
11293 let name = self.function_name();
11294 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
11295
11296 // If any values are returned from the extracted function, we need to
11297 // bind them so that they are accessible in the current scope.
11298 let call = if returns_anything {
11299 format!("let {return_value} = {name}({arguments})")
11300 } else {
11301 format!("{name}({arguments})")
11302 };
11303 self.edits.replace(location, call);
11304
11305 let mut printer = Printer::new(&self.module.ast.names);
11306
11307 let parameters = parameters
11308 .iter()
11309 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11310 .join(", ");
11311
11312 let return_type = printer.print_type(&return_type);
11313
11314 let function = format!(
11315 "\n\nfn {name}({parameters}) -> {return_type} {{
11316 {code}
11317 {return_value}
11318}}"
11319 );
11320
11321 self.edits.insert(function_end, function);
11322 }
11323
11324 fn extract_pipeline_steps(
11325 &mut self,
11326 location: SrcSpan,
11327 function_end: u32,
11328 parameters: Vec<(EcoString, Arc<Type>)>,
11329 before_first: Option<Arc<Type>>,
11330 return_type: Arc<Type>,
11331 ) {
11332 let name = self.function_name();
11333 let code = code_at(self.module, location);
11334 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", ");
11335 let replacement = match before_first {
11336 Some(_) if parameters.is_empty() => format!("{name}"),
11337 Some(_) | None => format!("{name}({arguments})"),
11338 };
11339 self.edits.replace(location, replacement);
11340
11341 // When extracting something out of the middle of a pipeline the
11342 // function we produce will produce a single value as output but could
11343 // take multiple values as input:
11344 //
11345 // ```gleam
11346 // wibble
11347 // |> wobble(a) // extracting this
11348 // |> woo(b) //
11349 // |> something
11350 // ```
11351 //
11352 // It will take the type returned by `wibble`, `a`, and `b` as input,
11353 // and produce the value returned by `woo` as output:
11354 //
11355 // ```gleam
11356 // wibble
11357 // |> function(a, b)
11358 // |> something
11359 // ```
11360 //
11361 // If the steps extracted are at the beginning of the pipeline, then it
11362 // won't take that additional argument!
11363 //
11364 // ```gleam
11365 // wibble // extracting these
11366 // |> wobble(a) //
11367 // |> woo(b) //
11368 // |> something
11369 // ```
11370 //
11371 // Becomes:
11372 //
11373 // ```gleam
11374 // function(a, b)
11375 // |> something
11376 // ```
11377
11378 let mut type_printer = Printer::new(&self.module.ast.names);
11379 let return_type = type_printer.print_type(&return_type);
11380 let first_argument = before_first.map(|type_of_first_argument| {
11381 let mut generator = NameGenerator::new();
11382 for (name, _) in parameters.iter() {
11383 generator.add_used_name(name.clone());
11384 }
11385 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument);
11386 (first_argument_name, type_of_first_argument.clone())
11387 });
11388 let parameters = first_argument
11389 .clone()
11390 .into_iter()
11391 .chain(parameters)
11392 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_)))
11393 .join(", ");
11394
11395 let code = if let Some((first_argument_name, _)) = first_argument {
11396 format!("{first_argument_name}\n |> {code}")
11397 } else {
11398 code.trim_start_matches("|>").to_string()
11399 };
11400 let function = format!(
11401 "\n\nfn {name}({parameters}) -> {return_type} {{
11402 {code}
11403}}"
11404 );
11405
11406 self.edits.insert(function_end, function);
11407 }
11408
11409 /// When a variable is referenced, we need to decide if we need to do anything
11410 /// to ensure that the reference is still valid after extracting a function.
11411 /// If the variable is defined outside the extracted function, but used inside
11412 /// it, then we need to add it as a parameter of the function. Similarly, if
11413 /// a variable is defined inside the extracted code, but used outside of it,
11414 /// we need to ensure that value is returned from the function so that it is
11415 /// accessible.
11416 fn register_referenced_variable(
11417 &mut self,
11418 name: &EcoString,
11419 type_: &Arc<Type>,
11420 location: SrcSpan,
11421 definition_location: SrcSpan,
11422 ) {
11423 let Some(extracted) = &mut self.function else {
11424 return;
11425 };
11426
11427 let extracted_location = extracted.location();
11428
11429 // If a variable defined outside the extracted code is referenced inside
11430 // it, we need to add it to the list of parameters.
11431 let variables = if extracted_location.contains_span(location)
11432 && !extracted_location.contains_span(definition_location)
11433 {
11434 &mut extracted.parameters
11435 // If a variable defined inside the extracted code is referenced outside
11436 // it, then we need to ensure that it is returned from the function.
11437 } else if extracted_location.contains_span(definition_location)
11438 && !extracted_location.contains_span(location)
11439 {
11440 &mut extracted.returned_variables
11441 } else {
11442 return;
11443 };
11444
11445 // If the variable has already been tracked, no need to register it again.
11446 // We use a `Vec` here rather than a `HashMap` because we want to ensure
11447 // the order of arguments is consistent; in this case it will be determined
11448 // by the order the variables are used. This isn't always desired, but it's
11449 // better than random order, and makes it easier to write tests too.
11450 // The cost of iterating the list here is minimal; it is unlikely that
11451 // a given function will ever have more than 10 or so parameters.
11452 if variables.iter().any(|(variable, _)| variable == name) {
11453 return;
11454 }
11455
11456 variables.push((name.clone(), type_.clone()));
11457 }
11458
11459 fn can_extract_expression(&self, expression: &TypedExpr) -> bool {
11460 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
11461 let selected_range = self.params.range;
11462
11463 // If the selected range doesn't touch the expression at all, then there
11464 // is no reason to extract it.
11465 if !overlaps(expression_range, selected_range) {
11466 return false;
11467 }
11468
11469 match expression {
11470 TypedExpr::Pipeline {
11471 first_value,
11472 finally,
11473 ..
11474 } => {
11475 // We can extract a pipeline as a whole only if the selection
11476 // spans all of its steps!
11477 let first_step = self.edits.src_span_to_lsp_range(first_value.location);
11478 let last_step = self.edits.src_span_to_lsp_range(finally.location());
11479 position_within(selected_range.start, first_step)
11480 && position_within(selected_range.end, last_step)
11481 }
11482
11483 TypedExpr::Int { .. }
11484 | TypedExpr::Float { .. }
11485 | TypedExpr::String { .. }
11486 | TypedExpr::Block { .. }
11487 | TypedExpr::Var { .. }
11488 | TypedExpr::Fn { .. }
11489 | TypedExpr::List { .. }
11490 | TypedExpr::Call { .. }
11491 | TypedExpr::BinOp { .. }
11492 | TypedExpr::Case { .. }
11493 | TypedExpr::RecordAccess { .. }
11494 | TypedExpr::PositionalAccess { .. }
11495 | TypedExpr::ModuleSelect { .. }
11496 | TypedExpr::Tuple { .. }
11497 | TypedExpr::TupleIndex { .. }
11498 | TypedExpr::Todo { .. }
11499 | TypedExpr::Panic { .. }
11500 | TypedExpr::Echo { .. }
11501 | TypedExpr::BitArray { .. }
11502 | TypedExpr::RecordUpdate { .. }
11503 | TypedExpr::NegateBool { .. }
11504 | TypedExpr::NegateInt { .. }
11505 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range),
11506 }
11507 }
11508
11509 fn can_extract_statement(&self, statement: &TypedStatement) -> bool {
11510 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
11511 let selected_range = self.params.range;
11512
11513 // If the selected range doesn't touch the statement at all, then there
11514 // is no reason to extract it.
11515 if !overlaps(statement_range, selected_range) {
11516 return false;
11517 }
11518
11519 match statement {
11520 ast::Statement::Expression(expression) => self.can_extract_expression(expression),
11521
11522 // Determine whether the selected range falls completely within the
11523 // expression. For example:
11524 // ```gleam
11525 // pub fn main() {
11526 // let something = {
11527 // let a = 1
11528 // let b = 2
11529 // let c = a + b
11530 // //^ The user has selected from here
11531 // let d = a * b
11532 // c / d
11533 // // ^ Until here
11534 // }
11535 // }
11536 // ```
11537 //
11538 // Here, the selected range does overlap with the `let something`
11539 // statement; but we don't want to extract that whole statement! The
11540 // user only wanted to extract the statements inside the block. So if
11541 // the selected range falls completely within the expression, we ignore
11542 // it and traverse the tree further until we find exactly what the user
11543 // selected.
11544 //
11545 // If the selected range is completely within the expression, we don't
11546 // want to extract it.
11547 ast::Statement::Use(_) | ast::Statement::Assert(_) => {
11548 !completely_within(selected_range, statement_range)
11549 }
11550
11551 // We can only extract a whole let statement if the assignment
11552 // part itself is selected. If the only part being selected is the
11553 // expression then the right call is not extracting the whole
11554 // statement but just the expression.
11555 ast::Statement::Assignment(assignment) => {
11556 let value_range = self
11557 .edits
11558 .src_span_to_lsp_range(assignment.value.location());
11559
11560 !within(selected_range, value_range)
11561 && !completely_within(selected_range, statement_range)
11562 }
11563 }
11564 }
11565}
11566
11567impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
11568 fn visit_typed_function(&mut self, function: &'ast TypedFunction) {
11569 let range = self.edits.src_span_to_lsp_range(function.full_location());
11570
11571 if within(self.params.range, range) {
11572 self.function_end_position = Some(function.end_position);
11573 self.last_statement_location = function.body.last().map(|last| last.location());
11574
11575 ast::visit::visit_typed_function(self, function);
11576 }
11577 }
11578
11579 fn visit_typed_expr_block(
11580 &mut self,
11581 location: &'ast SrcSpan,
11582 statements: &'ast [TypedStatement],
11583 ) {
11584 let last_statement_location = self.last_statement_location;
11585 self.last_statement_location = statements.last().map(|last| last.location());
11586
11587 ast::visit::visit_typed_expr_block(self, location, statements);
11588
11589 self.last_statement_location = last_statement_location;
11590 }
11591
11592 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
11593 let last_statement_location = self.last_statement_location;
11594 // The body must be visited first, before the desugared function
11595 if let TypedExpr::Call { arguments, .. } = &*use_.call
11596 && let Some(CallArg {
11597 value: TypedExpr::Fn { body, .. },
11598 ..
11599 }) = arguments.last()
11600 {
11601 self.last_statement_location = Some(body.last().location());
11602 for statement in body {
11603 self.visit_typed_statement(statement);
11604 }
11605 }
11606 ast::visit::visit_typed_use(self, use_);
11607 self.last_statement_location = last_statement_location;
11608 }
11609
11610 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
11611 // If we have already determined what code we want to extract, we don't
11612 // want to extract this instead. This expression would be inside the
11613 // piece of code we already are going to extract, leading to us
11614 // extracting just a single literal in any selection, which is of course
11615 // not desired.
11616 if self.function.is_none() {
11617 // If this expression is fully selected, we mark it as being extracted.
11618 if self.can_extract_expression(expression) {
11619 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
11620 expression,
11621 )));
11622 }
11623 }
11624
11625 // If the expression is a function, then the last statement location
11626 // needs to be updated accordingly
11627 let last_statement_location = self.last_statement_location;
11628 if let TypedExpr::Fn { body, .. } = expression {
11629 self.last_statement_location = Some(body.last().location());
11630 }
11631
11632 ast::visit::visit_typed_expr(self, expression);
11633
11634 self.last_statement_location = last_statement_location;
11635 }
11636
11637 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
11638 let statement_location = statement.location();
11639
11640 if self.can_extract_statement(statement) {
11641 let is_in_tail_position =
11642 self.last_statement_location
11643 .is_some_and(|last_statement_location| {
11644 last_statement_location == statement_location
11645 });
11646
11647 // A use is always eating up the entire block, if we're extracting it,
11648 // it will be in tail position there and the extracted function should
11649 // return its returned value.
11650 let position = if statement.is_use() || is_in_tail_position {
11651 StatementPosition::Tail {
11652 type_: statement.type_(),
11653 }
11654 } else {
11655 StatementPosition::NotTail
11656 };
11657
11658 match &mut self.function {
11659 None => {
11660 self.function = match statement {
11661 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None,
11662 TypedStatement::Assert(_)
11663 | TypedStatement::Assignment(_)
11664 | TypedStatement::Expression(_) => {
11665 Some(ExtractedFunction::new(ExtractedValue::Statements {
11666 location: statement_location,
11667 position,
11668 }))
11669 }
11670 TypedStatement::Use(use_) => {
11671 Some(ExtractedFunction::new(ExtractedValue::Use {
11672 location: use_.call.location(),
11673 use_line_location: use_.location,
11674 type_: statement.type_(),
11675 }))
11676 }
11677 };
11678 }
11679
11680 // If we're extracting something that is within a use expression
11681 // we need to check whether to extract the whole use or just
11682 // statements inside of it
11683 Some(ExtractedFunction {
11684 value:
11685 ExtractedValue::Use {
11686 location,
11687 use_line_location,
11688 ..
11689 },
11690 parameters,
11691 returned_variables,
11692 }) if location.contains_span(statement_location) => {
11693 let use_line_range = self.edits.src_span_to_lsp_range(*use_line_location);
11694
11695 // If the current statement is not a part of the use line,
11696 // and the current selection does not overlap the use line,
11697 // then the outer use is not the correct target.
11698 if !use_line_location.contains_span(statement_location)
11699 && !overlaps(use_line_range, self.params.range)
11700 {
11701 self.function = Some(ExtractedFunction {
11702 value: ExtractedValue::Statements {
11703 location: statement_location,
11704 position,
11705 },
11706 parameters: parameters.to_vec(),
11707 returned_variables: returned_variables.to_vec(),
11708 });
11709 }
11710 }
11711 // Otherwise it means we're extracting multiple statements
11712 // _including_ some use expression, we fallback to extracting
11713 // multiple statements
11714 Some(ExtractedFunction {
11715 value: value @ ExtractedValue::Use { .. },
11716 ..
11717 }) => {
11718 *value = ExtractedValue::Statements {
11719 location: value.location().merge(&statement_location),
11720 position,
11721 };
11722 }
11723
11724 // If we have already chosen an expression to extract, that means
11725 // that this statement is within the already extracted expression,
11726 // so we don't want to extract this instead.
11727 Some(ExtractedFunction {
11728 value: ExtractedValue::Expression(_),
11729 ..
11730 }) => {}
11731 // If we are selecting multiple statements, this statement should
11732 // be included within that list, so we merge the spans to ensure
11733 // it is included.
11734 Some(ExtractedFunction {
11735 value:
11736 ExtractedValue::Statements {
11737 location,
11738 position: extracted_position,
11739 },
11740 ..
11741 }) => {
11742 *location = location.merge(&statement_location);
11743 *extracted_position = position;
11744 }
11745 Some(ExtractedFunction {
11746 value: value @ ExtractedValue::PipelineSteps { .. },
11747 ..
11748 }) => {
11749 // If we were extracting a pipeline, but end up selecting
11750 // some statement that is not part of it, then we go back to
11751 // selecting a batch of statements.
11752 *value = ExtractedValue::Statements {
11753 location: value.location(),
11754 position,
11755 }
11756 }
11757 }
11758 }
11759 ast::visit::visit_typed_statement(self, statement);
11760 }
11761
11762 fn visit_typed_expr_pipeline(
11763 &mut self,
11764 _location: &'ast SrcSpan,
11765 first_value: &'ast TypedPipelineAssignment,
11766 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
11767 finally: &'ast TypedExpr,
11768 _finally_kind: &'ast PipelineAssignmentKind,
11769 ) {
11770 self.previous_pipeline_assignment_type = None;
11771 self.visit_typed_pipeline_assignment(first_value);
11772
11773 self.previous_pipeline_assignment_type = Some(first_value.type_());
11774 for (assignment, _kind) in assignments {
11775 self.visit_typed_pipeline_assignment(assignment);
11776 self.previous_pipeline_assignment_type = Some(assignment.type_());
11777 }
11778
11779 // If we're selecting a pipeline and the selection ends on its final step
11780 // we want to include that as well into the extracted bit.
11781 let final_step_range = self.edits.src_span_to_lsp_range(finally.location());
11782 if let Some(extracted_function) = &mut self.function
11783 && position_within(self.params.range.end, final_step_range)
11784 {
11785 extracted_function.try_add_pipeline_step(finally.type_(), finally.location());
11786 };
11787
11788 self.visit_typed_expr(finally);
11789 self.previous_pipeline_assignment_type = None;
11790 }
11791
11792 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
11793 // In order to be extracted, a pipeline step must be overlapping with
11794 // the cursor selection!
11795 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
11796 if !overlaps(self.params.range, assignment_range) {
11797 return;
11798 }
11799
11800 match &mut self.function {
11801 None => {
11802 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps {
11803 location: assignment.location,
11804 before_first: self.previous_pipeline_assignment_type.clone(),
11805 return_type: assignment.type_(),
11806 }));
11807 }
11808 Some(extracted_function) => {
11809 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location)
11810 }
11811 }
11812 ast::visit::visit_typed_pipeline_assignment(self, assignment);
11813 }
11814
11815 fn visit_typed_expr_var(
11816 &mut self,
11817 location: &'ast SrcSpan,
11818 constructor: &'ast ValueConstructor,
11819 name: &'ast EcoString,
11820 ) {
11821 if let type_::ValueConstructorVariant::LocalVariable {
11822 location: definition_location,
11823 ..
11824 } = &constructor.variant
11825 {
11826 self.register_referenced_variable(
11827 name,
11828 &constructor.type_,
11829 *location,
11830 *definition_location,
11831 );
11832 }
11833 }
11834
11835 fn visit_typed_clause_guard_var(
11836 &mut self,
11837 location: &'ast SrcSpan,
11838 name: &'ast EcoString,
11839 type_: &'ast Arc<Type>,
11840 definition_location: &'ast SrcSpan,
11841 _origin: &'ast VariableOrigin,
11842 ) {
11843 self.register_referenced_variable(name, type_, *location, *definition_location);
11844 }
11845
11846 fn visit_typed_expr_case(
11847 &mut self,
11848 location: &'ast SrcSpan,
11849 type_: &'ast Arc<Type>,
11850 subjects: &'ast [TypedExpr],
11851 clauses: &'ast [ast::TypedClause],
11852 compiled_case: &'ast CompiledCase,
11853 ) {
11854 let was_extracting_already = self.function.is_some();
11855
11856 // We first visit as usual...
11857 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
11858
11859 // But then we need to check we're in a situation where it actually makes
11860 // sense to extract: if the cursor is entirely within the case (so it's
11861 // not part of a bigger extracted chunk) and it spans over one of the
11862 // branches' pattern or guard, then we don't want to allow extracting
11863 // anything. Popping up the action would be confusing.
11864 // For example:
11865 //
11866 // ```gleam
11867 // case wibble {
11868 // Ok(_) -> todo
11869 // //^^^^^^^^^^^^^ If I'm selecting this whole branch it makes no sense
11870 // to propose extracting it as a function
11871 // _ if wibble -> todo
11872 // // ^^^^^^^^^^^ If I'm selecting this it makes no sense to
11873 // // propose extracting it as a function
11874 // }
11875 // ```
11876
11877 // We were already extracting something, we don't want to render that
11878 // choice null, this is just a part of a bigger piece being extracted.
11879 if was_extracting_already {
11880 return;
11881 }
11882
11883 for clause in clauses {
11884 let left_hand_side_location = SrcSpan {
11885 start: clause.pattern_location().start,
11886 end: clause.then.location().start - 1,
11887 };
11888 let left_hand_side_range = self.edits.src_span_to_lsp_range(left_hand_side_location);
11889 // If there's any overlapping with one of the patterns, then we
11890 // don't want to extract anything.
11891 if overlaps(self.params.range, left_hand_side_range) {
11892 self.function = None;
11893 break;
11894 }
11895 }
11896 }
11897
11898 fn visit_typed_bit_array_size_variable(
11899 &mut self,
11900 location: &'ast SrcSpan,
11901 name: &'ast EcoString,
11902 constructor: &'ast Option<Box<ValueConstructor>>,
11903 type_: &'ast Arc<Type>,
11904 ) {
11905 let variant = match constructor {
11906 Some(constructor) => &constructor.variant,
11907 None => return,
11908 };
11909 if let type_::ValueConstructorVariant::LocalVariable {
11910 location: definition_location,
11911 ..
11912 } = variant
11913 {
11914 self.register_referenced_variable(name, type_, *location, *definition_location);
11915 }
11916 }
11917}
11918
11919/// Code action to merge two identical branches together.
11920///
11921pub struct MergeCaseBranches<'a> {
11922 module: &'a Module,
11923 params: &'a CodeActionParams,
11924 edits: TextEdits<'a>,
11925 /// These are the positions of the patterns of all the consecutive branches
11926 /// we've determined can be merged, for example if we're mergin the first
11927 /// two branches here:
11928 ///
11929 /// ```gleam
11930 /// case wibble {
11931 /// 1 -> todo
11932 /// // ^ this location here
11933 /// 20 -> todo
11934 /// // ^^ and this location here
11935 /// _ -> todo
11936 /// }
11937 /// ```
11938 ///
11939 /// We need those to delete all the space between each consecutive pattern,
11940 /// replacing it with the `|` for alternatives
11941 ///
11942 patterns_to_merge: Option<MergeableBranches>,
11943}
11944
11945struct MergeableBranches {
11946 /// The span of the body to keep when merging multiple branches. For
11947 /// example:
11948 ///
11949 /// ```gleam
11950 /// case n {
11951 /// // Imagine we're merging the first three branches together...
11952 /// 1 -> todo
11953 /// 2 -> n * 2
11954 /// // ^^^^^ This would be the location of the one body to keep
11955 /// 3 -> todo
11956 /// _ -> todo
11957 /// }
11958 /// ```
11959 ///
11960 body_to_keep: SrcSpan,
11961
11962 /// The location body of the last of the branches that are going to be
11963 /// merged; that is where we're going to place the code of the body to keep
11964 /// once the action is done. For example:
11965 ///
11966 /// ```gleam
11967 /// case n {
11968 /// // Imagine we're merging the first three branches together...
11969 /// 1 -> todo
11970 /// 2 -> n * 2
11971 /// 3 -> todo
11972 /// // ^^^^ This would be the location of the final body
11973 /// _ -> todo
11974 /// }
11975 /// ```
11976 ///
11977 final_body: SrcSpan,
11978
11979 /// The span of the patterns whose branches are going to be merged. For
11980 /// example:
11981 ///
11982 /// ```gleam
11983 /// case n {
11984 /// // Imagine we're merging the first three branches together...
11985 /// 1 -> todo
11986 /// // ^
11987 /// 2 -> n * 2
11988 /// // ^
11989 /// 3 -> todo
11990 /// // ^ These would be the locations of the patterns
11991 /// _ -> todo
11992 /// }
11993 /// ```
11994 ///
11995 patterns_to_merge: Vec<SrcSpan>,
11996}
11997
11998impl<'a> MergeCaseBranches<'a> {
11999 pub fn new(
12000 module: &'a Module,
12001 line_numbers: &'a LineNumbers,
12002 params: &'a CodeActionParams,
12003 ) -> Self {
12004 Self {
12005 module,
12006 params,
12007 edits: TextEdits::new(line_numbers),
12008 patterns_to_merge: None,
12009 }
12010 }
12011
12012 pub fn code_actions(mut self) -> Vec<CodeAction> {
12013 self.visit_typed_module(&self.module.ast);
12014
12015 let Some(mergeable_branches) = self.patterns_to_merge else {
12016 return vec![];
12017 };
12018
12019 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() {
12020 self.edits
12021 .replace(SrcSpan::new(one.end, next.start), " | ".into());
12022 }
12023
12024 self.edits.replace(
12025 mergeable_branches.final_body,
12026 code_at(self.module, mergeable_branches.body_to_keep).into(),
12027 );
12028
12029 let mut action = Vec::with_capacity(1);
12030 CodeActionBuilder::new("Merge case branches")
12031 .kind(CodeActionKind::RefactorRewrite)
12032 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12033 .preferred(false)
12034 .push_to(&mut action);
12035 action
12036 }
12037
12038 fn select_mergeable_branches(
12039 &self,
12040 clauses: &'a [ast::TypedClause],
12041 ) -> Option<MergeableBranches> {
12042 let mut clauses = clauses
12043 .iter()
12044 // We want to skip all the branches at the beginning of the case
12045 // expression that the cursor is not hovering over. For example:
12046 //
12047 // ```gleam
12048 // case wibble {
12049 // a -> 1 <- we want to skip this one here that is not selected
12050 // b -> 2
12051 // ^^^^ this is the selection
12052 // _ -> 3
12053 // ^^
12054 // }
12055 // ```
12056 .skip_while(|clause| {
12057 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
12058 !overlaps(self.params.range, clause_range)
12059 })
12060 // Then we only want to take the clauses that we're hovering over
12061 // with our selection (even partially!)
12062 // In the provious example they would be `b -> 2` and `_ -> 3`.
12063 .take_while(|clause| {
12064 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
12065 overlaps(self.params.range, clause_range)
12066 });
12067
12068 let first_hovered_clause = clauses.next()?;
12069
12070 // This is the clause we're comparing all the others with. We need to
12071 // make sure that all the clauses we're going to join can be merged with
12072 // this one.
12073 let mut reference_clause = first_hovered_clause;
12074 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()];
12075 let mut final_body = first_hovered_clause.then.location();
12076
12077 for clause in clauses {
12078 // As soon as we find a clause that can't be merged with the current
12079 // reference we know we're done looking for consecutive clauses to
12080 // merge.
12081 if !clauses_can_be_merged(reference_clause, clause) {
12082 break;
12083 }
12084
12085 clause_patterns_to_merge.push(clause.pattern_location());
12086 final_body = clause.then.location();
12087
12088 // If the current reference is a `todo` expression, we want to use
12089 // the newly found mergeable clause as the next reference. The
12090 // reference clause is the one whose body will be kept around, so if
12091 // we can we avoid keeping `todo`s
12092 if reference_clause.then.is_todo_with_no_message() {
12093 reference_clause = clause;
12094 }
12095 }
12096
12097 // We only offer the code action if we have found two or more clauses
12098 // to merge.
12099 if clause_patterns_to_merge.len() >= 2 {
12100 Some(MergeableBranches {
12101 final_body,
12102 body_to_keep: reference_clause.then.location(),
12103 patterns_to_merge: clause_patterns_to_merge,
12104 })
12105 } else {
12106 None
12107 }
12108 }
12109}
12110
12111fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool {
12112 // Two clauses cannot be merged if any of those has an if guard
12113 if one.guard.is_some() || other.guard.is_some() {
12114 return false;
12115 }
12116
12117 // Two clauses can only be merged if they define the same variables,
12118 // otherwise joining them would result in invalid code.
12119 let variables_one = one
12120 .bound_variables()
12121 .map(|variable| (variable.name(), variable.type_))
12122 .collect::<HashMap<_, _>>();
12123
12124 let variables_other = other
12125 .bound_variables()
12126 .map(|variable| (variable.name(), variable.type_))
12127 .collect::<HashMap<_, _>>();
12128
12129 for (name, type_) in variables_one.iter() {
12130 if let Some(type_other) = variables_other.get(name)
12131 && type_other.same_as(type_)
12132 {
12133 continue;
12134 }
12135
12136 // There's a variable that is not defined in the second branch but
12137 // is defined in the first one, or it's defined in the second branch
12138 // but it has an incompatible type.
12139 return false;
12140 }
12141
12142 for (name, _) in variables_other.iter() {
12143 if !variables_one.contains_key(name) {
12144 // There's some variables defined in the second branch that are not
12145 // defined in the first one, so they can't be merged!
12146 return false;
12147 }
12148 }
12149
12150 // Anything can be merged with a simple todo, or the two bodies must be
12151 // syntactically equal.
12152 one.then.is_todo_with_no_message()
12153 || other.then.is_todo_with_no_message()
12154 || one.then.syntactically_eq(&other.then)
12155}
12156
12157impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> {
12158 fn visit_typed_expr_case(
12159 &mut self,
12160 location: &'ast SrcSpan,
12161 type_: &'ast Arc<Type>,
12162 subjects: &'ast [TypedExpr],
12163 clauses: &'ast [ast::TypedClause],
12164 compiled_case: &'ast CompiledCase,
12165 ) {
12166 // We only trigger the code action if we are within a case expression,
12167 // otherwise there's no point in exploring the expression any further.
12168 let case_range = self.edits.src_span_to_lsp_range(*location);
12169 if !within(self.params.range, case_range) {
12170 return;
12171 }
12172
12173 if let result @ Some(_) = self.select_mergeable_branches(clauses) {
12174 self.patterns_to_merge = result
12175 }
12176
12177 // We still need to visit the case expression in case we want to apply
12178 // the code action to some case expression that is nested in one of its
12179 // branches!
12180 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
12181 }
12182}
12183
12184/// Code action to add a missing type parameter to custom types.
12185/// If a custom type is missing a type parameter, as it is the case
12186/// in the following example, this action will offer to add the
12187/// type parameter to the type definition.
12188///
12189/// Before:
12190/// ```gleam
12191/// type Wibble {
12192/// Wibble(field: t)
12193/// }
12194/// ```
12195///
12196/// After:
12197/// ```gleam
12198/// type Wibble(t) {
12199/// Wibble(field: t)
12200/// }
12201/// ```
12202///
12203pub struct AddMissingTypeParameter<'a> {
12204 module: &'a Module,
12205 params: &'a CodeActionParams,
12206 edits: TextEdits<'a>,
12207 /// The source location where the parameters should be defined.
12208 /// This might be a zero-length span if there are no parameters yet,
12209 /// or it might cover the already existing type parameter definitions.
12210 parameters_location: Option<SrcSpan>,
12211 /// If the type definition already had existing parameters before.
12212 has_existing_parameters: bool,
12213 /// The set of all type parameter names in the different variants of the type
12214 /// that are not already part of the type parameter definition on the type.
12215 missing_parameters: HashSet<EcoString>,
12216}
12217
12218impl<'a> AddMissingTypeParameter<'a> {
12219 pub fn new(
12220 module: &'a Module,
12221 line_numbers: &'a LineNumbers,
12222 params: &'a CodeActionParams,
12223 ) -> Self {
12224 Self {
12225 module,
12226 params,
12227 edits: TextEdits::new(line_numbers),
12228 parameters_location: None,
12229 has_existing_parameters: false,
12230 missing_parameters: HashSet::new(),
12231 }
12232 }
12233
12234 pub fn code_actions(mut self) -> Vec<CodeAction> {
12235 self.visit_typed_module(&self.module.ast);
12236
12237 let Some(type_parameters_location) = self.parameters_location else {
12238 return vec![];
12239 };
12240
12241 if self.missing_parameters.is_empty() {
12242 return vec![];
12243 }
12244
12245 let mut new_parameters = self.missing_parameters.iter().sorted().join(", ");
12246 if self.has_existing_parameters {
12247 let has_trailing_comma = self
12248 .module
12249 .extra
12250 .trailing_commas
12251 .iter()
12252 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma));
12253
12254 if !has_trailing_comma {
12255 new_parameters.insert_str(0, ", ");
12256 }
12257
12258 self.edits
12259 .insert(type_parameters_location.end - 1, new_parameters);
12260 } else {
12261 self.edits
12262 .insert(type_parameters_location.end, format!("({new_parameters})"));
12263 }
12264
12265 let mut action = Vec::with_capacity(1);
12266 CodeActionBuilder::new("Add missing type parameter")
12267 .kind(CodeActionKind::QuickFix)
12268 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12269 .preferred(true)
12270 .push_to(&mut action);
12271 action
12272 }
12273}
12274
12275impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> {
12276 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
12277 let custom_type_range = self
12278 .edits
12279 .src_span_to_lsp_range(custom_type.full_location());
12280
12281 // Only continue, if the action was selected anywhere within the custom type definition.
12282 if !within(self.params.range, custom_type_range) {
12283 return;
12284 }
12285
12286 self.parameters_location = Some(SrcSpan::new(
12287 custom_type.name_location.end,
12288 custom_type.location.end,
12289 ));
12290
12291 self.has_existing_parameters = !custom_type.typed_parameters.is_empty();
12292
12293 let existing_names: HashSet<_> = custom_type
12294 .parameters
12295 .iter()
12296 .map(|(_, name)| name)
12297 .collect();
12298
12299 // Collect the remaining type parameters from the variant constructors.
12300 for record in &custom_type.constructors {
12301 for argument in &record.arguments {
12302 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast
12303 && !existing_names.contains(name)
12304 {
12305 let _ = self.missing_parameters.insert(name.clone());
12306 }
12307 }
12308 }
12309 }
12310}
12311
12312/// Code action to replace a `_` with its actual type in an annotation.
12313///
12314/// Before:
12315/// ```gleam
12316/// fn wibble() -> Ok(_) { Ok(1) }
12317/// // ^ Trigger it here
12318/// ```
12319///
12320/// After:
12321/// ```gleam
12322/// fn wibble() -> Ok(Int) { Ok(1) }
12323/// ```
12324///
12325pub struct ReplaceUnderscoreWithType<'a> {
12326 module: &'a Module,
12327 params: &'a CodeActionParams,
12328 edits: TextEdits<'a>,
12329 hovered_hole: Option<HoveredHole>,
12330}
12331
12332struct HoveredHole {
12333 type_: Arc<Type>,
12334 location: SrcSpan,
12335}
12336
12337impl<'a> ReplaceUnderscoreWithType<'a> {
12338 pub fn new(
12339 module: &'a Module,
12340 line_numbers: &'a LineNumbers,
12341 params: &'a CodeActionParams,
12342 ) -> Self {
12343 Self {
12344 module,
12345 params,
12346 edits: TextEdits::new(line_numbers),
12347 hovered_hole: None,
12348 }
12349 }
12350
12351 pub fn code_actions(mut self) -> Vec<CodeAction> {
12352 self.visit_typed_module(&self.module.ast);
12353
12354 let mut action = Vec::with_capacity(1);
12355
12356 let Some(HoveredHole { type_, location }) = self.hovered_hole else {
12357 return vec![];
12358 };
12359 let mut printer = Printer::new(&self.module.ast.names);
12360 self.edits
12361 .replace(location, format!("{}", printer.print_type(&type_)));
12362
12363 CodeActionBuilder::new("Replace `_` with type")
12364 .kind(CodeActionKind::QuickFix)
12365 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12366 .preferred(true)
12367 .push_to(&mut action);
12368 action
12369 }
12370}
12371
12372impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> {
12373 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) {
12374 // We never traverse a type annotation we're not hovering
12375 let node_location = self.edits.src_span_to_lsp_range(node.location());
12376 if !within(self.params.range, node_location) {
12377 return;
12378 }
12379 ast::visit::visit_type_ast(self, node, inferred_type);
12380 }
12381
12382 fn visit_type_ast_hole(
12383 &mut self,
12384 location: &'ast SrcSpan,
12385 _name: &'ast EcoString,
12386 type_: Option<Arc<Type>>,
12387 ) {
12388 if let Some(type_) = type_ {
12389 self.hovered_hole = Some(HoveredHole {
12390 type_,
12391 location: *location,
12392 })
12393 }
12394 }
12395}
12396
12397/// Code action to turn a function used as a reference into a one-statement anonymous function.
12398///
12399/// For example, if the code action was used on `op` here:
12400///
12401/// ```gleam
12402/// list.map([1, 2, 3], op)
12403/// ```
12404///
12405/// it would become:
12406///
12407/// ```gleam
12408/// list.map([1, 2, 3], fn(int) {
12409/// op(int)
12410/// })
12411/// ```
12412pub struct WrapInAnonymousFunction<'a> {
12413 module: &'a Module,
12414 params: &'a CodeActionParams,
12415 functions: Vec<FunctionToWrap>,
12416 edits: TextEdits<'a>,
12417}
12418
12419/// Helper struct, a target for [WrapInAnonymousFunction].
12420struct FunctionToWrap {
12421 location: SrcSpan,
12422 arguments: Vec<Arc<Type>>,
12423 variables_names: VariablesNames,
12424}
12425
12426impl<'a> WrapInAnonymousFunction<'a> {
12427 pub fn new(
12428 module: &'a Module,
12429 line_numbers: &'a LineNumbers,
12430 params: &'a CodeActionParams,
12431 ) -> Self {
12432 Self {
12433 module,
12434 params,
12435 edits: TextEdits::new(line_numbers),
12436 functions: vec![],
12437 }
12438 }
12439
12440 pub fn code_actions(mut self) -> Vec<CodeAction> {
12441 self.visit_typed_module(&self.module.ast);
12442
12443 let mut actions = Vec::with_capacity(self.functions.len());
12444 for target in self.functions {
12445 let mut name_generator = NameGenerator::new();
12446 name_generator.reserve_variable_names(target.variables_names);
12447 let arguments = target
12448 .arguments
12449 .iter()
12450 .map(|type_| name_generator.generate_name_from_type(type_))
12451 .join(", ");
12452
12453 self.edits
12454 .insert(target.location.start, format!("fn({arguments}) {{ "));
12455 self.edits
12456 .insert(target.location.end, format!("({arguments}) }}"));
12457
12458 CodeActionBuilder::new("Wrap in anonymous function")
12459 .kind(CodeActionKind::RefactorRewrite)
12460 .changes(
12461 self.params.text_document.uri.clone(),
12462 self.edits.edits.drain(..).collect(),
12463 )
12464 .push_to(&mut actions);
12465 }
12466 actions
12467 }
12468}
12469
12470impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> {
12471 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
12472 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
12473 if !within(self.params.range, expression_range) {
12474 return;
12475 }
12476
12477 // Exclude if the expression is already a function literal
12478 let is_excluded = matches!(expression, TypedExpr::Fn { .. });
12479
12480 if let Type::Fn { arguments, .. } = &*expression.type_()
12481 && !is_excluded
12482 {
12483 self.functions.push(FunctionToWrap {
12484 location: expression.location(),
12485 arguments: arguments.clone(),
12486 variables_names: VariablesNames::from_expression(expression),
12487 });
12488 }
12489
12490 ast::visit::visit_typed_expr(self, expression);
12491 }
12492
12493 /// We don't want to apply to functions that are being explicitly called
12494 /// already, so we need to intercept visits to function calls and bounce
12495 /// them out again so they don't end up in our impl for visit_typed_expr.
12496 /// Otherwise this is the same as [].
12497 fn visit_typed_expr_call(
12498 &mut self,
12499 _location: &'ast SrcSpan,
12500 _type: &'ast Arc<Type>,
12501 fun: &'ast TypedExpr,
12502 arguments: &'ast [TypedCallArg],
12503 _argument_parentheses: &'ast Option<u32>,
12504 ) {
12505 // We only need to do this interception for explicit calls, so if any
12506 // of our arguments are explicit we re-enter the visitor as usual.
12507 if arguments.iter().any(|argument| argument.is_implicit()) {
12508 self.visit_typed_expr(fun);
12509 } else {
12510 // We still want to visit other nodes nested in the function being
12511 // called so we bounce the call back out.
12512 ast::visit::visit_typed_expr(self, fun);
12513 }
12514
12515 for argument in arguments {
12516 self.visit_typed_call_arg(argument);
12517 }
12518 }
12519
12520 /// We don't want to apply this to record constructors used in a record
12521 /// update.
12522 fn visit_typed_expr_record_update(
12523 &mut self,
12524 location: &'ast SrcSpan,
12525 spread_start: &'ast u32,
12526 type_: &'ast Arc<Type>,
12527 updated_record: &'ast TypedExpr,
12528 updated_record_assigned_name: &'ast Option<EcoString>,
12529 constructor: &'ast TypedExpr,
12530 arguments: &'ast [TypedCallArg],
12531 ) {
12532 // If we're hovering the record constructor of an update and that is a
12533 // simple record constructor, we don't want to offer the wrap in
12534 // anonymous function code action:
12535 //
12536 // ```gleam
12537 // Wibble(..wibble, a: 1)
12538 // // ^^^ Wrap in anonymous function makes no sense on this!
12539 // ```
12540 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location());
12541 if within(self.params.range, constructor_range)
12542 && constructor.is_record_constructor_function()
12543 {
12544 return;
12545 }
12546
12547 ast::visit::visit_typed_expr_record_update(
12548 self,
12549 location,
12550 spread_start,
12551 type_,
12552 updated_record,
12553 updated_record_assigned_name,
12554 constructor,
12555 arguments,
12556 );
12557 }
12558}
12559
12560/// Code action to unwrap trivial one-statement anonymous functions into just a
12561/// reference to the function called
12562///
12563/// For example, if the code action was used on the anonymous function here:
12564///
12565/// ```gleam
12566/// list.map([1, 2, 3], fn(int) {
12567/// op(int)
12568/// })
12569/// ```
12570///
12571/// it would become:
12572///
12573/// ```gleam
12574/// list.map([1, 2, 3], op)
12575/// ```
12576pub struct UnwrapAnonymousFunction<'a> {
12577 module: &'a Module,
12578 line_numbers: &'a LineNumbers,
12579 params: &'a CodeActionParams,
12580 functions: Vec<FunctionToUnwrap>,
12581}
12582
12583/// Helper struct, a target for [UnwrapAnonymousFunction]
12584struct FunctionToUnwrap {
12585 /// Location of the anonymous function to apply the action to.
12586 outer_function: SrcSpan,
12587 /// Location of the opening brace of the anonymous function.
12588 outer_function_body_start: u32,
12589 /// Location of the function being called inside the anonymous function.
12590 /// This will be all that's left after the action, plus any comments.
12591 inner_function: SrcSpan,
12592 // Location of the opening parenthesis of the inner function's argument list.
12593 inner_function_arguments_start: u32,
12594}
12595
12596impl<'a> UnwrapAnonymousFunction<'a> {
12597 pub fn new(
12598 module: &'a Module,
12599 line_numbers: &'a LineNumbers,
12600 params: &'a CodeActionParams,
12601 ) -> Self {
12602 Self {
12603 module,
12604 line_numbers,
12605 params,
12606 functions: vec![],
12607 }
12608 }
12609
12610 pub fn code_actions(mut self) -> Vec<CodeAction> {
12611 self.visit_typed_module(&self.module.ast);
12612
12613 let mut actions = Vec::with_capacity(self.functions.len());
12614 for function in &self.functions {
12615 let mut edits = TextEdits::new(self.line_numbers);
12616
12617 // We need to delete the anonymous function's head and the opening
12618 // brace but preserve comments between it and the inner function call.
12619 // We set our endpoint at the start of the function body, and move
12620 // it on through any whitespace.
12621 let head_deletion_end =
12622 next_nonwhitespace(&self.module.code, function.outer_function_body_start + 1);
12623 edits.delete(SrcSpan {
12624 start: function.outer_function.start,
12625 end: head_deletion_end,
12626 });
12627
12628 // Delete the inner function call's arguments.
12629 edits.delete(SrcSpan {
12630 start: function.inner_function_arguments_start,
12631 end: function.inner_function.end,
12632 });
12633
12634 // To delete the tail we remove the function end (the '}') and any
12635 // whitespace before it.
12636 let tail_deletion_start =
12637 previous_nonwhitespace(&self.module.code, function.outer_function.end - 1);
12638 edits.delete(SrcSpan {
12639 start: tail_deletion_start,
12640 end: function.outer_function.end,
12641 });
12642
12643 CodeActionBuilder::new("Remove anonymous function wrapper")
12644 .kind(CodeActionKind::RefactorRewrite)
12645 .changes(self.params.text_document.uri.clone(), edits.edits)
12646 .push_to(&mut actions);
12647 }
12648 actions
12649 }
12650
12651 /// If an anonymous function can be unwrapped, save it to our list
12652 ///
12653 /// We need to ensure our subjects:
12654 /// - are anonymous function literals (not captures)
12655 /// - only contain a single statement
12656 /// - that statement is a function call
12657 /// - that call's arguments exactly match the arguments of the enclosing
12658 /// function
12659 fn register_function(
12660 &mut self,
12661 location: &'a SrcSpan,
12662 kind: &'a FunctionLiteralKind,
12663 arguments: &'a [TypedArg],
12664 body: &'a Vec1<TypedStatement>,
12665 ) {
12666 let function_range = src_span_to_lsp_range(*location, self.line_numbers);
12667 if !within(self.params.range, function_range) {
12668 return;
12669 }
12670
12671 let outer_body = match kind {
12672 FunctionLiteralKind::Anonymous { head, .. } => SrcSpan::new(
12673 next_nonwhitespace(&self.module.code, head.end),
12674 location.end,
12675 ),
12676 _ => return,
12677 };
12678
12679 // We can only apply to anonymous functions containing a single function call
12680 let [
12681 TypedStatement::Expression(TypedExpr::Call {
12682 location: call_location,
12683 arguments: call_arguments,
12684 open_parenthesis: Some(arguments_start),
12685 ..
12686 }),
12687 ] = body.as_slice()
12688 else {
12689 return;
12690 };
12691
12692 // We need the existing argument list for the fn to be a 1:1 match for
12693 // the args we pass to the called function, so we need to collect the
12694 // names used in both lists and check they're equal.
12695
12696 let outer_argument_names = arguments.iter().map(|a| match &a.names {
12697 ArgNames::Named { name, .. } => Some(name),
12698 // We can bail out early if any arguments are discarded, since
12699 // they couldn't match those actually used.
12700 ArgNames::Discard { .. } => None,
12701 // Anonymous functions can't have labelled arguments.
12702 ArgNames::NamedLabelled { .. } => unreachable!(),
12703 ArgNames::LabelledDiscard { .. } => unreachable!(),
12704 });
12705
12706 let inner_argument_names = call_arguments.iter().map(|a| match &a.value {
12707 TypedExpr::Var { name, .. } => Some(name),
12708 // We can bail out early if any of these aren't variables, since
12709 // they couldn't match the inputs.
12710 _ => None,
12711 });
12712
12713 if !inner_argument_names.eq(outer_argument_names) {
12714 return;
12715 }
12716
12717 self.functions.push(FunctionToUnwrap {
12718 outer_function: *location,
12719 outer_function_body_start: outer_body.start,
12720 inner_function: *call_location,
12721 inner_function_arguments_start: *arguments_start,
12722 })
12723 }
12724}
12725
12726impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
12727 fn visit_typed_expr_fn(
12728 &mut self,
12729 location: &'ast SrcSpan,
12730 type_: &'ast Arc<Type>,
12731 kind: &'ast FunctionLiteralKind,
12732 arguments: &'ast [TypedArg],
12733 body: &'ast Vec1<TypedStatement>,
12734 return_annotation: &'ast Option<ast::TypeAst>,
12735 ) {
12736 let function_range = src_span_to_lsp_range(*location, self.line_numbers);
12737 if !within(self.params.range, function_range) {
12738 return;
12739 }
12740
12741 self.register_function(location, kind, arguments, body);
12742
12743 ast::visit::visit_typed_expr_fn(
12744 self,
12745 location,
12746 type_,
12747 kind,
12748 arguments,
12749 body,
12750 return_annotation,
12751 )
12752 }
12753}
12754
12755/// Code action to create unknown modules when an import is added for a
12756/// module that doesn't exist.
12757///
12758/// For example, if `import wobble/woo` is added to `src/wiggle.gleam`,
12759/// then a code action to create `src/wobble/woo.gleam` will be presented
12760/// when triggered over `import wobble/woo`.
12761pub struct CreateUnknownModule<'a, IO> {
12762 module: &'a Module,
12763 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
12764 lines: &'a LineNumbers,
12765 params: &'a CodeActionParams,
12766 paths: &'a ProjectPaths,
12767 error: &'a Option<Error>,
12768}
12769
12770impl<'a, IO> CreateUnknownModule<'a, IO> {
12771 pub fn new(
12772 module: &'a Module,
12773 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
12774 lines: &'a LineNumbers,
12775 params: &'a CodeActionParams,
12776 paths: &'a ProjectPaths,
12777 error: &'a Option<Error>,
12778 ) -> Self {
12779 Self {
12780 module,
12781 compiler,
12782 lines,
12783 params,
12784 paths,
12785 error,
12786 }
12787 }
12788
12789 pub fn code_actions(self) -> Vec<CodeAction> {
12790 // This code action can be derived from `UnknownModule` type errors.
12791 // If those errors don't exist for the current module, then we will not
12792 // suggest this action.
12793 let Some(Error::Type { failed_modules, .. }) = self.error else {
12794 return vec![];
12795 };
12796 let Some(failed_module) = failed_modules.get(&self.module.name) else {
12797 return vec![];
12798 };
12799
12800 let mut unknown_module_name = None;
12801 // We then need to find the `UnknownModule` error that is under the
12802 // cursor (if any, otherwise there's no action to suggest)!
12803 for error in &failed_module.errors {
12804 let TypeError::UnknownModule { location, name, .. } = error else {
12805 continue;
12806 };
12807 let error_range = src_span_to_lsp_range(*location, self.lines);
12808 if !within(self.params.range, error_range) {
12809 continue;
12810 }
12811 // We've found the unknown module error!!
12812 unknown_module_name = Some(name);
12813 }
12814
12815 let Some(unknown_module_name) = unknown_module_name else {
12816 return vec![];
12817 };
12818
12819 // Now we need to check the module actually doesn't exist among the
12820 // importable ones! Imagine we've written `timestamp.to_string` and
12821 // `timestamp` is unknown: if there's any importable module in the form
12822 // `.../timestamp` then the most likely scenario is that the programmer
12823 // wanted to import that and not create a new `src/timestamp.gleam`
12824 // file!
12825 // So we check if any of the importable modules ends with the unknown
12826 // name and if that's the case we don't suggest this action.
12827 let importable_modules = self.compiler.project_compiler.get_importable_modules();
12828 let unknown_module_can_be_imported = importable_modules
12829 .keys()
12830 .find(|module_name| module_name.split('/').next_back() == Some(unknown_module_name))
12831 .is_some();
12832 if unknown_module_can_be_imported {
12833 return vec![];
12834 }
12835
12836 // We've made sure the module is not among the importable ones, so now
12837 // we figure out if the generated module needs to go under `src`,
12838 // `test`, or `dev` and we're good to actually generate it!
12839 let origin_directory = match self.module.origin {
12840 Origin::Src => self.paths.src_directory(),
12841 Origin::Test => self.paths.test_directory(),
12842 Origin::Dev => self.paths.dev_directory(),
12843 };
12844
12845 let uri = url_from_path(&format!("{origin_directory}/{}.gleam", unknown_module_name))
12846 .expect("origin directory is absolute");
12847
12848 let mut actions = vec![];
12849 CodeActionBuilder::new(&format!(
12850 "Create {}/{}.gleam",
12851 self.module.origin.folder_name(),
12852 unknown_module_name
12853 ))
12854 .kind(CodeActionKind::QuickFix)
12855 .document_changes(vec![DocumentChange::CreateFile(CreateFile {
12856 uri,
12857 options: Some(CreateFileOptions {
12858 overwrite: Some(false),
12859 ignore_if_exists: Some(true),
12860 }),
12861 annotation_id: None,
12862 })])
12863 .push_to(&mut actions);
12864
12865 actions
12866 }
12867}
12868
12869/// Code action to discard unused variable.
12870pub struct DiscardUnusedVariable<'a> {
12871 module: &'a Module,
12872 params: &'a CodeActionParams,
12873 edits: TextEdits<'a>,
12874}
12875
12876#[derive(Debug)]
12877struct UnusedVariable<'a> {
12878 location: &'a SrcSpan,
12879 origin: &'a VariableOrigin,
12880}
12881
12882impl<'a> DiscardUnusedVariable<'a> {
12883 pub fn new(
12884 module: &'a Module,
12885 line_numbers: &'a LineNumbers,
12886 params: &'a CodeActionParams,
12887 ) -> Self {
12888 Self {
12889 module,
12890 params,
12891 edits: TextEdits::new(line_numbers),
12892 }
12893 }
12894
12895 pub fn code_actions(mut self) -> Vec<CodeAction> {
12896 let unused_variable = self
12897 .module
12898 .ast
12899 .type_info
12900 .warnings
12901 .iter()
12902 .find_map(|warning| {
12903 if let type_::Warning::UnusedVariable { location, origin } = warning
12904 && within(
12905 self.params.range,
12906 self.edits.src_span_to_lsp_range(*location),
12907 )
12908 {
12909 Some(UnusedVariable { location, origin })
12910 } else {
12911 None
12912 }
12913 });
12914
12915 let Some(unused_variable) = unused_variable else {
12916 return vec![];
12917 };
12918
12919 match unused_variable.origin.syntax {
12920 type_::error::VariableSyntax::Variable(_) => {
12921 self.edits
12922 .insert(unused_variable.location.start, ("_").to_string());
12923 }
12924 type_::error::VariableSyntax::LabelShorthand(_) => {
12925 self.edits
12926 .insert(unused_variable.location.end, (" _").to_string());
12927 }
12928 type_::error::VariableSyntax::AssignmentPattern(location) => {
12929 self.edits.delete(SrcSpan {
12930 start: location.start,
12931 end: location.end,
12932 });
12933 }
12934 type_::error::VariableSyntax::Generated => (),
12935 }
12936
12937 let mut action = Vec::with_capacity(1);
12938 CodeActionBuilder::new("Discard unused variable")
12939 .kind(CodeActionKind::QuickFix)
12940 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12941 .preferred(true)
12942 .push_to(&mut action);
12943 action
12944 }
12945}