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