Fork of daniellemaywood.uk/gleam — Wasm codegen work
457 kB
13101 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4use std::{collections::HashSet, iter, sync::Arc};
5
6use ecow::{EcoString, eco_format};
7use gleam_core::{
8 Error, STDLIB_PACKAGE_NAME,
9 analyse::Inferred,
10 ast::{
11 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable,
12 BoundVariableName, CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import,
13 InvalidExpression, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments,
14 PipelineAssignmentKind, Publicity, RecordConstructor, SrcSpan, TodoKind,
15 TypeAstConstructorName, TypedArg, TypedAssignment, TypedClauseGuard, TypedDefinitions,
16 TypedExpr, TypedFunction, TypedModuleConstant, TypedPattern, TypedPipelineAssignment,
17 TypedRecordConstructor, TypedStatement, TypedTailPattern, TypedUse, visit::Visit as _,
18 },
19 build::{Located, Module, Origin},
20 config::PackageConfig,
21 exhaustiveness::CompiledCase,
22 line_numbers::LineNumbers,
23 parse::{extra::ModuleExtra, lexer::string_to_keyword},
24 paths::ProjectPaths,
25 strings::to_snake_case,
26 type_::{
27 self, Error as TypeError, FieldMap, ModuleValueConstructor, Opaque, Type, TypeVar,
28 TypedCallArg, ValueConstructor,
29 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin},
30 printer::Printer,
31 },
32};
33use im::HashMap;
34use itertools::Itertools;
35use lsp_types::{
36 CodeAction, CodeActionKind, CodeActionParams, CreateFile, CreateFileOptions, DocumentChange,
37 Position, Range, TextEdit, Uri as Url,
38};
39use vec1::{Vec1, vec1};
40
41use crate::engine::{completely_within, position_within};
42
43use super::{
44 TextEdits,
45 compiler::LspProjectCompiler,
46 edits,
47 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
48 engine::{overlaps, within},
49 files::FileSystemProxy,
50 reference::{FindVariableReferences, VariableReferenceKind},
51 src_span_to_lsp_range, url_from_path,
52};
53
54#[derive(Debug)]
55pub struct CodeActionBuilder {
56 action: CodeAction,
57}
58
59impl CodeActionBuilder {
60 pub fn new(title: &str) -> Self {
61 Self {
62 action: CodeAction {
63 title: title.to_string(),
64 kind: None,
65 diagnostics: None,
66 edit: None,
67 command: None,
68 is_preferred: None,
69 disabled: None,
70 data: None,
71 tags: None,
72 },
73 }
74 }
75
76 pub fn kind(mut self, kind: CodeActionKind) -> Self {
77 self.action.kind = Some(kind);
78 self
79 }
80
81 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
82 let mut edit = self.action.edit.take().unwrap_or_default();
83 let mut changes = edit.changes.take().unwrap_or_default();
84 _ = changes.insert(uri, edits);
85
86 edit.changes = Some(changes);
87 self.action.edit = Some(edit);
88 self
89 }
90
91 pub fn document_changes(mut self, changes: Vec<DocumentChange>) -> Self {
92 let mut edit = self.action.edit.take().unwrap_or_default();
93
94 edit.document_changes = Some(changes);
95
96 self.action.edit = Some(edit);
97 self
98 }
99
100 pub fn preferred(mut self, is_preferred: bool) -> Self {
101 self.action.is_preferred = Some(is_preferred);
102 self
103 }
104
105 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
106 actions.push(self.action);
107 }
108}
109
110/// A small helper function to get the indentation at a given position.
111fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
112 let mut indent_size = 0;
113 let line_start = *line_numbers
114 .line_starts
115 .get(line as usize)
116 .expect("Line number should be valid");
117
118 let mut chars = code[line_start as usize..].chars();
119 while chars.next() == Some(' ') {
120 indent_size += 1;
121 }
122
123 indent_size
124}
125
126// Given a string and a position in it, if the position points to whitespace,
127// this function returns the next position which doesn't.
128fn next_nonwhitespace(string: &EcoString, position: u32) -> u32 {
129 let mut n = position;
130 let mut chars = string[position as usize..].chars();
131 while chars.next().is_some_and(char::is_whitespace) {
132 n += 1;
133 }
134 n
135}
136
137// Given a string and a position in it, if the position points after whitespace,
138// this function returns the previous position which doesn't.
139fn previous_nonwhitespace(string: &EcoString, position: u32) -> u32 {
140 let mut n = position;
141 let mut chars = string[..position as usize].chars();
142 while chars.next_back().is_some_and(char::is_whitespace) {
143 n -= 1;
144 }
145 n
146}
147
148/// Code action to remove literal tuples in case subjects, essentially making
149/// the elements of the tuples into the case's subjects.
150///
151/// The code action is only available for the i'th subject if:
152/// - it is a non-empty tuple, and
153/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
154///
155/// # Basic example:
156///
157/// The following case expression:
158///
159/// ```gleam
160/// case #(1, 2) {
161/// #(a, b) -> 0
162/// }
163/// ```
164///
165/// Becomes:
166///
167/// ```gleam
168/// case 1, 2 {
169/// a, b -> 0
170/// }
171/// ```
172///
173/// # Another example:
174///
175/// The following case expression does not produce any code action
176///
177/// ```gleam
178/// case #(1, 2) {
179/// a -> 0 // <- the pattern is not a tuple
180/// }
181/// ```
182pub struct RedundantTupleInCaseSubject<'a> {
183 edits: TextEdits<'a>,
184 code: &'a EcoString,
185 extra: &'a ModuleExtra,
186 params: &'a CodeActionParams,
187 module: &'a ast::TypedModule,
188 hovered: bool,
189}
190
191impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
192 fn visit_typed_expr_case(
193 &mut self,
194 location: &'ast SrcSpan,
195 type_: &'ast Arc<Type>,
196 subjects: &'ast [TypedExpr],
197 clauses: &'ast [ast::TypedClause],
198 compiled_case: &'ast CompiledCase,
199 ) {
200 for (subject_idx, subject) in subjects.iter().enumerate() {
201 let TypedExpr::Tuple {
202 location, elements, ..
203 } = subject
204 else {
205 continue;
206 };
207
208 // Ignore empty tuple
209 if elements.is_empty() {
210 continue;
211 }
212
213 // We cannot rewrite clauses whose i-th pattern is not a discard or
214 // tuples.
215 let all_ith_patterns_are_tuples_or_discards = clauses
216 .iter()
217 .map(|clause| clause.pattern.get(subject_idx))
218 .all(|pattern| {
219 matches!(
220 pattern,
221 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
222 )
223 });
224
225 if !all_ith_patterns_are_tuples_or_discards {
226 continue;
227 }
228
229 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
230
231 for clause in clauses {
232 match clause.pattern.get(subject_idx) {
233 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
234 *location,
235 elements.last().map(|element| element.location()),
236 ),
237 Some(Pattern::Discard { location, .. }) => {
238 self.discard_tuple_items(*location, elements.len());
239 }
240 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
241 }
242 }
243 let range = self.edits.src_span_to_lsp_range(*location);
244 self.hovered = self.hovered || overlaps(self.params.range, range);
245 }
246
247 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
248 }
249}
250
251impl<'a> RedundantTupleInCaseSubject<'a> {
252 pub fn new(
253 module: &'a Module,
254 line_numbers: &'a LineNumbers,
255 params: &'a CodeActionParams,
256 ) -> Self {
257 Self {
258 edits: TextEdits::new(line_numbers),
259 code: &module.code,
260 extra: &module.extra,
261 params,
262 module: &module.ast,
263 hovered: false,
264 }
265 }
266
267 pub fn code_actions(mut self) -> Vec<CodeAction> {
268 self.visit_typed_module(self.module);
269 if !self.hovered {
270 return vec![];
271 }
272
273 self.edits.edits.sort_by_key(|edit| edit.range.start);
274
275 let mut actions = vec![];
276 CodeActionBuilder::new("Remove redundant tuples")
277 .kind(CodeActionKind::RefactorRewrite)
278 .changes(self.params.text_document.uri.clone(), self.edits.edits)
279 .preferred(true)
280 .push_to(&mut actions);
281
282 actions
283 }
284
285 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
286 let tuple_code = self
287 .code
288 .get(location.start as usize..location.end as usize)
289 .expect("valid span");
290
291 // Delete `#`
292 self.edits
293 .delete(SrcSpan::new(location.start, location.start + 1));
294
295 // Delete `(`
296 let (lparen_offset, _) = tuple_code
297 .match_indices('(')
298 // Ignore in comments
299 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
300 .expect("`(` not found in tuple");
301
302 self.edits.delete(SrcSpan::new(
303 location.start + lparen_offset as u32,
304 location.start + lparen_offset as u32 + 1,
305 ));
306
307 // Delete trailing `,` (if applicable)
308 if let Some(last_elem_location) = last_elem_location {
309 // Get the code after the last element until the tuple's `)`
310 let code_after_last_elem = self
311 .code
312 .get(last_elem_location.end as usize..location.end as usize)
313 .expect("valid span");
314
315 if let Some((trailing_comma_offset, _)) = code_after_last_elem
316 .rmatch_indices(',')
317 // Ignore in comments
318 .find(|(i, _)| {
319 !self
320 .extra
321 .is_within_comment(last_elem_location.end + *i as u32)
322 })
323 {
324 self.edits.delete(SrcSpan::new(
325 last_elem_location.end + trailing_comma_offset as u32,
326 last_elem_location.end + trailing_comma_offset as u32 + 1,
327 ));
328 }
329 }
330
331 // Delete )
332 self.edits
333 .delete(SrcSpan::new(location.end - 1, location.end));
334 }
335
336 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
337 // Replace the old discard with multiple discard, one for each of the
338 // tuple items.
339 self.edits.replace(
340 discard_location,
341 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
342 );
343 }
344}
345
346/// Builder for code action to convert `let assert` into a case expression.
347///
348pub struct LetAssertToCase<'a> {
349 module: &'a Module,
350 params: &'a CodeActionParams,
351 actions: Vec<CodeAction>,
352 edits: TextEdits<'a>,
353}
354
355impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
356 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
357 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
358 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan {
359 start: assignment.location.start,
360 end: assignment.value.location().start,
361 });
362 self.visit_typed_expr(&assignment.value);
363
364 // Only offer the code action if the cursor is over the statement and
365 // to prevent weird behaviour when `let assert` statements are nested,
366 // we only check for the code action between the `let` and `=`.
367 if !(within(self.params.range, assignment_range)
368 && overlaps(self.params.range, assignment_start_range))
369 {
370 return;
371 }
372
373 // This pattern only applies to `let assert`
374 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
375 return;
376 };
377
378 // Get the source code for the tested expression
379 let location = assignment.value.location();
380 let expr = self
381 .module
382 .code
383 .get(location.start as usize..location.end as usize)
384 .expect("Location must be valid");
385
386 // Get the source code for the pattern
387 let pattern_location = assignment.pattern.location();
388 let pattern = self
389 .module
390 .code
391 .get(pattern_location.start as usize..pattern_location.end as usize)
392 .expect("Location must be valid");
393
394 let message = message.as_ref().map(|message| {
395 let location = message.location();
396 self.module
397 .code
398 .get(location.start as usize..location.end as usize)
399 .expect("Location must be valid")
400 });
401
402 let range = self.edits.src_span_to_lsp_range(assignment.location);
403
404 // Figure out which variables are assigned in the pattern
405 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
406
407 let assigned = match variables.len() {
408 0 => "_",
409 1 => variables.first().expect("Variables is length one"),
410 _ => &format!("#({})", variables.join(", ")),
411 };
412
413 let mut new_text = format!("let {assigned} = ");
414 let panic_message = if let Some(message) = message {
415 &format!("panic as {message}")
416 } else {
417 "panic"
418 };
419 let clauses = vec![
420 // The existing pattern
421 CaseClause {
422 pattern,
423 // `_` is not a valid expression, so if we are not
424 // binding any variables in the pattern, we simply return Nil.
425 expression: if assigned == "_" { "Nil" } else { assigned },
426 },
427 CaseClause {
428 pattern: "_",
429 expression: panic_message,
430 },
431 ];
432 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
433
434 let uri = &self.params.text_document.uri;
435
436 CodeActionBuilder::new("Convert to case")
437 .kind(CodeActionKind::RefactorRewrite)
438 .changes(uri.clone(), vec![TextEdit { range, new_text }])
439 .preferred(false)
440 .push_to(&mut self.actions);
441 }
442}
443
444impl<'a> LetAssertToCase<'a> {
445 pub fn new(
446 module: &'a Module,
447 line_numbers: &'a LineNumbers,
448 params: &'a CodeActionParams,
449 ) -> Self {
450 Self {
451 module,
452 params,
453 actions: Vec::new(),
454 edits: TextEdits::new(line_numbers),
455 }
456 }
457
458 pub fn code_actions(mut self) -> Vec<CodeAction> {
459 self.visit_typed_module(&self.module.ast);
460 self.actions
461 }
462}
463
464struct PatternVariableFinder {
465 pattern_variables: Vec<EcoString>,
466}
467
468impl PatternVariableFinder {
469 fn new() -> Self {
470 Self {
471 pattern_variables: Vec::new(),
472 }
473 }
474
475 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
476 let mut finder = Self::new();
477 finder.visit_typed_pattern(pattern);
478 finder.pattern_variables
479 }
480}
481
482impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
483 fn visit_typed_pattern_variable(
484 &mut self,
485 _location: &'ast SrcSpan,
486 name: &'ast EcoString,
487 _type: &'ast Arc<Type>,
488 _origin: &'ast VariableOrigin,
489 ) {
490 self.pattern_variables.push(name.clone());
491 }
492
493 fn visit_typed_pattern_assign(
494 &mut self,
495 location: &'ast SrcSpan,
496 name: &'ast EcoString,
497 pattern: &'ast TypedPattern,
498 ) {
499 self.pattern_variables.push(name.clone());
500 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
501 }
502
503 fn visit_typed_pattern_string_prefix(
504 &mut self,
505 _location: &'ast SrcSpan,
506 _left_location: &'ast SrcSpan,
507 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
508 _right_location: &'ast SrcSpan,
509 _left_side_string: &'ast EcoString,
510 right_side_assignment: &'ast AssignName,
511 ) {
512 if let Some((name, _)) = left_side_assignment {
513 self.pattern_variables.push(name.clone());
514 }
515 if let AssignName::Variable(name) = right_side_assignment {
516 self.pattern_variables.push(name.clone());
517 }
518 }
519}
520
521pub fn code_action_inexhaustive_let_to_case(
522 module: &Module,
523 line_numbers: &LineNumbers,
524 params: &CodeActionParams,
525 error: &Option<Error>,
526 actions: &mut Vec<CodeAction>,
527) {
528 let Some(errors) = type_errors_for_module(error, module) else {
529 return;
530 };
531
532 let inexhaustive_assignments = errors
533 .iter()
534 .filter_map(|error| {
535 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error {
536 Some((*location, missing))
537 } else {
538 None
539 }
540 })
541 .collect_vec();
542
543 if inexhaustive_assignments.is_empty() {
544 return;
545 }
546
547 for (location, missing) in inexhaustive_assignments {
548 let mut text_edits = TextEdits::new(line_numbers);
549
550 let range = text_edits.src_span_to_lsp_range(location);
551 if !within(params.range, range) {
552 continue;
553 }
554
555 let Some(Located::Statement(TypedStatement::Assignment(assignment))) =
556 module.find_node(location.start)
557 else {
558 continue;
559 };
560
561 let TypedAssignment {
562 value,
563 pattern,
564 kind: AssignmentKind::Let,
565 location,
566 compiled_case: _,
567 annotation: _,
568 } = assignment.as_ref()
569 else {
570 continue;
571 };
572
573 // Get the source code for the tested expression
574 let value_location = value.location();
575 let expr = module
576 .code
577 .get(value_location.start as usize..value_location.end as usize)
578 .expect("Location must be valid");
579
580 // Get the source code for the pattern
581 let pattern_location = pattern.location();
582 let pattern_code = module
583 .code
584 .get(pattern_location.start as usize..pattern_location.end as usize)
585 .expect("Location must be valid");
586
587 let range = text_edits.src_span_to_lsp_range(*location);
588
589 // Figure out which variables are assigned in the pattern
590 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
591
592 let assigned = match variables.len() {
593 0 => "_",
594 1 => variables.first().expect("Variables is length one"),
595 _ => &format!("#({})", variables.join(", ")),
596 };
597
598 let mut new_text = format!("let {assigned} = ");
599 print_case_expression(
600 range.start.character as usize,
601 expr,
602 iter::once(CaseClause {
603 pattern: pattern_code,
604 expression: if assigned == "_" { "Nil" } else { assigned },
605 })
606 .chain(missing.iter().map(|pattern| CaseClause {
607 pattern,
608 expression: "todo",
609 }))
610 .collect(),
611 &mut new_text,
612 );
613
614 let uri = ¶ms.text_document.uri;
615
616 text_edits.replace(*location, new_text);
617
618 CodeActionBuilder::new("Convert to case")
619 .kind(CodeActionKind::QuickFix)
620 .changes(uri.clone(), text_edits.edits)
621 .preferred(true)
622 .push_to(actions);
623 }
624}
625
626pub(crate) fn type_errors_for_module<'a>(
627 error: &'a Option<Error>,
628 module: &Module,
629) -> Option<&'a Vec1<type_::Error>> {
630 let Some(Error::Type {
631 skipped_modules: _,
632 failed_modules,
633 }) = error
634 else {
635 return None;
636 };
637 Some(&failed_modules.get(&module.name)?.errors)
638}
639
640struct CaseClause<'a> {
641 pattern: &'a str,
642 expression: &'a str,
643}
644
645fn print_case_expression(
646 indent_size: usize,
647 subject: &str,
648 clauses: Vec<CaseClause<'_>>,
649 buffer: &mut String,
650) {
651 let indent = " ".repeat(indent_size);
652
653 // Print the beginning of the expression
654 buffer.push_str("case ");
655 buffer.push_str(subject);
656 buffer.push_str(" {");
657
658 for clause in clauses.iter() {
659 // Print the newline and indentation for this clause
660 buffer.push('\n');
661 buffer.push_str(&indent);
662 // Indent this clause one level deeper than the case expression
663 buffer.push_str(" ");
664
665 // Print the clause
666 buffer.push_str(clause.pattern);
667 buffer.push_str(" -> ");
668 buffer.push_str(clause.expression);
669 }
670
671 // If there are no clauses to print, the closing brace should be
672 // on the same line as the opening one, with no space between.
673 if !clauses.is_empty() {
674 buffer.push('\n');
675 buffer.push_str(&indent);
676 }
677 buffer.push('}');
678}
679
680/// Builder for code action to apply the label shorthand syntax on arguments
681/// where the label has the same name as the variable.
682///
683pub struct UseLabelShorthandSyntax<'a> {
684 module: &'a Module,
685 params: &'a CodeActionParams,
686 edits: TextEdits<'a>,
687}
688
689impl<'a> UseLabelShorthandSyntax<'a> {
690 pub fn new(
691 module: &'a Module,
692 line_numbers: &'a LineNumbers,
693 params: &'a CodeActionParams,
694 ) -> Self {
695 Self {
696 module,
697 params,
698 edits: TextEdits::new(line_numbers),
699 }
700 }
701
702 pub fn code_actions(mut self) -> Vec<CodeAction> {
703 self.visit_typed_module(&self.module.ast);
704 if self.edits.edits.is_empty() {
705 return vec![];
706 }
707 let mut action = Vec::with_capacity(1);
708 CodeActionBuilder::new("Use label shorthand syntax")
709 .kind(CodeActionKind::Refactor)
710 .changes(self.params.text_document.uri.clone(), self.edits.edits)
711 .preferred(false)
712 .push_to(&mut action);
713 action
714 }
715}
716
717impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
718 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
719 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
720 let is_selected = overlaps(arg_range, self.params.range);
721
722 match arg {
723 CallArg {
724 label: Some(label),
725 value: TypedExpr::Var { name, location, .. },
726 ..
727 } if is_selected && !arg.uses_label_shorthand() && label == name => {
728 self.edits.delete(*location);
729 }
730 _ => (),
731 }
732
733 ast::visit::visit_typed_call_arg(self, arg);
734 }
735
736 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
737 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
738 let is_selected = overlaps(arg_range, self.params.range);
739
740 match arg {
741 CallArg {
742 label: Some(label),
743 value: TypedPattern::Variable { name, location, .. },
744 ..
745 } if is_selected && !arg.uses_label_shorthand() && label == name => {
746 self.edits.delete(*location);
747 }
748 _ => (),
749 }
750
751 ast::visit::visit_typed_pattern_call_arg(self, arg);
752 }
753}
754
755/// Builder for code action to apply the fill in the missing labelled arguments
756/// of the selected function call.
757///
758pub struct FillInMissingLabelledArgs<'a> {
759 module: &'a Module,
760 params: &'a CodeActionParams,
761 edits: TextEdits<'a>,
762 use_right_hand_side_location: Option<SrcSpan>,
763 selected_call: Option<SelectedCall<'a>>,
764 current_function: Option<&'a TypedFunction>,
765}
766
767struct SelectedCall<'a> {
768 location: SrcSpan,
769 field_map: &'a FieldMap,
770 arguments: Vec<CallArg<()>>,
771 kind: SelectedCallKind,
772 fun_type: Option<Arc<Type>>,
773 enclosing_function: Option<&'a TypedFunction>,
774}
775
776enum SelectedCallKind {
777 Value,
778 Pattern,
779}
780
781impl<'a> FillInMissingLabelledArgs<'a> {
782 pub fn new(
783 module: &'a Module,
784 line_numbers: &'a LineNumbers,
785 params: &'a CodeActionParams,
786 ) -> Self {
787 Self {
788 module,
789 params,
790 edits: TextEdits::new(line_numbers),
791 use_right_hand_side_location: None,
792 selected_call: None,
793 current_function: None,
794 }
795 }
796
797 pub fn code_actions(mut self) -> Vec<CodeAction> {
798 self.visit_typed_module(&self.module.ast);
799 let Some(SelectedCall {
800 location: call_location,
801 field_map,
802 arguments,
803 kind,
804 fun_type,
805 enclosing_function,
806 }) = self.selected_call
807 else {
808 return vec![];
809 };
810
811 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
812 let missing_labels = field_map.missing_labels(&arguments);
813
814 // If we're applying the code action to a use call, then we know
815 // that the last missing argument is going to be implicitly inserted
816 // by the compiler, so in that case we don't want to also add that
817 // last label to the completions.
818 let missing_labels = missing_labels.iter().peekable();
819 let mut missing_labels = if is_use_call {
820 missing_labels.dropping_back(1)
821 } else {
822 missing_labels
823 };
824
825 // If we couldn't find any missing label to insert we just return.
826 if missing_labels.peek().is_none() {
827 return vec![];
828 }
829
830 // A pattern could have been written with no parentheses at all!
831 // So we need to check for the last character to see if parentheses
832 // are there or not before filling the arguments in
833 let has_parentheses = ")"
834 == code_at(
835 self.module,
836 SrcSpan::new(call_location.end - 1, call_location.end),
837 );
838 let label_insertion_start = if has_parentheses {
839 // If it ends with a parentheses we'll need to start inserting
840 // right before the closing one...
841 call_location.end - 1
842 } else {
843 // ...otherwise we just append the result
844 call_location.end
845 };
846
847 // Now we need to figure out if there's a comma at the end of the
848 // arguments list:
849 //
850 // call(one, |)
851 // ^ Cursor here, with a comma behind
852 //
853 // call(one|)
854 // ^ Cursor here, no comma behind, we'll have to add one!
855 //
856 let has_comma_after_last_argument =
857 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) {
858 self.module
859 .code
860 .get(last_arg.location.end as usize..=label_insertion_start as usize)
861 .is_some_and(|text| text.contains(','))
862 } else {
863 false
864 };
865
866 let scope_variables =
867 ScopeVariableCollector::new(call_location.start, &self.module.ast.definitions);
868 let variables_in_scope = match enclosing_function {
869 Some(function) => scope_variables.collect_from_function(function),
870 None => scope_variables.variables,
871 };
872
873 let labels_list = missing_labels
874 .map(|label| {
875 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope)
876 })
877 .join(", ");
878
879 let has_no_explicit_arguments = arguments
880 .iter()
881 .filter(|arg| !arg.is_implicit())
882 .peekable()
883 .peek()
884 .is_none();
885
886 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
887 labels_list
888 } else {
889 format!(", {labels_list}")
890 };
891
892 let edit = if has_parentheses {
893 labels_list
894 } else {
895 // If the variant whose arguments we're filling in was written
896 // with no parentheses we need to add those as well to make it a
897 // valid constructor.
898 format!("({labels_list})")
899 };
900
901 self.edits.insert(label_insertion_start, edit);
902
903 let mut action = Vec::with_capacity(1);
904 CodeActionBuilder::new("Fill labels")
905 .kind(CodeActionKind::QuickFix)
906 .changes(self.params.text_document.uri.clone(), self.edits.edits)
907 .preferred(true)
908 .push_to(&mut action);
909 action
910 }
911
912 /// Formats a label for insertion. Uses `label:` syntax if there's a variable
913 /// in scope with matching name and type, otherwise uses `label: todo`.
914 fn format_label(
915 label: &EcoString,
916 kind: &SelectedCallKind,
917 fun_type: &Option<Arc<Type>>,
918 field_map: &FieldMap,
919 variables_in_scope: &HashMap<EcoString, Arc<Type>>,
920 ) -> String {
921 if matches!(kind, SelectedCallKind::Pattern) {
922 return format!("{label}:");
923 }
924
925 if let Some(var_type) = variables_in_scope.get(label) {
926 let expected_type = fun_type.as_ref().and_then(|ft| {
927 let (arg_types, _) = ft.fn_types()?;
928 let &index = field_map.fields.get(label)?;
929 arg_types.get(index as usize).cloned()
930 });
931
932 if let Some(expected) = expected_type
933 && expected.same_as(var_type)
934 {
935 return format!("{label}:");
936 }
937 }
938
939 format!("{label}: todo")
940 }
941
942 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
943 CallArg {
944 label: argument.label.clone(),
945 location: argument.location,
946 value: (),
947 implicit: argument.implicit,
948 }
949 }
950}
951
952impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
953 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
954 // We store the current function as the containing function while we traverse
955 // it, allowing handling nested functions correctly.
956 let previous_function = self.current_function;
957 self.current_function = Some(fun);
958 ast::visit::visit_typed_function(self, fun);
959 self.current_function = previous_function;
960 }
961
962 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
963 // If we're adding labels to a use call the correct location of the
964 // function we need to add labels to is `use_right_hand_side_location`.
965 // So we store it for when we're typing the use call.
966 let previous = self.use_right_hand_side_location;
967 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
968 ast::visit::visit_typed_use(self, use_);
969 self.use_right_hand_side_location = previous;
970 }
971
972 fn visit_typed_constant_record(
973 &mut self,
974 location: &'ast SrcSpan,
975 module: &'ast Option<(EcoString, SrcSpan)>,
976 name: &'ast EcoString,
977 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
978 type_: &'ast Arc<Type>,
979 field_map: &'ast Inferred<FieldMap>,
980 record_constructor: &'ast Option<Box<ValueConstructor>>,
981 ) {
982 let record_range = self.edits.src_span_to_lsp_range(*location);
983 if !within(self.params.range, record_range) {
984 return;
985 }
986
987 if let Some(arguments) = arguments
988 && let Inferred::Known(field_map) = field_map
989 {
990 self.selected_call = Some(SelectedCall {
991 location: *location,
992 field_map,
993 arguments: arguments.iter().map(Self::empty_argument).collect(),
994 kind: SelectedCallKind::Value,
995 fun_type: record_constructor
996 .as_ref()
997 .map(|constructor| constructor.type_.clone()),
998 enclosing_function: None,
999 });
1000 }
1001
1002 // We only want to take into account the innermost function call
1003 // containing the current selection so we can't stop at the first call
1004 // we find (the outermost one) and have to keep traversing it in case
1005 // we're inside a nested call.
1006 ast::visit::visit_typed_constant_record(
1007 self,
1008 location,
1009 module,
1010 name,
1011 arguments,
1012 type_,
1013 field_map,
1014 record_constructor,
1015 );
1016 }
1017
1018 fn visit_typed_expr_call(
1019 &mut self,
1020 location: &'ast SrcSpan,
1021 type_: &'ast Arc<Type>,
1022 fun: &'ast TypedExpr,
1023 arguments: &'ast [TypedCallArg],
1024 open_parenthesis: &'ast Option<u32>,
1025 ) {
1026 let call_range = self.edits.src_span_to_lsp_range(*location);
1027 if !within(self.params.range, call_range) {
1028 return;
1029 }
1030
1031 if let Some(field_map) = fun.field_map() {
1032 let location = self.use_right_hand_side_location.unwrap_or(*location);
1033 self.selected_call = Some(SelectedCall {
1034 location,
1035 field_map,
1036 arguments: arguments.iter().map(Self::empty_argument).collect(),
1037 kind: SelectedCallKind::Value,
1038 fun_type: Some(fun.type_()),
1039 enclosing_function: self.current_function,
1040 });
1041 }
1042
1043 // We only want to take into account the innermost function call
1044 // containing the current selection so we can't stop at the first call
1045 // we find (the outermost one) and have to keep traversing it in case
1046 // we're inside a nested call.
1047 let previous = self.use_right_hand_side_location;
1048 self.use_right_hand_side_location = None;
1049 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
1050 self.use_right_hand_side_location = previous;
1051 }
1052
1053 fn visit_typed_pattern_constructor(
1054 &mut self,
1055 location: &'ast SrcSpan,
1056 name_location: &'ast SrcSpan,
1057 name: &'ast EcoString,
1058 arguments: &'ast Vec<CallArg<TypedPattern>>,
1059 module: &'ast Option<(EcoString, SrcSpan)>,
1060 constructor: &'ast Inferred<type_::PatternConstructor>,
1061 spread: &'ast Option<SrcSpan>,
1062 type_: &'ast Arc<Type>,
1063 ) {
1064 let call_range = self.edits.src_span_to_lsp_range(*location);
1065 if !within(self.params.range, call_range) {
1066 return;
1067 }
1068
1069 if let Some(field_map) = constructor.field_map() {
1070 self.selected_call = Some(SelectedCall {
1071 location: *location,
1072 field_map,
1073 arguments: arguments.iter().map(Self::empty_argument).collect(),
1074 kind: SelectedCallKind::Pattern,
1075 fun_type: None,
1076 enclosing_function: self.current_function,
1077 });
1078 }
1079
1080 ast::visit::visit_typed_pattern_constructor(
1081 self,
1082 location,
1083 name_location,
1084 name,
1085 arguments,
1086 module,
1087 constructor,
1088 spread,
1089 type_,
1090 );
1091 }
1092}
1093
1094/// Collects variables that are in scope at a given cursor position.
1095struct ScopeVariableCollector {
1096 cursor: u32,
1097 pub variables: HashMap<EcoString, Arc<Type>>,
1098}
1099
1100impl ScopeVariableCollector {
1101 fn new(cursor: u32, definitions: &TypedDefinitions) -> Self {
1102 let TypedDefinitions { constants, .. } = definitions;
1103
1104 // When creating a collector, we add module constants to the variables
1105 // that are in scope.
1106 // These will always be available inside the body of any function we
1107 // might want to analyse (unless they are shadowed and replaced in the
1108 // map, that is)
1109 let variables = constants
1110 .iter()
1111 .map(|constant| (constant.name.clone(), constant.type_.clone()))
1112 .collect();
1113
1114 Self { cursor, variables }
1115 }
1116
1117 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> {
1118 for arg in &fun.arguments {
1119 if let Some(name) = arg.get_variable_name() {
1120 _ = self.variables.insert(name.clone(), arg.type_.clone());
1121 }
1122 }
1123
1124 for statement in &fun.body {
1125 self.visit_typed_statement(statement);
1126 }
1127
1128 self.variables
1129 }
1130}
1131
1132impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector {
1133 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
1134 // We need to consider variables defined only before the cursor
1135 if statement.location().start >= self.cursor {
1136 return;
1137 }
1138
1139 ast::visit::visit_typed_statement(self, statement);
1140 }
1141
1142 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1143 // if cursor is inside the assignment, we only visit the value expression,
1144 // because the variable being defined isn't in scope yet.
1145 if assignment.location.contains(self.cursor) {
1146 self.visit_typed_expr(&assignment.value);
1147 } else {
1148 self.visit_typed_pattern(&assignment.pattern);
1149 }
1150 }
1151
1152 fn visit_typed_expr_fn(
1153 &mut self,
1154 _location: &'ast SrcSpan,
1155 _type_: &'ast Arc<Type>,
1156 _kind: &'ast FunctionLiteralKind,
1157 _arguments: &'ast [TypedArg],
1158 _body: &'ast Vec1<TypedStatement>,
1159 _return_annotation: &'ast Option<ast::TypeAst>,
1160 ) {
1161 // We don't descend into nested functions, as their variables aren't in
1162 // our scope
1163 }
1164
1165 fn visit_typed_expr_block(
1166 &mut self,
1167 location: &'ast SrcSpan,
1168 statements: &'ast [TypedStatement],
1169 ) {
1170 if self.cursor >= location.end {
1171 return;
1172 }
1173 ast::visit::visit_typed_expr_block(self, location, statements);
1174 }
1175
1176 fn visit_typed_pattern_variable(
1177 &mut self,
1178 _location: &'ast SrcSpan,
1179 name: &'ast EcoString,
1180 type_: &'ast Arc<Type>,
1181 _origin: &'ast VariableOrigin,
1182 ) {
1183 if !name.starts_with('_') {
1184 _ = self.variables.insert(name.clone(), type_.clone());
1185 }
1186 }
1187
1188 fn visit_typed_pattern_assign(
1189 &mut self,
1190 _location: &'ast SrcSpan,
1191 name: &'ast EcoString,
1192 pattern: &'ast Pattern<Arc<Type>>,
1193 ) {
1194 self.visit_typed_pattern(pattern);
1195 if !name.starts_with('_') {
1196 _ = self.variables.insert(name.clone(), pattern.type_());
1197 }
1198 }
1199}
1200
1201struct MissingImport {
1202 location: SrcSpan,
1203 suggestions: Vec<ImportSuggestion>,
1204}
1205
1206struct ImportSuggestion {
1207 // The name to replace with, if the user made a typo
1208 name: EcoString,
1209 // The optional module to import, if suggesting an importable module
1210 import: Option<EcoString>,
1211}
1212
1213pub fn code_action_import_module(
1214 module: &Module,
1215 line_numbers: &LineNumbers,
1216 params: &CodeActionParams,
1217 error: &Option<Error>,
1218 actions: &mut Vec<CodeAction>,
1219) {
1220 let uri = ¶ms.text_document.uri;
1221 let Some(errors) = type_errors_for_module(error, module) else {
1222 return;
1223 };
1224
1225 let missing_imports = errors
1226 .into_iter()
1227 .filter_map(|error| {
1228 if let type_::Error::UnknownModule {
1229 location,
1230 suggestions,
1231 ..
1232 } = error
1233 {
1234 suggest_imports(*location, suggestions)
1235 } else {
1236 None
1237 }
1238 })
1239 .collect_vec();
1240
1241 if missing_imports.is_empty() {
1242 return;
1243 }
1244
1245 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
1246 let first_is_import = first_import_pos.is_some();
1247 let import_location = first_import_pos.unwrap_or_default();
1248
1249 let after_import_newlines =
1250 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
1251
1252 for missing_import in missing_imports {
1253 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
1254 if !overlaps(params.range, range) {
1255 continue;
1256 }
1257
1258 for suggestion in missing_import.suggestions {
1259 let mut edits = vec![TextEdit {
1260 range,
1261 new_text: suggestion.name.to_string(),
1262 }];
1263 if let Some(import) = &suggestion.import {
1264 edits.push(get_import_edit(
1265 import_location,
1266 import,
1267 &after_import_newlines,
1268 ));
1269 }
1270
1271 let title = match &suggestion.import {
1272 Some(import) => &format!("Import `{import}`"),
1273 _ => &format!("Did you mean `{}`", suggestion.name),
1274 };
1275
1276 CodeActionBuilder::new(title)
1277 .kind(CodeActionKind::QuickFix)
1278 .changes(uri.clone(), edits)
1279 .preferred(true)
1280 .push_to(actions);
1281 }
1282 }
1283}
1284
1285pub fn code_action_generate_type(
1286 module: &Module,
1287 line_numbers: &LineNumbers,
1288 params: &CodeActionParams,
1289 error: &Option<Error>,
1290 actions: &mut Vec<CodeAction>,
1291) {
1292 let uri = ¶ms.text_document.uri;
1293 let Some(errors) = type_errors_for_module(error, module) else {
1294 return;
1295 };
1296
1297 for error in errors {
1298 let type_::Error::UnknownType {
1299 location,
1300 name,
1301 parameter_names,
1302 ..
1303 } = error
1304 else {
1305 continue;
1306 };
1307
1308 let range = src_span_to_lsp_range(*location, line_numbers);
1309 if !within(params.range, range) {
1310 continue;
1311 }
1312
1313 // Insert the new type stub before the top-level definition that
1314 // contains the error, so it appears close to where it is used.
1315 let (insert_at, is_public) =
1316 definition_start_and_publicity_containing(&module.ast.definitions, location.start)
1317 .unwrap_or((module.code.len() as u32, false));
1318 let insert_range = src_span_to_lsp_range(
1319 SrcSpan {
1320 start: insert_at,
1321 end: insert_at,
1322 },
1323 line_numbers,
1324 );
1325
1326 let pub_prefix = if is_public { "pub " } else { "" };
1327 let new_text = if parameter_names.is_empty() {
1328 format!("{pub_prefix}type {name}\n\n")
1329 } else {
1330 let parameters = parameter_names.join(", ");
1331 format!("{pub_prefix}type {name}({parameters})\n\n")
1332 };
1333
1334 let edit = TextEdit {
1335 range: insert_range,
1336 new_text,
1337 };
1338
1339 CodeActionBuilder::new("Generate type")
1340 .kind(CodeActionKind::QuickFix)
1341 .changes(uri.clone(), vec![edit])
1342 .preferred(true)
1343 .push_to(actions);
1344 }
1345}
1346
1347/// Returns the source offset and publicity of the top-level definition that
1348/// contains `position`, so the caller can insert code before it.
1349fn definition_start_and_publicity_containing(
1350 definitions: &TypedDefinitions,
1351 position: u32,
1352) -> Option<(u32, bool)> {
1353 let functions = definitions
1354 .functions
1355 .iter()
1356 .map(|function| (function.full_location(), function.publicity.is_public()));
1357 let custom_types = definitions.custom_types.iter().map(|custom_type| {
1358 (
1359 custom_type.full_location(),
1360 custom_type.publicity.is_public(),
1361 )
1362 });
1363 let type_aliases = definitions
1364 .type_aliases
1365 .iter()
1366 .map(|type_alias| (type_alias.location, type_alias.publicity.is_public()));
1367 let constants = definitions
1368 .constants
1369 .iter()
1370 .map(|constant| (constant.location, constant.publicity.is_public()));
1371
1372 functions
1373 .chain(custom_types)
1374 .chain(type_aliases)
1375 .chain(constants)
1376 .filter(|(span, _)| span.start <= position && position <= span.end)
1377 .map(|(span, is_public)| (span.start, is_public))
1378 .next()
1379}
1380
1381fn suggest_imports(
1382 location: SrcSpan,
1383 importable_modules: &[ModuleSuggestion],
1384) -> Option<MissingImport> {
1385 let suggestions = importable_modules
1386 .iter()
1387 .map(|suggestion| {
1388 let imported_name = suggestion.last_name_component();
1389 match suggestion {
1390 ModuleSuggestion::Importable(name) => ImportSuggestion {
1391 name: imported_name.into(),
1392 import: Some(name.clone()),
1393 },
1394 ModuleSuggestion::Imported(_) => ImportSuggestion {
1395 name: imported_name.into(),
1396 import: None,
1397 },
1398 }
1399 })
1400 .collect_vec();
1401
1402 if suggestions.is_empty() {
1403 None
1404 } else {
1405 Some(MissingImport {
1406 location,
1407 suggestions,
1408 })
1409 }
1410}
1411
1412pub fn code_action_add_missing_patterns(
1413 module: &Module,
1414 line_numbers: &LineNumbers,
1415 params: &CodeActionParams,
1416 error: &Option<Error>,
1417 actions: &mut Vec<CodeAction>,
1418) {
1419 let uri = ¶ms.text_document.uri;
1420 let Some(errors) = type_errors_for_module(error, module) else {
1421 return;
1422 };
1423 let missing_patterns = errors
1424 .iter()
1425 .filter_map(|error| {
1426 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error {
1427 Some((*location, missing))
1428 } else {
1429 None
1430 }
1431 })
1432 .collect_vec();
1433
1434 if missing_patterns.is_empty() {
1435 return;
1436 }
1437
1438 for (location, missing) in missing_patterns {
1439 let mut edits = TextEdits::new(line_numbers);
1440 let range = edits.src_span_to_lsp_range(location);
1441 if !within(params.range, range) {
1442 continue;
1443 }
1444
1445 let Some(Located::Expression {
1446 expression: TypedExpr::Case {
1447 clauses, subjects, ..
1448 },
1449 ..
1450 }) = module.find_node(location.start)
1451 else {
1452 continue;
1453 };
1454
1455 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1456
1457 let indent = " ".repeat(indent_size);
1458
1459 // Insert the missing patterns just after the final clause, or just before
1460 // the closing brace if there are no clauses
1461
1462 let insert_at = clauses
1463 .last()
1464 .map(|clause| clause.location.end)
1465 .unwrap_or(location.end - 1);
1466
1467 for pattern in missing {
1468 let new_text = format!("\n{indent} {pattern} -> todo");
1469 edits.insert(insert_at, new_text);
1470 }
1471
1472 // Add a newline + indent after the last pattern if there are no clauses
1473 //
1474 // This improves the generated code for this case:
1475 // ```gleam
1476 // case True {}
1477 // ```
1478 // This produces:
1479 // ```gleam
1480 // case True {
1481 // True -> todo
1482 // False -> todo
1483 // }
1484 // ```
1485 // Instead of:
1486 // ```gleam
1487 // case True {
1488 // True -> todo
1489 // False -> todo}
1490 // ```
1491 //
1492 if clauses.is_empty() {
1493 let last_subject_location = subjects
1494 .last()
1495 .expect("Case expressions have at least one subject")
1496 .location()
1497 .end;
1498
1499 // Find the opening brace of the case expression
1500 let chars = module.code[last_subject_location as usize..].chars();
1501 let mut start_brace_location = last_subject_location;
1502 for char in chars {
1503 start_brace_location += 1;
1504 if char == '{' {
1505 break;
1506 }
1507 }
1508
1509 // We remove any blank spaces/lines between the end of the last
1510 // comment inside the case expression and the insertion point.
1511 // If there's no comments we remove all empty space in between the
1512 // two braces
1513 let deletion_start = module
1514 .extra
1515 .last_comment_between(start_brace_location, insert_at)
1516 .map(|src_span| src_span.end)
1517 .unwrap_or(start_brace_location);
1518
1519 edits.delete(SrcSpan::new(deletion_start, insert_at));
1520 edits.insert(insert_at, format!("\n{indent}"));
1521 }
1522
1523 CodeActionBuilder::new("Add missing patterns")
1524 .kind(CodeActionKind::QuickFix)
1525 .changes(uri.clone(), edits.edits)
1526 .preferred(true)
1527 .push_to(actions);
1528 }
1529}
1530
1531/// Builder for code action to add annotations to an assignment or function
1532///
1533pub struct AddAnnotations<'a> {
1534 module: &'a Module,
1535 params: &'a CodeActionParams,
1536 edits: TextEdits<'a>,
1537 printer: Printer<'a>,
1538}
1539
1540impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1541 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1542 self.visit_typed_expr(&assignment.value);
1543
1544 // We only offer this code action between `let` and `=`, because
1545 // otherwise it could lead to confusing behaviour if inside a block
1546 // which is part of a let binding.
1547 let pattern_location = assignment.pattern.location();
1548 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1549 let code_action_range = self.edits.src_span_to_lsp_range(location);
1550
1551 // Only offer the code action if the cursor is over the statement
1552 if !overlaps(code_action_range, self.params.range) {
1553 return;
1554 }
1555
1556 // We don't need to add an annotation if there already is one
1557 if assignment.annotation.is_some() {
1558 return;
1559 }
1560
1561 // Various expressions such as pipelines and `use` expressions generate assignments
1562 // internally. However, these cannot be annotated and so we don't offer a code action here.
1563 if matches!(assignment.kind, AssignmentKind::Generated) {
1564 return;
1565 }
1566
1567 self.edits.insert(
1568 pattern_location.end,
1569 format!(": {}", self.printer.print_type(&assignment.type_())),
1570 );
1571 }
1572
1573 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1574 // Since type variable names are local to definitions, any type variables
1575 // in other parts of the module shouldn't affect what we print for the
1576 // annotations of this constant.
1577 self.printer.clear_type_variables();
1578
1579 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1580
1581 // Only offer the code action if the cursor is over the statement
1582 if !overlaps(code_action_range, self.params.range) {
1583 return;
1584 }
1585
1586 // We don't need to add an annotation if there already is one
1587 if constant.annotation.is_some() {
1588 return;
1589 }
1590
1591 self.edits.insert(
1592 constant.name_location.end,
1593 format!(": {}", self.printer.print_type(&constant.type_)),
1594 );
1595 }
1596
1597 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1598 // Since type variable names are local to definitions, any type variables
1599 // in other parts of the module shouldn't affect what we print for the
1600 // annotations of this functions. The only variables which cannot clash
1601 // are ones defined in the signature of this function, which we register
1602 // when we visit the parameters of this function inside `collect_type_variables`.
1603 self.printer.clear_type_variables();
1604 collect_type_variables(&mut self.printer, fun);
1605
1606 ast::visit::visit_typed_function(self, fun);
1607
1608 let code_action_range = self.edits.src_span_to_lsp_range(
1609 fun.body_start
1610 .map(|body_start| SrcSpan {
1611 start: fun.location.start,
1612 end: body_start,
1613 })
1614 .unwrap_or(fun.location),
1615 );
1616
1617 // Only offer the code action if the cursor is over the statement
1618 if !overlaps(code_action_range, self.params.range) {
1619 return;
1620 }
1621
1622 // Annotate each argument separately
1623 for argument in fun.arguments.iter() {
1624 // Don't annotate the argument if it's already annotated
1625 if argument.annotation.is_some() {
1626 continue;
1627 }
1628
1629 self.edits.insert(
1630 argument.location.end,
1631 format!(": {}", self.printer.print_type(&argument.type_)),
1632 );
1633 }
1634
1635 // Annotate the return type if it isn't already annotated
1636 if fun.return_annotation.is_none() {
1637 self.edits.insert(
1638 fun.location.end,
1639 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1640 );
1641 }
1642 }
1643
1644 fn visit_typed_expr_fn(
1645 &mut self,
1646 location: &'ast SrcSpan,
1647 type_: &'ast Arc<Type>,
1648 kind: &'ast FunctionLiteralKind,
1649 arguments: &'ast [TypedArg],
1650 body: &'ast Vec1<TypedStatement>,
1651 return_annotation: &'ast Option<ast::TypeAst>,
1652 ) {
1653 ast::visit::visit_typed_expr_fn(
1654 self,
1655 location,
1656 type_,
1657 kind,
1658 arguments,
1659 body,
1660 return_annotation,
1661 );
1662
1663 // If the function doesn't have a head, we can't annotate it
1664 let location = match kind {
1665 // Function captures don't need any type annotations
1666 FunctionLiteralKind::Capture { .. } => return,
1667 FunctionLiteralKind::Anonymous { head, .. } => head,
1668 FunctionLiteralKind::Use { location } => location,
1669 };
1670
1671 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1672
1673 // Only offer the code action if the cursor is over the expression
1674 if !overlaps(code_action_range, self.params.range) {
1675 return;
1676 }
1677
1678 // Annotate each argument separately
1679 for argument in arguments.iter() {
1680 // Don't annotate the argument if it's already annotated
1681 if argument.annotation.is_some() {
1682 continue;
1683 }
1684
1685 self.edits.insert(
1686 argument.location.end,
1687 format!(": {}", self.printer.print_type(&argument.type_)),
1688 );
1689 }
1690
1691 // Annotate the return type if it isn't already annotated, and this is
1692 // an anonymous function.
1693 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1694 let return_type = &type_.return_type().expect("Type must be a function");
1695 let pretty_type = self.printer.print_type(return_type);
1696 self.edits
1697 .insert(location.end, format!(" -> {pretty_type}"));
1698 }
1699 }
1700}
1701
1702impl<'a> AddAnnotations<'a> {
1703 pub fn new(
1704 module: &'a Module,
1705 line_numbers: &'a LineNumbers,
1706 params: &'a CodeActionParams,
1707 ) -> Self {
1708 Self {
1709 module,
1710 params,
1711 edits: TextEdits::new(line_numbers),
1712 // We need to use the same printer for all the edits because otherwise
1713 // we could get duplicate type variable names.
1714 printer: Printer::new_without_type_variables(&module.ast.names),
1715 }
1716 }
1717
1718 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1719 self.visit_typed_module(&self.module.ast);
1720
1721 let uri = &self.params.text_document.uri;
1722
1723 let title = match self.edits.edits.len() {
1724 // We don't offer a code action if there is no action to perform
1725 0 => return,
1726 1 => "Add type annotation",
1727 _ => "Add type annotations",
1728 };
1729
1730 CodeActionBuilder::new(title)
1731 .kind(CodeActionKind::Refactor)
1732 .changes(uri.clone(), self.edits.edits)
1733 .preferred(false)
1734 .push_to(actions);
1735 }
1736}
1737
1738/// Code action to add type annotations to all top level definitions
1739///
1740pub struct AnnotateTopLevelDefinitions<'a> {
1741 module: &'a Module,
1742 params: &'a CodeActionParams,
1743 edits: TextEdits<'a>,
1744 is_hovering_definition_requiring_annotations: bool,
1745}
1746
1747impl<'a> AnnotateTopLevelDefinitions<'a> {
1748 pub fn new(
1749 module: &'a Module,
1750 line_numbers: &'a LineNumbers,
1751 params: &'a CodeActionParams,
1752 ) -> Self {
1753 Self {
1754 module,
1755 params,
1756 edits: TextEdits::new(line_numbers),
1757 is_hovering_definition_requiring_annotations: false,
1758 }
1759 }
1760
1761 pub fn code_actions(mut self) -> Vec<CodeAction> {
1762 self.visit_typed_module(&self.module.ast);
1763
1764 // We only want to trigger the action if we're over one of the definition
1765 // which is lacking some annotations in the module
1766 if !self.is_hovering_definition_requiring_annotations {
1767 return vec![];
1768 }
1769
1770 let mut action = Vec::with_capacity(1);
1771 CodeActionBuilder::new("Annotate all top level definitions")
1772 .kind(CodeActionKind::RefactorRewrite)
1773 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1774 .preferred(false)
1775 .push_to(&mut action);
1776 action
1777 }
1778}
1779
1780impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> {
1781 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1782 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1783
1784 // We don't need to add an annotation if there already is one
1785 if constant.annotation.is_some() {
1786 return;
1787 }
1788
1789 // We're hovering definition which needs some annotations
1790 if overlaps(code_action_range, self.params.range) {
1791 self.is_hovering_definition_requiring_annotations = true;
1792 }
1793
1794 self.edits.insert(
1795 constant.name_location.end,
1796 format!(
1797 ": {}",
1798 // Create new printer to ignore type variables from other definitions
1799 Printer::new_without_type_variables(&self.module.ast.names)
1800 .print_type(&constant.type_)
1801 ),
1802 );
1803 }
1804
1805 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1806 // Don't annotate already annotated arguments
1807 let arguments_to_annotate = fun
1808 .arguments
1809 .iter()
1810 .filter(|argument| argument.annotation.is_none())
1811 .collect::<Vec<_>>();
1812 let needs_return_annotation = fun.return_annotation.is_none();
1813
1814 if arguments_to_annotate.is_empty() && !needs_return_annotation {
1815 return;
1816 }
1817
1818 let code_action_range = self.edits.src_span_to_lsp_range(fun.location);
1819 if overlaps(code_action_range, self.params.range) {
1820 self.is_hovering_definition_requiring_annotations = true;
1821 }
1822
1823 // Create new printer to ignore type variables from other definitions
1824 let mut printer = Printer::new_without_type_variables(&self.module.ast.names);
1825 collect_type_variables(&mut printer, fun);
1826
1827 // Annotate each argument separately
1828 for argument in arguments_to_annotate {
1829 self.edits.insert(
1830 argument.location.end,
1831 format!(": {}", printer.print_type(&argument.type_)),
1832 );
1833 }
1834
1835 // Annotate the return type if it isn't already annotated
1836 if needs_return_annotation {
1837 self.edits.insert(
1838 fun.location.end,
1839 format!(" -> {}", printer.print_type(&fun.return_type)),
1840 );
1841 }
1842 }
1843}
1844
1845struct TypeVariableCollector<'a, 'b> {
1846 printer: &'a mut Printer<'b>,
1847}
1848
1849/// Collect type variables defined within a function and register them for a
1850/// `Printer`
1851fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) {
1852 TypeVariableCollector { printer }.visit_typed_function(function);
1853}
1854
1855impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> {
1856 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) {
1857 // Register this type variable so that we don't duplicate names when
1858 // adding annotations.
1859 self.printer.register_type_variable(name.clone());
1860 }
1861}
1862
1863pub struct QualifiedConstructor<'a> {
1864 import: &'a Import<EcoString>,
1865 used_name: EcoString,
1866 constructor: EcoString,
1867 layer: ast::Layer,
1868}
1869
1870impl QualifiedConstructor<'_> {
1871 fn constructor_import(&self) -> String {
1872 if self.layer.is_value() {
1873 self.constructor.to_string()
1874 } else {
1875 format!("type {}", self.constructor)
1876 }
1877 }
1878}
1879
1880pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1881 module: &'a Module,
1882 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1883 params: &'a CodeActionParams,
1884 line_numbers: &'a LineNumbers,
1885 qualified_constructor: Option<QualifiedConstructor<'a>>,
1886}
1887
1888impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1889 fn new(
1890 module: &'a Module,
1891 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1892 params: &'a CodeActionParams,
1893 line_numbers: &'a LineNumbers,
1894 ) -> Self {
1895 Self {
1896 module,
1897 compiler,
1898 params,
1899 line_numbers,
1900 qualified_constructor: None,
1901 }
1902 }
1903
1904 fn get_module_import(
1905 &self,
1906 module_name: &EcoString,
1907 constructor: &EcoString,
1908 layer: ast::Layer,
1909 ) -> Option<&'a Import<EcoString>> {
1910 let mut matching_import = None;
1911
1912 for import in &self.module.ast.definitions.imports {
1913 if import.used_name().as_deref() == Some(module_name)
1914 && let Some(module) = self.compiler.get_module_interface(&import.module)
1915 {
1916 // If the import is the one we're referring to, we see if the
1917 // referred module exports the type/value we are trying to
1918 // unqualify: we don't want to offer the action indiscriminately if
1919 // it would generate invalid code!
1920 let module_exports_constructor = match layer {
1921 ast::Layer::Value => module.get_importable_value(constructor).is_some(),
1922 ast::Layer::Type => module.get_importable_type(constructor).is_some(),
1923 };
1924 if module_exports_constructor {
1925 matching_import = Some(import);
1926 }
1927 } else {
1928 // If the import refers to another module we still want to check
1929 // if in its unqualified import list there is a name that's equal
1930 // to the one we're trying to unqualify. In this case we can't
1931 // offer the action as it would generate invalid code.
1932 //
1933 // For example:
1934 // ```gleam
1935 // import wibble.{Some}
1936 // import option
1937 //
1938 // pub fn something() {
1939 // option.Some(1)
1940 // ^^^^ We can't unqualify this because `Some` is already
1941 // imported unqualified from the `wibble` module
1942 // }
1943 // ```
1944 //
1945 let imported = match layer {
1946 ast::Layer::Value => &import.unqualified_values,
1947 ast::Layer::Type => &import.unqualified_types,
1948 };
1949 let constructor_already_imported_by_other_module = imported
1950 .iter()
1951 .any(|value| value.used_name() == constructor);
1952
1953 if constructor_already_imported_by_other_module {
1954 return None;
1955 }
1956 }
1957 }
1958
1959 matching_import
1960 }
1961}
1962
1963impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> {
1964 fn visit_type_ast_constructor(
1965 &mut self,
1966 location: &'ast SrcSpan,
1967 name: &'ast TypeAstConstructorName,
1968 arguments: &'ast [ast::TypeAst],
1969 arguments_types: Option<Vec<Arc<Type>>>,
1970 ) {
1971 let range = src_span_to_lsp_range(*location, self.line_numbers);
1972 if within(self.params.range, range)
1973 && let Some(module_alias) = name.module_name()
1974 && let Some(name) = name.name()
1975 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type)
1976 {
1977 self.qualified_constructor = Some(QualifiedConstructor {
1978 import,
1979 used_name: module_alias.clone(),
1980 constructor: name.clone(),
1981 layer: ast::Layer::Type,
1982 });
1983 }
1984 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
1985 }
1986
1987 fn visit_typed_expr_module_select(
1988 &mut self,
1989 location: &'ast SrcSpan,
1990 field_start: &'ast u32,
1991 type_: &'ast Arc<Type>,
1992 label: &'ast EcoString,
1993 module_name: &'ast EcoString,
1994 module_alias: &'ast EcoString,
1995 constructor: &'ast ModuleValueConstructor,
1996 ) {
1997 // When hovering over a Record Value Constructor, we want to expand the source span to
1998 // include the module name:
1999 // option.Some
2000 // ↑
2001 // This allows us to offer a code action when hovering over the module name.
2002 let range = src_span_to_lsp_range(*location, self.line_numbers);
2003 if within(self.params.range, range)
2004 && let ModuleValueConstructor::Record {
2005 name: constructor_name,
2006 ..
2007 } = constructor
2008 && let Some(import) =
2009 self.get_module_import(module_alias, constructor_name, ast::Layer::Value)
2010 {
2011 self.qualified_constructor = Some(QualifiedConstructor {
2012 import,
2013 used_name: module_alias.clone(),
2014 constructor: constructor_name.clone(),
2015 layer: ast::Layer::Value,
2016 });
2017 }
2018 ast::visit::visit_typed_expr_module_select(
2019 self,
2020 location,
2021 field_start,
2022 type_,
2023 label,
2024 module_name,
2025 module_alias,
2026 constructor,
2027 );
2028 }
2029
2030 fn visit_typed_pattern_constructor(
2031 &mut self,
2032 location: &'ast SrcSpan,
2033 name_location: &'ast SrcSpan,
2034 name: &'ast EcoString,
2035 arguments: &'ast Vec<CallArg<TypedPattern>>,
2036 module: &'ast Option<(EcoString, SrcSpan)>,
2037 constructor: &'ast Inferred<type_::PatternConstructor>,
2038 spread: &'ast Option<SrcSpan>,
2039 type_: &'ast Arc<Type>,
2040 ) {
2041 let range = src_span_to_lsp_range(*location, self.line_numbers);
2042 if within(self.params.range, range)
2043 && let Some((module_alias, _)) = module
2044 && let Inferred::Known(_) = constructor
2045 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2046 {
2047 self.qualified_constructor = Some(QualifiedConstructor {
2048 import,
2049 used_name: module_alias.clone(),
2050 constructor: name.clone(),
2051 layer: ast::Layer::Value,
2052 });
2053 }
2054 ast::visit::visit_typed_pattern_constructor(
2055 self,
2056 location,
2057 name_location,
2058 name,
2059 arguments,
2060 module,
2061 constructor,
2062 spread,
2063 type_,
2064 );
2065 }
2066
2067 fn visit_typed_constant_record(
2068 &mut self,
2069 location: &'ast SrcSpan,
2070 module: &'ast Option<(EcoString, SrcSpan)>,
2071 name: &'ast EcoString,
2072 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2073 type_: &'ast Arc<Type>,
2074 field_map: &'ast Inferred<FieldMap>,
2075 record_constructor: &'ast Option<Box<ValueConstructor>>,
2076 ) {
2077 let range = src_span_to_lsp_range(*location, self.line_numbers);
2078 if within(self.params.range, range)
2079 && let Some((module_alias, _)) = module
2080 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2081 {
2082 self.qualified_constructor = Some(QualifiedConstructor {
2083 import,
2084 used_name: module_alias.clone(),
2085 constructor: name.clone(),
2086 layer: ast::Layer::Value,
2087 });
2088 }
2089 ast::visit::visit_typed_constant_record(
2090 self,
2091 location,
2092 module,
2093 name,
2094 arguments,
2095 type_,
2096 field_map,
2097 record_constructor,
2098 );
2099 }
2100
2101 fn visit_typed_constant_var(
2102 &mut self,
2103 location: &'ast SrcSpan,
2104 module: &'ast Option<(EcoString, SrcSpan)>,
2105 name: &'ast EcoString,
2106 constructor: &'ast Option<Box<ValueConstructor>>,
2107 type_: &'ast Arc<Type>,
2108 ) {
2109 let range = src_span_to_lsp_range(*location, self.line_numbers);
2110 if within(self.params.range, range)
2111 && let Some((module_alias, _)) = module
2112 && let Some(constructor) = constructor
2113 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant
2114 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2115 {
2116 self.qualified_constructor = Some(QualifiedConstructor {
2117 import,
2118 used_name: module_alias.clone(),
2119 constructor: name.clone(),
2120 layer: ast::Layer::Value,
2121 });
2122 }
2123 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2124 }
2125}
2126
2127pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
2128 module: &'a Module,
2129 params: &'a CodeActionParams,
2130 edits: TextEdits<'a>,
2131 qualified_constructor: QualifiedConstructor<'a>,
2132}
2133
2134impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
2135 pub fn new(
2136 module: &'a Module,
2137 params: &'a CodeActionParams,
2138 line_numbers: &'a LineNumbers,
2139 qualified_constructor: QualifiedConstructor<'a>,
2140 ) -> Self {
2141 Self {
2142 module,
2143 params,
2144 edits: TextEdits::new(line_numbers),
2145 qualified_constructor,
2146 }
2147 }
2148
2149 pub fn code_actions(mut self) -> Vec<CodeAction> {
2150 self.visit_typed_module(&self.module.ast);
2151 if self.edits.edits.is_empty() {
2152 return vec![];
2153 }
2154 self.edit_import();
2155 let mut action = Vec::with_capacity(1);
2156 CodeActionBuilder::new(&format!(
2157 "Unqualify {}.{}",
2158 self.qualified_constructor.used_name, self.qualified_constructor.constructor
2159 ))
2160 .kind(CodeActionKind::Refactor)
2161 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2162 .preferred(false)
2163 .push_to(&mut action);
2164 action
2165 }
2166
2167 fn remove_module_qualifier(&mut self, location: SrcSpan) {
2168 self.edits.delete(SrcSpan {
2169 start: location.start,
2170 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
2171 });
2172 }
2173
2174 fn edit_import(&mut self) {
2175 let QualifiedConstructor {
2176 constructor,
2177 layer,
2178 import,
2179 ..
2180 } = &self.qualified_constructor;
2181 let is_imported = if layer.is_value() {
2182 import
2183 .unqualified_values
2184 .iter()
2185 .any(|value| value.used_name() == constructor)
2186 } else {
2187 import
2188 .unqualified_types
2189 .iter()
2190 .any(|type_| type_.used_name() == constructor)
2191 };
2192 if is_imported {
2193 return;
2194 }
2195 let (insert_pos, new_text) = edits::insert_unqualified_import(
2196 import,
2197 &self.module.code,
2198 self.qualified_constructor.constructor_import(),
2199 );
2200 let span = SrcSpan::new(insert_pos, insert_pos);
2201 self.edits.replace(span, new_text);
2202 }
2203}
2204
2205impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
2206 fn visit_type_ast_constructor(
2207 &mut self,
2208 location: &'ast SrcSpan,
2209 name: &'ast TypeAstConstructorName,
2210 arguments: &'ast [ast::TypeAst],
2211 arguments_types: Option<Vec<Arc<Type>>>,
2212 ) {
2213 if let Some(module_name) = name.module_name()
2214 && let Some(name) = name.name()
2215 {
2216 let QualifiedConstructor {
2217 used_name,
2218 constructor,
2219 layer,
2220 ..
2221 } = &self.qualified_constructor;
2222
2223 if !layer.is_value() && used_name == module_name && name == constructor {
2224 self.remove_module_qualifier(*location);
2225 }
2226 }
2227 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2228 }
2229
2230 fn visit_typed_expr_module_select(
2231 &mut self,
2232 location: &'ast SrcSpan,
2233 field_start: &'ast u32,
2234 type_: &'ast Arc<Type>,
2235 label: &'ast EcoString,
2236 module_name: &'ast EcoString,
2237 module_alias: &'ast EcoString,
2238 constructor: &'ast ModuleValueConstructor,
2239 ) {
2240 if let ModuleValueConstructor::Record { name, .. } = constructor {
2241 let QualifiedConstructor {
2242 used_name,
2243 constructor,
2244 layer,
2245 ..
2246 } = &self.qualified_constructor;
2247
2248 if layer.is_value() && used_name == module_alias && name == constructor {
2249 self.remove_module_qualifier(*location);
2250 }
2251 }
2252 ast::visit::visit_typed_expr_module_select(
2253 self,
2254 location,
2255 field_start,
2256 type_,
2257 label,
2258 module_name,
2259 module_alias,
2260 constructor,
2261 );
2262 }
2263
2264 fn visit_typed_pattern_constructor(
2265 &mut self,
2266 location: &'ast SrcSpan,
2267 name_location: &'ast SrcSpan,
2268 name: &'ast EcoString,
2269 arguments: &'ast Vec<CallArg<TypedPattern>>,
2270 module: &'ast Option<(EcoString, SrcSpan)>,
2271 constructor: &'ast Inferred<type_::PatternConstructor>,
2272 spread: &'ast Option<SrcSpan>,
2273 type_: &'ast Arc<Type>,
2274 ) {
2275 if let Some((module_alias, _)) = module
2276 && let Inferred::Known(_) = constructor
2277 {
2278 let QualifiedConstructor {
2279 used_name,
2280 constructor,
2281 layer,
2282 ..
2283 } = &self.qualified_constructor;
2284
2285 if layer.is_value() && used_name == module_alias && name == constructor {
2286 self.remove_module_qualifier(*location);
2287 }
2288 }
2289 ast::visit::visit_typed_pattern_constructor(
2290 self,
2291 location,
2292 name_location,
2293 name,
2294 arguments,
2295 module,
2296 constructor,
2297 spread,
2298 type_,
2299 );
2300 }
2301
2302 fn visit_typed_constant_record(
2303 &mut self,
2304 location: &'ast SrcSpan,
2305 module: &'ast Option<(EcoString, SrcSpan)>,
2306 name: &'ast EcoString,
2307 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2308 type_: &'ast Arc<Type>,
2309 field_map: &'ast Inferred<FieldMap>,
2310 record_constructor: &'ast Option<Box<ValueConstructor>>,
2311 ) {
2312 if let Some((module_alias, _)) = module {
2313 let QualifiedConstructor {
2314 used_name,
2315 constructor,
2316 layer,
2317 ..
2318 } = &self.qualified_constructor;
2319
2320 if layer.is_value() && used_name == module_alias && name == constructor {
2321 self.remove_module_qualifier(*location);
2322 }
2323 }
2324 ast::visit::visit_typed_constant_record(
2325 self,
2326 location,
2327 module,
2328 name,
2329 arguments,
2330 type_,
2331 field_map,
2332 record_constructor,
2333 );
2334 }
2335
2336 fn visit_typed_constant_var(
2337 &mut self,
2338 location: &'ast SrcSpan,
2339 module: &'ast Option<(EcoString, SrcSpan)>,
2340 name: &'ast EcoString,
2341 constructor: &'ast Option<Box<ValueConstructor>>,
2342 type_: &'ast Arc<Type>,
2343 ) {
2344 if let Some((module_alias, _)) = module {
2345 let QualifiedConstructor {
2346 used_name,
2347 constructor: wanted_constructor,
2348 layer,
2349 ..
2350 } = &self.qualified_constructor;
2351
2352 if layer.is_value() && used_name == module_alias && name == wanted_constructor {
2353 self.remove_module_qualifier(*location);
2354 }
2355 }
2356 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2357 }
2358}
2359
2360pub fn code_action_convert_qualified_constructor_to_unqualified<IO>(
2361 module: &Module,
2362 compiler: &LspProjectCompiler<FileSystemProxy<IO>>,
2363 line_numbers: &LineNumbers,
2364 params: &CodeActionParams,
2365 actions: &mut Vec<CodeAction>,
2366) {
2367 let mut first_pass =
2368 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers);
2369 first_pass.visit_typed_module(&module.ast);
2370 let Some(qualified_constructor) = first_pass.qualified_constructor else {
2371 return;
2372 };
2373 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
2374 module,
2375 params,
2376 line_numbers,
2377 qualified_constructor,
2378 );
2379 let new_actions = second_pass.code_actions();
2380 actions.extend(new_actions);
2381}
2382
2383struct UnqualifiedConstructor<'a> {
2384 module_name: EcoString,
2385 constructor: &'a ast::UnqualifiedImport,
2386 layer: ast::Layer,
2387}
2388
2389struct UnqualifiedToQualifiedImportFirstPass<'a> {
2390 module: &'a Module,
2391 params: &'a CodeActionParams,
2392 line_numbers: &'a LineNumbers,
2393 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
2394}
2395
2396impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
2397 fn new(
2398 module: &'a Module,
2399 params: &'a CodeActionParams,
2400 line_numbers: &'a LineNumbers,
2401 ) -> Self {
2402 Self {
2403 module,
2404 params,
2405 line_numbers,
2406 unqualified_constructor: None,
2407 }
2408 }
2409
2410 fn get_module_import_from_value_constructor(
2411 &mut self,
2412 module_name: &EcoString,
2413 constructor_name: &EcoString,
2414 ) {
2415 self.unqualified_constructor = self
2416 .module
2417 .ast
2418 .definitions
2419 .imports
2420 .iter()
2421 .filter(|import| import.module == *module_name)
2422 .find_map(|import| {
2423 import
2424 .unqualified_values
2425 .iter()
2426 .find(|value| value.used_name() == constructor_name)
2427 .and_then(|value| {
2428 Some(UnqualifiedConstructor {
2429 constructor: value,
2430 module_name: import.used_name()?,
2431 layer: ast::Layer::Value,
2432 })
2433 })
2434 });
2435 }
2436
2437 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
2438 self.unqualified_constructor =
2439 self.module
2440 .ast
2441 .definitions
2442 .imports
2443 .iter()
2444 .find_map(|import| {
2445 if let Some(ty) = import
2446 .unqualified_types
2447 .iter()
2448 .find(|ty| ty.used_name() == constructor_name)
2449 {
2450 return Some(UnqualifiedConstructor {
2451 constructor: ty,
2452 module_name: import.used_name()?,
2453 layer: ast::Layer::Type,
2454 });
2455 }
2456 None
2457 });
2458 }
2459}
2460
2461impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
2462 fn visit_type_ast_constructor(
2463 &mut self,
2464 location: &'ast SrcSpan,
2465 name: &'ast TypeAstConstructorName,
2466 arguments: &'ast [ast::TypeAst],
2467 arguments_types: Option<Vec<Arc<Type>>>,
2468 ) {
2469 if !name.is_qualified()
2470 && let Some(name) = name.name()
2471 && within(
2472 self.params.range,
2473 src_span_to_lsp_range(*location, self.line_numbers),
2474 )
2475 {
2476 self.get_module_import_from_type_constructor(name);
2477 }
2478
2479 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2480 }
2481
2482 fn visit_typed_expr_var(
2483 &mut self,
2484 location: &'ast SrcSpan,
2485 constructor: &'ast ValueConstructor,
2486 name: &'ast EcoString,
2487 ) {
2488 let range = src_span_to_lsp_range(*location, self.line_numbers);
2489 if within(self.params.range, range)
2490 && let Some(module_name) = match &constructor.variant {
2491 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2492 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2493 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2494
2495 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2496 }
2497 {
2498 self.get_module_import_from_value_constructor(module_name, name);
2499 }
2500 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2501 }
2502
2503 fn visit_typed_pattern_constructor(
2504 &mut self,
2505 location: &'ast SrcSpan,
2506 name_location: &'ast SrcSpan,
2507 name: &'ast EcoString,
2508 arguments: &'ast Vec<CallArg<TypedPattern>>,
2509 module: &'ast Option<(EcoString, SrcSpan)>,
2510 constructor: &'ast Inferred<type_::PatternConstructor>,
2511 spread: &'ast Option<SrcSpan>,
2512 type_: &'ast Arc<Type>,
2513 ) {
2514 if module.is_none()
2515 && within(
2516 self.params.range,
2517 src_span_to_lsp_range(*location, self.line_numbers),
2518 )
2519 && let Inferred::Known(constructor) = constructor
2520 {
2521 self.get_module_import_from_value_constructor(&constructor.module, name);
2522 }
2523
2524 ast::visit::visit_typed_pattern_constructor(
2525 self,
2526 location,
2527 name_location,
2528 name,
2529 arguments,
2530 module,
2531 constructor,
2532 spread,
2533 type_,
2534 );
2535 }
2536
2537 fn visit_typed_constant_record(
2538 &mut self,
2539 location: &'ast SrcSpan,
2540 module: &'ast Option<(EcoString, SrcSpan)>,
2541 name: &'ast EcoString,
2542 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2543 type_: &'ast Arc<Type>,
2544 field_map: &'ast Inferred<FieldMap>,
2545 record_constructor: &'ast Option<Box<ValueConstructor>>,
2546 ) {
2547 if module.is_none()
2548 && within(
2549 self.params.range,
2550 src_span_to_lsp_range(*location, self.line_numbers),
2551 )
2552 && let Some(record_constructor) = record_constructor
2553 && let Some(module_name) = match &record_constructor.variant {
2554 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2555 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2556 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2557
2558 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2559 }
2560 {
2561 self.get_module_import_from_value_constructor(module_name, name);
2562 }
2563 ast::visit::visit_typed_constant_record(
2564 self,
2565 location,
2566 module,
2567 name,
2568 arguments,
2569 type_,
2570 field_map,
2571 record_constructor,
2572 );
2573 }
2574
2575 fn visit_typed_constant_var(
2576 &mut self,
2577 location: &'ast SrcSpan,
2578 module: &'ast Option<(EcoString, SrcSpan)>,
2579 name: &'ast EcoString,
2580 constructor: &'ast Option<Box<ValueConstructor>>,
2581 type_: &'ast Arc<Type>,
2582 ) {
2583 if module.is_none()
2584 && within(
2585 self.params.range,
2586 src_span_to_lsp_range(*location, self.line_numbers),
2587 )
2588 && let Some(constructor) = constructor
2589 && let Some(module_name) = match &constructor.variant {
2590 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2591 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2592 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2593
2594 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2595 }
2596 {
2597 self.get_module_import_from_value_constructor(module_name, name);
2598 }
2599 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2600 }
2601}
2602
2603struct UnqualifiedToQualifiedImportSecondPass<'a> {
2604 module: &'a Module,
2605 params: &'a CodeActionParams,
2606 edits: TextEdits<'a>,
2607 unqualified_constructor: UnqualifiedConstructor<'a>,
2608}
2609
2610impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2611 pub fn new(
2612 module: &'a Module,
2613 params: &'a CodeActionParams,
2614 line_numbers: &'a LineNumbers,
2615 unqualified_constructor: UnqualifiedConstructor<'a>,
2616 ) -> Self {
2617 Self {
2618 module,
2619 params,
2620 edits: TextEdits::new(line_numbers),
2621 unqualified_constructor,
2622 }
2623 }
2624
2625 fn add_module_qualifier(&mut self, location: SrcSpan) {
2626 let src_span = SrcSpan::new(
2627 location.start,
2628 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
2629 );
2630
2631 self.edits.replace(
2632 src_span,
2633 format!(
2634 "{}.{}",
2635 self.unqualified_constructor.module_name,
2636 self.unqualified_constructor.constructor.name
2637 ),
2638 );
2639 }
2640
2641 pub fn code_actions(mut self) -> Vec<CodeAction> {
2642 self.visit_typed_module(&self.module.ast);
2643 if self.edits.edits.is_empty() {
2644 return vec![];
2645 }
2646 self.edit_import();
2647 let mut action = Vec::with_capacity(1);
2648 let UnqualifiedConstructor {
2649 module_name,
2650 constructor,
2651 ..
2652 } = self.unqualified_constructor;
2653 CodeActionBuilder::new(&format!(
2654 "Qualify {} as {}.{}",
2655 constructor.used_name(),
2656 module_name,
2657 constructor.name,
2658 ))
2659 .kind(CodeActionKind::Refactor)
2660 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2661 .preferred(false)
2662 .push_to(&mut action);
2663 action
2664 }
2665
2666 fn edit_import(&mut self) {
2667 let UnqualifiedConstructor {
2668 constructor:
2669 ast::UnqualifiedImport {
2670 location: constructor_import_span,
2671 ..
2672 },
2673 ..
2674 } = self.unqualified_constructor;
2675
2676 let mut last_char_pos = constructor_import_span.end as usize;
2677 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2678 last_char_pos += 1;
2679 }
2680 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2681 last_char_pos += 1;
2682 }
2683 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2684 last_char_pos += 1;
2685 }
2686
2687 self.edits.delete(SrcSpan::new(
2688 constructor_import_span.start,
2689 last_char_pos as u32,
2690 ));
2691 }
2692}
2693
2694impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2695 fn visit_type_ast_constructor(
2696 &mut self,
2697 location: &'ast SrcSpan,
2698 name: &'ast TypeAstConstructorName,
2699 arguments: &'ast [ast::TypeAst],
2700 arguments_types: Option<Vec<Arc<Type>>>,
2701 ) {
2702 if !name.is_qualified()
2703 && let Some(name) = name.name()
2704 {
2705 let UnqualifiedConstructor {
2706 constructor, layer, ..
2707 } = &self.unqualified_constructor;
2708 if !layer.is_value() && constructor.used_name() == name {
2709 self.add_module_qualifier(*location);
2710 }
2711 }
2712 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2713 }
2714
2715 fn visit_typed_expr_var(
2716 &mut self,
2717 location: &'ast SrcSpan,
2718 constructor: &'ast ValueConstructor,
2719 name: &'ast EcoString,
2720 ) {
2721 let UnqualifiedConstructor {
2722 constructor: wanted_constructor,
2723 layer,
2724 ..
2725 } = &self.unqualified_constructor;
2726
2727 if layer.is_value()
2728 && wanted_constructor.used_name() == name
2729 && !constructor.is_local_variable()
2730 {
2731 self.add_module_qualifier(*location);
2732 }
2733 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2734 }
2735
2736 fn visit_typed_pattern_constructor(
2737 &mut self,
2738 location: &'ast SrcSpan,
2739 name_location: &'ast SrcSpan,
2740 name: &'ast EcoString,
2741 arguments: &'ast Vec<CallArg<TypedPattern>>,
2742 module: &'ast Option<(EcoString, SrcSpan)>,
2743 constructor: &'ast Inferred<type_::PatternConstructor>,
2744 spread: &'ast Option<SrcSpan>,
2745 type_: &'ast Arc<Type>,
2746 ) {
2747 if module.is_none() {
2748 let UnqualifiedConstructor {
2749 constructor: wanted_constructor,
2750 layer,
2751 ..
2752 } = &self.unqualified_constructor;
2753 if layer.is_value() && wanted_constructor.used_name() == name {
2754 self.add_module_qualifier(*location);
2755 }
2756 }
2757 ast::visit::visit_typed_pattern_constructor(
2758 self,
2759 location,
2760 name_location,
2761 name,
2762 arguments,
2763 module,
2764 constructor,
2765 spread,
2766 type_,
2767 );
2768 }
2769
2770 fn visit_typed_constant_record(
2771 &mut self,
2772 location: &'ast SrcSpan,
2773 module: &'ast Option<(EcoString, SrcSpan)>,
2774 name: &'ast EcoString,
2775 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2776 type_: &'ast Arc<Type>,
2777 field_map: &'ast Inferred<FieldMap>,
2778 record_constructor: &'ast Option<Box<ValueConstructor>>,
2779 ) {
2780 if module.is_none() {
2781 let UnqualifiedConstructor {
2782 constructor: wanted_constructor,
2783 layer,
2784 ..
2785 } = &self.unqualified_constructor;
2786 if layer.is_value() && wanted_constructor.used_name() == name {
2787 self.add_module_qualifier(*location);
2788 }
2789 }
2790 ast::visit::visit_typed_constant_record(
2791 self,
2792 location,
2793 module,
2794 name,
2795 arguments,
2796 type_,
2797 field_map,
2798 record_constructor,
2799 );
2800 }
2801
2802 fn visit_typed_constant_var(
2803 &mut self,
2804 location: &'ast SrcSpan,
2805 module: &'ast Option<(EcoString, SrcSpan)>,
2806 name: &'ast EcoString,
2807 constructor: &'ast Option<Box<ValueConstructor>>,
2808 type_: &'ast Arc<Type>,
2809 ) {
2810 if module.is_none() {
2811 let UnqualifiedConstructor {
2812 constructor: wanted_constructor,
2813 layer,
2814 ..
2815 } = &self.unqualified_constructor;
2816 if layer.is_value() && wanted_constructor.used_name() == name {
2817 self.add_module_qualifier(*location);
2818 }
2819 }
2820 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2821 }
2822}
2823
2824pub fn code_action_convert_unqualified_constructor_to_qualified(
2825 module: &Module,
2826 line_numbers: &LineNumbers,
2827 params: &CodeActionParams,
2828 actions: &mut Vec<CodeAction>,
2829) {
2830 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2831 first_pass.visit_typed_module(&module.ast);
2832 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2833 return;
2834 };
2835 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2836 module,
2837 params,
2838 line_numbers,
2839 unqualified_constructor,
2840 );
2841 let new_actions = second_pass.code_actions();
2842 actions.extend(new_actions);
2843}
2844
2845/// Builder for code action to apply the convert from use action, turning a use
2846/// expression into a regular function call.
2847///
2848pub struct ConvertFromUse<'a> {
2849 module: &'a Module,
2850 params: &'a CodeActionParams,
2851 edits: TextEdits<'a>,
2852 selected_use: Option<&'a TypedUse>,
2853}
2854
2855impl<'a> ConvertFromUse<'a> {
2856 pub fn new(
2857 module: &'a Module,
2858 line_numbers: &'a LineNumbers,
2859 params: &'a CodeActionParams,
2860 ) -> Self {
2861 Self {
2862 module,
2863 params,
2864 edits: TextEdits::new(line_numbers),
2865 selected_use: None,
2866 }
2867 }
2868
2869 pub fn code_actions(mut self) -> Vec<CodeAction> {
2870 self.visit_typed_module(&self.module.ast);
2871
2872 let Some(use_) = self.selected_use else {
2873 return vec![];
2874 };
2875
2876 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else {
2877 return vec![];
2878 };
2879
2880 // If the use callback we're desugaring is using labels, that means we
2881 // have to add the last argument's label when writing the callback;
2882 // otherwise, it would result in invalid code.
2883 //
2884 // use acc, item <- list.fold(over: list, from: 1)
2885 // todo
2886 //
2887 // Needs to be rewritten as:
2888 //
2889 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2890 // ^^^^^ We cannot forget to add this label back!
2891 //
2892 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) {
2893 fun.field_map()
2894 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned())
2895 .map(|label| eco_format!("{label}: "))
2896 .unwrap_or(EcoString::from(""))
2897 } else {
2898 EcoString::from("")
2899 };
2900
2901 // The use callback is not necessarily the last argument. If you have
2902 // the following function: `wibble(a a, b b) { todo }`
2903 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2904 // is going to be the use callback, not the last one!
2905 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback());
2906 let Some(CallArg {
2907 implicit: Some(ImplicitCallArgOrigin::Use),
2908 value: TypedExpr::Fn { body, type_, .. },
2909 ..
2910 }) = use_callback
2911 else {
2912 return vec![];
2913 };
2914
2915 // If there's arguments on the left hand side of the function we extract
2916 // those so we can paste them back as the anonymous function arguments.
2917 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2918 let assignments_range =
2919 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2920 self.module
2921 .code
2922 .get(assignments_range)
2923 .expect("use assignments")
2924 } else {
2925 ""
2926 };
2927
2928 // We first delete everything on the left hand side of use and the use
2929 // arrow.
2930 self.edits.delete(SrcSpan {
2931 start: use_.location.start,
2932 end: use_.right_hand_side_location.start,
2933 });
2934
2935 let use_line_end = use_.right_hand_side_location.end;
2936 let use_rhs_function_has_some_explicit_arguments = arguments
2937 .iter()
2938 .filter(|argument| !argument.is_use_implicit_callback())
2939 .peekable()
2940 .peek()
2941 .is_some();
2942
2943 let use_rhs_function_ends_with_closed_parentheses = self
2944 .module
2945 .code
2946 .get(use_line_end as usize - 1..use_line_end as usize)
2947 == Some(")");
2948
2949 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit());
2950 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2951
2952 // This is the piece of code between the end of the last argument and
2953 // the end of the use_expression:
2954 //
2955 // use <- wibble(a, b, )
2956 // ^^^^^ This piece right here, from `,` included
2957 // up to `)` excluded.
2958 //
2959 let text_after_last_argument = self
2960 .module
2961 .code
2962 .get(last_arg_end as usize..use_line_end as usize - 1);
2963 let use_rhs_has_comma_after_last_argument =
2964 text_after_last_argument.is_some_and(|code| code.contains(','));
2965 let needs_space_before_callback =
2966 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2967
2968 if use_rhs_function_ends_with_closed_parentheses {
2969 // If the function on the right hand side of use ends with a closed
2970 // parentheses then we have to remove it and add it later at the end
2971 // of the anonymous function we're inserting.
2972 //
2973 // use <- wibble()
2974 // ^ To add the fn() we need to first remove this
2975 //
2976 // So here we write over the last closed parentheses to remove it.
2977 let callback_start = format!("{callback_label}fn({assignments}) {{");
2978 self.edits.replace(
2979 SrcSpan {
2980 start: use_line_end - 1,
2981 end: use_line_end,
2982 },
2983 // If the function on the rhs of use has other orguments besides
2984 // the implicit fn expression then we need to put a comma after
2985 // the last argument.
2986 if use_rhs_function_has_some_explicit_arguments
2987 && !use_rhs_has_comma_after_last_argument
2988 {
2989 format!(", {callback_start}")
2990 } else if needs_space_before_callback {
2991 format!(" {callback_start}")
2992 } else {
2993 callback_start
2994 },
2995 );
2996 } else {
2997 // On the other hand, if the function on the right hand side doesn't
2998 // end with a closed parenthese then we have to manually add it.
2999 //
3000 // use <- wibble
3001 // ^ No parentheses
3002 //
3003 self.edits
3004 .insert(use_line_end, format!("(fn({assignments}) {{"));
3005 }
3006
3007 // Then we have to increase indentation for all the lines of the use
3008 // body.
3009 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
3010 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
3011
3012 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
3013 self.edits.edits.push(TextEdit {
3014 range: Range {
3015 start: Position { line, character: 0 },
3016 end: Position { line, character: 0 },
3017 },
3018 new_text: " ".to_string(),
3019 });
3020 }
3021
3022 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
3023 self.edits.insert(
3024 use_.call.location().end,
3025 format!("\n{final_line_indentation}}})"),
3026 );
3027
3028 let mut action = Vec::with_capacity(1);
3029 CodeActionBuilder::new("Convert from `use`")
3030 .kind(CodeActionKind::RefactorRewrite)
3031 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3032 .preferred(false)
3033 .push_to(&mut action);
3034 action
3035 }
3036}
3037
3038impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
3039 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3040 let use_range = self.edits.src_span_to_lsp_range(use_.location);
3041
3042 // If the use expression is using patterns that are not just variable
3043 // assignments then we can't automatically rewrite it as it would result
3044 // in a syntax error as we can't pattern match in an anonymous function
3045 // head.
3046 // At the same time we can't safely add bindings inside the anonymous
3047 // function body by picking placeholder names as we'd risk shadowing
3048 // variables coming from the outer scope.
3049 // So we just skip those use expressions we can't safely rewrite!
3050 if within(self.params.range, use_range)
3051 && use_
3052 .assignments
3053 .iter()
3054 .all(|assignment| assignment.pattern.is_variable())
3055 {
3056 self.selected_use = Some(use_);
3057 }
3058
3059 // We still want to visit the use expression so that we always end up
3060 // picking the innermost, most relevant use under the cursor.
3061 self.visit_typed_expr(&use_.call);
3062 }
3063}
3064
3065/// Builder for code action to apply the convert to use action.
3066///
3067pub struct ConvertToUse<'a> {
3068 module: &'a Module,
3069 params: &'a CodeActionParams,
3070 edits: TextEdits<'a>,
3071 selected_call: Option<CallLocations>,
3072}
3073
3074/// All the locations we'll need to transform a function call into a use
3075/// expression.
3076///
3077struct CallLocations {
3078 call_span: SrcSpan,
3079 called_function_span: SrcSpan,
3080 callback_arguments_span: Option<SrcSpan>,
3081 arg_before_callback_span: Option<SrcSpan>,
3082 callback_body_span: SrcSpan,
3083}
3084
3085impl<'a> ConvertToUse<'a> {
3086 pub fn new(
3087 module: &'a Module,
3088 line_numbers: &'a LineNumbers,
3089 params: &'a CodeActionParams,
3090 ) -> Self {
3091 Self {
3092 module,
3093 params,
3094 edits: TextEdits::new(line_numbers),
3095 selected_call: None,
3096 }
3097 }
3098
3099 pub fn code_actions(mut self) -> Vec<CodeAction> {
3100 self.visit_typed_module(&self.module.ast);
3101
3102 let Some(CallLocations {
3103 call_span,
3104 called_function_span,
3105 callback_arguments_span,
3106 arg_before_callback_span,
3107 callback_body_span,
3108 }) = self.selected_call
3109 else {
3110 return vec![];
3111 };
3112
3113 // This is the nesting level of the `use` keyword we've inserted, we
3114 // want to move the entire body of the anonymous function to this level.
3115 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
3116 let indentation = " ".repeat(use_nesting_level as usize);
3117
3118 // First we move the callback arguments to the left hand side of the
3119 // call and add the `use` keyword.
3120 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span {
3121 let arguments_start = arguments_location.start as usize;
3122 let arguments_end = arguments_location.end as usize;
3123 let arguments_text = self
3124 .module
3125 .code
3126 .get(arguments_start..arguments_end)
3127 .expect("fn args");
3128 format!("use {arguments_text} <- ")
3129 } else {
3130 "use <- ".into()
3131 };
3132
3133 self.edits.insert(call_span.start, left_hand_side_text);
3134
3135 match arg_before_callback_span {
3136 // If the function call has no other arguments besides the callback then
3137 // we just have to remove the `fn(...) {` part.
3138 //
3139 // wibble(fn(...) { ... })
3140 // ^^^^^^^^^^ This goes from the end of the called function
3141 // To the start of the first thing in the anonymous
3142 // function's body.
3143 //
3144 None => self.edits.replace(
3145 SrcSpan::new(called_function_span.end, callback_body_span.start),
3146 format!("\n{indentation}"),
3147 ),
3148 // If it has other arguments we'll have to remove those and add a closed
3149 // parentheses too:
3150 //
3151 // wibble(1, 2, fn(...) { ... })
3152 // ^^^^^^^^^^^ We have to replace this with a `)`, it
3153 // goes from the end of the second-to-last
3154 // argument to the start of the first thing
3155 // in the anonymous function's body.
3156 //
3157 Some(arg_before_callback) => self.edits.replace(
3158 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
3159 format!(")\n{indentation}"),
3160 ),
3161 }
3162
3163 // Then we have to remove two spaces of indentation from each line of
3164 // the callback function's body.
3165 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
3166 for line in body_range.start.line + 1..=body_range.end.line {
3167 self.edits.delete_range(Range::new(
3168 Position { line, character: 0 },
3169 Position { line, character: 2 },
3170 ));
3171 }
3172
3173 // Then we have to remove the anonymous fn closing `}` and the call's
3174 // closing `)`.
3175 self.edits
3176 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
3177
3178 let mut action = Vec::with_capacity(1);
3179 CodeActionBuilder::new("Convert to `use`")
3180 .kind(CodeActionKind::RefactorRewrite)
3181 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3182 .preferred(false)
3183 .push_to(&mut action);
3184 action
3185 }
3186}
3187
3188impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
3189 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3190 // The cursor has to be inside the last statement of the function to
3191 // offer the code action.
3192 if let Some(last) = &fun.body.last()
3193 && within(
3194 self.params.range,
3195 self.edits.src_span_to_lsp_range(last.location()),
3196 )
3197 && let Some(call_data) = turn_statement_into_use(last)
3198 {
3199 self.selected_call = Some(call_data);
3200 }
3201
3202 ast::visit::visit_typed_function(self, fun);
3203 }
3204
3205 fn visit_typed_expr_fn(
3206 &mut self,
3207 location: &'ast SrcSpan,
3208 type_: &'ast Arc<Type>,
3209 kind: &'ast FunctionLiteralKind,
3210 arguments: &'ast [TypedArg],
3211 body: &'ast Vec1<TypedStatement>,
3212 return_annotation: &'ast Option<ast::TypeAst>,
3213 ) {
3214 // The cursor has to be inside the last statement of the body to
3215 // offer the code action.
3216 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
3217 if within(self.params.range, last_statement_range)
3218 && let Some(call_data) = turn_statement_into_use(body.last())
3219 {
3220 self.selected_call = Some(call_data);
3221 }
3222
3223 ast::visit::visit_typed_expr_fn(
3224 self,
3225 location,
3226 type_,
3227 kind,
3228 arguments,
3229 body,
3230 return_annotation,
3231 );
3232 }
3233
3234 fn visit_typed_expr_block(
3235 &mut self,
3236 location: &'ast SrcSpan,
3237 statements: &'ast [TypedStatement],
3238 ) {
3239 let Some(last_statement) = statements.last() else {
3240 return;
3241 };
3242
3243 // The cursor has to be inside the last statement of the block to offer
3244 // the code action.
3245 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
3246 if within(self.params.range, statement_range) {
3247 // Only the last statement of a block can be turned into a use!
3248 if let Some(selected_call) = turn_statement_into_use(last_statement) {
3249 self.selected_call = Some(selected_call);
3250 }
3251 }
3252
3253 ast::visit::visit_typed_expr_block(self, location, statements);
3254 }
3255}
3256
3257fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
3258 match statement {
3259 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
3260 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
3261 }
3262}
3263
3264fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
3265 let TypedExpr::Call {
3266 arguments,
3267 location: call_span,
3268 fun: called_function,
3269 ..
3270 } = expr
3271 else {
3272 return None;
3273 };
3274
3275 // The function arguments in the ast are reordered using function's field map.
3276 // This means that in the `args` array they might not appear in the same order
3277 // in which they are written by the user. Since the rest of the code relies
3278 // on their order in the written code we first have to sort them by their
3279 // source position.
3280 let arguments = arguments
3281 .iter()
3282 .sorted_by_key(|argument| argument.location.start)
3283 .collect_vec();
3284
3285 let CallArg {
3286 value: last_arg,
3287 implicit: None,
3288 ..
3289 } = arguments.last()?
3290 else {
3291 return None;
3292 };
3293
3294 let TypedExpr::Fn {
3295 arguments: callback_arguments,
3296 body,
3297 ..
3298 } = last_arg
3299 else {
3300 return None;
3301 };
3302
3303 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) {
3304 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
3305 _ => None,
3306 };
3307
3308 let arg_before_callback_span = if arguments.len() >= 2 {
3309 arguments
3310 .get(arguments.len() - 2)
3311 .map(|call_arg| call_arg.location)
3312 } else {
3313 None
3314 };
3315
3316 let callback_body_span = body.first().location().merge(&body.last().last_location());
3317
3318 Some(CallLocations {
3319 call_span: *call_span,
3320 called_function_span: called_function.location(),
3321 callback_arguments_span,
3322 arg_before_callback_span,
3323 callback_body_span,
3324 })
3325}
3326
3327/// Builder for code action to extract expression into a variable.
3328/// The action will wrap the expression in a block if needed in the appropriate scope.
3329///
3330/// For using the code action on the following selection:
3331///
3332/// ```gleam
3333/// fn void() {
3334/// case result {
3335/// Ok(value) -> 2 * value + 1
3336/// // ^^^^^^^^^
3337/// Error(_) -> panic
3338/// }
3339/// }
3340/// ```
3341///
3342/// Will result:
3343///
3344/// ```gleam
3345/// fn void() {
3346/// case result {
3347/// Ok(value) -> {
3348/// let int = 2 * value
3349/// int + 1
3350/// }
3351/// Error(_) -> panic
3352/// }
3353/// }
3354/// ```
3355pub struct ExtractVariable<'a> {
3356 module: &'a Module,
3357 params: &'a CodeActionParams,
3358 edits: TextEdits<'a>,
3359 position: Option<ExtractVariablePosition>,
3360 selected_expression: Option<ExtractedToVariable>,
3361 statement_before_selected_expression: Option<SrcSpan>,
3362 latest_statement: Option<SrcSpan>,
3363 to_be_wrapped: bool,
3364 name_generator: NameGenerator,
3365}
3366
3367pub enum ExtractedToVariable {
3368 Expression { location: SrcSpan, name: EcoString },
3369 StartOfPipeline { location: SrcSpan, name: EcoString },
3370}
3371
3372/// The Position of the selected code
3373#[derive(PartialEq, Eq, Copy, Clone, Debug)]
3374enum ExtractVariablePosition {
3375 InsideCaptureBody,
3376 /// Full statements (i.e. assignments, `use`s, and simple expressions).
3377 TopLevelStatement,
3378 /// The call on the right hand side of a pipe `|>`.
3379 PipelineCall,
3380 /// The right hand side of the `->` in a case expression.
3381 InsideCaseClause,
3382 /// A call argument. This can also be a `use` callback.
3383 CallArg,
3384}
3385
3386impl<'a> ExtractVariable<'a> {
3387 pub fn new(
3388 module: &'a Module,
3389 line_numbers: &'a LineNumbers,
3390 params: &'a CodeActionParams,
3391 ) -> Self {
3392 Self {
3393 module,
3394 params,
3395 edits: TextEdits::new(line_numbers),
3396 position: None,
3397 selected_expression: None,
3398 statement_before_selected_expression: None,
3399 latest_statement: None,
3400 to_be_wrapped: false,
3401 name_generator: NameGenerator::new(),
3402 }
3403 }
3404
3405 pub fn code_actions(mut self) -> Vec<CodeAction> {
3406 self.visit_typed_module(&self.module.ast);
3407
3408 let (Some(extracted_value), Some(insert_location)) = (
3409 self.selected_expression,
3410 self.statement_before_selected_expression,
3411 ) else {
3412 return vec![];
3413 };
3414
3415 let variable_name = match &extracted_value {
3416 ExtractedToVariable::Expression { name, .. }
3417 | ExtractedToVariable::StartOfPipeline { name, .. } => name,
3418 };
3419 let expression_span = match &extracted_value {
3420 ExtractedToVariable::Expression { location, .. }
3421 | ExtractedToVariable::StartOfPipeline { location, .. } => location,
3422 };
3423
3424 let content = self
3425 .module
3426 .code
3427 .get(expression_span.start as usize..expression_span.end as usize)
3428 .expect("selected expression");
3429
3430 let range = self.edits.src_span_to_lsp_range(insert_location);
3431
3432 let indent_size =
3433 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3434
3435 let mut indent = " ".repeat(indent_size);
3436
3437 // We insert the variable declaration
3438 // Wrap in a block if needed
3439 let mut insertion = match extracted_value {
3440 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"),
3441 ExtractedToVariable::StartOfPipeline { .. } => {
3442 format!("let {variable_name} =\n{indent} {content}\n")
3443 }
3444 };
3445
3446 if self.to_be_wrapped {
3447 let line_end = self
3448 .edits
3449 .line_numbers
3450 .line_starts
3451 .get((range.end.line + 1) as usize)
3452 .expect("Line number should be valid");
3453
3454 self.edits.insert(*line_end, format!("{indent}}}\n"));
3455 indent += " ";
3456 insertion = format!("{{\n{indent}{insertion}");
3457 }
3458
3459 self.edits
3460 .insert(insert_location.start, format!("{insertion}\n{indent}"));
3461
3462 self.edits
3463 .replace(*expression_span, String::from(variable_name));
3464
3465 let mut action = Vec::with_capacity(1);
3466 CodeActionBuilder::new("Extract variable")
3467 .kind(CodeActionKind::RefactorExtract)
3468 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3469 .preferred(false)
3470 .push_to(&mut action);
3471 action
3472 }
3473
3474 fn inside_new_scope<F>(&mut self, fun: F)
3475 where
3476 F: Fn(&mut Self),
3477 {
3478 let names = self.name_generator.clone();
3479 fun(self);
3480 self.name_generator = names;
3481 }
3482
3483 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString {
3484 let name = self.name_generator.generate_name_from_type(&type_);
3485 // When the generator generates a name, it rightfully inserts it in the
3486 // current scope so that it cannot be used again.
3487 // However, in our case it's not what we want: the name we're generating
3488 // is a candidate for what we might use for a single variable, at the
3489 // end of the whole process we're gonna pick just a single name.
3490 // If we were to insert this name into scope, that means that all the
3491 // other candidates would have a suffix `int_2`, `int_3`, ...
3492 // When we finally pick one it would be strange if the picked name had
3493 // a suffix but no `int`, `int_1`, ... were in scope!
3494 let _ = self.name_generator.used_names.remove(&name);
3495 name
3496 }
3497
3498 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
3499 where
3500 F: Fn(&mut Self),
3501 {
3502 self.at_optional_position(Some(position), fun);
3503 }
3504
3505 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
3506 where
3507 F: Fn(&mut Self),
3508 {
3509 let previous_statement = self.latest_statement;
3510 let previous_position = self.position;
3511 self.position = position;
3512 fun(self);
3513 self.position = previous_position;
3514 self.latest_statement = previous_statement;
3515 }
3516}
3517
3518impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
3519 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
3520 let range = self.edits.src_span_to_lsp_range(statement.location());
3521 if !within(self.params.range, range) {
3522 self.latest_statement = Some(statement.location());
3523 ast::visit::visit_typed_statement(self, statement);
3524 return;
3525 }
3526
3527 match self.position {
3528 // A capture body is comprised of just a single expression statement
3529 // that is inserted by the compiler, we don't really want to put
3530 // anything before that; so in this case we avoid tracking it.
3531 Some(ExtractVariablePosition::InsideCaptureBody) => {}
3532 Some(ExtractVariablePosition::PipelineCall) => {
3533 // Insert above the pipeline start
3534 self.latest_statement = Some(statement.location());
3535 }
3536 _ => {
3537 // Insert below the previous statement
3538 self.latest_statement = Some(statement.location());
3539 self.statement_before_selected_expression = self.latest_statement;
3540 }
3541 }
3542
3543 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3544 ast::visit::visit_typed_statement(this, statement);
3545 });
3546 }
3547
3548 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3549 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3550 start: fun.location.start,
3551 end: fun.end_position,
3552 });
3553
3554 if !within(self.params.range, fun_range) {
3555 return;
3556 }
3557
3558 // We reset the name generator to purge the variable names from other
3559 // scopes.
3560 // We then reserve the names already used by top level definitions.
3561 self.name_generator = NameGenerator::new();
3562 self.name_generator
3563 .reserve_module_value_names(&self.module.ast.definitions);
3564
3565 ast::visit::visit_typed_function(self, fun);
3566 }
3567
3568 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3569 if let Pattern::Variable { name, .. } = &assignment.pattern {
3570 self.name_generator.add_used_name(name.clone());
3571 }
3572 ast::visit::visit_typed_assignment(self, assignment);
3573 }
3574
3575 fn visit_typed_expr_pipeline(
3576 &mut self,
3577 location: &'ast SrcSpan,
3578 first_value: &'ast TypedPipelineAssignment,
3579 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
3580 finally: &'ast TypedExpr,
3581 finally_kind: &'ast PipelineAssignmentKind,
3582 ) {
3583 let expr_range = self.edits.src_span_to_lsp_range(*location);
3584 if !within(self.params.range, expr_range) {
3585 ast::visit::visit_typed_expr_pipeline(
3586 self,
3587 location,
3588 first_value,
3589 assignments,
3590 finally,
3591 finally_kind,
3592 );
3593 return;
3594 }
3595
3596 // Visiting a pipeline requires a bit of care, we don't want to extract
3597 // intermediate steps as variables (those are function calls)!
3598 // So we start by checking if the selected section contains multiple
3599 // steps including the first one: in that case we can extract all those
3600 // steps as a single variable.
3601 let selection = self.edits.lsp_range_to_src_span(self.params.range);
3602 let is_inside_first_step = first_value.location.contains(selection.start);
3603 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| {
3604 if assignment.location.contains(selection.end) {
3605 Some(assignment)
3606 } else {
3607 None
3608 }
3609 });
3610
3611 if let Some(last) = last_included_step
3612 && is_inside_first_step
3613 {
3614 let location = first_value.location.merge(&last.value.location());
3615 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline {
3616 location,
3617 name: self.generate_candidate_name(last.type_()),
3618 });
3619 return;
3620 }
3621
3622 // Otherwise we visit all the steps individually to see if there's
3623 // something _inside_ a step that might be extracted.
3624 let all_assignments =
3625 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
3626 for assignment in all_assignments {
3627 // With the position as "PipelineCall" we know we can't extract the
3628 // pipeline step itself!
3629 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3630 this.visit_typed_pipeline_assignment(assignment);
3631 });
3632 }
3633
3634 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3635 this.visit_typed_expr(finally);
3636 });
3637 }
3638
3639 fn visit_typed_expr_call(
3640 &mut self,
3641 location: &'ast SrcSpan,
3642 type_: &'ast Arc<Type>,
3643 fun: &'ast TypedExpr,
3644 arguments: &'ast [TypedCallArg],
3645 open_parenthesis: &'ast Option<u32>,
3646 ) {
3647 // Function calls need some extra care. If we're inspecting a record
3648 // call like this one: `Wibble(wobble, woo)` and the cursor is over the
3649 // constructor itself we never want to allow extracting it, or it would
3650 // result in the following code:
3651 //
3652 // ```gleam
3653 // Wibble(wobble, woo)
3654 // // ^^ Cursor here
3655 //
3656 // let wibble = Wibble
3657 // wibble(wobble, woo)
3658 // // That's a bit silly!
3659 // ```
3660 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
3661 if within(self.params.range, fun_range) && fun.is_record_constructor_function() {
3662 return;
3663 }
3664
3665 // Otherwise we just keep visiting like usual, no special handling is
3666 // required.
3667 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
3668 }
3669
3670 fn visit_typed_expr_record_update(
3671 &mut self,
3672 location: &'ast SrcSpan,
3673 spread_start: &'ast u32,
3674 type_: &'ast Arc<Type>,
3675 updated_record: &'ast TypedExpr,
3676 updated_record_assigned_name: &'ast Option<EcoString>,
3677 constructor: &'ast TypedExpr,
3678 arguments: &'ast [TypedCallArg],
3679 ) {
3680 // Record updates need some extra care. If we're inspecting a record
3681 // update like this one: `Wibble(..wobble, woo)` and the cursor is over
3682 // the constructor itself we never want to allow extracting it, or it
3683 // would result in the following code:
3684 //
3685 // ```gleam
3686 // Wibble(..wobble, woo)
3687 // // ^^ Cursor here
3688 //
3689 // let wibble = Wibble
3690 // wibble(..wobble, woo)
3691 // // That's a bit silly!
3692 // ```
3693 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location());
3694 if within(self.params.range, constructor_range)
3695 && constructor.is_record_constructor_function()
3696 {
3697 return;
3698 }
3699
3700 // Otherwise we just keep visiting like usual, no special handling is
3701 // required.
3702 ast::visit::visit_typed_expr_record_update(
3703 self,
3704 location,
3705 spread_start,
3706 type_,
3707 updated_record,
3708 updated_record_assigned_name,
3709 constructor,
3710 arguments,
3711 );
3712 }
3713
3714 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3715 let expr_location = expr.location();
3716 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3717 if !within(self.params.range, expr_range) {
3718 ast::visit::visit_typed_expr(self, expr);
3719 return;
3720 }
3721
3722 // If the expression is a top level statement we don't want to extract
3723 // it into a variable. It would mean we would turn this:
3724 //
3725 // ```gleam
3726 // pub fn main() {
3727 // let wibble = 1
3728 // // ^ cursor here
3729 // }
3730 //
3731 // // into:
3732 //
3733 // pub fn main() {
3734 // let int = 1
3735 // let wibble = int
3736 // }
3737 // ```
3738 //
3739 // Not all that useful!
3740 //
3741 match self.position {
3742 Some(
3743 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
3744 ) => {
3745 self.at_optional_position(None, |this| {
3746 ast::visit::visit_typed_expr(this, expr);
3747 });
3748 return;
3749 }
3750 Some(
3751 ExtractVariablePosition::InsideCaptureBody
3752 | ExtractVariablePosition::InsideCaseClause
3753 | ExtractVariablePosition::CallArg,
3754 )
3755 | None => {}
3756 }
3757
3758 match expr {
3759 TypedExpr::Fn {
3760 kind: FunctionLiteralKind::Anonymous { .. },
3761 ..
3762 } => {
3763 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3764 ast::visit::visit_typed_expr(this, expr);
3765 });
3766 return;
3767 }
3768
3769 TypedExpr::Int { location, .. }
3770 | TypedExpr::Float { location, .. }
3771 | TypedExpr::String { location, .. }
3772 | TypedExpr::Pipeline { location, .. }
3773 | TypedExpr::Fn { location, .. }
3774 | TypedExpr::Todo { location, .. }
3775 | TypedExpr::List { location, .. }
3776 | TypedExpr::Call { location, .. }
3777 | TypedExpr::BinOp { location, .. }
3778 | TypedExpr::Case { location, .. }
3779 | TypedExpr::RecordAccess { location, .. }
3780 | TypedExpr::Tuple { location, .. }
3781 | TypedExpr::TupleIndex { location, .. }
3782 | TypedExpr::BitArray { location, .. }
3783 | TypedExpr::RecordUpdate { location, .. }
3784 | TypedExpr::NegateBool { location, .. }
3785 | TypedExpr::NegateInt { location, .. }
3786 // It generally makes no sense to extract variables, the only
3787 // exception is for records with no fields (like `True` and
3788 // `False`): in the AST those are `Var`s with a `Record`
3789 // constructor. Extracting them is allowed!
3790 | TypedExpr::Var {
3791 constructor:
3792 ValueConstructor {
3793 variant: type_::ValueConstructorVariant::Record { .. },
3794 ..
3795 },
3796 location,
3797 ..
3798 } => {
3799 if let Some(ExtractVariablePosition::CallArg) = self.position {
3800 // Don't update latest statement, we don't want to insert the extracted
3801 // variable inside the parenthesis where the call argument is located.
3802 } else {
3803 self.statement_before_selected_expression = self.latest_statement;
3804 }
3805 self.selected_expression = Some(ExtractedToVariable::Expression {
3806 location: *location,
3807 name: self.generate_candidate_name(expr.type_()),
3808 });
3809 }
3810
3811 // Expressions that don't make sense to extract
3812 TypedExpr::Panic { .. }
3813 | TypedExpr::Echo { .. }
3814 | TypedExpr::Block { .. }
3815 | TypedExpr::ModuleSelect { .. }
3816 | TypedExpr::Invalid { .. }
3817 | TypedExpr::PositionalAccess { .. }
3818 | TypedExpr::Var { .. } => (),
3819 }
3820
3821 ast::visit::visit_typed_expr(self, expr);
3822 }
3823
3824 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3825 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3826 if !within(self.params.range, range) {
3827 ast::visit::visit_typed_use(self, use_);
3828 return;
3829 }
3830
3831 // Insert code under the `use`
3832 self.statement_before_selected_expression = Some(use_.call.location());
3833 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3834 ast::visit::visit_typed_use(this, use_);
3835 });
3836 }
3837
3838 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3839 let range = self.edits.src_span_to_lsp_range(clause.location());
3840 if !within(self.params.range, range) {
3841 self.inside_new_scope(|this| {
3842 ast::visit::visit_typed_clause(this, clause);
3843 });
3844 return;
3845 }
3846
3847 // Insert code after the `->`
3848 self.latest_statement = Some(clause.then.location());
3849 self.to_be_wrapped = true;
3850 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3851 this.inside_new_scope(|this| {
3852 ast::visit::visit_typed_clause(this, clause);
3853 });
3854 });
3855 }
3856
3857 fn visit_typed_expr_block(
3858 &mut self,
3859 location: &'ast SrcSpan,
3860 statements: &'ast [TypedStatement],
3861 ) {
3862 let range = self.edits.src_span_to_lsp_range(*location);
3863 if !within(self.params.range, range) {
3864 self.inside_new_scope(|this| {
3865 ast::visit::visit_typed_expr_block(this, location, statements);
3866 });
3867 return;
3868 }
3869
3870 // Don't extract block as variable
3871 let mut position = self.position;
3872 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3873 position = None;
3874 self.to_be_wrapped = false;
3875 }
3876
3877 self.at_optional_position(position, |this| {
3878 this.inside_new_scope(|this| {
3879 ast::visit::visit_typed_expr_block(this, location, statements);
3880 });
3881 });
3882 }
3883
3884 fn visit_typed_expr_fn(
3885 &mut self,
3886 location: &'ast SrcSpan,
3887 type_: &'ast Arc<Type>,
3888 kind: &'ast FunctionLiteralKind,
3889 arguments: &'ast [TypedArg],
3890 body: &'ast Vec1<TypedStatement>,
3891 return_annotation: &'ast Option<ast::TypeAst>,
3892 ) {
3893 let range = self.edits.src_span_to_lsp_range(*location);
3894 if !within(self.params.range, range) {
3895 self.inside_new_scope(|this| {
3896 ast::visit::visit_typed_expr_fn(
3897 this,
3898 location,
3899 type_,
3900 kind,
3901 arguments,
3902 body,
3903 return_annotation,
3904 );
3905 });
3906 return;
3907 }
3908
3909 let position = match kind {
3910 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3911 // just a single expression statement. When visiting we must record
3912 // we're inside a capture body.
3913 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3914 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3915 FunctionLiteralKind::Anonymous { .. } => self.position,
3916 };
3917
3918 self.at_optional_position(position, |this| {
3919 this.inside_new_scope(|this| {
3920 ast::visit::visit_typed_expr_fn(
3921 this,
3922 location,
3923 type_,
3924 kind,
3925 arguments,
3926 body,
3927 return_annotation,
3928 );
3929 });
3930 });
3931 }
3932
3933 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3934 let range = self.edits.src_span_to_lsp_range(arg.location);
3935 if !within(self.params.range, range) {
3936 ast::visit::visit_typed_call_arg(self, arg);
3937 return;
3938 }
3939
3940 // An implicit record update arg in inserted by the compiler, we don't
3941 // want folks to interact with this since it doesn't translate to
3942 // anything in the source code despite having a default position.
3943 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit {
3944 return;
3945 }
3946
3947 let position = if arg.is_use_implicit_callback() {
3948 Some(ExtractVariablePosition::TopLevelStatement)
3949 } else {
3950 Some(ExtractVariablePosition::CallArg)
3951 };
3952
3953 self.at_optional_position(position, |this| {
3954 ast::visit::visit_typed_call_arg(this, arg);
3955 });
3956 }
3957
3958 // We don't want to offer the action if the cursor is over some invalid
3959 // piece of code.
3960 fn visit_typed_expr_invalid(
3961 &mut self,
3962 location: &'ast SrcSpan,
3963 _type_: &'ast Arc<Type>,
3964 _extra_information: &'ast Option<InvalidExpression>,
3965 ) {
3966 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3967 if within(self.params.range, invalid_range) {
3968 self.selected_expression = None;
3969 }
3970 }
3971}
3972
3973/// Builder for code action to convert a literal use into a const.
3974///
3975/// For using the code action on each of the following lines:
3976///
3977/// ```gleam
3978/// fn void() {
3979/// let var = [1, 2, 3]
3980/// let res = function("Statement", var)
3981/// }
3982/// ```
3983///
3984/// Both value literals will become:
3985///
3986/// ```gleam
3987/// const var = [1, 2, 3]
3988/// const string = "Statement"
3989///
3990/// fn void() {
3991/// let res = function(string, var)
3992/// }
3993/// ```
3994pub struct ExtractConstant<'a> {
3995 module: &'a Module,
3996 params: &'a CodeActionParams,
3997 edits: TextEdits<'a>,
3998 /// The whole selected expression
3999 selected_expression: Option<SrcSpan>,
4000 /// The location of the start of the function containing the expression.
4001 /// It includes function's documentation as well
4002 container_function_start: Option<u32>,
4003 /// The variant of the extractable expression being extracted (if any)
4004 variant_of_extractable: Option<ExtractableToConstant>,
4005 /// The name of the newly created constant
4006 name_to_use: Option<EcoString>,
4007 /// The right hand side expression of the newly created constant
4008 value_to_use: Option<EcoString>,
4009}
4010
4011/// Used when an expression can be extracted to a constant
4012enum ExtractableToConstant {
4013 /// Used for collections and operator uses. This means that elements
4014 /// inside, are also extractable as constants.
4015 ComposedValue,
4016 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
4017 /// and type variants (not records).
4018 SingleValue,
4019 /// Used for whole variable assignments. If the right hand side of the
4020 /// expression can be extracted, the whole expression extracted and use the
4021 /// local variable as a constant.
4022 Assignment,
4023}
4024
4025fn can_be_constant(
4026 module: &Module,
4027 expr: &TypedExpr,
4028 module_constants: Option<&HashSet<&EcoString>>,
4029) -> bool {
4030 // We pass the `module_constants` on recursion to not compute them each time
4031 let module_constants = match module_constants {
4032 Some(module_constants) => module_constants,
4033 None => &module
4034 .ast
4035 .definitions
4036 .constants
4037 .iter()
4038 .map(|constant| &constant.name)
4039 .collect(),
4040 };
4041
4042 match expr {
4043 // Attempt to extract whole list as long as it's comprised of only literals
4044 TypedExpr::List { elements, tail, .. } => {
4045 elements
4046 .iter()
4047 .all(|element| can_be_constant(module, element, Some(module_constants)))
4048 && tail.is_none()
4049 }
4050
4051 // Attempt to extract whole bit array as long as it's made up of literals
4052 TypedExpr::BitArray { segments, .. } => {
4053 segments
4054 .iter()
4055 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants)))
4056 && segments.iter().all(|segment| {
4057 segment.options.iter().all(|option| match option {
4058 ast::BitArrayOption::Size { value, .. } => {
4059 can_be_constant(module, value, Some(module_constants))
4060 }
4061
4062 ast::BitArrayOption::Bytes { .. }
4063 | ast::BitArrayOption::Int { .. }
4064 | ast::BitArrayOption::Float { .. }
4065 | ast::BitArrayOption::Bits { .. }
4066 | ast::BitArrayOption::Utf8 { .. }
4067 | ast::BitArrayOption::Utf16 { .. }
4068 | ast::BitArrayOption::Utf32 { .. }
4069 | ast::BitArrayOption::Utf8Codepoint { .. }
4070 | ast::BitArrayOption::Utf16Codepoint { .. }
4071 | ast::BitArrayOption::Utf32Codepoint { .. }
4072 | ast::BitArrayOption::Signed { .. }
4073 | ast::BitArrayOption::Unsigned { .. }
4074 | ast::BitArrayOption::Big { .. }
4075 | ast::BitArrayOption::Little { .. }
4076 | ast::BitArrayOption::Native { .. }
4077 | ast::BitArrayOption::Unit { .. } => true,
4078 })
4079 })
4080 }
4081
4082 // Attempt to extract whole tuple as long as it's comprised of only literals
4083 TypedExpr::Tuple { elements, .. } => elements
4084 .iter()
4085 .all(|element| can_be_constant(module, element, Some(module_constants))),
4086
4087 // Extract literals directly
4088 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
4089
4090 // Extract non-record types directly
4091 TypedExpr::Var {
4092 constructor, name, ..
4093 } => {
4094 matches!(
4095 constructor.variant,
4096 type_::ValueConstructorVariant::Record { arity: 0, .. }
4097 ) || module_constants.contains(name)
4098 }
4099
4100 // Extract record types as long as arguments can be constant
4101 TypedExpr::Call { arguments, fun, .. } => {
4102 fun.is_record_literal()
4103 && arguments
4104 .iter()
4105 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants)))
4106 }
4107
4108 // Extract concat binary operation if both sides can be constants
4109 TypedExpr::BinOp {
4110 operator,
4111 left,
4112 right,
4113 ..
4114 } => {
4115 matches!(operator, ast::BinOp::Concatenate)
4116 && can_be_constant(module, left, Some(module_constants))
4117 && can_be_constant(module, right, Some(module_constants))
4118 }
4119
4120 TypedExpr::Block { .. }
4121 | TypedExpr::Pipeline { .. }
4122 | TypedExpr::Fn { .. }
4123 | TypedExpr::Case { .. }
4124 | TypedExpr::RecordAccess { .. }
4125 | TypedExpr::PositionalAccess { .. }
4126 | TypedExpr::ModuleSelect { .. }
4127 | TypedExpr::TupleIndex { .. }
4128 | TypedExpr::Todo { .. }
4129 | TypedExpr::Panic { .. }
4130 | TypedExpr::Echo { .. }
4131 | TypedExpr::RecordUpdate { .. }
4132 | TypedExpr::NegateBool { .. }
4133 | TypedExpr::NegateInt { .. }
4134 | TypedExpr::Invalid { .. } => false,
4135 }
4136}
4137
4138/// Takes the list of already existing constants and functions and creates a
4139/// name that doesn't conflict with them
4140///
4141fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
4142 let mut name_generator = NameGenerator::new();
4143 name_generator.reserve_module_value_names(&module.ast.definitions);
4144 name_generator.generate_name_from_type(&expr.type_())
4145}
4146
4147/// Converts the source start position of a documentation comment's contents into
4148/// the position of the leading slash in its marker ('///').
4149fn get_doc_marker_position(content_pos: u32) -> u32 {
4150 content_pos.saturating_sub(3)
4151}
4152
4153impl<'a> ExtractConstant<'a> {
4154 pub fn new(
4155 module: &'a Module,
4156 line_numbers: &'a LineNumbers,
4157 params: &'a CodeActionParams,
4158 ) -> Self {
4159 Self {
4160 module,
4161 params,
4162 edits: TextEdits::new(line_numbers),
4163 selected_expression: None,
4164 container_function_start: None,
4165 variant_of_extractable: None,
4166 name_to_use: None,
4167 value_to_use: None,
4168 }
4169 }
4170
4171 pub fn code_actions(mut self) -> Vec<CodeAction> {
4172 self.visit_typed_module(&self.module.ast);
4173
4174 let (
4175 Some(expr_span),
4176 Some(function_start),
4177 Some(type_of_extractable),
4178 Some(new_const_name),
4179 Some(const_value),
4180 ) = (
4181 self.selected_expression,
4182 self.container_function_start,
4183 self.variant_of_extractable,
4184 self.name_to_use,
4185 self.value_to_use,
4186 )
4187 else {
4188 return vec![];
4189 };
4190
4191 // We insert the constant declaration
4192 self.edits.insert(
4193 function_start,
4194 format!("const {new_const_name} = {const_value}\n\n"),
4195 );
4196
4197 // We remove or replace the selected expression
4198 match type_of_extractable {
4199 // The whole expression is deleted for assignments
4200 ExtractableToConstant::Assignment => {
4201 let range = self
4202 .edits
4203 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
4204
4205 let indent_size =
4206 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
4207
4208 let expr_span_with_new_line = SrcSpan {
4209 // We remove leading indentation + 1 to remove the newline with it
4210 start: expr_span.start - (indent_size as u32 + 1),
4211 end: expr_span.end,
4212 };
4213 self.edits.delete(expr_span_with_new_line);
4214 }
4215
4216 // Only right hand side is replaced for collection or values
4217 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
4218 self.edits.replace(expr_span, String::from(new_const_name));
4219 }
4220 }
4221
4222 let mut action = Vec::with_capacity(1);
4223 CodeActionBuilder::new("Extract constant")
4224 .kind(CodeActionKind::RefactorExtract)
4225 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4226 .preferred(false)
4227 .push_to(&mut action);
4228 action
4229 }
4230}
4231
4232impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
4233 /// To get the position of the function containing the value or assignment
4234 /// to extract
4235 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
4236 let fun_location = fun.location;
4237 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
4238 start: fun_location.start,
4239 end: fun.end_position,
4240 });
4241
4242 if !within(self.params.range, fun_range) {
4243 return;
4244 }
4245
4246 // Here we need to get position of the function, starting from the leading slash in the
4247 // documentation comment's marker ('///'), not from comment's content (of which
4248 // we have the position), so we must convert the content start position
4249 // to the leading slash's position.
4250 self.container_function_start = Some(
4251 fun.documentation
4252 .as_ref()
4253 .map(|(doc_start, _)| get_doc_marker_position(*doc_start))
4254 .unwrap_or(fun_location.start),
4255 );
4256
4257 ast::visit::visit_typed_function(self, fun);
4258 }
4259
4260 /// To extract the whole assignment
4261 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
4262 let expr_location = assignment.location;
4263
4264 // We only offer this code action for extracting the whole assignment
4265 // between `let` and `=`.
4266 let pattern_location = assignment.pattern.location();
4267 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
4268 let code_action_range = self.edits.src_span_to_lsp_range(location);
4269
4270 if !within(self.params.range, code_action_range) {
4271 ast::visit::visit_typed_assignment(self, assignment);
4272 return;
4273 }
4274
4275 // Has to be variable because patterns can't be constants.
4276 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
4277 {
4278 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
4279 self.selected_expression = Some(expr_location);
4280 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern {
4281 Some(name.clone())
4282 } else {
4283 None
4284 };
4285 self.value_to_use = Some(EcoString::from(
4286 self.module
4287 .code
4288 .get(
4289 (assignment.value.location().start as usize)
4290 ..(assignment.location.end as usize),
4291 )
4292 .expect("selected expression"),
4293 ));
4294 }
4295 }
4296
4297 /// To extract only the literal
4298 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
4299 let expr_location = expr.location();
4300 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
4301
4302 if !within(self.params.range, expr_range) {
4303 ast::visit::visit_typed_expr(self, expr);
4304 return;
4305 }
4306
4307 // Keep going down recursively if:
4308 // - It's no extractable has been found yet (`None`).
4309 // - It's a collection, which may or may not contain a value that can
4310 // be extracted.
4311 // - It's a binary operator, which may or may not operate on
4312 // extractable values.
4313 if matches!(
4314 self.variant_of_extractable,
4315 None | Some(ExtractableToConstant::ComposedValue)
4316 ) && can_be_constant(self.module, expr, None)
4317 {
4318 self.variant_of_extractable = match expr {
4319 TypedExpr::Var { .. }
4320 | TypedExpr::Int { .. }
4321 | TypedExpr::Float { .. }
4322 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
4323
4324 TypedExpr::List { .. }
4325 | TypedExpr::Tuple { .. }
4326 | TypedExpr::BitArray { .. }
4327 | TypedExpr::BinOp { .. }
4328 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
4329
4330 TypedExpr::Block { .. }
4331 | TypedExpr::Pipeline { .. }
4332 | TypedExpr::Fn { .. }
4333 | TypedExpr::Case { .. }
4334 | TypedExpr::RecordAccess { .. }
4335 | TypedExpr::PositionalAccess { .. }
4336 | TypedExpr::ModuleSelect { .. }
4337 | TypedExpr::TupleIndex { .. }
4338 | TypedExpr::Todo { .. }
4339 | TypedExpr::Panic { .. }
4340 | TypedExpr::Echo { .. }
4341 | TypedExpr::RecordUpdate { .. }
4342 | TypedExpr::NegateBool { .. }
4343 | TypedExpr::NegateInt { .. }
4344 | TypedExpr::Invalid { .. } => None,
4345 };
4346
4347 self.selected_expression = Some(expr_location);
4348 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
4349 self.value_to_use = Some(EcoString::from(
4350 self.module
4351 .code
4352 .get((expr_location.start as usize)..(expr_location.end as usize))
4353 .expect("selected expression"),
4354 ));
4355 }
4356
4357 ast::visit::visit_typed_expr(self, expr);
4358 }
4359}
4360
4361/// Builder for code action to apply the "expand function capture" action.
4362///
4363pub struct ExpandFunctionCapture<'a> {
4364 module: &'a Module,
4365 params: &'a CodeActionParams,
4366 edits: TextEdits<'a>,
4367 function_capture_data: Option<FunctionCaptureData>,
4368}
4369
4370pub struct FunctionCaptureData {
4371 function_span: SrcSpan,
4372 hole_span: SrcSpan,
4373 hole_type: Arc<Type>,
4374 reserved_names: VariablesNames,
4375}
4376
4377impl<'a> ExpandFunctionCapture<'a> {
4378 pub fn new(
4379 module: &'a Module,
4380 line_numbers: &'a LineNumbers,
4381 params: &'a CodeActionParams,
4382 ) -> Self {
4383 Self {
4384 module,
4385 params,
4386 edits: TextEdits::new(line_numbers),
4387 function_capture_data: None,
4388 }
4389 }
4390
4391 pub fn code_actions(mut self) -> Vec<CodeAction> {
4392 self.visit_typed_module(&self.module.ast);
4393
4394 let Some(FunctionCaptureData {
4395 function_span,
4396 hole_span,
4397 hole_type,
4398 reserved_names,
4399 }) = self.function_capture_data
4400 else {
4401 return vec![];
4402 };
4403
4404 let mut name_generator = NameGenerator::new();
4405 name_generator.reserve_variable_names(reserved_names);
4406 let name = name_generator.generate_name_from_type(&hole_type);
4407
4408 self.edits.replace(hole_span, name.clone().into());
4409 self.edits.insert(function_span.end, " }".into());
4410 self.edits
4411 .insert(function_span.start, format!("fn({name}) {{ "));
4412
4413 let mut action = Vec::with_capacity(1);
4414 CodeActionBuilder::new("Expand function capture")
4415 .kind(CodeActionKind::RefactorRewrite)
4416 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4417 .preferred(false)
4418 .push_to(&mut action);
4419 action
4420 }
4421}
4422
4423impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
4424 fn visit_typed_expr_fn(
4425 &mut self,
4426 location: &'ast SrcSpan,
4427 type_: &'ast Arc<Type>,
4428 kind: &'ast FunctionLiteralKind,
4429 arguments: &'ast [TypedArg],
4430 body: &'ast Vec1<TypedStatement>,
4431 return_annotation: &'ast Option<ast::TypeAst>,
4432 ) {
4433 let fn_range = self.edits.src_span_to_lsp_range(*location);
4434 if within(self.params.range, fn_range)
4435 && kind.is_capture()
4436 && let [argument] = arguments
4437 {
4438 self.function_capture_data = Some(FunctionCaptureData {
4439 function_span: *location,
4440 hole_span: argument.location,
4441 hole_type: argument.type_.clone(),
4442 reserved_names: VariablesNames::from_statements(body),
4443 });
4444 }
4445
4446 ast::visit::visit_typed_expr_fn(
4447 self,
4448 location,
4449 type_,
4450 kind,
4451 arguments,
4452 body,
4453 return_annotation,
4454 );
4455 }
4456}
4457
4458/// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names].
4459struct VariablesNames {
4460 names: HashSet<EcoString>,
4461}
4462
4463impl VariablesNames {
4464 /// Creates a `VariableNames` by collecting all variables used in a list of statements.
4465 fn from_statements(statements: &[TypedStatement]) -> Self {
4466 let mut variables = Self {
4467 names: HashSet::new(),
4468 };
4469
4470 for statement in statements {
4471 variables.visit_typed_statement(statement);
4472 }
4473 variables
4474 }
4475
4476 /// Creates a `VariableNames` by collecting all variables used within an expression.
4477 fn from_expression(expression: &TypedExpr) -> Self {
4478 let mut variables = Self {
4479 names: HashSet::new(),
4480 };
4481
4482 variables.visit_typed_expr(expression);
4483 variables
4484 }
4485}
4486
4487impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
4488 fn visit_typed_expr_var(
4489 &mut self,
4490 _location: &'ast SrcSpan,
4491 _constructor: &'ast ValueConstructor,
4492 name: &'ast EcoString,
4493 ) {
4494 let _ = self.names.insert(name.clone());
4495 }
4496}
4497
4498/// Builder for code action to apply the "generate dynamic decoder action.
4499///
4500pub struct GenerateDynamicDecoder<'a, IO> {
4501 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4502 module: &'a Module,
4503 params: &'a CodeActionParams,
4504 edits: TextEdits<'a>,
4505 printer: Printer<'a>,
4506 actions: &'a mut Vec<CodeAction>,
4507}
4508
4509const DECODE_MODULE: &str = "gleam/dynamic/decode";
4510
4511impl<'a, IO> GenerateDynamicDecoder<'a, IO> {
4512 pub fn new(
4513 module: &'a Module,
4514 line_numbers: &'a LineNumbers,
4515 params: &'a CodeActionParams,
4516 actions: &'a mut Vec<CodeAction>,
4517 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4518 ) -> Self {
4519 // Since we are generating a new function, type variables from other
4520 // functions and constants are irrelevant to the types we print.
4521 let printer = Printer::new_without_type_variables(&module.ast.names);
4522 Self {
4523 module,
4524 params,
4525 edits: TextEdits::new(line_numbers),
4526 printer,
4527 actions,
4528 compiler,
4529 }
4530 }
4531
4532 pub fn code_actions(&mut self) {
4533 self.visit_typed_module(&self.module.ast);
4534 }
4535
4536 fn custom_type_decoder_body(
4537 &mut self,
4538 custom_type: &CustomType<Arc<Type>>,
4539 ) -> Option<EcoString> {
4540 // We cannot generate a decoder for an external type with no constructors!
4541 let constructors_size = custom_type.constructors.len();
4542 let (first, rest) = custom_type.constructors.split_first()?;
4543 let mode = EncodingMode::for_custom_type(custom_type);
4544
4545 // We generate a decoder for a type with a single constructor: it does not
4546 // require pattern matching on a tag as there's no variants to tell apart.
4547 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
4548 return self.constructor_decoder(mode, custom_type, first, 0);
4549 }
4550
4551 // Otherwise we need to generate a decoder that has to tell apart different
4552 // variants, depending on the mode we might have to decode a type field or
4553 // plain strings!
4554 let module = self.printer.print_module(DECODE_MODULE);
4555 let discriminant = if mode == EncodingMode::PlainString {
4556 eco_format!("use variant <- {module}.then({module}.string)")
4557 } else {
4558 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
4559 };
4560
4561 let mut clauses = Vec::with_capacity(constructors_size);
4562 for constructor in iter::once(first).chain(rest) {
4563 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
4564 let name = to_snake_case(&constructor.name);
4565 clauses.push(eco_format!(r#" "{name}" -> {body}"#));
4566 }
4567
4568 let failure_clause = self.failure_clause(custom_type);
4569
4570 let cases = clauses.join("\n");
4571 Some(eco_format!(
4572 r#"{{
4573 {discriminant}
4574 case variant {{
4575{cases}
4576{failure_clause}
4577 }}
4578}}"#,
4579 ))
4580 }
4581
4582 fn constructor_decoder(
4583 &mut self,
4584 mode: EncodingMode,
4585 custom_type: &ast::TypedCustomType,
4586 constructor: &TypedRecordConstructor,
4587 nesting: usize,
4588 ) -> Option<EcoString> {
4589 let decode_module = self.printer.print_module(DECODE_MODULE);
4590 let constructor_name = &constructor.name;
4591
4592 // If the constructor was encoded as a plain string with no additional
4593 // fields it means there's nothing else to decode and we can just
4594 // succeed.
4595 if mode == EncodingMode::PlainString {
4596 return Some(eco_format!("{decode_module}.success({constructor_name})"));
4597 }
4598
4599 // Otherwise we have to decode all the constructor fields to build it.
4600 let mut fields = Vec::with_capacity(constructor.arguments.len());
4601 for argument in constructor.arguments.iter() {
4602 let (_, name) = argument.label.as_ref()?;
4603 let field = RecordField {
4604 label: RecordLabel::Labeled(name),
4605 type_: &argument.type_,
4606 };
4607 fields.push(field);
4608 }
4609
4610 let mut decoder_printer = DecoderPrinter::new(
4611 &mut self.printer,
4612 custom_type.name.clone(),
4613 self.module.name.clone(),
4614 self.compiler,
4615 );
4616
4617 let decoders = fields
4618 .iter()
4619 .map(|field| decoder_printer.decode_field(field, nesting + 2))
4620 .join("\n");
4621
4622 let indent = " ".repeat(nesting);
4623
4624 Some(if decoders.is_empty() {
4625 eco_format!("{decode_module}.success({constructor_name})")
4626 } else {
4627 let field_names = fields
4628 .iter()
4629 .map(|field| format!("{}:", field.label.variable_name()))
4630 .join(", ");
4631
4632 eco_format!(
4633 "{{
4634{decoders}
4635{indent} {decode_module}.success({constructor_name}({field_names}))
4636{indent}}}",
4637 )
4638 })
4639 }
4640
4641 /// Generates the failure/catch-all clause in a decoder function for a
4642 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()`
4643 /// clause executed when the `type` field does not match any of the known variants.
4644 ///
4645 /// # Arguments
4646 /// * `custom_type` - The root type we are printing a decoder for
4647 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString {
4648 let decode_module = self.printer.print_module(DECODE_MODULE);
4649 let type_name = &custom_type.name;
4650
4651 let mut decoder_printer = DecoderPrinter::new(
4652 &mut self.printer,
4653 type_name.clone(),
4654 self.module.name.clone(),
4655 self.compiler,
4656 );
4657
4658 // The construction of the zero value might necessitate importing
4659 // modules that aren't currently in scope. We keep track of them here.
4660 let mut modules_to_import = HashSet::new();
4661 // We also need to keep track of the path we are tracing through the types
4662 // to make sure we don't get stuck in an infinite loop building a recursive
4663 // type.
4664 let mut path = vec![];
4665
4666 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type(
4667 &self.module.name,
4668 type_name,
4669 &mut path,
4670 &mut modules_to_import,
4671 ) {
4672 for module_name in modules_to_import {
4673 maybe_import(&mut self.edits, self.module, &module_name);
4674 }
4675 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,)
4676 } else {
4677 eco_format!(
4678 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"#
4679 )
4680 }
4681 }
4682}
4683
4684impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> {
4685 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4686 let range = self
4687 .edits
4688 .src_span_to_lsp_range(custom_type.full_location());
4689 if !within(self.params.range, range) {
4690 return;
4691 }
4692
4693 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
4694 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
4695 return;
4696 };
4697
4698 let parameters = match custom_type.parameters.len() {
4699 0 => EcoString::new(),
4700 _ => eco_format!(
4701 "({})",
4702 custom_type
4703 .parameters
4704 .iter()
4705 .map(|(_, name)| name)
4706 .join(", ")
4707 ),
4708 };
4709
4710 let decoder_type = self.printer.print_type(&Type::Named {
4711 publicity: Publicity::Public,
4712 package: STDLIB_PACKAGE_NAME.into(),
4713 module: DECODE_MODULE.into(),
4714 name: "Decoder".into(),
4715 arguments: vec![],
4716 inferred_variant: None,
4717 });
4718
4719 let function = format!(
4720 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
4721 type_name = custom_type.name,
4722 );
4723
4724 self.edits.insert(custom_type.end_position, function);
4725 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
4726
4727 CodeActionBuilder::new("Generate dynamic decoder")
4728 .kind(CodeActionKind::Refactor)
4729 .preferred(false)
4730 .changes(
4731 self.params.text_document.uri.clone(),
4732 std::mem::take(&mut self.edits.edits),
4733 )
4734 .push_to(self.actions);
4735 }
4736}
4737
4738/// If `module_name` is not already imported inside `module`, adds an edit to
4739/// add that import.
4740/// This function also makes sure not to import a module in itself.
4741///
4742fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
4743 if module.ast.names.is_imported(module_name) || module.name == module_name {
4744 return;
4745 }
4746
4747 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
4748 let first_is_import = first_import_pos.is_some();
4749 let import_location = first_import_pos.unwrap_or_default();
4750 let after_import_newlines = add_newlines_after_import(
4751 import_location,
4752 first_is_import,
4753 edits.line_numbers,
4754 &module.code,
4755 );
4756
4757 edits.edits.push(get_import_edit(
4758 import_location,
4759 module_name,
4760 &after_import_newlines,
4761 ));
4762}
4763
4764struct DecoderPrinter<'a, 'b, IO> {
4765 printer: &'a mut Printer<'b>,
4766 /// The name of the root type we are printing a decoder for
4767 type_name: EcoString,
4768 /// The module name of the root type we are printing a decoder for
4769 type_module: EcoString,
4770 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4771}
4772
4773const DYNAMIC_MODULE: &str = "gleam/dynamic";
4774const DICT_MODULE: &str = "gleam/dict";
4775const OPTION_MODULE: &str = "gleam/option";
4776
4777struct RecordField<'a> {
4778 label: RecordLabel<'a>,
4779 type_: &'a Type,
4780}
4781
4782enum RecordLabel<'a> {
4783 Labeled(&'a str),
4784 Unlabeled(usize),
4785}
4786
4787impl RecordLabel<'_> {
4788 fn field_key(&self) -> EcoString {
4789 match self {
4790 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
4791 RecordLabel::Unlabeled(index) => {
4792 eco_format!("{index}")
4793 }
4794 }
4795 }
4796
4797 fn variable_name(&self) -> EcoString {
4798 match self {
4799 RecordLabel::Labeled(label) => (*label).into(),
4800 &RecordLabel::Unlabeled(mut index) => {
4801 let mut characters = Vec::new();
4802 let alphabet_length = 26;
4803 let alphabet_offset = b'a';
4804 loop {
4805 let alphabet_index = (index % alphabet_length) as u8;
4806 characters.push((alphabet_offset + alphabet_index) as char);
4807 index /= alphabet_length;
4808
4809 if index == 0 {
4810 break;
4811 }
4812 index -= 1;
4813 }
4814 characters.into_iter().rev().collect()
4815 }
4816 }
4817 }
4818}
4819
4820impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> {
4821 fn new(
4822 printer: &'a mut Printer<'b>,
4823 type_name: EcoString,
4824 type_module: EcoString,
4825 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4826 ) -> Self {
4827 Self {
4828 type_name,
4829 type_module,
4830 printer,
4831 compiler,
4832 }
4833 }
4834
4835 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
4836 let module_name = self.printer.print_module(DECODE_MODULE);
4837 if type_.is_bit_array() {
4838 eco_format!("{module_name}.bit_array")
4839 } else if type_.is_bool() {
4840 eco_format!("{module_name}.bool")
4841 } else if type_.is_float() {
4842 eco_format!("{module_name}.float")
4843 } else if type_.is_int() {
4844 eco_format!("{module_name}.int")
4845 } else if type_.is_string() {
4846 eco_format!("{module_name}.string")
4847 } else if type_.is_nil() {
4848 eco_format!("{module_name}.success(Nil)")
4849 } else {
4850 match type_.tuple_types() {
4851 Some(types) => {
4852 let fields = types
4853 .iter()
4854 .enumerate()
4855 .map(|(index, type_)| RecordField {
4856 type_,
4857 label: RecordLabel::Unlabeled(index),
4858 })
4859 .collect_vec();
4860 let decoders = fields
4861 .iter()
4862 .map(|field| self.decode_field(field, indent + 2))
4863 .join("\n");
4864 let mut field_names = fields.iter().map(|field| field.label.variable_name());
4865
4866 eco_format!(
4867 "{{
4868{decoders}
4869
4870{indent} {module_name}.success(#({fields}))
4871{indent}}}",
4872 fields = field_names.join(", "),
4873 indent = " ".repeat(indent)
4874 )
4875 }
4876 _ => {
4877 let type_information = type_.named_type_information();
4878 let type_information =
4879 type_information.as_ref().map(|(module, name, arguments)| {
4880 (module.as_str(), name.as_str(), arguments.as_slice())
4881 });
4882
4883 match type_information {
4884 Some(("gleam/dynamic", "Dynamic", _)) => {
4885 eco_format!("{module_name}.dynamic")
4886 }
4887 Some(("gleam", "List", [element])) => {
4888 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
4889 }
4890 Some(("gleam/option", "Option", [some])) => {
4891 eco_format!(
4892 "{module_name}.optional({})",
4893 self.decoder_for(some, indent)
4894 )
4895 }
4896 Some(("gleam/dict", "Dict", [key, value])) => {
4897 eco_format!(
4898 "{module_name}.dict({}, {})",
4899 self.decoder_for(key, indent),
4900 self.decoder_for(value, indent)
4901 )
4902 }
4903 Some((module, name, _))
4904 if module == self.type_module && name == self.type_name =>
4905 {
4906 eco_format!("{}_decoder()", to_snake_case(name))
4907 }
4908 _ => eco_format!(
4909 r#"todo as "Decoder for {}""#,
4910 self.printer.print_type(type_)
4911 ),
4912 }
4913 }
4914 }
4915 }
4916 }
4917
4918 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4919 let decoder = self.decoder_for(field.type_, indent);
4920
4921 eco_format!(
4922 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4923 indent = " ".repeat(indent),
4924 variable = field.label.variable_name(),
4925 field = field.label.field_key(),
4926 module = self.printer.print_module(DECODE_MODULE)
4927 )
4928 }
4929
4930 /// Performs best-effort generation of zero values for a given type.
4931 /// Will succeed for all prelude types and most stdlib types.
4932 /// For specifics on how custom user-defined types are handled, see
4933 /// `zero_value_for_custom_type`.
4934 fn zero_value_for_type(
4935 &mut self,
4936 type_: &Type,
4937 // Keeps track of types visited already to ensure we don't end up in an infinite
4938 // loop due to recursive types
4939 path: &mut Vec<(EcoString, EcoString)>,
4940 modules_to_import: &mut HashSet<EcoString>,
4941 ) -> Option<EcoString> {
4942 if type_.is_bit_array() {
4943 return Some("<<>>".into());
4944 }
4945 if type_.is_bool() {
4946 return Some("False".into());
4947 }
4948 if type_.is_float() {
4949 return Some("0.0".into());
4950 }
4951 if type_.is_int() {
4952 return Some("0".into());
4953 }
4954 if type_.is_string() {
4955 return Some("\"\"".into());
4956 }
4957 if type_.is_list() {
4958 return Some("[]".into());
4959 }
4960 if type_.is_nil() {
4961 return Some("Nil".into());
4962 }
4963
4964 if let Some(types) = type_.tuple_types() {
4965 // We try generating zero values for the tuple members and exit early if any of them fail.
4966 let field_zeroes = types
4967 .iter()
4968 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import))
4969 .collect_vec();
4970
4971 if field_zeroes.len() < types.len() {
4972 return None;
4973 } else {
4974 return Some(eco_format!("#({})", field_zeroes.iter().join(", ")));
4975 }
4976 }
4977
4978 let (module, name, _) = type_.named_type_information()?;
4979 match (module.as_str(), name.as_str()) {
4980 (OPTION_MODULE, "Option") => {
4981 let _ = modules_to_import.insert(OPTION_MODULE.into());
4982 Some(eco_format!(
4983 "{}.None",
4984 self.printer.print_module(OPTION_MODULE)
4985 ))
4986 }
4987
4988 (DYNAMIC_MODULE, "Dynamic") => {
4989 let _ = modules_to_import.insert(DYNAMIC_MODULE.into());
4990 Some(eco_format!(
4991 "{}.nil()",
4992 self.printer.print_module(DYNAMIC_MODULE)
4993 ))
4994 }
4995
4996 (DICT_MODULE, "Dict") => {
4997 let _ = modules_to_import.insert(DICT_MODULE.into());
4998 Some(eco_format!(
4999 "{}.new()",
5000 self.printer.print_module(DICT_MODULE)
5001 ))
5002 }
5003
5004 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import),
5005 }
5006 }
5007
5008 /// Best-effort zero value generation for user-defined types in the
5009 /// current package.
5010 fn zero_value_for_custom_type(
5011 &mut self,
5012 custom_type_module: &EcoString,
5013 custom_type_name: &EcoString,
5014 path: &mut Vec<(EcoString, EcoString)>,
5015 modules_to_import: &mut HashSet<EcoString>,
5016 ) -> Option<EcoString> {
5017 // First we check that we have not already visited this type before to
5018 // avoid cycles.
5019 let already_seen_type = path.iter().any(|(path_module, path_type_name)| {
5020 path_module == custom_type_module && path_type_name == custom_type_name
5021 });
5022
5023 if already_seen_type {
5024 return None;
5025 }
5026
5027 let type_is_inside_current_module = &self.type_module == custom_type_module;
5028
5029 let current_module_interface = self
5030 .compiler
5031 .modules
5032 .get(&self.type_module)
5033 .map(|module| &module.ast.type_info)?;
5034
5035 let type_module_interface = if !type_is_inside_current_module {
5036 self.compiler.get_module_interface(custom_type_module)?
5037 } else {
5038 current_module_interface
5039 };
5040
5041 // We only try and generate zero values for user-defined types in the current
5042 // package. If you are expanding the scope of this functionality to
5043 // remove this limitation, make sure to check for internal modules.
5044 if current_module_interface.package != type_module_interface.package {
5045 return None;
5046 }
5047
5048 let constructors = type_module_interface
5049 .types_value_constructors
5050 .get(custom_type_name)?;
5051
5052 // Opaque types cannot be constructed outside the module they were defined in,
5053 // so we will be unable to produce a zero value.
5054 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque {
5055 return None;
5056 }
5057
5058 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor
5059 // to construct our zero value to reduce visual noise, but this might not always
5060 // be possible. So we check all constructors in increasing order of size to
5061 // find the first one that succeeds, and then short circuit.
5062 constructors
5063 .variants
5064 .iter()
5065 .sorted_by_key(|v| v.parameters.len())
5066 .find_map(|zero_constructor| {
5067 self.zero_value_for_custom_type_constructor(
5068 custom_type_module,
5069 custom_type_name,
5070 zero_constructor,
5071 type_is_inside_current_module,
5072 path,
5073 modules_to_import,
5074 )
5075 })
5076 }
5077
5078 /// Attempts to construct a zero value for one specific constructor
5079 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs
5080 /// _important checks on type visibility that this method does NOT_.
5081 /// This is a helper method extracted for readability.
5082 fn zero_value_for_custom_type_constructor(
5083 &mut self,
5084 custom_type_module: &EcoString,
5085 custom_type_name: &EcoString,
5086 zero_constructor: &type_::TypeValueConstructor,
5087 type_is_inside_current_module: bool,
5088 path: &mut Vec<(EcoString, EcoString)>,
5089 modules_to_import: &mut HashSet<EcoString>,
5090 ) -> Option<EcoString> {
5091 path.push((custom_type_module.clone(), custom_type_name.clone()));
5092
5093 // Try to generate zero values for all fields in the constructor
5094 let zero_params = zero_constructor
5095 .parameters
5096 .iter()
5097 .map_while(|parameter| {
5098 let zero = self.zero_value_for_type(¶meter.type_, path, modules_to_import)?;
5099
5100 if let Some(label) = ¶meter.label {
5101 Some(eco_format!("{label}: {zero}"))
5102 } else {
5103 Some(zero)
5104 }
5105 })
5106 .collect_vec();
5107
5108 // We need to make sure to clean up the path once we've finished
5109 // "visiting" this type.
5110 let _ = path.pop();
5111
5112 // Only proceed if we were able to construct every field successfully
5113 if zero_params.len() < zero_constructor.parameters.len() {
5114 return None;
5115 }
5116
5117 let zero_constructor = if !type_is_inside_current_module {
5118 // Type constructors from other modules need to be qualified appropriately,
5119 // and they might need to be brought into scope.
5120 let _ = modules_to_import.insert(custom_type_module.clone());
5121 eco_format!(
5122 "{}.{}",
5123 self.printer.print_module(custom_type_module),
5124 zero_constructor.name
5125 )
5126 } else {
5127 eco_format!("{}", zero_constructor.name)
5128 };
5129
5130 if zero_params.is_empty() {
5131 Some(eco_format!("{zero_constructor}"))
5132 } else {
5133 let zero_args = zero_params.iter().join(", ");
5134 Some(eco_format!("{zero_constructor}({zero_args})"))
5135 }
5136 }
5137}
5138
5139/// Builder for code action to apply the "Generate to-JSON function" action.
5140///
5141pub struct GenerateJsonEncoder<'a> {
5142 module: &'a Module,
5143 params: &'a CodeActionParams,
5144 edits: TextEdits<'a>,
5145 printer: Printer<'a>,
5146 actions: &'a mut Vec<CodeAction>,
5147 config: &'a PackageConfig,
5148}
5149
5150const JSON_MODULE: &str = "gleam/json";
5151const JSON_PACKAGE_NAME: &str = "gleam_json";
5152
5153#[derive(Eq, PartialEq, Copy, Clone)]
5154enum EncodingMode {
5155 PlainString,
5156 ObjectWithTypeTag,
5157 ObjectWithNoTypeTag,
5158}
5159
5160impl EncodingMode {
5161 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
5162 match type_.constructors.as_slice() {
5163 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
5164 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
5165 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
5166 EncodingMode::PlainString
5167 }
5168 _constructors => EncodingMode::ObjectWithTypeTag,
5169 }
5170 }
5171}
5172
5173impl<'a> GenerateJsonEncoder<'a> {
5174 pub fn new(
5175 module: &'a Module,
5176 line_numbers: &'a LineNumbers,
5177 params: &'a CodeActionParams,
5178 actions: &'a mut Vec<CodeAction>,
5179 config: &'a PackageConfig,
5180 ) -> Self {
5181 // Since we are generating a new function, type variables from other
5182 // functions and constants are irrelevant to the types we print.
5183 let printer = Printer::new_without_type_variables(&module.ast.names);
5184 Self {
5185 module,
5186 params,
5187 edits: TextEdits::new(line_numbers),
5188 printer,
5189 actions,
5190 config,
5191 }
5192 }
5193
5194 pub fn code_actions(&mut self) {
5195 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
5196 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
5197 {
5198 self.visit_typed_module(&self.module.ast);
5199 }
5200 }
5201
5202 fn custom_type_encoder_body(
5203 &mut self,
5204 record_name: EcoString,
5205 custom_type: &CustomType<Arc<Type>>,
5206 ) -> Option<EcoString> {
5207 // We cannot generate a decoder for an external type with no constructors!
5208 let constructors_size = custom_type.constructors.len();
5209 let (first, rest) = custom_type.constructors.split_first()?;
5210 let mode = EncodingMode::for_custom_type(custom_type);
5211
5212 // We generate an encoder for a type with a single constructor: it does not
5213 // require pattern matching on the argument as we can access all its fields
5214 // with the usual record access syntax.
5215 if rest.is_empty() {
5216 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
5217 let unpacking = if first.arguments.is_empty() {
5218 ""
5219 } else {
5220 &eco_format!(
5221 "let {name}({fields}:) = {record_name}\n ",
5222 name = first.name,
5223 fields = first
5224 .arguments
5225 .iter()
5226 .filter_map(|argument| {
5227 argument.label.as_ref().map(|(_location, label)| label)
5228 })
5229 .join(":, ")
5230 )
5231 };
5232 return Some(eco_format!("{unpacking}{encoder}"));
5233 }
5234
5235 // Otherwise we generate an encoder for a type with multiple constructors:
5236 // it will need to pattern match on the various constructors and encode each
5237 // one separately.
5238 let mut clauses = Vec::with_capacity(constructors_size);
5239 for constructor in iter::once(first).chain(rest) {
5240 let RecordConstructor { name, .. } = constructor;
5241 let encoder =
5242 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
5243 if constructor.arguments.is_empty() {
5244 clauses.push(eco_format!(" {name} -> {encoder}"));
5245 } else {
5246 let unpacking = constructor
5247 .arguments
5248 .iter()
5249 .filter_map(|argument| {
5250 argument.label.as_ref().map(|(_location, label)| {
5251 if is_nil_like(&argument.type_) {
5252 eco_format!("{}: _", label)
5253 } else {
5254 eco_format!("{}:", label)
5255 }
5256 })
5257 })
5258 .join(", ");
5259 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}"));
5260 }
5261 }
5262
5263 let clauses = clauses.join("\n");
5264 Some(eco_format!(
5265 "case {record_name} {{
5266{clauses}
5267 }}",
5268 ))
5269 }
5270
5271 fn constructor_encoder(
5272 &mut self,
5273 mode: EncodingMode,
5274 constructor: &TypedRecordConstructor,
5275 type_name: EcoString,
5276 nesting: usize,
5277 ) -> Option<EcoString> {
5278 let json_module = self.printer.print_module(JSON_MODULE);
5279 let tag = to_snake_case(&constructor.name);
5280 let indent = " ".repeat(nesting);
5281
5282 // If the variant is encoded as a simple json string we just call the
5283 // `json.string` with the variant tag as an argument.
5284 if mode == EncodingMode::PlainString {
5285 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
5286 }
5287
5288 // Otherwise we turn it into an object with a `type` tag field.
5289 let mut encoder_printer =
5290 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone());
5291
5292 // These are the fields of the json object to encode.
5293 let mut fields = Vec::with_capacity(constructor.arguments.len());
5294 if mode == EncodingMode::ObjectWithTypeTag {
5295 // Any needed type tag is always going to be the first field in the object
5296 fields.push(eco_format!(
5297 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
5298 ));
5299 }
5300
5301 for argument in constructor.arguments.iter() {
5302 let (_, label) = argument.label.as_ref()?;
5303 let field = RecordField {
5304 label: RecordLabel::Labeled(label),
5305 type_: &argument.type_,
5306 };
5307 let encoder = encoder_printer.encode_field(&field, nesting + 2);
5308 fields.push(encoder);
5309 }
5310
5311 let fields = fields.join(",\n");
5312 Some(eco_format!(
5313 "{json_module}.object([
5314{fields},
5315{indent}])"
5316 ))
5317 }
5318}
5319
5320/// When generating an encoder, we need to know when we can ignore the fields
5321/// destructured from a constructor.
5322/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil`
5323/// types, then we will never need to use the field in our encoder function.
5324fn is_nil_like(type_: &Type) -> bool {
5325 if type_.is_nil() {
5326 return true;
5327 }
5328
5329 match type_.tuple_types() {
5330 Some(types) => types.iter().all(|type_| is_nil_like(type_)),
5331 _ => false,
5332 }
5333}
5334
5335impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
5336 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
5337 let range = self
5338 .edits
5339 .src_span_to_lsp_range(custom_type.full_location());
5340 if !within(self.params.range, range) {
5341 return;
5342 }
5343
5344 let record_name = to_snake_case(&custom_type.name);
5345 let name = eco_format!("{record_name}_to_json");
5346 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
5347 return;
5348 };
5349
5350 let json_type = self.printer.print_type(&Type::Named {
5351 publicity: Publicity::Public,
5352 package: JSON_PACKAGE_NAME.into(),
5353 module: JSON_MODULE.into(),
5354 name: "Json".into(),
5355 arguments: vec![],
5356 inferred_variant: None,
5357 });
5358
5359 let type_ = if custom_type.parameters.is_empty() {
5360 custom_type.name.clone()
5361 } else {
5362 let parameters = custom_type
5363 .parameters
5364 .iter()
5365 .map(|(_, name)| name)
5366 .join(", ");
5367 eco_format!("{}({})", custom_type.name, parameters)
5368 };
5369
5370 let function = format!(
5371 "
5372
5373fn {name}({record_name}: {type_}) -> {json_type} {{
5374 {encoder}
5375}}",
5376 );
5377
5378 self.edits.insert(custom_type.end_position, function);
5379 maybe_import(&mut self.edits, self.module, JSON_MODULE);
5380
5381 CodeActionBuilder::new("Generate to-JSON function")
5382 .kind(CodeActionKind::Refactor)
5383 .preferred(false)
5384 .changes(
5385 self.params.text_document.uri.clone(),
5386 std::mem::take(&mut self.edits.edits),
5387 )
5388 .push_to(self.actions);
5389 }
5390}
5391
5392struct JsonEncoderPrinter<'a, 'b> {
5393 printer: &'a mut Printer<'b>,
5394 /// The name of the root type we are printing an encoder for
5395 type_name: EcoString,
5396 /// The module name of the root type we are printing an encoder for
5397 type_module: EcoString,
5398}
5399
5400impl<'a, 'b> JsonEncoderPrinter<'a, 'b> {
5401 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self {
5402 Self {
5403 type_name,
5404 type_module,
5405 printer,
5406 }
5407 }
5408
5409 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
5410 let module_name = self.printer.print_module(JSON_MODULE);
5411 let is_capture = encoded_value == "_";
5412 let maybe_capture = |mut function: EcoString| {
5413 if is_capture {
5414 function
5415 } else {
5416 function.push('(');
5417 function.push_str(encoded_value);
5418 function.push(')');
5419 function
5420 }
5421 };
5422
5423 if type_.is_bool() {
5424 maybe_capture(eco_format!("{module_name}.bool"))
5425 } else if type_.is_float() {
5426 maybe_capture(eco_format!("{module_name}.float"))
5427 } else if type_.is_int() {
5428 maybe_capture(eco_format!("{module_name}.int"))
5429 } else if type_.is_string() {
5430 maybe_capture(eco_format!("{module_name}.string"))
5431 } else if type_.is_nil() {
5432 if is_capture {
5433 eco_format!("fn(_) {{ {module_name}.null() }}")
5434 } else {
5435 eco_format!("{module_name}.null()")
5436 }
5437 } else {
5438 match type_.tuple_types() {
5439 Some(types) => {
5440 let (tuple, new_indent) = if is_capture {
5441 ("value", indent + 4)
5442 } else {
5443 (encoded_value, indent + 2)
5444 };
5445
5446 // We need to iterate over all of the tuple's fields
5447 // to obtain an encoder for each one, so we reuse the
5448 // iteration to check whether this tuple can be ignored
5449 // in the encoder without calling `is_nil_like`.
5450 let mut encoders = Vec::new();
5451 let all_values_are_nil = types.iter().enumerate().fold(
5452 true,
5453 |all_values_are_nil, (index, type_)| {
5454 encoders.push(self.encoder_for(
5455 &format!("{tuple}.{index}"),
5456 type_,
5457 new_indent,
5458 ));
5459 all_values_are_nil && is_nil_like(type_)
5460 },
5461 );
5462
5463 if is_capture {
5464 eco_format!(
5465 "fn({value}) {{
5466{indent} {module_name}.preprocessed_array([
5467{indent} {encoders},
5468{indent} ])
5469{indent}}}",
5470 value = if all_values_are_nil { "_" } else { "value" },
5471 indent = " ".repeat(indent),
5472 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5473 )
5474 } else {
5475 eco_format!(
5476 "{module_name}.preprocessed_array([
5477{indent} {encoders},
5478{indent}])",
5479 indent = " ".repeat(indent),
5480 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5481 )
5482 }
5483 }
5484 _ => {
5485 let type_information = type_.named_type_information();
5486 let type_information: Option<(&str, &str, &[Arc<Type>])> =
5487 type_information.as_ref().map(|(module, name, arguments)| {
5488 (module.as_str(), name.as_str(), arguments.as_slice())
5489 });
5490
5491 match type_information {
5492 Some(("gleam", "List", [element])) => {
5493 eco_format!(
5494 "{module_name}.array({encoded_value}, {map_function})",
5495 map_function = self.encoder_for("_", element, indent)
5496 )
5497 }
5498 Some(("gleam/option", "Option", [some])) => {
5499 eco_format!(
5500 "case {encoded_value} {{
5501{indent} {none} -> {module_name}.null()
5502{indent} {some}({value}) -> {encoder}
5503{indent}}}",
5504 indent = " ".repeat(indent),
5505 none = self
5506 .printer
5507 .print_constructor(&"gleam/option".into(), &"None".into()),
5508 some = self
5509 .printer
5510 .print_constructor(&"gleam/option".into(), &"Some".into()),
5511 value = if is_nil_like(some) { "_" } else { "value" },
5512 encoder = self.encoder_for("value", some, indent + 2)
5513 )
5514 }
5515 Some(("gleam/dict", "Dict", [key, value])) => {
5516 let stringify_function = match key
5517 .named_type_information()
5518 .as_ref()
5519 .map(|(module, name, arguments)| {
5520 (module.as_str(), name.as_str(), arguments.as_slice())
5521 }) {
5522 Some(("gleam", "String", [])) => "fn(string) { string }",
5523 _ => &format!(
5524 r#"todo as "Function to stringify {}""#,
5525 self.printer.print_type(key)
5526 ),
5527 };
5528 eco_format!(
5529 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
5530 self.encoder_for("_", value, indent)
5531 )
5532 }
5533 Some((module, name, _))
5534 if module == self.type_module && name == self.type_name =>
5535 {
5536 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
5537 }
5538 _ => eco_format!(
5539 r#"todo as "Encoder for {}""#,
5540 self.printer.print_type(type_)
5541 ),
5542 }
5543 }
5544 }
5545 }
5546 }
5547
5548 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
5549 let field_name = field.label.variable_name();
5550 let encoder = self.encoder_for(&field_name, field.type_, indent);
5551
5552 eco_format!(
5553 r#"{indent}#("{field_name}", {encoder})"#,
5554 indent = " ".repeat(indent),
5555 )
5556 }
5557}
5558
5559/// Builder for code action to pattern match on things like (anonymous) function
5560/// arguments or variables.
5561/// For example:
5562///
5563/// ```gleam
5564/// pub fn wibble(arg: #(key, value)) {
5565/// // ^ [pattern match on argument]
5566/// }
5567///
5568/// // Generates
5569///
5570/// pub fn wibble(arg: #(key, value)) {
5571/// let #(value_0, value_1) = arg
5572/// }
5573/// ```
5574///
5575/// Another example with variables:
5576///
5577/// ```gleam
5578/// pub fn main() {
5579/// let pair = #(1, 3)
5580/// // ^ [pattern match on value]
5581/// }
5582///
5583/// // Generates
5584///
5585/// pub fn main() {
5586/// let pair = #(1, 3)
5587/// let #(value_0, value_1) = pair
5588/// }
5589/// ```
5590///
5591pub struct PatternMatchOnValue<'a, A> {
5592 module: &'a Module,
5593 params: &'a CodeActionParams,
5594 compiler: &'a LspProjectCompiler<A>,
5595 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>,
5596 selected_value: Option<PatternMatchedValue<'a>>,
5597 edits: TextEdits<'a>,
5598}
5599
5600/// A value we might want to pattern match on.
5601/// Each variant will also contain all the info needed to know how to properly
5602/// print and format the corresponding pattern matching code; that's why you'll
5603/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
5604///
5605#[derive(Clone)]
5606pub enum PatternMatchedValue<'a> {
5607 /// A statement we can match on. For example, function calls, record
5608 /// and tuple accesses:
5609 ///
5610 /// ```gleam
5611 /// pub fn wibble() {
5612 /// wobble(1, 2)
5613 /// //^^^^ This
5614 /// }
5615 /// ```
5616 ///
5617 Statement {
5618 /// The span covering the entire statement:
5619 /// ```gleam
5620 /// wobble(1, 2)
5621 /// //^^^^^^^^^^^^ This
5622 /// ```
5623 location: SrcSpan,
5624 /// The statement's type defining what we will be pattern matching
5625 /// on.
5626 type_: Arc<Type>,
5627 },
5628 FunctionArgument {
5629 /// The argument being pattern matched on.
5630 ///
5631 arg: &'a TypedArg,
5632 /// The first statement inside the function body. Used to correctly
5633 /// position the inserted pattern matching.
5634 ///
5635 first_statement: &'a TypedStatement,
5636 /// The range of the entire function holding the argument.
5637 ///
5638 function_range: Range,
5639 },
5640 LetVariable {
5641 variable_name: &'a EcoString,
5642 variable_type: Arc<Type>,
5643 /// The location of the entire let assignment the variable is part of,
5644 /// so that we can add the pattern matching _after_ it.
5645 ///
5646 assignment_location: SrcSpan,
5647 },
5648 /// A variable that is bound in a case branch's pattern. For example:
5649 /// ```gleam
5650 /// case wibble {
5651 /// wobble -> 1
5652 /// // ^^^^^ This!
5653 /// }
5654 /// ```
5655 ///
5656 ClausePatternVariable {
5657 variable_type: Arc<Type>,
5658 variable_location: PatternLocation,
5659 clause_location: SrcSpan,
5660 /// All the names in the clause that are already taken by pattern variables.
5661 /// We need this to avoid generating invalid code were two pattern variables
5662 /// have the same name.
5663 ///
5664 /// For example:
5665 ///
5666 /// ```gleam
5667 /// case wibble {
5668 /// [first, ..rest] -> todo
5669 /// ^^^^^ When expanding `first` we can't add any variable pattern
5670 /// called `rest` as it would clash with the `rest` tail that is
5671 /// already there.
5672 /// }
5673 /// ```
5674 ///
5675 bound_variables: Vec<BoundVariable>,
5676 },
5677 UseVariable {
5678 variable_name: &'a EcoString,
5679 variable_type: Arc<Type>,
5680 /// The location of the entire use expression the variable is part of,
5681 /// so that we can add the pattern matching _after_ it.
5682 ///
5683 use_location: SrcSpan,
5684 },
5685}
5686
5687#[derive(Clone)]
5688pub enum PatternLocation {
5689 /// Any pattern that doesn't need any special handling.
5690 ///
5691 Regular { location: SrcSpan },
5692 /// List tails need some care to not generate invalid syntax when pattern
5693 /// matched on in case expressions.
5694 ///
5695 ListTail {
5696 /// This location covers the entire list tail pattern, including the `..`
5697 location: SrcSpan,
5698 },
5699 /// When the pattern being matched is a discard. For example:
5700 /// ```gleam
5701 /// case wibble {
5702 /// Ok(_) -> todo
5703 /// // ^ Hovering this!
5704 /// }
5705 /// ```
5706 Discard { location: SrcSpan },
5707}
5708
5709impl PatternLocation {
5710 fn regular(location: SrcSpan) -> Self {
5711 Self::Regular { location }
5712 }
5713
5714 fn is_discard(&self) -> bool {
5715 match self {
5716 PatternLocation::Regular { .. } | PatternLocation::ListTail { .. } => false,
5717 PatternLocation::Discard { .. } => true,
5718 }
5719 }
5720}
5721
5722impl<'a, IO> PatternMatchOnValue<'a, IO> {
5723 pub fn new(
5724 module: &'a Module,
5725 line_numbers: &'a LineNumbers,
5726 params: &'a CodeActionParams,
5727 compiler: &'a LspProjectCompiler<IO>,
5728 ) -> Self {
5729 Self {
5730 module,
5731 params,
5732 compiler,
5733 selected_value: None,
5734 pattern_variable_under_cursor: None,
5735 edits: TextEdits::new(line_numbers),
5736 }
5737 }
5738
5739 pub fn code_actions(mut self) -> Vec<CodeAction> {
5740 self.visit_typed_module(&self.module.ast);
5741
5742 let action_title = match self.selected_value.clone() {
5743 Some(PatternMatchedValue::FunctionArgument {
5744 arg,
5745 first_statement: function_body,
5746 function_range,
5747 }) => {
5748 self.match_on_function_argument(arg, function_body, function_range);
5749 "Pattern match on argument"
5750 }
5751 Some(
5752 PatternMatchedValue::LetVariable {
5753 variable_name,
5754 variable_type,
5755 assignment_location: location,
5756 }
5757 | PatternMatchedValue::UseVariable {
5758 variable_name,
5759 variable_type,
5760 use_location: location,
5761 },
5762 ) => {
5763 self.match_on_let_variable(variable_name, variable_type, location);
5764 "Pattern match on variable"
5765 }
5766
5767 Some(PatternMatchedValue::Statement { location, type_ }) => {
5768 self.match_on_statement(location, type_);
5769 "Pattern match on value"
5770 }
5771
5772 Some(PatternMatchedValue::ClausePatternVariable {
5773 variable_type,
5774 variable_location,
5775 clause_location,
5776 bound_variables,
5777 }) => {
5778 let title = if variable_location.is_discard() {
5779 "Pattern match on value"
5780 } else {
5781 "Pattern match on variable"
5782 };
5783
5784 self.match_on_clause_variable(
5785 variable_type,
5786 variable_location,
5787 clause_location,
5788 &bound_variables,
5789 );
5790
5791 title
5792 }
5793
5794 None => return vec![],
5795 };
5796
5797 if self.edits.edits.is_empty() {
5798 return vec![];
5799 }
5800
5801 let mut action = Vec::with_capacity(1);
5802 CodeActionBuilder::new(action_title)
5803 .kind(CodeActionKind::RefactorRewrite)
5804 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5805 .preferred(false)
5806 .push_to(&mut action);
5807 action
5808 }
5809
5810 fn match_on_function_argument(
5811 &mut self,
5812 arg: &TypedArg,
5813 first_statement: &TypedStatement,
5814 function_range: Range,
5815 ) {
5816 let Some(arg_name) = arg.get_variable_name() else {
5817 return;
5818 };
5819
5820 let Some(patterns) =
5821 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new())
5822 else {
5823 return;
5824 };
5825
5826 let first_statement_location = first_statement.location();
5827 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
5828
5829 // If we're trying to insert the pattern matching on the same
5830 // line as the one where the function is defined we will want to
5831 // put it on a new line instead. So in that case the nesting will
5832 // be the default 2 spaces.
5833 let needs_newline = function_range.start.line == first_statement_range.start.line;
5834 let nesting = if needs_newline {
5835 String::from(" ")
5836 } else {
5837 " ".repeat(first_statement_range.start.character as usize)
5838 };
5839
5840 let pattern_matching = if patterns.len() == 1 {
5841 let pattern = patterns.first();
5842 format!("let {pattern} = {arg_name}")
5843 } else {
5844 let patterns = patterns
5845 .iter()
5846 .map(|p| format!(" {nesting}{p} -> todo"))
5847 .join("\n");
5848 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
5849 };
5850
5851 let pattern_matching = if needs_newline {
5852 format!("\n{nesting}{pattern_matching}")
5853 } else {
5854 pattern_matching
5855 };
5856
5857 let has_empty_body = match first_statement {
5858 ast::Statement::Expression(TypedExpr::Todo {
5859 kind: TodoKind::EmptyFunction { .. },
5860 ..
5861 }) => true,
5862 ast::Statement::Expression(_)
5863 | ast::Statement::Assignment(_)
5864 | ast::Statement::Use(_)
5865 | ast::Statement::Assert(_) => false,
5866 };
5867
5868 // If the pattern matching is added to a function with an empty
5869 // body then we do not add any nesting after it, or we would be
5870 // increasing the nesting of the closing `}`!
5871 let pattern_matching = if has_empty_body {
5872 format!("{pattern_matching}\n")
5873 } else {
5874 format!("{pattern_matching}\n{nesting}")
5875 };
5876
5877 self.edits
5878 .insert(first_statement_location.start, pattern_matching);
5879 }
5880
5881 fn match_on_let_variable(
5882 &mut self,
5883 variable_name: &EcoString,
5884 variable_type: Arc<Type>,
5885 assignment_location: SrcSpan,
5886 ) {
5887 let Some(patterns) =
5888 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new())
5889 else {
5890 return;
5891 };
5892
5893 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
5894 let nesting = " ".repeat(assignment_range.start.character as usize);
5895 let pattern_matching = if patterns.len() == 1 {
5896 let pattern = patterns.first();
5897 format!("let {pattern} = {variable_name}")
5898 } else {
5899 let patterns = patterns
5900 .iter()
5901 .map(|p| format!(" {nesting}{p} -> todo"))
5902 .join("\n");
5903 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
5904 };
5905
5906 self.edits.insert(
5907 assignment_location.end,
5908 format!("\n{nesting}{pattern_matching}"),
5909 );
5910 }
5911
5912 fn match_on_statement(&mut self, statement_location: SrcSpan, type_: Arc<Type>) {
5913 let Some(patterns) =
5914 self.type_to_destructure_patterns(type_.as_ref(), &mut NameGenerator::new())
5915 else {
5916 return;
5917 };
5918
5919 if patterns.len() == 1 {
5920 let pattern = patterns.first();
5921 self.edits
5922 .insert(statement_location.start, format!("let {pattern} = "));
5923 } else {
5924 let statement_range = self.edits.src_span_to_lsp_range(statement_location);
5925 let nesting = " ".repeat(statement_range.start.character as usize);
5926 let patterns = patterns
5927 .iter()
5928 .map(|p| format!(" {nesting}{p} -> todo"))
5929 .join("\n");
5930 self.edits.insert(statement_location.start, "case ".into());
5931 self.edits.insert(
5932 statement_location.end,
5933 format!(" {{\n{patterns}\n{nesting}}}"),
5934 );
5935 }
5936 }
5937
5938 fn match_on_clause_variable(
5939 &mut self,
5940 variable_type: Arc<Type>,
5941 variable_location: PatternLocation,
5942 clause_location: SrcSpan,
5943 bound_variables: &[BoundVariable],
5944 ) {
5945 let mut names = NameGenerator::new();
5946 names.reserve_bound_variables(bound_variables);
5947
5948 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) {
5949 // Here we're dealing with a special case: if someone wants to expand the tail
5950 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`.
5951 // That would result in invalid syntax. So we have to generate list patterns
5952 // that have no square brackets.
5953 let first = names.rename_to_avoid_shadowing("first".into());
5954 let rest = names.rename_to_avoid_shadowing("rest".into());
5955 vec1!["".into(), eco_format!("{first}, ..{rest}")]
5956 } else if let Some(patterns) =
5957 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names)
5958 {
5959 patterns
5960 } else {
5961 return;
5962 };
5963
5964 let clause_range = self.edits.src_span_to_lsp_range(clause_location);
5965 let nesting = " ".repeat(clause_range.start.character as usize);
5966
5967 let variable_location = match variable_location {
5968 PatternLocation::Regular { location }
5969 | PatternLocation::ListTail { location }
5970 | PatternLocation::Discard { location } => location,
5971 };
5972
5973 let variable_start = (variable_location.start - clause_location.start) as usize;
5974 let variable_end = variable_start + variable_location.len();
5975
5976 let clause_code = code_at(self.module, clause_location);
5977 let patterns = patterns
5978 .iter()
5979 .map(|pattern| {
5980 let mut clause_code = clause_code.to_string();
5981 // If we're replacing a variable that's using the shorthand
5982 // syntax we want to add a space to separate it from the
5983 // preceding `:`.
5984 let pattern = if variable_start == variable_end {
5985 &eco_format!(" {pattern}")
5986 } else {
5987 pattern
5988 };
5989
5990 clause_code.replace_range(variable_start..variable_end, pattern);
5991 clause_code
5992 })
5993 .join(&format!("\n{nesting}"));
5994
5995 self.edits.replace(clause_location, patterns);
5996 }
5997
5998 /// Will produce a pattern that can be used on the left hand side of a let
5999 /// assignment to destructure a value of the given type. For example given
6000 /// this type:
6001 ///
6002 /// ```gleam
6003 /// pub type Wibble {
6004 /// Wobble(Int, label: String)
6005 /// }
6006 /// ```
6007 ///
6008 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
6009 /// The pattern will use the correct qualified/unqualified name for the
6010 /// constructor if it comes from another package.
6011 ///
6012 /// The function will only produce a list of patterns that can be used from
6013 /// the current module. So if the type comes from another module it must be
6014 /// public! Otherwise this function will return an empty vec.
6015 ///
6016 fn type_to_destructure_patterns(
6017 &mut self,
6018 type_: &Type,
6019 names: &mut NameGenerator,
6020 ) -> Option<Vec1<EcoString>> {
6021 match type_ {
6022 Type::Fn { .. } => None,
6023
6024 // Pattern matchin on `Nil` is not all that useful.
6025 Type::Named { .. } if type_.is_nil() => None,
6026
6027 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names),
6028
6029 // We special case lists, they don't have "regular" constructors
6030 // like other types. Instead we always add the two clauses covering
6031 // the empty and non empty list.
6032 Type::Named { .. } if type_.is_list() => {
6033 let first = names.rename_to_avoid_shadowing("first".into());
6034 let rest = names.rename_to_avoid_shadowing("rest".into());
6035 Some(vec1![
6036 EcoString::from("[]"),
6037 eco_format!("[{first}, ..{rest}]")
6038 ])
6039 }
6040
6041 Type::Named {
6042 module: type_module,
6043 name: type_name,
6044 ..
6045 } => {
6046 let mut patterns = vec![];
6047 let constructors =
6048 get_type_constructors(self.compiler, &self.module.name, type_module, type_name);
6049 for constructor in constructors {
6050 let names_before = names.clone();
6051 if let Some(pattern) =
6052 self.record_constructor_to_destructure_pattern(constructor, names)
6053 {
6054 patterns.push(pattern);
6055 }
6056 *names = names_before;
6057 }
6058
6059 Vec1::try_from_vec(patterns).ok()
6060 }
6061
6062 // We don't want to suggest this action for empty tuple as it
6063 // doesn't make a lot of sense to match on those.
6064 Type::Tuple { elements } if elements.is_empty() => None,
6065 Type::Tuple { elements } => {
6066 let elements = elements
6067 .iter()
6068 .map(|element| names.generate_name_from_type(element))
6069 .join(", ");
6070 Some(vec1![eco_format!("#({elements})")])
6071 }
6072 }
6073 }
6074
6075 fn type_var_to_destructure_patterns(
6076 &mut self,
6077 type_var: &TypeVar,
6078 names: &mut NameGenerator,
6079 ) -> Option<Vec1<EcoString>> {
6080 match type_var {
6081 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
6082 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names),
6083 }
6084 }
6085
6086 /// Given the value constructor of a record, returns a string with the
6087 /// pattern used to match on that specific variant.
6088 ///
6089 /// Note how:
6090 /// - If the constructor is internal to another module or comes from another
6091 /// module, then this returns `None` since one cannot pattern match on it.
6092 /// - If the provided `ValueConstructor` is not a record constructor this
6093 /// will return `None`.
6094 ///
6095 fn record_constructor_to_destructure_pattern(
6096 &self,
6097 constructor: &ValueConstructor,
6098 names: &mut NameGenerator,
6099 ) -> Option<EcoString> {
6100 let type_::ValueConstructorVariant::Record {
6101 name: constructor_name,
6102 arity: constructor_arity,
6103 module: constructor_module,
6104 field_map,
6105 ..
6106 } = &constructor.variant
6107 else {
6108 // The constructor should always be a record, in case it's not
6109 // there's not much we can do and just fail.
6110 return None;
6111 };
6112
6113 // Since the constructor is a record constructor we know that its type
6114 // is either `Named` or a `Fn` type, in either case we have to get the
6115 // arguments types out of it.
6116 let Some(arguments_types) = constructor
6117 .type_
6118 .fn_types()
6119 .map(|(arguments_types, _return)| arguments_types)
6120 .or_else(|| constructor.type_.constructor_types())
6121 else {
6122 // This should never happen but just in case we don't want to unwrap
6123 // and panic.
6124 return None;
6125 };
6126
6127 let index_to_label = match field_map {
6128 None => HashMap::new(),
6129 Some(field_map) => {
6130 names.reserve_all_labels(field_map);
6131
6132 field_map
6133 .fields
6134 .iter()
6135 .map(|(label, index)| (index, label))
6136 .collect::<HashMap<_, _>>()
6137 }
6138 };
6139
6140 let mut pattern =
6141 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
6142
6143 if *constructor_arity == 0 {
6144 return Some(pattern);
6145 }
6146
6147 pattern.push('(');
6148 let arguments = (0..*constructor_arity as u32)
6149 .map(|i| match index_to_label.get(&i) {
6150 Some(label) => eco_format!("{label}:"),
6151 None => match arguments_types.get(i as usize) {
6152 None => names.rename_to_avoid_shadowing(EcoString::from("value")),
6153 Some(type_) => names.generate_name_from_type(type_),
6154 },
6155 })
6156 .join(", ");
6157
6158 pattern.push_str(&arguments);
6159 pattern.push(')');
6160 Some(pattern)
6161 }
6162}
6163
6164fn code_at(module: &Module, span: SrcSpan) -> &str {
6165 module
6166 .code
6167 .get(span.start as usize..span.end as usize)
6168 .expect("code location must be valid")
6169}
6170
6171impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> {
6172 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6173 // If we're not inside the function there's no point in exploring its
6174 // ast further.
6175 let function_span = SrcSpan {
6176 start: fun.location.start,
6177 end: fun.end_position,
6178 };
6179 let function_range = self.edits.src_span_to_lsp_range(function_span);
6180 if !within(self.params.range, function_range) {
6181 return;
6182 }
6183
6184 for arg in &fun.arguments {
6185 // If the cursor is placed on one of the arguments, then we can try
6186 // and generate code for that one.
6187 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
6188 if within(self.params.range, arg_range)
6189 && let Some(first_statement) = fun.body.first()
6190 {
6191 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
6192 arg,
6193 first_statement,
6194 function_range,
6195 });
6196 return;
6197 }
6198 }
6199
6200 // If the cursor is not on any of the function arguments then we keep
6201 // exploring the function body as we might want to destructure the
6202 // argument of an expression function!
6203 ast::visit::visit_typed_function(self, fun);
6204 }
6205
6206 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
6207 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
6208 if !within(self.params.range, statement_range) {
6209 return;
6210 }
6211
6212 ast::visit::visit_typed_statement(self, statement);
6213 match statement {
6214 // If we haven't found any more specific selected expression, then
6215 // we are going to select the statement to pattern match on (if it
6216 // can be meaningfully pattern matched on).
6217 ast::Statement::Expression(
6218 TypedExpr::Call { type_, .. }
6219 | TypedExpr::ModuleSelect { type_, .. }
6220 | TypedExpr::RecordAccess { type_, .. }
6221 | TypedExpr::TupleIndex { type_, .. },
6222 ) if self.selected_value.is_none() => {
6223 self.selected_value = Some(PatternMatchedValue::Statement {
6224 location: statement.location(),
6225 type_: type_.clone(),
6226 });
6227 }
6228
6229 ast::Statement::Expression(_)
6230 | ast::Statement::Assignment(_)
6231 | ast::Statement::Use(_)
6232 | ast::Statement::Assert(_) => (),
6233 }
6234 }
6235
6236 fn visit_typed_expr_fn(
6237 &mut self,
6238 location: &'ast SrcSpan,
6239 type_: &'ast Arc<Type>,
6240 kind: &'ast FunctionLiteralKind,
6241 arguments: &'ast [TypedArg],
6242 body: &'ast Vec1<TypedStatement>,
6243 return_annotation: &'ast Option<ast::TypeAst>,
6244 ) {
6245 // If we're not inside the function there's no point in exploring its
6246 // ast further.
6247 let function_range = self.edits.src_span_to_lsp_range(*location);
6248 if !within(self.params.range, function_range) {
6249 return;
6250 }
6251
6252 for argument in arguments {
6253 // If the cursor is placed on one of the arguments, then we can try
6254 // and generate code for that one.
6255 let arg_range = self.edits.src_span_to_lsp_range(argument.location);
6256 if within(self.params.range, arg_range) {
6257 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
6258 arg: argument,
6259 first_statement: body.first(),
6260 function_range,
6261 });
6262 return;
6263 }
6264 }
6265
6266 // If the cursor is not on any of the function arguments then we keep
6267 // exploring the function body as we might want to destructure the
6268 // argument of an expression function!
6269 ast::visit::visit_typed_expr_fn(
6270 self,
6271 location,
6272 type_,
6273 kind,
6274 arguments,
6275 body,
6276 return_annotation,
6277 );
6278 }
6279
6280 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
6281 // If we're not inside the assignment there's no point in exploring its
6282 // ast further.
6283 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
6284 if !within(self.params.range, assignment_range) {
6285 return;
6286 }
6287
6288 ast::visit::visit_typed_assignment(self, assignment);
6289 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor
6290 // We must make sure that no other value was selected while visiting
6291 // this. If it were `Some` that means that we have found _another_
6292 // variable to match on inside the assignmemt itself. For example:
6293 // ```gleam
6294 // let a = {
6295 // let b = todo
6296 // // ^ We're matching on this, not the outer one!
6297 // }
6298 // ```
6299 && self.selected_value.is_none()
6300 {
6301 self.selected_value = Some(PatternMatchedValue::LetVariable {
6302 variable_name: name,
6303 variable_type: type_.clone(),
6304 assignment_location: assignment.location,
6305 });
6306 }
6307 }
6308
6309 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
6310 // If we're not inside the clause there's no point in exploring its
6311 // ast further.
6312 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
6313 if !within(self.params.range, clause_range) {
6314 return;
6315 }
6316
6317 for pattern in clause.pattern.iter() {
6318 self.visit_typed_pattern(pattern);
6319 }
6320 for patterns in clause.alternative_patterns.iter() {
6321 for pattern in patterns {
6322 self.visit_typed_pattern(pattern);
6323 }
6324 }
6325
6326 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() {
6327 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable {
6328 variable_type: type_,
6329 variable_location,
6330 clause_location: clause.location(),
6331 bound_variables: clause.bound_variables().collect_vec(),
6332 });
6333 } else {
6334 self.visit_typed_expr(&clause.then);
6335 }
6336 }
6337
6338 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
6339 if let Some(assignments) = use_.callback_arguments() {
6340 for variable in assignments {
6341 let ast::Arg {
6342 names: ArgNames::Named { name, .. },
6343 location: variable_location,
6344 type_,
6345 ..
6346 } = variable
6347 else {
6348 continue;
6349 };
6350
6351 // If we use a pattern in a use assignment, that will end up
6352 // being called `_use` something. We don't want to offer the
6353 // action when hovering a pattern so we ignore those.
6354 if name.starts_with("_use") {
6355 continue;
6356 }
6357
6358 let variable_range = self.edits.src_span_to_lsp_range(*variable_location);
6359 if within(self.params.range, variable_range) {
6360 self.selected_value = Some(PatternMatchedValue::UseVariable {
6361 variable_name: name,
6362 variable_type: type_.clone(),
6363 use_location: use_.location,
6364 });
6365 // If we've found the variable to pattern match on, there's no
6366 // point in keeping traversing the AST.
6367 return;
6368 }
6369 }
6370 }
6371
6372 ast::visit::visit_typed_use(self, use_);
6373 }
6374
6375 fn visit_typed_pattern_discard(
6376 &mut self,
6377 location: &'ast SrcSpan,
6378 name: &'ast EcoString,
6379 type_: &'ast Arc<Type>,
6380 ) {
6381 if within(
6382 self.params.range,
6383 self.edits.src_span_to_lsp_range(*location),
6384 ) {
6385 let location = PatternLocation::Discard {
6386 location: *location,
6387 };
6388 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
6389 }
6390 }
6391
6392 fn visit_typed_pattern_variable(
6393 &mut self,
6394 location: &'ast SrcSpan,
6395 name: &'ast EcoString,
6396 type_: &'ast Arc<Type>,
6397 _origin: &'ast VariableOrigin,
6398 ) {
6399 if within(
6400 self.params.range,
6401 self.edits.src_span_to_lsp_range(*location),
6402 ) {
6403 let location = PatternLocation::regular(*location);
6404 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
6405 }
6406 }
6407
6408 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
6409 if let Some(name) = arg.label_shorthand_name()
6410 && within(
6411 self.params.range,
6412 self.edits.src_span_to_lsp_range(arg.location),
6413 )
6414 {
6415 let location = PatternLocation::regular(SrcSpan {
6416 start: arg.location.end,
6417 end: arg.location.end,
6418 });
6419 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_()));
6420 return;
6421 }
6422
6423 ast::visit::visit_typed_pattern_call_arg(self, arg);
6424 }
6425
6426 fn visit_typed_pattern_string_prefix(
6427 &mut self,
6428 _location: &'ast SrcSpan,
6429 _left_location: &'ast SrcSpan,
6430 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
6431 right_location: &'ast SrcSpan,
6432 _left_side_string: &'ast EcoString,
6433 right_side_assignment: &'ast AssignName,
6434 ) {
6435 if let Some((name, location)) = left_side_assignment
6436 && within(
6437 self.params.range,
6438 self.edits.src_span_to_lsp_range(*location),
6439 )
6440 {
6441 let location = PatternLocation::regular(*location);
6442 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6443 } else if let AssignName::Variable(name) = right_side_assignment
6444 && within(
6445 self.params.range,
6446 self.edits.src_span_to_lsp_range(*right_location),
6447 )
6448 {
6449 let location = PatternLocation::regular(*right_location);
6450 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6451 }
6452 }
6453
6454 fn visit_typed_pattern_list(
6455 &mut self,
6456 location: &'ast SrcSpan,
6457 elements: &'ast Vec<TypedPattern>,
6458 tail: &'ast Option<Box<TypedTailPattern>>,
6459 type_: &'ast Arc<Type>,
6460 ) {
6461 let (name, tail_location, tail_type) = if let Some(tail) = tail
6462 && let Pattern::Variable { name, type_, .. } = &tail.pattern
6463 {
6464 (name, tail.location, type_)
6465 } else {
6466 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6467 return;
6468 };
6469
6470 let tail_range = self.edits.src_span_to_lsp_range(tail_location);
6471 if !within(self.params.range, tail_range) {
6472 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6473 return;
6474 }
6475
6476 let location = PatternLocation::ListTail {
6477 location: tail_location,
6478 };
6479 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone()));
6480 }
6481}
6482
6483/// Given a type and its module, returns a list of its *importable*
6484/// constructors.
6485///
6486/// Since this focuses just on importable constructors, if either the module or
6487/// the type are internal the returned array will be empty!
6488///
6489fn get_type_constructors<'a, 'b, IO>(
6490 compiler: &'a LspProjectCompiler<IO>,
6491 current_module: &'b EcoString,
6492 type_module: &'b EcoString,
6493 type_name: &'b EcoString,
6494) -> Vec<&'a ValueConstructor> {
6495 let type_is_inside_current_module = current_module == type_module;
6496 let module_interface = if !type_is_inside_current_module {
6497 // If the type is outside of the module we're in, we can only pattern
6498 // match on it if the module can be imported.
6499 // The `get_module_interface` already takes care of making this check.
6500 compiler.get_module_interface(type_module)
6501 } else {
6502 // However, if the type is defined in the module we're in, we can always
6503 // pattern match on it. So we get the current module's interface.
6504 compiler
6505 .modules
6506 .get(current_module)
6507 .map(|module| &module.ast.type_info)
6508 };
6509
6510 let Some(module_interface) = module_interface else {
6511 return vec![];
6512 };
6513
6514 // If the type is in an internal module that is not the current one, we
6515 // cannot use its constructors!
6516 if !type_is_inside_current_module && module_interface.is_internal {
6517 return vec![];
6518 }
6519
6520 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
6521 return vec![];
6522 };
6523
6524 constructors
6525 .variants
6526 .iter()
6527 .filter_map(|variant| {
6528 let constructor = module_interface.values.get(&variant.name)?;
6529 if type_is_inside_current_module || constructor.publicity.is_public() {
6530 Some(constructor)
6531 } else {
6532 None
6533 }
6534 })
6535 .collect_vec()
6536}
6537
6538/// Returns a pretty printed record constructor name, the way it would be used
6539/// inside the given `module` (with the correct name and qualification).
6540///
6541/// If the constructor cannot be used inside the module because it's not
6542/// imported, then this function will return `None`.
6543///
6544fn pretty_constructor_name(
6545 module: &Module,
6546 constructor_module: &EcoString,
6547 constructor_name: &EcoString,
6548) -> Option<EcoString> {
6549 match module
6550 .ast
6551 .names
6552 .named_constructor(constructor_module, constructor_name)
6553 {
6554 type_::printer::NameContextInformation::Unimported(_, _) => None,
6555 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
6556 Some(eco_format!("{constructor_name}"))
6557 }
6558 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
6559 Some(eco_format!("{module_name}.{constructor_name}"))
6560 }
6561 }
6562}
6563
6564/// Builder for the "generate function" code action.
6565/// Whenever someone hovers an invalid expression that is inferred to have a
6566/// function type the language server can generate a function definition for it.
6567/// For example:
6568///
6569/// ```gleam
6570/// pub fn main() {
6571/// wibble(1, 2, "hello")
6572/// // ^ [generate function]
6573/// }
6574/// ```
6575///
6576/// Will generate the following definition:
6577///
6578/// ```gleam
6579/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
6580/// todo
6581/// }
6582/// ```
6583///
6584pub struct GenerateFunction<'a> {
6585 module: &'a Module,
6586 modules: &'a std::collections::HashMap<EcoString, Module>,
6587 params: &'a CodeActionParams,
6588 edits: TextEdits<'a>,
6589 last_visited_definition_end: Option<u32>,
6590 function_to_generate: Option<FunctionToGenerate<'a>>,
6591}
6592
6593struct FunctionToGenerate<'a> {
6594 module: Option<&'a str>,
6595 name: &'a str,
6596 arguments_types: Vec<Arc<Type>>,
6597
6598 /// The arguments actually supplied as input to the function, if any.
6599 /// A function to generate might as well be just a name passed as an argument
6600 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
6601 /// have any actual arguments!
6602 given_arguments: Option<&'a [TypedCallArg]>,
6603 return_type: Arc<Type>,
6604 previous_definition_end: Option<u32>,
6605}
6606
6607impl<'a> GenerateFunction<'a> {
6608 pub fn new(
6609 module: &'a Module,
6610 modules: &'a std::collections::HashMap<EcoString, Module>,
6611 line_numbers: &'a LineNumbers,
6612 params: &'a CodeActionParams,
6613 ) -> Self {
6614 Self {
6615 module,
6616 modules,
6617 params,
6618 edits: TextEdits::new(line_numbers),
6619 last_visited_definition_end: None,
6620 function_to_generate: None,
6621 }
6622 }
6623
6624 pub fn code_actions(mut self) -> Vec<CodeAction> {
6625 self.visit_typed_module(&self.module.ast);
6626
6627 let Some(
6628 function_to_generate @ FunctionToGenerate {
6629 module,
6630 previous_definition_end: Some(insert_at),
6631 ..
6632 },
6633 ) = self.function_to_generate.take()
6634 else {
6635 return vec![];
6636 };
6637
6638 if let Some(module) = module {
6639 if let Some(module) = self.modules.get(module) {
6640 let insert_at = module.code.len() as u32;
6641 self.code_action_for_module(
6642 module,
6643 Publicity::Public,
6644 function_to_generate,
6645 insert_at,
6646 )
6647 } else {
6648 Vec::new()
6649 }
6650 } else {
6651 let module = self.module;
6652 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at)
6653 }
6654 }
6655
6656 fn code_action_for_module(
6657 mut self,
6658 module: &'a Module,
6659 publicity: Publicity,
6660 function_to_generate: FunctionToGenerate<'a>,
6661 insert_at: u32,
6662 ) -> Vec<CodeAction> {
6663 let FunctionToGenerate {
6664 name,
6665 arguments_types,
6666 given_arguments,
6667 return_type,
6668 ..
6669 } = function_to_generate;
6670
6671 // This might be triggered on variants as well, in that case we don't
6672 // want to offer this action. The "generate variant" action will be
6673 // offered instead.
6674 if !is_valid_lowercase_name(name) {
6675 return vec![];
6676 }
6677
6678 // Labels do not share the same namespace as argument so we use two
6679 // separate generators to avoid renaming a label in case it shares a
6680 // name with an argument.
6681 let mut label_names = NameGenerator::new();
6682 let mut argument_names = NameGenerator::new();
6683
6684 // Since we are generating a new function, type variables from other
6685 // functions and constants are irrelevant to the types we print.
6686 let mut printer = Printer::new_without_type_variables(&module.ast.names);
6687 let arguments = arguments_types
6688 .iter()
6689 .enumerate()
6690 .map(|(index, argument_type)| {
6691 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
6692 let (label, name) =
6693 argument_names.generate_label_and_name(call_argument, argument_type);
6694 let pretty_type = printer.print_type(argument_type);
6695 if let Some(label) = label {
6696 let label = label_names.rename_to_avoid_shadowing(label);
6697 format!("{label} {name}: {pretty_type}")
6698 } else {
6699 format!("{name}: {pretty_type}")
6700 }
6701 })
6702 .join(", ");
6703
6704 let return_type = printer.print_type(&return_type);
6705
6706 let publicity = if publicity.is_public() { "pub " } else { "" };
6707
6708 // Make sure we use the line number information of the module we are
6709 // editing, which might not be the module where the code action is
6710 // triggered.
6711 self.edits.line_numbers = &module.ast.type_info.line_numbers;
6712 self.edits.insert(
6713 insert_at,
6714 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
6715 );
6716
6717 let Some(uri) = url_from_path(module.input_path.as_str()) else {
6718 return Vec::new();
6719 };
6720 let mut action = Vec::with_capacity(1);
6721 CodeActionBuilder::new("Generate function")
6722 .kind(CodeActionKind::QuickFix)
6723 .changes(uri, self.edits.edits)
6724 .preferred(true)
6725 .push_to(&mut action);
6726 action
6727 }
6728
6729 fn try_save_function_to_generate(
6730 &mut self,
6731 name: &'a EcoString,
6732 function_type: &Arc<Type>,
6733 given_arguments: Option<&'a [TypedCallArg]>,
6734 ) {
6735 match function_type.fn_types() {
6736 None => {}
6737 Some((arguments_types, return_type)) => {
6738 self.function_to_generate = Some(FunctionToGenerate {
6739 name,
6740 arguments_types,
6741 given_arguments,
6742 return_type,
6743 previous_definition_end: self.last_visited_definition_end,
6744 module: None,
6745 });
6746 }
6747 }
6748 }
6749
6750 fn try_save_function_from_other_module(
6751 &mut self,
6752 module: &'a str,
6753 name: &'a str,
6754 function_type: &Arc<Type>,
6755 given_arguments: Option<&'a [TypedCallArg]>,
6756 ) {
6757 if let Some((arguments_types, return_type)) = function_type.fn_types()
6758 && is_valid_lowercase_name(name)
6759 {
6760 self.function_to_generate = Some(FunctionToGenerate {
6761 name,
6762 arguments_types,
6763 given_arguments,
6764 return_type,
6765 previous_definition_end: self.last_visited_definition_end,
6766 module: Some(module),
6767 });
6768 }
6769 }
6770}
6771
6772impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
6773 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6774 self.last_visited_definition_end = Some(fun.end_position);
6775 ast::visit::visit_typed_function(self, fun);
6776 }
6777
6778 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
6779 self.last_visited_definition_end = Some(constant.value.location().end);
6780 ast::visit::visit_typed_module_constant(self, constant);
6781 }
6782
6783 fn visit_typed_expr_invalid(
6784 &mut self,
6785 location: &'ast SrcSpan,
6786 type_: &'ast Arc<Type>,
6787 extra_information: &'ast Option<InvalidExpression>,
6788 ) {
6789 let invalid_range = self.edits.src_span_to_lsp_range(*location);
6790 if within(self.params.range, invalid_range) {
6791 match extra_information {
6792 Some(InvalidExpression::ModuleSelect { module_name, label }) => {
6793 self.try_save_function_from_other_module(module_name, label, type_, None);
6794 }
6795 Some(InvalidExpression::UnknownVariable { name }) => {
6796 self.try_save_function_to_generate(name, type_, None);
6797 }
6798 None => {}
6799 }
6800 }
6801
6802 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
6803 }
6804
6805 fn visit_typed_constant_invalid(
6806 &mut self,
6807 location: &'ast SrcSpan,
6808 type_: &'ast Arc<Type>,
6809 extra_information: &'ast Option<InvalidExpression>,
6810 ) {
6811 let constant_range = self.edits.src_span_to_lsp_range(*location);
6812 if let Some(extra_information) = extra_information
6813 && within(self.params.range, constant_range)
6814 {
6815 match extra_information {
6816 InvalidExpression::ModuleSelect { module_name, label } => {
6817 self.try_save_function_from_other_module(module_name, label, type_, None);
6818 }
6819 InvalidExpression::UnknownVariable { name } => {
6820 self.try_save_function_to_generate(name, type_, None);
6821 }
6822 }
6823 }
6824 }
6825
6826 fn visit_typed_expr_call(
6827 &mut self,
6828 location: &'ast SrcSpan,
6829 type_: &'ast Arc<Type>,
6830 fun: &'ast TypedExpr,
6831 arguments: &'ast [TypedCallArg],
6832 argument_parentheses: &'ast Option<u32>,
6833 ) {
6834 // If the function being called is invalid we need to generate a
6835 // function that has the proper labels.
6836 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
6837
6838 if within(self.params.range, fun_range) {
6839 if !labels_are_correct(arguments) {
6840 return;
6841 }
6842
6843 match fun {
6844 TypedExpr::Invalid {
6845 type_,
6846 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }),
6847 location: _,
6848 } => {
6849 return self.try_save_function_from_other_module(
6850 module_name,
6851 label,
6852 type_,
6853 Some(arguments),
6854 );
6855 }
6856 TypedExpr::Invalid {
6857 type_,
6858 extra_information: Some(InvalidExpression::UnknownVariable { name }),
6859 location: _,
6860 } => {
6861 return self.try_save_function_to_generate(name, type_, Some(arguments));
6862 }
6863 TypedExpr::Int { .. }
6864 | TypedExpr::Float { .. }
6865 | TypedExpr::String { .. }
6866 | TypedExpr::Block { .. }
6867 | TypedExpr::Pipeline { .. }
6868 | TypedExpr::Var { .. }
6869 | TypedExpr::Fn { .. }
6870 | TypedExpr::List { .. }
6871 | TypedExpr::Call { .. }
6872 | TypedExpr::BinOp { .. }
6873 | TypedExpr::Case { .. }
6874 | TypedExpr::RecordAccess { .. }
6875 | TypedExpr::PositionalAccess { .. }
6876 | TypedExpr::ModuleSelect { .. }
6877 | TypedExpr::Tuple { .. }
6878 | TypedExpr::TupleIndex { .. }
6879 | TypedExpr::Todo { .. }
6880 | TypedExpr::Panic { .. }
6881 | TypedExpr::Echo { .. }
6882 | TypedExpr::BitArray { .. }
6883 | TypedExpr::RecordUpdate { .. }
6884 | TypedExpr::NegateBool { .. }
6885 | TypedExpr::NegateInt { .. }
6886 | TypedExpr::Invalid { .. } => {}
6887 }
6888 }
6889 ast::visit::visit_typed_expr_call(
6890 self,
6891 location,
6892 type_,
6893 fun,
6894 arguments,
6895 argument_parentheses,
6896 );
6897 }
6898}
6899
6900/// Builder for the "generate variant" code action. This will generate a variant
6901/// for a type if it can tell the type it should come from. It will work with
6902/// non-existing variants both used as expressions
6903///
6904/// ```gleam
6905/// let a = IDoNotExist(1)
6906/// // ^^^^^^^^^^^ It would generate this variant here
6907/// ```
6908///
6909/// And as patterns:
6910///
6911/// ```gleam
6912/// let assert IDoNotExist(1) = todo
6913/// ^^^^^^^^^^^ It would generate this variant here
6914/// ```
6915///
6916pub struct GenerateVariant<'a, IO> {
6917 module: &'a Module,
6918 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
6919 params: &'a CodeActionParams,
6920 line_numbers: &'a LineNumbers,
6921 variant_to_generate: Option<VariantToGenerate<'a>>,
6922}
6923
6924struct VariantToGenerate<'a> {
6925 name: &'a str,
6926
6927 arguments_types: Vec<Arc<Type>>,
6928
6929 /// The start of the variant where the code action was triggered.
6930 /// For example:
6931 ///
6932 /// ```gleam
6933 /// Wobble
6934 /// ^ Trigger here to generate `Wobble`
6935 /// ^ The start is here!
6936 /// ```
6937 variant_start: u32,
6938
6939 /// If the variant where we triggered the code action is already qualified.
6940 /// For example:
6941 ///
6942 /// ```gleam
6943 /// wibble.Wobble // -> true
6944 /// Wobble // -> false
6945 /// ```
6946 ///
6947 is_qualified: bool,
6948
6949 /// Where the custom type to add this variant to ends.
6950 ///
6951 end_position: u32,
6952
6953 /// The already existing constructors of the custom type this new variant is
6954 /// going to be added to.
6955 ///
6956 constructors: &'a [RecordConstructor<Arc<Type>>],
6957
6958 /// Wether the type we're adding the variant to is written with braces or
6959 /// not. We need this information to add braces when missing.
6960 ///
6961 type_braces: TypeBraces,
6962
6963 /// The module this variant will be added to.
6964 ///
6965 module_name: EcoString,
6966
6967 /// The arguments actually supplied as input to the variant, if any.
6968 /// A variant to generate might as well be just a name passed as an argument
6969 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
6970 /// have any actual arguments!
6971 ///
6972 given_arguments: Option<Arguments<'a>>,
6973}
6974
6975#[derive(Debug, Clone, Copy)]
6976enum TypeBraces {
6977 /// If the type is written like this: `pub type Wibble`
6978 HasBraces,
6979 /// If the type is written like this: `pub type Wibble {}`
6980 NoBraces,
6981}
6982
6983/// The arguments to an invalid call or pattern we can use to generate a variant.
6984///
6985enum Arguments<'a> {
6986 /// These are the arguments provided to the invalid variant constructor
6987 /// when it's used as a function: `let a = Wibble(1, 2)`.
6988 ///
6989 Expressions(&'a [TypedCallArg]),
6990 /// These are the arguments provided to the invalid variant constructor when
6991 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
6992 ///
6993 Patterns(&'a [CallArg<TypedPattern>]),
6994}
6995
6996/// An invalid variant might be used both as a pattern in a case expression or
6997/// as a regular value in an expression. We want to generate the variant in both
6998/// cases, so we use this enum to tell apart the two cases and be able to reuse
6999/// most of the code for both as they are very similar.
7000///
7001enum Argument<'a> {
7002 Expression(&'a TypedCallArg),
7003 Pattern(&'a CallArg<TypedPattern>),
7004}
7005
7006impl<'a> Arguments<'a> {
7007 fn get(&self, index: usize) -> Option<Argument<'a>> {
7008 match self {
7009 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern),
7010 Arguments::Expressions(call_arguments) => {
7011 call_arguments.get(index).map(Argument::Expression)
7012 }
7013 }
7014 }
7015
7016 fn types(&self) -> Vec<Arc<Type>> {
7017 match self {
7018 Arguments::Expressions(call_arguments) => call_arguments
7019 .iter()
7020 .map(|argument| argument.value.type_())
7021 .collect_vec(),
7022
7023 Arguments::Patterns(call_arguments) => call_arguments
7024 .iter()
7025 .map(|argument| argument.value.type_())
7026 .collect_vec(),
7027 }
7028 }
7029}
7030
7031impl Argument<'_> {
7032 fn label(&self) -> Option<EcoString> {
7033 match self {
7034 Argument::Expression(call_arg) => call_arg.label.clone(),
7035 Argument::Pattern(call_arg) => call_arg.label.clone(),
7036 }
7037 }
7038}
7039
7040enum GenerateVariantEdits<'a> {
7041 GenerateInCurrentModule {
7042 current_module_edits: TextEdits<'a>,
7043 },
7044 GenerateInDifferentModule {
7045 current_module_edits: TextEdits<'a>,
7046 variant_module_edits: TextEdits<'a>,
7047 },
7048}
7049
7050impl<'a, IO> GenerateVariant<'a, IO> {
7051 pub fn new(
7052 module: &'a Module,
7053 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
7054 line_numbers: &'a LineNumbers,
7055 params: &'a CodeActionParams,
7056 ) -> Self {
7057 Self {
7058 module,
7059 params,
7060 compiler,
7061 line_numbers,
7062 variant_to_generate: None,
7063 }
7064 }
7065
7066 pub fn code_actions(mut self) -> Vec<CodeAction> {
7067 self.visit_typed_module(&self.module.ast);
7068
7069 let Some(VariantToGenerate {
7070 name,
7071 constructors,
7072 arguments_types,
7073 given_arguments,
7074 module_name,
7075 end_position,
7076 type_braces,
7077 variant_start,
7078 is_qualified,
7079 }) = &self.variant_to_generate
7080 else {
7081 return vec![];
7082 };
7083
7084 // Now we need to figure out if we're going to have to edit just the current
7085 // module (because the variant will be added to a type that is defined there),
7086 // or if we'll have to edit both the current module (to import the newly
7087 // generated variant) and a different module (where the variant definition
7088 // is going to end up).
7089 let current_module_line_numbers = LineNumbers::new(&self.module.code);
7090 let current_module_edits = TextEdits::new(¤t_module_line_numbers);
7091 let Some(variant_module) = self.compiler.modules.get(module_name) else {
7092 return vec![];
7093 };
7094 let variant_module_line_numbers = LineNumbers::new(&variant_module.code);
7095 let variant_module_edits = TextEdits::new(&variant_module_line_numbers);
7096
7097 let mut edits = if *module_name == self.module.name {
7098 GenerateVariantEdits::GenerateInCurrentModule {
7099 current_module_edits,
7100 }
7101 } else {
7102 GenerateVariantEdits::GenerateInDifferentModule {
7103 current_module_edits,
7104 variant_module_edits,
7105 }
7106 };
7107
7108 self.edits_to_create_variant(
7109 &mut edits,
7110 name,
7111 arguments_types,
7112 given_arguments,
7113 *end_position,
7114 *type_braces,
7115 );
7116 // If the variant is qualified already we don't have to do anything,
7117 // otherwise we need to import it in the current module.
7118 if !is_qualified {
7119 self.edits_to_import_variant(
7120 &mut edits,
7121 module_name,
7122 name,
7123 *variant_start,
7124 self.module,
7125 constructors,
7126 );
7127 }
7128
7129 let mut builder = CodeActionBuilder::new("Generate variant")
7130 .kind(CodeActionKind::QuickFix)
7131 .preferred(true);
7132
7133 match edits {
7134 GenerateVariantEdits::GenerateInCurrentModule {
7135 current_module_edits,
7136 } => {
7137 builder = builder.changes(
7138 self.params.text_document.uri.clone(),
7139 current_module_edits.edits,
7140 );
7141 }
7142 GenerateVariantEdits::GenerateInDifferentModule {
7143 current_module_edits,
7144 variant_module_edits,
7145 } => {
7146 let Some(variant_module_path) = url_from_path(variant_module.input_path.as_str())
7147 else {
7148 return vec![];
7149 };
7150
7151 if !current_module_edits.edits.is_empty() {
7152 builder = builder.changes(
7153 self.params.text_document.uri.clone(),
7154 current_module_edits.edits,
7155 );
7156 }
7157
7158 builder = builder.changes(variant_module_path, variant_module_edits.edits);
7159 }
7160 }
7161
7162 let mut action = Vec::with_capacity(1);
7163 builder.push_to(&mut action);
7164 action
7165 }
7166
7167 /// Returns the edits needed to add this new variant to the given module.
7168 /// It also returns the uri of the module the edits should be applied to.
7169 ///
7170 fn edits_to_create_variant(
7171 &self,
7172 edits: &mut GenerateVariantEdits<'_>,
7173 variant_name: &str,
7174 arguments_types: &[Arc<Type>],
7175 given_arguments: &Option<Arguments<'_>>,
7176 end_position: u32,
7177 type_braces: TypeBraces,
7178 ) {
7179 let mut label_names = NameGenerator::new();
7180 let mut printer = Printer::new(&self.module.ast.names);
7181 let arguments = arguments_types
7182 .iter()
7183 .enumerate()
7184 .map(|(index, argument_type)| {
7185 let label = given_arguments
7186 .as_ref()
7187 .and_then(|arguments| arguments.get(index)?.label())
7188 .map(|label| label_names.rename_to_avoid_shadowing(label));
7189
7190 let pretty_type = printer.print_type(argument_type);
7191 if let Some(arg_label) = label {
7192 format!("{arg_label}: {pretty_type}")
7193 } else {
7194 format!("{pretty_type}")
7195 }
7196 })
7197 .join(", ");
7198
7199 let variant = if arguments.is_empty() {
7200 variant_name.to_string()
7201 } else {
7202 format!("{variant_name}({arguments})")
7203 };
7204
7205 let (new_text, insert_at) = match type_braces {
7206 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
7207 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
7208 };
7209
7210 match edits {
7211 GenerateVariantEdits::GenerateInCurrentModule {
7212 current_module_edits,
7213 } => current_module_edits.insert(insert_at, new_text),
7214 GenerateVariantEdits::GenerateInDifferentModule {
7215 variant_module_edits,
7216 ..
7217 } => variant_module_edits.insert(insert_at, new_text),
7218 }
7219 }
7220
7221 fn try_save_variant_to_generate(
7222 &mut self,
7223 is_qualified: bool,
7224 function_name_location: SrcSpan,
7225 function_type: &Arc<Type>,
7226 given_arguments: Option<Arguments<'a>>,
7227 ) {
7228 let variant_to_generate = self.variant_to_generate(
7229 is_qualified,
7230 function_name_location,
7231 function_type,
7232 given_arguments,
7233 );
7234 if variant_to_generate.is_some() {
7235 self.variant_to_generate = variant_to_generate;
7236 }
7237 }
7238
7239 fn variant_to_generate(
7240 &mut self,
7241 is_qualified: bool,
7242 function_name_location: SrcSpan,
7243 type_: &Arc<Type>,
7244 given_arguments: Option<Arguments<'a>>,
7245 ) -> Option<VariantToGenerate<'a>> {
7246 let name = code_at(self.module, function_name_location);
7247 if !is_valid_uppercase_name(name) {
7248 return None;
7249 }
7250
7251 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
7252 (Some(result), _) => result,
7253 (None, Some(arguments)) => (arguments.types(), type_.clone()),
7254 (None, None) => (vec![], type_.clone()),
7255 };
7256
7257 let (module_name, type_name, _) = custom_type.named_type_information()?;
7258 let module = self.compiler.modules.get(&module_name)?;
7259 let (end_position, type_braces, constructors) =
7260 (module.ast.definitions.custom_types.iter())
7261 .filter(|custom_type| custom_type.name == type_name)
7262 .find_map(|custom_type| {
7263 // If there's already a variant with this name then we definitely
7264 // don't want to generate a new variant with the same name!
7265 let variant_with_this_name_already_exists = custom_type
7266 .constructors
7267 .iter()
7268 .map(|constructor| &constructor.name)
7269 .any(|existing_constructor_name| existing_constructor_name == name);
7270 if variant_with_this_name_already_exists {
7271 return None;
7272 }
7273 let type_braces = if custom_type.end_position == custom_type.location.end {
7274 TypeBraces::NoBraces
7275 } else {
7276 TypeBraces::HasBraces
7277 };
7278 Some((
7279 custom_type.end_position,
7280 type_braces,
7281 &custom_type.constructors,
7282 ))
7283 })?;
7284
7285 Some(VariantToGenerate {
7286 name,
7287 is_qualified,
7288 constructors,
7289 arguments_types,
7290 given_arguments,
7291 module_name,
7292 end_position,
7293 type_braces,
7294 variant_start: function_name_location.start,
7295 })
7296 }
7297
7298 /// If the variant is generated in a module different from the current one,
7299 /// this will add the edits needed to correctly import the variant so that
7300 /// it's readily available.
7301 /// It will also respect the developer's choice of how variants for the type
7302 /// are imported:
7303 ///
7304 /// ```diff
7305 /// - import wibble.{ Wibble }
7306 /// + import wibble.{ Wibble, Wobble }
7307 /// // If generating `Wobble`, and other variants of that type are
7308 /// // unqualified already the new variant is imported unqualified as well.
7309 /// ```
7310 ///
7311 /// ```diff
7312 /// import wibble
7313 ///
7314 /// pub fn main() {
7315 /// - let assert Wobble = todo
7316 /// + let assert wibble.Wobble = todo
7317 /// }
7318 /// // If no variant is used in an unqualified manner, than the variant
7319 /// // that triggered the generation is also qualified!
7320 /// ```
7321 fn edits_to_import_variant(
7322 &self,
7323 edits: &mut GenerateVariantEdits<'_>,
7324 variant_module_name: &str,
7325 variant_name: &str,
7326 variant_start: u32,
7327 module: &'a Module,
7328 constructors: &[RecordConstructor<Arc<Type>>],
7329 ) {
7330 let GenerateVariantEdits::GenerateInDifferentModule {
7331 current_module_edits,
7332 ..
7333 } = edits
7334 else {
7335 // If the variant is added to the current module, then no further
7336 // edits are needed. The variant is already available in the current
7337 // module!
7338 return;
7339 };
7340
7341 let constructors_names: HashSet<_> = constructors
7342 .iter()
7343 .map(|constructor| &constructor.name)
7344 .collect();
7345
7346 // We start by getting the import for the module where the variant
7347 // is going to be added...
7348 let Some(variant_module_import) = module
7349 .ast
7350 .definitions
7351 .imports
7352 .iter()
7353 .find(|import_| import_.module == variant_module_name)
7354 else {
7355 return;
7356 };
7357 // ...and then check if any of the variants of the type where the variant
7358 // is going to be added have already been imported in an unqualified way.
7359 let constructors_for_this_type_are_unqualified = variant_module_import
7360 .unqualified_values
7361 .iter()
7362 .any(|value| constructors_names.contains(&value.name));
7363
7364 if constructors_for_this_type_are_unqualified {
7365 // We need to add an unqualified import!
7366 let (insert_positions, new_text) = edits::insert_unqualified_import(
7367 variant_module_import,
7368 &self.module.code,
7369 variant_name.into(),
7370 );
7371 current_module_edits.insert(insert_positions, new_text);
7372 } else {
7373 // We need to qualify the variant that triggered the code action!
7374 current_module_edits.insert(variant_start, format!("{variant_module_name}."));
7375 }
7376 }
7377}
7378
7379impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> {
7380 fn visit_typed_expr_invalid(
7381 &mut self,
7382 location: &'ast SrcSpan,
7383 type_: &'ast Arc<Type>,
7384 extra_information: &'ast Option<InvalidExpression>,
7385 ) {
7386 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
7387 if within(self.params.range, invalid_range) {
7388 self.try_save_variant_to_generate(false, *location, type_, None);
7389 }
7390 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
7391 }
7392
7393 fn visit_typed_expr_call(
7394 &mut self,
7395 location: &'ast SrcSpan,
7396 type_: &'ast Arc<Type>,
7397 fun: &'ast TypedExpr,
7398 arguments: &'ast [TypedCallArg],
7399 open_parenthesis: &'ast Option<u32>,
7400 ) {
7401 // If the function being called is invalid we need to generate a
7402 // function that has the proper labels.
7403 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
7404 if within(self.params.range, fun_range) && fun.is_invalid() {
7405 if labels_are_correct(arguments) {
7406 self.try_save_variant_to_generate(
7407 fun.is_module_select(),
7408 fun.location(),
7409 &fun.type_(),
7410 Some(Arguments::Expressions(arguments)),
7411 );
7412 }
7413 } else {
7414 ast::visit::visit_typed_expr_call(
7415 self,
7416 location,
7417 type_,
7418 fun,
7419 arguments,
7420 open_parenthesis,
7421 );
7422 }
7423 }
7424
7425 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
7426 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
7427 if within(self.params.range, invalid_range) {
7428 self.try_save_variant_to_generate(false, *location, type_, None);
7429 }
7430 ast::visit::visit_typed_pattern_invalid(self, location, type_);
7431 }
7432
7433 fn visit_typed_pattern_constructor(
7434 &mut self,
7435 location: &'ast SrcSpan,
7436 name_location: &'ast SrcSpan,
7437 name: &'ast EcoString,
7438 arguments: &'ast Vec<CallArg<TypedPattern>>,
7439 module: &'ast Option<(EcoString, SrcSpan)>,
7440 constructor: &'ast Inferred<type_::PatternConstructor>,
7441 spread: &'ast Option<SrcSpan>,
7442 type_: &'ast Arc<Type>,
7443 ) {
7444 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
7445 if within(self.params.range, pattern_range) {
7446 if labels_are_correct(arguments) {
7447 self.try_save_variant_to_generate(
7448 module.is_some(),
7449 *name_location,
7450 type_,
7451 Some(Arguments::Patterns(arguments)),
7452 );
7453 }
7454 } else {
7455 ast::visit::visit_typed_pattern_constructor(
7456 self,
7457 location,
7458 name_location,
7459 name,
7460 arguments,
7461 module,
7462 constructor,
7463 spread,
7464 type_,
7465 );
7466 }
7467 }
7468}
7469
7470#[must_use]
7471/// Checks the labels in the given arguments are correct: that is there's no
7472/// duplicate labels and all labelled arguments come after the unlabelled ones.
7473fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool {
7474 let mut labelled_arg_found = false;
7475 let mut used_labels = HashSet::new();
7476
7477 for argument in arguments {
7478 match &argument.label {
7479 // Labels are invalid if there's duplicate ones or if an unlabelled
7480 // argument comes after a labelled one.
7481 Some(label) if used_labels.contains(label) => return false,
7482 None if labelled_arg_found => return false,
7483 // Otherwise we just add the label to the used ones.
7484 Some(label) => {
7485 labelled_arg_found = true;
7486 let _ = used_labels.insert(label);
7487 }
7488 None => {}
7489 }
7490 }
7491
7492 true
7493}
7494
7495#[derive(Clone)]
7496struct NameGenerator {
7497 used_names: im::HashSet<EcoString>,
7498}
7499
7500impl NameGenerator {
7501 pub fn new() -> Self {
7502 NameGenerator {
7503 used_names: im::HashSet::new(),
7504 }
7505 }
7506
7507 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
7508 let mut i = 1;
7509 let mut candidate_name = base.clone();
7510
7511 loop {
7512 if self.used_names.contains(&candidate_name) {
7513 i += 1;
7514 candidate_name = eco_format!("{base}_{i}");
7515 } else {
7516 let _ = self.used_names.insert(candidate_name.clone());
7517 return candidate_name;
7518 }
7519 }
7520 }
7521
7522 /// Given an argument type and the actual call argument (if any), comes up
7523 /// with a label and a name to use for that argument when generating a
7524 /// function.
7525 ///
7526 pub fn generate_label_and_name(
7527 &mut self,
7528 call_argument: Option<&CallArg<TypedExpr>>,
7529 argument_type: &Arc<Type>,
7530 ) -> (Option<EcoString>, EcoString) {
7531 let label = call_argument.and_then(|argument| argument.label.clone());
7532 let argument_name = call_argument
7533 // We always favour a name derived from the expression (for example if
7534 // the argument is a variable)
7535 .and_then(|argument| self.generate_name_from_expression(&argument.value))
7536 // If we don't have such a name and there's a label we use that name.
7537 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
7538 // If all else fails we fallback to using a name derived from the
7539 // argument's type.
7540 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
7541
7542 (label, argument_name)
7543 }
7544
7545 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
7546 let type_to_base_name = |type_: &Arc<Type>| {
7547 type_
7548 .named_type_name()
7549 .map(|(_type_module, type_name)| to_snake_case(&type_name))
7550 .filter(|name| is_valid_lowercase_name(name))
7551 .unwrap_or(EcoString::from("value"))
7552 };
7553
7554 let base_name = match type_.list_type() {
7555 None => type_to_base_name(type_),
7556 // If we're coming up with a name for a list we want to use the
7557 // plural form for the name of the inner type. For example:
7558 // `List(Pokemon)` should generate `pokemons`.
7559 Some(inner_type) => {
7560 let base_name = type_to_base_name(&inner_type);
7561 // If the inner type name already ends in "s" we leave it as it
7562 // is, or it would look funny.
7563 if base_name.ends_with('s') {
7564 base_name
7565 } else {
7566 eco_format!("{base_name}s")
7567 }
7568 }
7569 };
7570
7571 self.rename_to_avoid_shadowing(base_name)
7572 }
7573
7574 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
7575 match expression {
7576 // If the argument is a record, we can't use it as an argument name.
7577 // Similarly, we don't want to base the variable name off a
7578 // compiler-generated variable like `_pipe`.
7579 TypedExpr::Var {
7580 name, constructor, ..
7581 } if !constructor.variant.is_record()
7582 && !constructor.variant.is_generated_variable() =>
7583 {
7584 Some(self.rename_to_avoid_shadowing(name.clone()))
7585 }
7586
7587 // If the argument is a record access, we generate a name from the
7588 // label used.
7589 // For example if we have `wibble.id` we would end up picking `id`.
7590 TypedExpr::RecordAccess { label, .. } => {
7591 Some(self.rename_to_avoid_shadowing(label.clone()))
7592 }
7593
7594 TypedExpr::Int { .. }
7595 | TypedExpr::Float { .. }
7596 | TypedExpr::String { .. }
7597 | TypedExpr::Block { .. }
7598 | TypedExpr::Pipeline { .. }
7599 | TypedExpr::Var { .. }
7600 | TypedExpr::Fn { .. }
7601 | TypedExpr::List { .. }
7602 | TypedExpr::Call { .. }
7603 | TypedExpr::BinOp { .. }
7604 | TypedExpr::Case { .. }
7605 | TypedExpr::PositionalAccess { .. }
7606 | TypedExpr::ModuleSelect { .. }
7607 | TypedExpr::Tuple { .. }
7608 | TypedExpr::TupleIndex { .. }
7609 | TypedExpr::Todo { .. }
7610 | TypedExpr::Panic { .. }
7611 | TypedExpr::Echo { .. }
7612 | TypedExpr::BitArray { .. }
7613 | TypedExpr::RecordUpdate { .. }
7614 | TypedExpr::NegateBool { .. }
7615 | TypedExpr::NegateInt { .. }
7616 | TypedExpr::Invalid { .. } => None,
7617 }
7618 }
7619
7620 /// Given some typed definitions this reserves all the value names defined
7621 /// by all the top level definitions. That is: all function names, constant
7622 /// names, and imported modules names.
7623 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) {
7624 for constant in &definitions.constants {
7625 self.add_used_name(constant.name.clone());
7626 }
7627
7628 for function in &definitions.functions {
7629 if let Some((_, name)) = &function.name {
7630 self.add_used_name(name.clone());
7631 }
7632 }
7633
7634 for import in &definitions.imports {
7635 let module_name = match &import.used_name() {
7636 Some(used_name) => used_name.clone(),
7637 None => import.module.clone(),
7638 };
7639 self.add_used_name(module_name);
7640 }
7641 }
7642
7643 pub fn add_used_name(&mut self, name: EcoString) {
7644 let _ = self.used_names.insert(name);
7645 }
7646
7647 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
7648 field_map
7649 .fields
7650 .iter()
7651 .for_each(|(label, _)| self.add_used_name(label.clone()));
7652 }
7653
7654 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
7655 variable_names
7656 .names
7657 .iter()
7658 .for_each(|name| self.add_used_name(name.clone()));
7659 }
7660
7661 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) {
7662 for variable in bound_variables {
7663 self.add_used_name(variable.name());
7664 }
7665 }
7666}
7667
7668#[must_use]
7669fn is_valid_lowercase_name(name: &str) -> bool {
7670 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
7671 return false;
7672 }
7673
7674 for char in name.chars() {
7675 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
7676 if !is_valid_char {
7677 return false;
7678 }
7679 }
7680
7681 string_to_keyword(name).is_none()
7682}
7683
7684#[must_use]
7685fn is_valid_uppercase_name(name: &str) -> bool {
7686 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
7687 return false;
7688 }
7689
7690 for char in name.chars() {
7691 if !char.is_ascii_alphanumeric() {
7692 return false;
7693 }
7694 }
7695
7696 true
7697}
7698
7699/// Code action to rewrite a single-step pipeline into a regular function call.
7700/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
7701///
7702pub struct ConvertToFunctionCall<'a> {
7703 module: &'a Module,
7704 params: &'a CodeActionParams,
7705 edits: TextEdits<'a>,
7706 locations: Option<ConvertToFunctionCallLocations>,
7707}
7708
7709/// All the different locations the "Convert to function call" code action needs
7710/// to properly rewrite a pipeline into a function call.
7711///
7712struct ConvertToFunctionCallLocations {
7713 /// This is the location of the value being piped into a call.
7714 ///
7715 /// ```gleam
7716 /// [1, 2, 3] |> list.length
7717 /// // ^^^^^^^^^ This one here
7718 /// ```
7719 ///
7720 first_value: SrcSpan,
7721
7722 /// This is the location of the call the value is being piped into.
7723 ///
7724 /// ```gleam
7725 /// [1, 2, 3] |> list.length
7726 /// // ^^^^^^^^^^^ This one here
7727 /// ```
7728 ///
7729 call: SrcSpan,
7730
7731 /// This is the kind of desugaring that is taking place when piping
7732 /// `first_value` into `call`.
7733 ///
7734 call_kind: PipelineAssignmentKind,
7735}
7736
7737impl<'a> ConvertToFunctionCall<'a> {
7738 pub fn new(
7739 module: &'a Module,
7740 line_numbers: &'a LineNumbers,
7741 params: &'a CodeActionParams,
7742 ) -> Self {
7743 Self {
7744 module,
7745 params,
7746 edits: TextEdits::new(line_numbers),
7747 locations: None,
7748 }
7749 }
7750
7751 pub fn code_actions(mut self) -> Vec<CodeAction> {
7752 self.visit_typed_module(&self.module.ast);
7753
7754 // If we couldn't find a pipeline to rewrite we don't return any action.
7755 let Some(ConvertToFunctionCallLocations {
7756 first_value,
7757 call,
7758 call_kind,
7759 }) = self.locations
7760 else {
7761 return vec![];
7762 };
7763
7764 // We first delete the first value of the pipeline as it's going to be
7765 // inlined as a function call argument.
7766 self.edits.delete(SrcSpan {
7767 start: first_value.start,
7768 end: call.start,
7769 });
7770
7771 // Then we have to insert the piped value in the appropriate position.
7772 // This will change based on how the pipeline is being desugared, we
7773 // know this thanks to the `call_kind`
7774 let first_value_text = self
7775 .module
7776 .code
7777 .get(first_value.start as usize..first_value.end as usize)
7778 .expect("invalid code span")
7779 .to_string();
7780
7781 match call_kind {
7782 // When piping into a `_` we replace the hole with the piped value:
7783 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
7784 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
7785
7786 // When piping is desguared as a function call we need to add the
7787 // missing parentheses:
7788 // `[1, 2] |> length` becomes `length([1, 2])`
7789 PipelineAssignmentKind::FunctionCall => {
7790 self.edits.insert(call.end, format!("({first_value_text})"));
7791 }
7792
7793 // When the piped value is inserted as the first argument there's two
7794 // possible scenarios:
7795 // - there's a second argument as well: in that case we insert it
7796 // before the second arg and add a comma
7797 // - there's no other argument: `[1, 2] |> length()` becomes
7798 // `length([1, 2])`, we insert the value between the empty
7799 // parentheses
7800 PipelineAssignmentKind::FirstArgument {
7801 second_argument: Some(SrcSpan { start, .. }),
7802 } => self.edits.insert(start, format!("{first_value_text}, ")),
7803 PipelineAssignmentKind::FirstArgument {
7804 second_argument: None,
7805 } => self.edits.insert(call.end - 1, first_value_text),
7806
7807 // When the value is piped into an echo, to rewrite the pipeline we
7808 // have to insert the value after the `echo` with no parentheses:
7809 // `a |> echo` is rewritten as `echo a`.
7810 PipelineAssignmentKind::Echo => {
7811 self.edits.insert(call.end, format!(" {first_value_text}"));
7812 }
7813 }
7814
7815 let mut action = Vec::with_capacity(1);
7816 CodeActionBuilder::new("Convert to function call")
7817 .kind(CodeActionKind::RefactorRewrite)
7818 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7819 .preferred(false)
7820 .push_to(&mut action);
7821 action
7822 }
7823}
7824
7825impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
7826 fn visit_typed_expr_pipeline(
7827 &mut self,
7828 location: &'ast SrcSpan,
7829 first_value: &'ast TypedPipelineAssignment,
7830 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
7831 finally: &'ast TypedExpr,
7832 finally_kind: &'ast PipelineAssignmentKind,
7833 ) {
7834 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
7835 if within(self.params.range, pipeline_range) {
7836 // Add final assignment to the list
7837 let all_assignments = assignments
7838 .iter()
7839 .map(|(call, kind)| (call.location, *kind))
7840 .chain(iter::once((finally.location(), *finally_kind)));
7841 let mut call_information = None;
7842 // Span, containing content that will be extracted as call argument
7843 let mut accumulated_span = first_value.location;
7844
7845 for (current_location, current_kind) in all_assignments {
7846 if within(
7847 self.params.range,
7848 self.edits.src_span_to_lsp_range(current_location),
7849 ) {
7850 call_information = Some((accumulated_span, current_location, current_kind));
7851 break;
7852 }
7853 accumulated_span = accumulated_span.merge(¤t_location);
7854 }
7855
7856 let (final_prev, final_call, final_kind) = match call_information {
7857 Some(triple) => triple,
7858
7859 // Here two situations are possible:
7860 // - If the cursor is not on any of the assignments (for example, it is on first value), then we need
7861 // to use that first value as accumulated location, and the first call location and kind
7862 // - If there are no assignments, then we are dealing with a single
7863 // step pipeline and the call is `finally`
7864 None => {
7865 let (call, call_kind) = &assignments
7866 .first()
7867 .map(|(call, call_kind)| (call.location, *call_kind))
7868 .unwrap_or_else(|| (finally.location(), *finally_kind));
7869 (first_value.location, *call, *call_kind)
7870 }
7871 };
7872
7873 self.locations = Some(ConvertToFunctionCallLocations {
7874 first_value: final_prev,
7875 call: final_call,
7876 call_kind: final_kind,
7877 });
7878
7879 ast::visit::visit_typed_expr_pipeline(
7880 self,
7881 location,
7882 first_value,
7883 assignments,
7884 finally,
7885 finally_kind,
7886 );
7887 }
7888 }
7889}
7890
7891/// Builder for code action to inline a variable.
7892///
7893pub struct InlineVariable<'a> {
7894 module: &'a Module,
7895 params: &'a CodeActionParams,
7896 edits: TextEdits<'a>,
7897 actions: Vec<CodeAction>,
7898}
7899
7900impl<'a> InlineVariable<'a> {
7901 pub fn new(
7902 module: &'a Module,
7903 line_numbers: &'a LineNumbers,
7904 params: &'a CodeActionParams,
7905 ) -> Self {
7906 Self {
7907 module,
7908 params,
7909 edits: TextEdits::new(line_numbers),
7910 actions: Vec::new(),
7911 }
7912 }
7913
7914 pub fn code_actions(mut self) -> Vec<CodeAction> {
7915 self.visit_typed_module(&self.module.ast);
7916
7917 self.actions
7918 }
7919
7920 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) {
7921 let references =
7922 FindVariableReferences::new(location, name).find_in_module(&self.module.ast);
7923 let reference = if references.len() == 1 {
7924 references
7925 .into_iter()
7926 .next()
7927 .expect("References has length 1")
7928 } else {
7929 return;
7930 };
7931
7932 let Some(ast::Statement::Assignment(assignment)) =
7933 self.module.ast.find_statement(location.start)
7934 else {
7935 return;
7936 };
7937
7938 // If the assignment does not simple bind a variable, for example:
7939 // ```gleam
7940 // let #(first, second, third)
7941 // io.println(first)
7942 // // ^ Inline here
7943 // ```
7944 // We can't inline it.
7945 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
7946 return;
7947 }
7948
7949 // If the assignment was generated by the compiler, it doesn't have a
7950 // syntactical representation, so we can't inline it.
7951 if matches!(assignment.kind, AssignmentKind::Generated) {
7952 return;
7953 }
7954
7955 let value_location = assignment.value.location();
7956 let value = self
7957 .module
7958 .code
7959 .get(value_location.start as usize..value_location.end as usize)
7960 .expect("Span is valid");
7961
7962 match reference.kind {
7963 VariableReferenceKind::Variable => {
7964 self.edits.replace(reference.location, value.into());
7965 }
7966 VariableReferenceKind::LabelShorthand => {
7967 self.edits
7968 .insert(reference.location.end, format!(" {value}"));
7969 }
7970 }
7971
7972 let mut location = assignment.location;
7973 location.end = next_nonwhitespace(&self.module.code, location.end);
7974
7975 self.edits.delete(location);
7976
7977 CodeActionBuilder::new("Inline variable")
7978 .kind(CodeActionKind::RefactorInline)
7979 .changes(
7980 self.params.text_document.uri.clone(),
7981 std::mem::take(&mut self.edits.edits),
7982 )
7983 .preferred(false)
7984 .push_to(&mut self.actions);
7985 }
7986}
7987
7988impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
7989 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
7990 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else {
7991 ast::visit::visit_typed_assignment(self, assignment);
7992 return;
7993 };
7994
7995 // We special case assignment variables because we want to trigger the
7996 // code action also if we're over the let keyword:
7997 //
7998 // ```gleam
7999 // let wibble = 11
8000 // // ^^^^^^^^^^ Here!
8001 // ```
8002 //
8003 let assignment_range = self
8004 .edits
8005 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end));
8006 if !within(self.params.range, assignment_range) {
8007 ast::visit::visit_typed_assignment(self, assignment);
8008 return;
8009 }
8010
8011 self.maybe_inline(*location, name.clone());
8012 }
8013
8014 fn visit_typed_expr_var(
8015 &mut self,
8016 location: &'ast SrcSpan,
8017 constructor: &'ast ValueConstructor,
8018 name: &'ast EcoString,
8019 ) {
8020 let range = self.edits.src_span_to_lsp_range(*location);
8021
8022 if !within(self.params.range, range) {
8023 return;
8024 }
8025
8026 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
8027 &constructor.variant
8028 else {
8029 return;
8030 };
8031
8032 // We can only inline variables assigned by `let` statements, as it
8033 //doesn't make sense to do so with any other kind of variable.
8034 match origin.declaration {
8035 VariableDeclaration::LetPattern => {}
8036 VariableDeclaration::UsePattern
8037 | VariableDeclaration::ClausePattern
8038 | VariableDeclaration::FunctionParameter { .. }
8039 | VariableDeclaration::Generated => return,
8040 }
8041
8042 self.maybe_inline(*location, name.clone());
8043 }
8044
8045 fn visit_typed_pattern_variable(
8046 &mut self,
8047 location: &'ast SrcSpan,
8048 name: &'ast EcoString,
8049 _type: &'ast Arc<Type>,
8050 origin: &'ast VariableOrigin,
8051 ) {
8052 // We can only inline variables assigned by `let` statements, as it
8053 //doesn't make sense to do so with any other kind of variable.
8054 match origin.declaration {
8055 VariableDeclaration::LetPattern => {}
8056 VariableDeclaration::UsePattern
8057 | VariableDeclaration::ClausePattern
8058 | VariableDeclaration::FunctionParameter { .. }
8059 | VariableDeclaration::Generated => return,
8060 }
8061
8062 let range = self.edits.src_span_to_lsp_range(*location);
8063
8064 if !within(self.params.range, range) {
8065 return;
8066 }
8067
8068 self.maybe_inline(*location, name.clone());
8069 }
8070}
8071
8072/// Builder for the "convert to pipe" code action.
8073///
8074/// ```gleam
8075/// pub fn main() {
8076/// wibble(wobble, woo)
8077/// // ^ [convert to pipe]
8078/// }
8079/// ```
8080///
8081/// Will turn the code into the following pipeline:
8082///
8083/// ```gleam
8084/// pub fn main() {
8085/// wobble |> wibble(woo)
8086/// }
8087/// ```
8088///
8089pub struct ConvertToPipe<'a> {
8090 module: &'a Module,
8091 params: &'a CodeActionParams,
8092 edits: TextEdits<'a>,
8093 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
8094 visited_item: VisitedItem,
8095}
8096
8097pub enum VisitedItem {
8098 RegularExpression,
8099 UseRightHandSide,
8100 PipelineFinalStep,
8101}
8102
8103/// Holds all the data needed by the "convert to pipe" code action to properly
8104/// rewrite a call into a pipe. Here's what each span means:
8105///
8106/// ```gleam
8107/// wibble(wobb|le, woo)
8108/// // ^^^^^^^^^^^^^^^^^^^^ call
8109/// // ^^^^^^ called
8110/// // ^^^^^^^ arg
8111/// // ^^^ next arg
8112/// ```
8113///
8114/// In this example `position` is 0, since the cursor is over the first
8115/// argument.
8116///
8117pub struct ConvertToPipeArg<'a> {
8118 /// The span of the called function.
8119 called: SrcSpan,
8120 /// The span of the entire function call.
8121 call: SrcSpan,
8122 /// The position (0-based) of the argument.
8123 position: usize,
8124 /// The argument we have to pipe.
8125 arg: &'a TypedCallArg,
8126 /// The span of the argument following the one we have to pipe, if there's
8127 /// any.
8128 next_arg: Option<SrcSpan>,
8129}
8130
8131impl<'a> ConvertToPipe<'a> {
8132 pub fn new(
8133 module: &'a Module,
8134 line_numbers: &'a LineNumbers,
8135 params: &'a CodeActionParams,
8136 ) -> Self {
8137 Self {
8138 module,
8139 params,
8140 edits: TextEdits::new(line_numbers),
8141 visited_item: VisitedItem::RegularExpression,
8142 argument_to_pipe: None,
8143 }
8144 }
8145
8146 pub fn code_actions(mut self) -> Vec<CodeAction> {
8147 self.visit_typed_module(&self.module.ast);
8148
8149 let Some(ConvertToPipeArg {
8150 called,
8151 call,
8152 position,
8153 arg,
8154 next_arg,
8155 }) = self.argument_to_pipe
8156 else {
8157 return vec![];
8158 };
8159
8160 let arg_location = if arg.uses_label_shorthand() {
8161 SrcSpan {
8162 start: arg.location.start,
8163 end: arg.location.end - 1,
8164 }
8165 } else if arg.label.is_some() {
8166 arg.value.location()
8167 } else {
8168 arg.location
8169 };
8170
8171 let arg_text = code_at(self.module, arg_location);
8172 // If the expression being piped is a binary operation with
8173 // precedence lower than pipes then we have to wrap it in curly
8174 // braces to not mess with the order of operations.
8175 let arg_text = if let TypedExpr::BinOp { operator, .. } = arg.value
8176 && operator.precedence() < PIPE_PRECEDENCE
8177 {
8178 &format!("{{ {arg_text} }}")
8179 } else {
8180 arg_text
8181 };
8182
8183 match next_arg {
8184 // When extracting an argument we never want to remove any explicit
8185 // label that was written down, so in case it is labelled (be it a
8186 // shorthand or not) we'll always replace the value with a `_`
8187 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
8188 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
8189
8190 // Now we can deal with unlabelled arguments:
8191 // If we're removing the first argument and there's other arguments
8192 // after it, we need to delete the comma that was separating the
8193 // two.
8194 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
8195 start: arg.location.start,
8196 end: next_arg.start,
8197 }),
8198 // Otherwise, if we're deleting the first argument and there's
8199 // no other arguments following it, we remove the call's
8200 // parentheses.
8201 None if position == 0 => self.edits.delete(SrcSpan {
8202 start: called.end,
8203 end: call.end,
8204 }),
8205 // In all other cases we're piping something that is not the first
8206 // argument so we just replace it with an `_`.
8207 _ => self.edits.replace(arg.location, "_".into()),
8208 }
8209
8210 // Finally we can add the argument that was removed as the first step
8211 // of the newly defined pipeline.
8212 self.edits.insert(call.start, format!("{arg_text} |> "));
8213
8214 let mut action = Vec::with_capacity(1);
8215 CodeActionBuilder::new("Convert to pipe")
8216 .kind(CodeActionKind::RefactorRewrite)
8217 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8218 .preferred(false)
8219 .push_to(&mut action);
8220 action
8221 }
8222}
8223
8224impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
8225 fn visit_typed_expr_call(
8226 &mut self,
8227 location: &'ast SrcSpan,
8228 _type_: &'ast Arc<Type>,
8229 fun: &'ast TypedExpr,
8230 arguments: &'ast [TypedCallArg],
8231 _open_parenthesis: &'ast Option<u32>,
8232 ) {
8233 if arguments.iter().any(|arg| arg.is_capture_hole()) {
8234 return;
8235 }
8236
8237 // If we're visiting the typed function produced by typing a use, we
8238 // skip the thing itself and only visit its arguments and called
8239 // function, that is the body of the use.
8240 match self.visited_item {
8241 VisitedItem::RegularExpression => (),
8242 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => {
8243 self.visited_item = VisitedItem::RegularExpression;
8244 ast::visit::visit_typed_expr(self, fun);
8245 arguments
8246 .iter()
8247 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
8248 return;
8249 }
8250 }
8251
8252 // We only visit a call if the cursor is somewhere within its location,
8253 // otherwise we skip it entirely.
8254 let call_range = self.edits.src_span_to_lsp_range(*location);
8255 if !within(self.params.range, call_range) {
8256 return;
8257 }
8258
8259 // If the cursor is over any of the arguments then we'll use that as
8260 // the one to extract.
8261 // Otherwise the cursor must be over the called function, in that case
8262 // we extract the first argument (if there's one):
8263 //
8264 // ```gleam
8265 // wibble(wobble, woo)
8266 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
8267 // // ^^^ pipe the second argument if I'm here
8268 // ```
8269 let argument_to_pipe = arguments
8270 .iter()
8271 .enumerate()
8272 .find_map(|(position, arg)| {
8273 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
8274 if within(self.params.range, arg_range) {
8275 Some((position, arg))
8276 } else {
8277 None
8278 }
8279 })
8280 .or_else(|| arguments.first().map(|argument| (0, argument)));
8281
8282 // If we're not hovering over any of the arguments _or_ there's no
8283 // argument to extract at all we just return, there's nothing we can do
8284 // on this call or any of its arguments (since we've determined the
8285 // cursor is not over any of those).
8286 let Some((position, arg)) = argument_to_pipe else {
8287 return;
8288 };
8289
8290 self.argument_to_pipe = Some(ConvertToPipeArg {
8291 called: fun.location(),
8292 call: *location,
8293 position,
8294 arg,
8295 next_arg: arguments
8296 .get(position + 1)
8297 .map(|argument| argument.location),
8298 });
8299
8300 // We still want to visit the arguments so that if we're hovering a
8301 // nested pipeline, that's going to be the one we transform:
8302 //
8303 // ```gleam
8304 // wibble(Wobble(
8305 // field: call(other(last(1)))
8306 // // ^^^^ We want to convert this one if we hover over it,
8307 // // not the outer `wibble(Wobble(...))` call
8308 // ))
8309 // ```
8310 //
8311 for argument in arguments {
8312 ast::visit::visit_typed_call_arg(self, argument);
8313 }
8314 }
8315
8316 fn visit_typed_expr_pipeline(
8317 &mut self,
8318 _location: &'ast SrcSpan,
8319 first_value: &'ast TypedPipelineAssignment,
8320 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
8321 finally: &'ast TypedExpr,
8322 _finally_kind: &'ast PipelineAssignmentKind,
8323 ) {
8324 // We can only apply the action on the first step of a pipeline, so we
8325 // visit just that one and skip all the others.
8326 ast::visit::visit_typed_pipeline_assignment(self, first_value);
8327 self.visited_item = VisitedItem::PipelineFinalStep;
8328 ast::visit::visit_typed_expr(self, finally);
8329 }
8330
8331 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
8332 self.visited_item = VisitedItem::UseRightHandSide;
8333 ast::visit::visit_typed_use(self, use_);
8334 }
8335}
8336
8337/// Code action to interpolate a string. If the cursor is inside the string
8338/// (not selecting anything) the language server will offer to split it:
8339///
8340/// ```gleam
8341/// "wibble | wobble"
8342/// // ^ [Split string]
8343/// // Will produce the following
8344/// "wibble " <> todo <> " wobble"
8345/// ```
8346///
8347/// If the cursor is selecting an entire valid gleam name, then the language
8348/// server will offer to interpolate it as a variable:
8349///
8350/// ```gleam
8351/// "wibble wobble woo"
8352/// // ^^^^^^ [Interpolate variable]
8353/// // Will produce the following
8354/// "wibble " <> wobble <> " woo"
8355/// ```
8356///
8357/// > Note: the cursor won't end up right after the inserted variable/todo.
8358/// > that's a bit annoying, but in a future LSP version we will be able to
8359/// > isnert tab stops to allow one to jump to the newly added variable/todo.
8360///
8361pub struct InterpolateString<'a> {
8362 module: &'a Module,
8363 params: &'a CodeActionParams,
8364 edits: TextEdits<'a>,
8365 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
8366 string_literal_position: StringLiteralPosition,
8367}
8368
8369#[derive(Debug, Clone, Copy, Eq, PartialEq)]
8370pub enum StringLiteralPosition {
8371 FirstPipelineStep,
8372 Other,
8373}
8374
8375#[derive(Clone, Copy)]
8376enum StringInterpolation {
8377 InterpolateValue { value_location: SrcSpan },
8378 SplitString { split_at: u32 },
8379}
8380
8381impl<'a> InterpolateString<'a> {
8382 pub fn new(
8383 module: &'a Module,
8384 line_numbers: &'a LineNumbers,
8385 params: &'a CodeActionParams,
8386 ) -> Self {
8387 Self {
8388 module,
8389 params,
8390 edits: TextEdits::new(line_numbers),
8391 string_interpolation: None,
8392 string_literal_position: StringLiteralPosition::Other,
8393 }
8394 }
8395
8396 pub fn code_actions(mut self) -> Vec<CodeAction> {
8397 self.visit_typed_module(&self.module.ast);
8398
8399 let Some((string_location, interpolation)) = self.string_interpolation else {
8400 return vec![];
8401 };
8402
8403 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
8404 self.edits.insert(string_location.start, "{ ".into());
8405 }
8406
8407 match interpolation {
8408 StringInterpolation::InterpolateValue { value_location } => {
8409 let name = self
8410 .module
8411 .code
8412 .get(value_location.start as usize..value_location.end as usize)
8413 .expect("invalid value range");
8414
8415 // We trust that the programmer has correctly selected the part
8416 // of the string they want to interpolate and simply "cut it out"
8417 // for them. In future, we could try and parse their selection to
8418 // see if it is a valid expression in Gleam.
8419 if value_location.start == string_location.start + 1 {
8420 self.edits
8421 .insert(string_location.start, format!("{name} <> "));
8422 } else if value_location.end == string_location.end - 1 {
8423 self.edits
8424 .insert(string_location.end, format!(" <> {name}"));
8425 } else {
8426 self.edits
8427 .insert(value_location.start, format!("\" <> {name} <> \""));
8428 }
8429 self.edits.delete(value_location);
8430 }
8431
8432 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
8433 self.edits.insert(split_at, "\" <> todo <> \"".into());
8434 }
8435
8436 StringInterpolation::SplitString { .. } => return vec![],
8437 }
8438
8439 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
8440 self.edits.insert(string_location.end, " }".into());
8441 }
8442
8443 let mut action = Vec::with_capacity(1);
8444 CodeActionBuilder::new("Interpolate string")
8445 .kind(CodeActionKind::RefactorRewrite)
8446 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8447 .preferred(false)
8448 .push_to(&mut action);
8449 action
8450 }
8451
8452 fn can_split_string_at(&self, at: u32) -> bool {
8453 self.string_interpolation
8454 .is_some_and(|(string_location, _)| {
8455 !(at <= string_location.start + 1 || at >= string_location.end - 1)
8456 })
8457 }
8458
8459 fn visit_literal_string(
8460 &mut self,
8461 string_location: SrcSpan,
8462 string_position: StringLiteralPosition,
8463 ) {
8464 // We can only interpolate/split a string if the cursor is somewhere
8465 // within its location, otherwise we skip it.
8466 let string_range = self.edits.src_span_to_lsp_range(string_location);
8467 if !within(self.params.range, string_range) {
8468 return;
8469 }
8470
8471 let selection @ SrcSpan { start, end } =
8472 self.edits.lsp_range_to_src_span(self.params.range);
8473
8474 // We can't interpolate/split if the double quotes delimiting the
8475 // string have been selected.
8476 if start == string_location.start || end == string_location.end {
8477 return;
8478 }
8479
8480 let name = self
8481 .module
8482 .code
8483 .get(start as usize..end as usize)
8484 .expect("invalid value range");
8485
8486 // TUI editors like Helix and Kakoune that use the selection-action edit
8487 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always
8488 // have something selected. For programmers using these editors, the
8489 // smallest selection possible is a 1-character selection. The best we can do
8490 // to provide parity with other editors is to consider a single-character SPACE
8491 // selection as an empty selection, as they most likely want to split the
8492 // string instead of interpolating a variable.
8493 let interpolation = if start == end || (end - start == 1 && name == " ") {
8494 StringInterpolation::SplitString { split_at: start }
8495 } else {
8496 StringInterpolation::InterpolateValue {
8497 value_location: selection,
8498 }
8499 };
8500 self.string_interpolation = Some((string_location, interpolation));
8501 self.string_literal_position = string_position;
8502 }
8503}
8504
8505impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
8506 fn visit_typed_expr_string(
8507 &mut self,
8508 location: &'ast SrcSpan,
8509 _type_: &'ast Arc<Type>,
8510 _value: &'ast EcoString,
8511 ) {
8512 self.visit_literal_string(*location, StringLiteralPosition::Other);
8513 }
8514
8515 fn visit_typed_expr_pipeline(
8516 &mut self,
8517 _location: &'ast SrcSpan,
8518 first_value: &'ast TypedPipelineAssignment,
8519 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
8520 finally: &'ast TypedExpr,
8521 _finally_kind: &'ast PipelineAssignmentKind,
8522 ) {
8523 if first_value.value.is_literal_string() {
8524 self.visit_literal_string(
8525 first_value.location,
8526 StringLiteralPosition::FirstPipelineStep,
8527 );
8528 } else {
8529 ast::visit::visit_typed_pipeline_assignment(self, first_value);
8530 }
8531
8532 assignments
8533 .iter()
8534 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
8535 self.visit_typed_expr(finally);
8536 }
8537}
8538
8539/// Code action to replace a `..` in a pattern with all the missing fields that
8540/// have not been explicitly provided; labelled ones are introduced with the
8541/// shorthand syntax.
8542///
8543/// ```gleam
8544/// pub type Pokemon {
8545/// Pokemon(Int, name: String, moves: List(String))
8546/// }
8547///
8548/// pub fn main() {
8549/// let Pokemon(..) = todo
8550/// // ^^ Cursor over the spread
8551/// }
8552/// ```
8553/// Would become
8554/// ```gleam
8555/// pub fn main() {
8556/// let Pokemon(int, name:, moves:) = todo
8557/// }
8558///
8559pub struct FillUnusedFields<'a> {
8560 module: &'a Module,
8561 params: &'a CodeActionParams,
8562 edits: TextEdits<'a>,
8563 data: Option<FillUnusedFieldsData>,
8564}
8565
8566pub struct FillUnusedFieldsData {
8567 /// All the missing positional and labelled fields.
8568 positional: Vec<Arc<Type>>,
8569 labelled: Vec<(EcoString, Arc<Type>)>,
8570 /// We need this in order to tell where the missing positional arguments
8571 /// should be inserted.
8572 first_labelled_argument_start: Option<u32>,
8573 /// The end of the final argument before the spread, if there's any.
8574 /// We'll use this to delete everything that comes after the final argument,
8575 /// after adding all the ignored fields.
8576 last_argument_end: Option<u32>,
8577 spread_location: SrcSpan,
8578}
8579
8580impl<'a> FillUnusedFields<'a> {
8581 pub fn new(
8582 module: &'a Module,
8583 line_numbers: &'a LineNumbers,
8584 params: &'a CodeActionParams,
8585 ) -> Self {
8586 Self {
8587 module,
8588 params,
8589 edits: TextEdits::new(line_numbers),
8590 data: None,
8591 }
8592 }
8593
8594 pub fn code_actions(mut self) -> Vec<CodeAction> {
8595 self.visit_typed_module(&self.module.ast);
8596
8597 let Some(FillUnusedFieldsData {
8598 positional,
8599 labelled,
8600 first_labelled_argument_start,
8601 last_argument_end,
8602 spread_location,
8603 }) = self.data
8604 else {
8605 return vec![];
8606 };
8607
8608 // Do not suggest this code action if there's no ignored fields at all.
8609 if positional.is_empty() && labelled.is_empty() {
8610 return vec![];
8611 }
8612
8613 // We add all the missing positional arguments before the first
8614 // labelled one (and so after all the already existing positional ones).
8615 if !positional.is_empty() {
8616 // We want to make sure that all positional args will have a name
8617 // that's different from any label. So we add those as already used
8618 // names.
8619 let mut names = NameGenerator::new();
8620 for (label, _) in labelled.iter() {
8621 names.add_used_name(label.clone());
8622 }
8623
8624 let positional_arguments = positional
8625 .iter()
8626 .map(|type_| names.generate_name_from_type(type_))
8627 .join(", ");
8628 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
8629
8630 // The positional arguments are going to be followed by some other
8631 // arguments if there's some already existing labelled args
8632 // (`last_argument_end.is_some`), of if we're adding those labelled args
8633 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
8634 // final positional argument we're adding to separate it from the ones that
8635 // are going to come after.
8636 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
8637 let positional_arguments = if has_arguments_after {
8638 format!("{positional_arguments}, ")
8639 } else {
8640 positional_arguments
8641 };
8642
8643 self.edits.insert(insert_at, positional_arguments);
8644 }
8645
8646 if !labelled.is_empty() {
8647 // If there's labelled arguments to add, we replace the existing spread
8648 // with the arguments to be added. This way commas and all should already
8649 // be correct.
8650 let labelled_arguments = labelled
8651 .iter()
8652 .map(|(label, _)| format!("{label}:"))
8653 .join(", ");
8654 self.edits.replace(spread_location, labelled_arguments);
8655 } else if let Some(delete_start) = last_argument_end {
8656 // However, if there's no labelled arguments to insert we still need
8657 // to delete the entire spread: we start deleting from the end of the
8658 // final argument, if there's one.
8659 // This way we also get rid of any comma separating the last argument
8660 // and the spread to be removed.
8661 self.edits
8662 .delete(SrcSpan::new(delete_start, spread_location.end));
8663 } else {
8664 // Otherwise we just delete the spread.
8665 self.edits.delete(spread_location);
8666 }
8667
8668 let mut action = Vec::with_capacity(1);
8669 CodeActionBuilder::new("Fill unused fields")
8670 .kind(CodeActionKind::RefactorRewrite)
8671 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8672 .preferred(false)
8673 .push_to(&mut action);
8674 action
8675 }
8676}
8677
8678impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
8679 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
8680 // We can only interpolate/split a string if the cursor is somewhere
8681 // within its location, otherwise we skip it.
8682 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
8683 if !within(self.params.range, pattern_range) {
8684 return;
8685 }
8686
8687 if let TypedPattern::Constructor {
8688 arguments,
8689 spread: Some(spread_location),
8690 ..
8691 } = pattern
8692 && let Some(PatternUnusedArguments {
8693 positional,
8694 labelled,
8695 }) = pattern.unused_arguments()
8696 {
8697 // If there's any unused argument that's being ignored we want to
8698 // suggest the code action.
8699 let first_labelled_argument_start = arguments
8700 .iter()
8701 .find(|arg| !arg.is_implicit() && arg.label.is_some())
8702 .map(|arg| arg.location.start);
8703
8704 let last_argument_end = arguments
8705 .iter()
8706 .rfind(|arg| !arg.is_implicit())
8707 .map(|arg| arg.location.end);
8708
8709 self.data = Some(FillUnusedFieldsData {
8710 positional,
8711 labelled,
8712 first_labelled_argument_start,
8713 last_argument_end,
8714 spread_location: *spread_location,
8715 });
8716 }
8717
8718 ast::visit::visit_typed_pattern(self, pattern);
8719 }
8720}
8721
8722/// Code action to remove an echo.
8723///
8724pub struct RemoveEchos<'a> {
8725 module: &'a Module,
8726 params: &'a CodeActionParams,
8727 edits: TextEdits<'a>,
8728 is_hovering_echo: bool,
8729 echo_spans_to_delete: Vec<SrcSpan>,
8730 // We need to keep a reference to the two latest pipeline assignments we
8731 // run into to properly delete an echo that's inside a pipeline.
8732 latest_pipe_step: Option<SrcSpan>,
8733 second_to_latest_pipe_step: Option<SrcSpan>,
8734}
8735
8736impl<'a> RemoveEchos<'a> {
8737 pub fn new(
8738 module: &'a Module,
8739 line_numbers: &'a LineNumbers,
8740 params: &'a CodeActionParams,
8741 ) -> Self {
8742 Self {
8743 module,
8744 params,
8745 edits: TextEdits::new(line_numbers),
8746 is_hovering_echo: false,
8747 echo_spans_to_delete: vec![],
8748 latest_pipe_step: None,
8749 second_to_latest_pipe_step: None,
8750 }
8751 }
8752
8753 pub fn code_actions(mut self) -> Vec<CodeAction> {
8754 self.visit_typed_module(&self.module.ast);
8755
8756 // We only want to trigger the action if we're over one of the echos in
8757 // the module
8758 if !self.is_hovering_echo {
8759 return vec![];
8760 }
8761
8762 for span in self.echo_spans_to_delete {
8763 self.edits.delete(span);
8764 }
8765
8766 let mut action = Vec::with_capacity(1);
8767 CodeActionBuilder::new("Remove all `echo`s from this module")
8768 .kind(CodeActionKind::RefactorRewrite)
8769 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8770 .preferred(false)
8771 .push_to(&mut action);
8772 action
8773 }
8774
8775 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
8776 for i in 0..statements.len() {
8777 let statement = statements
8778 .get(i)
8779 .expect("Statement must exist in iteration");
8780 let next_statement = statements.get(i + 1);
8781 let is_last = i == statements.len() - 1;
8782
8783 match statement {
8784 // We remove any echo that is used as a standalone statement used
8785 // to print a literal value.
8786 //
8787 // ```gleam
8788 // pub fn main() {
8789 // echo "I'm here"
8790 // do_something()
8791 // echo "Safe!"
8792 // do_something_else()
8793 // }
8794 // ```
8795 //
8796 // Here we want to remove not just the echo but also the literal
8797 // strings they're printing.
8798 //
8799 // It's safe to do this only if echo is not the last expression
8800 // in a function's block (otherwise we might change the function's
8801 // return type by removing the entire line) and the value being
8802 // printed is a literal expression.
8803 //
8804 ast::Statement::Expression(TypedExpr::Echo {
8805 location,
8806 expression,
8807 ..
8808 }) if !is_last
8809 && expression.as_ref().is_some_and(|expression| {
8810 expression.is_literal() || expression.is_var()
8811 }) =>
8812 {
8813 let echo_range = self.edits.src_span_to_lsp_range(*location);
8814 if within(self.params.range, echo_range) {
8815 self.is_hovering_echo = true;
8816 }
8817
8818 let end = next_statement
8819 .map(|next| {
8820 let echo_end = location.end;
8821 let next_start = next.location().start;
8822 // We want to remove everything until the start of the
8823 // following statement. However, we have to be careful not to
8824 // delete any comments. So if there's any comment between the
8825 // echo to remove and the next statement, we just delete until
8826 // the comment's start.
8827 self.module
8828 .extra
8829 .first_comment_between(echo_end, next_start)
8830 // For comments we record the start of their content, not of the `//`
8831 // so we're subtracting 2 here to not delete the `//` as well
8832 .map(|comment| comment.start - 2)
8833 .unwrap_or(next_start)
8834 })
8835 .unwrap_or(location.end);
8836
8837 self.echo_spans_to_delete.push(SrcSpan {
8838 start: location.start,
8839 end,
8840 });
8841 }
8842
8843 // Otherwise we visit the statement as usual.
8844 ast::Statement::Expression(_)
8845 | ast::Statement::Assignment(_)
8846 | ast::Statement::Use(_)
8847 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement),
8848 }
8849 }
8850 }
8851}
8852
8853impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
8854 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
8855 self.visit_function_statements(&fun.body);
8856 }
8857
8858 fn visit_typed_expr_fn(
8859 &mut self,
8860 _location: &'ast SrcSpan,
8861 _type_: &'ast Arc<Type>,
8862 _kind: &'ast FunctionLiteralKind,
8863 _arguments: &'ast [TypedArg],
8864 body: &'ast Vec1<TypedStatement>,
8865 _return_annotation: &'ast Option<ast::TypeAst>,
8866 ) {
8867 self.visit_function_statements(body);
8868 }
8869
8870 fn visit_typed_expr_echo(
8871 &mut self,
8872 location: &'ast SrcSpan,
8873 type_: &'ast Arc<Type>,
8874 expression: &'ast Option<Box<TypedExpr>>,
8875 message: &'ast Option<Box<TypedExpr>>,
8876 ) {
8877 // We also want to trigger the action if we're hovering over the expression
8878 // being printed. So we create a unique span starting from the start of echo
8879 // end ending at the end of the expression.
8880 //
8881 // ```
8882 // echo 1 + 2
8883 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
8884 // inside it, not just the keyword
8885 // ```
8886 //
8887 let echo_range = self.edits.src_span_to_lsp_range(*location);
8888 if within(self.params.range, echo_range) {
8889 self.is_hovering_echo = true;
8890 }
8891
8892 // We also want to remove the echo message!
8893 if message.is_some() {
8894 let start = expression
8895 .as_ref()
8896 .map(|expression| expression.location().end)
8897 .unwrap_or(location.start + 4);
8898
8899 self.echo_spans_to_delete
8900 .push(SrcSpan::new(start, location.end));
8901 }
8902
8903 if let Some(expression) = expression {
8904 // If there's an expression we delete everything we find until its
8905 // start (excluded).
8906 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
8907 self.echo_spans_to_delete.push(span_to_delete);
8908 } else {
8909 // Othwerise we know we're inside a pipeline, we take the closest step
8910 // that is not echo itself and delete everything from its end until the
8911 // end of the echo keyword:
8912 //
8913 // ```txt
8914 // wibble |> echo |> wobble
8915 // ^^^^^^^^ This span right here
8916 // ```
8917 let step_preceding_echo = self
8918 .latest_pipe_step
8919 .filter(|l| l != location)
8920 .or(self.second_to_latest_pipe_step);
8921 if let Some(step_preceding_echo) = step_preceding_echo {
8922 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
8923 self.echo_spans_to_delete.push(span_to_delete);
8924 }
8925 }
8926
8927 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message);
8928 }
8929
8930 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
8931 if self.latest_pipe_step.is_some() {
8932 self.second_to_latest_pipe_step = self.latest_pipe_step;
8933 }
8934 self.latest_pipe_step = Some(assignment.location);
8935 ast::visit::visit_typed_pipeline_assignment(self, assignment);
8936 }
8937}
8938
8939/// Code action to wrap assignment and case clause values in a block.
8940///
8941/// ```gleam
8942/// pub type PokemonType {
8943/// Fire
8944/// Water
8945/// }
8946///
8947/// pub fn main() {
8948/// let pokemon_type: PokemonType = todo
8949/// case pokemon_type {
8950/// Water -> soak()
8951/// ^^^^^^ Cursor over the spread
8952/// Fire -> burn()
8953/// }
8954/// }
8955/// ```
8956/// Becomes
8957/// ```gleam
8958/// pub type PokemonType {
8959/// Fire
8960/// Water
8961/// }
8962///
8963/// pub fn main() {
8964/// let pokemon_type: PokemonType = todo
8965/// case pokemon_type {
8966/// Water -> {
8967/// soak()
8968/// }
8969/// Fire -> burn()
8970/// }
8971/// }
8972/// ```
8973///
8974pub struct WrapInBlock<'a> {
8975 module: &'a Module,
8976 params: &'a CodeActionParams,
8977 edits: TextEdits<'a>,
8978 selected_expression: Option<SrcSpan>,
8979}
8980
8981impl<'a> WrapInBlock<'a> {
8982 pub fn new(
8983 module: &'a Module,
8984 line_numbers: &'a LineNumbers,
8985 params: &'a CodeActionParams,
8986 ) -> Self {
8987 Self {
8988 module,
8989 params,
8990 edits: TextEdits::new(line_numbers),
8991 selected_expression: None,
8992 }
8993 }
8994
8995 pub fn code_actions(mut self) -> Vec<CodeAction> {
8996 self.visit_typed_module(&self.module.ast);
8997
8998 let Some(expr_span) = self.selected_expression else {
8999 return vec![];
9000 };
9001
9002 let Some(expr_string) = self
9003 .module
9004 .code
9005 .get(expr_span.start as usize..(expr_span.end as usize + 1))
9006 else {
9007 return vec![];
9008 };
9009
9010 let range = self
9011 .edits
9012 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
9013
9014 let indent_size =
9015 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
9016
9017 let expr_indent_size = indent_size + 2;
9018
9019 let indent = " ".repeat(indent_size);
9020 let inner_indent = " ".repeat(expr_indent_size);
9021
9022 self.edits.replace(
9023 expr_span,
9024 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
9025 );
9026
9027 let mut action = Vec::with_capacity(1);
9028 CodeActionBuilder::new("Wrap in block")
9029 .kind(CodeActionKind::RefactorExtract)
9030 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9031 .preferred(false)
9032 .push_to(&mut action);
9033 action
9034 }
9035}
9036
9037impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
9038 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
9039 ast::visit::visit_typed_expr(self, &assignment.value);
9040 if !within(
9041 self.params.range,
9042 self.edits
9043 .src_span_to_lsp_range(assignment.value.location()),
9044 ) {
9045 return;
9046 }
9047 match &assignment.value {
9048 // To avoid wrapping the same expression in multiple, nested blocks.
9049 TypedExpr::Block { .. } => {}
9050 TypedExpr::RecordAccess { .. }
9051 | TypedExpr::PositionalAccess { .. }
9052 | TypedExpr::Int { .. }
9053 | TypedExpr::Float { .. }
9054 | TypedExpr::String { .. }
9055 | TypedExpr::Pipeline { .. }
9056 | TypedExpr::Var { .. }
9057 | TypedExpr::Fn { .. }
9058 | TypedExpr::List { .. }
9059 | TypedExpr::Call { .. }
9060 | TypedExpr::BinOp { .. }
9061 | TypedExpr::Case { .. }
9062 | TypedExpr::ModuleSelect { .. }
9063 | TypedExpr::Tuple { .. }
9064 | TypedExpr::TupleIndex { .. }
9065 | TypedExpr::Todo { .. }
9066 | TypedExpr::Panic { .. }
9067 | TypedExpr::Echo { .. }
9068 | TypedExpr::BitArray { .. }
9069 | TypedExpr::RecordUpdate { .. }
9070 | TypedExpr::NegateBool { .. }
9071 | TypedExpr::NegateInt { .. }
9072 | TypedExpr::Invalid { .. } => {
9073 self.selected_expression = Some(assignment.value.location());
9074 }
9075 }
9076 ast::visit::visit_typed_assignment(self, assignment);
9077 }
9078
9079 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
9080 ast::visit::visit_typed_clause(self, clause);
9081
9082 if !within(
9083 self.params.range,
9084 self.edits.src_span_to_lsp_range(clause.then.location()),
9085 ) {
9086 return;
9087 }
9088
9089 // To avoid wrapping the same expression in multiple, nested blocks.
9090 if !matches!(clause.then, TypedExpr::Block { .. }) {
9091 self.selected_expression = Some(clause.then.location());
9092 }
9093
9094 ast::visit::visit_typed_clause(self, clause);
9095 }
9096}
9097
9098/// Code action to fix wrong binary operators when the compiler can easily tell
9099/// what the correct alternative is.
9100///
9101/// ```gleam
9102/// 1 +. 2 // becomes 1 + 2
9103/// 1.0 + 2.3 // becomes 1.0 +. 2.3
9104/// ```
9105///
9106pub struct FixBinaryOperation<'a> {
9107 module: &'a Module,
9108 params: &'a CodeActionParams,
9109 edits: TextEdits<'a>,
9110 fix: Option<(SrcSpan, ast::BinOp)>,
9111}
9112
9113impl<'a> FixBinaryOperation<'a> {
9114 pub fn new(
9115 module: &'a Module,
9116 line_numbers: &'a LineNumbers,
9117 params: &'a CodeActionParams,
9118 ) -> Self {
9119 Self {
9120 module,
9121 params,
9122 edits: TextEdits::new(line_numbers),
9123 fix: None,
9124 }
9125 }
9126
9127 pub fn code_actions(mut self) -> Vec<CodeAction> {
9128 self.visit_typed_module(&self.module.ast);
9129
9130 let Some((location, replacement)) = self.fix else {
9131 return vec![];
9132 };
9133
9134 self.edits.replace(location, replacement.name().into());
9135
9136 let mut action = Vec::with_capacity(1);
9137 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
9138 .kind(CodeActionKind::RefactorRewrite)
9139 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9140 .preferred(true)
9141 .push_to(&mut action);
9142 action
9143 }
9144
9145 fn try_fix(
9146 &mut self,
9147 left: Arc<Type>,
9148 right: Arc<Type>,
9149 operator: ast::BinOp,
9150 operator_start: u32,
9151 ) {
9152 let operator_location = SrcSpan::new(operator_start, operator_start + operator.size());
9153 if operator.is_int_operator() && left.is_float() && right.is_float() {
9154 self.fix = operator
9155 .float_equivalent()
9156 .map(|fix| (operator_location, fix));
9157 } else if operator.is_float_operator() && left.is_int() && right.is_int() {
9158 self.fix = operator
9159 .int_equivalent()
9160 .map(|fix| (operator_location, fix));
9161 } else if operator == ast::BinOp::AddInt && left.is_string() && right.is_string() {
9162 self.fix = Some((operator_location, ast::BinOp::Concatenate));
9163 }
9164 }
9165}
9166
9167impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
9168 fn visit_typed_expr_case(
9169 &mut self,
9170 location: &'ast SrcSpan,
9171 type_: &'ast Arc<Type>,
9172 subjects: &'ast [TypedExpr],
9173 clauses: &'ast [ast::TypedClause],
9174 compiled_case: &'ast CompiledCase,
9175 ) {
9176 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
9177 }
9178 fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) {
9179 ast::visit::visit_typed_clause_guard(self, guard);
9180 }
9181
9182 fn visit_typed_clause_guard_bin_op(
9183 &mut self,
9184 left: &'ast TypedClauseGuard,
9185 right: &'ast TypedClauseGuard,
9186 operator: &'ast ast::BinOp,
9187 operator_start: &'ast u32,
9188 location: &'ast SrcSpan,
9189 ) {
9190 let binop_range = self.edits.src_span_to_lsp_range(*location);
9191 if !within(self.params.range, binop_range) {
9192 return;
9193 }
9194
9195 self.try_fix(left.type_(), right.type_(), *operator, *operator_start);
9196
9197 ast::visit::visit_typed_clause_guard_bin_op(
9198 self,
9199 left,
9200 right,
9201 operator,
9202 operator_start,
9203 location,
9204 );
9205 }
9206
9207 fn visit_typed_expr_bin_op(
9208 &mut self,
9209 location: &'ast SrcSpan,
9210 type_: &'ast Arc<Type>,
9211 operator: &'ast ast::BinOp,
9212 operator_start: &'ast u32,
9213 left: &'ast TypedExpr,
9214 right: &'ast TypedExpr,
9215 ) {
9216 let binop_range = self.edits.src_span_to_lsp_range(*location);
9217 if !within(self.params.range, binop_range) {
9218 return;
9219 }
9220
9221 self.try_fix(left.type_(), right.type_(), *operator, *operator_start);
9222
9223 ast::visit::visit_typed_expr_bin_op(
9224 self,
9225 location,
9226 type_,
9227 operator,
9228 operator_start,
9229 left,
9230 right,
9231 );
9232 }
9233}
9234
9235/// Code action builder to automatically fix segments that have a value that's
9236/// guaranteed to overflow.
9237///
9238pub struct FixTruncatedBitArraySegment<'a> {
9239 module: &'a Module,
9240 params: &'a CodeActionParams,
9241 edits: TextEdits<'a>,
9242 truncation: Option<BitArraySegmentTruncation>,
9243}
9244
9245impl<'a> FixTruncatedBitArraySegment<'a> {
9246 pub fn new(
9247 module: &'a Module,
9248 line_numbers: &'a LineNumbers,
9249 params: &'a CodeActionParams,
9250 ) -> Self {
9251 Self {
9252 module,
9253 params,
9254 edits: TextEdits::new(line_numbers),
9255 truncation: None,
9256 }
9257 }
9258
9259 pub fn code_actions(mut self) -> Vec<CodeAction> {
9260 self.visit_typed_module(&self.module.ast);
9261
9262 let Some(truncation) = self.truncation else {
9263 return vec![];
9264 };
9265
9266 let replacement = truncation.truncated_into.to_string();
9267 self.edits
9268 .replace(truncation.value_location, replacement.clone());
9269
9270 let mut action = Vec::with_capacity(1);
9271 CodeActionBuilder::new(&format!("Replace with `{replacement}`"))
9272 .kind(CodeActionKind::RefactorRewrite)
9273 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9274 .preferred(true)
9275 .push_to(&mut action);
9276 action
9277 }
9278}
9279
9280impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
9281 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
9282 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
9283 if !within(self.params.range, segment_range) {
9284 return;
9285 }
9286
9287 if let Some(truncation) = segment.check_for_truncated_value() {
9288 self.truncation = Some(truncation);
9289 }
9290
9291 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
9292 }
9293}
9294
9295/// Code action builder to remove unused imports and values.
9296///
9297pub struct RemoveUnusedImports<'a> {
9298 module: &'a Module,
9299 params: &'a CodeActionParams,
9300 edits: TextEdits<'a>,
9301}
9302
9303#[derive(Debug)]
9304enum UnusedImport {
9305 ValueOrType(SrcSpan),
9306 Module(SrcSpan),
9307 ModuleAlias(SrcSpan),
9308}
9309
9310impl UnusedImport {
9311 fn location(&self) -> SrcSpan {
9312 match self {
9313 UnusedImport::ValueOrType(location)
9314 | UnusedImport::Module(location)
9315 | UnusedImport::ModuleAlias(location) => *location,
9316 }
9317 }
9318}
9319
9320impl<'a> RemoveUnusedImports<'a> {
9321 pub fn new(
9322 module: &'a Module,
9323 line_numbers: &'a LineNumbers,
9324 params: &'a CodeActionParams,
9325 ) -> Self {
9326 Self {
9327 module,
9328 params,
9329 edits: TextEdits::new(line_numbers),
9330 }
9331 }
9332
9333 /// Given an import location, returns a list of the spans of all the
9334 /// unqualified values it's importing. Sorted by SrcSpan location.
9335 ///
9336 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> {
9337 self.module
9338 .ast
9339 .definitions
9340 .imports
9341 .iter()
9342 .find(|import| import.location.contains(import_location.start))
9343 .map(|import| {
9344 let types = import.unqualified_types.iter().map(|type_| type_.location);
9345 let values = import.unqualified_values.iter().map(|value| value.location);
9346 types
9347 .chain(values)
9348 .sorted_by_key(|location| location.start)
9349 .collect_vec()
9350 })
9351 .unwrap_or_default()
9352 }
9353
9354 pub fn code_actions(mut self) -> Vec<CodeAction> {
9355 // If there's no import in the module then there can't be any unused
9356 // import to remove.
9357 if self.module.ast.definitions.imports.is_empty() {
9358 return vec![];
9359 }
9360
9361 let unused_imports = self
9362 .module
9363 .ast
9364 .type_info
9365 .warnings
9366 .iter()
9367 .filter_map(|warning| match warning {
9368 type_::Warning::UnusedImportedValue { location, .. } => {
9369 Some(UnusedImport::ValueOrType(*location))
9370 }
9371 type_::Warning::UnusedType {
9372 location,
9373 imported: true,
9374 ..
9375 } => Some(UnusedImport::ValueOrType(*location)),
9376 type_::Warning::UnusedImportedModule { location, .. } => {
9377 Some(UnusedImport::Module(*location))
9378 }
9379 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
9380 Some(UnusedImport::ModuleAlias(*location))
9381 }
9382 type_::Warning::Todo { .. }
9383 | type_::Warning::ImplicitlyDiscardedResult { .. }
9384 | type_::Warning::UnusedLiteral { .. }
9385 | type_::Warning::UnusedValue { .. }
9386 | type_::Warning::NoFieldsRecordUpdate { .. }
9387 | type_::Warning::AllFieldsRecordUpdate { .. }
9388 | type_::Warning::UnusedType { .. }
9389 | type_::Warning::UnusedConstructor { .. }
9390 | type_::Warning::UnusedPrivateModuleConstant { .. }
9391 | type_::Warning::UnusedPrivateFunction { .. }
9392 | type_::Warning::UnusedVariable { .. }
9393 | type_::Warning::UnnecessaryDoubleIntNegation { .. }
9394 | type_::Warning::UnnecessaryDoubleBoolNegation { .. }
9395 | type_::Warning::InefficientEmptyListCheck { .. }
9396 | type_::Warning::TransitiveDependencyImported { .. }
9397 | type_::Warning::DeprecatedItem { .. }
9398 | type_::Warning::UnreachableCasePattern { .. }
9399 | type_::Warning::UnusedDiscardPattern { .. }
9400 | type_::Warning::CaseMatchOnLiteralCollection { .. }
9401 | type_::Warning::CaseMatchOnLiteralValue { .. }
9402 | type_::Warning::OpaqueExternalType { .. }
9403 | type_::Warning::RedundantAssertAssignment { .. }
9404 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. }
9405 | type_::Warning::TodoOrPanicUsedAsFunction { .. }
9406 | type_::Warning::UnreachableCodeAfterPanic { .. }
9407 | type_::Warning::RedundantPipeFunctionCapture { .. }
9408 | type_::Warning::FeatureRequiresHigherGleamVersion { .. }
9409 | type_::Warning::JavaScriptIntUnsafe { .. }
9410 | type_::Warning::AssertLiteralBool { .. }
9411 | type_::Warning::BitArraySegmentTruncatedValue { .. }
9412 | type_::Warning::ModuleImportedTwice { .. }
9413 | type_::Warning::TopLevelDefinitionShadowsImport { .. }
9414 | type_::Warning::RedundantComparison { .. }
9415 | type_::Warning::UnusedRecursiveArgument { .. }
9416 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. }
9417 | type_::Warning::PipeIntoCallWhichReturnsFunction { .. } => None,
9418 })
9419 .sorted_by_key(|import| import.location())
9420 .collect_vec();
9421
9422 // If the cursor is not over any of the unused imports then we don't offer
9423 // the code action.
9424 let hovering_unused_import = unused_imports.iter().any(|import| {
9425 let unused_range = self.edits.src_span_to_lsp_range(import.location());
9426 overlaps(self.params.range, unused_range)
9427 });
9428 if !hovering_unused_import {
9429 return vec![];
9430 }
9431
9432 // Otherwise we start removing all unused imports:
9433 for import in &unused_imports {
9434 match import {
9435 // When an entire module is unused we can delete its entire location
9436 // in the source code.
9437 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
9438 if self.edits.line_numbers.spans_entire_line(location) {
9439 // If the unused module spans over the entire line then
9440 // we also take care of removing the following newline
9441 // characther!
9442 self.edits.delete(SrcSpan {
9443 start: location.start,
9444 end: location.end + 1,
9445 });
9446 } else {
9447 self.edits.delete(*location);
9448 }
9449 }
9450
9451 // When removing unused imported values we have to be a bit more
9452 // careful: an unused value might be followed or preceded by a
9453 // comma that we also need to remove!
9454 UnusedImport::ValueOrType(location) => {
9455 let imported = self.imported_values(*location);
9456 let unused_index = imported.binary_search(location);
9457 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1);
9458 let next_value = unused_index
9459 .ok()
9460 .and_then(|value_index| imported.get(value_index + 1));
9461 let previous_value = unused_index.ok().and_then(|value_index| {
9462 value_index
9463 .checked_sub(1)
9464 .and_then(|previous_index| imported.get(previous_index))
9465 });
9466 let previous_is_unused = previous_value.is_some_and(|previous| {
9467 unused_imports
9468 .as_slice()
9469 .binary_search_by_key(previous, |import| import.location())
9470 .is_ok()
9471 });
9472
9473 match (previous_value, next_value) {
9474 // If there's a value following the unused import we need
9475 // to remove all characters until its start!
9476 //
9477 // ```gleam
9478 // import wibble.{unused, used}
9479 // // ^^^^^^^^^^^ We need to remove all of this!
9480 // ```
9481 //
9482 (_, Some(next_value)) => self.edits.delete(SrcSpan {
9483 start: location.start,
9484 end: next_value.start,
9485 }),
9486
9487 // If this unused import is the last of the unuqualified
9488 // list and is preceded by another used value then we
9489 // need to do some additional cleanup and remove all
9490 // characters starting from its end.
9491 // (If the previous one is unused as well it will take
9492 // care of removing all the extra space)
9493 //
9494 // ```gleam
9495 // import wibble.{used, unused}
9496 // // ^^^^^^^^^^^^ We need to remove all of this!
9497 // ```
9498 //
9499 (Some(previous_value), _) if is_last && !previous_is_unused => {
9500 self.edits.delete(SrcSpan {
9501 start: previous_value.end,
9502 end: location.end,
9503 });
9504 }
9505
9506 // In all other cases it means that this is the only
9507 // item in the import list. We can just remove it.
9508 //
9509 // ```gleam
9510 // import wibble.{unused}
9511 // // ^^^^^^ We remove this import, the formatter will already
9512 // // take care of removing the empty curly braces
9513 // ```
9514 //
9515 (_, _) => self.edits.delete(*location),
9516 }
9517 }
9518 }
9519 }
9520
9521 let mut action = Vec::with_capacity(1);
9522 CodeActionBuilder::new("Remove unused imports")
9523 .kind(CodeActionKind::RefactorRewrite)
9524 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9525 .preferred(true)
9526 .push_to(&mut action);
9527 action
9528 }
9529}
9530
9531/// Code action to remove a block wrapping a single expression.
9532///
9533pub struct RemoveBlock<'a> {
9534 module: &'a Module,
9535 params: &'a CodeActionParams,
9536 edits: TextEdits<'a>,
9537 block_span: Option<SrcSpan>,
9538 position: RemoveBlockPosition,
9539}
9540
9541#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
9542enum RemoveBlockPosition {
9543 InsideBinOp,
9544 OutsideBinOp,
9545}
9546
9547impl<'a> RemoveBlock<'a> {
9548 pub fn new(
9549 module: &'a Module,
9550 line_numbers: &'a LineNumbers,
9551 params: &'a CodeActionParams,
9552 ) -> Self {
9553 Self {
9554 module,
9555 params,
9556 edits: TextEdits::new(line_numbers),
9557 block_span: None,
9558 position: RemoveBlockPosition::OutsideBinOp,
9559 }
9560 }
9561
9562 pub fn code_actions(mut self) -> Vec<CodeAction> {
9563 self.visit_typed_module(&self.module.ast);
9564
9565 let Some(SrcSpan { start, end }) = self.block_span else {
9566 return vec![];
9567 };
9568
9569 self.edits.delete(SrcSpan::new(start, start + 1));
9570 self.edits.delete(SrcSpan::new(end - 1, end));
9571
9572 let mut action = Vec::with_capacity(1);
9573 CodeActionBuilder::new("Remove block")
9574 .kind(CodeActionKind::RefactorRewrite)
9575 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9576 .preferred(true)
9577 .push_to(&mut action);
9578 action
9579 }
9580}
9581
9582impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> {
9583 fn visit_typed_expr_bin_op(
9584 &mut self,
9585 _location: &'ast SrcSpan,
9586 _type_: &'ast Arc<Type>,
9587 _operator: &'ast ast::BinOp,
9588 _operator_start: &'ast u32,
9589 left: &'ast TypedExpr,
9590 right: &'ast TypedExpr,
9591 ) {
9592 let old_position = self.position;
9593 self.position = RemoveBlockPosition::InsideBinOp;
9594 ast::visit::visit_typed_expr(self, left);
9595 self.position = RemoveBlockPosition::InsideBinOp;
9596 ast::visit::visit_typed_expr(self, right);
9597 self.position = old_position;
9598 }
9599
9600 fn visit_typed_expr_block(
9601 &mut self,
9602 location: &'ast SrcSpan,
9603 statements: &'ast [TypedStatement],
9604 ) {
9605 let block_range = self.edits.src_span_to_lsp_range(*location);
9606 if !within(self.params.range, block_range) {
9607 return;
9608 }
9609
9610 match statements {
9611 [] | [_, _, ..] => (),
9612 [value] => match value {
9613 ast::Statement::Use(_)
9614 | ast::Statement::Assert(_)
9615 | ast::Statement::Assignment(_) => {
9616 ast::visit::visit_typed_expr_block(self, location, statements);
9617 }
9618
9619 ast::Statement::Expression(expr) => match expr {
9620 TypedExpr::Int { .. }
9621 | TypedExpr::Float { .. }
9622 | TypedExpr::String { .. }
9623 | TypedExpr::Block { .. }
9624 | TypedExpr::Var { .. }
9625 | TypedExpr::Fn { .. }
9626 | TypedExpr::List { .. }
9627 | TypedExpr::Call { .. }
9628 | TypedExpr::Case { .. }
9629 | TypedExpr::RecordAccess { .. }
9630 | TypedExpr::PositionalAccess { .. }
9631 | TypedExpr::ModuleSelect { .. }
9632 | TypedExpr::Tuple { .. }
9633 | TypedExpr::TupleIndex { .. }
9634 | TypedExpr::Todo { .. }
9635 | TypedExpr::Panic { .. }
9636 | TypedExpr::Echo { .. }
9637 | TypedExpr::BitArray { .. }
9638 | TypedExpr::RecordUpdate { .. }
9639 | TypedExpr::NegateBool { .. }
9640 | TypedExpr::NegateInt { .. }
9641 | TypedExpr::Invalid { .. } => {
9642 self.block_span = Some(*location);
9643 }
9644 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => {
9645 if self.position == RemoveBlockPosition::OutsideBinOp {
9646 self.block_span = Some(*location);
9647 }
9648 }
9649 },
9650 },
9651 }
9652
9653 ast::visit::visit_typed_expr_block(self, location, statements);
9654 }
9655}
9656
9657/// Code action to remove `opaque` from a private type.
9658///
9659pub struct RemovePrivateOpaque<'a> {
9660 module: &'a Module,
9661 params: &'a CodeActionParams,
9662 edits: TextEdits<'a>,
9663 opaque_span: Option<SrcSpan>,
9664}
9665
9666impl<'a> RemovePrivateOpaque<'a> {
9667 pub fn new(
9668 module: &'a Module,
9669 line_numbers: &'a LineNumbers,
9670 params: &'a CodeActionParams,
9671 ) -> Self {
9672 Self {
9673 module,
9674 params,
9675 edits: TextEdits::new(line_numbers),
9676 opaque_span: None,
9677 }
9678 }
9679
9680 pub fn code_actions(mut self) -> Vec<CodeAction> {
9681 self.visit_typed_module(&self.module.ast);
9682
9683 let Some(opaque_span) = self.opaque_span else {
9684 return vec![];
9685 };
9686
9687 self.edits.delete(opaque_span);
9688
9689 let mut action = Vec::with_capacity(1);
9690 CodeActionBuilder::new("Remove opaque from private type")
9691 .kind(CodeActionKind::QuickFix)
9692 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9693 .preferred(true)
9694 .push_to(&mut action);
9695 action
9696 }
9697}
9698
9699impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> {
9700 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
9701 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location);
9702 if !within(self.params.range, custom_type_range) {
9703 return;
9704 }
9705
9706 if custom_type.opaque && custom_type.publicity.is_private() {
9707 self.opaque_span = Some(SrcSpan {
9708 start: custom_type.location.start,
9709 end: custom_type.location.start + 7,
9710 });
9711 }
9712 }
9713}
9714
9715/// Code action to rewrite a case expression as part of an outer case expression
9716/// branch. For example:
9717///
9718/// ```gleam
9719/// case wibble {
9720/// Ok(a) -> case a {
9721/// 1 -> todo
9722/// _ -> todo
9723/// }
9724/// Error(_) -> todo
9725/// }
9726/// ```
9727///
9728/// Would become:
9729///
9730/// ```gleam
9731/// case wibble {
9732/// Ok(1) -> todo
9733/// Ok(_) -> todo
9734/// Error(_) -> todo
9735/// }
9736/// ```
9737///
9738pub struct CollapseNestedCase<'a> {
9739 module: &'a Module,
9740 params: &'a CodeActionParams,
9741 edits: TextEdits<'a>,
9742 collapsed: Option<Collapsed<'a>>,
9743}
9744
9745/// This holds all the needed data about the pattern to collapse.
9746/// We'll use this piece of code as an example:
9747/// ```gleam
9748/// case something {
9749/// User(username: _, NotAdmin) -> "Stranger!!"
9750/// User(username:, Admin) if wibble ->
9751/// case username { // <- We're collapsing this nested case
9752/// "Joe" -> "Hello, Joe!"
9753/// _ -> "I don't know you, " <> username
9754/// }
9755/// }
9756/// ```
9757///
9758struct Collapsed<'a> {
9759 /// This is the span covering the entire clause being collapsed:
9760 ///
9761 /// ```gleam
9762 /// case something {
9763 /// User(username: _, NotAdmin) -> "Stranger!!"
9764 /// User(username:, Admin) if wibble ->
9765 /// ┬ It goes all the way from here...
9766 /// ╭─╯
9767 /// │ case username {
9768 /// │ "Joe" -> "Hello, Joe!"
9769 /// │ _ -> "I don't know you, " <> username
9770 /// │ }
9771 /// │ ┬ ...to here!
9772 /// ╰───╯
9773 /// }
9774 /// ```
9775 ///
9776 outer_clause_span: SrcSpan,
9777
9778 /// The (optional) guard of the outer branch. In this exmaple it's this one:
9779 ///
9780 /// ```gleam
9781 /// case something {
9782 /// User(username: _, NotAdmin) -> "Stranger!!"
9783 /// User(username:, Admin) if wibble ->
9784 /// ┬────────
9785 /// ╰─ `outer_guard`
9786 /// case username {
9787 /// "Joe" -> "Hello, Joe!"
9788 /// _ -> "I don't know you, " <> username
9789 /// }
9790 /// }
9791 /// ```
9792 ///
9793 outer_guard: &'a Option<TypedClauseGuard>,
9794
9795 /// The pattern variable being matched on:
9796 ///
9797 /// ```gleam
9798 /// case something {
9799 /// User(username: _, NotAdmin) -> "Stranger!!"
9800 /// User(username:, Admin) if wibble ->
9801 /// ┬───────
9802 /// ╰─ `matched_variable`
9803 /// case username {
9804 /// "Joe" -> "Hello, Joe!"
9805 /// _ -> "I don't know you, " <> username
9806 /// }
9807 /// }
9808 /// ```
9809 ///
9810 matched_variable: BoundVariable,
9811
9812 /// The span covering the entire pattern that is bringing the matched
9813 /// variable in scope:
9814 ///
9815 /// ```gleam
9816 /// case something {
9817 /// User(username: _, NotAdmin) -> "Stranger!!"
9818 /// User(username:, Admin) if wibble ->
9819 /// ┬─────────────────────
9820 /// ╰─ `matched_pattern_span`
9821 /// case username {
9822 /// "Joe" -> "Hello, Joe!"
9823 /// _ -> "I don't know you, " <> username
9824 /// }
9825 /// }
9826 /// ```
9827 ///
9828 matched_pattern_span: SrcSpan,
9829
9830 /// The clauses matching on the `username` variable. In this case they are:
9831 /// ```gleam
9832 /// "Joe" -> "Hello, Joe!"
9833 /// _ -> "I don't know you, " <> username
9834 /// ```
9835 ///
9836 inner_clauses: &'a Vec<ast::TypedClause>,
9837}
9838
9839impl<'a> CollapseNestedCase<'a> {
9840 pub fn new(
9841 module: &'a Module,
9842 line_numbers: &'a LineNumbers,
9843 params: &'a CodeActionParams,
9844 ) -> Self {
9845 Self {
9846 module,
9847 params,
9848 edits: TextEdits::new(line_numbers),
9849 collapsed: None,
9850 }
9851 }
9852
9853 pub fn code_actions(mut self) -> Vec<CodeAction> {
9854 self.visit_typed_module(&self.module.ast);
9855
9856 let Some(Collapsed {
9857 outer_clause_span,
9858 outer_guard,
9859 ref matched_variable,
9860 matched_pattern_span,
9861 inner_clauses,
9862 }) = self.collapsed
9863 else {
9864 return vec![];
9865 };
9866
9867 // Now comes the tricky part: we need to replace the current pattern
9868 // that is bringing the variable into scope with many new patterns, one
9869 // for each of the inner clauses.
9870 //
9871 // Each time we will have to replace the matched variable with the
9872 // pattern used in the inner clause. Let's look at an example:
9873 //
9874 // ```gleam
9875 // Ok(a) -> case a {
9876 // 1 -> wibble
9877 // 2 | 3 -> wobble
9878 // _ -> woo
9879 // }
9880 // ```
9881 //
9882 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then
9883 // with `2` and `3`, and finally with `_`. Obtaining something like
9884 // this:
9885 //
9886 // ```gleam
9887 // Ok(1) -> wibble
9888 // Ok(2) | Ok(3) -> wobble
9889 // Ok(_) -> woo
9890 // ```
9891 //
9892 // Notice one key detail: since alternative patterns can't be nested we
9893 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`!
9894
9895 let pattern_text: String = code_at(self.module, matched_pattern_span).into();
9896 let matched_variable_span = matched_variable.location;
9897
9898 let pattern_with_variable = |mut new_content: String| {
9899 let mut new_pattern = pattern_text.clone();
9900
9901 match matched_variable {
9902 BoundVariable {
9903 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. },
9904 ..
9905 } => {
9906 let trimmed_contents = new_content.trim();
9907
9908 let pattern_is_literal_list =
9909 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]");
9910 let pattern_is_discard = trimmed_contents == "_";
9911
9912 let span_to_replace = match (
9913 &matched_variable.name,
9914 // We verify whether the pattern is compatible with the list prefix `..`.
9915 // For example, `..var` is valid syntax, but `..[]` and `.._` are not.
9916 pattern_is_literal_list || pattern_is_discard,
9917 ) {
9918 // We normally replace the selected variable with the pattern.
9919 (BoundVariableName::Regular { .. }, _) => matched_variable_span,
9920
9921 // If the selected pattern is not a list, we also replace it normally.
9922 (BoundVariableName::ListTail { .. }, false) => matched_variable_span,
9923 // If the pattern is a list to also remove the list tail prefix.
9924 (BoundVariableName::ListTail { tail_location, .. }, true) => {
9925 // When it's a list literal, we remove the surrounding brackets.
9926 let len = trimmed_contents.len();
9927 if let Some(slice) = new_content.trim().get(1..(len - 1)) {
9928 new_content = slice.to_string();
9929 }
9930
9931 *tail_location
9932 }
9933
9934 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(),
9935 };
9936
9937 let start_of_pattern =
9938 (span_to_replace.start - matched_pattern_span.start) as usize;
9939 let pattern_length = span_to_replace.len();
9940
9941 let end_of_pattern = start_of_pattern + pattern_length;
9942 let replaced_range = start_of_pattern..end_of_pattern;
9943
9944 new_pattern.replace_range(replaced_range, &new_content);
9945 }
9946
9947 BoundVariable {
9948 name: BoundVariableName::ShorthandLabel { .. },
9949 ..
9950 } => {
9951 // But if it's introduced using the shorthand syntax we can't
9952 // just replace it's location with the new pattern: we would be
9953 // removing the label!!
9954 // So we instead insert the pattern right after the label.
9955 new_pattern.insert_str(
9956 (matched_variable_span.end - matched_pattern_span.start) as usize,
9957 &format!(" {new_content}"),
9958 );
9959 }
9960 }
9961
9962 new_pattern
9963 };
9964
9965 let mut new_clauses = vec![];
9966 for clause in inner_clauses {
9967 // Here we take care of unrolling any alterantive patterns: for each
9968 // of the alternatives we build a new pattern and then join
9969 // everything together with ` | `.
9970
9971 let references_to_matched_variable =
9972 FindVariableReferences::new(matched_variable_span, matched_variable.name())
9973 .find(&clause.then);
9974
9975 let new_patterns = iter::once(&clause.pattern)
9976 .chain(&clause.alternative_patterns)
9977 .map(|patterns| {
9978 // If we've reached this point we've already made in the
9979 // traversal that the inner clause is matching on a single
9980 // subject. So this should be safe to expect!
9981 let pattern_location =
9982 patterns.first().expect("must have a pattern").location();
9983
9984 let mut pattern_code = code_at(self.module, pattern_location).to_string();
9985 if !references_to_matched_variable.is_empty() {
9986 pattern_code = format!("{pattern_code} as {}", matched_variable.name());
9987 }
9988 pattern_with_variable(pattern_code)
9989 })
9990 .join(" | ");
9991
9992 let clause_code = code_at(self.module, clause.then.location());
9993 let guard_code = match (outer_guard, &clause.guard) {
9994 (Some(outer), Some(inner)) => {
9995 let mut outer_code = code_at(self.module, outer.location()).to_string();
9996 let mut inner_code = code_at(self.module, inner.location()).to_string();
9997 if ast::BinOp::And.precedence() > outer.precedence() {
9998 outer_code = format!("{{ {outer_code} }}");
9999 }
10000 if ast::BinOp::And.precedence() > inner.precedence() {
10001 inner_code = format!("{{ {inner_code} }}");
10002 }
10003 format!(" if {outer_code} && {inner_code}")
10004 }
10005 (None, Some(guard)) | (Some(guard), None) => {
10006 format!(" if {}", code_at(self.module, guard.location()))
10007 }
10008 (None, None) => "".into(),
10009 };
10010
10011 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}"));
10012 }
10013
10014 let pattern_nesting = self
10015 .edits
10016 .src_span_to_lsp_range(outer_clause_span)
10017 .start
10018 .character;
10019 let indentation = " ".repeat(pattern_nesting as usize);
10020
10021 self.edits.replace(
10022 outer_clause_span,
10023 new_clauses.join(&format!("\n{indentation}")),
10024 );
10025
10026 let mut action = Vec::with_capacity(1);
10027 CodeActionBuilder::new("Collapse nested case")
10028 .kind(CodeActionKind::RefactorRewrite)
10029 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10030 .preferred(false)
10031 .push_to(&mut action);
10032 action
10033 }
10034
10035 /// If the clause can be flattened because it's matching on a single variable
10036 /// defined in it, this function will return the info needed by the language
10037 /// server to flatten that case.
10038 ///
10039 /// We can only flatten a case expression in a very specific case:
10040 /// - This pattern may be introducing multiple variables,
10041 /// - The expression following this branch must be a case, and
10042 /// - It must be matching on one of those variables
10043 ///
10044 /// For example:
10045 ///
10046 /// ```gleam
10047 /// Wibble(a, b, 1) -> case a { ... }
10048 /// Wibble(a, b, 1) -> case b { ... }
10049 /// ```
10050 ///
10051 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> {
10052 let ast::TypedClause {
10053 pattern,
10054 alternative_patterns,
10055 then,
10056 location,
10057 guard,
10058 } = clause;
10059
10060 if !alternative_patterns.is_empty() {
10061 return None;
10062 }
10063
10064 // The `then` clause must be a single case expression matching on a
10065 // single variable.
10066 let Some(TypedExpr::Case {
10067 subjects, clauses, ..
10068 }) = single_expression(then)
10069 else {
10070 return None;
10071 };
10072
10073 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else {
10074 return None;
10075 };
10076
10077 // That variable must be one the variables we brought into scope in this
10078 // branch.
10079 let variable = pattern
10080 .iter()
10081 .flat_map(|pattern| pattern.bound_variables())
10082 .find(|variable| variable.name() == *name)?;
10083
10084 // There's one last condition to trigger the code action: we must
10085 // actually be with the cursor over the pattern or the nested case
10086 // expression!
10087 //
10088 // ```gleam
10089 // case wibble {
10090 // Ok(a) -> case a {
10091 // //^^^^^^^^^^^^^^^ Anywhere over here!
10092 // }
10093 // }
10094 // ```
10095 //
10096 let first_pattern = pattern.first().expect("at least one pattern");
10097 let last_pattern = pattern.last().expect("at least one pattern");
10098 let pattern_location = first_pattern.location().merge(&last_pattern.location());
10099
10100 let last_inner_subject = subjects.last().expect("at least one subject");
10101 let trigger_location = pattern_location.merge(&last_inner_subject.location());
10102 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location);
10103
10104 if within(self.params.range, trigger_range) {
10105 Some(Collapsed {
10106 outer_clause_span: *location,
10107 outer_guard: guard,
10108 matched_variable: variable,
10109 matched_pattern_span: pattern_location,
10110 inner_clauses: clauses,
10111 })
10112 } else {
10113 None
10114 }
10115 }
10116}
10117
10118impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> {
10119 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
10120 if let Some(collapsed) = self.flatten_clause(clause) {
10121 self.collapsed = Some(collapsed);
10122
10123 // We're done, there's no need to keep exploring as we know the
10124 // cursor is over this pattern and it can't be over any other one!
10125 return;
10126 }
10127
10128 ast::visit::visit_typed_clause(self, clause);
10129 }
10130}
10131
10132/// If the expression is a single expression, or a block containing a single
10133/// expression, this function will return it.
10134/// But if the expression is a block with multiple statements, an assignment
10135/// of a use, this will return None.
10136///
10137fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
10138 match expression {
10139 // If a block has a single statement, we can flatten it into a
10140 // single expression if that one statement is an expression.
10141 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() {
10142 ast::Statement::Expression(expression) => single_expression(expression),
10143 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
10144 None
10145 }
10146 },
10147
10148 // If a block has multiple statements then it can't be flattened
10149 // into a single expression.
10150 TypedExpr::Block { .. } => None,
10151
10152 TypedExpr::Int { .. }
10153 | TypedExpr::Float { .. }
10154 | TypedExpr::String { .. }
10155 | TypedExpr::Pipeline { .. }
10156 | TypedExpr::Var { .. }
10157 | TypedExpr::Fn { .. }
10158 | TypedExpr::List { .. }
10159 | TypedExpr::Call { .. }
10160 | TypedExpr::BinOp { .. }
10161 | TypedExpr::Case { .. }
10162 | TypedExpr::RecordAccess { .. }
10163 | TypedExpr::PositionalAccess { .. }
10164 | TypedExpr::ModuleSelect { .. }
10165 | TypedExpr::Tuple { .. }
10166 | TypedExpr::TupleIndex { .. }
10167 | TypedExpr::Todo { .. }
10168 | TypedExpr::Panic { .. }
10169 | TypedExpr::Echo { .. }
10170 | TypedExpr::BitArray { .. }
10171 | TypedExpr::RecordUpdate { .. }
10172 | TypedExpr::NegateBool { .. }
10173 | TypedExpr::NegateInt { .. }
10174 | TypedExpr::Invalid { .. } => Some(expression),
10175 }
10176}
10177
10178/// Code action to remove unreachable clauses from a case expression.
10179///
10180pub struct RemoveUnreachableCaseClauses<'a> {
10181 module: &'a Module,
10182 params: &'a CodeActionParams,
10183 edits: TextEdits<'a>,
10184 /// The source location of the patterns of all the unreachable clauses in
10185 /// the current module.
10186 ///
10187 unreachable_clauses: HashSet<SrcSpan>,
10188 clauses_to_delete: Vec<SrcSpan>,
10189}
10190
10191impl<'a> RemoveUnreachableCaseClauses<'a> {
10192 pub fn new(
10193 module: &'a Module,
10194 line_numbers: &'a LineNumbers,
10195 params: &'a CodeActionParams,
10196 ) -> Self {
10197 let unreachable_clauses = module
10198 .ast
10199 .type_info
10200 .warnings
10201 .iter()
10202 .filter_map(|warning| {
10203 if let type_::Warning::UnreachableCasePattern { location, .. } = warning {
10204 Some(*location)
10205 } else {
10206 None
10207 }
10208 })
10209 .collect();
10210
10211 Self {
10212 unreachable_clauses,
10213 module,
10214 params,
10215 edits: TextEdits::new(line_numbers),
10216 clauses_to_delete: vec![],
10217 }
10218 }
10219
10220 pub fn code_actions(mut self) -> Vec<CodeAction> {
10221 self.visit_typed_module(&self.module.ast);
10222 if self.clauses_to_delete.is_empty() {
10223 return vec![];
10224 }
10225
10226 for branch in self.clauses_to_delete {
10227 self.edits.delete(branch);
10228 }
10229
10230 let mut action = Vec::with_capacity(1);
10231 CodeActionBuilder::new("Remove unreachable clauses")
10232 .kind(CodeActionKind::QuickFix)
10233 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10234 .preferred(true)
10235 .push_to(&mut action);
10236 action
10237 }
10238}
10239
10240impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> {
10241 fn visit_typed_expr_case(
10242 &mut self,
10243 location: &'ast SrcSpan,
10244 type_: &'ast Arc<Type>,
10245 subjects: &'ast [TypedExpr],
10246 clauses: &'ast [ast::TypedClause],
10247 compiled_case: &'ast CompiledCase,
10248 ) {
10249 // We're showing the code action only if we're within one of the
10250 // unreachable patterns. And the code action is going to remove all the
10251 // unreachable patterns for this case.
10252 let is_hovering_clause = clauses.iter().any(|clause| {
10253 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location());
10254 within(self.params.range, pattern_range)
10255 });
10256
10257 // If we're not hovering any of the clauses then we want to
10258 // keep visiting the case expression as the unreachable branch might be
10259 // in one of the nested cases.
10260 if !is_hovering_clause {
10261 ast::visit::visit_typed_expr_case(
10262 self,
10263 location,
10264 type_,
10265 subjects,
10266 clauses,
10267 compiled_case,
10268 );
10269 return;
10270 }
10271
10272 for clause in clauses {
10273 let mut all_patterns_are_unreachable = true;
10274 let mut unreachable_patterns = vec![];
10275 let mut previous_pattern_end = None;
10276 let mut all_previous_patterns_were_deleted = true;
10277
10278 for pattern in clause.patterns() {
10279 let pattern_location = multi_pattern_location(pattern);
10280 if self.unreachable_clauses.contains(&pattern_location) {
10281 // If an alternative is unreachable we want to delete
10282 // everything from the end of the previous alternative to
10283 // the start of this one.
10284 //
10285 // ```gleam
10286 // Error(_) | Error(_) | Ok(_)
10287 // // ^^^^^^^^^^^ We want to delete all of this
10288 // ```
10289 unreachable_patterns.push(
10290 previous_pattern_end.map_or(pattern_location, |previous_end| {
10291 SrcSpan::new(previous_end, pattern_location.end)
10292 }),
10293 );
10294 } else {
10295 // If all the previous alternatives have been deleted and
10296 // this one is reachable there's some final cleanup we need
10297 // to take care of:
10298 //
10299 // ```gleam
10300 // Ok(_) | Ok(_) | Error(_) -> todo
10301 // //^^^^^^^^^^^ All of this has been deleted, but there's
10302 // // that last vertical bar that needs to be
10303 // // taken care of!
10304 // ```
10305 //
10306
10307 if all_previous_patterns_were_deleted && let Some(end) = previous_pattern_end {
10308 self.clauses_to_delete
10309 .push(SrcSpan::new(end, pattern_location.start));
10310 }
10311
10312 all_previous_patterns_were_deleted = false;
10313 all_patterns_are_unreachable = false;
10314 }
10315
10316 previous_pattern_end = pattern.last().map(|pattern| pattern.location().end);
10317 }
10318
10319 if all_patterns_are_unreachable {
10320 // If all the patterns of the clause are unreachable then we
10321 // want to delete the entire branch:
10322 //
10323 // ```gleam
10324 // case a, b {
10325 // _, _ -> todo
10326 // Ok(_), Ok(_) | Error(_), Error(_) -> todo
10327 // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10328 // // we want the entire branch to be deleted!
10329 // }
10330 // ```
10331 self.clauses_to_delete.push(clause.location());
10332 } else {
10333 // If only some of the variants are unreachable but not all
10334 // we want to delete just those.
10335 // case a, b {
10336 // 1, 2 | 1, 2 -> todo
10337 // // ^^^^ just this one should be deleted
10338 // }
10339 self.clauses_to_delete.extend(&unreachable_patterns);
10340 }
10341 }
10342 }
10343}
10344
10345/// Given a pattern with possibly many subjects, this returns the location
10346/// spanning the whole thing.
10347fn multi_pattern_location(alternative: &[Pattern<Arc<Type>>]) -> SrcSpan {
10348 let start = alternative
10349 .first()
10350 .map(|pattern| pattern.location().start)
10351 .unwrap_or_default();
10352 let end = alternative
10353 .last()
10354 .map(|pattern| pattern.location().end)
10355 .unwrap_or_default();
10356
10357 SrcSpan::new(start, end)
10358}
10359
10360/// Code action to remove a record update when all of its fields have been
10361/// provided already:
10362///
10363/// ```gleam
10364/// pub type Wibble { Wibble(one: Int, two: Int) }
10365///
10366/// wibble(..wibble, one:, two:)
10367/// // ^^^^^^^^ This is not needed and raises a warning!
10368/// ```
10369///
10370pub struct RemoveRedundantRecordUpdate<'a> {
10371 module: &'a Module,
10372 params: &'a CodeActionParams,
10373 edits: TextEdits<'a>,
10374}
10375
10376impl<'a> RemoveRedundantRecordUpdate<'a> {
10377 pub fn new(
10378 module: &'a Module,
10379 line_numbers: &'a LineNumbers,
10380 params: &'a CodeActionParams,
10381 ) -> Self {
10382 Self {
10383 module,
10384 params,
10385 edits: TextEdits::new(line_numbers),
10386 }
10387 }
10388
10389 pub fn code_actions(mut self) -> Vec<CodeAction> {
10390 let spread_to_remove = self
10391 .module
10392 .ast
10393 .type_info
10394 .warnings
10395 .iter()
10396 .find_map(|warning| {
10397 if let type_::Warning::AllFieldsRecordUpdate {
10398 location,
10399 record_location,
10400 } = warning
10401 && within(
10402 self.params.range,
10403 self.edits.src_span_to_lsp_range(*location),
10404 )
10405 {
10406 Some(*record_location)
10407 } else {
10408 None
10409 }
10410 });
10411
10412 let Some(spread_to_remove) = spread_to_remove else {
10413 return vec![];
10414 };
10415 self.edits.delete(spread_to_remove);
10416
10417 let mut action = Vec::with_capacity(1);
10418 CodeActionBuilder::new("Remove redundant record update")
10419 .kind(CodeActionKind::QuickFix)
10420 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10421 .preferred(true)
10422 .push_to(&mut action);
10423 action
10424 }
10425}
10426
10427/// Code action to add labels to a constructor/call where all the labels where
10428/// omitted.
10429///
10430pub struct AddOmittedLabels<'a> {
10431 module: &'a Module,
10432 params: &'a CodeActionParams,
10433 edits: TextEdits<'a>,
10434 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>,
10435}
10436
10437struct CallArgumentWithOmittedLabel {
10438 location: SrcSpan,
10439
10440 /// If the argument has a label this will be the label we can use for it.
10441 ///
10442 omitted_label: Option<EcoString>,
10443
10444 /// If the argument is a variable that has the same name as the omitted label
10445 /// and could use the shorthand syntax.
10446 ///
10447 can_use_shorthand_syntax: bool,
10448}
10449
10450impl<'a> AddOmittedLabels<'a> {
10451 pub fn new(
10452 module: &'a Module,
10453 line_numbers: &'a LineNumbers,
10454 params: &'a CodeActionParams,
10455 ) -> Self {
10456 Self {
10457 module,
10458 params,
10459 edits: TextEdits::new(line_numbers),
10460 arguments_and_omitted_labels: None,
10461 }
10462 }
10463
10464 pub fn code_actions(mut self) -> Vec<CodeAction> {
10465 self.visit_typed_module(&self.module.ast);
10466
10467 let Some(call_arguments) = self.arguments_and_omitted_labels else {
10468 return vec![];
10469 };
10470
10471 for call_argument in call_arguments {
10472 let Some(label) = call_argument.omitted_label else {
10473 continue;
10474 };
10475 if call_argument.can_use_shorthand_syntax {
10476 self.edits.insert(call_argument.location.end, ":".into());
10477 } else {
10478 self.edits
10479 .insert(call_argument.location.start, format!("{label}: "));
10480 }
10481 }
10482
10483 let mut action = Vec::with_capacity(1);
10484 CodeActionBuilder::new("Add omitted labels")
10485 .kind(CodeActionKind::RefactorRewrite)
10486 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10487 .preferred(false)
10488 .push_to(&mut action);
10489 action
10490 }
10491}
10492
10493impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
10494 fn visit_typed_expr_call(
10495 &mut self,
10496 location: &'ast SrcSpan,
10497 type_: &'ast Arc<Type>,
10498 fun: &'ast TypedExpr,
10499 arguments: &'ast [TypedCallArg],
10500 open_parenthesis: &'ast Option<u32>,
10501 ) {
10502 let called_function_range = self.edits.src_span_to_lsp_range(fun.location());
10503 if !within(self.params.range, called_function_range) {
10504 ast::visit::visit_typed_expr_call(
10505 self,
10506 location,
10507 type_,
10508 fun,
10509 arguments,
10510 open_parenthesis,
10511 );
10512 return;
10513 }
10514
10515 let Some(field_map) = fun.field_map() else {
10516 ast::visit::visit_typed_expr_call(
10517 self,
10518 location,
10519 type_,
10520 fun,
10521 arguments,
10522 open_parenthesis,
10523 );
10524 return;
10525 };
10526 let argument_index_to_label = field_map.indices_to_labels();
10527
10528 let mut omitted_labels = Vec::with_capacity(arguments.len());
10529 for (index, argument) in arguments.iter().enumerate() {
10530 // If the argument already has a label we don't want to add a label
10531 // for it, so we skip it.
10532 if let Some(label) = &argument.label {
10533 // Though, before skipping, we want to make sure that the label
10534 // is actually right for the function call. If it's not then we
10535 // give up on adding labels because there wouldn't be no way of
10536 // knowing which label to add.
10537 if !field_map.fields.contains_key(label) {
10538 return;
10539 } else {
10540 continue;
10541 }
10542 }
10543 // No labels for pipes, uses, etc!
10544 if argument.is_implicit() {
10545 continue;
10546 }
10547
10548 let label = argument_index_to_label
10549 .get(&(index as u32))
10550 .cloned()
10551 .cloned();
10552
10553 let can_use_shorthand_syntax = match (&label, &argument.value) {
10554 (Some(label), TypedExpr::Var { name, .. }) => name == label,
10555 (Some(_) | None, _) => false,
10556 };
10557
10558 omitted_labels.push(CallArgumentWithOmittedLabel {
10559 location: argument.location,
10560 omitted_label: label,
10561 can_use_shorthand_syntax,
10562 });
10563 }
10564 self.arguments_and_omitted_labels = Some(omitted_labels);
10565 }
10566}
10567
10568/// Code action to extract selected code into a separate function.
10569/// If a user selected a portion of code in a function, we offer a code action
10570/// to extract it into a new one. This can either be a single expression, such
10571/// as in the following example:
10572///
10573/// ```gleam
10574/// pub fn main() {
10575/// let value = {
10576/// // ^ User selects from here
10577/// ...
10578/// }
10579/// //^ Until here
10580/// }
10581/// ```
10582///
10583/// Here, we would extract the selected block expression. It could also be a
10584/// series of statements. For example:
10585///
10586/// ```gleam
10587/// pub fn main() {
10588/// let a = 1
10589/// //^ User selects from here
10590/// let b = 2
10591/// let c = a + b
10592/// // ^ Until here
10593///
10594/// do_more_things(c)
10595/// }
10596/// ```
10597///
10598/// Here, we want to extract the statements inside the user's selection.
10599///
10600pub struct ExtractFunction<'a> {
10601 module: &'a Module,
10602 params: &'a CodeActionParams,
10603 edits: TextEdits<'a>,
10604 function: Option<ExtractedFunction<'a>>,
10605 function_end_position: Option<u32>,
10606 /// Since the `visit_typed_statement` visitor function doesn't tell us when
10607 /// a statement is the last in a block or function, we need to track that
10608 /// manually.
10609 last_statement_location: Option<SrcSpan>,
10610 /// When visiting a pipeline step, this will hold the type of the value
10611 /// returned by the previous step (if any!)
10612 previous_pipeline_assignment_type: Option<Arc<Type>>,
10613}
10614
10615/// Information about a section of code we are extracting as a function.
10616#[derive(Debug)]
10617struct ExtractedFunction<'a> {
10618 /// A list of parameters which need to be passed to the extracted function.
10619 /// These are any variables used in the extracted code, which are defined
10620 /// outside of the extracted code.
10621 parameters: Vec<(EcoString, Arc<Type>)>,
10622 /// A list of values which need to be returned from the extracted function.
10623 /// These are the variables defined in the extracted code which are used
10624 /// outside of the extracted section.
10625 returned_variables: Vec<(EcoString, Arc<Type>)>,
10626 /// The piece of code to be extracted. This is either a single expression or
10627 /// a list of statements, as explained in the documentation of `ExtractFunction`
10628 value: ExtractedValue<'a>,
10629}
10630
10631impl<'a> ExtractedFunction<'a> {
10632 fn new(value: ExtractedValue<'a>) -> Self {
10633 Self {
10634 value,
10635 parameters: Vec::new(),
10636 returned_variables: Vec::new(),
10637 }
10638 }
10639
10640 fn location(&self) -> SrcSpan {
10641 match &self.value {
10642 ExtractedValue::Expression(expression) => expression.location(),
10643 ExtractedValue::Statements { location, .. }
10644 | ExtractedValue::Use { location, .. }
10645 | ExtractedValue::PipelineSteps { location, .. } => *location,
10646 }
10647 }
10648
10649 /// If the extracted function is a series of pipeline steps, this adds to it
10650 /// the given pipeline step, otherwise leaving it unchanged.
10651 /// If the extracted function was indeed a pipeline, this will return `true`,
10652 /// otherwise it returns `false`.
10653 ///
10654 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) {
10655 if let ExtractedFunction {
10656 value:
10657 ExtractedValue::PipelineSteps {
10658 location,
10659 before_first: _,
10660 return_type,
10661 },
10662 ..
10663 } = self
10664 {
10665 // If we're extracting this pipeline and the final step is included
10666 // in the selection we want to add it to the extracted steps
10667 *return_type = step_type;
10668 *location = location.merge(&step_location);
10669 }
10670 }
10671}
10672
10673#[derive(Debug)]
10674enum ExtractedValue<'a> {
10675 Expression(&'a TypedExpr),
10676 Statements {
10677 location: SrcSpan,
10678 position: StatementPosition,
10679 /// The type of the final statement.
10680 type_: Arc<Type>,
10681 },
10682 /// We're extracting a single use statement. We need this special case to
10683 /// properly handle the statements inside of them.
10684 Use {
10685 /// This is the location of the entire use block, including the
10686 /// statements in it.
10687 location: SrcSpan,
10688 /// This is the location of the expression on the right hand side of the use
10689 /// arrow.
10690 ///
10691 /// ```gleam
10692 /// use a <- result.try(result)
10693 /// ^^^^^^^^^^^^^^^^^^
10694 /// ```
10695 ///
10696 use_line_location: SrcSpan,
10697 type_: Arc<Type>,
10698 },
10699 PipelineSteps {
10700 location: SrcSpan,
10701 /// The type of the value produced by the pipeline steps that will be
10702 /// piped into the extracted function. Could be none if the steps we're
10703 /// extracting include the first step, in that case there would be
10704 /// nothing that is fed into it.
10705 before_first: Option<Arc<Type>>,
10706 /// The type returned by the extracted steps.
10707 return_type: Arc<Type>,
10708 },
10709}
10710
10711impl ExtractedValue<'_> {
10712 fn location(&self) -> SrcSpan {
10713 match self {
10714 ExtractedValue::Expression(typed_expr) => typed_expr.location(),
10715 ExtractedValue::Statements { location, .. }
10716 | ExtractedValue::PipelineSteps { location, .. }
10717 | ExtractedValue::Use { location, .. } => *location,
10718 }
10719 }
10720}
10721
10722#[derive(Debug)]
10723enum StatementPosition {
10724 Tail,
10725 NotTail,
10726}
10727
10728impl<'a> ExtractFunction<'a> {
10729 pub fn new(
10730 module: &'a Module,
10731 line_numbers: &'a LineNumbers,
10732 params: &'a CodeActionParams,
10733 ) -> Self {
10734 Self {
10735 module,
10736 params,
10737 edits: TextEdits::new(line_numbers),
10738 function: None,
10739 function_end_position: None,
10740 last_statement_location: None,
10741 previous_pipeline_assignment_type: None,
10742 }
10743 }
10744
10745 pub fn code_actions(mut self) -> Vec<CodeAction> {
10746 // If no code is selected, then there is no function to extract and we
10747 // can return no code actions.
10748 if self.params.range.start == self.params.range.end {
10749 return Vec::new();
10750 }
10751
10752 self.visit_typed_module(&self.module.ast);
10753
10754 let Some(end) = self.function_end_position else {
10755 return Vec::new();
10756 };
10757
10758 // If nothing was found in the selected range, there is no code action.
10759 let Some(extracted) = self.function.take() else {
10760 return Vec::new();
10761 };
10762
10763 match extracted.value {
10764 // If we extract a block, it isn't very helpful to have the body of
10765 // the extracted function just be a single block expression, so
10766 // instead we extract the statements inside the block. For example,
10767 // the following code:
10768 //
10769 // ```gleam
10770 // pub fn main() {
10771 // let x = {
10772 // // ^ Select from here
10773 // let a = 1
10774 // let b = 2
10775 // a + b
10776 // }
10777 // //^ Until here
10778 // x
10779 // }
10780 // ```
10781 //
10782 // Would produce the following extracted function:
10783 //
10784 // ```gleam
10785 // fn function() {
10786 // let a = 1
10787 // let b = 2
10788 // a + b
10789 // }
10790 // ```
10791 //
10792 // Rather than:
10793 //
10794 // ```gleam
10795 // fn function() {
10796 // {
10797 // let a = 1
10798 // let b = 2
10799 // a + b
10800 // }
10801 // }
10802 // ```
10803 //
10804 ExtractedValue::Expression(TypedExpr::Block {
10805 statements,
10806 location: full_location,
10807 }) => {
10808 let location = statements
10809 .first()
10810 .location()
10811 .merge(&statements.last().location());
10812
10813 self.extract_unbound_statements(
10814 *full_location,
10815 location,
10816 extracted.parameters,
10817 statements.last().type_(),
10818 end,
10819 );
10820 }
10821 ExtractedValue::Expression(TypedExpr::Fn {
10822 type_,
10823 location: full_location,
10824 kind: FunctionLiteralKind::Anonymous { .. },
10825 arguments,
10826 body,
10827 ..
10828 }) => {
10829 let location = body.first().location().merge(&body.last().location());
10830 let return_type = type_.return_type().expect("Fn should have a return type");
10831
10832 if extracted.parameters.is_empty() {
10833 self.extract_anonymous_function(
10834 *full_location,
10835 location,
10836 arguments,
10837 return_type,
10838 end,
10839 );
10840 } else if arguments.len() == 1 {
10841 self.extract_anonymous_function_with_capture_hole(
10842 *full_location,
10843 location,
10844 arguments.first().expect("There is exactly one argument"),
10845 extracted.parameters,
10846 return_type,
10847 end,
10848 );
10849 } else {
10850 self.extract_anonymous_function_body(
10851 location,
10852 arguments,
10853 extracted.parameters,
10854 return_type,
10855 end,
10856 );
10857 }
10858 }
10859 ExtractedValue::Expression(expression) => {
10860 let expression_type = if let TypedExpr::Fn {
10861 type_,
10862 kind: FunctionLiteralKind::Use { .. },
10863 ..
10864 } = expression
10865 {
10866 type_.fn_types().expect("use callback to be a function").1
10867 } else {
10868 expression.type_()
10869 };
10870 self.extract_unbound_statements(
10871 expression.location(),
10872 expression.location(),
10873 extracted.parameters,
10874 expression_type,
10875 end,
10876 );
10877 }
10878 ExtractedValue::Statements {
10879 location,
10880 position: StatementPosition::NotTail,
10881 type_,
10882 } => {
10883 if extracted.returned_variables.is_empty() {
10884 self.extract_unbound_statements(
10885 location,
10886 location,
10887 extracted.parameters,
10888 type_,
10889 end,
10890 );
10891 } else {
10892 self.extract_bound_statements(
10893 location,
10894 extracted.parameters,
10895 extracted.returned_variables,
10896 end,
10897 );
10898 }
10899 }
10900
10901 ExtractedValue::Use {
10902 location,
10903 use_line_location: _,
10904 type_,
10905 }
10906 | ExtractedValue::Statements {
10907 location,
10908 position: StatementPosition::Tail,
10909 type_,
10910 } => self.extract_unbound_statements(
10911 location,
10912 location,
10913 extracted.parameters,
10914 type_,
10915 end,
10916 ),
10917 ExtractedValue::PipelineSteps {
10918 location,
10919 before_first,
10920 return_type,
10921 } => self.extract_pipeline_steps(
10922 location,
10923 end,
10924 extracted.parameters,
10925 before_first,
10926 return_type,
10927 ),
10928 }
10929
10930 let mut action = Vec::with_capacity(1);
10931 CodeActionBuilder::new("Extract function")
10932 .kind(CodeActionKind::RefactorExtract)
10933 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10934 .preferred(false)
10935 .push_to(&mut action);
10936 action
10937 }
10938
10939 /// Choose a suitable name for an extracted function to make sure it doesn't
10940 /// clash with existing functions defined in the module and cause an error.
10941 fn function_name(&self) -> EcoString {
10942 if !self.module.ast.type_info.values.contains_key("function") {
10943 return "function".into();
10944 }
10945
10946 let mut number = 2;
10947 loop {
10948 let name = eco_format!("function_{number}");
10949 if !self.module.ast.type_info.values.contains_key(&name) {
10950 return name;
10951 }
10952 number += 1;
10953 }
10954 }
10955
10956 /// For anonymous functions that do not capture any variables from an outer scope.
10957 /// Moves the function so it is defined at the module top-level instead,
10958 /// replacing the original literal with a reference to the new function.
10959 fn extract_anonymous_function(
10960 &mut self,
10961 location: SrcSpan,
10962 code_location: SrcSpan,
10963 arguments: &[TypedArg],
10964 return_type: Arc<Type>,
10965 function_end: u32,
10966 ) {
10967 // --- BEFORE
10968 // ```gleam
10969 // pub fn main() {
10970 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) })
10971 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
10972 // }
10973 // ```
10974 //
10975 // --- AFTER
10976 // ```gleam
10977 // pub fn main() {
10978 // list.each([1, 2, 3], function)
10979 // }
10980 //
10981 // fn function(x: Int) -> Nil {
10982 // io.println(int.to_string(x))
10983 // }
10984 // ```
10985
10986 let name = self.function_name();
10987 self.edits.replace(location, name.to_string());
10988
10989 let mut printer = Printer::new(&self.module.ast.names);
10990
10991 let return_type = printer.print_type(&return_type);
10992 let function_body = code_at(self.module, code_location);
10993 let mut name_generator = NameGenerator::new();
10994 let arguments = arguments
10995 .iter()
10996 .map(|arg| {
10997 if let Some(name) = arg.get_variable_name() {
10998 eco_format!("{name}: {}", printer.print_type(&arg.type_))
10999 } else {
11000 let name = name_generator.generate_name_from_type(&arg.type_);
11001 eco_format!("_{name}: {}", printer.print_type(&arg.type_))
11002 }
11003 })
11004 .join(", ");
11005
11006 let function = format!(
11007 "\n\nfn {name}({arguments}) -> {return_type} {{
11008 {function_body}
11009}}"
11010 );
11011 self.edits.insert(function_end, function);
11012 }
11013
11014 /// For anonymous functions that capture variables from an external scope
11015 /// but only expect a single argument.
11016 /// Uses function caputre syntax to provide a more concise refactoring than
11017 /// `extract_anonymous_function_body`.
11018 fn extract_anonymous_function_with_capture_hole(
11019 &mut self,
11020 location: SrcSpan,
11021 code_location: SrcSpan,
11022 argument: &TypedArg,
11023 extra_parameters: Vec<(EcoString, Arc<Type>)>,
11024 return_type: Arc<Type>,
11025 function_end: u32,
11026 ) {
11027 let name = self.function_name();
11028
11029 // --- BEFORE
11030 // ```gleam
11031 // pub fn main() {
11032 // let needle = 42
11033 // let haystack = [25, 81, 74, 42, 33]
11034 // list.filter(haystack, fn(x) { x == needle })
11035 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
11036 // }
11037 // ```
11038 //
11039 // --- AFTER
11040 // ```gleam
11041 // pub fn main() {
11042 // let needle = 42
11043 // let haystack = [25, 81, 74, 42, 33]
11044 // list.filter(haystack, function(_, needle))
11045 // }
11046 //
11047 // fn function(x: Int, needle: Int) -> Bool {
11048 // x == needle
11049 // }
11050 // ```
11051
11052 let call = format!(
11053 "{name}(_, {})",
11054 extra_parameters.iter().map(|(name, _)| name).join(", ")
11055 );
11056 self.edits.replace(location, call);
11057
11058 let mut printer = Printer::new(&self.module.ast.names);
11059
11060 // build up the code for the newly generated function
11061 let return_type = printer.print_type(&return_type);
11062 let function_body = code_at(self.module, code_location);
11063 let argument = if let Some(name) = argument.get_variable_name() {
11064 eco_format!("{name}: {}", printer.print_type(&argument.type_))
11065 } else {
11066 let name = NameGenerator::new().generate_name_from_type(&argument.type_);
11067 eco_format!("_{name}: {}", printer.print_type(&argument.type_))
11068 };
11069 let extra_parameters = extra_parameters
11070 .iter()
11071 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11072 .join(", ");
11073
11074 let function = format!(
11075 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{
11076 {function_body}
11077}}"
11078 );
11079 self.edits.insert(function_end, function);
11080 }
11081
11082 /// For non-unary anonymous functions that capture variables from an external scope.
11083 /// Replaces just the _function body_ with a call to the newly generated function.
11084 fn extract_anonymous_function_body(
11085 &mut self,
11086 location: SrcSpan,
11087 arguments: &[TypedArg],
11088 extra_parameters: Vec<(EcoString, Arc<Type>)>,
11089 return_type: Arc<Type>,
11090 function_end: u32,
11091 ) {
11092 let name = self.function_name();
11093 // --- BEFORE
11094 // ```gleam
11095 // pub fn main() {
11096 // let factor = 2
11097 // list.fold([], 0, fn(acc, value) { acc + value * factor })
11098 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
11099 // }
11100 // ```
11101 //
11102 // --- AFTER
11103 // ```gleam
11104 // pub fn main() {
11105 // let factor = 2
11106 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) })
11107 // }
11108 //
11109 // fn function(acc: Int, value: Int, factor: Int) -> Int {
11110 // acc + value * factor
11111 // }
11112 // ```
11113
11114 // if the programmer has ignored an argument, the generated function
11115 // cannot take it as an parameter
11116 let arguments = arguments
11117 .iter()
11118 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_)))
11119 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_)))
11120 .collect::<Vec<(&EcoString, &Arc<Type>)>>();
11121
11122 let call = format!(
11123 "{name}({})",
11124 arguments.iter().map(|(name, _)| name).join(", ")
11125 );
11126 self.edits.replace(location, call);
11127
11128 let mut printer = Printer::new(&self.module.ast.names);
11129
11130 let return_type = printer.print_type(&return_type);
11131 let function_body = code_at(self.module, location);
11132 let arguments = arguments
11133 .iter()
11134 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11135 .join(", ");
11136
11137 let function = format!(
11138 "\n\nfn {name}({arguments}) -> {return_type} {{
11139 {function_body}
11140}}"
11141 );
11142 self.edits.insert(function_end, function);
11143 }
11144
11145 /// Extracts a function whose return value does not get bound to a variable
11146 /// in the calling function.
11147 ///
11148 /// There are two cases:
11149 /// 1. The function is in tail position, and its return value is used as the
11150 /// return value of the calling function.
11151 /// 2. The function's return value is unused in the calling function, and so
11152 /// does not get bound to a variable.
11153 ///
11154 /// # Parameters
11155 ///
11156 /// In most cases, `location` and `code_location` are the same. They differ
11157 /// only when the code being extracted is a single block expression. In
11158 /// that case, the code being replaced in the calling function (`location`)
11159 /// includes the braces around the block, while the code being put into the
11160 /// body of the extracted function (`code_location`) does not.
11161 fn extract_unbound_statements(
11162 &mut self,
11163 location: SrcSpan,
11164 code_location: SrcSpan,
11165 parameters: Vec<(EcoString, Arc<Type>)>,
11166 return_type: Arc<Type>,
11167 function_end: u32,
11168 ) {
11169 let expression_code = code_at(self.module, code_location);
11170
11171 let name = self.function_name();
11172 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
11173 let call = format!("{name}({arguments})");
11174
11175 // Since we are only extracting a single expression, we can just replace
11176 // it with the call and preserve all other semantics; only one value can
11177 // be returned from the expression, unlike when extracting multiple
11178 // statements.
11179 self.edits.replace(location, call);
11180
11181 let mut printer = Printer::new(&self.module.ast.names);
11182
11183 let parameters = parameters
11184 .iter()
11185 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11186 .join(", ");
11187 let return_type = printer.print_type(&return_type);
11188
11189 let function = format!(
11190 "\n\nfn {name}({parameters}) -> {return_type} {{
11191 {expression_code}
11192}}"
11193 );
11194
11195 self.edits.insert(function_end, function);
11196 }
11197
11198 /// Extracts a function that does get bound to one or more variables in the
11199 /// calling function.
11200 ///
11201 /// For example, in the code
11202 ///
11203 /// ```gleam
11204 /// pub fn main() {
11205 /// let a = 10
11206 /// //^ Select from here
11207 /// let b = 20
11208 /// let c = a + b
11209 /// // ^ Until here
11210 ///
11211 /// echo a
11212 /// echo b
11213 /// echo c
11214 /// }
11215 /// ```
11216 ///
11217 /// multiple values defined in the extracted block are used in the calling
11218 /// function. So, the extracted function must return multiple values that
11219 /// are bound to variables: `let #(a, b, c) = function()`.
11220 ///
11221 /// There is a special case when there is only one value being bound, as it
11222 /// is unnecessary to create a single-element tuple. For example:
11223 ///
11224 /// ```gleam
11225 /// pub fn main() {
11226 /// let a = 10
11227 /// //^ Select from here
11228 /// let b = 20
11229 /// let c = a + b
11230 /// // ^ Until here
11231 ///
11232 /// echo c
11233 /// }
11234 /// ```
11235 ///
11236 /// Here, the return value of the extracted function will be bound to a
11237 /// single variable in the calling function: `let c = function()`.
11238 fn extract_bound_statements(
11239 &mut self,
11240 location: SrcSpan,
11241 parameters: Vec<(EcoString, Arc<Type>)>,
11242 returned_variables: Vec<(EcoString, Arc<Type>)>,
11243 function_end: u32,
11244 ) {
11245 let code = code_at(self.module, location);
11246
11247 let (return_type, return_value) = match returned_variables.as_slice() {
11248 [(returned_name, type_)] => (type_.clone(), returned_name.clone()),
11249 _ => {
11250 let returned_names = returned_variables
11251 .iter()
11252 .map(|(name, _type)| name)
11253 .join(", ");
11254 let return_value = eco_format!("#({returned_names})");
11255
11256 let returned_types = returned_variables
11257 .into_iter()
11258 .map(|(_name, type_)| type_)
11259 .collect();
11260 let type_ = type_::tuple(returned_types);
11261
11262 (type_, return_value)
11263 }
11264 };
11265
11266 let name = self.function_name();
11267 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
11268 let call = format!("let {return_value} = {name}({arguments})");
11269
11270 self.edits.replace(location, call);
11271
11272 let mut printer = Printer::new(&self.module.ast.names);
11273
11274 let parameters = parameters
11275 .iter()
11276 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11277 .join(", ");
11278 let return_type = printer.print_type(&return_type);
11279
11280 let function = format!(
11281 "\n\nfn {name}({parameters}) -> {return_type} {{
11282 {code}
11283 {return_value}
11284}}"
11285 );
11286
11287 self.edits.insert(function_end, function);
11288 }
11289
11290 fn extract_pipeline_steps(
11291 &mut self,
11292 location: SrcSpan,
11293 function_end: u32,
11294 parameters: Vec<(EcoString, Arc<Type>)>,
11295 before_first: Option<Arc<Type>>,
11296 return_type: Arc<Type>,
11297 ) {
11298 let name = self.function_name();
11299 let code = code_at(self.module, location);
11300 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", ");
11301 let replacement = match before_first {
11302 Some(_) if parameters.is_empty() => format!("{name}"),
11303 Some(_) | None => format!("{name}({arguments})"),
11304 };
11305 self.edits.replace(location, replacement);
11306
11307 // When extracting something out of the middle of a pipeline the
11308 // function we produce will produce a single value as output but could
11309 // take multiple values as input:
11310 //
11311 // ```gleam
11312 // wibble
11313 // |> wobble(a) // extracting this
11314 // |> woo(b) //
11315 // |> something
11316 // ```
11317 //
11318 // It will take the type returned by `wibble`, `a`, and `b` as input,
11319 // and produce the value returned by `woo` as output:
11320 //
11321 // ```gleam
11322 // wibble
11323 // |> function(a, b)
11324 // |> something
11325 // ```
11326 //
11327 // If the steps extracted are at the beginning of the pipeline, then it
11328 // won't take that additional argument!
11329 //
11330 // ```gleam
11331 // wibble // extracting these
11332 // |> wobble(a) //
11333 // |> woo(b) //
11334 // |> something
11335 // ```
11336 //
11337 // Becomes:
11338 //
11339 // ```gleam
11340 // function(a, b)
11341 // |> something
11342 // ```
11343
11344 let mut type_printer = Printer::new(&self.module.ast.names);
11345 let return_type = type_printer.print_type(&return_type);
11346 let first_argument = before_first.map(|type_of_first_argument| {
11347 let mut generator = NameGenerator::new();
11348 for (name, _) in parameters.iter() {
11349 generator.add_used_name(name.clone());
11350 }
11351 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument);
11352 (first_argument_name, type_of_first_argument.clone())
11353 });
11354 let parameters = first_argument
11355 .clone()
11356 .into_iter()
11357 .chain(parameters)
11358 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_)))
11359 .join(", ");
11360
11361 let code = if let Some((first_argument_name, _)) = first_argument {
11362 format!("{first_argument_name}\n |> {code}")
11363 } else {
11364 code.trim_start_matches("|>").to_string()
11365 };
11366 let function = format!(
11367 "\n\nfn {name}({parameters}) -> {return_type} {{
11368 {code}
11369}}"
11370 );
11371
11372 self.edits.insert(function_end, function);
11373 }
11374
11375 /// When a variable is referenced, we need to decide if we need to do anything
11376 /// to ensure that the reference is still valid after extracting a function.
11377 /// If the variable is defined outside the extracted function, but used inside
11378 /// it, then we need to add it as a parameter of the function. Similarly, if
11379 /// a variable is defined inside the extracted code, but used outside of it,
11380 /// we need to ensure that value is returned from the function so that it is
11381 /// accessible.
11382 fn register_referenced_variable(
11383 &mut self,
11384 name: &EcoString,
11385 type_: &Arc<Type>,
11386 location: SrcSpan,
11387 definition_location: SrcSpan,
11388 ) {
11389 let Some(extracted) = &mut self.function else {
11390 return;
11391 };
11392
11393 let extracted_location = extracted.location();
11394
11395 // If a variable defined outside the extracted code is referenced inside
11396 // it, we need to add it to the list of parameters.
11397 let variables = if extracted_location.contains_span(location)
11398 && !extracted_location.contains_span(definition_location)
11399 {
11400 &mut extracted.parameters
11401 // If a variable defined inside the extracted code is referenced outside
11402 // it, then we need to ensure that it is returned from the function.
11403 } else if extracted_location.contains_span(definition_location)
11404 && !extracted_location.contains_span(location)
11405 {
11406 &mut extracted.returned_variables
11407 } else {
11408 return;
11409 };
11410
11411 // If the variable has already been tracked, no need to register it again.
11412 // We use a `Vec` here rather than a `HashMap` because we want to ensure
11413 // the order of arguments is consistent; in this case it will be determined
11414 // by the order the variables are used. This isn't always desired, but it's
11415 // better than random order, and makes it easier to write tests too.
11416 // The cost of iterating the list here is minimal; it is unlikely that
11417 // a given function will ever have more than 10 or so parameters.
11418 if variables.iter().any(|(variable, _)| variable == name) {
11419 return;
11420 }
11421
11422 variables.push((name.clone(), type_.clone()));
11423 }
11424
11425 fn can_extract_expression(&self, expression: &TypedExpr) -> bool {
11426 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
11427 let selected_range = self.params.range;
11428
11429 // If the selected range doesn't touch the expression at all, then there
11430 // is no reason to extract it.
11431 if !overlaps(expression_range, selected_range) {
11432 return false;
11433 }
11434
11435 match expression {
11436 TypedExpr::Pipeline {
11437 first_value,
11438 finally,
11439 ..
11440 } => {
11441 // We can extract a pipeline as a whole only if the selection
11442 // spans all of its steps!
11443 let first_step = self.edits.src_span_to_lsp_range(first_value.location);
11444 let last_step = self.edits.src_span_to_lsp_range(finally.location());
11445 position_within(selected_range.start, first_step)
11446 && position_within(selected_range.end, last_step)
11447 }
11448
11449 TypedExpr::Int { .. }
11450 | TypedExpr::Float { .. }
11451 | TypedExpr::String { .. }
11452 | TypedExpr::Block { .. }
11453 | TypedExpr::Var { .. }
11454 | TypedExpr::Fn { .. }
11455 | TypedExpr::List { .. }
11456 | TypedExpr::Call { .. }
11457 | TypedExpr::BinOp { .. }
11458 | TypedExpr::Case { .. }
11459 | TypedExpr::RecordAccess { .. }
11460 | TypedExpr::PositionalAccess { .. }
11461 | TypedExpr::ModuleSelect { .. }
11462 | TypedExpr::Tuple { .. }
11463 | TypedExpr::TupleIndex { .. }
11464 | TypedExpr::Todo { .. }
11465 | TypedExpr::Panic { .. }
11466 | TypedExpr::Echo { .. }
11467 | TypedExpr::BitArray { .. }
11468 | TypedExpr::RecordUpdate { .. }
11469 | TypedExpr::NegateBool { .. }
11470 | TypedExpr::NegateInt { .. }
11471 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range),
11472 }
11473 }
11474
11475 fn can_extract_statement(&self, statement: &TypedStatement) -> bool {
11476 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
11477 let selected_range = self.params.range;
11478
11479 // If the selected range doesn't touch the statement at all, then there
11480 // is no reason to extract it.
11481 if !overlaps(statement_range, selected_range) {
11482 return false;
11483 }
11484
11485 match statement {
11486 ast::Statement::Expression(expression) => self.can_extract_expression(expression),
11487
11488 // Determine whether the selected range falls completely within the
11489 // expression. For example:
11490 // ```gleam
11491 // pub fn main() {
11492 // let something = {
11493 // let a = 1
11494 // let b = 2
11495 // let c = a + b
11496 // //^ The user has selected from here
11497 // let d = a * b
11498 // c / d
11499 // // ^ Until here
11500 // }
11501 // }
11502 // ```
11503 //
11504 // Here, the selected range does overlap with the `let something`
11505 // statement; but we don't want to extract that whole statement! The
11506 // user only wanted to extract the statements inside the block. So if
11507 // the selected range falls completely within the expression, we ignore
11508 // it and traverse the tree further until we find exactly what the user
11509 // selected.
11510 //
11511 // If the selected range is completely within the expression, we don't
11512 // want to extract it.
11513 ast::Statement::Use(_) | ast::Statement::Assert(_) => {
11514 !completely_within(selected_range, statement_range)
11515 }
11516
11517 // We can only extract a whole let statement if the assignment
11518 // part itself is selected. If the only part being selected is the
11519 // expression then the right call is not extracting the whole
11520 // statement but just the expression.
11521 ast::Statement::Assignment(assignment) => {
11522 let value_range = self
11523 .edits
11524 .src_span_to_lsp_range(assignment.value.location());
11525
11526 !within(selected_range, value_range)
11527 && !completely_within(selected_range, statement_range)
11528 }
11529 }
11530 }
11531}
11532
11533impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
11534 fn visit_typed_function(&mut self, function: &'ast TypedFunction) {
11535 let range = self.edits.src_span_to_lsp_range(function.full_location());
11536
11537 if within(self.params.range, range) {
11538 self.function_end_position = Some(function.end_position);
11539 self.last_statement_location = function.body.last().map(|last| last.location());
11540
11541 ast::visit::visit_typed_function(self, function);
11542 }
11543 }
11544
11545 fn visit_typed_expr_block(
11546 &mut self,
11547 location: &'ast SrcSpan,
11548 statements: &'ast [TypedStatement],
11549 ) {
11550 let last_statement_location = self.last_statement_location;
11551 self.last_statement_location = statements.last().map(|last| last.location());
11552
11553 ast::visit::visit_typed_expr_block(self, location, statements);
11554
11555 self.last_statement_location = last_statement_location;
11556 }
11557
11558 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
11559 let last_statement_location = self.last_statement_location;
11560 // The body must be visited first, before the desugared function
11561 if let TypedExpr::Call { arguments, .. } = &*use_.call
11562 && let Some(CallArg {
11563 value: TypedExpr::Fn { body, .. },
11564 ..
11565 }) = arguments.last()
11566 {
11567 self.last_statement_location = Some(body.last().location());
11568 for statement in body {
11569 self.visit_typed_statement(statement);
11570 }
11571 }
11572 ast::visit::visit_typed_use(self, use_);
11573 self.last_statement_location = last_statement_location;
11574 }
11575
11576 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
11577 // If we have already determined what code we want to extract, we don't
11578 // want to extract this instead. This expression would be inside the
11579 // piece of code we already are going to extract, leading to us
11580 // extracting just a single literal in any selection, which is of course
11581 // not desired.
11582 if self.function.is_none() {
11583 // If this expression is fully selected, we mark it as being extracted.
11584 if self.can_extract_expression(expression) {
11585 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
11586 expression,
11587 )));
11588 }
11589 }
11590
11591 // If the expression is a function, then the last statement location
11592 // needs to be updated accordingly
11593 let last_statement_location = self.last_statement_location;
11594 if let TypedExpr::Fn { body, .. } = expression {
11595 self.last_statement_location = Some(body.last().location());
11596 }
11597
11598 ast::visit::visit_typed_expr(self, expression);
11599
11600 self.last_statement_location = last_statement_location;
11601 }
11602
11603 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
11604 let statement_location = statement.location();
11605
11606 if self.can_extract_statement(statement) {
11607 let is_in_tail_position =
11608 self.last_statement_location
11609 .is_some_and(|last_statement_location| {
11610 last_statement_location == statement_location
11611 });
11612
11613 // A use is always eating up the entire block, if we're extracting it,
11614 // it will be in tail position there and the extracted function should
11615 // return its returned value.
11616 let position = if statement.is_use() || is_in_tail_position {
11617 StatementPosition::Tail
11618 } else {
11619 StatementPosition::NotTail
11620 };
11621
11622 match &mut self.function {
11623 None => {
11624 self.function = match statement {
11625 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None,
11626 TypedStatement::Assert(_)
11627 | TypedStatement::Assignment(_)
11628 | TypedStatement::Expression(_) => {
11629 Some(ExtractedFunction::new(ExtractedValue::Statements {
11630 location: statement_location,
11631 position,
11632 type_: statement.type_(),
11633 }))
11634 }
11635 TypedStatement::Use(use_) => {
11636 Some(ExtractedFunction::new(ExtractedValue::Use {
11637 location: use_.call.location(),
11638 use_line_location: use_.location,
11639 type_: statement.type_(),
11640 }))
11641 }
11642 };
11643 }
11644
11645 // If we're extracting something that is within a use expression
11646 // we need to check whether to extract the whole use or just
11647 // statements inside of it
11648 Some(ExtractedFunction {
11649 value:
11650 ExtractedValue::Use {
11651 location,
11652 use_line_location,
11653 ..
11654 },
11655 parameters,
11656 returned_variables,
11657 }) if location.contains_span(statement_location) => {
11658 let use_line_range = self.edits.src_span_to_lsp_range(*use_line_location);
11659
11660 // If the current statement is not a part of the use line,
11661 // and the current selection does not overlap the use line,
11662 // then the outer use is not the correct target.
11663 if !use_line_location.contains_span(statement_location)
11664 && !overlaps(use_line_range, self.params.range)
11665 {
11666 self.function = Some(ExtractedFunction {
11667 value: ExtractedValue::Statements {
11668 location: statement_location,
11669 position,
11670 type_: statement.type_(),
11671 },
11672 parameters: parameters.to_vec(),
11673 returned_variables: returned_variables.to_vec(),
11674 });
11675 }
11676 }
11677 // Otherwise it means we're extracting multiple statements
11678 // _including_ some use expression, we fallback to extracting
11679 // multiple statements
11680 Some(ExtractedFunction {
11681 value: value @ ExtractedValue::Use { .. },
11682 ..
11683 }) => {
11684 *value = ExtractedValue::Statements {
11685 location: value.location().merge(&statement_location),
11686 position,
11687 type_: statement.type_(),
11688 };
11689 }
11690
11691 // If we have already chosen an expression to extract, that means
11692 // that this statement is within the already extracted expression,
11693 // so we don't want to extract this instead.
11694 Some(ExtractedFunction {
11695 value: ExtractedValue::Expression(_),
11696 ..
11697 }) => {}
11698 // If we are selecting multiple statements, this statement should
11699 // be included within that list, so we merge the spans to ensure
11700 // it is included.
11701 Some(ExtractedFunction {
11702 value:
11703 ExtractedValue::Statements {
11704 location,
11705 position: extracted_position,
11706 type_: extracted_type,
11707 },
11708 ..
11709 }) => {
11710 *location = location.merge(&statement_location);
11711 *extracted_position = position;
11712 *extracted_type = statement.type_();
11713 }
11714 Some(ExtractedFunction {
11715 value: value @ ExtractedValue::PipelineSteps { .. },
11716 ..
11717 }) => {
11718 // If we were extracting a pipeline, but end up selecting
11719 // some statement that is not part of it, then we go back to
11720 // selecting a batch of statements.
11721 *value = ExtractedValue::Statements {
11722 location: value.location(),
11723 position,
11724 type_: statement.type_(),
11725 }
11726 }
11727 }
11728 }
11729 ast::visit::visit_typed_statement(self, statement);
11730 }
11731
11732 fn visit_typed_expr_pipeline(
11733 &mut self,
11734 _location: &'ast SrcSpan,
11735 first_value: &'ast TypedPipelineAssignment,
11736 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
11737 finally: &'ast TypedExpr,
11738 _finally_kind: &'ast PipelineAssignmentKind,
11739 ) {
11740 self.previous_pipeline_assignment_type = None;
11741 self.visit_typed_pipeline_assignment(first_value);
11742
11743 self.previous_pipeline_assignment_type = Some(first_value.type_());
11744 for (assignment, _kind) in assignments {
11745 self.visit_typed_pipeline_assignment(assignment);
11746 self.previous_pipeline_assignment_type = Some(assignment.type_());
11747 }
11748
11749 // If we're selecting a pipeline and the selection ends on its final step
11750 // we want to include that as well into the extracted bit.
11751 let final_step_range = self.edits.src_span_to_lsp_range(finally.location());
11752 if let Some(extracted_function) = &mut self.function
11753 && position_within(self.params.range.end, final_step_range)
11754 {
11755 extracted_function.try_add_pipeline_step(finally.type_(), finally.location());
11756 }
11757
11758 self.visit_typed_expr(finally);
11759 self.previous_pipeline_assignment_type = None;
11760 }
11761
11762 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
11763 // In order to be extracted, a pipeline step must be overlapping with
11764 // the cursor selection!
11765 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
11766 if !overlaps(self.params.range, assignment_range) {
11767 return;
11768 }
11769
11770 match &mut self.function {
11771 None => {
11772 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps {
11773 location: assignment.location,
11774 before_first: self.previous_pipeline_assignment_type.clone(),
11775 return_type: assignment.type_(),
11776 }));
11777 }
11778 Some(extracted_function) => {
11779 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location);
11780 }
11781 }
11782 ast::visit::visit_typed_pipeline_assignment(self, assignment);
11783 }
11784
11785 fn visit_typed_expr_var(
11786 &mut self,
11787 location: &'ast SrcSpan,
11788 constructor: &'ast ValueConstructor,
11789 name: &'ast EcoString,
11790 ) {
11791 if let type_::ValueConstructorVariant::LocalVariable {
11792 location: definition_location,
11793 ..
11794 } = &constructor.variant
11795 {
11796 self.register_referenced_variable(
11797 name,
11798 &constructor.type_,
11799 *location,
11800 *definition_location,
11801 );
11802 }
11803 }
11804
11805 fn visit_typed_clause_guard_var(
11806 &mut self,
11807 location: &'ast SrcSpan,
11808 name: &'ast EcoString,
11809 type_: &'ast Arc<Type>,
11810 definition_location: &'ast SrcSpan,
11811 _origin: &'ast VariableOrigin,
11812 ) {
11813 self.register_referenced_variable(name, type_, *location, *definition_location);
11814 }
11815
11816 fn visit_typed_expr_case(
11817 &mut self,
11818 location: &'ast SrcSpan,
11819 type_: &'ast Arc<Type>,
11820 subjects: &'ast [TypedExpr],
11821 clauses: &'ast [ast::TypedClause],
11822 compiled_case: &'ast CompiledCase,
11823 ) {
11824 let was_extracting_already = self.function.is_some();
11825
11826 // We first visit as usual...
11827 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
11828
11829 // But then we need to check we're in a situation where it actually makes
11830 // sense to extract: if the cursor is entirely within the case (so it's
11831 // not part of a bigger extracted chunk) and it spans over one of the
11832 // branches' pattern or guard, then we don't want to allow extracting
11833 // anything. Popping up the action would be confusing.
11834 // For example:
11835 //
11836 // ```gleam
11837 // case wibble {
11838 // Ok(_) -> todo
11839 // //^^^^^^^^^^^^^ If I'm selecting this whole branch it makes no sense
11840 // to propose extracting it as a function
11841 // _ if wibble -> todo
11842 // // ^^^^^^^^^^^ If I'm selecting this it makes no sense to
11843 // // propose extracting it as a function
11844 // }
11845 // ```
11846
11847 // We were already extracting something, we don't want to render that
11848 // choice null, this is just a part of a bigger piece being extracted.
11849 if was_extracting_already {
11850 return;
11851 }
11852
11853 for clause in clauses {
11854 let left_hand_side_location = SrcSpan {
11855 start: clause.pattern_location().start,
11856 end: clause.then.location().start - 1,
11857 };
11858 let left_hand_side_range = self.edits.src_span_to_lsp_range(left_hand_side_location);
11859 // If there's any overlapping with one of the patterns, then we
11860 // don't want to extract anything.
11861 if overlaps(self.params.range, left_hand_side_range) {
11862 self.function = None;
11863 break;
11864 }
11865 }
11866 }
11867
11868 fn visit_typed_bit_array_size_variable(
11869 &mut self,
11870 location: &'ast SrcSpan,
11871 name: &'ast EcoString,
11872 constructor: &'ast Option<Box<ValueConstructor>>,
11873 type_: &'ast Arc<Type>,
11874 ) {
11875 let variant = match constructor {
11876 Some(constructor) => &constructor.variant,
11877 None => return,
11878 };
11879 if let type_::ValueConstructorVariant::LocalVariable {
11880 location: definition_location,
11881 ..
11882 } = variant
11883 {
11884 self.register_referenced_variable(name, type_, *location, *definition_location);
11885 }
11886 }
11887}
11888
11889/// Code action to merge two identical branches together.
11890///
11891pub struct MergeCaseBranches<'a> {
11892 module: &'a Module,
11893 params: &'a CodeActionParams,
11894 edits: TextEdits<'a>,
11895 /// These are the positions of the patterns of all the consecutive branches
11896 /// we've determined can be merged, for example if we're mergin the first
11897 /// two branches here:
11898 ///
11899 /// ```gleam
11900 /// case wibble {
11901 /// 1 -> todo
11902 /// // ^ this location here
11903 /// 20 -> todo
11904 /// // ^^ and this location here
11905 /// _ -> todo
11906 /// }
11907 /// ```
11908 ///
11909 /// We need those to delete all the space between each consecutive pattern,
11910 /// replacing it with the `|` for alternatives
11911 ///
11912 patterns_to_merge: Option<MergeableBranches>,
11913}
11914
11915struct MergeableBranches {
11916 /// The span of the body to keep when merging multiple branches. For
11917 /// example:
11918 ///
11919 /// ```gleam
11920 /// case n {
11921 /// // Imagine we're merging the first three branches together...
11922 /// 1 -> todo
11923 /// 2 -> n * 2
11924 /// // ^^^^^ This would be the location of the one body to keep
11925 /// 3 -> todo
11926 /// _ -> todo
11927 /// }
11928 /// ```
11929 ///
11930 body_to_keep: SrcSpan,
11931
11932 /// The location body of the last of the branches that are going to be
11933 /// merged; that is where we're going to place the code of the body to keep
11934 /// once the action is done. For example:
11935 ///
11936 /// ```gleam
11937 /// case n {
11938 /// // Imagine we're merging the first three branches together...
11939 /// 1 -> todo
11940 /// 2 -> n * 2
11941 /// 3 -> todo
11942 /// // ^^^^ This would be the location of the final body
11943 /// _ -> todo
11944 /// }
11945 /// ```
11946 ///
11947 final_body: SrcSpan,
11948
11949 /// The span of the patterns whose branches are going to be merged. For
11950 /// example:
11951 ///
11952 /// ```gleam
11953 /// case n {
11954 /// // Imagine we're merging the first three branches together...
11955 /// 1 -> todo
11956 /// // ^
11957 /// 2 -> n * 2
11958 /// // ^
11959 /// 3 -> todo
11960 /// // ^ These would be the locations of the patterns
11961 /// _ -> todo
11962 /// }
11963 /// ```
11964 ///
11965 patterns_to_merge: Vec<SrcSpan>,
11966}
11967
11968impl<'a> MergeCaseBranches<'a> {
11969 pub fn new(
11970 module: &'a Module,
11971 line_numbers: &'a LineNumbers,
11972 params: &'a CodeActionParams,
11973 ) -> Self {
11974 Self {
11975 module,
11976 params,
11977 edits: TextEdits::new(line_numbers),
11978 patterns_to_merge: None,
11979 }
11980 }
11981
11982 pub fn code_actions(mut self) -> Vec<CodeAction> {
11983 self.visit_typed_module(&self.module.ast);
11984
11985 let Some(mergeable_branches) = self.patterns_to_merge else {
11986 return vec![];
11987 };
11988
11989 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() {
11990 self.edits
11991 .replace(SrcSpan::new(one.end, next.start), " | ".into());
11992 }
11993
11994 self.edits.replace(
11995 mergeable_branches.final_body,
11996 code_at(self.module, mergeable_branches.body_to_keep).into(),
11997 );
11998
11999 let mut action = Vec::with_capacity(1);
12000 CodeActionBuilder::new("Merge case branches")
12001 .kind(CodeActionKind::RefactorRewrite)
12002 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12003 .preferred(false)
12004 .push_to(&mut action);
12005 action
12006 }
12007
12008 fn select_mergeable_branches(
12009 &self,
12010 clauses: &'a [ast::TypedClause],
12011 ) -> Option<MergeableBranches> {
12012 let mut clauses = clauses
12013 .iter()
12014 // We want to skip all the branches at the beginning of the case
12015 // expression that the cursor is not hovering over. For example:
12016 //
12017 // ```gleam
12018 // case wibble {
12019 // a -> 1 <- we want to skip this one here that is not selected
12020 // b -> 2
12021 // ^^^^ this is the selection
12022 // _ -> 3
12023 // ^^
12024 // }
12025 // ```
12026 .skip_while(|clause| {
12027 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
12028 !overlaps(self.params.range, clause_range)
12029 })
12030 // Then we only want to take the clauses that we're hovering over
12031 // with our selection (even partially!)
12032 // In the provious example they would be `b -> 2` and `_ -> 3`.
12033 .take_while(|clause| {
12034 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
12035 overlaps(self.params.range, clause_range)
12036 });
12037
12038 let first_hovered_clause = clauses.next()?;
12039
12040 // This is the clause we're comparing all the others with. We need to
12041 // make sure that all the clauses we're going to join can be merged with
12042 // this one.
12043 let mut reference_clause = first_hovered_clause;
12044 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()];
12045 let mut final_body = first_hovered_clause.then.location();
12046
12047 for clause in clauses {
12048 // As soon as we find a clause that can't be merged with the current
12049 // reference we know we're done looking for consecutive clauses to
12050 // merge.
12051 if !clauses_can_be_merged(reference_clause, clause) {
12052 break;
12053 }
12054
12055 clause_patterns_to_merge.push(clause.pattern_location());
12056 final_body = clause.then.location();
12057
12058 // If the current reference is a `todo` expression, we want to use
12059 // the newly found mergeable clause as the next reference. The
12060 // reference clause is the one whose body will be kept around, so if
12061 // we can we avoid keeping `todo`s
12062 if reference_clause.then.is_todo_with_no_message() {
12063 reference_clause = clause;
12064 }
12065 }
12066
12067 // We only offer the code action if we have found two or more clauses
12068 // to merge.
12069 if clause_patterns_to_merge.len() >= 2 {
12070 Some(MergeableBranches {
12071 final_body,
12072 body_to_keep: reference_clause.then.location(),
12073 patterns_to_merge: clause_patterns_to_merge,
12074 })
12075 } else {
12076 None
12077 }
12078 }
12079}
12080
12081fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool {
12082 // Two clauses cannot be merged if any of those has an if guard
12083 if one.guard.is_some() || other.guard.is_some() {
12084 return false;
12085 }
12086
12087 // Two clauses can only be merged if they define the same variables,
12088 // otherwise joining them would result in invalid code.
12089 let variables_one = one
12090 .bound_variables()
12091 .map(|variable| (variable.name(), variable.type_))
12092 .collect::<HashMap<_, _>>();
12093
12094 let variables_other = other
12095 .bound_variables()
12096 .map(|variable| (variable.name(), variable.type_))
12097 .collect::<HashMap<_, _>>();
12098
12099 for (name, type_) in variables_one.iter() {
12100 if let Some(type_other) = variables_other.get(name)
12101 && type_other.same_as(type_)
12102 {
12103 continue;
12104 }
12105
12106 // There's a variable that is not defined in the second branch but
12107 // is defined in the first one, or it's defined in the second branch
12108 // but it has an incompatible type.
12109 return false;
12110 }
12111
12112 for (name, _) in variables_other.iter() {
12113 if !variables_one.contains_key(name) {
12114 // There's some variables defined in the second branch that are not
12115 // defined in the first one, so they can't be merged!
12116 return false;
12117 }
12118 }
12119
12120 // Anything can be merged with a simple todo, or the two bodies must be
12121 // syntactically equal.
12122 one.then.is_todo_with_no_message()
12123 || other.then.is_todo_with_no_message()
12124 || one.then.syntactically_eq(&other.then)
12125}
12126
12127impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> {
12128 fn visit_typed_expr_case(
12129 &mut self,
12130 location: &'ast SrcSpan,
12131 type_: &'ast Arc<Type>,
12132 subjects: &'ast [TypedExpr],
12133 clauses: &'ast [ast::TypedClause],
12134 compiled_case: &'ast CompiledCase,
12135 ) {
12136 // We only trigger the code action if we are within a case expression,
12137 // otherwise there's no point in exploring the expression any further.
12138 let case_range = self.edits.src_span_to_lsp_range(*location);
12139 if !within(self.params.range, case_range) {
12140 return;
12141 }
12142
12143 if let result @ Some(_) = self.select_mergeable_branches(clauses) {
12144 self.patterns_to_merge = result;
12145 }
12146
12147 // We still need to visit the case expression in case we want to apply
12148 // the code action to some case expression that is nested in one of its
12149 // branches!
12150 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
12151 }
12152}
12153
12154/// Code action to add a missing type parameter to custom types.
12155/// If a custom type is missing a type parameter, as it is the case
12156/// in the following example, this action will offer to add the
12157/// type parameter to the type definition.
12158///
12159/// Before:
12160/// ```gleam
12161/// type Wibble {
12162/// Wibble(field: t)
12163/// }
12164/// ```
12165///
12166/// After:
12167/// ```gleam
12168/// type Wibble(t) {
12169/// Wibble(field: t)
12170/// }
12171/// ```
12172///
12173pub struct AddMissingTypeParameter<'a> {
12174 module: &'a Module,
12175 params: &'a CodeActionParams,
12176 edits: TextEdits<'a>,
12177 /// The source location where the parameters should be defined.
12178 /// This might be a zero-length span if there are no parameters yet,
12179 /// or it might cover the already existing type parameter definitions.
12180 parameters_location: Option<SrcSpan>,
12181 /// If the type definition already had existing parameters before.
12182 has_existing_parameters: bool,
12183 /// The set of all type parameter names in the different variants of the type
12184 /// that are not already part of the type parameter definition on the type.
12185 missing_parameters: HashSet<EcoString>,
12186}
12187
12188impl<'a> AddMissingTypeParameter<'a> {
12189 pub fn new(
12190 module: &'a Module,
12191 line_numbers: &'a LineNumbers,
12192 params: &'a CodeActionParams,
12193 ) -> Self {
12194 Self {
12195 module,
12196 params,
12197 edits: TextEdits::new(line_numbers),
12198 parameters_location: None,
12199 has_existing_parameters: false,
12200 missing_parameters: HashSet::new(),
12201 }
12202 }
12203
12204 pub fn code_actions(mut self) -> Vec<CodeAction> {
12205 self.visit_typed_module(&self.module.ast);
12206
12207 let Some(type_parameters_location) = self.parameters_location else {
12208 return vec![];
12209 };
12210
12211 if self.missing_parameters.is_empty() {
12212 return vec![];
12213 }
12214
12215 let mut new_parameters = self.missing_parameters.iter().sorted().join(", ");
12216 if self.has_existing_parameters {
12217 let has_trailing_comma = self
12218 .module
12219 .extra
12220 .trailing_commas
12221 .iter()
12222 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma));
12223
12224 if !has_trailing_comma {
12225 new_parameters.insert_str(0, ", ");
12226 }
12227
12228 self.edits
12229 .insert(type_parameters_location.end - 1, new_parameters);
12230 } else {
12231 self.edits
12232 .insert(type_parameters_location.end, format!("({new_parameters})"));
12233 }
12234
12235 let mut action = Vec::with_capacity(1);
12236 CodeActionBuilder::new("Add missing type parameter")
12237 .kind(CodeActionKind::QuickFix)
12238 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12239 .preferred(true)
12240 .push_to(&mut action);
12241 action
12242 }
12243}
12244
12245impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> {
12246 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
12247 let custom_type_range = self
12248 .edits
12249 .src_span_to_lsp_range(custom_type.full_location());
12250
12251 // Only continue, if the action was selected anywhere within the custom type definition.
12252 if !within(self.params.range, custom_type_range) {
12253 return;
12254 }
12255
12256 self.parameters_location = Some(SrcSpan::new(
12257 custom_type.name_location.end,
12258 custom_type.location.end,
12259 ));
12260
12261 self.has_existing_parameters = !custom_type.typed_parameters.is_empty();
12262
12263 let existing_names: HashSet<_> = custom_type
12264 .parameters
12265 .iter()
12266 .map(|(_, name)| name)
12267 .collect();
12268
12269 // Collect the remaining type parameters from the variant constructors.
12270 for record in &custom_type.constructors {
12271 for argument in &record.arguments {
12272 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast
12273 && !existing_names.contains(name)
12274 {
12275 let _ = self.missing_parameters.insert(name.clone());
12276 }
12277 }
12278 }
12279 }
12280}
12281
12282/// Code action to replace a `_` with its actual type in an annotation.
12283///
12284/// Before:
12285/// ```gleam
12286/// fn wibble() -> Ok(_) { Ok(1) }
12287/// // ^ Trigger it here
12288/// ```
12289///
12290/// After:
12291/// ```gleam
12292/// fn wibble() -> Ok(Int) { Ok(1) }
12293/// ```
12294///
12295pub struct ReplaceUnderscoreWithType<'a> {
12296 module: &'a Module,
12297 params: &'a CodeActionParams,
12298 edits: TextEdits<'a>,
12299 hovered_hole: Option<HoveredHole>,
12300}
12301
12302struct HoveredHole {
12303 type_: Arc<Type>,
12304 location: SrcSpan,
12305}
12306
12307impl<'a> ReplaceUnderscoreWithType<'a> {
12308 pub fn new(
12309 module: &'a Module,
12310 line_numbers: &'a LineNumbers,
12311 params: &'a CodeActionParams,
12312 ) -> Self {
12313 Self {
12314 module,
12315 params,
12316 edits: TextEdits::new(line_numbers),
12317 hovered_hole: None,
12318 }
12319 }
12320
12321 pub fn code_actions(mut self) -> Vec<CodeAction> {
12322 self.visit_typed_module(&self.module.ast);
12323
12324 let mut action = Vec::with_capacity(1);
12325
12326 let Some(HoveredHole { type_, location }) = self.hovered_hole else {
12327 return vec![];
12328 };
12329 let mut printer = Printer::new(&self.module.ast.names);
12330 self.edits
12331 .replace(location, format!("{}", printer.print_type(&type_)));
12332
12333 CodeActionBuilder::new("Replace `_` with type")
12334 .kind(CodeActionKind::QuickFix)
12335 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12336 .preferred(true)
12337 .push_to(&mut action);
12338 action
12339 }
12340}
12341
12342impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> {
12343 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) {
12344 // We never traverse a type annotation we're not hovering
12345 let node_location = self.edits.src_span_to_lsp_range(node.location());
12346 if !within(self.params.range, node_location) {
12347 return;
12348 }
12349 ast::visit::visit_type_ast(self, node, inferred_type);
12350 }
12351
12352 fn visit_type_ast_hole(
12353 &mut self,
12354 location: &'ast SrcSpan,
12355 _name: &'ast EcoString,
12356 type_: Option<Arc<Type>>,
12357 ) {
12358 if let Some(type_) = type_ {
12359 self.hovered_hole = Some(HoveredHole {
12360 type_,
12361 location: *location,
12362 });
12363 }
12364 }
12365}
12366
12367/// Code action to turn a function used as a reference into a one-statement anonymous function.
12368///
12369/// For example, if the code action was used on `op` here:
12370///
12371/// ```gleam
12372/// list.map([1, 2, 3], op)
12373/// ```
12374///
12375/// it would become:
12376///
12377/// ```gleam
12378/// list.map([1, 2, 3], fn(int) {
12379/// op(int)
12380/// })
12381/// ```
12382pub struct WrapInAnonymousFunction<'a> {
12383 module: &'a Module,
12384 params: &'a CodeActionParams,
12385 functions: Vec<FunctionToWrap>,
12386 edits: TextEdits<'a>,
12387}
12388
12389/// Helper struct, a target for [WrapInAnonymousFunction].
12390struct FunctionToWrap {
12391 location: SrcSpan,
12392 arguments: Vec<Arc<Type>>,
12393 variables_names: VariablesNames,
12394}
12395
12396impl<'a> WrapInAnonymousFunction<'a> {
12397 pub fn new(
12398 module: &'a Module,
12399 line_numbers: &'a LineNumbers,
12400 params: &'a CodeActionParams,
12401 ) -> Self {
12402 Self {
12403 module,
12404 params,
12405 edits: TextEdits::new(line_numbers),
12406 functions: vec![],
12407 }
12408 }
12409
12410 pub fn code_actions(mut self) -> Vec<CodeAction> {
12411 self.visit_typed_module(&self.module.ast);
12412
12413 let mut actions = Vec::with_capacity(self.functions.len());
12414 for target in self.functions {
12415 let mut name_generator = NameGenerator::new();
12416 name_generator.reserve_variable_names(target.variables_names);
12417 let arguments = target
12418 .arguments
12419 .iter()
12420 .map(|type_| name_generator.generate_name_from_type(type_))
12421 .join(", ");
12422
12423 self.edits
12424 .insert(target.location.start, format!("fn({arguments}) {{ "));
12425 self.edits
12426 .insert(target.location.end, format!("({arguments}) }}"));
12427
12428 CodeActionBuilder::new("Wrap in anonymous function")
12429 .kind(CodeActionKind::RefactorRewrite)
12430 .changes(
12431 self.params.text_document.uri.clone(),
12432 self.edits.edits.drain(..).collect(),
12433 )
12434 .push_to(&mut actions);
12435 }
12436 actions
12437 }
12438}
12439
12440impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> {
12441 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
12442 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
12443 if !within(self.params.range, expression_range) {
12444 return;
12445 }
12446
12447 // Exclude if the expression is already a function literal
12448 let is_excluded = matches!(expression, TypedExpr::Fn { .. });
12449
12450 if let Type::Fn { arguments, .. } = &*expression.type_()
12451 && !is_excluded
12452 {
12453 self.functions.push(FunctionToWrap {
12454 location: expression.location(),
12455 arguments: arguments.clone(),
12456 variables_names: VariablesNames::from_expression(expression),
12457 });
12458 }
12459
12460 ast::visit::visit_typed_expr(self, expression);
12461 }
12462
12463 /// We don't want to apply to functions that are being explicitly called
12464 /// already, so we need to intercept visits to function calls and bounce
12465 /// them out again so they don't end up in our impl for visit_typed_expr.
12466 /// Otherwise this is the same as [].
12467 fn visit_typed_expr_call(
12468 &mut self,
12469 _location: &'ast SrcSpan,
12470 _type: &'ast Arc<Type>,
12471 fun: &'ast TypedExpr,
12472 arguments: &'ast [TypedCallArg],
12473 _argument_parentheses: &'ast Option<u32>,
12474 ) {
12475 // We only need to do this interception for explicit calls, so if any
12476 // of our arguments are explicit we re-enter the visitor as usual.
12477 if arguments.iter().any(|argument| argument.is_implicit()) {
12478 self.visit_typed_expr(fun);
12479 } else {
12480 // We still want to visit other nodes nested in the function being
12481 // called so we bounce the call back out.
12482 ast::visit::visit_typed_expr(self, fun);
12483 }
12484
12485 for argument in arguments {
12486 self.visit_typed_call_arg(argument);
12487 }
12488 }
12489
12490 /// We don't want to apply this to record constructors used in a record
12491 /// update.
12492 fn visit_typed_expr_record_update(
12493 &mut self,
12494 location: &'ast SrcSpan,
12495 spread_start: &'ast u32,
12496 type_: &'ast Arc<Type>,
12497 updated_record: &'ast TypedExpr,
12498 updated_record_assigned_name: &'ast Option<EcoString>,
12499 constructor: &'ast TypedExpr,
12500 arguments: &'ast [TypedCallArg],
12501 ) {
12502 // If we're hovering the record constructor of an update and that is a
12503 // simple record constructor, we don't want to offer the wrap in
12504 // anonymous function code action:
12505 //
12506 // ```gleam
12507 // Wibble(..wibble, a: 1)
12508 // // ^^^ Wrap in anonymous function makes no sense on this!
12509 // ```
12510 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location());
12511 if within(self.params.range, constructor_range)
12512 && constructor.is_record_constructor_function()
12513 {
12514 return;
12515 }
12516
12517 ast::visit::visit_typed_expr_record_update(
12518 self,
12519 location,
12520 spread_start,
12521 type_,
12522 updated_record,
12523 updated_record_assigned_name,
12524 constructor,
12525 arguments,
12526 );
12527 }
12528}
12529
12530/// Code action to unwrap trivial one-statement anonymous functions into just a
12531/// reference to the function called
12532///
12533/// For example, if the code action was used on the anonymous function here:
12534///
12535/// ```gleam
12536/// list.map([1, 2, 3], fn(int) {
12537/// op(int)
12538/// })
12539/// ```
12540///
12541/// it would become:
12542///
12543/// ```gleam
12544/// list.map([1, 2, 3], op)
12545/// ```
12546pub struct UnwrapAnonymousFunction<'a> {
12547 module: &'a Module,
12548 line_numbers: &'a LineNumbers,
12549 params: &'a CodeActionParams,
12550 functions: Vec<FunctionToUnwrap>,
12551}
12552
12553/// Helper struct, a target for [UnwrapAnonymousFunction]
12554struct FunctionToUnwrap {
12555 /// Location of the anonymous function to apply the action to.
12556 outer_function: SrcSpan,
12557 /// Location of the opening brace of the anonymous function.
12558 outer_function_body_start: u32,
12559 /// Location of the function being called inside the anonymous function.
12560 /// This will be all that's left after the action, plus any comments.
12561 inner_function: SrcSpan,
12562 // Location of the opening parenthesis of the inner function's argument list.
12563 inner_function_arguments_start: u32,
12564}
12565
12566impl<'a> UnwrapAnonymousFunction<'a> {
12567 pub fn new(
12568 module: &'a Module,
12569 line_numbers: &'a LineNumbers,
12570 params: &'a CodeActionParams,
12571 ) -> Self {
12572 Self {
12573 module,
12574 line_numbers,
12575 params,
12576 functions: vec![],
12577 }
12578 }
12579
12580 pub fn code_actions(mut self) -> Vec<CodeAction> {
12581 self.visit_typed_module(&self.module.ast);
12582
12583 let mut actions = Vec::with_capacity(self.functions.len());
12584 for function in &self.functions {
12585 let mut edits = TextEdits::new(self.line_numbers);
12586
12587 // We need to delete the anonymous function's head and the opening
12588 // brace but preserve comments between it and the inner function call.
12589 // We set our endpoint at the start of the function body, and move
12590 // it on through any whitespace.
12591 let head_deletion_end =
12592 next_nonwhitespace(&self.module.code, function.outer_function_body_start + 1);
12593 edits.delete(SrcSpan {
12594 start: function.outer_function.start,
12595 end: head_deletion_end,
12596 });
12597
12598 // Delete the inner function call's arguments.
12599 edits.delete(SrcSpan {
12600 start: function.inner_function_arguments_start,
12601 end: function.inner_function.end,
12602 });
12603
12604 // To delete the tail we remove the function end (the '}') and any
12605 // whitespace before it.
12606 let tail_deletion_start =
12607 previous_nonwhitespace(&self.module.code, function.outer_function.end - 1);
12608 edits.delete(SrcSpan {
12609 start: tail_deletion_start,
12610 end: function.outer_function.end,
12611 });
12612
12613 CodeActionBuilder::new("Remove anonymous function wrapper")
12614 .kind(CodeActionKind::RefactorRewrite)
12615 .changes(self.params.text_document.uri.clone(), edits.edits)
12616 .push_to(&mut actions);
12617 }
12618 actions
12619 }
12620
12621 /// If an anonymous function can be unwrapped, save it to our list
12622 ///
12623 /// We need to ensure our subjects:
12624 /// - are anonymous function literals (not captures)
12625 /// - only contain a single statement
12626 /// - that statement is a function call
12627 /// - that call's arguments exactly match the arguments of the enclosing
12628 /// function
12629 fn register_function(
12630 &mut self,
12631 location: &'a SrcSpan,
12632 kind: &'a FunctionLiteralKind,
12633 arguments: &'a [TypedArg],
12634 body: &'a Vec1<TypedStatement>,
12635 ) {
12636 let function_range = src_span_to_lsp_range(*location, self.line_numbers);
12637 if !within(self.params.range, function_range) {
12638 return;
12639 }
12640
12641 let outer_body = match kind {
12642 FunctionLiteralKind::Anonymous { head, .. } => SrcSpan::new(
12643 next_nonwhitespace(&self.module.code, head.end),
12644 location.end,
12645 ),
12646 _ => return,
12647 };
12648
12649 // We can only apply to anonymous functions containing a single function call
12650 let [
12651 TypedStatement::Expression(TypedExpr::Call {
12652 location: call_location,
12653 arguments: call_arguments,
12654 open_parenthesis: Some(arguments_start),
12655 ..
12656 }),
12657 ] = body.as_slice()
12658 else {
12659 return;
12660 };
12661
12662 // We need the existing argument list for the fn to be a 1:1 match for
12663 // the args we pass to the called function, so we need to collect the
12664 // names used in both lists and check they're equal.
12665
12666 let outer_argument_names = arguments.iter().map(|a| match &a.names {
12667 ArgNames::Named { name, .. } => Some(name),
12668 // We can bail out early if any arguments are discarded, since
12669 // they couldn't match those actually used.
12670 ArgNames::Discard { .. } => None,
12671 // Anonymous functions can't have labelled arguments.
12672 ArgNames::NamedLabelled { .. } => unreachable!(),
12673 ArgNames::LabelledDiscard { .. } => unreachable!(),
12674 });
12675
12676 let inner_argument_names = call_arguments.iter().map(|a| match &a.value {
12677 TypedExpr::Var { name, .. } => Some(name),
12678 // We can bail out early if any of these aren't variables, since
12679 // they couldn't match the inputs.
12680 _ => None,
12681 });
12682
12683 if !inner_argument_names.eq(outer_argument_names) {
12684 return;
12685 }
12686
12687 self.functions.push(FunctionToUnwrap {
12688 outer_function: *location,
12689 outer_function_body_start: outer_body.start,
12690 inner_function: *call_location,
12691 inner_function_arguments_start: *arguments_start,
12692 });
12693 }
12694}
12695
12696impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
12697 fn visit_typed_expr_fn(
12698 &mut self,
12699 location: &'ast SrcSpan,
12700 type_: &'ast Arc<Type>,
12701 kind: &'ast FunctionLiteralKind,
12702 arguments: &'ast [TypedArg],
12703 body: &'ast Vec1<TypedStatement>,
12704 return_annotation: &'ast Option<ast::TypeAst>,
12705 ) {
12706 let function_range = src_span_to_lsp_range(*location, self.line_numbers);
12707 if !within(self.params.range, function_range) {
12708 return;
12709 }
12710
12711 self.register_function(location, kind, arguments, body);
12712
12713 ast::visit::visit_typed_expr_fn(
12714 self,
12715 location,
12716 type_,
12717 kind,
12718 arguments,
12719 body,
12720 return_annotation,
12721 );
12722 }
12723}
12724
12725/// Code action to create unknown modules when an import is added for a
12726/// module that doesn't exist.
12727///
12728/// For example, if `import wobble/woo` is added to `src/wiggle.gleam`,
12729/// then a code action to create `src/wobble/woo.gleam` will be presented
12730/// when triggered over `import wobble/woo`.
12731pub struct CreateUnknownModule<'a, IO> {
12732 module: &'a Module,
12733 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
12734 lines: &'a LineNumbers,
12735 params: &'a CodeActionParams,
12736 paths: &'a ProjectPaths,
12737 error: &'a Option<Error>,
12738}
12739
12740impl<'a, IO> CreateUnknownModule<'a, IO> {
12741 pub fn new(
12742 module: &'a Module,
12743 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
12744 lines: &'a LineNumbers,
12745 params: &'a CodeActionParams,
12746 paths: &'a ProjectPaths,
12747 error: &'a Option<Error>,
12748 ) -> Self {
12749 Self {
12750 module,
12751 compiler,
12752 lines,
12753 params,
12754 paths,
12755 error,
12756 }
12757 }
12758
12759 pub fn code_actions(self) -> Vec<CodeAction> {
12760 // This code action can be derived from `UnknownModule` type errors.
12761 // If those errors don't exist for the current module, then we will not
12762 // suggest this action.
12763 let Some(Error::Type { failed_modules, .. }) = self.error else {
12764 return vec![];
12765 };
12766 let Some(failed_module) = failed_modules.get(&self.module.name) else {
12767 return vec![];
12768 };
12769
12770 let mut unknown_module_name = None;
12771 // We then need to find the `UnknownModule` error that is under the
12772 // cursor (if any, otherwise there's no action to suggest)!
12773 for error in &failed_module.errors {
12774 let TypeError::UnknownModule { location, name, .. } = error else {
12775 continue;
12776 };
12777 let error_range = src_span_to_lsp_range(*location, self.lines);
12778 if !within(self.params.range, error_range) {
12779 continue;
12780 }
12781 // We've found the unknown module error!!
12782 unknown_module_name = Some(name);
12783 }
12784
12785 let Some(unknown_module_name) = unknown_module_name else {
12786 return vec![];
12787 };
12788
12789 // Now we need to check the module actually doesn't exist among the
12790 // importable ones! Imagine we've written `timestamp.to_string` and
12791 // `timestamp` is unknown: if there's any importable module in the form
12792 // `.../timestamp` then the most likely scenario is that the programmer
12793 // wanted to import that and not create a new `src/timestamp.gleam`
12794 // file!
12795 // So we check if any of the importable modules ends with the unknown
12796 // name and if that's the case we don't suggest this action.
12797 let importable_modules = self.compiler.project_compiler.get_importable_modules();
12798 let unknown_module_can_be_imported = importable_modules
12799 .keys()
12800 .find(|module_name| module_name.split('/').next_back() == Some(unknown_module_name))
12801 .is_some();
12802 if unknown_module_can_be_imported {
12803 return vec![];
12804 }
12805
12806 // We've made sure the module is not among the importable ones, so now
12807 // we figure out if the generated module needs to go under `src`,
12808 // `test`, or `dev` and we're good to actually generate it!
12809 let origin_directory = match self.module.origin {
12810 Origin::Src => self.paths.src_directory(),
12811 Origin::Test => self.paths.test_directory(),
12812 Origin::Dev => self.paths.dev_directory(),
12813 };
12814
12815 let uri = url_from_path(&format!("{origin_directory}/{}.gleam", unknown_module_name))
12816 .expect("origin directory is absolute");
12817
12818 let mut actions = vec![];
12819 CodeActionBuilder::new(&format!(
12820 "Create {}/{}.gleam",
12821 self.module.origin.folder_name(),
12822 unknown_module_name
12823 ))
12824 .kind(CodeActionKind::QuickFix)
12825 .document_changes(vec![DocumentChange::CreateFile(CreateFile {
12826 uri,
12827 options: Some(CreateFileOptions {
12828 overwrite: Some(false),
12829 ignore_if_exists: Some(true),
12830 }),
12831 annotation_id: None,
12832 })])
12833 .push_to(&mut actions);
12834
12835 actions
12836 }
12837}
12838
12839/// Code action to discard unused variable.
12840pub struct DiscardUnusedVariable<'a> {
12841 module: &'a Module,
12842 params: &'a CodeActionParams,
12843 edits: TextEdits<'a>,
12844}
12845
12846#[derive(Debug)]
12847struct UnusedVariable<'a> {
12848 location: &'a SrcSpan,
12849 origin: &'a VariableOrigin,
12850}
12851
12852impl<'a> DiscardUnusedVariable<'a> {
12853 pub fn new(
12854 module: &'a Module,
12855 line_numbers: &'a LineNumbers,
12856 params: &'a CodeActionParams,
12857 ) -> Self {
12858 Self {
12859 module,
12860 params,
12861 edits: TextEdits::new(line_numbers),
12862 }
12863 }
12864
12865 pub fn code_actions(mut self) -> Vec<CodeAction> {
12866 let unused_variable = self
12867 .module
12868 .ast
12869 .type_info
12870 .warnings
12871 .iter()
12872 .find_map(|warning| {
12873 if let type_::Warning::UnusedVariable { location, origin } = warning
12874 && within(
12875 self.params.range,
12876 self.edits.src_span_to_lsp_range(*location),
12877 )
12878 {
12879 Some(UnusedVariable { location, origin })
12880 } else {
12881 None
12882 }
12883 });
12884
12885 let Some(unused_variable) = unused_variable else {
12886 return vec![];
12887 };
12888
12889 match unused_variable.origin.syntax {
12890 type_::error::VariableSyntax::Variable(_) => {
12891 self.edits
12892 .insert(unused_variable.location.start, ("_").to_string());
12893 }
12894 type_::error::VariableSyntax::LabelShorthand(_) => {
12895 self.edits
12896 .insert(unused_variable.location.end, (" _").to_string());
12897 }
12898 type_::error::VariableSyntax::AssignmentPattern(location) => {
12899 self.edits.delete(SrcSpan {
12900 start: location.start,
12901 end: location.end,
12902 });
12903 }
12904 type_::error::VariableSyntax::Generated => (),
12905 }
12906
12907 let mut action = Vec::with_capacity(1);
12908 CodeActionBuilder::new("Discard unused variable")
12909 .kind(CodeActionKind::QuickFix)
12910 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12911 .preferred(true)
12912 .push_to(&mut action);
12913 action
12914 }
12915}
12916
12917pub fn code_action_fix_deprecated_pipe(
12918 module: &Module,
12919 line_numbers: &LineNumbers,
12920 params: &CodeActionParams,
12921 actions: &mut Vec<CodeAction>,
12922) {
12923 let uri = ¶ms.text_document.uri;
12924 let mut deprecated_pipes: Vec<&SrcSpan> = module
12925 .ast
12926 .type_info
12927 .warnings
12928 .iter()
12929 .filter_map(|warning| {
12930 if let type_::Warning::PipeIntoCallWhichReturnsFunction { location } = warning {
12931 Some(location)
12932 } else {
12933 None
12934 }
12935 })
12936 .collect();
12937
12938 if deprecated_pipes.is_empty() {
12939 return;
12940 }
12941
12942 // Sort spans by start position, with longer spans coming first
12943 deprecated_pipes.sort_by_key(|span| (span.start, -(span.len() as i64)));
12944
12945 for location in deprecated_pipes {
12946 let range = src_span_to_lsp_range(*location, line_numbers);
12947
12948 // Check if the cursor is within this span
12949 if !within(params.range, range) {
12950 continue;
12951 }
12952
12953 let edit = TextEdit {
12954 range: Range::new(range.end, range.end),
12955 new_text: "()".into(),
12956 };
12957
12958 CodeActionBuilder::new("Add extra parentheses")
12959 .kind(CodeActionKind::QuickFix)
12960 .changes(uri.clone(), vec![edit])
12961 .preferred(true)
12962 .push_to(actions);
12963 }
12964}
12965
12966/// Code action to switch between doc and regular comments.
12967pub struct ConvertBetweenDocAndRegularComment<'a> {
12968 module: &'a Module,
12969 lines: &'a LineNumbers,
12970 params: &'a CodeActionParams,
12971}
12972
12973impl<'a> ConvertBetweenDocAndRegularComment<'a> {
12974 pub fn new(module: &'a Module, lines: &'a LineNumbers, params: &'a CodeActionParams) -> Self {
12975 Self {
12976 module,
12977 lines,
12978 params,
12979 }
12980 }
12981
12982 pub fn code_actions(self) -> Vec<CodeAction> {
12983 let line = self.params.range.start.line;
12984 let num_slashes = self
12985 .count_leading_slashes(line)
12986 .expect("Line number should be valid");
12987
12988 if !(2..=4).contains(&num_slashes) {
12989 return vec![];
12990 }
12991
12992 let start_line = self
12993 .find_comment_edge((0..line).rev(), num_slashes)
12994 .unwrap_or(line);
12995 let end_line = self
12996 .find_comment_edge(line + 1.., num_slashes)
12997 .unwrap_or(line);
12998
12999 if self.params.range.end.line > end_line {
13000 return vec![];
13001 }
13002
13003 let comment = match num_slashes {
13004 2 if self.is_module_comment(start_line, end_line) => "////",
13005 2 if self.can_have_doc_comment(end_line) => "///",
13006 3 | 4 => "//",
13007 _ => return vec![],
13008 };
13009
13010 let mut edits = TextEdits::new(self.lines);
13011 for line in start_line..=end_line {
13012 let start = next_nonwhitespace(
13013 &self.module.code,
13014 self.line_start(line).expect("Line number should be valid"),
13015 );
13016 edits.replace(SrcSpan::new(start, start + num_slashes), comment.to_owned());
13017 }
13018
13019 let action_name = if comment == "//" {
13020 "Convert to regular comment"
13021 } else {
13022 "Convert to documentation comment"
13023 };
13024
13025 let mut action = Vec::with_capacity(1);
13026 CodeActionBuilder::new(action_name)
13027 .kind(CodeActionKind::RefactorRewrite)
13028 .changes(self.params.text_document.uri.clone(), edits.edits)
13029 .push_to(&mut action);
13030 action
13031 }
13032
13033 fn count_leading_slashes(&self, line: u32) -> Option<u32> {
13034 let mut count = 0;
13035 for c in self.module.code[self.line_start(line)? as usize..].chars() {
13036 if c == '/' {
13037 count += 1;
13038 } else if !c.is_whitespace() || c == '\n' || count > 0 {
13039 break;
13040 }
13041 }
13042 Some(count)
13043 }
13044
13045 fn line_start(&self, line: u32) -> Option<u32> {
13046 self.lines.line_starts.get(line as usize).copied()
13047 }
13048
13049 /// Find the last line in the range that is part of the same comment.
13050 fn find_comment_edge(&self, range: impl Iterator<Item = u32>, num_slashes: u32) -> Option<u32> {
13051 range
13052 .take_while(|&i| {
13053 self.count_leading_slashes(i)
13054 .is_some_and(|n| n == num_slashes)
13055 })
13056 .last()
13057 }
13058
13059 fn is_module_comment(&self, start_line: u32, end_line: u32) -> bool {
13060 previous_nonwhitespace(
13061 &self.module.code,
13062 self.line_start(start_line)
13063 .expect("Line number should be valid"),
13064 ) == 0
13065 && self
13066 .line_start(end_line + 1)
13067 .is_none_or(|position| self.module.extra.empty_lines.contains(&position))
13068 }
13069
13070 /// Check if the comment is before a node that can have a doc comment, e.g. a function.
13071 fn can_have_doc_comment(&self, end_line: u32) -> bool {
13072 self.line_start(end_line + 1).is_some_and(|position| {
13073 let next_node = next_nonwhitespace(&self.module.code, position);
13074 let definitions = &self.module.ast.definitions;
13075 definitions
13076 .functions
13077 .iter()
13078 .any(|function| function.location.contains(next_node))
13079 || definitions
13080 .constants
13081 .iter()
13082 .any(|constant| constant.location.contains(next_node))
13083 || definitions
13084 .type_aliases
13085 .iter()
13086 .any(|type_alias| type_alias.location.contains(next_node))
13087 || definitions.custom_types.iter().any(|custom_type| {
13088 custom_type.location.contains(next_node)
13089 || custom_type.constructors.iter().any(|constructor| {
13090 constructor.location.contains(next_node)
13091 || constructor.arguments.iter().any(|argument| {
13092 argument
13093 .label
13094 .as_ref()
13095 .is_some_and(|label| label.0.contains(next_node))
13096 })
13097 })
13098 })
13099 })
13100 }
13101}