Fork of daniellemaywood.uk/gleam — Wasm codegen work
462 kB
13254 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4use std::{collections::HashSet, iter, ops::Neg, 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 num_bigint::BigInt;
40use vec1::{Vec1, vec1};
41
42use crate::engine::{completely_within, position_within};
43
44use super::{
45 TextEdits,
46 compiler::LspProjectCompiler,
47 edits,
48 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
49 engine::{overlaps, within},
50 files::FileSystemProxy,
51 reference::{FindVariableReferences, VariableReferenceKind},
52 src_span_to_lsp_range, url_from_path,
53};
54
55#[derive(Debug)]
56pub struct CodeActionBuilder {
57 action: CodeAction,
58}
59
60impl CodeActionBuilder {
61 pub fn new(title: &str) -> Self {
62 Self {
63 action: CodeAction {
64 title: title.to_string(),
65 kind: None,
66 diagnostics: None,
67 edit: None,
68 command: None,
69 is_preferred: None,
70 disabled: None,
71 data: None,
72 tags: None,
73 },
74 }
75 }
76
77 pub fn kind(mut self, kind: CodeActionKind) -> Self {
78 self.action.kind = Some(kind);
79 self
80 }
81
82 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
83 let mut edit = self.action.edit.take().unwrap_or_default();
84 let mut changes = edit.changes.take().unwrap_or_default();
85 _ = changes.insert(uri, edits);
86
87 edit.changes = Some(changes);
88 self.action.edit = Some(edit);
89 self
90 }
91
92 pub fn document_changes(mut self, changes: Vec<DocumentChange>) -> Self {
93 let mut edit = self.action.edit.take().unwrap_or_default();
94
95 edit.document_changes = Some(changes);
96
97 self.action.edit = Some(edit);
98 self
99 }
100
101 pub fn preferred(mut self, is_preferred: bool) -> Self {
102 self.action.is_preferred = Some(is_preferred);
103 self
104 }
105
106 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
107 actions.push(self.action);
108 }
109}
110
111/// A small helper function to get the indentation at a given position.
112fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
113 let mut indent_size = 0;
114 let line_start = *line_numbers
115 .line_starts
116 .get(line as usize)
117 .expect("Line number should be valid");
118
119 let mut chars = code[line_start as usize..].chars();
120 while chars.next() == Some(' ') {
121 indent_size += 1;
122 }
123
124 indent_size
125}
126
127// Given a string and a position in it, if the position points to whitespace,
128// this function returns the next position which doesn't.
129fn next_nonwhitespace(string: &EcoString, position: u32) -> u32 {
130 let mut n = position;
131 let mut chars = string[position as usize..].chars();
132 while chars.next().is_some_and(char::is_whitespace) {
133 n += 1;
134 }
135 n
136}
137
138// Given a string and a position in it, if the position points after whitespace,
139// this function returns the previous position which doesn't.
140fn previous_nonwhitespace(string: &EcoString, position: u32) -> u32 {
141 let mut n = position;
142 let mut chars = string[..position as usize].chars();
143 while chars.next_back().is_some_and(char::is_whitespace) {
144 n -= 1;
145 }
146 n
147}
148
149/// Code action to remove literal tuples in case subjects, essentially making
150/// the elements of the tuples into the case's subjects.
151///
152/// The code action is only available for the i'th subject if:
153/// - it is a non-empty tuple, and
154/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
155///
156/// # Basic example:
157///
158/// The following case expression:
159///
160/// ```gleam
161/// case #(1, 2) {
162/// #(a, b) -> 0
163/// }
164/// ```
165///
166/// Becomes:
167///
168/// ```gleam
169/// case 1, 2 {
170/// a, b -> 0
171/// }
172/// ```
173///
174/// # Another example:
175///
176/// The following case expression does not produce any code action
177///
178/// ```gleam
179/// case #(1, 2) {
180/// a -> 0 // <- the pattern is not a tuple
181/// }
182/// ```
183pub struct RedundantTupleInCaseSubject<'a> {
184 edits: TextEdits<'a>,
185 code: &'a EcoString,
186 extra: &'a ModuleExtra,
187 params: &'a CodeActionParams,
188 module: &'a ast::TypedModule,
189 hovered: bool,
190}
191
192impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
193 fn visit_typed_expr_case(
194 &mut self,
195 location: &'ast SrcSpan,
196 type_: &'ast Arc<Type>,
197 subjects: &'ast [TypedExpr],
198 clauses: &'ast [ast::TypedClause],
199 compiled_case: &'ast CompiledCase,
200 ) {
201 for (subject_idx, subject) in subjects.iter().enumerate() {
202 let TypedExpr::Tuple {
203 location, elements, ..
204 } = subject
205 else {
206 continue;
207 };
208
209 // Ignore empty tuple
210 if elements.is_empty() {
211 continue;
212 }
213
214 // We cannot rewrite clauses whose i-th pattern is not a discard or
215 // tuples.
216 let all_ith_patterns_are_tuples_or_discards = clauses
217 .iter()
218 .map(|clause| clause.pattern.get(subject_idx))
219 .all(|pattern| {
220 matches!(
221 pattern,
222 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
223 )
224 });
225
226 if !all_ith_patterns_are_tuples_or_discards {
227 continue;
228 }
229
230 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
231
232 for clause in clauses {
233 match clause.pattern.get(subject_idx) {
234 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
235 *location,
236 elements.last().map(|element| element.location()),
237 ),
238 Some(Pattern::Discard { location, .. }) => {
239 self.discard_tuple_items(*location, elements.len());
240 }
241 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
242 }
243 }
244 let range = self.edits.src_span_to_lsp_range(*location);
245 self.hovered = self.hovered || overlaps(self.params.range, range);
246 }
247
248 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
249 }
250}
251
252impl<'a> RedundantTupleInCaseSubject<'a> {
253 pub fn new(
254 module: &'a Module,
255 line_numbers: &'a LineNumbers,
256 params: &'a CodeActionParams,
257 ) -> Self {
258 Self {
259 edits: TextEdits::new(line_numbers),
260 code: &module.code,
261 extra: &module.extra,
262 params,
263 module: &module.ast,
264 hovered: false,
265 }
266 }
267
268 pub fn code_actions(mut self) -> Vec<CodeAction> {
269 self.visit_typed_module(self.module);
270 if !self.hovered {
271 return vec![];
272 }
273
274 self.edits.edits.sort_by_key(|edit| edit.range.start);
275
276 let mut actions = vec![];
277 CodeActionBuilder::new("Remove redundant tuples")
278 .kind(CodeActionKind::RefactorRewrite)
279 .changes(self.params.text_document.uri.clone(), self.edits.edits)
280 .preferred(true)
281 .push_to(&mut actions);
282
283 actions
284 }
285
286 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
287 let tuple_code = self
288 .code
289 .get(location.start as usize..location.end as usize)
290 .expect("valid span");
291
292 // Delete `#`
293 self.edits
294 .delete(SrcSpan::new(location.start, location.start + 1));
295
296 // Delete `(`
297 let (lparen_offset, _) = tuple_code
298 .match_indices('(')
299 // Ignore in comments
300 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
301 .expect("`(` not found in tuple");
302
303 self.edits.delete(SrcSpan::new(
304 location.start + lparen_offset as u32,
305 location.start + lparen_offset as u32 + 1,
306 ));
307
308 // Delete trailing `,` (if applicable)
309 if let Some(last_elem_location) = last_elem_location {
310 // Get the code after the last element until the tuple's `)`
311 let code_after_last_elem = self
312 .code
313 .get(last_elem_location.end as usize..location.end as usize)
314 .expect("valid span");
315
316 if let Some((trailing_comma_offset, _)) = code_after_last_elem
317 .rmatch_indices(',')
318 // Ignore in comments
319 .find(|(i, _)| {
320 !self
321 .extra
322 .is_within_comment(last_elem_location.end + *i as u32)
323 })
324 {
325 self.edits.delete(SrcSpan::new(
326 last_elem_location.end + trailing_comma_offset as u32,
327 last_elem_location.end + trailing_comma_offset as u32 + 1,
328 ));
329 }
330 }
331
332 // Delete )
333 self.edits
334 .delete(SrcSpan::new(location.end - 1, location.end));
335 }
336
337 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
338 // Replace the old discard with multiple discard, one for each of the
339 // tuple items.
340 self.edits.replace(
341 discard_location,
342 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
343 );
344 }
345}
346
347/// Builder for code action to convert `let assert` into a case expression.
348///
349pub struct LetAssertToCase<'a> {
350 module: &'a Module,
351 params: &'a CodeActionParams,
352 actions: Vec<CodeAction>,
353 edits: TextEdits<'a>,
354}
355
356impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
357 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
358 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
359 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan {
360 start: assignment.location.start,
361 end: assignment.value.location().start,
362 });
363 self.visit_typed_expr(&assignment.value);
364
365 // Only offer the code action if the cursor is over the statement and
366 // to prevent weird behaviour when `let assert` statements are nested,
367 // we only check for the code action between the `let` and `=`.
368 if !(within(self.params.range, assignment_range)
369 && overlaps(self.params.range, assignment_start_range))
370 {
371 return;
372 }
373
374 // This pattern only applies to `let assert`
375 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
376 return;
377 };
378
379 // Get the source code for the tested expression
380 let location = assignment.value.location();
381 let expr = self
382 .module
383 .code
384 .get(location.start as usize..location.end as usize)
385 .expect("Location must be valid");
386
387 // Get the source code for the pattern
388 let pattern_location = assignment.pattern.location();
389 let pattern = self
390 .module
391 .code
392 .get(pattern_location.start as usize..pattern_location.end as usize)
393 .expect("Location must be valid");
394
395 let message = message.as_ref().map(|message| {
396 let location = message.location();
397 self.module
398 .code
399 .get(location.start as usize..location.end as usize)
400 .expect("Location must be valid")
401 });
402
403 let range = self.edits.src_span_to_lsp_range(assignment.location);
404
405 // Figure out which variables are assigned in the pattern
406 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
407
408 let assigned = match variables.len() {
409 0 => "_",
410 1 => variables.first().expect("Variables is length one"),
411 _ => &format!("#({})", variables.join(", ")),
412 };
413
414 let mut new_text = format!("let {assigned} = ");
415 let panic_message = if let Some(message) = message {
416 &format!("panic as {message}")
417 } else {
418 "panic"
419 };
420 let clauses = vec![
421 // The existing pattern
422 CaseClause {
423 pattern,
424 // `_` is not a valid expression, so if we are not
425 // binding any variables in the pattern, we simply return Nil.
426 expression: if assigned == "_" { "Nil" } else { assigned },
427 },
428 CaseClause {
429 pattern: "_",
430 expression: panic_message,
431 },
432 ];
433 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
434
435 let uri = &self.params.text_document.uri;
436
437 CodeActionBuilder::new("Convert to case")
438 .kind(CodeActionKind::RefactorRewrite)
439 .changes(uri.clone(), vec![TextEdit { range, new_text }])
440 .preferred(false)
441 .push_to(&mut self.actions);
442 }
443}
444
445impl<'a> LetAssertToCase<'a> {
446 pub fn new(
447 module: &'a Module,
448 line_numbers: &'a LineNumbers,
449 params: &'a CodeActionParams,
450 ) -> Self {
451 Self {
452 module,
453 params,
454 actions: Vec::new(),
455 edits: TextEdits::new(line_numbers),
456 }
457 }
458
459 pub fn code_actions(mut self) -> Vec<CodeAction> {
460 self.visit_typed_module(&self.module.ast);
461 self.actions
462 }
463}
464
465struct PatternVariableFinder {
466 pattern_variables: Vec<EcoString>,
467}
468
469impl PatternVariableFinder {
470 fn new() -> Self {
471 Self {
472 pattern_variables: Vec::new(),
473 }
474 }
475
476 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
477 let mut finder = Self::new();
478 finder.visit_typed_pattern(pattern);
479 finder.pattern_variables
480 }
481}
482
483impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
484 fn visit_typed_pattern_variable(
485 &mut self,
486 _location: &'ast SrcSpan,
487 name: &'ast EcoString,
488 _type: &'ast Arc<Type>,
489 _origin: &'ast VariableOrigin,
490 ) {
491 self.pattern_variables.push(name.clone());
492 }
493
494 fn visit_typed_pattern_assign(
495 &mut self,
496 location: &'ast SrcSpan,
497 name: &'ast EcoString,
498 pattern: &'ast TypedPattern,
499 ) {
500 self.pattern_variables.push(name.clone());
501 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
502 }
503
504 fn visit_typed_pattern_string_prefix(
505 &mut self,
506 _location: &'ast SrcSpan,
507 _left_location: &'ast SrcSpan,
508 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
509 _right_location: &'ast SrcSpan,
510 _left_side_string: &'ast EcoString,
511 right_side_assignment: &'ast AssignName,
512 ) {
513 if let Some((name, _)) = left_side_assignment {
514 self.pattern_variables.push(name.clone());
515 }
516 if let AssignName::Variable(name) = right_side_assignment {
517 self.pattern_variables.push(name.clone());
518 }
519 }
520}
521
522pub fn code_action_inexhaustive_let_to_case(
523 module: &Module,
524 line_numbers: &LineNumbers,
525 params: &CodeActionParams,
526 error: &Option<Error>,
527 actions: &mut Vec<CodeAction>,
528) {
529 let Some(errors) = type_errors_for_module(error, module) else {
530 return;
531 };
532
533 let inexhaustive_assignments = errors
534 .iter()
535 .filter_map(|error| {
536 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error {
537 Some((*location, missing))
538 } else {
539 None
540 }
541 })
542 .collect_vec();
543
544 if inexhaustive_assignments.is_empty() {
545 return;
546 }
547
548 for (location, missing) in inexhaustive_assignments {
549 let mut text_edits = TextEdits::new(line_numbers);
550
551 let range = text_edits.src_span_to_lsp_range(location);
552 if !within(params.range, range) {
553 continue;
554 }
555
556 let Some(Located::Statement(TypedStatement::Assignment(assignment))) =
557 module.find_node(location.start)
558 else {
559 continue;
560 };
561
562 let TypedAssignment {
563 value,
564 pattern,
565 kind: AssignmentKind::Let,
566 location,
567 compiled_case: _,
568 annotation: _,
569 } = assignment.as_ref()
570 else {
571 continue;
572 };
573
574 // Get the source code for the tested expression
575 let value_location = value.location();
576 let expr = module
577 .code
578 .get(value_location.start as usize..value_location.end as usize)
579 .expect("Location must be valid");
580
581 // Get the source code for the pattern
582 let pattern_location = pattern.location();
583 let pattern_code = module
584 .code
585 .get(pattern_location.start as usize..pattern_location.end as usize)
586 .expect("Location must be valid");
587
588 let range = text_edits.src_span_to_lsp_range(*location);
589
590 // Figure out which variables are assigned in the pattern
591 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
592
593 let assigned = match variables.len() {
594 0 => "_",
595 1 => variables.first().expect("Variables is length one"),
596 _ => &format!("#({})", variables.join(", ")),
597 };
598
599 let mut new_text = format!("let {assigned} = ");
600 print_case_expression(
601 range.start.character as usize,
602 expr,
603 iter::once(CaseClause {
604 pattern: pattern_code,
605 expression: if assigned == "_" { "Nil" } else { assigned },
606 })
607 .chain(missing.iter().map(|pattern| CaseClause {
608 pattern,
609 expression: "todo",
610 }))
611 .collect(),
612 &mut new_text,
613 );
614
615 let uri = ¶ms.text_document.uri;
616
617 text_edits.replace(*location, new_text);
618
619 CodeActionBuilder::new("Convert to case")
620 .kind(CodeActionKind::QuickFix)
621 .changes(uri.clone(), text_edits.edits)
622 .preferred(true)
623 .push_to(actions);
624 }
625}
626
627pub(crate) fn type_errors_for_module<'a>(
628 error: &'a Option<Error>,
629 module: &Module,
630) -> Option<&'a Vec1<type_::Error>> {
631 let Some(Error::Type {
632 skipped_modules: _,
633 failed_modules,
634 }) = error
635 else {
636 return None;
637 };
638 Some(&failed_modules.get(&module.name)?.errors)
639}
640
641struct CaseClause<'a> {
642 pattern: &'a str,
643 expression: &'a str,
644}
645
646fn print_case_expression(
647 indent_size: usize,
648 subject: &str,
649 clauses: Vec<CaseClause<'_>>,
650 buffer: &mut String,
651) {
652 let indent = " ".repeat(indent_size);
653
654 // Print the beginning of the expression
655 buffer.push_str("case ");
656 buffer.push_str(subject);
657 buffer.push_str(" {");
658
659 for clause in clauses.iter() {
660 // Print the newline and indentation for this clause
661 buffer.push('\n');
662 buffer.push_str(&indent);
663 // Indent this clause one level deeper than the case expression
664 buffer.push_str(" ");
665
666 // Print the clause
667 buffer.push_str(clause.pattern);
668 buffer.push_str(" -> ");
669 buffer.push_str(clause.expression);
670 }
671
672 // If there are no clauses to print, the closing brace should be
673 // on the same line as the opening one, with no space between.
674 if !clauses.is_empty() {
675 buffer.push('\n');
676 buffer.push_str(&indent);
677 }
678 buffer.push('}');
679}
680
681/// Builder for code action to apply the label shorthand syntax on arguments
682/// where the label has the same name as the variable.
683///
684pub struct UseLabelShorthandSyntax<'a> {
685 module: &'a Module,
686 params: &'a CodeActionParams,
687 edits: TextEdits<'a>,
688}
689
690impl<'a> UseLabelShorthandSyntax<'a> {
691 pub fn new(
692 module: &'a Module,
693 line_numbers: &'a LineNumbers,
694 params: &'a CodeActionParams,
695 ) -> Self {
696 Self {
697 module,
698 params,
699 edits: TextEdits::new(line_numbers),
700 }
701 }
702
703 pub fn code_actions(mut self) -> Vec<CodeAction> {
704 self.visit_typed_module(&self.module.ast);
705 if self.edits.edits.is_empty() {
706 return vec![];
707 }
708 let mut action = Vec::with_capacity(1);
709 CodeActionBuilder::new("Use label shorthand syntax")
710 .kind(CodeActionKind::Refactor)
711 .changes(self.params.text_document.uri.clone(), self.edits.edits)
712 .preferred(false)
713 .push_to(&mut action);
714 action
715 }
716}
717
718impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
719 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
720 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
721 let is_selected = overlaps(arg_range, self.params.range);
722
723 match arg {
724 CallArg {
725 label: Some(label),
726 value: TypedExpr::Var { name, location, .. },
727 ..
728 } if is_selected && !arg.uses_label_shorthand() && label == name => {
729 self.edits.delete(*location);
730 }
731 _ => (),
732 }
733
734 ast::visit::visit_typed_call_arg(self, arg);
735 }
736
737 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
738 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
739 let is_selected = overlaps(arg_range, self.params.range);
740
741 match arg {
742 CallArg {
743 label: Some(label),
744 value: TypedPattern::Variable { name, location, .. },
745 ..
746 } if is_selected && !arg.uses_label_shorthand() && label == name => {
747 self.edits.delete(*location);
748 }
749 _ => (),
750 }
751
752 ast::visit::visit_typed_pattern_call_arg(self, arg);
753 }
754}
755
756/// Builder for code action to apply the fill in the missing labelled arguments
757/// of the selected function call.
758///
759pub struct FillInMissingLabelledArgs<'a> {
760 module: &'a Module,
761 params: &'a CodeActionParams,
762 edits: TextEdits<'a>,
763 use_right_hand_side_location: Option<SrcSpan>,
764 selected_call: Option<SelectedCall<'a>>,
765 current_function: Option<&'a TypedFunction>,
766}
767
768struct SelectedCall<'a> {
769 location: SrcSpan,
770 field_map: &'a FieldMap,
771 arguments: Vec<CallArg<()>>,
772 kind: SelectedCallKind,
773 fun_type: Option<Arc<Type>>,
774 enclosing_function: Option<&'a TypedFunction>,
775}
776
777enum SelectedCallKind {
778 Value,
779 Pattern,
780}
781
782impl<'a> FillInMissingLabelledArgs<'a> {
783 pub fn new(
784 module: &'a Module,
785 line_numbers: &'a LineNumbers,
786 params: &'a CodeActionParams,
787 ) -> Self {
788 Self {
789 module,
790 params,
791 edits: TextEdits::new(line_numbers),
792 use_right_hand_side_location: None,
793 selected_call: None,
794 current_function: None,
795 }
796 }
797
798 pub fn code_actions(mut self) -> Vec<CodeAction> {
799 self.visit_typed_module(&self.module.ast);
800 let Some(SelectedCall {
801 location: call_location,
802 field_map,
803 arguments,
804 kind,
805 fun_type,
806 enclosing_function,
807 }) = self.selected_call
808 else {
809 return vec![];
810 };
811
812 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
813 let missing_labels = field_map.missing_labels(&arguments);
814
815 // If we're applying the code action to a use call, then we know
816 // that the last missing argument is going to be implicitly inserted
817 // by the compiler, so in that case we don't want to also add that
818 // last label to the completions.
819 let missing_labels = missing_labels.iter().peekable();
820 let mut missing_labels = if is_use_call {
821 missing_labels.dropping_back(1)
822 } else {
823 missing_labels
824 };
825
826 // If we couldn't find any missing label to insert we just return.
827 if missing_labels.peek().is_none() {
828 return vec![];
829 }
830
831 // A pattern could have been written with no parentheses at all!
832 // So we need to check for the last character to see if parentheses
833 // are there or not before filling the arguments in
834 let has_parentheses = ")"
835 == code_at(
836 self.module,
837 SrcSpan::new(call_location.end - 1, call_location.end),
838 );
839 let label_insertion_start = if has_parentheses {
840 // If it ends with a parentheses we'll need to start inserting
841 // right before the closing one...
842 call_location.end - 1
843 } else {
844 // ...otherwise we just append the result
845 call_location.end
846 };
847
848 // Now we need to figure out if there's a comma at the end of the
849 // arguments list:
850 //
851 // call(one, |)
852 // ^ Cursor here, with a comma behind
853 //
854 // call(one|)
855 // ^ Cursor here, no comma behind, we'll have to add one!
856 //
857 let has_comma_after_last_argument =
858 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) {
859 self.module
860 .code
861 .get(last_arg.location.end as usize..=label_insertion_start as usize)
862 .is_some_and(|text| text.contains(','))
863 } else {
864 false
865 };
866
867 let scope_variables =
868 ScopeVariableCollector::new(call_location.start, &self.module.ast.definitions);
869 let variables_in_scope = match enclosing_function {
870 Some(function) => scope_variables.collect_from_function(function),
871 None => scope_variables.variables,
872 };
873
874 let labels_list = missing_labels
875 .map(|label| {
876 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope)
877 })
878 .join(", ");
879
880 let has_no_explicit_arguments = arguments
881 .iter()
882 .filter(|arg| !arg.is_implicit())
883 .peekable()
884 .peek()
885 .is_none();
886
887 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
888 labels_list
889 } else {
890 format!(", {labels_list}")
891 };
892
893 let edit = if has_parentheses {
894 labels_list
895 } else {
896 // If the variant whose arguments we're filling in was written
897 // with no parentheses we need to add those as well to make it a
898 // valid constructor.
899 format!("({labels_list})")
900 };
901
902 self.edits.insert(label_insertion_start, edit);
903
904 let mut action = Vec::with_capacity(1);
905 CodeActionBuilder::new("Fill labels")
906 .kind(CodeActionKind::QuickFix)
907 .changes(self.params.text_document.uri.clone(), self.edits.edits)
908 .preferred(true)
909 .push_to(&mut action);
910 action
911 }
912
913 /// Formats a label for insertion. Uses `label:` syntax if there's a variable
914 /// in scope with matching name and type, otherwise uses `label: todo`.
915 fn format_label(
916 label: &EcoString,
917 kind: &SelectedCallKind,
918 fun_type: &Option<Arc<Type>>,
919 field_map: &FieldMap,
920 variables_in_scope: &HashMap<EcoString, Arc<Type>>,
921 ) -> String {
922 if matches!(kind, SelectedCallKind::Pattern) {
923 return format!("{label}:");
924 }
925
926 if let Some(var_type) = variables_in_scope.get(label) {
927 let expected_type = fun_type.as_ref().and_then(|ft| {
928 let (arg_types, _) = ft.fn_types()?;
929 let &index = field_map.fields.get(label)?;
930 arg_types.get(index as usize).cloned()
931 });
932
933 if let Some(expected) = expected_type
934 && expected.same_as(var_type)
935 {
936 return format!("{label}:");
937 }
938 }
939
940 format!("{label}: todo")
941 }
942
943 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
944 CallArg {
945 label: argument.label.clone(),
946 location: argument.location,
947 value: (),
948 implicit: argument.implicit,
949 }
950 }
951}
952
953impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
954 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
955 // We store the current function as the containing function while we traverse
956 // it, allowing handling nested functions correctly.
957 let previous_function = self.current_function;
958 self.current_function = Some(fun);
959 ast::visit::visit_typed_function(self, fun);
960 self.current_function = previous_function;
961 }
962
963 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
964 // If we're adding labels to a use call the correct location of the
965 // function we need to add labels to is `use_right_hand_side_location`.
966 // So we store it for when we're typing the use call.
967 let previous = self.use_right_hand_side_location;
968 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
969 ast::visit::visit_typed_use(self, use_);
970 self.use_right_hand_side_location = previous;
971 }
972
973 fn visit_typed_constant_record(
974 &mut self,
975 location: &'ast SrcSpan,
976 module: &'ast Option<(EcoString, SrcSpan)>,
977 name: &'ast EcoString,
978 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
979 type_: &'ast Arc<Type>,
980 field_map: &'ast Inferred<FieldMap>,
981 record_constructor: &'ast Option<Box<ValueConstructor>>,
982 ) {
983 let record_range = self.edits.src_span_to_lsp_range(*location);
984 if !within(self.params.range, record_range) {
985 return;
986 }
987
988 if let Some(arguments) = arguments
989 && let Inferred::Known(field_map) = field_map
990 {
991 self.selected_call = Some(SelectedCall {
992 location: *location,
993 field_map,
994 arguments: arguments.iter().map(Self::empty_argument).collect(),
995 kind: SelectedCallKind::Value,
996 fun_type: record_constructor
997 .as_ref()
998 .map(|constructor| constructor.type_.clone()),
999 enclosing_function: None,
1000 });
1001 }
1002
1003 // We only want to take into account the innermost function call
1004 // containing the current selection so we can't stop at the first call
1005 // we find (the outermost one) and have to keep traversing it in case
1006 // we're inside a nested call.
1007 ast::visit::visit_typed_constant_record(
1008 self,
1009 location,
1010 module,
1011 name,
1012 arguments,
1013 type_,
1014 field_map,
1015 record_constructor,
1016 );
1017 }
1018
1019 fn visit_typed_expr_call(
1020 &mut self,
1021 location: &'ast SrcSpan,
1022 type_: &'ast Arc<Type>,
1023 fun: &'ast TypedExpr,
1024 arguments: &'ast [TypedCallArg],
1025 open_parenthesis: &'ast Option<u32>,
1026 ) {
1027 let call_range = self.edits.src_span_to_lsp_range(*location);
1028 if !within(self.params.range, call_range) {
1029 return;
1030 }
1031
1032 if let Some(field_map) = fun.field_map() {
1033 let location = self.use_right_hand_side_location.unwrap_or(*location);
1034 self.selected_call = Some(SelectedCall {
1035 location,
1036 field_map,
1037 arguments: arguments.iter().map(Self::empty_argument).collect(),
1038 kind: SelectedCallKind::Value,
1039 fun_type: Some(fun.type_()),
1040 enclosing_function: self.current_function,
1041 });
1042 }
1043
1044 // We only want to take into account the innermost function call
1045 // containing the current selection so we can't stop at the first call
1046 // we find (the outermost one) and have to keep traversing it in case
1047 // we're inside a nested call.
1048 let previous = self.use_right_hand_side_location;
1049 self.use_right_hand_side_location = None;
1050 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
1051 self.use_right_hand_side_location = previous;
1052 }
1053
1054 fn visit_typed_pattern_constructor(
1055 &mut self,
1056 location: &'ast SrcSpan,
1057 name_location: &'ast SrcSpan,
1058 name: &'ast EcoString,
1059 arguments: &'ast Vec<CallArg<TypedPattern>>,
1060 module: &'ast Option<(EcoString, SrcSpan)>,
1061 constructor: &'ast Inferred<type_::PatternConstructor>,
1062 spread: &'ast Option<SrcSpan>,
1063 type_: &'ast Arc<Type>,
1064 ) {
1065 let call_range = self.edits.src_span_to_lsp_range(*location);
1066 if !within(self.params.range, call_range) {
1067 return;
1068 }
1069
1070 if let Some(field_map) = constructor.field_map() {
1071 self.selected_call = Some(SelectedCall {
1072 location: *location,
1073 field_map,
1074 arguments: arguments.iter().map(Self::empty_argument).collect(),
1075 kind: SelectedCallKind::Pattern,
1076 fun_type: None,
1077 enclosing_function: self.current_function,
1078 });
1079 }
1080
1081 ast::visit::visit_typed_pattern_constructor(
1082 self,
1083 location,
1084 name_location,
1085 name,
1086 arguments,
1087 module,
1088 constructor,
1089 spread,
1090 type_,
1091 );
1092 }
1093}
1094
1095/// Collects variables that are in scope at a given cursor position.
1096struct ScopeVariableCollector {
1097 cursor: u32,
1098 pub variables: HashMap<EcoString, Arc<Type>>,
1099}
1100
1101impl ScopeVariableCollector {
1102 fn new(cursor: u32, definitions: &TypedDefinitions) -> Self {
1103 let TypedDefinitions { constants, .. } = definitions;
1104
1105 // When creating a collector, we add module constants to the variables
1106 // that are in scope.
1107 // These will always be available inside the body of any function we
1108 // might want to analyse (unless they are shadowed and replaced in the
1109 // map, that is)
1110 let variables = constants
1111 .iter()
1112 .map(|constant| (constant.name.clone(), constant.type_.clone()))
1113 .collect();
1114
1115 Self { cursor, variables }
1116 }
1117
1118 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> {
1119 for arg in &fun.arguments {
1120 if let Some(name) = arg.get_variable_name() {
1121 _ = self.variables.insert(name.clone(), arg.type_.clone());
1122 }
1123 }
1124
1125 for statement in &fun.body {
1126 self.visit_typed_statement(statement);
1127 }
1128
1129 self.variables
1130 }
1131}
1132
1133impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector {
1134 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
1135 // We need to consider variables defined only before the cursor
1136 if statement.location().start >= self.cursor {
1137 return;
1138 }
1139
1140 ast::visit::visit_typed_statement(self, statement);
1141 }
1142
1143 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1144 // if cursor is inside the assignment, we only visit the value expression,
1145 // because the variable being defined isn't in scope yet.
1146 if assignment.location.contains(self.cursor) {
1147 self.visit_typed_expr(&assignment.value);
1148 } else {
1149 self.visit_typed_pattern(&assignment.pattern);
1150 }
1151 }
1152
1153 fn visit_typed_expr_fn(
1154 &mut self,
1155 _location: &'ast SrcSpan,
1156 _type_: &'ast Arc<Type>,
1157 _kind: &'ast FunctionLiteralKind,
1158 _arguments: &'ast [TypedArg],
1159 _body: &'ast Vec1<TypedStatement>,
1160 _return_annotation: &'ast Option<ast::TypeAst>,
1161 ) {
1162 // We don't descend into nested functions, as their variables aren't in
1163 // our scope
1164 }
1165
1166 fn visit_typed_expr_block(
1167 &mut self,
1168 location: &'ast SrcSpan,
1169 statements: &'ast [TypedStatement],
1170 ) {
1171 if self.cursor >= location.end {
1172 return;
1173 }
1174 ast::visit::visit_typed_expr_block(self, location, statements);
1175 }
1176
1177 fn visit_typed_pattern_variable(
1178 &mut self,
1179 _location: &'ast SrcSpan,
1180 name: &'ast EcoString,
1181 type_: &'ast Arc<Type>,
1182 _origin: &'ast VariableOrigin,
1183 ) {
1184 if !name.starts_with('_') {
1185 _ = self.variables.insert(name.clone(), type_.clone());
1186 }
1187 }
1188
1189 fn visit_typed_pattern_assign(
1190 &mut self,
1191 _location: &'ast SrcSpan,
1192 name: &'ast EcoString,
1193 pattern: &'ast Pattern<Arc<Type>>,
1194 ) {
1195 self.visit_typed_pattern(pattern);
1196 if !name.starts_with('_') {
1197 _ = self.variables.insert(name.clone(), pattern.type_());
1198 }
1199 }
1200}
1201
1202struct MissingImport {
1203 location: SrcSpan,
1204 suggestions: Vec<ImportSuggestion>,
1205}
1206
1207struct ImportSuggestion {
1208 // The name to replace with, if the user made a typo
1209 name: EcoString,
1210 // The optional module to import, if suggesting an importable module
1211 import: Option<EcoString>,
1212}
1213
1214pub fn code_action_import_module(
1215 module: &Module,
1216 line_numbers: &LineNumbers,
1217 params: &CodeActionParams,
1218 error: &Option<Error>,
1219 actions: &mut Vec<CodeAction>,
1220) {
1221 let uri = ¶ms.text_document.uri;
1222 let Some(errors) = type_errors_for_module(error, module) else {
1223 return;
1224 };
1225
1226 let missing_imports = errors
1227 .into_iter()
1228 .filter_map(|error| {
1229 if let type_::Error::UnknownModule {
1230 location,
1231 suggestions,
1232 ..
1233 } = error
1234 {
1235 suggest_imports(*location, suggestions)
1236 } else {
1237 None
1238 }
1239 })
1240 .collect_vec();
1241
1242 if missing_imports.is_empty() {
1243 return;
1244 }
1245
1246 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
1247 let first_is_import = first_import_pos.is_some();
1248 let import_location = first_import_pos.unwrap_or_default();
1249
1250 let after_import_newlines =
1251 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
1252
1253 for missing_import in missing_imports {
1254 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
1255 if !overlaps(params.range, range) {
1256 continue;
1257 }
1258
1259 for suggestion in missing_import.suggestions {
1260 let mut edits = vec![TextEdit {
1261 range,
1262 new_text: suggestion.name.to_string(),
1263 }];
1264 if let Some(import) = &suggestion.import {
1265 edits.push(get_import_edit(
1266 import_location,
1267 import,
1268 &after_import_newlines,
1269 ));
1270 }
1271
1272 let title = match &suggestion.import {
1273 Some(import) => &format!("Import `{import}`"),
1274 _ => &format!("Did you mean `{}`", suggestion.name),
1275 };
1276
1277 CodeActionBuilder::new(title)
1278 .kind(CodeActionKind::QuickFix)
1279 .changes(uri.clone(), edits)
1280 .preferred(true)
1281 .push_to(actions);
1282 }
1283 }
1284}
1285
1286pub fn code_action_generate_type(
1287 module: &Module,
1288 line_numbers: &LineNumbers,
1289 params: &CodeActionParams,
1290 error: &Option<Error>,
1291 actions: &mut Vec<CodeAction>,
1292) {
1293 let uri = ¶ms.text_document.uri;
1294 let Some(errors) = type_errors_for_module(error, module) else {
1295 return;
1296 };
1297
1298 for error in errors {
1299 let type_::Error::UnknownType {
1300 location,
1301 name,
1302 parameter_names,
1303 ..
1304 } = error
1305 else {
1306 continue;
1307 };
1308
1309 let range = src_span_to_lsp_range(*location, line_numbers);
1310 if !within(params.range, range) {
1311 continue;
1312 }
1313
1314 // Insert the new type stub before the top-level definition that
1315 // contains the error, so it appears close to where it is used.
1316 let (insert_at, is_public) =
1317 definition_start_and_publicity_containing(&module.ast.definitions, location.start)
1318 .unwrap_or((module.code.len() as u32, false));
1319 let insert_range = src_span_to_lsp_range(
1320 SrcSpan {
1321 start: insert_at,
1322 end: insert_at,
1323 },
1324 line_numbers,
1325 );
1326
1327 let pub_prefix = if is_public { "pub " } else { "" };
1328 let new_text = if parameter_names.is_empty() {
1329 format!("{pub_prefix}type {name}\n\n")
1330 } else {
1331 let parameters = parameter_names.join(", ");
1332 format!("{pub_prefix}type {name}({parameters})\n\n")
1333 };
1334
1335 let edit = TextEdit {
1336 range: insert_range,
1337 new_text,
1338 };
1339
1340 CodeActionBuilder::new("Generate type")
1341 .kind(CodeActionKind::QuickFix)
1342 .changes(uri.clone(), vec![edit])
1343 .preferred(true)
1344 .push_to(actions);
1345 }
1346}
1347
1348/// Returns the source offset and publicity of the top-level definition that
1349/// contains `position`, so the caller can insert code before it.
1350fn definition_start_and_publicity_containing(
1351 definitions: &TypedDefinitions,
1352 position: u32,
1353) -> Option<(u32, bool)> {
1354 let functions = definitions
1355 .functions
1356 .iter()
1357 .map(|function| (function.full_location(), function.publicity.is_public()));
1358 let custom_types = definitions.custom_types.iter().map(|custom_type| {
1359 (
1360 custom_type.full_location(),
1361 custom_type.publicity.is_public(),
1362 )
1363 });
1364 let type_aliases = definitions
1365 .type_aliases
1366 .iter()
1367 .map(|type_alias| (type_alias.location, type_alias.publicity.is_public()));
1368 let constants = definitions
1369 .constants
1370 .iter()
1371 .map(|constant| (constant.location, constant.publicity.is_public()));
1372
1373 functions
1374 .chain(custom_types)
1375 .chain(type_aliases)
1376 .chain(constants)
1377 .filter(|(span, _)| span.start <= position && position <= span.end)
1378 .map(|(span, is_public)| (span.start, is_public))
1379 .next()
1380}
1381
1382fn suggest_imports(
1383 location: SrcSpan,
1384 importable_modules: &[ModuleSuggestion],
1385) -> Option<MissingImport> {
1386 let suggestions = importable_modules
1387 .iter()
1388 .map(|suggestion| {
1389 let imported_name = suggestion.last_name_component();
1390 match suggestion {
1391 ModuleSuggestion::Importable(name) => ImportSuggestion {
1392 name: imported_name.into(),
1393 import: Some(name.clone()),
1394 },
1395 ModuleSuggestion::Imported(_) => ImportSuggestion {
1396 name: imported_name.into(),
1397 import: None,
1398 },
1399 }
1400 })
1401 .collect_vec();
1402
1403 if suggestions.is_empty() {
1404 None
1405 } else {
1406 Some(MissingImport {
1407 location,
1408 suggestions,
1409 })
1410 }
1411}
1412
1413pub fn code_action_add_missing_patterns(
1414 module: &Module,
1415 line_numbers: &LineNumbers,
1416 params: &CodeActionParams,
1417 error: &Option<Error>,
1418 actions: &mut Vec<CodeAction>,
1419) {
1420 let uri = ¶ms.text_document.uri;
1421 let Some(errors) = type_errors_for_module(error, module) else {
1422 return;
1423 };
1424 let missing_patterns = errors
1425 .iter()
1426 .filter_map(|error| {
1427 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error {
1428 Some((*location, missing))
1429 } else {
1430 None
1431 }
1432 })
1433 .collect_vec();
1434
1435 if missing_patterns.is_empty() {
1436 return;
1437 }
1438
1439 for (location, missing) in missing_patterns {
1440 let mut edits = TextEdits::new(line_numbers);
1441 let range = edits.src_span_to_lsp_range(location);
1442 if !within(params.range, range) {
1443 continue;
1444 }
1445
1446 let Some(Located::Expression {
1447 expression: TypedExpr::Case {
1448 clauses, subjects, ..
1449 },
1450 ..
1451 }) = module.find_node(location.start)
1452 else {
1453 continue;
1454 };
1455
1456 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1457
1458 let indent = " ".repeat(indent_size);
1459
1460 // Insert the missing patterns just after the final clause, or just before
1461 // the closing brace if there are no clauses
1462
1463 let insert_at = clauses
1464 .last()
1465 .map(|clause| clause.location.end)
1466 .unwrap_or(location.end - 1);
1467
1468 for pattern in missing {
1469 let new_text = format!("\n{indent} {pattern} -> todo");
1470 edits.insert(insert_at, new_text);
1471 }
1472
1473 // Add a newline + indent after the last pattern if there are no clauses
1474 //
1475 // This improves the generated code for this case:
1476 // ```gleam
1477 // case True {}
1478 // ```
1479 // This produces:
1480 // ```gleam
1481 // case True {
1482 // True -> todo
1483 // False -> todo
1484 // }
1485 // ```
1486 // Instead of:
1487 // ```gleam
1488 // case True {
1489 // True -> todo
1490 // False -> todo}
1491 // ```
1492 //
1493 if clauses.is_empty() {
1494 let last_subject_location = subjects
1495 .last()
1496 .expect("Case expressions have at least one subject")
1497 .location()
1498 .end;
1499
1500 // Find the opening brace of the case expression
1501 let chars = module.code[last_subject_location as usize..].chars();
1502 let mut start_brace_location = last_subject_location;
1503 for char in chars {
1504 start_brace_location += 1;
1505 if char == '{' {
1506 break;
1507 }
1508 }
1509
1510 // We remove any blank spaces/lines between the end of the last
1511 // comment inside the case expression and the insertion point.
1512 // If there's no comments we remove all empty space in between the
1513 // two braces
1514 let deletion_start = module
1515 .extra
1516 .last_comment_between(start_brace_location, insert_at)
1517 .map(|src_span| src_span.end)
1518 .unwrap_or(start_brace_location);
1519
1520 edits.delete(SrcSpan::new(deletion_start, insert_at));
1521 edits.insert(insert_at, format!("\n{indent}"));
1522 }
1523
1524 CodeActionBuilder::new("Add missing patterns")
1525 .kind(CodeActionKind::QuickFix)
1526 .changes(uri.clone(), edits.edits)
1527 .preferred(true)
1528 .push_to(actions);
1529 }
1530}
1531
1532/// Builder for code action to add annotations to an assignment or function
1533///
1534pub struct AddAnnotations<'a> {
1535 module: &'a Module,
1536 params: &'a CodeActionParams,
1537 edits: TextEdits<'a>,
1538 printer: Printer<'a>,
1539}
1540
1541impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1542 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1543 self.visit_typed_expr(&assignment.value);
1544
1545 // We only offer this code action between `let` and `=`, because
1546 // otherwise it could lead to confusing behaviour if inside a block
1547 // which is part of a let binding.
1548 let pattern_location = assignment.pattern.location();
1549 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1550 let code_action_range = self.edits.src_span_to_lsp_range(location);
1551
1552 // Only offer the code action if the cursor is over the statement
1553 if !overlaps(code_action_range, self.params.range) {
1554 return;
1555 }
1556
1557 // We don't need to add an annotation if there already is one
1558 if assignment.annotation.is_some() {
1559 return;
1560 }
1561
1562 // Various expressions such as pipelines and `use` expressions generate assignments
1563 // internally. However, these cannot be annotated and so we don't offer a code action here.
1564 if matches!(assignment.kind, AssignmentKind::Generated) {
1565 return;
1566 }
1567
1568 self.edits.insert(
1569 pattern_location.end,
1570 format!(": {}", self.printer.print_type(&assignment.type_())),
1571 );
1572 }
1573
1574 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1575 // Since type variable names are local to definitions, any type variables
1576 // in other parts of the module shouldn't affect what we print for the
1577 // annotations of this constant.
1578 self.printer.clear_type_variables();
1579
1580 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1581
1582 // Only offer the code action if the cursor is over the statement
1583 if !overlaps(code_action_range, self.params.range) {
1584 return;
1585 }
1586
1587 // We don't need to add an annotation if there already is one
1588 if constant.annotation.is_some() {
1589 return;
1590 }
1591
1592 self.edits.insert(
1593 constant.name_location.end,
1594 format!(": {}", self.printer.print_type(&constant.type_)),
1595 );
1596 }
1597
1598 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1599 // Since type variable names are local to definitions, any type variables
1600 // in other parts of the module shouldn't affect what we print for the
1601 // annotations of this functions. The only variables which cannot clash
1602 // are ones defined in the signature of this function, which we register
1603 // when we visit the parameters of this function inside `collect_type_variables`.
1604 self.printer.clear_type_variables();
1605 collect_type_variables(&mut self.printer, fun);
1606
1607 ast::visit::visit_typed_function(self, fun);
1608
1609 let code_action_range = self.edits.src_span_to_lsp_range(
1610 fun.body_start
1611 .map(|body_start| SrcSpan {
1612 start: fun.location.start,
1613 end: body_start,
1614 })
1615 .unwrap_or(fun.location),
1616 );
1617
1618 // Only offer the code action if the cursor is over the statement
1619 if !overlaps(code_action_range, self.params.range) {
1620 return;
1621 }
1622
1623 // Annotate each argument separately
1624 for argument in fun.arguments.iter() {
1625 // Don't annotate the argument if it's already annotated
1626 if argument.annotation.is_some() {
1627 continue;
1628 }
1629
1630 self.edits.insert(
1631 argument.location.end,
1632 format!(": {}", self.printer.print_type(&argument.type_)),
1633 );
1634 }
1635
1636 // Annotate the return type if it isn't already annotated
1637 if fun.return_annotation.is_none() {
1638 self.edits.insert(
1639 fun.location.end,
1640 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1641 );
1642 }
1643 }
1644
1645 fn visit_typed_expr_fn(
1646 &mut self,
1647 location: &'ast SrcSpan,
1648 type_: &'ast Arc<Type>,
1649 kind: &'ast FunctionLiteralKind,
1650 arguments: &'ast [TypedArg],
1651 body: &'ast Vec1<TypedStatement>,
1652 return_annotation: &'ast Option<ast::TypeAst>,
1653 ) {
1654 ast::visit::visit_typed_expr_fn(
1655 self,
1656 location,
1657 type_,
1658 kind,
1659 arguments,
1660 body,
1661 return_annotation,
1662 );
1663
1664 // If the function doesn't have a head, we can't annotate it
1665 let location = match kind {
1666 // Function captures don't need any type annotations
1667 FunctionLiteralKind::Capture { .. } => return,
1668 FunctionLiteralKind::Anonymous { head, .. } => head,
1669 FunctionLiteralKind::Use { location } => location,
1670 };
1671
1672 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1673
1674 // Only offer the code action if the cursor is over the expression
1675 if !overlaps(code_action_range, self.params.range) {
1676 return;
1677 }
1678
1679 // Annotate each argument separately
1680 for argument in arguments.iter() {
1681 // Don't annotate the argument if it's already annotated
1682 if argument.annotation.is_some() {
1683 continue;
1684 }
1685
1686 self.edits.insert(
1687 argument.location.end,
1688 format!(": {}", self.printer.print_type(&argument.type_)),
1689 );
1690 }
1691
1692 // Annotate the return type if it isn't already annotated, and this is
1693 // an anonymous function.
1694 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1695 let return_type = &type_.return_type().expect("Type must be a function");
1696 let pretty_type = self.printer.print_type(return_type);
1697 self.edits
1698 .insert(location.end, format!(" -> {pretty_type}"));
1699 }
1700 }
1701}
1702
1703impl<'a> AddAnnotations<'a> {
1704 pub fn new(
1705 module: &'a Module,
1706 line_numbers: &'a LineNumbers,
1707 params: &'a CodeActionParams,
1708 ) -> Self {
1709 Self {
1710 module,
1711 params,
1712 edits: TextEdits::new(line_numbers),
1713 // We need to use the same printer for all the edits because otherwise
1714 // we could get duplicate type variable names.
1715 printer: Printer::new_without_type_variables(&module.ast.names),
1716 }
1717 }
1718
1719 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1720 self.visit_typed_module(&self.module.ast);
1721
1722 let uri = &self.params.text_document.uri;
1723
1724 let title = match self.edits.edits.len() {
1725 // We don't offer a code action if there is no action to perform
1726 0 => return,
1727 1 => "Add type annotation",
1728 _ => "Add type annotations",
1729 };
1730
1731 CodeActionBuilder::new(title)
1732 .kind(CodeActionKind::Refactor)
1733 .changes(uri.clone(), self.edits.edits)
1734 .preferred(false)
1735 .push_to(actions);
1736 }
1737}
1738
1739/// Code action to add type annotations to all top level definitions
1740///
1741pub struct AnnotateTopLevelDefinitions<'a> {
1742 module: &'a Module,
1743 params: &'a CodeActionParams,
1744 edits: TextEdits<'a>,
1745 is_hovering_definition_requiring_annotations: bool,
1746}
1747
1748impl<'a> AnnotateTopLevelDefinitions<'a> {
1749 pub fn new(
1750 module: &'a Module,
1751 line_numbers: &'a LineNumbers,
1752 params: &'a CodeActionParams,
1753 ) -> Self {
1754 Self {
1755 module,
1756 params,
1757 edits: TextEdits::new(line_numbers),
1758 is_hovering_definition_requiring_annotations: false,
1759 }
1760 }
1761
1762 pub fn code_actions(mut self) -> Vec<CodeAction> {
1763 self.visit_typed_module(&self.module.ast);
1764
1765 // We only want to trigger the action if we're over one of the definition
1766 // which is lacking some annotations in the module
1767 if !self.is_hovering_definition_requiring_annotations {
1768 return vec![];
1769 }
1770
1771 let mut action = Vec::with_capacity(1);
1772 CodeActionBuilder::new("Annotate all top level definitions")
1773 .kind(CodeActionKind::RefactorRewrite)
1774 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1775 .preferred(false)
1776 .push_to(&mut action);
1777 action
1778 }
1779}
1780
1781impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> {
1782 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1783 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1784
1785 // We don't need to add an annotation if there already is one
1786 if constant.annotation.is_some() {
1787 return;
1788 }
1789
1790 // We're hovering definition which needs some annotations
1791 if overlaps(code_action_range, self.params.range) {
1792 self.is_hovering_definition_requiring_annotations = true;
1793 }
1794
1795 self.edits.insert(
1796 constant.name_location.end,
1797 format!(
1798 ": {}",
1799 // Create new printer to ignore type variables from other definitions
1800 Printer::new_without_type_variables(&self.module.ast.names)
1801 .print_type(&constant.type_)
1802 ),
1803 );
1804 }
1805
1806 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
1807 // Don't annotate already annotated arguments
1808 let arguments_to_annotate = fun
1809 .arguments
1810 .iter()
1811 .filter(|argument| argument.annotation.is_none())
1812 .collect::<Vec<_>>();
1813 let needs_return_annotation = fun.return_annotation.is_none();
1814
1815 if arguments_to_annotate.is_empty() && !needs_return_annotation {
1816 return;
1817 }
1818
1819 let code_action_range = self.edits.src_span_to_lsp_range(fun.location);
1820 if overlaps(code_action_range, self.params.range) {
1821 self.is_hovering_definition_requiring_annotations = true;
1822 }
1823
1824 // Create new printer to ignore type variables from other definitions
1825 let mut printer = Printer::new_without_type_variables(&self.module.ast.names);
1826 collect_type_variables(&mut printer, fun);
1827
1828 // Annotate each argument separately
1829 for argument in arguments_to_annotate {
1830 self.edits.insert(
1831 argument.location.end,
1832 format!(": {}", printer.print_type(&argument.type_)),
1833 );
1834 }
1835
1836 // Annotate the return type if it isn't already annotated
1837 if needs_return_annotation {
1838 self.edits.insert(
1839 fun.location.end,
1840 format!(" -> {}", printer.print_type(&fun.return_type)),
1841 );
1842 }
1843 }
1844}
1845
1846struct TypeVariableCollector<'a, 'b> {
1847 printer: &'a mut Printer<'b>,
1848}
1849
1850/// Collect type variables defined within a function and register them for a
1851/// `Printer`
1852fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) {
1853 TypeVariableCollector { printer }.visit_typed_function(function);
1854}
1855
1856impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> {
1857 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) {
1858 // Register this type variable so that we don't duplicate names when
1859 // adding annotations.
1860 self.printer.register_type_variable(name.clone());
1861 }
1862}
1863
1864pub struct QualifiedConstructor<'a> {
1865 import: &'a Import<EcoString>,
1866 used_name: EcoString,
1867 constructor: EcoString,
1868 layer: ast::Layer,
1869}
1870
1871impl QualifiedConstructor<'_> {
1872 fn constructor_import(&self) -> String {
1873 if self.layer.is_value() {
1874 self.constructor.to_string()
1875 } else {
1876 format!("type {}", self.constructor)
1877 }
1878 }
1879}
1880
1881pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1882 module: &'a Module,
1883 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1884 params: &'a CodeActionParams,
1885 line_numbers: &'a LineNumbers,
1886 qualified_constructor: Option<QualifiedConstructor<'a>>,
1887}
1888
1889impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1890 fn new(
1891 module: &'a Module,
1892 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1893 params: &'a CodeActionParams,
1894 line_numbers: &'a LineNumbers,
1895 ) -> Self {
1896 Self {
1897 module,
1898 compiler,
1899 params,
1900 line_numbers,
1901 qualified_constructor: None,
1902 }
1903 }
1904
1905 fn get_module_import(
1906 &self,
1907 module_name: &EcoString,
1908 constructor: &EcoString,
1909 layer: ast::Layer,
1910 ) -> Option<&'a Import<EcoString>> {
1911 let mut matching_import = None;
1912
1913 for import in &self.module.ast.definitions.imports {
1914 if import.used_name().as_deref() == Some(module_name)
1915 && let Some(module) = self.compiler.get_module_interface(&import.module)
1916 {
1917 // If the import is the one we're referring to, we see if the
1918 // referred module exports the type/value we are trying to
1919 // unqualify: we don't want to offer the action indiscriminately if
1920 // it would generate invalid code!
1921 let module_exports_constructor = match layer {
1922 ast::Layer::Value => module.get_importable_value(constructor).is_some(),
1923 ast::Layer::Type => module.get_importable_type(constructor).is_some(),
1924 };
1925 if module_exports_constructor {
1926 matching_import = Some(import);
1927 }
1928 } else {
1929 // If the import refers to another module we still want to check
1930 // if in its unqualified import list there is a name that's equal
1931 // to the one we're trying to unqualify. In this case we can't
1932 // offer the action as it would generate invalid code.
1933 //
1934 // For example:
1935 // ```gleam
1936 // import wibble.{Some}
1937 // import option
1938 //
1939 // pub fn something() {
1940 // option.Some(1)
1941 // ^^^^ We can't unqualify this because `Some` is already
1942 // imported unqualified from the `wibble` module
1943 // }
1944 // ```
1945 //
1946 let imported = match layer {
1947 ast::Layer::Value => &import.unqualified_values,
1948 ast::Layer::Type => &import.unqualified_types,
1949 };
1950 let constructor_already_imported_by_other_module = imported
1951 .iter()
1952 .any(|value| value.used_name() == constructor);
1953
1954 if constructor_already_imported_by_other_module {
1955 return None;
1956 }
1957 }
1958 }
1959
1960 matching_import
1961 }
1962}
1963
1964impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> {
1965 fn visit_type_ast_constructor(
1966 &mut self,
1967 location: &'ast SrcSpan,
1968 name: &'ast TypeAstConstructorName,
1969 arguments: &'ast [ast::TypeAst],
1970 arguments_types: Option<Vec<Arc<Type>>>,
1971 ) {
1972 let range = src_span_to_lsp_range(*location, self.line_numbers);
1973 if within(self.params.range, range)
1974 && let Some(module_alias) = name.module_name()
1975 && let Some(name) = name.name()
1976 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type)
1977 {
1978 self.qualified_constructor = Some(QualifiedConstructor {
1979 import,
1980 used_name: module_alias.clone(),
1981 constructor: name.clone(),
1982 layer: ast::Layer::Type,
1983 });
1984 }
1985 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
1986 }
1987
1988 fn visit_typed_expr_module_select(
1989 &mut self,
1990 location: &'ast SrcSpan,
1991 field_start: &'ast u32,
1992 type_: &'ast Arc<Type>,
1993 label: &'ast EcoString,
1994 module_name: &'ast EcoString,
1995 module_alias: &'ast EcoString,
1996 constructor: &'ast ModuleValueConstructor,
1997 ) {
1998 // When hovering over a Record Value Constructor, we want to expand the source span to
1999 // include the module name:
2000 // option.Some
2001 // ↑
2002 // This allows us to offer a code action when hovering over the module name.
2003 let range = src_span_to_lsp_range(*location, self.line_numbers);
2004 if within(self.params.range, range)
2005 && let ModuleValueConstructor::Record {
2006 name: constructor_name,
2007 ..
2008 } = constructor
2009 && let Some(import) =
2010 self.get_module_import(module_alias, constructor_name, ast::Layer::Value)
2011 {
2012 self.qualified_constructor = Some(QualifiedConstructor {
2013 import,
2014 used_name: module_alias.clone(),
2015 constructor: constructor_name.clone(),
2016 layer: ast::Layer::Value,
2017 });
2018 }
2019 ast::visit::visit_typed_expr_module_select(
2020 self,
2021 location,
2022 field_start,
2023 type_,
2024 label,
2025 module_name,
2026 module_alias,
2027 constructor,
2028 );
2029 }
2030
2031 fn visit_typed_pattern_constructor(
2032 &mut self,
2033 location: &'ast SrcSpan,
2034 name_location: &'ast SrcSpan,
2035 name: &'ast EcoString,
2036 arguments: &'ast Vec<CallArg<TypedPattern>>,
2037 module: &'ast Option<(EcoString, SrcSpan)>,
2038 constructor: &'ast Inferred<type_::PatternConstructor>,
2039 spread: &'ast Option<SrcSpan>,
2040 type_: &'ast Arc<Type>,
2041 ) {
2042 let range = src_span_to_lsp_range(*location, self.line_numbers);
2043 if within(self.params.range, range)
2044 && let Some((module_alias, _)) = module
2045 && let Inferred::Known(_) = constructor
2046 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2047 {
2048 self.qualified_constructor = Some(QualifiedConstructor {
2049 import,
2050 used_name: module_alias.clone(),
2051 constructor: name.clone(),
2052 layer: ast::Layer::Value,
2053 });
2054 }
2055 ast::visit::visit_typed_pattern_constructor(
2056 self,
2057 location,
2058 name_location,
2059 name,
2060 arguments,
2061 module,
2062 constructor,
2063 spread,
2064 type_,
2065 );
2066 }
2067
2068 fn visit_typed_constant_record(
2069 &mut self,
2070 location: &'ast SrcSpan,
2071 module: &'ast Option<(EcoString, SrcSpan)>,
2072 name: &'ast EcoString,
2073 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2074 type_: &'ast Arc<Type>,
2075 field_map: &'ast Inferred<FieldMap>,
2076 record_constructor: &'ast Option<Box<ValueConstructor>>,
2077 ) {
2078 let range = src_span_to_lsp_range(*location, self.line_numbers);
2079 if within(self.params.range, range)
2080 && let Some((module_alias, _)) = module
2081 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2082 {
2083 self.qualified_constructor = Some(QualifiedConstructor {
2084 import,
2085 used_name: module_alias.clone(),
2086 constructor: name.clone(),
2087 layer: ast::Layer::Value,
2088 });
2089 }
2090 ast::visit::visit_typed_constant_record(
2091 self,
2092 location,
2093 module,
2094 name,
2095 arguments,
2096 type_,
2097 field_map,
2098 record_constructor,
2099 );
2100 }
2101
2102 fn visit_typed_constant_var(
2103 &mut self,
2104 location: &'ast SrcSpan,
2105 module: &'ast Option<(EcoString, SrcSpan)>,
2106 name: &'ast EcoString,
2107 constructor: &'ast Option<Box<ValueConstructor>>,
2108 type_: &'ast Arc<Type>,
2109 ) {
2110 let range = src_span_to_lsp_range(*location, self.line_numbers);
2111 if within(self.params.range, range)
2112 && let Some((module_alias, _)) = module
2113 && let Some(constructor) = constructor
2114 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant
2115 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
2116 {
2117 self.qualified_constructor = Some(QualifiedConstructor {
2118 import,
2119 used_name: module_alias.clone(),
2120 constructor: name.clone(),
2121 layer: ast::Layer::Value,
2122 });
2123 }
2124 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2125 }
2126}
2127
2128pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
2129 module: &'a Module,
2130 params: &'a CodeActionParams,
2131 edits: TextEdits<'a>,
2132 qualified_constructor: QualifiedConstructor<'a>,
2133}
2134
2135impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
2136 pub fn new(
2137 module: &'a Module,
2138 params: &'a CodeActionParams,
2139 line_numbers: &'a LineNumbers,
2140 qualified_constructor: QualifiedConstructor<'a>,
2141 ) -> Self {
2142 Self {
2143 module,
2144 params,
2145 edits: TextEdits::new(line_numbers),
2146 qualified_constructor,
2147 }
2148 }
2149
2150 pub fn code_actions(mut self) -> Vec<CodeAction> {
2151 self.visit_typed_module(&self.module.ast);
2152 if self.edits.edits.is_empty() {
2153 return vec![];
2154 }
2155 self.edit_import();
2156 let mut action = Vec::with_capacity(1);
2157 CodeActionBuilder::new(&format!(
2158 "Unqualify {}.{}",
2159 self.qualified_constructor.used_name, self.qualified_constructor.constructor
2160 ))
2161 .kind(CodeActionKind::Refactor)
2162 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2163 .preferred(false)
2164 .push_to(&mut action);
2165 action
2166 }
2167
2168 fn remove_module_qualifier(&mut self, location: SrcSpan) {
2169 self.edits.delete(SrcSpan {
2170 start: location.start,
2171 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
2172 });
2173 }
2174
2175 fn edit_import(&mut self) {
2176 let QualifiedConstructor {
2177 constructor,
2178 layer,
2179 import,
2180 ..
2181 } = &self.qualified_constructor;
2182 let is_imported = if layer.is_value() {
2183 import
2184 .unqualified_values
2185 .iter()
2186 .any(|value| value.used_name() == constructor)
2187 } else {
2188 import
2189 .unqualified_types
2190 .iter()
2191 .any(|type_| type_.used_name() == constructor)
2192 };
2193 if is_imported {
2194 return;
2195 }
2196 let (insert_pos, new_text) = edits::insert_unqualified_import(
2197 import,
2198 &self.module.code,
2199 self.qualified_constructor.constructor_import(),
2200 );
2201 let span = SrcSpan::new(insert_pos, insert_pos);
2202 self.edits.replace(span, new_text);
2203 }
2204}
2205
2206impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
2207 fn visit_type_ast_constructor(
2208 &mut self,
2209 location: &'ast SrcSpan,
2210 name: &'ast TypeAstConstructorName,
2211 arguments: &'ast [ast::TypeAst],
2212 arguments_types: Option<Vec<Arc<Type>>>,
2213 ) {
2214 if let Some(module_name) = name.module_name()
2215 && let Some(name) = name.name()
2216 {
2217 let QualifiedConstructor {
2218 used_name,
2219 constructor,
2220 layer,
2221 ..
2222 } = &self.qualified_constructor;
2223
2224 if !layer.is_value() && used_name == module_name && name == constructor {
2225 self.remove_module_qualifier(*location);
2226 }
2227 }
2228 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2229 }
2230
2231 fn visit_typed_expr_module_select(
2232 &mut self,
2233 location: &'ast SrcSpan,
2234 field_start: &'ast u32,
2235 type_: &'ast Arc<Type>,
2236 label: &'ast EcoString,
2237 module_name: &'ast EcoString,
2238 module_alias: &'ast EcoString,
2239 constructor: &'ast ModuleValueConstructor,
2240 ) {
2241 if let ModuleValueConstructor::Record { name, .. } = constructor {
2242 let QualifiedConstructor {
2243 used_name,
2244 constructor,
2245 layer,
2246 ..
2247 } = &self.qualified_constructor;
2248
2249 if layer.is_value() && used_name == module_alias && name == constructor {
2250 self.remove_module_qualifier(*location);
2251 }
2252 }
2253 ast::visit::visit_typed_expr_module_select(
2254 self,
2255 location,
2256 field_start,
2257 type_,
2258 label,
2259 module_name,
2260 module_alias,
2261 constructor,
2262 );
2263 }
2264
2265 fn visit_typed_pattern_constructor(
2266 &mut self,
2267 location: &'ast SrcSpan,
2268 name_location: &'ast SrcSpan,
2269 name: &'ast EcoString,
2270 arguments: &'ast Vec<CallArg<TypedPattern>>,
2271 module: &'ast Option<(EcoString, SrcSpan)>,
2272 constructor: &'ast Inferred<type_::PatternConstructor>,
2273 spread: &'ast Option<SrcSpan>,
2274 type_: &'ast Arc<Type>,
2275 ) {
2276 if let Some((module_alias, _)) = module
2277 && let Inferred::Known(_) = constructor
2278 {
2279 let QualifiedConstructor {
2280 used_name,
2281 constructor,
2282 layer,
2283 ..
2284 } = &self.qualified_constructor;
2285
2286 if layer.is_value() && used_name == module_alias && name == constructor {
2287 self.remove_module_qualifier(*location);
2288 }
2289 }
2290 ast::visit::visit_typed_pattern_constructor(
2291 self,
2292 location,
2293 name_location,
2294 name,
2295 arguments,
2296 module,
2297 constructor,
2298 spread,
2299 type_,
2300 );
2301 }
2302
2303 fn visit_typed_constant_record(
2304 &mut self,
2305 location: &'ast SrcSpan,
2306 module: &'ast Option<(EcoString, SrcSpan)>,
2307 name: &'ast EcoString,
2308 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2309 type_: &'ast Arc<Type>,
2310 field_map: &'ast Inferred<FieldMap>,
2311 record_constructor: &'ast Option<Box<ValueConstructor>>,
2312 ) {
2313 if let Some((module_alias, _)) = module {
2314 let QualifiedConstructor {
2315 used_name,
2316 constructor,
2317 layer,
2318 ..
2319 } = &self.qualified_constructor;
2320
2321 if layer.is_value() && used_name == module_alias && name == constructor {
2322 self.remove_module_qualifier(*location);
2323 }
2324 }
2325 ast::visit::visit_typed_constant_record(
2326 self,
2327 location,
2328 module,
2329 name,
2330 arguments,
2331 type_,
2332 field_map,
2333 record_constructor,
2334 );
2335 }
2336
2337 fn visit_typed_constant_var(
2338 &mut self,
2339 location: &'ast SrcSpan,
2340 module: &'ast Option<(EcoString, SrcSpan)>,
2341 name: &'ast EcoString,
2342 constructor: &'ast Option<Box<ValueConstructor>>,
2343 type_: &'ast Arc<Type>,
2344 ) {
2345 if let Some((module_alias, _)) = module {
2346 let QualifiedConstructor {
2347 used_name,
2348 constructor: wanted_constructor,
2349 layer,
2350 ..
2351 } = &self.qualified_constructor;
2352
2353 if layer.is_value() && used_name == module_alias && name == wanted_constructor {
2354 self.remove_module_qualifier(*location);
2355 }
2356 }
2357 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2358 }
2359}
2360
2361pub fn code_action_convert_qualified_constructor_to_unqualified<IO>(
2362 module: &Module,
2363 compiler: &LspProjectCompiler<FileSystemProxy<IO>>,
2364 line_numbers: &LineNumbers,
2365 params: &CodeActionParams,
2366 actions: &mut Vec<CodeAction>,
2367) {
2368 let mut first_pass =
2369 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers);
2370 first_pass.visit_typed_module(&module.ast);
2371 let Some(qualified_constructor) = first_pass.qualified_constructor else {
2372 return;
2373 };
2374 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
2375 module,
2376 params,
2377 line_numbers,
2378 qualified_constructor,
2379 );
2380 let new_actions = second_pass.code_actions();
2381 actions.extend(new_actions);
2382}
2383
2384struct UnqualifiedConstructor<'a> {
2385 module_name: EcoString,
2386 constructor: &'a ast::UnqualifiedImport,
2387 layer: ast::Layer,
2388}
2389
2390struct UnqualifiedToQualifiedImportFirstPass<'a> {
2391 module: &'a Module,
2392 params: &'a CodeActionParams,
2393 line_numbers: &'a LineNumbers,
2394 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
2395}
2396
2397impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
2398 fn new(
2399 module: &'a Module,
2400 params: &'a CodeActionParams,
2401 line_numbers: &'a LineNumbers,
2402 ) -> Self {
2403 Self {
2404 module,
2405 params,
2406 line_numbers,
2407 unqualified_constructor: None,
2408 }
2409 }
2410
2411 fn get_module_import_from_value_constructor(
2412 &mut self,
2413 module_name: &EcoString,
2414 constructor_name: &EcoString,
2415 ) {
2416 self.unqualified_constructor = self
2417 .module
2418 .ast
2419 .definitions
2420 .imports
2421 .iter()
2422 .filter(|import| import.module == *module_name)
2423 .find_map(|import| {
2424 import
2425 .unqualified_values
2426 .iter()
2427 .find(|value| value.used_name() == constructor_name)
2428 .and_then(|value| {
2429 Some(UnqualifiedConstructor {
2430 constructor: value,
2431 module_name: import.used_name()?,
2432 layer: ast::Layer::Value,
2433 })
2434 })
2435 });
2436 }
2437
2438 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
2439 self.unqualified_constructor =
2440 self.module
2441 .ast
2442 .definitions
2443 .imports
2444 .iter()
2445 .find_map(|import| {
2446 if let Some(ty) = import
2447 .unqualified_types
2448 .iter()
2449 .find(|ty| ty.used_name() == constructor_name)
2450 {
2451 return Some(UnqualifiedConstructor {
2452 constructor: ty,
2453 module_name: import.used_name()?,
2454 layer: ast::Layer::Type,
2455 });
2456 }
2457 None
2458 });
2459 }
2460}
2461
2462impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
2463 fn visit_type_ast_constructor(
2464 &mut self,
2465 location: &'ast SrcSpan,
2466 name: &'ast TypeAstConstructorName,
2467 arguments: &'ast [ast::TypeAst],
2468 arguments_types: Option<Vec<Arc<Type>>>,
2469 ) {
2470 if !name.is_qualified()
2471 && let Some(name) = name.name()
2472 && within(
2473 self.params.range,
2474 src_span_to_lsp_range(*location, self.line_numbers),
2475 )
2476 {
2477 self.get_module_import_from_type_constructor(name);
2478 }
2479
2480 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2481 }
2482
2483 fn visit_typed_expr_var(
2484 &mut self,
2485 location: &'ast SrcSpan,
2486 constructor: &'ast ValueConstructor,
2487 name: &'ast EcoString,
2488 ) {
2489 let range = src_span_to_lsp_range(*location, self.line_numbers);
2490 if within(self.params.range, range)
2491 && let Some(module_name) = match &constructor.variant {
2492 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2493 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2494 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2495
2496 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2497 }
2498 {
2499 self.get_module_import_from_value_constructor(module_name, name);
2500 }
2501 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2502 }
2503
2504 fn visit_typed_pattern_constructor(
2505 &mut self,
2506 location: &'ast SrcSpan,
2507 name_location: &'ast SrcSpan,
2508 name: &'ast EcoString,
2509 arguments: &'ast Vec<CallArg<TypedPattern>>,
2510 module: &'ast Option<(EcoString, SrcSpan)>,
2511 constructor: &'ast Inferred<type_::PatternConstructor>,
2512 spread: &'ast Option<SrcSpan>,
2513 type_: &'ast Arc<Type>,
2514 ) {
2515 if module.is_none()
2516 && within(
2517 self.params.range,
2518 src_span_to_lsp_range(*location, self.line_numbers),
2519 )
2520 && let Inferred::Known(constructor) = constructor
2521 {
2522 self.get_module_import_from_value_constructor(&constructor.module, name);
2523 }
2524
2525 ast::visit::visit_typed_pattern_constructor(
2526 self,
2527 location,
2528 name_location,
2529 name,
2530 arguments,
2531 module,
2532 constructor,
2533 spread,
2534 type_,
2535 );
2536 }
2537
2538 fn visit_typed_constant_record(
2539 &mut self,
2540 location: &'ast SrcSpan,
2541 module: &'ast Option<(EcoString, SrcSpan)>,
2542 name: &'ast EcoString,
2543 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2544 type_: &'ast Arc<Type>,
2545 field_map: &'ast Inferred<FieldMap>,
2546 record_constructor: &'ast Option<Box<ValueConstructor>>,
2547 ) {
2548 if module.is_none()
2549 && within(
2550 self.params.range,
2551 src_span_to_lsp_range(*location, self.line_numbers),
2552 )
2553 && let Some(record_constructor) = record_constructor
2554 && let Some(module_name) = match &record_constructor.variant {
2555 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2556 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2557 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2558
2559 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2560 }
2561 {
2562 self.get_module_import_from_value_constructor(module_name, name);
2563 }
2564 ast::visit::visit_typed_constant_record(
2565 self,
2566 location,
2567 module,
2568 name,
2569 arguments,
2570 type_,
2571 field_map,
2572 record_constructor,
2573 );
2574 }
2575
2576 fn visit_typed_constant_var(
2577 &mut self,
2578 location: &'ast SrcSpan,
2579 module: &'ast Option<(EcoString, SrcSpan)>,
2580 name: &'ast EcoString,
2581 constructor: &'ast Option<Box<ValueConstructor>>,
2582 type_: &'ast Arc<Type>,
2583 ) {
2584 if module.is_none()
2585 && within(
2586 self.params.range,
2587 src_span_to_lsp_range(*location, self.line_numbers),
2588 )
2589 && let Some(constructor) = constructor
2590 && let Some(module_name) = match &constructor.variant {
2591 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2592 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2593 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2594
2595 type_::ValueConstructorVariant::LocalVariable { .. } => None,
2596 }
2597 {
2598 self.get_module_import_from_value_constructor(module_name, name);
2599 }
2600 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2601 }
2602}
2603
2604struct UnqualifiedToQualifiedImportSecondPass<'a> {
2605 module: &'a Module,
2606 params: &'a CodeActionParams,
2607 edits: TextEdits<'a>,
2608 unqualified_constructor: UnqualifiedConstructor<'a>,
2609}
2610
2611impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2612 pub fn new(
2613 module: &'a Module,
2614 params: &'a CodeActionParams,
2615 line_numbers: &'a LineNumbers,
2616 unqualified_constructor: UnqualifiedConstructor<'a>,
2617 ) -> Self {
2618 Self {
2619 module,
2620 params,
2621 edits: TextEdits::new(line_numbers),
2622 unqualified_constructor,
2623 }
2624 }
2625
2626 fn add_module_qualifier(&mut self, location: SrcSpan) {
2627 let src_span = SrcSpan::new(
2628 location.start,
2629 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
2630 );
2631
2632 self.edits.replace(
2633 src_span,
2634 format!(
2635 "{}.{}",
2636 self.unqualified_constructor.module_name,
2637 self.unqualified_constructor.constructor.name
2638 ),
2639 );
2640 }
2641
2642 pub fn code_actions(mut self) -> Vec<CodeAction> {
2643 self.visit_typed_module(&self.module.ast);
2644 if self.edits.edits.is_empty() {
2645 return vec![];
2646 }
2647 self.edit_import();
2648 let mut action = Vec::with_capacity(1);
2649 let UnqualifiedConstructor {
2650 module_name,
2651 constructor,
2652 ..
2653 } = self.unqualified_constructor;
2654 CodeActionBuilder::new(&format!(
2655 "Qualify {} as {}.{}",
2656 constructor.used_name(),
2657 module_name,
2658 constructor.name,
2659 ))
2660 .kind(CodeActionKind::Refactor)
2661 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2662 .preferred(false)
2663 .push_to(&mut action);
2664 action
2665 }
2666
2667 fn edit_import(&mut self) {
2668 let UnqualifiedConstructor {
2669 constructor:
2670 ast::UnqualifiedImport {
2671 location: constructor_import_span,
2672 ..
2673 },
2674 ..
2675 } = self.unqualified_constructor;
2676
2677 let mut last_char_pos = constructor_import_span.end as usize;
2678 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2679 last_char_pos += 1;
2680 }
2681 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2682 last_char_pos += 1;
2683 }
2684 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2685 last_char_pos += 1;
2686 }
2687
2688 self.edits.delete(SrcSpan::new(
2689 constructor_import_span.start,
2690 last_char_pos as u32,
2691 ));
2692 }
2693}
2694
2695impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2696 fn visit_type_ast_constructor(
2697 &mut self,
2698 location: &'ast SrcSpan,
2699 name: &'ast TypeAstConstructorName,
2700 arguments: &'ast [ast::TypeAst],
2701 arguments_types: Option<Vec<Arc<Type>>>,
2702 ) {
2703 if !name.is_qualified()
2704 && let Some(name) = name.name()
2705 {
2706 let UnqualifiedConstructor {
2707 constructor, layer, ..
2708 } = &self.unqualified_constructor;
2709 if !layer.is_value() && constructor.used_name() == name {
2710 self.add_module_qualifier(*location);
2711 }
2712 }
2713 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types);
2714 }
2715
2716 fn visit_typed_expr_var(
2717 &mut self,
2718 location: &'ast SrcSpan,
2719 constructor: &'ast ValueConstructor,
2720 name: &'ast EcoString,
2721 ) {
2722 let UnqualifiedConstructor {
2723 constructor: wanted_constructor,
2724 layer,
2725 ..
2726 } = &self.unqualified_constructor;
2727
2728 if layer.is_value()
2729 && wanted_constructor.used_name() == name
2730 && !constructor.is_local_variable()
2731 {
2732 self.add_module_qualifier(*location);
2733 }
2734 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2735 }
2736
2737 fn visit_typed_pattern_constructor(
2738 &mut self,
2739 location: &'ast SrcSpan,
2740 name_location: &'ast SrcSpan,
2741 name: &'ast EcoString,
2742 arguments: &'ast Vec<CallArg<TypedPattern>>,
2743 module: &'ast Option<(EcoString, SrcSpan)>,
2744 constructor: &'ast Inferred<type_::PatternConstructor>,
2745 spread: &'ast Option<SrcSpan>,
2746 type_: &'ast Arc<Type>,
2747 ) {
2748 if module.is_none() {
2749 let UnqualifiedConstructor {
2750 constructor: wanted_constructor,
2751 layer,
2752 ..
2753 } = &self.unqualified_constructor;
2754 if layer.is_value() && wanted_constructor.used_name() == name {
2755 self.add_module_qualifier(*location);
2756 }
2757 }
2758 ast::visit::visit_typed_pattern_constructor(
2759 self,
2760 location,
2761 name_location,
2762 name,
2763 arguments,
2764 module,
2765 constructor,
2766 spread,
2767 type_,
2768 );
2769 }
2770
2771 fn visit_typed_constant_record(
2772 &mut self,
2773 location: &'ast SrcSpan,
2774 module: &'ast Option<(EcoString, SrcSpan)>,
2775 name: &'ast EcoString,
2776 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>,
2777 type_: &'ast Arc<Type>,
2778 field_map: &'ast Inferred<FieldMap>,
2779 record_constructor: &'ast Option<Box<ValueConstructor>>,
2780 ) {
2781 if module.is_none() {
2782 let UnqualifiedConstructor {
2783 constructor: wanted_constructor,
2784 layer,
2785 ..
2786 } = &self.unqualified_constructor;
2787 if layer.is_value() && wanted_constructor.used_name() == name {
2788 self.add_module_qualifier(*location);
2789 }
2790 }
2791 ast::visit::visit_typed_constant_record(
2792 self,
2793 location,
2794 module,
2795 name,
2796 arguments,
2797 type_,
2798 field_map,
2799 record_constructor,
2800 );
2801 }
2802
2803 fn visit_typed_constant_var(
2804 &mut self,
2805 location: &'ast SrcSpan,
2806 module: &'ast Option<(EcoString, SrcSpan)>,
2807 name: &'ast EcoString,
2808 constructor: &'ast Option<Box<ValueConstructor>>,
2809 type_: &'ast Arc<Type>,
2810 ) {
2811 if module.is_none() {
2812 let UnqualifiedConstructor {
2813 constructor: wanted_constructor,
2814 layer,
2815 ..
2816 } = &self.unqualified_constructor;
2817 if layer.is_value() && wanted_constructor.used_name() == name {
2818 self.add_module_qualifier(*location);
2819 }
2820 }
2821 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_);
2822 }
2823}
2824
2825pub fn code_action_convert_unqualified_constructor_to_qualified(
2826 module: &Module,
2827 line_numbers: &LineNumbers,
2828 params: &CodeActionParams,
2829 actions: &mut Vec<CodeAction>,
2830) {
2831 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2832 first_pass.visit_typed_module(&module.ast);
2833 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2834 return;
2835 };
2836 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2837 module,
2838 params,
2839 line_numbers,
2840 unqualified_constructor,
2841 );
2842 let new_actions = second_pass.code_actions();
2843 actions.extend(new_actions);
2844}
2845
2846/// Builder for code action to apply the convert from use action, turning a use
2847/// expression into a regular function call.
2848///
2849pub struct ConvertFromUse<'a> {
2850 module: &'a Module,
2851 params: &'a CodeActionParams,
2852 edits: TextEdits<'a>,
2853 selected_use: Option<&'a TypedUse>,
2854}
2855
2856impl<'a> ConvertFromUse<'a> {
2857 pub fn new(
2858 module: &'a Module,
2859 line_numbers: &'a LineNumbers,
2860 params: &'a CodeActionParams,
2861 ) -> Self {
2862 Self {
2863 module,
2864 params,
2865 edits: TextEdits::new(line_numbers),
2866 selected_use: None,
2867 }
2868 }
2869
2870 pub fn code_actions(mut self) -> Vec<CodeAction> {
2871 self.visit_typed_module(&self.module.ast);
2872
2873 let Some(use_) = self.selected_use else {
2874 return vec![];
2875 };
2876
2877 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else {
2878 return vec![];
2879 };
2880
2881 // If the use callback we're desugaring is using labels, that means we
2882 // have to add the last argument's label when writing the callback;
2883 // otherwise, it would result in invalid code.
2884 //
2885 // use acc, item <- list.fold(over: list, from: 1)
2886 // todo
2887 //
2888 // Needs to be rewritten as:
2889 //
2890 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2891 // ^^^^^ We cannot forget to add this label back!
2892 //
2893 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) {
2894 fun.field_map()
2895 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned())
2896 .map(|label| eco_format!("{label}: "))
2897 .unwrap_or(EcoString::from(""))
2898 } else {
2899 EcoString::from("")
2900 };
2901
2902 // The use callback is not necessarily the last argument. If you have
2903 // the following function: `wibble(a a, b b) { todo }`
2904 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2905 // is going to be the use callback, not the last one!
2906 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback());
2907 let Some(CallArg {
2908 implicit: Some(ImplicitCallArgOrigin::Use),
2909 value: TypedExpr::Fn { body, type_, .. },
2910 ..
2911 }) = use_callback
2912 else {
2913 return vec![];
2914 };
2915
2916 // If there's arguments on the left hand side of the function we extract
2917 // those so we can paste them back as the anonymous function arguments.
2918 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2919 let assignments_range =
2920 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2921 self.module
2922 .code
2923 .get(assignments_range)
2924 .expect("use assignments")
2925 } else {
2926 ""
2927 };
2928
2929 // We first delete everything on the left hand side of use and the use
2930 // arrow.
2931 self.edits.delete(SrcSpan {
2932 start: use_.location.start,
2933 end: use_.right_hand_side_location.start,
2934 });
2935
2936 let use_line_end = use_.right_hand_side_location.end;
2937 let use_rhs_function_has_some_explicit_arguments = arguments
2938 .iter()
2939 .filter(|argument| !argument.is_use_implicit_callback())
2940 .peekable()
2941 .peek()
2942 .is_some();
2943
2944 let use_rhs_function_ends_with_closed_parentheses = self
2945 .module
2946 .code
2947 .get(use_line_end as usize - 1..use_line_end as usize)
2948 == Some(")");
2949
2950 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit());
2951 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2952
2953 // This is the piece of code between the end of the last argument and
2954 // the end of the use_expression:
2955 //
2956 // use <- wibble(a, b, )
2957 // ^^^^^ This piece right here, from `,` included
2958 // up to `)` excluded.
2959 //
2960 let text_after_last_argument = self
2961 .module
2962 .code
2963 .get(last_arg_end as usize..use_line_end as usize - 1);
2964 let use_rhs_has_comma_after_last_argument =
2965 text_after_last_argument.is_some_and(|code| code.contains(','));
2966 let needs_space_before_callback =
2967 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2968
2969 if use_rhs_function_ends_with_closed_parentheses {
2970 // If the function on the right hand side of use ends with a closed
2971 // parentheses then we have to remove it and add it later at the end
2972 // of the anonymous function we're inserting.
2973 //
2974 // use <- wibble()
2975 // ^ To add the fn() we need to first remove this
2976 //
2977 // So here we write over the last closed parentheses to remove it.
2978 let callback_start = format!("{callback_label}fn({assignments}) {{");
2979 self.edits.replace(
2980 SrcSpan {
2981 start: use_line_end - 1,
2982 end: use_line_end,
2983 },
2984 // If the function on the rhs of use has other orguments besides
2985 // the implicit fn expression then we need to put a comma after
2986 // the last argument.
2987 if use_rhs_function_has_some_explicit_arguments
2988 && !use_rhs_has_comma_after_last_argument
2989 {
2990 format!(", {callback_start}")
2991 } else if needs_space_before_callback {
2992 format!(" {callback_start}")
2993 } else {
2994 callback_start
2995 },
2996 );
2997 } else {
2998 // On the other hand, if the function on the right hand side doesn't
2999 // end with a closed parenthese then we have to manually add it.
3000 //
3001 // use <- wibble
3002 // ^ No parentheses
3003 //
3004 self.edits
3005 .insert(use_line_end, format!("(fn({assignments}) {{"));
3006 }
3007
3008 // Then we have to increase indentation for all the lines of the use
3009 // body.
3010 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
3011 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
3012
3013 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
3014 self.edits.edits.push(TextEdit {
3015 range: Range {
3016 start: Position { line, character: 0 },
3017 end: Position { line, character: 0 },
3018 },
3019 new_text: " ".to_string(),
3020 });
3021 }
3022
3023 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
3024 self.edits.insert(
3025 use_.call.location().end,
3026 format!("\n{final_line_indentation}}})"),
3027 );
3028
3029 let mut action = Vec::with_capacity(1);
3030 CodeActionBuilder::new("Convert from `use`")
3031 .kind(CodeActionKind::RefactorRewrite)
3032 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3033 .preferred(false)
3034 .push_to(&mut action);
3035 action
3036 }
3037}
3038
3039impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
3040 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3041 let use_range = self.edits.src_span_to_lsp_range(use_.location);
3042
3043 // If the use expression is using patterns that are not just variable
3044 // assignments then we can't automatically rewrite it as it would result
3045 // in a syntax error as we can't pattern match in an anonymous function
3046 // head.
3047 // At the same time we can't safely add bindings inside the anonymous
3048 // function body by picking placeholder names as we'd risk shadowing
3049 // variables coming from the outer scope.
3050 // So we just skip those use expressions we can't safely rewrite!
3051 if within(self.params.range, use_range)
3052 && use_
3053 .assignments
3054 .iter()
3055 .all(|assignment| assignment.pattern.is_variable())
3056 {
3057 self.selected_use = Some(use_);
3058 }
3059
3060 // We still want to visit the use expression so that we always end up
3061 // picking the innermost, most relevant use under the cursor.
3062 self.visit_typed_expr(&use_.call);
3063 }
3064}
3065
3066/// Builder for code action to apply the convert to use action.
3067///
3068pub struct ConvertToUse<'a> {
3069 module: &'a Module,
3070 params: &'a CodeActionParams,
3071 edits: TextEdits<'a>,
3072 selected_call: Option<CallLocations>,
3073}
3074
3075/// All the locations we'll need to transform a function call into a use
3076/// expression.
3077///
3078struct CallLocations {
3079 call_span: SrcSpan,
3080 called_function_span: SrcSpan,
3081 callback_arguments_span: Option<SrcSpan>,
3082 arg_before_callback_span: Option<SrcSpan>,
3083 callback_body_span: SrcSpan,
3084}
3085
3086impl<'a> ConvertToUse<'a> {
3087 pub fn new(
3088 module: &'a Module,
3089 line_numbers: &'a LineNumbers,
3090 params: &'a CodeActionParams,
3091 ) -> Self {
3092 Self {
3093 module,
3094 params,
3095 edits: TextEdits::new(line_numbers),
3096 selected_call: None,
3097 }
3098 }
3099
3100 pub fn code_actions(mut self) -> Vec<CodeAction> {
3101 self.visit_typed_module(&self.module.ast);
3102
3103 let Some(CallLocations {
3104 call_span,
3105 called_function_span,
3106 callback_arguments_span,
3107 arg_before_callback_span,
3108 callback_body_span,
3109 }) = self.selected_call
3110 else {
3111 return vec![];
3112 };
3113
3114 // This is the nesting level of the `use` keyword we've inserted, we
3115 // want to move the entire body of the anonymous function to this level.
3116 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
3117 let indentation = " ".repeat(use_nesting_level as usize);
3118
3119 // First we move the callback arguments to the left hand side of the
3120 // call and add the `use` keyword.
3121 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span {
3122 let arguments_start = arguments_location.start as usize;
3123 let arguments_end = arguments_location.end as usize;
3124 let arguments_text = self
3125 .module
3126 .code
3127 .get(arguments_start..arguments_end)
3128 .expect("fn args");
3129 format!("use {arguments_text} <- ")
3130 } else {
3131 "use <- ".into()
3132 };
3133
3134 self.edits.insert(call_span.start, left_hand_side_text);
3135
3136 match arg_before_callback_span {
3137 // If the function call has no other arguments besides the callback then
3138 // we just have to remove the `fn(...) {` part.
3139 //
3140 // wibble(fn(...) { ... })
3141 // ^^^^^^^^^^ This goes from the end of the called function
3142 // To the start of the first thing in the anonymous
3143 // function's body.
3144 //
3145 None => self.edits.replace(
3146 SrcSpan::new(called_function_span.end, callback_body_span.start),
3147 format!("\n{indentation}"),
3148 ),
3149 // If it has other arguments we'll have to remove those and add a closed
3150 // parentheses too:
3151 //
3152 // wibble(1, 2, fn(...) { ... })
3153 // ^^^^^^^^^^^ We have to replace this with a `)`, it
3154 // goes from the end of the second-to-last
3155 // argument to the start of the first thing
3156 // in the anonymous function's body.
3157 //
3158 Some(arg_before_callback) => self.edits.replace(
3159 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
3160 format!(")\n{indentation}"),
3161 ),
3162 }
3163
3164 // Then we have to remove two spaces of indentation from each line of
3165 // the callback function's body.
3166 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
3167 for line in body_range.start.line + 1..=body_range.end.line {
3168 self.edits.delete_range(Range::new(
3169 Position { line, character: 0 },
3170 Position { line, character: 2 },
3171 ));
3172 }
3173
3174 // Then we have to remove the anonymous fn closing `}` and the call's
3175 // closing `)`.
3176 self.edits
3177 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
3178
3179 let mut action = Vec::with_capacity(1);
3180 CodeActionBuilder::new("Convert to `use`")
3181 .kind(CodeActionKind::RefactorRewrite)
3182 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3183 .preferred(false)
3184 .push_to(&mut action);
3185 action
3186 }
3187}
3188
3189impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
3190 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3191 // The cursor has to be inside the last statement of the function to
3192 // offer the code action.
3193 if let Some(last) = &fun.body.last()
3194 && within(
3195 self.params.range,
3196 self.edits.src_span_to_lsp_range(last.location()),
3197 )
3198 && let Some(call_data) = turn_statement_into_use(last)
3199 {
3200 self.selected_call = Some(call_data);
3201 }
3202
3203 ast::visit::visit_typed_function(self, fun);
3204 }
3205
3206 fn visit_typed_expr_fn(
3207 &mut self,
3208 location: &'ast SrcSpan,
3209 type_: &'ast Arc<Type>,
3210 kind: &'ast FunctionLiteralKind,
3211 arguments: &'ast [TypedArg],
3212 body: &'ast Vec1<TypedStatement>,
3213 return_annotation: &'ast Option<ast::TypeAst>,
3214 ) {
3215 // The cursor has to be inside the last statement of the body to
3216 // offer the code action.
3217 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
3218 if within(self.params.range, last_statement_range)
3219 && let Some(call_data) = turn_statement_into_use(body.last())
3220 {
3221 self.selected_call = Some(call_data);
3222 }
3223
3224 ast::visit::visit_typed_expr_fn(
3225 self,
3226 location,
3227 type_,
3228 kind,
3229 arguments,
3230 body,
3231 return_annotation,
3232 );
3233 }
3234
3235 fn visit_typed_expr_block(
3236 &mut self,
3237 location: &'ast SrcSpan,
3238 statements: &'ast [TypedStatement],
3239 ) {
3240 let Some(last_statement) = statements.last() else {
3241 return;
3242 };
3243
3244 // The cursor has to be inside the last statement of the block to offer
3245 // the code action.
3246 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
3247 if within(self.params.range, statement_range) {
3248 // Only the last statement of a block can be turned into a use!
3249 if let Some(selected_call) = turn_statement_into_use(last_statement) {
3250 self.selected_call = Some(selected_call);
3251 }
3252 }
3253
3254 ast::visit::visit_typed_expr_block(self, location, statements);
3255 }
3256}
3257
3258fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
3259 match statement {
3260 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
3261 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
3262 }
3263}
3264
3265fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
3266 let TypedExpr::Call {
3267 arguments,
3268 location: call_span,
3269 fun: called_function,
3270 ..
3271 } = expr
3272 else {
3273 return None;
3274 };
3275
3276 // The function arguments in the ast are reordered using function's field map.
3277 // This means that in the `args` array they might not appear in the same order
3278 // in which they are written by the user. Since the rest of the code relies
3279 // on their order in the written code we first have to sort them by their
3280 // source position.
3281 let arguments = arguments
3282 .iter()
3283 .sorted_by_key(|argument| argument.location.start)
3284 .collect_vec();
3285
3286 let CallArg {
3287 value: last_arg,
3288 implicit: None,
3289 ..
3290 } = arguments.last()?
3291 else {
3292 return None;
3293 };
3294
3295 let TypedExpr::Fn {
3296 arguments: callback_arguments,
3297 body,
3298 ..
3299 } = last_arg
3300 else {
3301 return None;
3302 };
3303
3304 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) {
3305 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
3306 _ => None,
3307 };
3308
3309 let arg_before_callback_span = if arguments.len() >= 2 {
3310 arguments
3311 .get(arguments.len() - 2)
3312 .map(|call_arg| call_arg.location)
3313 } else {
3314 None
3315 };
3316
3317 let callback_body_span = body.first().location().merge(&body.last().last_location());
3318
3319 Some(CallLocations {
3320 call_span: *call_span,
3321 called_function_span: called_function.location(),
3322 callback_arguments_span,
3323 arg_before_callback_span,
3324 callback_body_span,
3325 })
3326}
3327
3328/// Builder for code action to extract expression into a variable.
3329/// The action will wrap the expression in a block if needed in the appropriate scope.
3330///
3331/// For using the code action on the following selection:
3332///
3333/// ```gleam
3334/// fn void() {
3335/// case result {
3336/// Ok(value) -> 2 * value + 1
3337/// // ^^^^^^^^^
3338/// Error(_) -> panic
3339/// }
3340/// }
3341/// ```
3342///
3343/// Will result:
3344///
3345/// ```gleam
3346/// fn void() {
3347/// case result {
3348/// Ok(value) -> {
3349/// let int = 2 * value
3350/// int + 1
3351/// }
3352/// Error(_) -> panic
3353/// }
3354/// }
3355/// ```
3356pub struct ExtractVariable<'a> {
3357 module: &'a Module,
3358 params: &'a CodeActionParams,
3359 edits: TextEdits<'a>,
3360 position: Option<ExtractVariablePosition>,
3361 selected_expression: Option<ExtractedToVariable>,
3362 statement_before_selected_expression: Option<SrcSpan>,
3363 latest_statement: Option<SrcSpan>,
3364 to_be_wrapped: bool,
3365 name_generator: NameGenerator,
3366}
3367
3368pub enum ExtractedToVariable {
3369 Expression { location: SrcSpan, name: EcoString },
3370 StartOfPipeline { location: SrcSpan, name: EcoString },
3371}
3372
3373/// The Position of the selected code
3374#[derive(PartialEq, Eq, Copy, Clone, Debug)]
3375enum ExtractVariablePosition {
3376 InsideCaptureBody,
3377 /// Full statements (i.e. assignments, `use`s, and simple expressions).
3378 TopLevelStatement,
3379 /// The call on the right hand side of a pipe `|>`.
3380 PipelineCall,
3381 /// The right hand side of the `->` in a case expression.
3382 InsideCaseClause,
3383 /// A call argument. This can also be a `use` callback.
3384 CallArg,
3385}
3386
3387impl<'a> ExtractVariable<'a> {
3388 pub fn new(
3389 module: &'a Module,
3390 line_numbers: &'a LineNumbers,
3391 params: &'a CodeActionParams,
3392 ) -> Self {
3393 Self {
3394 module,
3395 params,
3396 edits: TextEdits::new(line_numbers),
3397 position: None,
3398 selected_expression: None,
3399 statement_before_selected_expression: None,
3400 latest_statement: None,
3401 to_be_wrapped: false,
3402 name_generator: NameGenerator::new(),
3403 }
3404 }
3405
3406 pub fn code_actions(mut self) -> Vec<CodeAction> {
3407 self.visit_typed_module(&self.module.ast);
3408
3409 let (Some(extracted_value), Some(insert_location)) = (
3410 self.selected_expression,
3411 self.statement_before_selected_expression,
3412 ) else {
3413 return vec![];
3414 };
3415
3416 let variable_name = match &extracted_value {
3417 ExtractedToVariable::Expression { name, .. }
3418 | ExtractedToVariable::StartOfPipeline { name, .. } => name,
3419 };
3420 let expression_span = match &extracted_value {
3421 ExtractedToVariable::Expression { location, .. }
3422 | ExtractedToVariable::StartOfPipeline { location, .. } => location,
3423 };
3424
3425 let content = self
3426 .module
3427 .code
3428 .get(expression_span.start as usize..expression_span.end as usize)
3429 .expect("selected expression");
3430
3431 let range = self.edits.src_span_to_lsp_range(insert_location);
3432
3433 let indent_size =
3434 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3435
3436 let mut indent = " ".repeat(indent_size);
3437
3438 // We insert the variable declaration
3439 // Wrap in a block if needed
3440 let mut insertion = match extracted_value {
3441 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"),
3442 ExtractedToVariable::StartOfPipeline { .. } => {
3443 format!("let {variable_name} =\n{indent} {content}\n")
3444 }
3445 };
3446
3447 if self.to_be_wrapped {
3448 let line_end = self
3449 .edits
3450 .line_numbers
3451 .line_starts
3452 .get((range.end.line + 1) as usize)
3453 .expect("Line number should be valid");
3454
3455 self.edits.insert(*line_end, format!("{indent}}}\n"));
3456 indent += " ";
3457 insertion = format!("{{\n{indent}{insertion}");
3458 }
3459
3460 self.edits
3461 .insert(insert_location.start, format!("{insertion}\n{indent}"));
3462
3463 self.edits
3464 .replace(*expression_span, String::from(variable_name));
3465
3466 let mut action = Vec::with_capacity(1);
3467 CodeActionBuilder::new("Extract variable")
3468 .kind(CodeActionKind::RefactorExtract)
3469 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3470 .preferred(false)
3471 .push_to(&mut action);
3472 action
3473 }
3474
3475 fn inside_new_scope<F>(&mut self, fun: F)
3476 where
3477 F: Fn(&mut Self),
3478 {
3479 let names = self.name_generator.clone();
3480 fun(self);
3481 self.name_generator = names;
3482 }
3483
3484 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString {
3485 let name = self.name_generator.generate_name_from_type(&type_);
3486 // When the generator generates a name, it rightfully inserts it in the
3487 // current scope so that it cannot be used again.
3488 // However, in our case it's not what we want: the name we're generating
3489 // is a candidate for what we might use for a single variable, at the
3490 // end of the whole process we're gonna pick just a single name.
3491 // If we were to insert this name into scope, that means that all the
3492 // other candidates would have a suffix `int_2`, `int_3`, ...
3493 // When we finally pick one it would be strange if the picked name had
3494 // a suffix but no `int`, `int_1`, ... were in scope!
3495 let _ = self.name_generator.used_names.remove(&name);
3496 name
3497 }
3498
3499 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
3500 where
3501 F: Fn(&mut Self),
3502 {
3503 self.at_optional_position(Some(position), fun);
3504 }
3505
3506 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
3507 where
3508 F: Fn(&mut Self),
3509 {
3510 let previous_statement = self.latest_statement;
3511 let previous_position = self.position;
3512 self.position = position;
3513 fun(self);
3514 self.position = previous_position;
3515 self.latest_statement = previous_statement;
3516 }
3517}
3518
3519impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
3520 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
3521 let range = self.edits.src_span_to_lsp_range(statement.location());
3522 if !within(self.params.range, range) {
3523 self.latest_statement = Some(statement.location());
3524 ast::visit::visit_typed_statement(self, statement);
3525 return;
3526 }
3527
3528 match self.position {
3529 // A capture body is comprised of just a single expression statement
3530 // that is inserted by the compiler, we don't really want to put
3531 // anything before that; so in this case we avoid tracking it.
3532 Some(ExtractVariablePosition::InsideCaptureBody) => {}
3533 Some(ExtractVariablePosition::PipelineCall) => {
3534 // Insert above the pipeline start
3535 self.latest_statement = Some(statement.location());
3536 }
3537 _ => {
3538 // Insert below the previous statement
3539 self.latest_statement = Some(statement.location());
3540 self.statement_before_selected_expression = self.latest_statement;
3541 }
3542 }
3543
3544 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3545 ast::visit::visit_typed_statement(this, statement);
3546 });
3547 }
3548
3549 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
3550 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3551 start: fun.location.start,
3552 end: fun.end_position,
3553 });
3554
3555 if !within(self.params.range, fun_range) {
3556 return;
3557 }
3558
3559 // We reset the name generator to purge the variable names from other
3560 // scopes.
3561 // We then reserve the names already used by top level definitions.
3562 self.name_generator = NameGenerator::new();
3563 self.name_generator
3564 .reserve_module_value_names(&self.module.ast.definitions);
3565
3566 ast::visit::visit_typed_function(self, fun);
3567 }
3568
3569 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3570 if let Pattern::Variable { name, .. } = &assignment.pattern {
3571 self.name_generator.add_used_name(name.clone());
3572 }
3573 ast::visit::visit_typed_assignment(self, assignment);
3574 }
3575
3576 fn visit_typed_expr_pipeline(
3577 &mut self,
3578 location: &'ast SrcSpan,
3579 first_value: &'ast TypedPipelineAssignment,
3580 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
3581 finally: &'ast TypedExpr,
3582 finally_kind: &'ast PipelineAssignmentKind,
3583 ) {
3584 let expr_range = self.edits.src_span_to_lsp_range(*location);
3585 if !within(self.params.range, expr_range) {
3586 ast::visit::visit_typed_expr_pipeline(
3587 self,
3588 location,
3589 first_value,
3590 assignments,
3591 finally,
3592 finally_kind,
3593 );
3594 return;
3595 }
3596
3597 // Visiting a pipeline requires a bit of care, we don't want to extract
3598 // intermediate steps as variables (those are function calls)!
3599 // So we start by checking if the selected section contains multiple
3600 // steps including the first one: in that case we can extract all those
3601 // steps as a single variable.
3602 let selection = self.edits.lsp_range_to_src_span(self.params.range);
3603 let is_inside_first_step = first_value.location.contains(selection.start);
3604 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| {
3605 if assignment.location.contains(selection.end) {
3606 Some(assignment)
3607 } else {
3608 None
3609 }
3610 });
3611
3612 if let Some(last) = last_included_step
3613 && is_inside_first_step
3614 {
3615 let location = first_value.location.merge(&last.value.location());
3616 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline {
3617 location,
3618 name: self.generate_candidate_name(last.type_()),
3619 });
3620 return;
3621 }
3622
3623 // Otherwise we visit all the steps individually to see if there's
3624 // something _inside_ a step that might be extracted.
3625 let all_assignments =
3626 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
3627 for assignment in all_assignments {
3628 // With the position as "PipelineCall" we know we can't extract the
3629 // pipeline step itself!
3630 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3631 this.visit_typed_pipeline_assignment(assignment);
3632 });
3633 }
3634
3635 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
3636 this.visit_typed_expr(finally);
3637 });
3638 }
3639
3640 fn visit_typed_expr_call(
3641 &mut self,
3642 location: &'ast SrcSpan,
3643 type_: &'ast Arc<Type>,
3644 fun: &'ast TypedExpr,
3645 arguments: &'ast [TypedCallArg],
3646 open_parenthesis: &'ast Option<u32>,
3647 ) {
3648 // Function calls need some extra care. If we're inspecting a record
3649 // call like this one: `Wibble(wobble, woo)` and the cursor is over the
3650 // constructor itself we never want to allow extracting it, or it would
3651 // result in the following code:
3652 //
3653 // ```gleam
3654 // Wibble(wobble, woo)
3655 // // ^^ Cursor here
3656 //
3657 // let wibble = Wibble
3658 // wibble(wobble, woo)
3659 // // That's a bit silly!
3660 // ```
3661 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
3662 if within(self.params.range, fun_range) && fun.is_record_constructor_function() {
3663 return;
3664 }
3665
3666 // Otherwise we just keep visiting like usual, no special handling is
3667 // required.
3668 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
3669 }
3670
3671 fn visit_typed_expr_record_update(
3672 &mut self,
3673 location: &'ast SrcSpan,
3674 spread_start: &'ast u32,
3675 type_: &'ast Arc<Type>,
3676 updated_record: &'ast TypedExpr,
3677 updated_record_assigned_name: &'ast Option<EcoString>,
3678 constructor: &'ast TypedExpr,
3679 arguments: &'ast [TypedCallArg],
3680 ) {
3681 // Record updates need some extra care. If we're inspecting a record
3682 // update like this one: `Wibble(..wobble, woo)` and the cursor is over
3683 // the constructor itself we never want to allow extracting it, or it
3684 // would result in the following code:
3685 //
3686 // ```gleam
3687 // Wibble(..wobble, woo)
3688 // // ^^ Cursor here
3689 //
3690 // let wibble = Wibble
3691 // wibble(..wobble, woo)
3692 // // That's a bit silly!
3693 // ```
3694 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location());
3695 if within(self.params.range, constructor_range)
3696 && constructor.is_record_constructor_function()
3697 {
3698 return;
3699 }
3700
3701 // Otherwise we just keep visiting like usual, no special handling is
3702 // required.
3703 ast::visit::visit_typed_expr_record_update(
3704 self,
3705 location,
3706 spread_start,
3707 type_,
3708 updated_record,
3709 updated_record_assigned_name,
3710 constructor,
3711 arguments,
3712 );
3713 }
3714
3715 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3716 let expr_location = expr.location();
3717 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3718 if !within(self.params.range, expr_range) {
3719 ast::visit::visit_typed_expr(self, expr);
3720 return;
3721 }
3722
3723 // If the expression is a top level statement we don't want to extract
3724 // it into a variable. It would mean we would turn this:
3725 //
3726 // ```gleam
3727 // pub fn main() {
3728 // let wibble = 1
3729 // // ^ cursor here
3730 // }
3731 //
3732 // // into:
3733 //
3734 // pub fn main() {
3735 // let int = 1
3736 // let wibble = int
3737 // }
3738 // ```
3739 //
3740 // Not all that useful!
3741 //
3742 match self.position {
3743 Some(
3744 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
3745 ) => {
3746 self.at_optional_position(None, |this| {
3747 ast::visit::visit_typed_expr(this, expr);
3748 });
3749 return;
3750 }
3751 Some(
3752 ExtractVariablePosition::InsideCaptureBody
3753 | ExtractVariablePosition::InsideCaseClause
3754 | ExtractVariablePosition::CallArg,
3755 )
3756 | None => {}
3757 }
3758
3759 match expr {
3760 TypedExpr::Fn {
3761 kind: FunctionLiteralKind::Anonymous { .. },
3762 ..
3763 } => {
3764 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3765 ast::visit::visit_typed_expr(this, expr);
3766 });
3767 return;
3768 }
3769
3770 TypedExpr::Int { location, .. }
3771 | TypedExpr::Float { location, .. }
3772 | TypedExpr::String { location, .. }
3773 | TypedExpr::Pipeline { location, .. }
3774 | TypedExpr::Fn { location, .. }
3775 | TypedExpr::Todo { location, .. }
3776 | TypedExpr::List { location, .. }
3777 | TypedExpr::Call { location, .. }
3778 | TypedExpr::BinOp { location, .. }
3779 | TypedExpr::Case { location, .. }
3780 | TypedExpr::RecordAccess { location, .. }
3781 | TypedExpr::Tuple { location, .. }
3782 | TypedExpr::TupleIndex { location, .. }
3783 | TypedExpr::BitArray { location, .. }
3784 | TypedExpr::RecordUpdate { location, .. }
3785 | TypedExpr::NegateBool { location, .. }
3786 | TypedExpr::NegateInt { location, .. }
3787 // It generally makes no sense to extract variables, the only
3788 // exception is for records with no fields (like `True` and
3789 // `False`): in the AST those are `Var`s with a `Record`
3790 // constructor. Extracting them is allowed!
3791 | TypedExpr::Var {
3792 constructor:
3793 ValueConstructor {
3794 variant: type_::ValueConstructorVariant::Record { .. },
3795 ..
3796 },
3797 location,
3798 ..
3799 } => {
3800 if let Some(ExtractVariablePosition::CallArg) = self.position {
3801 // Don't update latest statement, we don't want to insert the extracted
3802 // variable inside the parenthesis where the call argument is located.
3803 } else {
3804 self.statement_before_selected_expression = self.latest_statement;
3805 }
3806 self.selected_expression = Some(ExtractedToVariable::Expression {
3807 location: *location,
3808 name: self.generate_candidate_name(expr.type_()),
3809 });
3810 }
3811
3812 // Expressions that don't make sense to extract
3813 TypedExpr::Panic { .. }
3814 | TypedExpr::Echo { .. }
3815 | TypedExpr::Block { .. }
3816 | TypedExpr::ModuleSelect { .. }
3817 | TypedExpr::Invalid { .. }
3818 | TypedExpr::PositionalAccess { .. }
3819 | TypedExpr::Var { .. } => (),
3820 }
3821
3822 ast::visit::visit_typed_expr(self, expr);
3823 }
3824
3825 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3826 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3827 if !within(self.params.range, range) {
3828 ast::visit::visit_typed_use(self, use_);
3829 return;
3830 }
3831
3832 // Insert code under the `use`
3833 self.statement_before_selected_expression = Some(use_.call.location());
3834 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3835 ast::visit::visit_typed_use(this, use_);
3836 });
3837 }
3838
3839 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3840 let range = self.edits.src_span_to_lsp_range(clause.location());
3841 if !within(self.params.range, range) {
3842 self.inside_new_scope(|this| {
3843 ast::visit::visit_typed_clause(this, clause);
3844 });
3845 return;
3846 }
3847
3848 // Insert code after the `->`
3849 self.latest_statement = Some(clause.then.location());
3850 self.to_be_wrapped = true;
3851 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3852 this.inside_new_scope(|this| {
3853 ast::visit::visit_typed_clause(this, clause);
3854 });
3855 });
3856 }
3857
3858 fn visit_typed_expr_block(
3859 &mut self,
3860 location: &'ast SrcSpan,
3861 statements: &'ast [TypedStatement],
3862 ) {
3863 let range = self.edits.src_span_to_lsp_range(*location);
3864 if !within(self.params.range, range) {
3865 self.inside_new_scope(|this| {
3866 ast::visit::visit_typed_expr_block(this, location, statements);
3867 });
3868 return;
3869 }
3870
3871 // Don't extract block as variable
3872 let mut position = self.position;
3873 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3874 position = None;
3875 self.to_be_wrapped = false;
3876 }
3877
3878 self.at_optional_position(position, |this| {
3879 this.inside_new_scope(|this| {
3880 ast::visit::visit_typed_expr_block(this, location, statements);
3881 });
3882 });
3883 }
3884
3885 fn visit_typed_expr_fn(
3886 &mut self,
3887 location: &'ast SrcSpan,
3888 type_: &'ast Arc<Type>,
3889 kind: &'ast FunctionLiteralKind,
3890 arguments: &'ast [TypedArg],
3891 body: &'ast Vec1<TypedStatement>,
3892 return_annotation: &'ast Option<ast::TypeAst>,
3893 ) {
3894 let range = self.edits.src_span_to_lsp_range(*location);
3895 if !within(self.params.range, range) {
3896 self.inside_new_scope(|this| {
3897 ast::visit::visit_typed_expr_fn(
3898 this,
3899 location,
3900 type_,
3901 kind,
3902 arguments,
3903 body,
3904 return_annotation,
3905 );
3906 });
3907 return;
3908 }
3909
3910 let position = match kind {
3911 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3912 // just a single expression statement. When visiting we must record
3913 // we're inside a capture body.
3914 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3915 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3916 FunctionLiteralKind::Anonymous { .. } => self.position,
3917 };
3918
3919 self.at_optional_position(position, |this| {
3920 this.inside_new_scope(|this| {
3921 ast::visit::visit_typed_expr_fn(
3922 this,
3923 location,
3924 type_,
3925 kind,
3926 arguments,
3927 body,
3928 return_annotation,
3929 );
3930 });
3931 });
3932 }
3933
3934 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3935 let range = self.edits.src_span_to_lsp_range(arg.location);
3936 if !within(self.params.range, range) {
3937 ast::visit::visit_typed_call_arg(self, arg);
3938 return;
3939 }
3940
3941 // An implicit record update arg in inserted by the compiler, we don't
3942 // want folks to interact with this since it doesn't translate to
3943 // anything in the source code despite having a default position.
3944 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit {
3945 return;
3946 }
3947
3948 let position = if arg.is_use_implicit_callback() {
3949 Some(ExtractVariablePosition::TopLevelStatement)
3950 } else {
3951 Some(ExtractVariablePosition::CallArg)
3952 };
3953
3954 self.at_optional_position(position, |this| {
3955 ast::visit::visit_typed_call_arg(this, arg);
3956 });
3957 }
3958
3959 // We don't want to offer the action if the cursor is over some invalid
3960 // piece of code.
3961 fn visit_typed_expr_invalid(
3962 &mut self,
3963 location: &'ast SrcSpan,
3964 _type_: &'ast Arc<Type>,
3965 _extra_information: &'ast Option<InvalidExpression>,
3966 ) {
3967 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3968 if within(self.params.range, invalid_range) {
3969 self.selected_expression = None;
3970 }
3971 }
3972}
3973
3974/// Builder for code action to convert a literal use into a const.
3975///
3976/// For using the code action on each of the following lines:
3977///
3978/// ```gleam
3979/// fn void() {
3980/// let var = [1, 2, 3]
3981/// let res = function("Statement", var)
3982/// }
3983/// ```
3984///
3985/// Both value literals will become:
3986///
3987/// ```gleam
3988/// const var = [1, 2, 3]
3989/// const string = "Statement"
3990///
3991/// fn void() {
3992/// let res = function(string, var)
3993/// }
3994/// ```
3995pub struct ExtractConstant<'a> {
3996 module: &'a Module,
3997 params: &'a CodeActionParams,
3998 edits: TextEdits<'a>,
3999 /// The whole selected expression
4000 selected_expression: Option<SrcSpan>,
4001 /// The location of the start of the function containing the expression.
4002 /// It includes function's documentation as well
4003 container_function_start: Option<u32>,
4004 /// The variant of the extractable expression being extracted (if any)
4005 variant_of_extractable: Option<ExtractableToConstant>,
4006 /// The name of the newly created constant
4007 name_to_use: Option<EcoString>,
4008 /// The right hand side expression of the newly created constant
4009 value_to_use: Option<EcoString>,
4010}
4011
4012/// Used when an expression can be extracted to a constant
4013enum ExtractableToConstant {
4014 /// Used for collections and operator uses. This means that elements
4015 /// inside, are also extractable as constants.
4016 ComposedValue,
4017 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
4018 /// and type variants (not records).
4019 SingleValue,
4020 /// Used for whole variable assignments. If the right hand side of the
4021 /// expression can be extracted, the whole expression extracted and use the
4022 /// local variable as a constant.
4023 Assignment,
4024}
4025
4026fn can_be_constant(
4027 module: &Module,
4028 expr: &TypedExpr,
4029 module_constants: Option<&HashSet<&EcoString>>,
4030) -> bool {
4031 // We pass the `module_constants` on recursion to not compute them each time
4032 let module_constants = match module_constants {
4033 Some(module_constants) => module_constants,
4034 None => &module
4035 .ast
4036 .definitions
4037 .constants
4038 .iter()
4039 .map(|constant| &constant.name)
4040 .collect(),
4041 };
4042
4043 match expr {
4044 // Attempt to extract whole list as long as it's comprised of only literals
4045 TypedExpr::List { elements, tail, .. } => {
4046 elements
4047 .iter()
4048 .all(|element| can_be_constant(module, element, Some(module_constants)))
4049 && tail.is_none()
4050 }
4051
4052 // Attempt to extract whole bit array as long as it's made up of literals
4053 TypedExpr::BitArray { segments, .. } => {
4054 segments
4055 .iter()
4056 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants)))
4057 && segments.iter().all(|segment| {
4058 segment.options.iter().all(|option| match option {
4059 ast::BitArrayOption::Size { value, .. } => {
4060 can_be_constant(module, value, Some(module_constants))
4061 }
4062
4063 ast::BitArrayOption::Bytes { .. }
4064 | ast::BitArrayOption::Int { .. }
4065 | ast::BitArrayOption::Float { .. }
4066 | ast::BitArrayOption::Bits { .. }
4067 | ast::BitArrayOption::Utf8 { .. }
4068 | ast::BitArrayOption::Utf16 { .. }
4069 | ast::BitArrayOption::Utf32 { .. }
4070 | ast::BitArrayOption::Utf8Codepoint { .. }
4071 | ast::BitArrayOption::Utf16Codepoint { .. }
4072 | ast::BitArrayOption::Utf32Codepoint { .. }
4073 | ast::BitArrayOption::Signed { .. }
4074 | ast::BitArrayOption::Unsigned { .. }
4075 | ast::BitArrayOption::Big { .. }
4076 | ast::BitArrayOption::Little { .. }
4077 | ast::BitArrayOption::Native { .. }
4078 | ast::BitArrayOption::Unit { .. } => true,
4079 })
4080 })
4081 }
4082
4083 // Attempt to extract whole tuple as long as it's comprised of only literals
4084 TypedExpr::Tuple { elements, .. } => elements
4085 .iter()
4086 .all(|element| can_be_constant(module, element, Some(module_constants))),
4087
4088 // Extract literals directly
4089 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
4090
4091 // Extract non-record types directly
4092 TypedExpr::Var {
4093 constructor, name, ..
4094 } => {
4095 matches!(
4096 constructor.variant,
4097 type_::ValueConstructorVariant::Record { arity: 0, .. }
4098 ) || module_constants.contains(name)
4099 }
4100
4101 // Extract record types as long as arguments can be constant
4102 TypedExpr::Call { arguments, fun, .. } => {
4103 fun.is_record_literal()
4104 && arguments
4105 .iter()
4106 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants)))
4107 }
4108
4109 // Extract concat binary operation if both sides can be constants
4110 TypedExpr::BinOp {
4111 operator,
4112 left,
4113 right,
4114 ..
4115 } => {
4116 matches!(operator, ast::BinOp::Concatenate)
4117 && can_be_constant(module, left, Some(module_constants))
4118 && can_be_constant(module, right, Some(module_constants))
4119 }
4120
4121 TypedExpr::Block { .. }
4122 | TypedExpr::Pipeline { .. }
4123 | TypedExpr::Fn { .. }
4124 | TypedExpr::Case { .. }
4125 | TypedExpr::RecordAccess { .. }
4126 | TypedExpr::PositionalAccess { .. }
4127 | TypedExpr::ModuleSelect { .. }
4128 | TypedExpr::TupleIndex { .. }
4129 | TypedExpr::Todo { .. }
4130 | TypedExpr::Panic { .. }
4131 | TypedExpr::Echo { .. }
4132 | TypedExpr::RecordUpdate { .. }
4133 | TypedExpr::NegateBool { .. }
4134 | TypedExpr::NegateInt { .. }
4135 | TypedExpr::Invalid { .. } => false,
4136 }
4137}
4138
4139/// Takes the list of already existing constants and functions and creates a
4140/// name that doesn't conflict with them
4141///
4142fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
4143 let mut name_generator = NameGenerator::new();
4144 name_generator.reserve_module_value_names(&module.ast.definitions);
4145 name_generator.generate_name_from_type(&expr.type_())
4146}
4147
4148/// Converts the source start position of a documentation comment's contents into
4149/// the position of the leading slash in its marker ('///').
4150fn get_doc_marker_position(content_pos: u32) -> u32 {
4151 content_pos.saturating_sub(3)
4152}
4153
4154impl<'a> ExtractConstant<'a> {
4155 pub fn new(
4156 module: &'a Module,
4157 line_numbers: &'a LineNumbers,
4158 params: &'a CodeActionParams,
4159 ) -> Self {
4160 Self {
4161 module,
4162 params,
4163 edits: TextEdits::new(line_numbers),
4164 selected_expression: None,
4165 container_function_start: None,
4166 variant_of_extractable: None,
4167 name_to_use: None,
4168 value_to_use: None,
4169 }
4170 }
4171
4172 pub fn code_actions(mut self) -> Vec<CodeAction> {
4173 self.visit_typed_module(&self.module.ast);
4174
4175 let (
4176 Some(expr_span),
4177 Some(function_start),
4178 Some(type_of_extractable),
4179 Some(new_const_name),
4180 Some(const_value),
4181 ) = (
4182 self.selected_expression,
4183 self.container_function_start,
4184 self.variant_of_extractable,
4185 self.name_to_use,
4186 self.value_to_use,
4187 )
4188 else {
4189 return vec![];
4190 };
4191
4192 // We insert the constant declaration
4193 self.edits.insert(
4194 function_start,
4195 format!("const {new_const_name} = {const_value}\n\n"),
4196 );
4197
4198 // We remove or replace the selected expression
4199 match type_of_extractable {
4200 // The whole expression is deleted for assignments
4201 ExtractableToConstant::Assignment => {
4202 let range = self
4203 .edits
4204 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
4205
4206 let indent_size =
4207 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
4208
4209 let expr_span_with_new_line = SrcSpan {
4210 // We remove leading indentation + 1 to remove the newline with it
4211 start: expr_span.start - (indent_size as u32 + 1),
4212 end: expr_span.end,
4213 };
4214 self.edits.delete(expr_span_with_new_line);
4215 }
4216
4217 // Only right hand side is replaced for collection or values
4218 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
4219 self.edits.replace(expr_span, String::from(new_const_name));
4220 }
4221 }
4222
4223 let mut action = Vec::with_capacity(1);
4224 CodeActionBuilder::new("Extract constant")
4225 .kind(CodeActionKind::RefactorExtract)
4226 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4227 .preferred(false)
4228 .push_to(&mut action);
4229 action
4230 }
4231}
4232
4233impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
4234 /// To get the position of the function containing the value or assignment
4235 /// to extract
4236 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
4237 let fun_location = fun.location;
4238 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
4239 start: fun_location.start,
4240 end: fun.end_position,
4241 });
4242
4243 if !within(self.params.range, fun_range) {
4244 return;
4245 }
4246
4247 // Here we need to get position of the function, starting from the leading slash in the
4248 // documentation comment's marker ('///'), not from comment's content (of which
4249 // we have the position), so we must convert the content start position
4250 // to the leading slash's position.
4251 self.container_function_start = Some(
4252 fun.documentation
4253 .as_ref()
4254 .map(|(doc_start, _)| get_doc_marker_position(*doc_start))
4255 .unwrap_or(fun_location.start),
4256 );
4257
4258 ast::visit::visit_typed_function(self, fun);
4259 }
4260
4261 /// To extract the whole assignment
4262 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
4263 let expr_location = assignment.location;
4264
4265 // We only offer this code action for extracting the whole assignment
4266 // between `let` and `=`.
4267 let pattern_location = assignment.pattern.location();
4268 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
4269 let code_action_range = self.edits.src_span_to_lsp_range(location);
4270
4271 if !within(self.params.range, code_action_range) {
4272 ast::visit::visit_typed_assignment(self, assignment);
4273 return;
4274 }
4275
4276 // Has to be variable because patterns can't be constants.
4277 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
4278 {
4279 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
4280 self.selected_expression = Some(expr_location);
4281 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern {
4282 Some(name.clone())
4283 } else {
4284 None
4285 };
4286 self.value_to_use = Some(EcoString::from(
4287 self.module
4288 .code
4289 .get(
4290 (assignment.value.location().start as usize)
4291 ..(assignment.location.end as usize),
4292 )
4293 .expect("selected expression"),
4294 ));
4295 }
4296 }
4297
4298 /// To extract only the literal
4299 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
4300 let expr_location = expr.location();
4301 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
4302
4303 if !within(self.params.range, expr_range) {
4304 ast::visit::visit_typed_expr(self, expr);
4305 return;
4306 }
4307
4308 // Keep going down recursively if:
4309 // - It's no extractable has been found yet (`None`).
4310 // - It's a collection, which may or may not contain a value that can
4311 // be extracted.
4312 // - It's a binary operator, which may or may not operate on
4313 // extractable values.
4314 if matches!(
4315 self.variant_of_extractable,
4316 None | Some(ExtractableToConstant::ComposedValue)
4317 ) && can_be_constant(self.module, expr, None)
4318 {
4319 self.variant_of_extractable = match expr {
4320 TypedExpr::Var { .. }
4321 | TypedExpr::Int { .. }
4322 | TypedExpr::Float { .. }
4323 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
4324
4325 TypedExpr::List { .. }
4326 | TypedExpr::Tuple { .. }
4327 | TypedExpr::BitArray { .. }
4328 | TypedExpr::BinOp { .. }
4329 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
4330
4331 TypedExpr::Block { .. }
4332 | TypedExpr::Pipeline { .. }
4333 | TypedExpr::Fn { .. }
4334 | TypedExpr::Case { .. }
4335 | TypedExpr::RecordAccess { .. }
4336 | TypedExpr::PositionalAccess { .. }
4337 | TypedExpr::ModuleSelect { .. }
4338 | TypedExpr::TupleIndex { .. }
4339 | TypedExpr::Todo { .. }
4340 | TypedExpr::Panic { .. }
4341 | TypedExpr::Echo { .. }
4342 | TypedExpr::RecordUpdate { .. }
4343 | TypedExpr::NegateBool { .. }
4344 | TypedExpr::NegateInt { .. }
4345 | TypedExpr::Invalid { .. } => None,
4346 };
4347
4348 self.selected_expression = Some(expr_location);
4349 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
4350 self.value_to_use = Some(EcoString::from(
4351 self.module
4352 .code
4353 .get((expr_location.start as usize)..(expr_location.end as usize))
4354 .expect("selected expression"),
4355 ));
4356 }
4357
4358 ast::visit::visit_typed_expr(self, expr);
4359 }
4360}
4361
4362/// Builder for code action to apply the "expand function capture" action.
4363///
4364pub struct ExpandFunctionCapture<'a> {
4365 module: &'a Module,
4366 params: &'a CodeActionParams,
4367 edits: TextEdits<'a>,
4368 function_capture_data: Option<FunctionCaptureData>,
4369}
4370
4371pub struct FunctionCaptureData {
4372 function_span: SrcSpan,
4373 hole_span: SrcSpan,
4374 hole_type: Arc<Type>,
4375 reserved_names: VariablesNames,
4376}
4377
4378impl<'a> ExpandFunctionCapture<'a> {
4379 pub fn new(
4380 module: &'a Module,
4381 line_numbers: &'a LineNumbers,
4382 params: &'a CodeActionParams,
4383 ) -> Self {
4384 Self {
4385 module,
4386 params,
4387 edits: TextEdits::new(line_numbers),
4388 function_capture_data: None,
4389 }
4390 }
4391
4392 pub fn code_actions(mut self) -> Vec<CodeAction> {
4393 self.visit_typed_module(&self.module.ast);
4394
4395 let Some(FunctionCaptureData {
4396 function_span,
4397 hole_span,
4398 hole_type,
4399 reserved_names,
4400 }) = self.function_capture_data
4401 else {
4402 return vec![];
4403 };
4404
4405 let mut name_generator = NameGenerator::new();
4406 name_generator.reserve_variable_names(reserved_names);
4407 let name = name_generator.generate_name_from_type(&hole_type);
4408
4409 self.edits.replace(hole_span, name.clone().into());
4410 self.edits.insert(function_span.end, " }".into());
4411 self.edits
4412 .insert(function_span.start, format!("fn({name}) {{ "));
4413
4414 let mut action = Vec::with_capacity(1);
4415 CodeActionBuilder::new("Expand function capture")
4416 .kind(CodeActionKind::RefactorRewrite)
4417 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4418 .preferred(false)
4419 .push_to(&mut action);
4420 action
4421 }
4422}
4423
4424impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
4425 fn visit_typed_expr_fn(
4426 &mut self,
4427 location: &'ast SrcSpan,
4428 type_: &'ast Arc<Type>,
4429 kind: &'ast FunctionLiteralKind,
4430 arguments: &'ast [TypedArg],
4431 body: &'ast Vec1<TypedStatement>,
4432 return_annotation: &'ast Option<ast::TypeAst>,
4433 ) {
4434 let fn_range = self.edits.src_span_to_lsp_range(*location);
4435 if within(self.params.range, fn_range)
4436 && kind.is_capture()
4437 && let [argument] = arguments
4438 {
4439 self.function_capture_data = Some(FunctionCaptureData {
4440 function_span: *location,
4441 hole_span: argument.location,
4442 hole_type: argument.type_.clone(),
4443 reserved_names: VariablesNames::from_statements(body),
4444 });
4445 }
4446
4447 ast::visit::visit_typed_expr_fn(
4448 self,
4449 location,
4450 type_,
4451 kind,
4452 arguments,
4453 body,
4454 return_annotation,
4455 );
4456 }
4457}
4458
4459/// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names].
4460struct VariablesNames {
4461 names: HashSet<EcoString>,
4462}
4463
4464impl VariablesNames {
4465 /// Creates a `VariableNames` by collecting all variables used in a list of statements.
4466 fn from_statements(statements: &[TypedStatement]) -> Self {
4467 let mut variables = Self {
4468 names: HashSet::new(),
4469 };
4470
4471 for statement in statements {
4472 variables.visit_typed_statement(statement);
4473 }
4474 variables
4475 }
4476
4477 /// Creates a `VariableNames` by collecting all variables used within an expression.
4478 fn from_expression(expression: &TypedExpr) -> Self {
4479 let mut variables = Self {
4480 names: HashSet::new(),
4481 };
4482
4483 variables.visit_typed_expr(expression);
4484 variables
4485 }
4486}
4487
4488impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
4489 fn visit_typed_expr_var(
4490 &mut self,
4491 _location: &'ast SrcSpan,
4492 _constructor: &'ast ValueConstructor,
4493 name: &'ast EcoString,
4494 ) {
4495 let _ = self.names.insert(name.clone());
4496 }
4497}
4498
4499/// Builder for code action to apply the "generate dynamic decoder action.
4500///
4501pub struct GenerateDynamicDecoder<'a, IO> {
4502 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4503 module: &'a Module,
4504 params: &'a CodeActionParams,
4505 edits: TextEdits<'a>,
4506 printer: Printer<'a>,
4507 actions: &'a mut Vec<CodeAction>,
4508}
4509
4510const DECODE_MODULE: &str = "gleam/dynamic/decode";
4511
4512impl<'a, IO> GenerateDynamicDecoder<'a, IO> {
4513 pub fn new(
4514 module: &'a Module,
4515 line_numbers: &'a LineNumbers,
4516 params: &'a CodeActionParams,
4517 actions: &'a mut Vec<CodeAction>,
4518 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4519 ) -> Self {
4520 // Since we are generating a new function, type variables from other
4521 // functions and constants are irrelevant to the types we print.
4522 let printer = Printer::new_without_type_variables(&module.ast.names);
4523 Self {
4524 module,
4525 params,
4526 edits: TextEdits::new(line_numbers),
4527 printer,
4528 actions,
4529 compiler,
4530 }
4531 }
4532
4533 pub fn code_actions(&mut self) {
4534 self.visit_typed_module(&self.module.ast);
4535 }
4536
4537 fn custom_type_decoder_body(
4538 &mut self,
4539 custom_type: &CustomType<Arc<Type>>,
4540 ) -> Option<EcoString> {
4541 // We cannot generate a decoder for an external type with no constructors!
4542 let constructors_size = custom_type.constructors.len();
4543 let (first, rest) = custom_type.constructors.split_first()?;
4544 let mode = EncodingMode::for_custom_type(custom_type);
4545
4546 // We generate a decoder for a type with a single constructor: it does not
4547 // require pattern matching on a tag as there's no variants to tell apart.
4548 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
4549 return self.constructor_decoder(mode, custom_type, first, 0);
4550 }
4551
4552 // Otherwise we need to generate a decoder that has to tell apart different
4553 // variants, depending on the mode we might have to decode a type field or
4554 // plain strings!
4555 let module = self.printer.print_module(DECODE_MODULE);
4556 let discriminant = if mode == EncodingMode::PlainString {
4557 eco_format!("use variant <- {module}.then({module}.string)")
4558 } else {
4559 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
4560 };
4561
4562 let mut clauses = Vec::with_capacity(constructors_size);
4563 for constructor in iter::once(first).chain(rest) {
4564 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
4565 let name = to_snake_case(&constructor.name);
4566 clauses.push(eco_format!(r#" "{name}" -> {body}"#));
4567 }
4568
4569 let failure_clause = self.failure_clause(custom_type);
4570
4571 let cases = clauses.join("\n");
4572 Some(eco_format!(
4573 r#"{{
4574 {discriminant}
4575 case variant {{
4576{cases}
4577{failure_clause}
4578 }}
4579}}"#,
4580 ))
4581 }
4582
4583 fn constructor_decoder(
4584 &mut self,
4585 mode: EncodingMode,
4586 custom_type: &ast::TypedCustomType,
4587 constructor: &TypedRecordConstructor,
4588 nesting: usize,
4589 ) -> Option<EcoString> {
4590 let decode_module = self.printer.print_module(DECODE_MODULE);
4591 let constructor_name = &constructor.name;
4592
4593 // If the constructor was encoded as a plain string with no additional
4594 // fields it means there's nothing else to decode and we can just
4595 // succeed.
4596 if mode == EncodingMode::PlainString {
4597 return Some(eco_format!("{decode_module}.success({constructor_name})"));
4598 }
4599
4600 // Otherwise we have to decode all the constructor fields to build it.
4601 let mut fields = Vec::with_capacity(constructor.arguments.len());
4602 for argument in constructor.arguments.iter() {
4603 let (_, name) = argument.label.as_ref()?;
4604 let field = RecordField {
4605 label: RecordLabel::Labeled(name),
4606 type_: &argument.type_,
4607 };
4608 fields.push(field);
4609 }
4610
4611 let mut decoder_printer = DecoderPrinter::new(
4612 &mut self.printer,
4613 custom_type.name.clone(),
4614 self.module.name.clone(),
4615 self.compiler,
4616 );
4617
4618 let decoders = fields
4619 .iter()
4620 .map(|field| decoder_printer.decode_field(field, nesting + 2))
4621 .join("\n");
4622
4623 let indent = " ".repeat(nesting);
4624
4625 Some(if decoders.is_empty() {
4626 eco_format!("{decode_module}.success({constructor_name})")
4627 } else {
4628 let field_names = fields
4629 .iter()
4630 .map(|field| format!("{}:", field.label.variable_name()))
4631 .join(", ");
4632
4633 eco_format!(
4634 "{{
4635{decoders}
4636{indent} {decode_module}.success({constructor_name}({field_names}))
4637{indent}}}",
4638 )
4639 })
4640 }
4641
4642 /// Generates the failure/catch-all clause in a decoder function for a
4643 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()`
4644 /// clause executed when the `type` field does not match any of the known variants.
4645 ///
4646 /// # Arguments
4647 /// * `custom_type` - The root type we are printing a decoder for
4648 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString {
4649 let decode_module = self.printer.print_module(DECODE_MODULE);
4650 let type_name = &custom_type.name;
4651
4652 let mut decoder_printer = DecoderPrinter::new(
4653 &mut self.printer,
4654 type_name.clone(),
4655 self.module.name.clone(),
4656 self.compiler,
4657 );
4658
4659 // The construction of the zero value might necessitate importing
4660 // modules that aren't currently in scope. We keep track of them here.
4661 let mut modules_to_import = HashSet::new();
4662 // We also need to keep track of the path we are tracing through the types
4663 // to make sure we don't get stuck in an infinite loop building a recursive
4664 // type.
4665 let mut path = vec![];
4666
4667 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type(
4668 &self.module.name,
4669 type_name,
4670 &mut path,
4671 &mut modules_to_import,
4672 ) {
4673 for module_name in modules_to_import {
4674 maybe_import(&mut self.edits, self.module, &module_name);
4675 }
4676 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,)
4677 } else {
4678 eco_format!(
4679 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"#
4680 )
4681 }
4682 }
4683}
4684
4685impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> {
4686 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4687 let range = self
4688 .edits
4689 .src_span_to_lsp_range(custom_type.full_location());
4690 if !within(self.params.range, range) {
4691 return;
4692 }
4693
4694 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
4695 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
4696 return;
4697 };
4698
4699 let parameters = match custom_type.parameters.len() {
4700 0 => EcoString::new(),
4701 _ => eco_format!(
4702 "({})",
4703 custom_type
4704 .parameters
4705 .iter()
4706 .map(|(_, name)| name)
4707 .join(", ")
4708 ),
4709 };
4710
4711 let decoder_type = self.printer.print_type(&Type::Named {
4712 publicity: Publicity::Public,
4713 package: STDLIB_PACKAGE_NAME.into(),
4714 module: DECODE_MODULE.into(),
4715 name: "Decoder".into(),
4716 arguments: vec![],
4717 inferred_variant: None,
4718 });
4719
4720 let function = format!(
4721 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
4722 type_name = custom_type.name,
4723 );
4724
4725 self.edits.insert(custom_type.end_position, function);
4726 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
4727
4728 CodeActionBuilder::new("Generate dynamic decoder")
4729 .kind(CodeActionKind::Refactor)
4730 .preferred(false)
4731 .changes(
4732 self.params.text_document.uri.clone(),
4733 std::mem::take(&mut self.edits.edits),
4734 )
4735 .push_to(self.actions);
4736 }
4737}
4738
4739/// If `module_name` is not already imported inside `module`, adds an edit to
4740/// add that import.
4741/// This function also makes sure not to import a module in itself.
4742///
4743fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
4744 if module.ast.names.is_imported(module_name) || module.name == module_name {
4745 return;
4746 }
4747
4748 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
4749 let first_is_import = first_import_pos.is_some();
4750 let import_location = first_import_pos.unwrap_or_default();
4751 let after_import_newlines = add_newlines_after_import(
4752 import_location,
4753 first_is_import,
4754 edits.line_numbers,
4755 &module.code,
4756 );
4757
4758 edits.edits.push(get_import_edit(
4759 import_location,
4760 module_name,
4761 &after_import_newlines,
4762 ));
4763}
4764
4765struct DecoderPrinter<'a, 'b, IO> {
4766 printer: &'a mut Printer<'b>,
4767 /// The name of the root type we are printing a decoder for
4768 type_name: EcoString,
4769 /// The module name of the root type we are printing a decoder for
4770 type_module: EcoString,
4771 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4772}
4773
4774const DYNAMIC_MODULE: &str = "gleam/dynamic";
4775const DICT_MODULE: &str = "gleam/dict";
4776const OPTION_MODULE: &str = "gleam/option";
4777
4778struct RecordField<'a> {
4779 label: RecordLabel<'a>,
4780 type_: &'a Type,
4781}
4782
4783enum RecordLabel<'a> {
4784 Labeled(&'a str),
4785 Unlabeled(usize),
4786}
4787
4788impl RecordLabel<'_> {
4789 fn field_key(&self) -> EcoString {
4790 match self {
4791 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
4792 RecordLabel::Unlabeled(index) => {
4793 eco_format!("{index}")
4794 }
4795 }
4796 }
4797
4798 fn variable_name(&self) -> EcoString {
4799 match self {
4800 RecordLabel::Labeled(label) => (*label).into(),
4801 &RecordLabel::Unlabeled(mut index) => {
4802 let mut characters = Vec::new();
4803 let alphabet_length = 26;
4804 let alphabet_offset = b'a';
4805 loop {
4806 let alphabet_index = (index % alphabet_length) as u8;
4807 characters.push((alphabet_offset + alphabet_index) as char);
4808 index /= alphabet_length;
4809
4810 if index == 0 {
4811 break;
4812 }
4813 index -= 1;
4814 }
4815 characters.into_iter().rev().collect()
4816 }
4817 }
4818 }
4819}
4820
4821impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> {
4822 fn new(
4823 printer: &'a mut Printer<'b>,
4824 type_name: EcoString,
4825 type_module: EcoString,
4826 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
4827 ) -> Self {
4828 Self {
4829 type_name,
4830 type_module,
4831 printer,
4832 compiler,
4833 }
4834 }
4835
4836 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
4837 let module_name = self.printer.print_module(DECODE_MODULE);
4838 if type_.is_bit_array() {
4839 eco_format!("{module_name}.bit_array")
4840 } else if type_.is_bool() {
4841 eco_format!("{module_name}.bool")
4842 } else if type_.is_float() {
4843 eco_format!("{module_name}.float")
4844 } else if type_.is_int() {
4845 eco_format!("{module_name}.int")
4846 } else if type_.is_string() {
4847 eco_format!("{module_name}.string")
4848 } else if type_.is_nil() {
4849 eco_format!("{module_name}.success(Nil)")
4850 } else {
4851 match type_.tuple_types() {
4852 Some(types) => {
4853 let fields = types
4854 .iter()
4855 .enumerate()
4856 .map(|(index, type_)| RecordField {
4857 type_,
4858 label: RecordLabel::Unlabeled(index),
4859 })
4860 .collect_vec();
4861 let decoders = fields
4862 .iter()
4863 .map(|field| self.decode_field(field, indent + 2))
4864 .join("\n");
4865 let mut field_names = fields.iter().map(|field| field.label.variable_name());
4866
4867 eco_format!(
4868 "{{
4869{decoders}
4870
4871{indent} {module_name}.success(#({fields}))
4872{indent}}}",
4873 fields = field_names.join(", "),
4874 indent = " ".repeat(indent)
4875 )
4876 }
4877 _ => {
4878 let type_information = type_.named_type_information();
4879 let type_information =
4880 type_information.as_ref().map(|(module, name, arguments)| {
4881 (module.as_str(), name.as_str(), arguments.as_slice())
4882 });
4883
4884 match type_information {
4885 Some(("gleam/dynamic", "Dynamic", _)) => {
4886 eco_format!("{module_name}.dynamic")
4887 }
4888 Some(("gleam", "List", [element])) => {
4889 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
4890 }
4891 Some(("gleam/option", "Option", [some])) => {
4892 eco_format!(
4893 "{module_name}.optional({})",
4894 self.decoder_for(some, indent)
4895 )
4896 }
4897 Some(("gleam/dict", "Dict", [key, value])) => {
4898 eco_format!(
4899 "{module_name}.dict({}, {})",
4900 self.decoder_for(key, indent),
4901 self.decoder_for(value, indent)
4902 )
4903 }
4904 Some((module, name, _))
4905 if module == self.type_module && name == self.type_name =>
4906 {
4907 eco_format!("{}_decoder()", to_snake_case(name))
4908 }
4909 _ => eco_format!(
4910 r#"todo as "Decoder for {}""#,
4911 self.printer.print_type(type_)
4912 ),
4913 }
4914 }
4915 }
4916 }
4917 }
4918
4919 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4920 let decoder = self.decoder_for(field.type_, indent);
4921
4922 eco_format!(
4923 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4924 indent = " ".repeat(indent),
4925 variable = field.label.variable_name(),
4926 field = field.label.field_key(),
4927 module = self.printer.print_module(DECODE_MODULE)
4928 )
4929 }
4930
4931 /// Performs best-effort generation of zero values for a given type.
4932 /// Will succeed for all prelude types and most stdlib types.
4933 /// For specifics on how custom user-defined types are handled, see
4934 /// `zero_value_for_custom_type`.
4935 fn zero_value_for_type(
4936 &mut self,
4937 type_: &Type,
4938 // Keeps track of types visited already to ensure we don't end up in an infinite
4939 // loop due to recursive types
4940 path: &mut Vec<(EcoString, EcoString)>,
4941 modules_to_import: &mut HashSet<EcoString>,
4942 ) -> Option<EcoString> {
4943 if type_.is_bit_array() {
4944 return Some("<<>>".into());
4945 }
4946 if type_.is_bool() {
4947 return Some("False".into());
4948 }
4949 if type_.is_float() {
4950 return Some("0.0".into());
4951 }
4952 if type_.is_int() {
4953 return Some("0".into());
4954 }
4955 if type_.is_string() {
4956 return Some("\"\"".into());
4957 }
4958 if type_.is_list() {
4959 return Some("[]".into());
4960 }
4961 if type_.is_nil() {
4962 return Some("Nil".into());
4963 }
4964
4965 if let Some(types) = type_.tuple_types() {
4966 // We try generating zero values for the tuple members and exit early if any of them fail.
4967 let field_zeroes = types
4968 .iter()
4969 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import))
4970 .collect_vec();
4971
4972 if field_zeroes.len() < types.len() {
4973 return None;
4974 } else {
4975 return Some(eco_format!("#({})", field_zeroes.iter().join(", ")));
4976 }
4977 }
4978
4979 let (module, name, _) = type_.named_type_information()?;
4980 match (module.as_str(), name.as_str()) {
4981 (OPTION_MODULE, "Option") => {
4982 let _ = modules_to_import.insert(OPTION_MODULE.into());
4983 Some(eco_format!(
4984 "{}.None",
4985 self.printer.print_module(OPTION_MODULE)
4986 ))
4987 }
4988
4989 (DYNAMIC_MODULE, "Dynamic") => {
4990 let _ = modules_to_import.insert(DYNAMIC_MODULE.into());
4991 Some(eco_format!(
4992 "{}.nil()",
4993 self.printer.print_module(DYNAMIC_MODULE)
4994 ))
4995 }
4996
4997 (DICT_MODULE, "Dict") => {
4998 let _ = modules_to_import.insert(DICT_MODULE.into());
4999 Some(eco_format!(
5000 "{}.new()",
5001 self.printer.print_module(DICT_MODULE)
5002 ))
5003 }
5004
5005 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import),
5006 }
5007 }
5008
5009 /// Best-effort zero value generation for user-defined types in the
5010 /// current package.
5011 fn zero_value_for_custom_type(
5012 &mut self,
5013 custom_type_module: &EcoString,
5014 custom_type_name: &EcoString,
5015 path: &mut Vec<(EcoString, EcoString)>,
5016 modules_to_import: &mut HashSet<EcoString>,
5017 ) -> Option<EcoString> {
5018 // First we check that we have not already visited this type before to
5019 // avoid cycles.
5020 let already_seen_type = path.iter().any(|(path_module, path_type_name)| {
5021 path_module == custom_type_module && path_type_name == custom_type_name
5022 });
5023
5024 if already_seen_type {
5025 return None;
5026 }
5027
5028 let type_is_inside_current_module = &self.type_module == custom_type_module;
5029
5030 let current_module_interface = self
5031 .compiler
5032 .modules
5033 .get(&self.type_module)
5034 .map(|module| &module.ast.type_info)?;
5035
5036 let type_module_interface = if !type_is_inside_current_module {
5037 self.compiler.get_module_interface(custom_type_module)?
5038 } else {
5039 current_module_interface
5040 };
5041
5042 // We only try and generate zero values for user-defined types in the current
5043 // package. If you are expanding the scope of this functionality to
5044 // remove this limitation, make sure to check for internal modules.
5045 if current_module_interface.package != type_module_interface.package {
5046 return None;
5047 }
5048
5049 let constructors = type_module_interface
5050 .types_value_constructors
5051 .get(custom_type_name)?;
5052
5053 // Opaque types cannot be constructed outside the module they were defined in,
5054 // so we will be unable to produce a zero value.
5055 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque {
5056 return None;
5057 }
5058
5059 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor
5060 // to construct our zero value to reduce visual noise, but this might not always
5061 // be possible. So we check all constructors in increasing order of size to
5062 // find the first one that succeeds, and then short circuit.
5063 constructors
5064 .variants
5065 .iter()
5066 .sorted_by_key(|v| v.parameters.len())
5067 .find_map(|zero_constructor| {
5068 self.zero_value_for_custom_type_constructor(
5069 custom_type_module,
5070 custom_type_name,
5071 zero_constructor,
5072 type_is_inside_current_module,
5073 path,
5074 modules_to_import,
5075 )
5076 })
5077 }
5078
5079 /// Attempts to construct a zero value for one specific constructor
5080 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs
5081 /// _important checks on type visibility that this method does NOT_.
5082 /// This is a helper method extracted for readability.
5083 fn zero_value_for_custom_type_constructor(
5084 &mut self,
5085 custom_type_module: &EcoString,
5086 custom_type_name: &EcoString,
5087 zero_constructor: &type_::TypeValueConstructor,
5088 type_is_inside_current_module: bool,
5089 path: &mut Vec<(EcoString, EcoString)>,
5090 modules_to_import: &mut HashSet<EcoString>,
5091 ) -> Option<EcoString> {
5092 path.push((custom_type_module.clone(), custom_type_name.clone()));
5093
5094 // Try to generate zero values for all fields in the constructor
5095 let zero_params = zero_constructor
5096 .parameters
5097 .iter()
5098 .map_while(|parameter| {
5099 let zero = self.zero_value_for_type(¶meter.type_, path, modules_to_import)?;
5100
5101 if let Some(label) = ¶meter.label {
5102 Some(eco_format!("{label}: {zero}"))
5103 } else {
5104 Some(zero)
5105 }
5106 })
5107 .collect_vec();
5108
5109 // We need to make sure to clean up the path once we've finished
5110 // "visiting" this type.
5111 let _ = path.pop();
5112
5113 // Only proceed if we were able to construct every field successfully
5114 if zero_params.len() < zero_constructor.parameters.len() {
5115 return None;
5116 }
5117
5118 let zero_constructor = if !type_is_inside_current_module {
5119 // Type constructors from other modules need to be qualified appropriately,
5120 // and they might need to be brought into scope.
5121 let _ = modules_to_import.insert(custom_type_module.clone());
5122 eco_format!(
5123 "{}.{}",
5124 self.printer.print_module(custom_type_module),
5125 zero_constructor.name
5126 )
5127 } else {
5128 eco_format!("{}", zero_constructor.name)
5129 };
5130
5131 if zero_params.is_empty() {
5132 Some(eco_format!("{zero_constructor}"))
5133 } else {
5134 let zero_args = zero_params.iter().join(", ");
5135 Some(eco_format!("{zero_constructor}({zero_args})"))
5136 }
5137 }
5138}
5139
5140/// Builder for code action to apply the "Generate to-JSON function" action.
5141///
5142pub struct GenerateJsonEncoder<'a> {
5143 module: &'a Module,
5144 params: &'a CodeActionParams,
5145 edits: TextEdits<'a>,
5146 printer: Printer<'a>,
5147 actions: &'a mut Vec<CodeAction>,
5148 config: &'a PackageConfig,
5149}
5150
5151const JSON_MODULE: &str = "gleam/json";
5152const JSON_PACKAGE_NAME: &str = "gleam_json";
5153
5154#[derive(Eq, PartialEq, Copy, Clone)]
5155enum EncodingMode {
5156 PlainString,
5157 ObjectWithTypeTag,
5158 ObjectWithNoTypeTag,
5159}
5160
5161impl EncodingMode {
5162 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
5163 match type_.constructors.as_slice() {
5164 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
5165 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
5166 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
5167 EncodingMode::PlainString
5168 }
5169 _constructors => EncodingMode::ObjectWithTypeTag,
5170 }
5171 }
5172}
5173
5174impl<'a> GenerateJsonEncoder<'a> {
5175 pub fn new(
5176 module: &'a Module,
5177 line_numbers: &'a LineNumbers,
5178 params: &'a CodeActionParams,
5179 actions: &'a mut Vec<CodeAction>,
5180 config: &'a PackageConfig,
5181 ) -> Self {
5182 // Since we are generating a new function, type variables from other
5183 // functions and constants are irrelevant to the types we print.
5184 let printer = Printer::new_without_type_variables(&module.ast.names);
5185 Self {
5186 module,
5187 params,
5188 edits: TextEdits::new(line_numbers),
5189 printer,
5190 actions,
5191 config,
5192 }
5193 }
5194
5195 pub fn code_actions(&mut self) {
5196 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
5197 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
5198 {
5199 self.visit_typed_module(&self.module.ast);
5200 }
5201 }
5202
5203 fn custom_type_encoder_body(
5204 &mut self,
5205 record_name: EcoString,
5206 custom_type: &CustomType<Arc<Type>>,
5207 ) -> Option<EcoString> {
5208 // We cannot generate a decoder for an external type with no constructors!
5209 let constructors_size = custom_type.constructors.len();
5210 let (first, rest) = custom_type.constructors.split_first()?;
5211 let mode = EncodingMode::for_custom_type(custom_type);
5212
5213 // We generate an encoder for a type with a single constructor: it does not
5214 // require pattern matching on the argument as we can access all its fields
5215 // with the usual record access syntax.
5216 if rest.is_empty() {
5217 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
5218 let unpacking = if first.arguments.is_empty() {
5219 ""
5220 } else {
5221 &eco_format!(
5222 "let {name}({fields}:) = {record_name}\n ",
5223 name = first.name,
5224 fields = first
5225 .arguments
5226 .iter()
5227 .filter_map(|argument| {
5228 argument.label.as_ref().map(|(_location, label)| label)
5229 })
5230 .join(":, ")
5231 )
5232 };
5233 return Some(eco_format!("{unpacking}{encoder}"));
5234 }
5235
5236 // Otherwise we generate an encoder for a type with multiple constructors:
5237 // it will need to pattern match on the various constructors and encode each
5238 // one separately.
5239 let mut clauses = Vec::with_capacity(constructors_size);
5240 for constructor in iter::once(first).chain(rest) {
5241 let RecordConstructor { name, .. } = constructor;
5242 let encoder =
5243 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
5244 if constructor.arguments.is_empty() {
5245 clauses.push(eco_format!(" {name} -> {encoder}"));
5246 } else {
5247 let unpacking = constructor
5248 .arguments
5249 .iter()
5250 .filter_map(|argument| {
5251 argument.label.as_ref().map(|(_location, label)| {
5252 if is_nil_like(&argument.type_) {
5253 eco_format!("{}: _", label)
5254 } else {
5255 eco_format!("{}:", label)
5256 }
5257 })
5258 })
5259 .join(", ");
5260 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}"));
5261 }
5262 }
5263
5264 let clauses = clauses.join("\n");
5265 Some(eco_format!(
5266 "case {record_name} {{
5267{clauses}
5268 }}",
5269 ))
5270 }
5271
5272 fn constructor_encoder(
5273 &mut self,
5274 mode: EncodingMode,
5275 constructor: &TypedRecordConstructor,
5276 type_name: EcoString,
5277 nesting: usize,
5278 ) -> Option<EcoString> {
5279 let json_module = self.printer.print_module(JSON_MODULE);
5280 let tag = to_snake_case(&constructor.name);
5281 let indent = " ".repeat(nesting);
5282
5283 // If the variant is encoded as a simple json string we just call the
5284 // `json.string` with the variant tag as an argument.
5285 if mode == EncodingMode::PlainString {
5286 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
5287 }
5288
5289 // Otherwise we turn it into an object with a `type` tag field.
5290 let mut encoder_printer =
5291 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone());
5292
5293 // These are the fields of the json object to encode.
5294 let mut fields = Vec::with_capacity(constructor.arguments.len());
5295 if mode == EncodingMode::ObjectWithTypeTag {
5296 // Any needed type tag is always going to be the first field in the object
5297 fields.push(eco_format!(
5298 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
5299 ));
5300 }
5301
5302 for argument in constructor.arguments.iter() {
5303 let (_, label) = argument.label.as_ref()?;
5304 let field = RecordField {
5305 label: RecordLabel::Labeled(label),
5306 type_: &argument.type_,
5307 };
5308 let encoder = encoder_printer.encode_field(&field, nesting + 2);
5309 fields.push(encoder);
5310 }
5311
5312 let fields = fields.join(",\n");
5313 Some(eco_format!(
5314 "{json_module}.object([
5315{fields},
5316{indent}])"
5317 ))
5318 }
5319}
5320
5321/// When generating an encoder, we need to know when we can ignore the fields
5322/// destructured from a constructor.
5323/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil`
5324/// types, then we will never need to use the field in our encoder function.
5325fn is_nil_like(type_: &Type) -> bool {
5326 if type_.is_nil() {
5327 return true;
5328 }
5329
5330 match type_.tuple_types() {
5331 Some(types) => types.iter().all(|type_| is_nil_like(type_)),
5332 _ => false,
5333 }
5334}
5335
5336impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
5337 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
5338 let range = self
5339 .edits
5340 .src_span_to_lsp_range(custom_type.full_location());
5341 if !within(self.params.range, range) {
5342 return;
5343 }
5344
5345 let record_name = to_snake_case(&custom_type.name);
5346 let name = eco_format!("{record_name}_to_json");
5347 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
5348 return;
5349 };
5350
5351 let json_type = self.printer.print_type(&Type::Named {
5352 publicity: Publicity::Public,
5353 package: JSON_PACKAGE_NAME.into(),
5354 module: JSON_MODULE.into(),
5355 name: "Json".into(),
5356 arguments: vec![],
5357 inferred_variant: None,
5358 });
5359
5360 let type_ = if custom_type.parameters.is_empty() {
5361 custom_type.name.clone()
5362 } else {
5363 let parameters = custom_type
5364 .parameters
5365 .iter()
5366 .map(|(_, name)| name)
5367 .join(", ");
5368 eco_format!("{}({})", custom_type.name, parameters)
5369 };
5370
5371 let function = format!(
5372 "
5373
5374fn {name}({record_name}: {type_}) -> {json_type} {{
5375 {encoder}
5376}}",
5377 );
5378
5379 self.edits.insert(custom_type.end_position, function);
5380 maybe_import(&mut self.edits, self.module, JSON_MODULE);
5381
5382 CodeActionBuilder::new("Generate to-JSON function")
5383 .kind(CodeActionKind::Refactor)
5384 .preferred(false)
5385 .changes(
5386 self.params.text_document.uri.clone(),
5387 std::mem::take(&mut self.edits.edits),
5388 )
5389 .push_to(self.actions);
5390 }
5391}
5392
5393struct JsonEncoderPrinter<'a, 'b> {
5394 printer: &'a mut Printer<'b>,
5395 /// The name of the root type we are printing an encoder for
5396 type_name: EcoString,
5397 /// The module name of the root type we are printing an encoder for
5398 type_module: EcoString,
5399}
5400
5401impl<'a, 'b> JsonEncoderPrinter<'a, 'b> {
5402 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self {
5403 Self {
5404 type_name,
5405 type_module,
5406 printer,
5407 }
5408 }
5409
5410 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
5411 let module_name = self.printer.print_module(JSON_MODULE);
5412 let is_capture = encoded_value == "_";
5413 let maybe_capture = |mut function: EcoString| {
5414 if is_capture {
5415 function
5416 } else {
5417 function.push('(');
5418 function.push_str(encoded_value);
5419 function.push(')');
5420 function
5421 }
5422 };
5423
5424 if type_.is_bool() {
5425 maybe_capture(eco_format!("{module_name}.bool"))
5426 } else if type_.is_float() {
5427 maybe_capture(eco_format!("{module_name}.float"))
5428 } else if type_.is_int() {
5429 maybe_capture(eco_format!("{module_name}.int"))
5430 } else if type_.is_string() {
5431 maybe_capture(eco_format!("{module_name}.string"))
5432 } else if type_.is_nil() {
5433 if is_capture {
5434 eco_format!("fn(_) {{ {module_name}.null() }}")
5435 } else {
5436 eco_format!("{module_name}.null()")
5437 }
5438 } else {
5439 match type_.tuple_types() {
5440 Some(types) => {
5441 let (tuple, new_indent) = if is_capture {
5442 ("value", indent + 4)
5443 } else {
5444 (encoded_value, indent + 2)
5445 };
5446
5447 // We need to iterate over all of the tuple's fields
5448 // to obtain an encoder for each one, so we reuse the
5449 // iteration to check whether this tuple can be ignored
5450 // in the encoder without calling `is_nil_like`.
5451 let mut encoders = Vec::new();
5452 let all_values_are_nil = types.iter().enumerate().fold(
5453 true,
5454 |all_values_are_nil, (index, type_)| {
5455 encoders.push(self.encoder_for(
5456 &format!("{tuple}.{index}"),
5457 type_,
5458 new_indent,
5459 ));
5460 all_values_are_nil && is_nil_like(type_)
5461 },
5462 );
5463
5464 if is_capture {
5465 eco_format!(
5466 "fn({value}) {{
5467{indent} {module_name}.preprocessed_array([
5468{indent} {encoders},
5469{indent} ])
5470{indent}}}",
5471 value = if all_values_are_nil { "_" } else { "value" },
5472 indent = " ".repeat(indent),
5473 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5474 )
5475 } else {
5476 eco_format!(
5477 "{module_name}.preprocessed_array([
5478{indent} {encoders},
5479{indent}])",
5480 indent = " ".repeat(indent),
5481 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
5482 )
5483 }
5484 }
5485 _ => {
5486 let type_information = type_.named_type_information();
5487 let type_information: Option<(&str, &str, &[Arc<Type>])> =
5488 type_information.as_ref().map(|(module, name, arguments)| {
5489 (module.as_str(), name.as_str(), arguments.as_slice())
5490 });
5491
5492 match type_information {
5493 Some(("gleam", "List", [element])) => {
5494 eco_format!(
5495 "{module_name}.array({encoded_value}, {map_function})",
5496 map_function = self.encoder_for("_", element, indent)
5497 )
5498 }
5499 Some(("gleam/option", "Option", [some])) => {
5500 eco_format!(
5501 "case {encoded_value} {{
5502{indent} {none} -> {module_name}.null()
5503{indent} {some}({value}) -> {encoder}
5504{indent}}}",
5505 indent = " ".repeat(indent),
5506 none = self
5507 .printer
5508 .print_constructor(&"gleam/option".into(), &"None".into()),
5509 some = self
5510 .printer
5511 .print_constructor(&"gleam/option".into(), &"Some".into()),
5512 value = if is_nil_like(some) { "_" } else { "value" },
5513 encoder = self.encoder_for("value", some, indent + 2)
5514 )
5515 }
5516 Some(("gleam/dict", "Dict", [key, value])) => {
5517 let stringify_function = match key
5518 .named_type_information()
5519 .as_ref()
5520 .map(|(module, name, arguments)| {
5521 (module.as_str(), name.as_str(), arguments.as_slice())
5522 }) {
5523 Some(("gleam", "String", [])) => "fn(string) { string }",
5524 _ => &format!(
5525 r#"todo as "Function to stringify {}""#,
5526 self.printer.print_type(key)
5527 ),
5528 };
5529 eco_format!(
5530 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
5531 self.encoder_for("_", value, indent)
5532 )
5533 }
5534 Some((module, name, _))
5535 if module == self.type_module && name == self.type_name =>
5536 {
5537 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
5538 }
5539 _ => eco_format!(
5540 r#"todo as "Encoder for {}""#,
5541 self.printer.print_type(type_)
5542 ),
5543 }
5544 }
5545 }
5546 }
5547 }
5548
5549 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
5550 let field_name = field.label.variable_name();
5551 let encoder = self.encoder_for(&field_name, field.type_, indent);
5552
5553 eco_format!(
5554 r#"{indent}#("{field_name}", {encoder})"#,
5555 indent = " ".repeat(indent),
5556 )
5557 }
5558}
5559
5560/// Builder for code action to pattern match on things like (anonymous) function
5561/// arguments or variables.
5562/// For example:
5563///
5564/// ```gleam
5565/// pub fn wibble(arg: #(key, value)) {
5566/// // ^ [pattern match on argument]
5567/// }
5568///
5569/// // Generates
5570///
5571/// pub fn wibble(arg: #(key, value)) {
5572/// let #(value_0, value_1) = arg
5573/// }
5574/// ```
5575///
5576/// Another example with variables:
5577///
5578/// ```gleam
5579/// pub fn main() {
5580/// let pair = #(1, 3)
5581/// // ^ [pattern match on value]
5582/// }
5583///
5584/// // Generates
5585///
5586/// pub fn main() {
5587/// let pair = #(1, 3)
5588/// let #(value_0, value_1) = pair
5589/// }
5590/// ```
5591///
5592pub struct PatternMatchOnValue<'a, A> {
5593 module: &'a Module,
5594 params: &'a CodeActionParams,
5595 compiler: &'a LspProjectCompiler<A>,
5596 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>,
5597 selected_value: Option<PatternMatchedValue<'a>>,
5598 edits: TextEdits<'a>,
5599}
5600
5601/// A value we might want to pattern match on.
5602/// Each variant will also contain all the info needed to know how to properly
5603/// print and format the corresponding pattern matching code; that's why you'll
5604/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
5605///
5606#[derive(Clone)]
5607pub enum PatternMatchedValue<'a> {
5608 /// A statement we can match on. For example, function calls, record
5609 /// and tuple accesses:
5610 ///
5611 /// ```gleam
5612 /// pub fn wibble() {
5613 /// wobble(1, 2)
5614 /// //^^^^ This
5615 /// }
5616 /// ```
5617 ///
5618 Statement {
5619 /// The span covering the entire statement:
5620 /// ```gleam
5621 /// wobble(1, 2)
5622 /// //^^^^^^^^^^^^ This
5623 /// ```
5624 location: SrcSpan,
5625 /// The statement's type defining what we will be pattern matching
5626 /// on.
5627 type_: Arc<Type>,
5628 },
5629 FunctionArgument {
5630 /// The argument being pattern matched on.
5631 ///
5632 arg: &'a TypedArg,
5633 /// The first statement inside the function body. Used to correctly
5634 /// position the inserted pattern matching.
5635 ///
5636 first_statement: &'a TypedStatement,
5637 /// The range of the entire function holding the argument.
5638 ///
5639 function_range: Range,
5640 },
5641 LetVariable {
5642 variable_name: &'a EcoString,
5643 variable_type: Arc<Type>,
5644 /// The location of the entire let assignment the variable is part of,
5645 /// so that we can add the pattern matching _after_ it.
5646 ///
5647 assignment_location: SrcSpan,
5648 },
5649 /// A variable that is bound in a case branch's pattern. For example:
5650 /// ```gleam
5651 /// case wibble {
5652 /// wobble -> 1
5653 /// // ^^^^^ This!
5654 /// }
5655 /// ```
5656 ///
5657 ClausePatternVariable {
5658 variable_type: Arc<Type>,
5659 variable_location: PatternLocation,
5660 clause_location: SrcSpan,
5661 /// All the names in the clause that are already taken by pattern variables.
5662 /// We need this to avoid generating invalid code were two pattern variables
5663 /// have the same name.
5664 ///
5665 /// For example:
5666 ///
5667 /// ```gleam
5668 /// case wibble {
5669 /// [first, ..rest] -> todo
5670 /// ^^^^^ When expanding `first` we can't add any variable pattern
5671 /// called `rest` as it would clash with the `rest` tail that is
5672 /// already there.
5673 /// }
5674 /// ```
5675 ///
5676 bound_variables: Vec<BoundVariable>,
5677 },
5678 UseVariable {
5679 variable_name: &'a EcoString,
5680 variable_type: Arc<Type>,
5681 /// The location of the entire use expression the variable is part of,
5682 /// so that we can add the pattern matching _after_ it.
5683 ///
5684 use_location: SrcSpan,
5685 },
5686}
5687
5688#[derive(Clone)]
5689pub enum PatternLocation {
5690 /// Any pattern that doesn't need any special handling.
5691 ///
5692 Regular { location: SrcSpan },
5693 /// List tails need some care to not generate invalid syntax when pattern
5694 /// matched on in case expressions.
5695 ///
5696 ListTail {
5697 /// This location covers the entire list tail pattern, including the `..`
5698 location: SrcSpan,
5699 },
5700 /// When the pattern being matched is a discard. For example:
5701 /// ```gleam
5702 /// case wibble {
5703 /// Ok(_) -> todo
5704 /// // ^ Hovering this!
5705 /// }
5706 /// ```
5707 Discard { location: SrcSpan },
5708}
5709
5710impl PatternLocation {
5711 fn regular(location: SrcSpan) -> Self {
5712 Self::Regular { location }
5713 }
5714
5715 fn is_discard(&self) -> bool {
5716 match self {
5717 PatternLocation::Regular { .. } | PatternLocation::ListTail { .. } => false,
5718 PatternLocation::Discard { .. } => true,
5719 }
5720 }
5721}
5722
5723impl<'a, IO> PatternMatchOnValue<'a, IO> {
5724 pub fn new(
5725 module: &'a Module,
5726 line_numbers: &'a LineNumbers,
5727 params: &'a CodeActionParams,
5728 compiler: &'a LspProjectCompiler<IO>,
5729 ) -> Self {
5730 Self {
5731 module,
5732 params,
5733 compiler,
5734 selected_value: None,
5735 pattern_variable_under_cursor: None,
5736 edits: TextEdits::new(line_numbers),
5737 }
5738 }
5739
5740 pub fn code_actions(mut self) -> Vec<CodeAction> {
5741 self.visit_typed_module(&self.module.ast);
5742
5743 let action_title = match self.selected_value.clone() {
5744 Some(PatternMatchedValue::FunctionArgument {
5745 arg,
5746 first_statement: function_body,
5747 function_range,
5748 }) => {
5749 self.match_on_function_argument(arg, function_body, function_range);
5750 "Pattern match on argument"
5751 }
5752 Some(
5753 PatternMatchedValue::LetVariable {
5754 variable_name,
5755 variable_type,
5756 assignment_location: location,
5757 }
5758 | PatternMatchedValue::UseVariable {
5759 variable_name,
5760 variable_type,
5761 use_location: location,
5762 },
5763 ) => {
5764 self.match_on_let_variable(variable_name, variable_type, location);
5765 "Pattern match on variable"
5766 }
5767
5768 Some(PatternMatchedValue::Statement { location, type_ }) => {
5769 self.match_on_statement(location, type_);
5770 "Pattern match on value"
5771 }
5772
5773 Some(PatternMatchedValue::ClausePatternVariable {
5774 variable_type,
5775 variable_location,
5776 clause_location,
5777 bound_variables,
5778 }) => {
5779 let title = if variable_location.is_discard() {
5780 "Pattern match on value"
5781 } else {
5782 "Pattern match on variable"
5783 };
5784
5785 self.match_on_clause_variable(
5786 variable_type,
5787 variable_location,
5788 clause_location,
5789 &bound_variables,
5790 );
5791
5792 title
5793 }
5794
5795 None => return vec![],
5796 };
5797
5798 if self.edits.edits.is_empty() {
5799 return vec![];
5800 }
5801
5802 let mut action = Vec::with_capacity(1);
5803 CodeActionBuilder::new(action_title)
5804 .kind(CodeActionKind::RefactorRewrite)
5805 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5806 .preferred(false)
5807 .push_to(&mut action);
5808 action
5809 }
5810
5811 fn match_on_function_argument(
5812 &mut self,
5813 arg: &TypedArg,
5814 first_statement: &TypedStatement,
5815 function_range: Range,
5816 ) {
5817 let Some(arg_name) = arg.get_variable_name() else {
5818 return;
5819 };
5820
5821 let Some(patterns) =
5822 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new())
5823 else {
5824 return;
5825 };
5826
5827 let first_statement_location = first_statement.location();
5828 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
5829
5830 // If we're trying to insert the pattern matching on the same
5831 // line as the one where the function is defined we will want to
5832 // put it on a new line instead. So in that case the nesting will
5833 // be the default 2 spaces.
5834 let needs_newline = function_range.start.line == first_statement_range.start.line;
5835 let nesting = if needs_newline {
5836 String::from(" ")
5837 } else {
5838 " ".repeat(first_statement_range.start.character as usize)
5839 };
5840
5841 let pattern_matching = if patterns.len() == 1 {
5842 let pattern = patterns.first();
5843 format!("let {pattern} = {arg_name}")
5844 } else {
5845 let patterns = patterns
5846 .iter()
5847 .map(|p| format!(" {nesting}{p} -> todo"))
5848 .join("\n");
5849 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
5850 };
5851
5852 let pattern_matching = if needs_newline {
5853 format!("\n{nesting}{pattern_matching}")
5854 } else {
5855 pattern_matching
5856 };
5857
5858 let has_empty_body = match first_statement {
5859 ast::Statement::Expression(TypedExpr::Todo {
5860 kind: TodoKind::EmptyFunction { .. },
5861 ..
5862 }) => true,
5863 ast::Statement::Expression(_)
5864 | ast::Statement::Assignment(_)
5865 | ast::Statement::Use(_)
5866 | ast::Statement::Assert(_) => false,
5867 };
5868
5869 // If the pattern matching is added to a function with an empty
5870 // body then we do not add any nesting after it, or we would be
5871 // increasing the nesting of the closing `}`!
5872 let pattern_matching = if has_empty_body {
5873 format!("{pattern_matching}\n")
5874 } else {
5875 format!("{pattern_matching}\n{nesting}")
5876 };
5877
5878 self.edits
5879 .insert(first_statement_location.start, pattern_matching);
5880 }
5881
5882 fn match_on_let_variable(
5883 &mut self,
5884 variable_name: &EcoString,
5885 variable_type: Arc<Type>,
5886 assignment_location: SrcSpan,
5887 ) {
5888 let Some(patterns) =
5889 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new())
5890 else {
5891 return;
5892 };
5893
5894 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
5895 let nesting = " ".repeat(assignment_range.start.character as usize);
5896 let pattern_matching = if patterns.len() == 1 {
5897 let pattern = patterns.first();
5898 format!("let {pattern} = {variable_name}")
5899 } else {
5900 let patterns = patterns
5901 .iter()
5902 .map(|p| format!(" {nesting}{p} -> todo"))
5903 .join("\n");
5904 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
5905 };
5906
5907 self.edits.insert(
5908 assignment_location.end,
5909 format!("\n{nesting}{pattern_matching}"),
5910 );
5911 }
5912
5913 fn match_on_statement(&mut self, statement_location: SrcSpan, type_: Arc<Type>) {
5914 let Some(patterns) =
5915 self.type_to_destructure_patterns(type_.as_ref(), &mut NameGenerator::new())
5916 else {
5917 return;
5918 };
5919
5920 if patterns.len() == 1 {
5921 let pattern = patterns.first();
5922 self.edits
5923 .insert(statement_location.start, format!("let {pattern} = "));
5924 } else {
5925 let statement_range = self.edits.src_span_to_lsp_range(statement_location);
5926 let nesting = " ".repeat(statement_range.start.character as usize);
5927 let patterns = patterns
5928 .iter()
5929 .map(|p| format!(" {nesting}{p} -> todo"))
5930 .join("\n");
5931 self.edits.insert(statement_location.start, "case ".into());
5932 self.edits.insert(
5933 statement_location.end,
5934 format!(" {{\n{patterns}\n{nesting}}}"),
5935 );
5936 }
5937 }
5938
5939 fn match_on_clause_variable(
5940 &mut self,
5941 variable_type: Arc<Type>,
5942 variable_location: PatternLocation,
5943 clause_location: SrcSpan,
5944 bound_variables: &[BoundVariable],
5945 ) {
5946 let mut names = NameGenerator::new();
5947 names.reserve_bound_variables(bound_variables);
5948
5949 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) {
5950 // Here we're dealing with a special case: if someone wants to expand the tail
5951 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`.
5952 // That would result in invalid syntax. So we have to generate list patterns
5953 // that have no square brackets.
5954 let first = names.rename_to_avoid_shadowing("first".into());
5955 let rest = names.rename_to_avoid_shadowing("rest".into());
5956 vec1!["".into(), eco_format!("{first}, ..{rest}")]
5957 } else if let Some(patterns) =
5958 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names)
5959 {
5960 patterns
5961 } else {
5962 return;
5963 };
5964
5965 let clause_range = self.edits.src_span_to_lsp_range(clause_location);
5966 let nesting = " ".repeat(clause_range.start.character as usize);
5967
5968 let variable_location = match variable_location {
5969 PatternLocation::Regular { location }
5970 | PatternLocation::ListTail { location }
5971 | PatternLocation::Discard { location } => location,
5972 };
5973
5974 let variable_start = (variable_location.start - clause_location.start) as usize;
5975 let variable_end = variable_start + variable_location.len();
5976
5977 let clause_code = code_at(self.module, clause_location);
5978 let patterns = patterns
5979 .iter()
5980 .map(|pattern| {
5981 let mut clause_code = clause_code.to_string();
5982 // If we're replacing a variable that's using the shorthand
5983 // syntax we want to add a space to separate it from the
5984 // preceding `:`.
5985 let pattern = if variable_start == variable_end {
5986 &eco_format!(" {pattern}")
5987 } else {
5988 pattern
5989 };
5990
5991 clause_code.replace_range(variable_start..variable_end, pattern);
5992 clause_code
5993 })
5994 .join(&format!("\n{nesting}"));
5995
5996 self.edits.replace(clause_location, patterns);
5997 }
5998
5999 /// Will produce a pattern that can be used on the left hand side of a let
6000 /// assignment to destructure a value of the given type. For example given
6001 /// this type:
6002 ///
6003 /// ```gleam
6004 /// pub type Wibble {
6005 /// Wobble(Int, label: String)
6006 /// }
6007 /// ```
6008 ///
6009 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
6010 /// The pattern will use the correct qualified/unqualified name for the
6011 /// constructor if it comes from another package.
6012 ///
6013 /// The function will only produce a list of patterns that can be used from
6014 /// the current module. So if the type comes from another module it must be
6015 /// public! Otherwise this function will return an empty vec.
6016 ///
6017 fn type_to_destructure_patterns(
6018 &mut self,
6019 type_: &Type,
6020 names: &mut NameGenerator,
6021 ) -> Option<Vec1<EcoString>> {
6022 match type_ {
6023 Type::Fn { .. } => None,
6024
6025 // Pattern matchin on `Nil` is not all that useful.
6026 Type::Named { .. } if type_.is_nil() => None,
6027
6028 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names),
6029
6030 // We special case lists, they don't have "regular" constructors
6031 // like other types. Instead we always add the two clauses covering
6032 // the empty and non empty list.
6033 Type::Named { .. } if type_.is_list() => {
6034 let first = names.rename_to_avoid_shadowing("first".into());
6035 let rest = names.rename_to_avoid_shadowing("rest".into());
6036 Some(vec1![
6037 EcoString::from("[]"),
6038 eco_format!("[{first}, ..{rest}]")
6039 ])
6040 }
6041
6042 Type::Named {
6043 module: type_module,
6044 name: type_name,
6045 ..
6046 } => {
6047 let mut patterns = vec![];
6048 let constructors =
6049 get_type_constructors(self.compiler, &self.module.name, type_module, type_name);
6050 for constructor in constructors {
6051 let names_before = names.clone();
6052 if let Some(pattern) =
6053 self.record_constructor_to_destructure_pattern(constructor, names)
6054 {
6055 patterns.push(pattern);
6056 }
6057 *names = names_before;
6058 }
6059
6060 Vec1::try_from_vec(patterns).ok()
6061 }
6062
6063 // We don't want to suggest this action for empty tuple as it
6064 // doesn't make a lot of sense to match on those.
6065 Type::Tuple { elements } if elements.is_empty() => None,
6066 Type::Tuple { elements } => {
6067 let elements = elements
6068 .iter()
6069 .map(|element| names.generate_name_from_type(element))
6070 .join(", ");
6071 Some(vec1![eco_format!("#({elements})")])
6072 }
6073 }
6074 }
6075
6076 fn type_var_to_destructure_patterns(
6077 &mut self,
6078 type_var: &TypeVar,
6079 names: &mut NameGenerator,
6080 ) -> Option<Vec1<EcoString>> {
6081 match type_var {
6082 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
6083 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names),
6084 }
6085 }
6086
6087 /// Given the value constructor of a record, returns a string with the
6088 /// pattern used to match on that specific variant.
6089 ///
6090 /// Note how:
6091 /// - If the constructor is internal to another module or comes from another
6092 /// module, then this returns `None` since one cannot pattern match on it.
6093 /// - If the provided `ValueConstructor` is not a record constructor this
6094 /// will return `None`.
6095 ///
6096 fn record_constructor_to_destructure_pattern(
6097 &self,
6098 constructor: &ValueConstructor,
6099 names: &mut NameGenerator,
6100 ) -> Option<EcoString> {
6101 let type_::ValueConstructorVariant::Record {
6102 name: constructor_name,
6103 arity: constructor_arity,
6104 module: constructor_module,
6105 field_map,
6106 ..
6107 } = &constructor.variant
6108 else {
6109 // The constructor should always be a record, in case it's not
6110 // there's not much we can do and just fail.
6111 return None;
6112 };
6113
6114 // Since the constructor is a record constructor we know that its type
6115 // is either `Named` or a `Fn` type, in either case we have to get the
6116 // arguments types out of it.
6117 let Some(arguments_types) = constructor
6118 .type_
6119 .fn_types()
6120 .map(|(arguments_types, _return)| arguments_types)
6121 .or_else(|| constructor.type_.constructor_types())
6122 else {
6123 // This should never happen but just in case we don't want to unwrap
6124 // and panic.
6125 return None;
6126 };
6127
6128 let index_to_label = match field_map {
6129 None => HashMap::new(),
6130 Some(field_map) => {
6131 names.reserve_all_labels(field_map);
6132
6133 field_map
6134 .fields
6135 .iter()
6136 .map(|(label, index)| (index, label))
6137 .collect::<HashMap<_, _>>()
6138 }
6139 };
6140
6141 let mut pattern =
6142 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
6143
6144 if *constructor_arity == 0 {
6145 return Some(pattern);
6146 }
6147
6148 pattern.push('(');
6149 let arguments = (0..*constructor_arity as u32)
6150 .map(|i| match index_to_label.get(&i) {
6151 Some(label) => eco_format!("{label}:"),
6152 None => match arguments_types.get(i as usize) {
6153 None => names.rename_to_avoid_shadowing(EcoString::from("value")),
6154 Some(type_) => names.generate_name_from_type(type_),
6155 },
6156 })
6157 .join(", ");
6158
6159 pattern.push_str(&arguments);
6160 pattern.push(')');
6161 Some(pattern)
6162 }
6163}
6164
6165fn code_at(module: &Module, span: SrcSpan) -> &str {
6166 module
6167 .code
6168 .get(span.start as usize..span.end as usize)
6169 .expect("code location must be valid")
6170}
6171
6172impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> {
6173 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6174 // If we're not inside the function there's no point in exploring its
6175 // ast further.
6176 let function_span = SrcSpan {
6177 start: fun.location.start,
6178 end: fun.end_position,
6179 };
6180 let function_range = self.edits.src_span_to_lsp_range(function_span);
6181 if !within(self.params.range, function_range) {
6182 return;
6183 }
6184
6185 for arg in &fun.arguments {
6186 // If the cursor is placed on one of the arguments, then we can try
6187 // and generate code for that one.
6188 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
6189 if within(self.params.range, arg_range)
6190 && let Some(first_statement) = fun.body.first()
6191 {
6192 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
6193 arg,
6194 first_statement,
6195 function_range,
6196 });
6197 return;
6198 }
6199 }
6200
6201 // If the cursor is not on any of the function arguments then we keep
6202 // exploring the function body as we might want to destructure the
6203 // argument of an expression function!
6204 ast::visit::visit_typed_function(self, fun);
6205 }
6206
6207 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
6208 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
6209 if !within(self.params.range, statement_range) {
6210 return;
6211 }
6212
6213 ast::visit::visit_typed_statement(self, statement);
6214 match statement {
6215 // If we haven't found any more specific selected expression, then
6216 // we are going to select the statement to pattern match on (if it
6217 // can be meaningfully pattern matched on).
6218 ast::Statement::Expression(
6219 TypedExpr::Call { type_, .. }
6220 | TypedExpr::ModuleSelect { type_, .. }
6221 | TypedExpr::RecordAccess { type_, .. }
6222 | TypedExpr::TupleIndex { type_, .. },
6223 ) if self.selected_value.is_none() => {
6224 self.selected_value = Some(PatternMatchedValue::Statement {
6225 location: statement.location(),
6226 type_: type_.clone(),
6227 });
6228 }
6229
6230 ast::Statement::Expression(_)
6231 | ast::Statement::Assignment(_)
6232 | ast::Statement::Use(_)
6233 | ast::Statement::Assert(_) => (),
6234 }
6235 }
6236
6237 fn visit_typed_expr_fn(
6238 &mut self,
6239 location: &'ast SrcSpan,
6240 type_: &'ast Arc<Type>,
6241 kind: &'ast FunctionLiteralKind,
6242 arguments: &'ast [TypedArg],
6243 body: &'ast Vec1<TypedStatement>,
6244 return_annotation: &'ast Option<ast::TypeAst>,
6245 ) {
6246 // If we're not inside the function there's no point in exploring its
6247 // ast further.
6248 let function_range = self.edits.src_span_to_lsp_range(*location);
6249 if !within(self.params.range, function_range) {
6250 return;
6251 }
6252
6253 for argument in arguments {
6254 // If the cursor is placed on one of the arguments, then we can try
6255 // and generate code for that one.
6256 let arg_range = self.edits.src_span_to_lsp_range(argument.location);
6257 if within(self.params.range, arg_range) {
6258 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
6259 arg: argument,
6260 first_statement: body.first(),
6261 function_range,
6262 });
6263 return;
6264 }
6265 }
6266
6267 // If the cursor is not on any of the function arguments then we keep
6268 // exploring the function body as we might want to destructure the
6269 // argument of an expression function!
6270 ast::visit::visit_typed_expr_fn(
6271 self,
6272 location,
6273 type_,
6274 kind,
6275 arguments,
6276 body,
6277 return_annotation,
6278 );
6279 }
6280
6281 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
6282 // If we're not inside the assignment there's no point in exploring its
6283 // ast further.
6284 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
6285 if !within(self.params.range, assignment_range) {
6286 return;
6287 }
6288
6289 ast::visit::visit_typed_assignment(self, assignment);
6290 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor
6291 // We must make sure that no other value was selected while visiting
6292 // this. If it were `Some` that means that we have found _another_
6293 // variable to match on inside the assignmemt itself. For example:
6294 // ```gleam
6295 // let a = {
6296 // let b = todo
6297 // // ^ We're matching on this, not the outer one!
6298 // }
6299 // ```
6300 && self.selected_value.is_none()
6301 {
6302 self.selected_value = Some(PatternMatchedValue::LetVariable {
6303 variable_name: name,
6304 variable_type: type_.clone(),
6305 assignment_location: assignment.location,
6306 });
6307 }
6308 }
6309
6310 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
6311 // If we're not inside the clause there's no point in exploring its
6312 // ast further.
6313 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
6314 if !within(self.params.range, clause_range) {
6315 return;
6316 }
6317
6318 for pattern in clause.pattern.iter() {
6319 self.visit_typed_pattern(pattern);
6320 }
6321 for patterns in clause.alternative_patterns.iter() {
6322 for pattern in patterns {
6323 self.visit_typed_pattern(pattern);
6324 }
6325 }
6326
6327 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() {
6328 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable {
6329 variable_type: type_,
6330 variable_location,
6331 clause_location: clause.location(),
6332 bound_variables: clause.bound_variables().collect_vec(),
6333 });
6334 } else {
6335 self.visit_typed_expr(&clause.then);
6336 }
6337 }
6338
6339 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
6340 if let Some(assignments) = use_.callback_arguments() {
6341 for variable in assignments {
6342 let ast::Arg {
6343 names: ArgNames::Named { name, .. },
6344 location: variable_location,
6345 type_,
6346 ..
6347 } = variable
6348 else {
6349 continue;
6350 };
6351
6352 // If we use a pattern in a use assignment, that will end up
6353 // being called `_use` something. We don't want to offer the
6354 // action when hovering a pattern so we ignore those.
6355 if name.starts_with("_use") {
6356 continue;
6357 }
6358
6359 let variable_range = self.edits.src_span_to_lsp_range(*variable_location);
6360 if within(self.params.range, variable_range) {
6361 self.selected_value = Some(PatternMatchedValue::UseVariable {
6362 variable_name: name,
6363 variable_type: type_.clone(),
6364 use_location: use_.location,
6365 });
6366 // If we've found the variable to pattern match on, there's no
6367 // point in keeping traversing the AST.
6368 return;
6369 }
6370 }
6371 }
6372
6373 ast::visit::visit_typed_use(self, use_);
6374 }
6375
6376 fn visit_typed_pattern_discard(
6377 &mut self,
6378 location: &'ast SrcSpan,
6379 name: &'ast EcoString,
6380 type_: &'ast Arc<Type>,
6381 ) {
6382 if within(
6383 self.params.range,
6384 self.edits.src_span_to_lsp_range(*location),
6385 ) {
6386 let location = PatternLocation::Discard {
6387 location: *location,
6388 };
6389 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
6390 }
6391 }
6392
6393 fn visit_typed_pattern_variable(
6394 &mut self,
6395 location: &'ast SrcSpan,
6396 name: &'ast EcoString,
6397 type_: &'ast Arc<Type>,
6398 _origin: &'ast VariableOrigin,
6399 ) {
6400 if within(
6401 self.params.range,
6402 self.edits.src_span_to_lsp_range(*location),
6403 ) {
6404 let location = PatternLocation::regular(*location);
6405 self.pattern_variable_under_cursor = Some((name, location, type_.clone()));
6406 }
6407 }
6408
6409 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
6410 if let Some(name) = arg.label_shorthand_name()
6411 && within(
6412 self.params.range,
6413 self.edits.src_span_to_lsp_range(arg.location),
6414 )
6415 {
6416 let location = PatternLocation::regular(SrcSpan {
6417 start: arg.location.end,
6418 end: arg.location.end,
6419 });
6420 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_()));
6421 return;
6422 }
6423
6424 ast::visit::visit_typed_pattern_call_arg(self, arg);
6425 }
6426
6427 fn visit_typed_pattern_string_prefix(
6428 &mut self,
6429 _location: &'ast SrcSpan,
6430 _left_location: &'ast SrcSpan,
6431 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
6432 right_location: &'ast SrcSpan,
6433 _left_side_string: &'ast EcoString,
6434 right_side_assignment: &'ast AssignName,
6435 ) {
6436 if let Some((name, location)) = left_side_assignment
6437 && within(
6438 self.params.range,
6439 self.edits.src_span_to_lsp_range(*location),
6440 )
6441 {
6442 let location = PatternLocation::regular(*location);
6443 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6444 } else if let AssignName::Variable(name) = right_side_assignment
6445 && within(
6446 self.params.range,
6447 self.edits.src_span_to_lsp_range(*right_location),
6448 )
6449 {
6450 let location = PatternLocation::regular(*right_location);
6451 self.pattern_variable_under_cursor = Some((name, location, type_::string()));
6452 }
6453 }
6454
6455 fn visit_typed_pattern_list(
6456 &mut self,
6457 location: &'ast SrcSpan,
6458 elements: &'ast Vec<TypedPattern>,
6459 tail: &'ast Option<Box<TypedTailPattern>>,
6460 type_: &'ast Arc<Type>,
6461 ) {
6462 let (name, tail_location, tail_type) = if let Some(tail) = tail
6463 && let Pattern::Variable { name, type_, .. } = &tail.pattern
6464 {
6465 (name, tail.location, type_)
6466 } else {
6467 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6468 return;
6469 };
6470
6471 let tail_range = self.edits.src_span_to_lsp_range(tail_location);
6472 if !within(self.params.range, tail_range) {
6473 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_);
6474 return;
6475 }
6476
6477 let location = PatternLocation::ListTail {
6478 location: tail_location,
6479 };
6480 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone()));
6481 }
6482}
6483
6484/// Given a type and its module, returns a list of its *importable*
6485/// constructors.
6486///
6487/// Since this focuses just on importable constructors, if either the module or
6488/// the type are internal the returned array will be empty!
6489///
6490fn get_type_constructors<'a, 'b, IO>(
6491 compiler: &'a LspProjectCompiler<IO>,
6492 current_module: &'b EcoString,
6493 type_module: &'b EcoString,
6494 type_name: &'b EcoString,
6495) -> Vec<&'a ValueConstructor> {
6496 let type_is_inside_current_module = current_module == type_module;
6497 let module_interface = if !type_is_inside_current_module {
6498 // If the type is outside of the module we're in, we can only pattern
6499 // match on it if the module can be imported.
6500 // The `get_module_interface` already takes care of making this check.
6501 compiler.get_module_interface(type_module)
6502 } else {
6503 // However, if the type is defined in the module we're in, we can always
6504 // pattern match on it. So we get the current module's interface.
6505 compiler
6506 .modules
6507 .get(current_module)
6508 .map(|module| &module.ast.type_info)
6509 };
6510
6511 let Some(module_interface) = module_interface else {
6512 return vec![];
6513 };
6514
6515 // If the type is in an internal module that is not the current one, we
6516 // cannot use its constructors!
6517 if !type_is_inside_current_module && module_interface.is_internal {
6518 return vec![];
6519 }
6520
6521 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
6522 return vec![];
6523 };
6524
6525 constructors
6526 .variants
6527 .iter()
6528 .filter_map(|variant| {
6529 let constructor = module_interface.values.get(&variant.name)?;
6530 if type_is_inside_current_module || constructor.publicity.is_public() {
6531 Some(constructor)
6532 } else {
6533 None
6534 }
6535 })
6536 .collect_vec()
6537}
6538
6539/// Returns a pretty printed record constructor name, the way it would be used
6540/// inside the given `module` (with the correct name and qualification).
6541///
6542/// If the constructor cannot be used inside the module because it's not
6543/// imported, then this function will return `None`.
6544///
6545fn pretty_constructor_name(
6546 module: &Module,
6547 constructor_module: &EcoString,
6548 constructor_name: &EcoString,
6549) -> Option<EcoString> {
6550 match module
6551 .ast
6552 .names
6553 .named_constructor(constructor_module, constructor_name)
6554 {
6555 type_::printer::NameContextInformation::Unimported(_, _) => None,
6556 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
6557 Some(eco_format!("{constructor_name}"))
6558 }
6559 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
6560 Some(eco_format!("{module_name}.{constructor_name}"))
6561 }
6562 }
6563}
6564
6565/// Builder for the "generate function" code action.
6566/// Whenever someone hovers an invalid expression that is inferred to have a
6567/// function type the language server can generate a function definition for it.
6568/// For example:
6569///
6570/// ```gleam
6571/// pub fn main() {
6572/// wibble(1, 2, "hello")
6573/// // ^ [generate function]
6574/// }
6575/// ```
6576///
6577/// Will generate the following definition:
6578///
6579/// ```gleam
6580/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
6581/// todo
6582/// }
6583/// ```
6584///
6585pub struct GenerateFunction<'a> {
6586 module: &'a Module,
6587 modules: &'a std::collections::HashMap<EcoString, Module>,
6588 params: &'a CodeActionParams,
6589 edits: TextEdits<'a>,
6590 last_visited_definition_end: Option<u32>,
6591 function_to_generate: Option<FunctionToGenerate<'a>>,
6592}
6593
6594struct FunctionToGenerate<'a> {
6595 module: Option<&'a str>,
6596 name: &'a str,
6597 arguments_types: Vec<Arc<Type>>,
6598
6599 /// The arguments actually supplied as input to the function, if any.
6600 /// A function to generate might as well be just a name passed as an argument
6601 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
6602 /// have any actual arguments!
6603 given_arguments: Option<&'a [TypedCallArg]>,
6604 return_type: Arc<Type>,
6605 previous_definition_end: Option<u32>,
6606}
6607
6608impl<'a> GenerateFunction<'a> {
6609 pub fn new(
6610 module: &'a Module,
6611 modules: &'a std::collections::HashMap<EcoString, Module>,
6612 line_numbers: &'a LineNumbers,
6613 params: &'a CodeActionParams,
6614 ) -> Self {
6615 Self {
6616 module,
6617 modules,
6618 params,
6619 edits: TextEdits::new(line_numbers),
6620 last_visited_definition_end: None,
6621 function_to_generate: None,
6622 }
6623 }
6624
6625 pub fn code_actions(mut self) -> Vec<CodeAction> {
6626 self.visit_typed_module(&self.module.ast);
6627
6628 let Some(
6629 function_to_generate @ FunctionToGenerate {
6630 module,
6631 previous_definition_end: Some(insert_at),
6632 ..
6633 },
6634 ) = self.function_to_generate.take()
6635 else {
6636 return vec![];
6637 };
6638
6639 if let Some(module) = module {
6640 if let Some(module) = self.modules.get(module) {
6641 let insert_at = module.code.len() as u32;
6642 self.code_action_for_module(
6643 module,
6644 Publicity::Public,
6645 function_to_generate,
6646 insert_at,
6647 )
6648 } else {
6649 Vec::new()
6650 }
6651 } else {
6652 let module = self.module;
6653 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at)
6654 }
6655 }
6656
6657 fn code_action_for_module(
6658 mut self,
6659 module: &'a Module,
6660 publicity: Publicity,
6661 function_to_generate: FunctionToGenerate<'a>,
6662 insert_at: u32,
6663 ) -> Vec<CodeAction> {
6664 let FunctionToGenerate {
6665 name,
6666 arguments_types,
6667 given_arguments,
6668 return_type,
6669 ..
6670 } = function_to_generate;
6671
6672 // This might be triggered on variants as well, in that case we don't
6673 // want to offer this action. The "generate variant" action will be
6674 // offered instead.
6675 if !is_valid_lowercase_name(name) {
6676 return vec![];
6677 }
6678
6679 // Labels do not share the same namespace as argument so we use two
6680 // separate generators to avoid renaming a label in case it shares a
6681 // name with an argument.
6682 let mut label_names = NameGenerator::new();
6683 let mut argument_names = NameGenerator::new();
6684
6685 // Since we are generating a new function, type variables from other
6686 // functions and constants are irrelevant to the types we print.
6687 let mut printer = Printer::new_without_type_variables(&module.ast.names);
6688 let arguments = arguments_types
6689 .iter()
6690 .enumerate()
6691 .map(|(index, argument_type)| {
6692 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
6693 let (label, name) =
6694 argument_names.generate_label_and_name(call_argument, argument_type);
6695 let pretty_type = printer.print_type(argument_type);
6696 if let Some(label) = label {
6697 let label = label_names.rename_to_avoid_shadowing(label);
6698 format!("{label} {name}: {pretty_type}")
6699 } else {
6700 format!("{name}: {pretty_type}")
6701 }
6702 })
6703 .join(", ");
6704
6705 let return_type = printer.print_type(&return_type);
6706
6707 let publicity = if publicity.is_public() { "pub " } else { "" };
6708
6709 // Make sure we use the line number information of the module we are
6710 // editing, which might not be the module where the code action is
6711 // triggered.
6712 self.edits.line_numbers = &module.ast.type_info.line_numbers;
6713 self.edits.insert(
6714 insert_at,
6715 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
6716 );
6717
6718 let Some(uri) = url_from_path(module.input_path.as_str()) else {
6719 return Vec::new();
6720 };
6721 let mut action = Vec::with_capacity(1);
6722 CodeActionBuilder::new("Generate function")
6723 .kind(CodeActionKind::QuickFix)
6724 .changes(uri, self.edits.edits)
6725 .preferred(true)
6726 .push_to(&mut action);
6727 action
6728 }
6729
6730 fn try_save_function_to_generate(
6731 &mut self,
6732 name: &'a EcoString,
6733 function_type: &Arc<Type>,
6734 given_arguments: Option<&'a [TypedCallArg]>,
6735 ) {
6736 match function_type.fn_types() {
6737 None => {}
6738 Some((arguments_types, return_type)) => {
6739 self.function_to_generate = Some(FunctionToGenerate {
6740 name,
6741 arguments_types,
6742 given_arguments,
6743 return_type,
6744 previous_definition_end: self.last_visited_definition_end,
6745 module: None,
6746 });
6747 }
6748 }
6749 }
6750
6751 fn try_save_function_from_other_module(
6752 &mut self,
6753 module: &'a str,
6754 name: &'a str,
6755 function_type: &Arc<Type>,
6756 given_arguments: Option<&'a [TypedCallArg]>,
6757 ) {
6758 if let Some((arguments_types, return_type)) = function_type.fn_types()
6759 && is_valid_lowercase_name(name)
6760 {
6761 self.function_to_generate = Some(FunctionToGenerate {
6762 name,
6763 arguments_types,
6764 given_arguments,
6765 return_type,
6766 previous_definition_end: self.last_visited_definition_end,
6767 module: Some(module),
6768 });
6769 }
6770 }
6771}
6772
6773impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
6774 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
6775 self.last_visited_definition_end = Some(fun.end_position);
6776 ast::visit::visit_typed_function(self, fun);
6777 }
6778
6779 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
6780 self.last_visited_definition_end = Some(constant.value.location().end);
6781 ast::visit::visit_typed_module_constant(self, constant);
6782 }
6783
6784 fn visit_typed_expr_invalid(
6785 &mut self,
6786 location: &'ast SrcSpan,
6787 type_: &'ast Arc<Type>,
6788 extra_information: &'ast Option<InvalidExpression>,
6789 ) {
6790 let invalid_range = self.edits.src_span_to_lsp_range(*location);
6791 if within(self.params.range, invalid_range) {
6792 match extra_information {
6793 Some(InvalidExpression::ModuleSelect { module_name, label }) => {
6794 self.try_save_function_from_other_module(module_name, label, type_, None);
6795 }
6796 Some(InvalidExpression::UnknownVariable { name }) => {
6797 self.try_save_function_to_generate(name, type_, None);
6798 }
6799 None => {}
6800 }
6801 }
6802
6803 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
6804 }
6805
6806 fn visit_typed_constant_invalid(
6807 &mut self,
6808 location: &'ast SrcSpan,
6809 type_: &'ast Arc<Type>,
6810 extra_information: &'ast Option<InvalidExpression>,
6811 ) {
6812 let constant_range = self.edits.src_span_to_lsp_range(*location);
6813 if let Some(extra_information) = extra_information
6814 && within(self.params.range, constant_range)
6815 {
6816 match extra_information {
6817 InvalidExpression::ModuleSelect { module_name, label } => {
6818 self.try_save_function_from_other_module(module_name, label, type_, None);
6819 }
6820 InvalidExpression::UnknownVariable { name } => {
6821 self.try_save_function_to_generate(name, type_, None);
6822 }
6823 }
6824 }
6825 }
6826
6827 fn visit_typed_expr_call(
6828 &mut self,
6829 location: &'ast SrcSpan,
6830 type_: &'ast Arc<Type>,
6831 fun: &'ast TypedExpr,
6832 arguments: &'ast [TypedCallArg],
6833 argument_parentheses: &'ast Option<u32>,
6834 ) {
6835 // If the function being called is invalid we need to generate a
6836 // function that has the proper labels.
6837 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
6838
6839 if within(self.params.range, fun_range) {
6840 if !labels_are_correct(arguments) {
6841 return;
6842 }
6843
6844 match fun {
6845 TypedExpr::Invalid {
6846 type_,
6847 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }),
6848 location: _,
6849 } => {
6850 return self.try_save_function_from_other_module(
6851 module_name,
6852 label,
6853 type_,
6854 Some(arguments),
6855 );
6856 }
6857 TypedExpr::Invalid {
6858 type_,
6859 extra_information: Some(InvalidExpression::UnknownVariable { name }),
6860 location: _,
6861 } => {
6862 return self.try_save_function_to_generate(name, type_, Some(arguments));
6863 }
6864 TypedExpr::Int { .. }
6865 | TypedExpr::Float { .. }
6866 | TypedExpr::String { .. }
6867 | TypedExpr::Block { .. }
6868 | TypedExpr::Pipeline { .. }
6869 | TypedExpr::Var { .. }
6870 | TypedExpr::Fn { .. }
6871 | TypedExpr::List { .. }
6872 | TypedExpr::Call { .. }
6873 | TypedExpr::BinOp { .. }
6874 | TypedExpr::Case { .. }
6875 | TypedExpr::RecordAccess { .. }
6876 | TypedExpr::PositionalAccess { .. }
6877 | TypedExpr::ModuleSelect { .. }
6878 | TypedExpr::Tuple { .. }
6879 | TypedExpr::TupleIndex { .. }
6880 | TypedExpr::Todo { .. }
6881 | TypedExpr::Panic { .. }
6882 | TypedExpr::Echo { .. }
6883 | TypedExpr::BitArray { .. }
6884 | TypedExpr::RecordUpdate { .. }
6885 | TypedExpr::NegateBool { .. }
6886 | TypedExpr::NegateInt { .. }
6887 | TypedExpr::Invalid { .. } => {}
6888 }
6889 }
6890 ast::visit::visit_typed_expr_call(
6891 self,
6892 location,
6893 type_,
6894 fun,
6895 arguments,
6896 argument_parentheses,
6897 );
6898 }
6899}
6900
6901/// Builder for the "generate variant" code action. This will generate a variant
6902/// for a type if it can tell the type it should come from. It will work with
6903/// non-existing variants both used as expressions
6904///
6905/// ```gleam
6906/// let a = IDoNotExist(1)
6907/// // ^^^^^^^^^^^ It would generate this variant here
6908/// ```
6909///
6910/// And as patterns:
6911///
6912/// ```gleam
6913/// let assert IDoNotExist(1) = todo
6914/// ^^^^^^^^^^^ It would generate this variant here
6915/// ```
6916///
6917pub struct GenerateVariant<'a, IO> {
6918 module: &'a Module,
6919 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
6920 params: &'a CodeActionParams,
6921 line_numbers: &'a LineNumbers,
6922 variant_to_generate: Option<VariantToGenerate<'a>>,
6923 printer: Printer<'a>,
6924}
6925
6926struct VariantToGenerate<'a> {
6927 name: &'a str,
6928
6929 arguments_types: Vec<Arc<Type>>,
6930
6931 /// The start of the variant where the code action was triggered.
6932 /// For example:
6933 ///
6934 /// ```gleam
6935 /// Wobble
6936 /// ^ Trigger here to generate `Wobble`
6937 /// ^ The start is here!
6938 /// ```
6939 variant_start: u32,
6940
6941 /// If the variant where we triggered the code action is already qualified.
6942 /// For example:
6943 ///
6944 /// ```gleam
6945 /// wibble.Wobble // -> true
6946 /// Wobble // -> false
6947 /// ```
6948 ///
6949 is_qualified: bool,
6950
6951 /// Where the custom type to add this variant to ends.
6952 ///
6953 end_position: u32,
6954
6955 /// The already existing constructors of the custom type this new variant is
6956 /// going to be added to.
6957 ///
6958 constructors: &'a [RecordConstructor<Arc<Type>>],
6959
6960 /// Wether the type we're adding the variant to is written with braces or
6961 /// not. We need this information to add braces when missing.
6962 ///
6963 type_braces: TypeBraces,
6964
6965 /// The module this variant will be added to.
6966 ///
6967 module_name: EcoString,
6968
6969 /// The type to add this variant to.
6970 type_: Arc<Type>,
6971
6972 /// The arguments actually supplied as input to the variant, if any.
6973 /// A variant to generate might as well be just a name passed as an argument
6974 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
6975 /// have any actual arguments!
6976 ///
6977 given_arguments: Option<Arguments<'a>>,
6978}
6979
6980#[derive(Debug, Clone, Copy)]
6981enum TypeBraces {
6982 /// If the type is written like this: `pub type Wibble`
6983 HasBraces,
6984 /// If the type is written like this: `pub type Wibble {}`
6985 NoBraces,
6986}
6987
6988/// The arguments to an invalid call or pattern we can use to generate a variant.
6989///
6990enum Arguments<'a> {
6991 /// These are the arguments provided to the invalid variant constructor
6992 /// when it's used as a function: `let a = Wibble(1, 2)`.
6993 ///
6994 Expressions(&'a [TypedCallArg]),
6995 /// These are the arguments provided to the invalid variant constructor when
6996 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
6997 ///
6998 Patterns(&'a [CallArg<TypedPattern>]),
6999}
7000
7001/// An invalid variant might be used both as a pattern in a case expression or
7002/// as a regular value in an expression. We want to generate the variant in both
7003/// cases, so we use this enum to tell apart the two cases and be able to reuse
7004/// most of the code for both as they are very similar.
7005///
7006enum Argument<'a> {
7007 Expression(&'a TypedCallArg),
7008 Pattern(&'a CallArg<TypedPattern>),
7009}
7010
7011impl<'a> Arguments<'a> {
7012 fn get(&self, index: usize) -> Option<Argument<'a>> {
7013 match self {
7014 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern),
7015 Arguments::Expressions(call_arguments) => {
7016 call_arguments.get(index).map(Argument::Expression)
7017 }
7018 }
7019 }
7020
7021 fn types(&self) -> Vec<Arc<Type>> {
7022 match self {
7023 Arguments::Expressions(call_arguments) => call_arguments
7024 .iter()
7025 .map(|argument| argument.value.type_())
7026 .collect_vec(),
7027
7028 Arguments::Patterns(call_arguments) => call_arguments
7029 .iter()
7030 .map(|argument| argument.value.type_())
7031 .collect_vec(),
7032 }
7033 }
7034}
7035
7036impl Argument<'_> {
7037 fn label(&self) -> Option<EcoString> {
7038 match self {
7039 Argument::Expression(call_arg) => call_arg.label.clone(),
7040 Argument::Pattern(call_arg) => call_arg.label.clone(),
7041 }
7042 }
7043}
7044
7045enum GenerateVariantEdits<'a> {
7046 GenerateInCurrentModule {
7047 current_module_edits: TextEdits<'a>,
7048 },
7049 GenerateInDifferentModule {
7050 current_module_edits: TextEdits<'a>,
7051 variant_module_edits: TextEdits<'a>,
7052 },
7053}
7054
7055impl<'a, IO> GenerateVariant<'a, IO> {
7056 pub fn new(
7057 module: &'a Module,
7058 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
7059 line_numbers: &'a LineNumbers,
7060 params: &'a CodeActionParams,
7061 ) -> Self {
7062 Self {
7063 module,
7064 params,
7065 compiler,
7066 line_numbers,
7067 variant_to_generate: None,
7068 printer: Printer::new(&module.ast.names),
7069 }
7070 }
7071
7072 pub fn code_actions(mut self) -> Vec<CodeAction> {
7073 self.visit_typed_module(&self.module.ast);
7074
7075 let Some(VariantToGenerate {
7076 name,
7077 type_,
7078 constructors,
7079 arguments_types,
7080 given_arguments,
7081 module_name,
7082 end_position,
7083 type_braces,
7084 variant_start,
7085 is_qualified,
7086 }) = &self.variant_to_generate
7087 else {
7088 return vec![];
7089 };
7090
7091 // Now we need to figure out if we're going to have to edit just the current
7092 // module (because the variant will be added to a type that is defined there),
7093 // or if we'll have to edit both the current module (to import the newly
7094 // generated variant) and a different module (where the variant definition
7095 // is going to end up).
7096 let current_module_line_numbers = LineNumbers::new(&self.module.code);
7097 let current_module_edits = TextEdits::new(¤t_module_line_numbers);
7098 let Some(variant_module) = self.compiler.modules.get(module_name) else {
7099 return vec![];
7100 };
7101 let variant_module_line_numbers = LineNumbers::new(&variant_module.code);
7102 let variant_module_edits = TextEdits::new(&variant_module_line_numbers);
7103
7104 let mut edits = if *module_name == self.module.name {
7105 GenerateVariantEdits::GenerateInCurrentModule {
7106 current_module_edits,
7107 }
7108 } else {
7109 GenerateVariantEdits::GenerateInDifferentModule {
7110 current_module_edits,
7111 variant_module_edits,
7112 }
7113 };
7114
7115 self.edits_to_create_variant(
7116 &mut edits,
7117 name,
7118 arguments_types,
7119 given_arguments,
7120 *end_position,
7121 *type_braces,
7122 );
7123 // If the variant is qualified already we don't have to do anything,
7124 // otherwise we need to import it in the current module.
7125 if !is_qualified {
7126 self.edits_to_import_variant(
7127 &mut edits,
7128 module_name,
7129 name,
7130 *variant_start,
7131 self.module,
7132 constructors,
7133 );
7134 }
7135
7136 let mut builder = CodeActionBuilder::new(&format!(
7137 "Generate `{}` variant",
7138 self.printer.print_type(type_)
7139 ))
7140 .kind(CodeActionKind::QuickFix)
7141 .preferred(true);
7142
7143 match edits {
7144 GenerateVariantEdits::GenerateInCurrentModule {
7145 current_module_edits,
7146 } => {
7147 builder = builder.changes(
7148 self.params.text_document.uri.clone(),
7149 current_module_edits.edits,
7150 );
7151 }
7152 GenerateVariantEdits::GenerateInDifferentModule {
7153 current_module_edits,
7154 variant_module_edits,
7155 } => {
7156 let Some(variant_module_path) = url_from_path(variant_module.input_path.as_str())
7157 else {
7158 return vec![];
7159 };
7160
7161 if !current_module_edits.edits.is_empty() {
7162 builder = builder.changes(
7163 self.params.text_document.uri.clone(),
7164 current_module_edits.edits,
7165 );
7166 }
7167
7168 builder = builder.changes(variant_module_path, variant_module_edits.edits);
7169 }
7170 }
7171
7172 let mut action = Vec::with_capacity(1);
7173 builder.push_to(&mut action);
7174 action
7175 }
7176
7177 /// Returns the edits needed to add this new variant to the given module.
7178 /// It also returns the uri of the module the edits should be applied to.
7179 ///
7180 fn edits_to_create_variant(
7181 &self,
7182 edits: &mut GenerateVariantEdits<'_>,
7183 variant_name: &str,
7184 arguments_types: &[Arc<Type>],
7185 given_arguments: &Option<Arguments<'_>>,
7186 end_position: u32,
7187 type_braces: TypeBraces,
7188 ) {
7189 let mut label_names = NameGenerator::new();
7190 let mut printer = Printer::new(&self.module.ast.names);
7191 let arguments = arguments_types
7192 .iter()
7193 .enumerate()
7194 .map(|(index, argument_type)| {
7195 let label = given_arguments
7196 .as_ref()
7197 .and_then(|arguments| arguments.get(index)?.label())
7198 .map(|label| label_names.rename_to_avoid_shadowing(label));
7199
7200 let pretty_type = printer.print_type(argument_type);
7201 if let Some(arg_label) = label {
7202 format!("{arg_label}: {pretty_type}")
7203 } else {
7204 format!("{pretty_type}")
7205 }
7206 })
7207 .join(", ");
7208
7209 let variant = if arguments.is_empty() {
7210 variant_name.to_string()
7211 } else {
7212 format!("{variant_name}({arguments})")
7213 };
7214
7215 let (new_text, insert_at) = match type_braces {
7216 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
7217 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
7218 };
7219
7220 match edits {
7221 GenerateVariantEdits::GenerateInCurrentModule {
7222 current_module_edits,
7223 } => current_module_edits.insert(insert_at, new_text),
7224 GenerateVariantEdits::GenerateInDifferentModule {
7225 variant_module_edits,
7226 ..
7227 } => variant_module_edits.insert(insert_at, new_text),
7228 }
7229 }
7230
7231 fn try_save_variant_to_generate(
7232 &mut self,
7233 is_qualified: bool,
7234 function_name_location: SrcSpan,
7235 function_type: &Arc<Type>,
7236 given_arguments: Option<Arguments<'a>>,
7237 ) {
7238 let variant_to_generate = self.variant_to_generate(
7239 is_qualified,
7240 function_name_location,
7241 function_type,
7242 given_arguments,
7243 );
7244 if variant_to_generate.is_some() {
7245 self.variant_to_generate = variant_to_generate;
7246 }
7247 }
7248
7249 fn variant_to_generate(
7250 &mut self,
7251 is_qualified: bool,
7252 function_name_location: SrcSpan,
7253 type_: &Arc<Type>,
7254 given_arguments: Option<Arguments<'a>>,
7255 ) -> Option<VariantToGenerate<'a>> {
7256 let name = code_at(self.module, function_name_location);
7257 if !is_valid_uppercase_name(name) {
7258 return None;
7259 }
7260
7261 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
7262 (Some(result), _) => result,
7263 (None, Some(arguments)) => (arguments.types(), type_.clone()),
7264 (None, None) => (vec![], type_.clone()),
7265 };
7266
7267 let (module_name, type_name, _) = custom_type.named_type_information()?;
7268 let module = self.compiler.modules.get(&module_name)?;
7269 let (end_position, type_braces, constructors) =
7270 (module.ast.definitions.custom_types.iter())
7271 .filter(|custom_type| custom_type.name == type_name)
7272 .find_map(|custom_type| {
7273 // If there's already a variant with this name then we definitely
7274 // don't want to generate a new variant with the same name!
7275 let variant_with_this_name_already_exists = custom_type
7276 .constructors
7277 .iter()
7278 .map(|constructor| &constructor.name)
7279 .any(|existing_constructor_name| existing_constructor_name == name);
7280 if variant_with_this_name_already_exists {
7281 return None;
7282 }
7283 let type_braces = if custom_type.end_position == custom_type.location.end {
7284 TypeBraces::NoBraces
7285 } else {
7286 TypeBraces::HasBraces
7287 };
7288 Some((
7289 custom_type.end_position,
7290 type_braces,
7291 &custom_type.constructors,
7292 ))
7293 })?;
7294
7295 Some(VariantToGenerate {
7296 name,
7297 is_qualified,
7298 constructors,
7299 arguments_types,
7300 given_arguments,
7301 module_name,
7302 type_: custom_type,
7303 end_position,
7304 type_braces,
7305 variant_start: function_name_location.start,
7306 })
7307 }
7308
7309 /// If the variant is generated in a module different from the current one,
7310 /// this will add the edits needed to correctly import the variant so that
7311 /// it's readily available.
7312 /// It will also respect the developer's choice of how variants for the type
7313 /// are imported:
7314 ///
7315 /// ```diff
7316 /// - import wibble.{ Wibble }
7317 /// + import wibble.{ Wibble, Wobble }
7318 /// // If generating `Wobble`, and other variants of that type are
7319 /// // unqualified already the new variant is imported unqualified as well.
7320 /// ```
7321 ///
7322 /// ```diff
7323 /// import wibble
7324 ///
7325 /// pub fn main() {
7326 /// - let assert Wobble = todo
7327 /// + let assert wibble.Wobble = todo
7328 /// }
7329 /// // If no variant is used in an unqualified manner, than the variant
7330 /// // that triggered the generation is also qualified!
7331 /// ```
7332 fn edits_to_import_variant(
7333 &self,
7334 edits: &mut GenerateVariantEdits<'_>,
7335 variant_module_name: &str,
7336 variant_name: &str,
7337 variant_start: u32,
7338 module: &'a Module,
7339 constructors: &[RecordConstructor<Arc<Type>>],
7340 ) {
7341 let GenerateVariantEdits::GenerateInDifferentModule {
7342 current_module_edits,
7343 ..
7344 } = edits
7345 else {
7346 // If the variant is added to the current module, then no further
7347 // edits are needed. The variant is already available in the current
7348 // module!
7349 return;
7350 };
7351
7352 let constructors_names: HashSet<_> = constructors
7353 .iter()
7354 .map(|constructor| &constructor.name)
7355 .collect();
7356
7357 // We start by getting the import for the module where the variant
7358 // is going to be added...
7359 let Some(variant_module_import) = module
7360 .ast
7361 .definitions
7362 .imports
7363 .iter()
7364 .find(|import_| import_.module == variant_module_name)
7365 else {
7366 return;
7367 };
7368 // ...and then check if any of the variants of the type where the variant
7369 // is going to be added have already been imported in an unqualified way.
7370 let constructors_for_this_type_are_unqualified = variant_module_import
7371 .unqualified_values
7372 .iter()
7373 .any(|value| constructors_names.contains(&value.name));
7374
7375 if constructors_for_this_type_are_unqualified {
7376 // We need to add an unqualified import!
7377 let (insert_positions, new_text) = edits::insert_unqualified_import(
7378 variant_module_import,
7379 &self.module.code,
7380 variant_name.into(),
7381 );
7382 current_module_edits.insert(insert_positions, new_text);
7383 } else {
7384 // We need to qualify the variant that triggered the code action!
7385 current_module_edits.insert(variant_start, format!("{variant_module_name}."));
7386 }
7387 }
7388}
7389
7390impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> {
7391 fn visit_typed_expr_invalid(
7392 &mut self,
7393 location: &'ast SrcSpan,
7394 type_: &'ast Arc<Type>,
7395 extra_information: &'ast Option<InvalidExpression>,
7396 ) {
7397 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
7398 if within(self.params.range, invalid_range) {
7399 self.try_save_variant_to_generate(false, *location, type_, None);
7400 }
7401 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
7402 }
7403
7404 fn visit_typed_expr_call(
7405 &mut self,
7406 location: &'ast SrcSpan,
7407 type_: &'ast Arc<Type>,
7408 fun: &'ast TypedExpr,
7409 arguments: &'ast [TypedCallArg],
7410 open_parenthesis: &'ast Option<u32>,
7411 ) {
7412 // If the function being called is invalid we need to generate a
7413 // function that has the proper labels.
7414 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
7415 if within(self.params.range, fun_range) && fun.is_invalid() {
7416 if labels_are_correct(arguments) {
7417 self.try_save_variant_to_generate(
7418 fun.is_module_select(),
7419 fun.location(),
7420 &fun.type_(),
7421 Some(Arguments::Expressions(arguments)),
7422 );
7423 }
7424 } else {
7425 ast::visit::visit_typed_expr_call(
7426 self,
7427 location,
7428 type_,
7429 fun,
7430 arguments,
7431 open_parenthesis,
7432 );
7433 }
7434 }
7435
7436 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
7437 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
7438 if within(self.params.range, invalid_range) {
7439 self.try_save_variant_to_generate(false, *location, type_, None);
7440 }
7441 ast::visit::visit_typed_pattern_invalid(self, location, type_);
7442 }
7443
7444 fn visit_typed_pattern_constructor(
7445 &mut self,
7446 location: &'ast SrcSpan,
7447 name_location: &'ast SrcSpan,
7448 name: &'ast EcoString,
7449 arguments: &'ast Vec<CallArg<TypedPattern>>,
7450 module: &'ast Option<(EcoString, SrcSpan)>,
7451 constructor: &'ast Inferred<type_::PatternConstructor>,
7452 spread: &'ast Option<SrcSpan>,
7453 type_: &'ast Arc<Type>,
7454 ) {
7455 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
7456 if within(self.params.range, pattern_range) {
7457 if labels_are_correct(arguments) {
7458 self.try_save_variant_to_generate(
7459 module.is_some(),
7460 *name_location,
7461 type_,
7462 Some(Arguments::Patterns(arguments)),
7463 );
7464 }
7465 } else {
7466 ast::visit::visit_typed_pattern_constructor(
7467 self,
7468 location,
7469 name_location,
7470 name,
7471 arguments,
7472 module,
7473 constructor,
7474 spread,
7475 type_,
7476 );
7477 }
7478 }
7479}
7480
7481#[must_use]
7482/// Checks the labels in the given arguments are correct: that is there's no
7483/// duplicate labels and all labelled arguments come after the unlabelled ones.
7484fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool {
7485 let mut labelled_arg_found = false;
7486 let mut used_labels = HashSet::new();
7487
7488 for argument in arguments {
7489 match &argument.label {
7490 // Labels are invalid if there's duplicate ones or if an unlabelled
7491 // argument comes after a labelled one.
7492 Some(label) if used_labels.contains(label) => return false,
7493 None if labelled_arg_found => return false,
7494 // Otherwise we just add the label to the used ones.
7495 Some(label) => {
7496 labelled_arg_found = true;
7497 let _ = used_labels.insert(label);
7498 }
7499 None => {}
7500 }
7501 }
7502
7503 true
7504}
7505
7506#[derive(Clone)]
7507struct NameGenerator {
7508 used_names: im::HashSet<EcoString>,
7509}
7510
7511impl NameGenerator {
7512 pub fn new() -> Self {
7513 NameGenerator {
7514 used_names: im::HashSet::new(),
7515 }
7516 }
7517
7518 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
7519 let mut i = 1;
7520 let mut candidate_name = base.clone();
7521
7522 loop {
7523 if self.used_names.contains(&candidate_name) {
7524 i += 1;
7525 candidate_name = eco_format!("{base}_{i}");
7526 } else {
7527 let _ = self.used_names.insert(candidate_name.clone());
7528 return candidate_name;
7529 }
7530 }
7531 }
7532
7533 /// Given an argument type and the actual call argument (if any), comes up
7534 /// with a label and a name to use for that argument when generating a
7535 /// function.
7536 ///
7537 pub fn generate_label_and_name(
7538 &mut self,
7539 call_argument: Option<&CallArg<TypedExpr>>,
7540 argument_type: &Arc<Type>,
7541 ) -> (Option<EcoString>, EcoString) {
7542 let label = call_argument.and_then(|argument| argument.label.clone());
7543 let argument_name = call_argument
7544 // We always favour a name derived from the expression (for example if
7545 // the argument is a variable)
7546 .and_then(|argument| self.generate_name_from_expression(&argument.value))
7547 // If we don't have such a name and there's a label we use that name.
7548 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
7549 // If all else fails we fallback to using a name derived from the
7550 // argument's type.
7551 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
7552
7553 (label, argument_name)
7554 }
7555
7556 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
7557 let type_to_base_name = |type_: &Arc<Type>| {
7558 type_
7559 .named_type_name()
7560 .map(|(_type_module, type_name)| to_snake_case(&type_name))
7561 .filter(|name| is_valid_lowercase_name(name))
7562 .unwrap_or(EcoString::from("value"))
7563 };
7564
7565 let base_name = match type_.list_type() {
7566 None => type_to_base_name(type_),
7567 // If we're coming up with a name for a list we want to use the
7568 // plural form for the name of the inner type. For example:
7569 // `List(Pokemon)` should generate `pokemons`.
7570 Some(inner_type) => {
7571 let base_name = type_to_base_name(&inner_type);
7572 // If the inner type name already ends in "s" we leave it as it
7573 // is, or it would look funny.
7574 if base_name.ends_with('s') {
7575 base_name
7576 } else {
7577 eco_format!("{base_name}s")
7578 }
7579 }
7580 };
7581
7582 self.rename_to_avoid_shadowing(base_name)
7583 }
7584
7585 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
7586 match expression {
7587 // If the argument is a record, we can't use it as an argument name.
7588 // Similarly, we don't want to base the variable name off a
7589 // compiler-generated variable like `_pipe`.
7590 TypedExpr::Var {
7591 name, constructor, ..
7592 } if !constructor.variant.is_record()
7593 && !constructor.variant.is_generated_variable() =>
7594 {
7595 Some(self.rename_to_avoid_shadowing(name.clone()))
7596 }
7597
7598 // If the argument is a record access, we generate a name from the
7599 // label used.
7600 // For example if we have `wibble.id` we would end up picking `id`.
7601 TypedExpr::RecordAccess { label, .. } => {
7602 Some(self.rename_to_avoid_shadowing(label.clone()))
7603 }
7604
7605 TypedExpr::Int { .. }
7606 | TypedExpr::Float { .. }
7607 | TypedExpr::String { .. }
7608 | TypedExpr::Block { .. }
7609 | TypedExpr::Pipeline { .. }
7610 | TypedExpr::Var { .. }
7611 | TypedExpr::Fn { .. }
7612 | TypedExpr::List { .. }
7613 | TypedExpr::Call { .. }
7614 | TypedExpr::BinOp { .. }
7615 | TypedExpr::Case { .. }
7616 | TypedExpr::PositionalAccess { .. }
7617 | TypedExpr::ModuleSelect { .. }
7618 | TypedExpr::Tuple { .. }
7619 | TypedExpr::TupleIndex { .. }
7620 | TypedExpr::Todo { .. }
7621 | TypedExpr::Panic { .. }
7622 | TypedExpr::Echo { .. }
7623 | TypedExpr::BitArray { .. }
7624 | TypedExpr::RecordUpdate { .. }
7625 | TypedExpr::NegateBool { .. }
7626 | TypedExpr::NegateInt { .. }
7627 | TypedExpr::Invalid { .. } => None,
7628 }
7629 }
7630
7631 /// Given some typed definitions this reserves all the value names defined
7632 /// by all the top level definitions. That is: all function names, constant
7633 /// names, and imported modules names.
7634 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) {
7635 for constant in &definitions.constants {
7636 self.add_used_name(constant.name.clone());
7637 }
7638
7639 for function in &definitions.functions {
7640 if let Some((_, name)) = &function.name {
7641 self.add_used_name(name.clone());
7642 }
7643 }
7644
7645 for import in &definitions.imports {
7646 let module_name = match &import.used_name() {
7647 Some(used_name) => used_name.clone(),
7648 None => import.module.clone(),
7649 };
7650 self.add_used_name(module_name);
7651 }
7652 }
7653
7654 pub fn add_used_name(&mut self, name: EcoString) {
7655 let _ = self.used_names.insert(name);
7656 }
7657
7658 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
7659 field_map
7660 .fields
7661 .iter()
7662 .for_each(|(label, _)| self.add_used_name(label.clone()));
7663 }
7664
7665 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
7666 variable_names
7667 .names
7668 .iter()
7669 .for_each(|name| self.add_used_name(name.clone()));
7670 }
7671
7672 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) {
7673 for variable in bound_variables {
7674 self.add_used_name(variable.name());
7675 }
7676 }
7677}
7678
7679#[must_use]
7680fn is_valid_lowercase_name(name: &str) -> bool {
7681 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
7682 return false;
7683 }
7684
7685 for char in name.chars() {
7686 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
7687 if !is_valid_char {
7688 return false;
7689 }
7690 }
7691
7692 string_to_keyword(name).is_none()
7693}
7694
7695#[must_use]
7696fn is_valid_uppercase_name(name: &str) -> bool {
7697 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
7698 return false;
7699 }
7700
7701 for char in name.chars() {
7702 if !char.is_ascii_alphanumeric() {
7703 return false;
7704 }
7705 }
7706
7707 true
7708}
7709
7710/// Code action to rewrite a single-step pipeline into a regular function call.
7711/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
7712///
7713pub struct ConvertToFunctionCall<'a> {
7714 module: &'a Module,
7715 params: &'a CodeActionParams,
7716 edits: TextEdits<'a>,
7717 locations: Option<ConvertToFunctionCallLocations>,
7718}
7719
7720/// All the different locations the "Convert to function call" code action needs
7721/// to properly rewrite a pipeline into a function call.
7722///
7723struct ConvertToFunctionCallLocations {
7724 /// This is the location of the value being piped into a call.
7725 ///
7726 /// ```gleam
7727 /// [1, 2, 3] |> list.length
7728 /// // ^^^^^^^^^ This one here
7729 /// ```
7730 ///
7731 first_value: SrcSpan,
7732
7733 /// This is the location of the call the value is being piped into.
7734 ///
7735 /// ```gleam
7736 /// [1, 2, 3] |> list.length
7737 /// // ^^^^^^^^^^^ This one here
7738 /// ```
7739 ///
7740 call: SrcSpan,
7741
7742 /// This is the kind of desugaring that is taking place when piping
7743 /// `first_value` into `call`.
7744 ///
7745 call_kind: PipelineAssignmentKind,
7746}
7747
7748impl<'a> ConvertToFunctionCall<'a> {
7749 pub fn new(
7750 module: &'a Module,
7751 line_numbers: &'a LineNumbers,
7752 params: &'a CodeActionParams,
7753 ) -> Self {
7754 Self {
7755 module,
7756 params,
7757 edits: TextEdits::new(line_numbers),
7758 locations: None,
7759 }
7760 }
7761
7762 pub fn code_actions(mut self) -> Vec<CodeAction> {
7763 self.visit_typed_module(&self.module.ast);
7764
7765 // If we couldn't find a pipeline to rewrite we don't return any action.
7766 let Some(ConvertToFunctionCallLocations {
7767 first_value,
7768 call,
7769 call_kind,
7770 }) = self.locations
7771 else {
7772 return vec![];
7773 };
7774
7775 // We first delete the first value of the pipeline as it's going to be
7776 // inlined as a function call argument.
7777 self.edits.delete(SrcSpan {
7778 start: first_value.start,
7779 end: call.start,
7780 });
7781
7782 // Then we have to insert the piped value in the appropriate position.
7783 // This will change based on how the pipeline is being desugared, we
7784 // know this thanks to the `call_kind`
7785 let first_value_text = self
7786 .module
7787 .code
7788 .get(first_value.start as usize..first_value.end as usize)
7789 .expect("invalid code span")
7790 .to_string();
7791
7792 match call_kind {
7793 // When piping into a `_` we replace the hole with the piped value:
7794 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
7795 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
7796
7797 // When piping is desguared as a function call we need to add the
7798 // missing parentheses:
7799 // `[1, 2] |> length` becomes `length([1, 2])`
7800 PipelineAssignmentKind::FunctionCall => {
7801 self.edits.insert(call.end, format!("({first_value_text})"));
7802 }
7803
7804 // When the piped value is inserted as the first argument there's two
7805 // possible scenarios:
7806 // - there's a second argument as well: in that case we insert it
7807 // before the second arg and add a comma
7808 // - there's no other argument: `[1, 2] |> length()` becomes
7809 // `length([1, 2])`, we insert the value between the empty
7810 // parentheses
7811 PipelineAssignmentKind::FirstArgument {
7812 second_argument: Some(SrcSpan { start, .. }),
7813 } => self.edits.insert(start, format!("{first_value_text}, ")),
7814 PipelineAssignmentKind::FirstArgument {
7815 second_argument: None,
7816 } => self.edits.insert(call.end - 1, first_value_text),
7817
7818 // When the value is piped into an echo, to rewrite the pipeline we
7819 // have to insert the value after the `echo` with no parentheses:
7820 // `a |> echo` is rewritten as `echo a`.
7821 PipelineAssignmentKind::Echo => {
7822 self.edits.insert(call.end, format!(" {first_value_text}"));
7823 }
7824 }
7825
7826 let mut action = Vec::with_capacity(1);
7827 CodeActionBuilder::new("Convert to function call")
7828 .kind(CodeActionKind::RefactorRewrite)
7829 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7830 .preferred(false)
7831 .push_to(&mut action);
7832 action
7833 }
7834}
7835
7836impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
7837 fn visit_typed_expr_pipeline(
7838 &mut self,
7839 location: &'ast SrcSpan,
7840 first_value: &'ast TypedPipelineAssignment,
7841 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
7842 finally: &'ast TypedExpr,
7843 finally_kind: &'ast PipelineAssignmentKind,
7844 ) {
7845 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
7846 if within(self.params.range, pipeline_range) {
7847 // Add final assignment to the list
7848 let all_assignments = assignments
7849 .iter()
7850 .map(|(call, kind)| (call.location, *kind))
7851 .chain(iter::once((finally.location(), *finally_kind)));
7852 let mut call_information = None;
7853 // Span, containing content that will be extracted as call argument
7854 let mut accumulated_span = first_value.location;
7855
7856 for (current_location, current_kind) in all_assignments {
7857 if within(
7858 self.params.range,
7859 self.edits.src_span_to_lsp_range(current_location),
7860 ) {
7861 call_information = Some((accumulated_span, current_location, current_kind));
7862 break;
7863 }
7864 accumulated_span = accumulated_span.merge(¤t_location);
7865 }
7866
7867 let (final_prev, final_call, final_kind) = match call_information {
7868 Some(triple) => triple,
7869
7870 // Here two situations are possible:
7871 // - If the cursor is not on any of the assignments (for example, it is on first value), then we need
7872 // to use that first value as accumulated location, and the first call location and kind
7873 // - If there are no assignments, then we are dealing with a single
7874 // step pipeline and the call is `finally`
7875 None => {
7876 let (call, call_kind) = &assignments
7877 .first()
7878 .map(|(call, call_kind)| (call.location, *call_kind))
7879 .unwrap_or_else(|| (finally.location(), *finally_kind));
7880 (first_value.location, *call, *call_kind)
7881 }
7882 };
7883
7884 self.locations = Some(ConvertToFunctionCallLocations {
7885 first_value: final_prev,
7886 call: final_call,
7887 call_kind: final_kind,
7888 });
7889
7890 ast::visit::visit_typed_expr_pipeline(
7891 self,
7892 location,
7893 first_value,
7894 assignments,
7895 finally,
7896 finally_kind,
7897 );
7898 }
7899 }
7900}
7901
7902/// Builder for code action to inline a variable.
7903///
7904pub struct InlineVariable<'a> {
7905 module: &'a Module,
7906 params: &'a CodeActionParams,
7907 edits: TextEdits<'a>,
7908 actions: Vec<CodeAction>,
7909}
7910
7911impl<'a> InlineVariable<'a> {
7912 pub fn new(
7913 module: &'a Module,
7914 line_numbers: &'a LineNumbers,
7915 params: &'a CodeActionParams,
7916 ) -> Self {
7917 Self {
7918 module,
7919 params,
7920 edits: TextEdits::new(line_numbers),
7921 actions: Vec::new(),
7922 }
7923 }
7924
7925 pub fn code_actions(mut self) -> Vec<CodeAction> {
7926 self.visit_typed_module(&self.module.ast);
7927
7928 self.actions
7929 }
7930
7931 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) {
7932 let references =
7933 FindVariableReferences::new(location, name).find_in_module(&self.module.ast);
7934 let reference = if references.len() == 1 {
7935 references
7936 .into_iter()
7937 .next()
7938 .expect("References has length 1")
7939 } else {
7940 return;
7941 };
7942
7943 let Some(ast::Statement::Assignment(assignment)) =
7944 self.module.ast.find_statement(location.start)
7945 else {
7946 return;
7947 };
7948
7949 // If the assignment does not simple bind a variable, for example:
7950 // ```gleam
7951 // let #(first, second, third)
7952 // io.println(first)
7953 // // ^ Inline here
7954 // ```
7955 // We can't inline it.
7956 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
7957 return;
7958 }
7959
7960 // If the assignment was generated by the compiler, it doesn't have a
7961 // syntactical representation, so we can't inline it.
7962 if matches!(assignment.kind, AssignmentKind::Generated) {
7963 return;
7964 }
7965
7966 let value_location = assignment.value.location();
7967 let value = self
7968 .module
7969 .code
7970 .get(value_location.start as usize..value_location.end as usize)
7971 .expect("Span is valid");
7972
7973 match reference.kind {
7974 VariableReferenceKind::Variable => {
7975 self.edits.replace(reference.location, value.into());
7976 }
7977 VariableReferenceKind::LabelShorthand => {
7978 self.edits
7979 .insert(reference.location.end, format!(" {value}"));
7980 }
7981 }
7982
7983 let mut location = assignment.location;
7984 location.end = next_nonwhitespace(&self.module.code, location.end);
7985
7986 self.edits.delete(location);
7987
7988 CodeActionBuilder::new("Inline variable")
7989 .kind(CodeActionKind::RefactorInline)
7990 .changes(
7991 self.params.text_document.uri.clone(),
7992 std::mem::take(&mut self.edits.edits),
7993 )
7994 .preferred(false)
7995 .push_to(&mut self.actions);
7996 }
7997}
7998
7999impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
8000 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
8001 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else {
8002 ast::visit::visit_typed_assignment(self, assignment);
8003 return;
8004 };
8005
8006 // We special case assignment variables because we want to trigger the
8007 // code action also if we're over the let keyword:
8008 //
8009 // ```gleam
8010 // let wibble = 11
8011 // // ^^^^^^^^^^ Here!
8012 // ```
8013 //
8014 let assignment_range = self
8015 .edits
8016 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end));
8017 if !within(self.params.range, assignment_range) {
8018 ast::visit::visit_typed_assignment(self, assignment);
8019 return;
8020 }
8021
8022 self.maybe_inline(*location, name.clone());
8023 }
8024
8025 fn visit_typed_expr_var(
8026 &mut self,
8027 location: &'ast SrcSpan,
8028 constructor: &'ast ValueConstructor,
8029 name: &'ast EcoString,
8030 ) {
8031 let range = self.edits.src_span_to_lsp_range(*location);
8032
8033 if !within(self.params.range, range) {
8034 return;
8035 }
8036
8037 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
8038 &constructor.variant
8039 else {
8040 return;
8041 };
8042
8043 // We can only inline variables assigned by `let` statements, as it
8044 //doesn't make sense to do so with any other kind of variable.
8045 match origin.declaration {
8046 VariableDeclaration::LetPattern => {}
8047 VariableDeclaration::UsePattern
8048 | VariableDeclaration::ClausePattern
8049 | VariableDeclaration::FunctionParameter { .. }
8050 | VariableDeclaration::Generated => return,
8051 }
8052
8053 self.maybe_inline(*location, name.clone());
8054 }
8055
8056 fn visit_typed_pattern_variable(
8057 &mut self,
8058 location: &'ast SrcSpan,
8059 name: &'ast EcoString,
8060 _type: &'ast Arc<Type>,
8061 origin: &'ast VariableOrigin,
8062 ) {
8063 // We can only inline variables assigned by `let` statements, as it
8064 //doesn't make sense to do so with any other kind of variable.
8065 match origin.declaration {
8066 VariableDeclaration::LetPattern => {}
8067 VariableDeclaration::UsePattern
8068 | VariableDeclaration::ClausePattern
8069 | VariableDeclaration::FunctionParameter { .. }
8070 | VariableDeclaration::Generated => return,
8071 }
8072
8073 let range = self.edits.src_span_to_lsp_range(*location);
8074
8075 if !within(self.params.range, range) {
8076 return;
8077 }
8078
8079 self.maybe_inline(*location, name.clone());
8080 }
8081}
8082
8083/// Builder for the "convert to pipe" code action.
8084///
8085/// ```gleam
8086/// pub fn main() {
8087/// wibble(wobble, woo)
8088/// // ^ [convert to pipe]
8089/// }
8090/// ```
8091///
8092/// Will turn the code into the following pipeline:
8093///
8094/// ```gleam
8095/// pub fn main() {
8096/// wobble |> wibble(woo)
8097/// }
8098/// ```
8099///
8100pub struct ConvertToPipe<'a> {
8101 module: &'a Module,
8102 params: &'a CodeActionParams,
8103 edits: TextEdits<'a>,
8104 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
8105 visited_item: VisitedItem,
8106}
8107
8108pub enum VisitedItem {
8109 RegularExpression,
8110 UseRightHandSide,
8111 PipelineFinalStep,
8112}
8113
8114/// Holds all the data needed by the "convert to pipe" code action to properly
8115/// rewrite a call into a pipe. Here's what each span means:
8116///
8117/// ```gleam
8118/// wibble(wobb|le, woo)
8119/// // ^^^^^^^^^^^^^^^^^^^^ call
8120/// // ^^^^^^ called
8121/// // ^^^^^^^ arg
8122/// // ^^^ next arg
8123/// ```
8124///
8125/// In this example `position` is 0, since the cursor is over the first
8126/// argument.
8127///
8128pub struct ConvertToPipeArg<'a> {
8129 /// The span of the called function.
8130 called: SrcSpan,
8131 /// The span of the entire function call.
8132 call: SrcSpan,
8133 /// The position (0-based) of the argument.
8134 position: usize,
8135 /// The argument we have to pipe.
8136 arg: &'a TypedCallArg,
8137 /// The span of the argument following the one we have to pipe, if there's
8138 /// any.
8139 next_arg: Option<SrcSpan>,
8140}
8141
8142impl<'a> ConvertToPipe<'a> {
8143 pub fn new(
8144 module: &'a Module,
8145 line_numbers: &'a LineNumbers,
8146 params: &'a CodeActionParams,
8147 ) -> Self {
8148 Self {
8149 module,
8150 params,
8151 edits: TextEdits::new(line_numbers),
8152 visited_item: VisitedItem::RegularExpression,
8153 argument_to_pipe: None,
8154 }
8155 }
8156
8157 pub fn code_actions(mut self) -> Vec<CodeAction> {
8158 self.visit_typed_module(&self.module.ast);
8159
8160 let Some(ConvertToPipeArg {
8161 called,
8162 call,
8163 position,
8164 arg,
8165 next_arg,
8166 }) = self.argument_to_pipe
8167 else {
8168 return vec![];
8169 };
8170
8171 let arg_location = if arg.uses_label_shorthand() {
8172 SrcSpan {
8173 start: arg.location.start,
8174 end: arg.location.end - 1,
8175 }
8176 } else if arg.label.is_some() {
8177 arg.value.location()
8178 } else {
8179 arg.location
8180 };
8181
8182 let arg_text = code_at(self.module, arg_location);
8183 // If the expression being piped is a binary operation with
8184 // precedence lower than pipes then we have to wrap it in curly
8185 // braces to not mess with the order of operations.
8186 let arg_text = if let TypedExpr::BinOp { operator, .. } = arg.value
8187 && operator.precedence() < PIPE_PRECEDENCE
8188 {
8189 &format!("{{ {arg_text} }}")
8190 } else {
8191 arg_text
8192 };
8193
8194 match next_arg {
8195 // When extracting an argument we never want to remove any explicit
8196 // label that was written down, so in case it is labelled (be it a
8197 // shorthand or not) we'll always replace the value with a `_`
8198 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
8199 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
8200
8201 // Now we can deal with unlabelled arguments:
8202 // If we're removing the first argument and there's other arguments
8203 // after it, we need to delete the comma that was separating the
8204 // two.
8205 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
8206 start: arg.location.start,
8207 end: next_arg.start,
8208 }),
8209 // Otherwise, if we're deleting the first argument and there's
8210 // no other arguments following it, we remove the call's
8211 // parentheses.
8212 None if position == 0 => self.edits.delete(SrcSpan {
8213 start: called.end,
8214 end: call.end,
8215 }),
8216 // In all other cases we're piping something that is not the first
8217 // argument so we just replace it with an `_`.
8218 _ => self.edits.replace(arg.location, "_".into()),
8219 }
8220
8221 // Finally we can add the argument that was removed as the first step
8222 // of the newly defined pipeline.
8223 self.edits.insert(call.start, format!("{arg_text} |> "));
8224
8225 let mut action = Vec::with_capacity(1);
8226 CodeActionBuilder::new("Convert to pipe")
8227 .kind(CodeActionKind::RefactorRewrite)
8228 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8229 .preferred(false)
8230 .push_to(&mut action);
8231 action
8232 }
8233}
8234
8235impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
8236 fn visit_typed_expr_call(
8237 &mut self,
8238 location: &'ast SrcSpan,
8239 _type_: &'ast Arc<Type>,
8240 fun: &'ast TypedExpr,
8241 arguments: &'ast [TypedCallArg],
8242 _open_parenthesis: &'ast Option<u32>,
8243 ) {
8244 if arguments.iter().any(|arg| arg.is_capture_hole()) {
8245 return;
8246 }
8247
8248 // If we're visiting the typed function produced by typing a use, we
8249 // skip the thing itself and only visit its arguments and called
8250 // function, that is the body of the use.
8251 match self.visited_item {
8252 VisitedItem::RegularExpression => (),
8253 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => {
8254 self.visited_item = VisitedItem::RegularExpression;
8255 ast::visit::visit_typed_expr(self, fun);
8256 arguments
8257 .iter()
8258 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
8259 return;
8260 }
8261 }
8262
8263 // We only visit a call if the cursor is somewhere within its location,
8264 // otherwise we skip it entirely.
8265 let call_range = self.edits.src_span_to_lsp_range(*location);
8266 if !within(self.params.range, call_range) {
8267 return;
8268 }
8269
8270 // If the cursor is over any of the arguments then we'll use that as
8271 // the one to extract.
8272 // Otherwise the cursor must be over the called function, in that case
8273 // we extract the first argument (if there's one):
8274 //
8275 // ```gleam
8276 // wibble(wobble, woo)
8277 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
8278 // // ^^^ pipe the second argument if I'm here
8279 // ```
8280 let argument_to_pipe = arguments
8281 .iter()
8282 .enumerate()
8283 .find_map(|(position, arg)| {
8284 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
8285 if within(self.params.range, arg_range) {
8286 Some((position, arg))
8287 } else {
8288 None
8289 }
8290 })
8291 .or_else(|| arguments.first().map(|argument| (0, argument)));
8292
8293 // If we're not hovering over any of the arguments _or_ there's no
8294 // argument to extract at all we just return, there's nothing we can do
8295 // on this call or any of its arguments (since we've determined the
8296 // cursor is not over any of those).
8297 let Some((position, arg)) = argument_to_pipe else {
8298 return;
8299 };
8300
8301 self.argument_to_pipe = Some(ConvertToPipeArg {
8302 called: fun.location(),
8303 call: *location,
8304 position,
8305 arg,
8306 next_arg: arguments
8307 .get(position + 1)
8308 .map(|argument| argument.location),
8309 });
8310
8311 // We still want to visit the arguments so that if we're hovering a
8312 // nested pipeline, that's going to be the one we transform:
8313 //
8314 // ```gleam
8315 // wibble(Wobble(
8316 // field: call(other(last(1)))
8317 // // ^^^^ We want to convert this one if we hover over it,
8318 // // not the outer `wibble(Wobble(...))` call
8319 // ))
8320 // ```
8321 //
8322 for argument in arguments {
8323 ast::visit::visit_typed_call_arg(self, argument);
8324 }
8325 }
8326
8327 fn visit_typed_expr_pipeline(
8328 &mut self,
8329 _location: &'ast SrcSpan,
8330 first_value: &'ast TypedPipelineAssignment,
8331 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
8332 finally: &'ast TypedExpr,
8333 _finally_kind: &'ast PipelineAssignmentKind,
8334 ) {
8335 // We can only apply the action on the first step of a pipeline, so we
8336 // visit just that one and skip all the others.
8337 ast::visit::visit_typed_pipeline_assignment(self, first_value);
8338 self.visited_item = VisitedItem::PipelineFinalStep;
8339 ast::visit::visit_typed_expr(self, finally);
8340 }
8341
8342 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
8343 self.visited_item = VisitedItem::UseRightHandSide;
8344 ast::visit::visit_typed_use(self, use_);
8345 }
8346}
8347
8348/// Code action to interpolate a string. If the cursor is inside the string
8349/// (not selecting anything) the language server will offer to split it:
8350///
8351/// ```gleam
8352/// "wibble | wobble"
8353/// // ^ [Split string]
8354/// // Will produce the following
8355/// "wibble " <> todo <> " wobble"
8356/// ```
8357///
8358/// If the cursor is selecting an entire valid gleam name, then the language
8359/// server will offer to interpolate it as a variable:
8360///
8361/// ```gleam
8362/// "wibble wobble woo"
8363/// // ^^^^^^ [Interpolate variable]
8364/// // Will produce the following
8365/// "wibble " <> wobble <> " woo"
8366/// ```
8367///
8368/// > Note: the cursor won't end up right after the inserted variable/todo.
8369/// > that's a bit annoying, but in a future LSP version we will be able to
8370/// > isnert tab stops to allow one to jump to the newly added variable/todo.
8371///
8372pub struct InterpolateString<'a> {
8373 module: &'a Module,
8374 params: &'a CodeActionParams,
8375 edits: TextEdits<'a>,
8376 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
8377 string_literal_position: StringLiteralPosition,
8378}
8379
8380#[derive(Debug, Clone, Copy, Eq, PartialEq)]
8381pub enum StringLiteralPosition {
8382 FirstPipelineStep,
8383 Other,
8384}
8385
8386#[derive(Clone, Copy)]
8387enum StringInterpolation {
8388 InterpolateValue { value_location: SrcSpan },
8389 SplitString { split_at: u32 },
8390}
8391
8392impl<'a> InterpolateString<'a> {
8393 pub fn new(
8394 module: &'a Module,
8395 line_numbers: &'a LineNumbers,
8396 params: &'a CodeActionParams,
8397 ) -> Self {
8398 Self {
8399 module,
8400 params,
8401 edits: TextEdits::new(line_numbers),
8402 string_interpolation: None,
8403 string_literal_position: StringLiteralPosition::Other,
8404 }
8405 }
8406
8407 pub fn code_actions(mut self) -> Vec<CodeAction> {
8408 self.visit_typed_module(&self.module.ast);
8409
8410 let Some((string_location, interpolation)) = self.string_interpolation else {
8411 return vec![];
8412 };
8413
8414 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
8415 self.edits.insert(string_location.start, "{ ".into());
8416 }
8417
8418 match interpolation {
8419 StringInterpolation::InterpolateValue { value_location } => {
8420 let name = self
8421 .module
8422 .code
8423 .get(value_location.start as usize..value_location.end as usize)
8424 .expect("invalid value range");
8425
8426 // We trust that the programmer has correctly selected the part
8427 // of the string they want to interpolate and simply "cut it out"
8428 // for them. In future, we could try and parse their selection to
8429 // see if it is a valid expression in Gleam.
8430 if value_location.start == string_location.start + 1 {
8431 self.edits
8432 .insert(string_location.start, format!("{name} <> "));
8433 } else if value_location.end == string_location.end - 1 {
8434 self.edits
8435 .insert(string_location.end, format!(" <> {name}"));
8436 } else {
8437 self.edits
8438 .insert(value_location.start, format!("\" <> {name} <> \""));
8439 }
8440 self.edits.delete(value_location);
8441 }
8442
8443 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
8444 self.edits.insert(split_at, "\" <> todo <> \"".into());
8445 }
8446
8447 StringInterpolation::SplitString { .. } => return vec![],
8448 }
8449
8450 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
8451 self.edits.insert(string_location.end, " }".into());
8452 }
8453
8454 let mut action = Vec::with_capacity(1);
8455 CodeActionBuilder::new("Interpolate string")
8456 .kind(CodeActionKind::RefactorRewrite)
8457 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8458 .preferred(false)
8459 .push_to(&mut action);
8460 action
8461 }
8462
8463 fn can_split_string_at(&self, at: u32) -> bool {
8464 self.string_interpolation
8465 .is_some_and(|(string_location, _)| {
8466 !(at <= string_location.start + 1 || at >= string_location.end - 1)
8467 })
8468 }
8469
8470 fn visit_literal_string(
8471 &mut self,
8472 string_location: SrcSpan,
8473 string_position: StringLiteralPosition,
8474 ) {
8475 // We can only interpolate/split a string if the cursor is somewhere
8476 // within its location, otherwise we skip it.
8477 let string_range = self.edits.src_span_to_lsp_range(string_location);
8478 if !within(self.params.range, string_range) {
8479 return;
8480 }
8481
8482 let selection @ SrcSpan { start, end } =
8483 self.edits.lsp_range_to_src_span(self.params.range);
8484
8485 // We can't interpolate/split if the double quotes delimiting the
8486 // string have been selected.
8487 if start == string_location.start || end == string_location.end {
8488 return;
8489 }
8490
8491 let name = self
8492 .module
8493 .code
8494 .get(start as usize..end as usize)
8495 .expect("invalid value range");
8496
8497 // TUI editors like Helix and Kakoune that use the selection-action edit
8498 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always
8499 // have something selected. For programmers using these editors, the
8500 // smallest selection possible is a 1-character selection. The best we can do
8501 // to provide parity with other editors is to consider a single-character SPACE
8502 // selection as an empty selection, as they most likely want to split the
8503 // string instead of interpolating a variable.
8504 let interpolation = if start == end || (end - start == 1 && name == " ") {
8505 StringInterpolation::SplitString { split_at: start }
8506 } else {
8507 StringInterpolation::InterpolateValue {
8508 value_location: selection,
8509 }
8510 };
8511 self.string_interpolation = Some((string_location, interpolation));
8512 self.string_literal_position = string_position;
8513 }
8514}
8515
8516impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
8517 fn visit_typed_expr_string(
8518 &mut self,
8519 location: &'ast SrcSpan,
8520 _type_: &'ast Arc<Type>,
8521 _value: &'ast EcoString,
8522 ) {
8523 self.visit_literal_string(*location, StringLiteralPosition::Other);
8524 }
8525
8526 fn visit_typed_expr_pipeline(
8527 &mut self,
8528 _location: &'ast SrcSpan,
8529 first_value: &'ast TypedPipelineAssignment,
8530 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
8531 finally: &'ast TypedExpr,
8532 _finally_kind: &'ast PipelineAssignmentKind,
8533 ) {
8534 if first_value.value.is_literal_string() {
8535 self.visit_literal_string(
8536 first_value.location,
8537 StringLiteralPosition::FirstPipelineStep,
8538 );
8539 } else {
8540 ast::visit::visit_typed_pipeline_assignment(self, first_value);
8541 }
8542
8543 assignments
8544 .iter()
8545 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
8546 self.visit_typed_expr(finally);
8547 }
8548}
8549
8550/// Code action to replace a `..` in a pattern with all the missing fields that
8551/// have not been explicitly provided; labelled ones are introduced with the
8552/// shorthand syntax.
8553///
8554/// ```gleam
8555/// pub type Pokemon {
8556/// Pokemon(Int, name: String, moves: List(String))
8557/// }
8558///
8559/// pub fn main() {
8560/// let Pokemon(..) = todo
8561/// // ^^ Cursor over the spread
8562/// }
8563/// ```
8564/// Would become
8565/// ```gleam
8566/// pub fn main() {
8567/// let Pokemon(int, name:, moves:) = todo
8568/// }
8569///
8570pub struct FillUnusedFields<'a> {
8571 module: &'a Module,
8572 params: &'a CodeActionParams,
8573 edits: TextEdits<'a>,
8574 data: Option<FillUnusedFieldsData>,
8575}
8576
8577pub struct FillUnusedFieldsData {
8578 /// All the missing positional and labelled fields.
8579 positional: Vec<Arc<Type>>,
8580 labelled: Vec<(EcoString, Arc<Type>)>,
8581 /// We need this in order to tell where the missing positional arguments
8582 /// should be inserted.
8583 first_labelled_argument_start: Option<u32>,
8584 /// The end of the final argument before the spread, if there's any.
8585 /// We'll use this to delete everything that comes after the final argument,
8586 /// after adding all the ignored fields.
8587 last_argument_end: Option<u32>,
8588 spread_location: SrcSpan,
8589}
8590
8591impl<'a> FillUnusedFields<'a> {
8592 pub fn new(
8593 module: &'a Module,
8594 line_numbers: &'a LineNumbers,
8595 params: &'a CodeActionParams,
8596 ) -> Self {
8597 Self {
8598 module,
8599 params,
8600 edits: TextEdits::new(line_numbers),
8601 data: None,
8602 }
8603 }
8604
8605 pub fn code_actions(mut self) -> Vec<CodeAction> {
8606 self.visit_typed_module(&self.module.ast);
8607
8608 let Some(FillUnusedFieldsData {
8609 positional,
8610 labelled,
8611 first_labelled_argument_start,
8612 last_argument_end,
8613 spread_location,
8614 }) = self.data
8615 else {
8616 return vec![];
8617 };
8618
8619 // Do not suggest this code action if there's no ignored fields at all.
8620 if positional.is_empty() && labelled.is_empty() {
8621 return vec![];
8622 }
8623
8624 // We add all the missing positional arguments before the first
8625 // labelled one (and so after all the already existing positional ones).
8626 if !positional.is_empty() {
8627 // We want to make sure that all positional args will have a name
8628 // that's different from any label. So we add those as already used
8629 // names.
8630 let mut names = NameGenerator::new();
8631 for (label, _) in labelled.iter() {
8632 names.add_used_name(label.clone());
8633 }
8634
8635 let positional_arguments = positional
8636 .iter()
8637 .map(|type_| names.generate_name_from_type(type_))
8638 .join(", ");
8639 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
8640
8641 // The positional arguments are going to be followed by some other
8642 // arguments if there's some already existing labelled args
8643 // (`last_argument_end.is_some`), of if we're adding those labelled args
8644 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
8645 // final positional argument we're adding to separate it from the ones that
8646 // are going to come after.
8647 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
8648 let positional_arguments = if has_arguments_after {
8649 format!("{positional_arguments}, ")
8650 } else {
8651 positional_arguments
8652 };
8653
8654 self.edits.insert(insert_at, positional_arguments);
8655 }
8656
8657 if !labelled.is_empty() {
8658 // If there's labelled arguments to add, we replace the existing spread
8659 // with the arguments to be added. This way commas and all should already
8660 // be correct.
8661 let labelled_arguments = labelled
8662 .iter()
8663 .map(|(label, _)| format!("{label}:"))
8664 .join(", ");
8665 self.edits.replace(spread_location, labelled_arguments);
8666 } else if let Some(delete_start) = last_argument_end {
8667 // However, if there's no labelled arguments to insert we still need
8668 // to delete the entire spread: we start deleting from the end of the
8669 // final argument, if there's one.
8670 // This way we also get rid of any comma separating the last argument
8671 // and the spread to be removed.
8672 self.edits
8673 .delete(SrcSpan::new(delete_start, spread_location.end));
8674 } else {
8675 // Otherwise we just delete the spread.
8676 self.edits.delete(spread_location);
8677 }
8678
8679 let mut action = Vec::with_capacity(1);
8680 CodeActionBuilder::new("Fill unused fields")
8681 .kind(CodeActionKind::RefactorRewrite)
8682 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8683 .preferred(false)
8684 .push_to(&mut action);
8685 action
8686 }
8687}
8688
8689impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
8690 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
8691 // We can only interpolate/split a string if the cursor is somewhere
8692 // within its location, otherwise we skip it.
8693 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
8694 if !within(self.params.range, pattern_range) {
8695 return;
8696 }
8697
8698 if let TypedPattern::Constructor {
8699 arguments,
8700 spread: Some(spread_location),
8701 ..
8702 } = pattern
8703 && let Some(PatternUnusedArguments {
8704 positional,
8705 labelled,
8706 }) = pattern.unused_arguments()
8707 {
8708 // If there's any unused argument that's being ignored we want to
8709 // suggest the code action.
8710 let first_labelled_argument_start = arguments
8711 .iter()
8712 .find(|arg| !arg.is_implicit() && arg.label.is_some())
8713 .map(|arg| arg.location.start);
8714
8715 let last_argument_end = arguments
8716 .iter()
8717 .rfind(|arg| !arg.is_implicit())
8718 .map(|arg| arg.location.end);
8719
8720 self.data = Some(FillUnusedFieldsData {
8721 positional,
8722 labelled,
8723 first_labelled_argument_start,
8724 last_argument_end,
8725 spread_location: *spread_location,
8726 });
8727 }
8728
8729 ast::visit::visit_typed_pattern(self, pattern);
8730 }
8731}
8732
8733/// Code action to remove an echo.
8734///
8735pub struct RemoveEchos<'a> {
8736 module: &'a Module,
8737 params: &'a CodeActionParams,
8738 edits: TextEdits<'a>,
8739 is_hovering_echo: bool,
8740 echo_spans_to_delete: Vec<SrcSpan>,
8741 // We need to keep a reference to the two latest pipeline assignments we
8742 // run into to properly delete an echo that's inside a pipeline.
8743 latest_pipe_step: Option<SrcSpan>,
8744 second_to_latest_pipe_step: Option<SrcSpan>,
8745}
8746
8747impl<'a> RemoveEchos<'a> {
8748 pub fn new(
8749 module: &'a Module,
8750 line_numbers: &'a LineNumbers,
8751 params: &'a CodeActionParams,
8752 ) -> Self {
8753 Self {
8754 module,
8755 params,
8756 edits: TextEdits::new(line_numbers),
8757 is_hovering_echo: false,
8758 echo_spans_to_delete: vec![],
8759 latest_pipe_step: None,
8760 second_to_latest_pipe_step: None,
8761 }
8762 }
8763
8764 pub fn code_actions(mut self) -> Vec<CodeAction> {
8765 self.visit_typed_module(&self.module.ast);
8766
8767 // We only want to trigger the action if we're over one of the echos in
8768 // the module
8769 if !self.is_hovering_echo {
8770 return vec![];
8771 }
8772
8773 for span in self.echo_spans_to_delete {
8774 self.edits.delete(span);
8775 }
8776
8777 let mut action = Vec::with_capacity(1);
8778 CodeActionBuilder::new("Remove all `echo`s from this module")
8779 .kind(CodeActionKind::RefactorRewrite)
8780 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8781 .preferred(false)
8782 .push_to(&mut action);
8783 action
8784 }
8785
8786 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
8787 for i in 0..statements.len() {
8788 let statement = statements
8789 .get(i)
8790 .expect("Statement must exist in iteration");
8791 let next_statement = statements.get(i + 1);
8792 let is_last = i == statements.len() - 1;
8793
8794 match statement {
8795 // We remove any echo that is used as a standalone statement used
8796 // to print a literal value.
8797 //
8798 // ```gleam
8799 // pub fn main() {
8800 // echo "I'm here"
8801 // do_something()
8802 // echo "Safe!"
8803 // do_something_else()
8804 // }
8805 // ```
8806 //
8807 // Here we want to remove not just the echo but also the literal
8808 // strings they're printing.
8809 //
8810 // It's safe to do this only if echo is not the last expression
8811 // in a function's block (otherwise we might change the function's
8812 // return type by removing the entire line) and the value being
8813 // printed is a literal expression.
8814 //
8815 ast::Statement::Expression(TypedExpr::Echo {
8816 location,
8817 expression,
8818 ..
8819 }) if !is_last
8820 && expression.as_ref().is_some_and(|expression| {
8821 expression.is_literal() || expression.is_var()
8822 }) =>
8823 {
8824 let echo_range = self.edits.src_span_to_lsp_range(*location);
8825 if within(self.params.range, echo_range) {
8826 self.is_hovering_echo = true;
8827 }
8828
8829 let end = next_statement
8830 .map(|next| {
8831 let echo_end = location.end;
8832 let next_start = next.location().start;
8833 // We want to remove everything until the start of the
8834 // following statement. However, we have to be careful not to
8835 // delete any comments. So if there's any comment between the
8836 // echo to remove and the next statement, we just delete until
8837 // the comment's start.
8838 self.module
8839 .extra
8840 .first_comment_between(echo_end, next_start)
8841 // For comments we record the start of their content, not of the `//`
8842 // so we're subtracting 2 here to not delete the `//` as well
8843 .map(|comment| comment.start - 2)
8844 .unwrap_or(next_start)
8845 })
8846 .unwrap_or(location.end);
8847
8848 self.echo_spans_to_delete.push(SrcSpan {
8849 start: location.start,
8850 end,
8851 });
8852 }
8853
8854 // Otherwise we visit the statement as usual.
8855 ast::Statement::Expression(_)
8856 | ast::Statement::Assignment(_)
8857 | ast::Statement::Use(_)
8858 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement),
8859 }
8860 }
8861 }
8862}
8863
8864impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
8865 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
8866 self.visit_function_statements(&fun.body);
8867 }
8868
8869 fn visit_typed_expr_fn(
8870 &mut self,
8871 _location: &'ast SrcSpan,
8872 _type_: &'ast Arc<Type>,
8873 _kind: &'ast FunctionLiteralKind,
8874 _arguments: &'ast [TypedArg],
8875 body: &'ast Vec1<TypedStatement>,
8876 _return_annotation: &'ast Option<ast::TypeAst>,
8877 ) {
8878 self.visit_function_statements(body);
8879 }
8880
8881 fn visit_typed_expr_echo(
8882 &mut self,
8883 location: &'ast SrcSpan,
8884 type_: &'ast Arc<Type>,
8885 expression: &'ast Option<Box<TypedExpr>>,
8886 message: &'ast Option<Box<TypedExpr>>,
8887 ) {
8888 // We also want to trigger the action if we're hovering over the expression
8889 // being printed. So we create a unique span starting from the start of echo
8890 // end ending at the end of the expression.
8891 //
8892 // ```
8893 // echo 1 + 2
8894 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
8895 // inside it, not just the keyword
8896 // ```
8897 //
8898 let echo_range = self.edits.src_span_to_lsp_range(*location);
8899 if within(self.params.range, echo_range) {
8900 self.is_hovering_echo = true;
8901 }
8902
8903 // We also want to remove the echo message!
8904 if message.is_some() {
8905 let start = expression
8906 .as_ref()
8907 .map(|expression| expression.location().end)
8908 .unwrap_or(location.start + 4);
8909
8910 self.echo_spans_to_delete
8911 .push(SrcSpan::new(start, location.end));
8912 }
8913
8914 if let Some(expression) = expression {
8915 // If there's an expression we delete everything we find until its
8916 // start (excluded).
8917 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
8918 self.echo_spans_to_delete.push(span_to_delete);
8919 } else {
8920 // Othwerise we know we're inside a pipeline, we take the closest step
8921 // that is not echo itself and delete everything from its end until the
8922 // end of the echo keyword:
8923 //
8924 // ```txt
8925 // wibble |> echo |> wobble
8926 // ^^^^^^^^ This span right here
8927 // ```
8928 let step_preceding_echo = self
8929 .latest_pipe_step
8930 .filter(|l| l != location)
8931 .or(self.second_to_latest_pipe_step);
8932 if let Some(step_preceding_echo) = step_preceding_echo {
8933 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
8934 self.echo_spans_to_delete.push(span_to_delete);
8935 }
8936 }
8937
8938 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message);
8939 }
8940
8941 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
8942 if self.latest_pipe_step.is_some() {
8943 self.second_to_latest_pipe_step = self.latest_pipe_step;
8944 }
8945 self.latest_pipe_step = Some(assignment.location);
8946 ast::visit::visit_typed_pipeline_assignment(self, assignment);
8947 }
8948}
8949
8950/// Code action to wrap assignment and case clause values in a block.
8951///
8952/// ```gleam
8953/// pub type PokemonType {
8954/// Fire
8955/// Water
8956/// }
8957///
8958/// pub fn main() {
8959/// let pokemon_type: PokemonType = todo
8960/// case pokemon_type {
8961/// Water -> soak()
8962/// ^^^^^^ Cursor over the spread
8963/// Fire -> burn()
8964/// }
8965/// }
8966/// ```
8967/// Becomes
8968/// ```gleam
8969/// pub type PokemonType {
8970/// Fire
8971/// Water
8972/// }
8973///
8974/// pub fn main() {
8975/// let pokemon_type: PokemonType = todo
8976/// case pokemon_type {
8977/// Water -> {
8978/// soak()
8979/// }
8980/// Fire -> burn()
8981/// }
8982/// }
8983/// ```
8984///
8985pub struct WrapInBlock<'a> {
8986 module: &'a Module,
8987 params: &'a CodeActionParams,
8988 edits: TextEdits<'a>,
8989 selected_expression: Option<SrcSpan>,
8990}
8991
8992impl<'a> WrapInBlock<'a> {
8993 pub fn new(
8994 module: &'a Module,
8995 line_numbers: &'a LineNumbers,
8996 params: &'a CodeActionParams,
8997 ) -> Self {
8998 Self {
8999 module,
9000 params,
9001 edits: TextEdits::new(line_numbers),
9002 selected_expression: None,
9003 }
9004 }
9005
9006 pub fn code_actions(mut self) -> Vec<CodeAction> {
9007 self.visit_typed_module(&self.module.ast);
9008
9009 let Some(expr_span) = self.selected_expression else {
9010 return vec![];
9011 };
9012
9013 let Some(expr_string) = self
9014 .module
9015 .code
9016 .get(expr_span.start as usize..(expr_span.end as usize + 1))
9017 else {
9018 return vec![];
9019 };
9020
9021 let range = self
9022 .edits
9023 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
9024
9025 let indent_size =
9026 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
9027
9028 let expr_indent_size = indent_size + 2;
9029
9030 let indent = " ".repeat(indent_size);
9031 let inner_indent = " ".repeat(expr_indent_size);
9032
9033 self.edits.replace(
9034 expr_span,
9035 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
9036 );
9037
9038 let mut action = Vec::with_capacity(1);
9039 CodeActionBuilder::new("Wrap in block")
9040 .kind(CodeActionKind::RefactorExtract)
9041 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9042 .preferred(false)
9043 .push_to(&mut action);
9044 action
9045 }
9046}
9047
9048impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
9049 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
9050 ast::visit::visit_typed_expr(self, &assignment.value);
9051 if !within(
9052 self.params.range,
9053 self.edits
9054 .src_span_to_lsp_range(assignment.value.location()),
9055 ) {
9056 return;
9057 }
9058 match &assignment.value {
9059 // To avoid wrapping the same expression in multiple, nested blocks.
9060 TypedExpr::Block { .. } => {}
9061 TypedExpr::RecordAccess { .. }
9062 | TypedExpr::PositionalAccess { .. }
9063 | TypedExpr::Int { .. }
9064 | TypedExpr::Float { .. }
9065 | TypedExpr::String { .. }
9066 | TypedExpr::Pipeline { .. }
9067 | TypedExpr::Var { .. }
9068 | TypedExpr::Fn { .. }
9069 | TypedExpr::List { .. }
9070 | TypedExpr::Call { .. }
9071 | TypedExpr::BinOp { .. }
9072 | TypedExpr::Case { .. }
9073 | TypedExpr::ModuleSelect { .. }
9074 | TypedExpr::Tuple { .. }
9075 | TypedExpr::TupleIndex { .. }
9076 | TypedExpr::Todo { .. }
9077 | TypedExpr::Panic { .. }
9078 | TypedExpr::Echo { .. }
9079 | TypedExpr::BitArray { .. }
9080 | TypedExpr::RecordUpdate { .. }
9081 | TypedExpr::NegateBool { .. }
9082 | TypedExpr::NegateInt { .. }
9083 | TypedExpr::Invalid { .. } => {
9084 self.selected_expression = Some(assignment.value.location());
9085 }
9086 }
9087 ast::visit::visit_typed_assignment(self, assignment);
9088 }
9089
9090 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
9091 ast::visit::visit_typed_clause(self, clause);
9092
9093 if !within(
9094 self.params.range,
9095 self.edits.src_span_to_lsp_range(clause.then.location()),
9096 ) {
9097 return;
9098 }
9099
9100 // To avoid wrapping the same expression in multiple, nested blocks.
9101 if !matches!(clause.then, TypedExpr::Block { .. }) {
9102 self.selected_expression = Some(clause.then.location());
9103 }
9104
9105 ast::visit::visit_typed_clause(self, clause);
9106 }
9107}
9108
9109/// Code action to fix wrong binary operators when the compiler can easily tell
9110/// what the correct alternative is.
9111///
9112/// ```gleam
9113/// 1 +. 2 // becomes 1 + 2
9114/// 1.0 + 2.3 // becomes 1.0 +. 2.3
9115/// ```
9116///
9117pub struct FixBinaryOperation<'a> {
9118 module: &'a Module,
9119 params: &'a CodeActionParams,
9120 edits: TextEdits<'a>,
9121 fix: Option<(SrcSpan, ast::BinOp)>,
9122}
9123
9124impl<'a> FixBinaryOperation<'a> {
9125 pub fn new(
9126 module: &'a Module,
9127 line_numbers: &'a LineNumbers,
9128 params: &'a CodeActionParams,
9129 ) -> Self {
9130 Self {
9131 module,
9132 params,
9133 edits: TextEdits::new(line_numbers),
9134 fix: None,
9135 }
9136 }
9137
9138 pub fn code_actions(mut self) -> Vec<CodeAction> {
9139 self.visit_typed_module(&self.module.ast);
9140
9141 let Some((location, replacement)) = self.fix else {
9142 return vec![];
9143 };
9144
9145 self.edits.replace(location, replacement.name().into());
9146
9147 let mut action = Vec::with_capacity(1);
9148 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
9149 .kind(CodeActionKind::RefactorRewrite)
9150 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9151 .preferred(true)
9152 .push_to(&mut action);
9153 action
9154 }
9155
9156 fn try_fix(
9157 &mut self,
9158 left: Arc<Type>,
9159 right: Arc<Type>,
9160 operator: ast::BinOp,
9161 operator_start: u32,
9162 ) {
9163 let operator_location = SrcSpan::new(operator_start, operator_start + operator.size());
9164 if operator.is_int_operator() && left.is_float() && right.is_float() {
9165 self.fix = operator
9166 .float_equivalent()
9167 .map(|fix| (operator_location, fix));
9168 } else if operator.is_float_operator() && left.is_int() && right.is_int() {
9169 self.fix = operator
9170 .int_equivalent()
9171 .map(|fix| (operator_location, fix));
9172 } else if operator == ast::BinOp::AddInt && left.is_string() && right.is_string() {
9173 self.fix = Some((operator_location, ast::BinOp::Concatenate));
9174 }
9175 }
9176}
9177
9178impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
9179 fn visit_typed_expr_case(
9180 &mut self,
9181 location: &'ast SrcSpan,
9182 type_: &'ast Arc<Type>,
9183 subjects: &'ast [TypedExpr],
9184 clauses: &'ast [ast::TypedClause],
9185 compiled_case: &'ast CompiledCase,
9186 ) {
9187 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
9188 }
9189 fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) {
9190 ast::visit::visit_typed_clause_guard(self, guard);
9191 }
9192
9193 fn visit_typed_clause_guard_bin_op(
9194 &mut self,
9195 left: &'ast TypedClauseGuard,
9196 right: &'ast TypedClauseGuard,
9197 operator: &'ast ast::BinOp,
9198 operator_start: &'ast u32,
9199 location: &'ast SrcSpan,
9200 ) {
9201 let binop_range = self.edits.src_span_to_lsp_range(*location);
9202 if !within(self.params.range, binop_range) {
9203 return;
9204 }
9205
9206 self.try_fix(left.type_(), right.type_(), *operator, *operator_start);
9207
9208 ast::visit::visit_typed_clause_guard_bin_op(
9209 self,
9210 left,
9211 right,
9212 operator,
9213 operator_start,
9214 location,
9215 );
9216 }
9217
9218 fn visit_typed_expr_bin_op(
9219 &mut self,
9220 location: &'ast SrcSpan,
9221 type_: &'ast Arc<Type>,
9222 operator: &'ast ast::BinOp,
9223 operator_start: &'ast u32,
9224 left: &'ast TypedExpr,
9225 right: &'ast TypedExpr,
9226 ) {
9227 let binop_range = self.edits.src_span_to_lsp_range(*location);
9228 if !within(self.params.range, binop_range) {
9229 return;
9230 }
9231
9232 self.try_fix(left.type_(), right.type_(), *operator, *operator_start);
9233
9234 ast::visit::visit_typed_expr_bin_op(
9235 self,
9236 location,
9237 type_,
9238 operator,
9239 operator_start,
9240 left,
9241 right,
9242 );
9243 }
9244}
9245
9246/// Code action builder to automatically fix segments that have a value that's
9247/// guaranteed to overflow.
9248///
9249pub struct FixTruncatedBitArraySegment<'a> {
9250 module: &'a Module,
9251 params: &'a CodeActionParams,
9252 edits: TextEdits<'a>,
9253 truncation: Option<BitArraySegmentTruncation>,
9254}
9255
9256impl<'a> FixTruncatedBitArraySegment<'a> {
9257 pub fn new(
9258 module: &'a Module,
9259 line_numbers: &'a LineNumbers,
9260 params: &'a CodeActionParams,
9261 ) -> Self {
9262 Self {
9263 module,
9264 params,
9265 edits: TextEdits::new(line_numbers),
9266 truncation: None,
9267 }
9268 }
9269
9270 pub fn code_actions(mut self) -> Vec<CodeAction> {
9271 self.visit_typed_module(&self.module.ast);
9272
9273 let Some(truncation) = self.truncation else {
9274 return vec![];
9275 };
9276
9277 let replacement = truncation.truncated_into.to_string();
9278 self.edits
9279 .replace(truncation.value_location, replacement.clone());
9280
9281 let mut action = Vec::with_capacity(1);
9282 CodeActionBuilder::new(&format!("Replace with `{replacement}`"))
9283 .kind(CodeActionKind::RefactorRewrite)
9284 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9285 .preferred(true)
9286 .push_to(&mut action);
9287 action
9288 }
9289}
9290
9291impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
9292 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
9293 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
9294 if !within(self.params.range, segment_range) {
9295 return;
9296 }
9297
9298 if let Some(truncation) = segment.check_for_truncated_value() {
9299 self.truncation = Some(truncation);
9300 }
9301
9302 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
9303 }
9304}
9305
9306/// Code action builder to remove unused imports and values.
9307///
9308pub struct RemoveUnusedImports<'a> {
9309 module: &'a Module,
9310 params: &'a CodeActionParams,
9311 edits: TextEdits<'a>,
9312}
9313
9314#[derive(Debug)]
9315enum UnusedImport {
9316 ValueOrType(SrcSpan),
9317 Module(SrcSpan),
9318 ModuleAlias(SrcSpan),
9319}
9320
9321impl UnusedImport {
9322 fn location(&self) -> SrcSpan {
9323 match self {
9324 UnusedImport::ValueOrType(location)
9325 | UnusedImport::Module(location)
9326 | UnusedImport::ModuleAlias(location) => *location,
9327 }
9328 }
9329}
9330
9331impl<'a> RemoveUnusedImports<'a> {
9332 pub fn new(
9333 module: &'a Module,
9334 line_numbers: &'a LineNumbers,
9335 params: &'a CodeActionParams,
9336 ) -> Self {
9337 Self {
9338 module,
9339 params,
9340 edits: TextEdits::new(line_numbers),
9341 }
9342 }
9343
9344 /// Given an import location, returns a list of the spans of all the
9345 /// unqualified values it's importing. Sorted by SrcSpan location.
9346 ///
9347 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> {
9348 self.module
9349 .ast
9350 .definitions
9351 .imports
9352 .iter()
9353 .find(|import| import.location.contains(import_location.start))
9354 .map(|import| {
9355 let types = import.unqualified_types.iter().map(|type_| type_.location);
9356 let values = import.unqualified_values.iter().map(|value| value.location);
9357 types
9358 .chain(values)
9359 .sorted_by_key(|location| location.start)
9360 .collect_vec()
9361 })
9362 .unwrap_or_default()
9363 }
9364
9365 pub fn code_actions(mut self) -> Vec<CodeAction> {
9366 // If there's no import in the module then there can't be any unused
9367 // import to remove.
9368 if self.module.ast.definitions.imports.is_empty() {
9369 return vec![];
9370 }
9371
9372 let unused_imports = self
9373 .module
9374 .ast
9375 .type_info
9376 .warnings
9377 .iter()
9378 .filter_map(|warning| match warning {
9379 type_::Warning::UnusedImportedValue { location, .. } => {
9380 Some(UnusedImport::ValueOrType(*location))
9381 }
9382 type_::Warning::UnusedType {
9383 location,
9384 imported: true,
9385 ..
9386 } => Some(UnusedImport::ValueOrType(*location)),
9387 type_::Warning::UnusedImportedModule { location, .. } => {
9388 Some(UnusedImport::Module(*location))
9389 }
9390 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
9391 Some(UnusedImport::ModuleAlias(*location))
9392 }
9393 type_::Warning::Todo { .. }
9394 | type_::Warning::ImplicitlyDiscardedResult { .. }
9395 | type_::Warning::UnusedLiteral { .. }
9396 | type_::Warning::UnusedValue { .. }
9397 | type_::Warning::NoFieldsRecordUpdate { .. }
9398 | type_::Warning::AllFieldsRecordUpdate { .. }
9399 | type_::Warning::UnusedType { .. }
9400 | type_::Warning::UnusedConstructor { .. }
9401 | type_::Warning::UnusedPrivateModuleConstant { .. }
9402 | type_::Warning::UnusedPrivateFunction { .. }
9403 | type_::Warning::UnusedVariable { .. }
9404 | type_::Warning::UnnecessaryDoubleIntNegation { .. }
9405 | type_::Warning::UnnecessaryDoubleBoolNegation { .. }
9406 | type_::Warning::InefficientEmptyListCheck { .. }
9407 | type_::Warning::TransitiveDependencyImported { .. }
9408 | type_::Warning::DeprecatedItem { .. }
9409 | type_::Warning::UnreachableCasePattern { .. }
9410 | type_::Warning::UnusedDiscardPattern { .. }
9411 | type_::Warning::CaseMatchOnLiteralCollection { .. }
9412 | type_::Warning::CaseMatchOnLiteralValue { .. }
9413 | type_::Warning::OpaqueExternalType { .. }
9414 | type_::Warning::RedundantAssertAssignment { .. }
9415 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. }
9416 | type_::Warning::TodoOrPanicUsedAsFunction { .. }
9417 | type_::Warning::UnreachableCodeAfterPanic { .. }
9418 | type_::Warning::RedundantPipeFunctionCapture { .. }
9419 | type_::Warning::FeatureRequiresHigherGleamVersion { .. }
9420 | type_::Warning::JavaScriptIntUnsafe { .. }
9421 | type_::Warning::AssertLiteralBool { .. }
9422 | type_::Warning::BitArraySegmentTruncatedValue { .. }
9423 | type_::Warning::ModuleImportedTwice { .. }
9424 | type_::Warning::TopLevelDefinitionShadowsImport { .. }
9425 | type_::Warning::RedundantComparison { .. }
9426 | type_::Warning::UnusedRecursiveArgument { .. }
9427 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. }
9428 | type_::Warning::PipeIntoCallWhichReturnsFunction { .. } => None,
9429 })
9430 .sorted_by_key(|import| import.location())
9431 .collect_vec();
9432
9433 // If the cursor is not over any of the unused imports then we don't offer
9434 // the code action.
9435 let hovering_unused_import = unused_imports.iter().any(|import| {
9436 let unused_range = self.edits.src_span_to_lsp_range(import.location());
9437 overlaps(self.params.range, unused_range)
9438 });
9439 if !hovering_unused_import {
9440 return vec![];
9441 }
9442
9443 // Otherwise we start removing all unused imports:
9444 for import in &unused_imports {
9445 match import {
9446 // When an entire module is unused we can delete its entire location
9447 // in the source code.
9448 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
9449 if self.edits.line_numbers.spans_entire_line(location) {
9450 // If the unused module spans over the entire line then
9451 // we also take care of removing the following newline
9452 // characther!
9453 self.edits.delete(SrcSpan {
9454 start: location.start,
9455 end: location.end + 1,
9456 });
9457 } else {
9458 self.edits.delete(*location);
9459 }
9460 }
9461
9462 // When removing unused imported values we have to be a bit more
9463 // careful: an unused value might be followed or preceded by a
9464 // comma that we also need to remove!
9465 UnusedImport::ValueOrType(location) => {
9466 let imported = self.imported_values(*location);
9467 let unused_index = imported.binary_search(location);
9468 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1);
9469 let next_value = unused_index
9470 .ok()
9471 .and_then(|value_index| imported.get(value_index + 1));
9472 let previous_value = unused_index.ok().and_then(|value_index| {
9473 value_index
9474 .checked_sub(1)
9475 .and_then(|previous_index| imported.get(previous_index))
9476 });
9477 let previous_is_unused = previous_value.is_some_and(|previous| {
9478 unused_imports
9479 .as_slice()
9480 .binary_search_by_key(previous, |import| import.location())
9481 .is_ok()
9482 });
9483
9484 match (previous_value, next_value) {
9485 // If there's a value following the unused import we need
9486 // to remove all characters until its start!
9487 //
9488 // ```gleam
9489 // import wibble.{unused, used}
9490 // // ^^^^^^^^^^^ We need to remove all of this!
9491 // ```
9492 //
9493 (_, Some(next_value)) => self.edits.delete(SrcSpan {
9494 start: location.start,
9495 end: next_value.start,
9496 }),
9497
9498 // If this unused import is the last of the unuqualified
9499 // list and is preceded by another used value then we
9500 // need to do some additional cleanup and remove all
9501 // characters starting from its end.
9502 // (If the previous one is unused as well it will take
9503 // care of removing all the extra space)
9504 //
9505 // ```gleam
9506 // import wibble.{used, unused}
9507 // // ^^^^^^^^^^^^ We need to remove all of this!
9508 // ```
9509 //
9510 (Some(previous_value), _) if is_last && !previous_is_unused => {
9511 self.edits.delete(SrcSpan {
9512 start: previous_value.end,
9513 end: location.end,
9514 });
9515 }
9516
9517 // In all other cases it means that this is the only
9518 // item in the import list. We can just remove it.
9519 //
9520 // ```gleam
9521 // import wibble.{unused}
9522 // // ^^^^^^ We remove this import, the formatter will already
9523 // // take care of removing the empty curly braces
9524 // ```
9525 //
9526 (_, _) => self.edits.delete(*location),
9527 }
9528 }
9529 }
9530 }
9531
9532 let mut action = Vec::with_capacity(1);
9533 CodeActionBuilder::new("Remove unused imports")
9534 .kind(CodeActionKind::RefactorRewrite)
9535 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9536 .preferred(true)
9537 .push_to(&mut action);
9538 action
9539 }
9540}
9541
9542/// Code action to remove a block wrapping a single expression.
9543///
9544pub struct RemoveBlock<'a> {
9545 module: &'a Module,
9546 params: &'a CodeActionParams,
9547 edits: TextEdits<'a>,
9548 block_span: Option<SrcSpan>,
9549 position: RemoveBlockPosition,
9550}
9551
9552#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
9553enum RemoveBlockPosition {
9554 InsideBinOp,
9555 OutsideBinOp,
9556}
9557
9558impl<'a> RemoveBlock<'a> {
9559 pub fn new(
9560 module: &'a Module,
9561 line_numbers: &'a LineNumbers,
9562 params: &'a CodeActionParams,
9563 ) -> Self {
9564 Self {
9565 module,
9566 params,
9567 edits: TextEdits::new(line_numbers),
9568 block_span: None,
9569 position: RemoveBlockPosition::OutsideBinOp,
9570 }
9571 }
9572
9573 pub fn code_actions(mut self) -> Vec<CodeAction> {
9574 self.visit_typed_module(&self.module.ast);
9575
9576 let Some(SrcSpan { start, end }) = self.block_span else {
9577 return vec![];
9578 };
9579
9580 self.edits.delete(SrcSpan::new(start, start + 1));
9581 self.edits.delete(SrcSpan::new(end - 1, end));
9582
9583 let mut action = Vec::with_capacity(1);
9584 CodeActionBuilder::new("Remove block")
9585 .kind(CodeActionKind::RefactorRewrite)
9586 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9587 .preferred(true)
9588 .push_to(&mut action);
9589 action
9590 }
9591}
9592
9593impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> {
9594 fn visit_typed_expr_bin_op(
9595 &mut self,
9596 _location: &'ast SrcSpan,
9597 _type_: &'ast Arc<Type>,
9598 _operator: &'ast ast::BinOp,
9599 _operator_start: &'ast u32,
9600 left: &'ast TypedExpr,
9601 right: &'ast TypedExpr,
9602 ) {
9603 let old_position = self.position;
9604 self.position = RemoveBlockPosition::InsideBinOp;
9605 ast::visit::visit_typed_expr(self, left);
9606 self.position = RemoveBlockPosition::InsideBinOp;
9607 ast::visit::visit_typed_expr(self, right);
9608 self.position = old_position;
9609 }
9610
9611 fn visit_typed_expr_block(
9612 &mut self,
9613 location: &'ast SrcSpan,
9614 statements: &'ast [TypedStatement],
9615 ) {
9616 let block_range = self.edits.src_span_to_lsp_range(*location);
9617 if !within(self.params.range, block_range) {
9618 return;
9619 }
9620
9621 match statements {
9622 [] | [_, _, ..] => (),
9623 [value] => match value {
9624 ast::Statement::Use(_)
9625 | ast::Statement::Assert(_)
9626 | ast::Statement::Assignment(_) => {
9627 ast::visit::visit_typed_expr_block(self, location, statements);
9628 }
9629
9630 ast::Statement::Expression(expr) => match expr {
9631 TypedExpr::Int { .. }
9632 | TypedExpr::Float { .. }
9633 | TypedExpr::String { .. }
9634 | TypedExpr::Block { .. }
9635 | TypedExpr::Var { .. }
9636 | TypedExpr::Fn { .. }
9637 | TypedExpr::List { .. }
9638 | TypedExpr::Call { .. }
9639 | TypedExpr::Case { .. }
9640 | TypedExpr::RecordAccess { .. }
9641 | TypedExpr::PositionalAccess { .. }
9642 | TypedExpr::ModuleSelect { .. }
9643 | TypedExpr::Tuple { .. }
9644 | TypedExpr::TupleIndex { .. }
9645 | TypedExpr::Todo { .. }
9646 | TypedExpr::Panic { .. }
9647 | TypedExpr::Echo { .. }
9648 | TypedExpr::BitArray { .. }
9649 | TypedExpr::RecordUpdate { .. }
9650 | TypedExpr::NegateBool { .. }
9651 | TypedExpr::NegateInt { .. }
9652 | TypedExpr::Invalid { .. } => {
9653 self.block_span = Some(*location);
9654 }
9655 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => {
9656 if self.position == RemoveBlockPosition::OutsideBinOp {
9657 self.block_span = Some(*location);
9658 }
9659 }
9660 },
9661 },
9662 }
9663
9664 ast::visit::visit_typed_expr_block(self, location, statements);
9665 }
9666}
9667
9668/// Code action to remove `opaque` from a private type.
9669///
9670pub struct RemovePrivateOpaque<'a> {
9671 module: &'a Module,
9672 params: &'a CodeActionParams,
9673 edits: TextEdits<'a>,
9674 opaque_span: Option<SrcSpan>,
9675}
9676
9677impl<'a> RemovePrivateOpaque<'a> {
9678 pub fn new(
9679 module: &'a Module,
9680 line_numbers: &'a LineNumbers,
9681 params: &'a CodeActionParams,
9682 ) -> Self {
9683 Self {
9684 module,
9685 params,
9686 edits: TextEdits::new(line_numbers),
9687 opaque_span: None,
9688 }
9689 }
9690
9691 pub fn code_actions(mut self) -> Vec<CodeAction> {
9692 self.visit_typed_module(&self.module.ast);
9693
9694 let Some(opaque_span) = self.opaque_span else {
9695 return vec![];
9696 };
9697
9698 self.edits.delete(opaque_span);
9699
9700 let mut action = Vec::with_capacity(1);
9701 CodeActionBuilder::new("Remove opaque from private type")
9702 .kind(CodeActionKind::QuickFix)
9703 .changes(self.params.text_document.uri.clone(), self.edits.edits)
9704 .preferred(true)
9705 .push_to(&mut action);
9706 action
9707 }
9708}
9709
9710impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> {
9711 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
9712 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location);
9713 if !within(self.params.range, custom_type_range) {
9714 return;
9715 }
9716
9717 if custom_type.opaque && custom_type.publicity.is_private() {
9718 self.opaque_span = Some(SrcSpan {
9719 start: custom_type.location.start,
9720 end: custom_type.location.start + 7,
9721 });
9722 }
9723 }
9724}
9725
9726/// Code action to rewrite a case expression as part of an outer case expression
9727/// branch. For example:
9728///
9729/// ```gleam
9730/// case wibble {
9731/// Ok(a) -> case a {
9732/// 1 -> todo
9733/// _ -> todo
9734/// }
9735/// Error(_) -> todo
9736/// }
9737/// ```
9738///
9739/// Would become:
9740///
9741/// ```gleam
9742/// case wibble {
9743/// Ok(1) -> todo
9744/// Ok(_) -> todo
9745/// Error(_) -> todo
9746/// }
9747/// ```
9748///
9749pub struct CollapseNestedCase<'a> {
9750 module: &'a Module,
9751 params: &'a CodeActionParams,
9752 edits: TextEdits<'a>,
9753 collapsed: Option<Collapsed<'a>>,
9754}
9755
9756/// This holds all the needed data about the pattern to collapse.
9757/// We'll use this piece of code as an example:
9758/// ```gleam
9759/// case something {
9760/// User(username: _, NotAdmin) -> "Stranger!!"
9761/// User(username:, Admin) if wibble ->
9762/// case username { // <- We're collapsing this nested case
9763/// "Joe" -> "Hello, Joe!"
9764/// _ -> "I don't know you, " <> username
9765/// }
9766/// }
9767/// ```
9768///
9769struct Collapsed<'a> {
9770 /// This is the span covering the entire clause being collapsed:
9771 ///
9772 /// ```gleam
9773 /// case something {
9774 /// User(username: _, NotAdmin) -> "Stranger!!"
9775 /// User(username:, Admin) if wibble ->
9776 /// ┬ It goes all the way from here...
9777 /// ╭─╯
9778 /// │ case username {
9779 /// │ "Joe" -> "Hello, Joe!"
9780 /// │ _ -> "I don't know you, " <> username
9781 /// │ }
9782 /// │ ┬ ...to here!
9783 /// ╰───╯
9784 /// }
9785 /// ```
9786 ///
9787 outer_clause_span: SrcSpan,
9788
9789 /// The (optional) guard of the outer branch. In this exmaple it's this one:
9790 ///
9791 /// ```gleam
9792 /// case something {
9793 /// User(username: _, NotAdmin) -> "Stranger!!"
9794 /// User(username:, Admin) if wibble ->
9795 /// ┬────────
9796 /// ╰─ `outer_guard`
9797 /// case username {
9798 /// "Joe" -> "Hello, Joe!"
9799 /// _ -> "I don't know you, " <> username
9800 /// }
9801 /// }
9802 /// ```
9803 ///
9804 outer_guard: &'a Option<TypedClauseGuard>,
9805
9806 /// The pattern variable being matched on:
9807 ///
9808 /// ```gleam
9809 /// case something {
9810 /// User(username: _, NotAdmin) -> "Stranger!!"
9811 /// User(username:, Admin) if wibble ->
9812 /// ┬───────
9813 /// ╰─ `matched_variable`
9814 /// case username {
9815 /// "Joe" -> "Hello, Joe!"
9816 /// _ -> "I don't know you, " <> username
9817 /// }
9818 /// }
9819 /// ```
9820 ///
9821 matched_variable: BoundVariable,
9822
9823 /// The span covering the entire pattern that is bringing the matched
9824 /// variable in scope:
9825 ///
9826 /// ```gleam
9827 /// case something {
9828 /// User(username: _, NotAdmin) -> "Stranger!!"
9829 /// User(username:, Admin) if wibble ->
9830 /// ┬─────────────────────
9831 /// ╰─ `matched_pattern_span`
9832 /// case username {
9833 /// "Joe" -> "Hello, Joe!"
9834 /// _ -> "I don't know you, " <> username
9835 /// }
9836 /// }
9837 /// ```
9838 ///
9839 matched_pattern_span: SrcSpan,
9840
9841 /// The clauses matching on the `username` variable. In this case they are:
9842 /// ```gleam
9843 /// "Joe" -> "Hello, Joe!"
9844 /// _ -> "I don't know you, " <> username
9845 /// ```
9846 ///
9847 inner_clauses: &'a Vec<ast::TypedClause>,
9848}
9849
9850impl<'a> CollapseNestedCase<'a> {
9851 pub fn new(
9852 module: &'a Module,
9853 line_numbers: &'a LineNumbers,
9854 params: &'a CodeActionParams,
9855 ) -> Self {
9856 Self {
9857 module,
9858 params,
9859 edits: TextEdits::new(line_numbers),
9860 collapsed: None,
9861 }
9862 }
9863
9864 pub fn code_actions(mut self) -> Vec<CodeAction> {
9865 self.visit_typed_module(&self.module.ast);
9866
9867 let Some(Collapsed {
9868 outer_clause_span,
9869 outer_guard,
9870 ref matched_variable,
9871 matched_pattern_span,
9872 inner_clauses,
9873 }) = self.collapsed
9874 else {
9875 return vec![];
9876 };
9877
9878 // Now comes the tricky part: we need to replace the current pattern
9879 // that is bringing the variable into scope with many new patterns, one
9880 // for each of the inner clauses.
9881 //
9882 // Each time we will have to replace the matched variable with the
9883 // pattern used in the inner clause. Let's look at an example:
9884 //
9885 // ```gleam
9886 // Ok(a) -> case a {
9887 // 1 -> wibble
9888 // 2 | 3 -> wobble
9889 // _ -> woo
9890 // }
9891 // ```
9892 //
9893 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then
9894 // with `2` and `3`, and finally with `_`. Obtaining something like
9895 // this:
9896 //
9897 // ```gleam
9898 // Ok(1) -> wibble
9899 // Ok(2) | Ok(3) -> wobble
9900 // Ok(_) -> woo
9901 // ```
9902 //
9903 // Notice one key detail: since alternative patterns can't be nested we
9904 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`!
9905
9906 let pattern_text: String = code_at(self.module, matched_pattern_span).into();
9907 let matched_variable_span = matched_variable.location;
9908
9909 let pattern_with_variable = |mut new_content: String| {
9910 let mut new_pattern = pattern_text.clone();
9911
9912 match matched_variable {
9913 BoundVariable {
9914 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. },
9915 ..
9916 } => {
9917 let trimmed_contents = new_content.trim();
9918
9919 let pattern_is_literal_list =
9920 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]");
9921 let pattern_is_discard = trimmed_contents == "_";
9922
9923 let span_to_replace = match (
9924 &matched_variable.name,
9925 // We verify whether the pattern is compatible with the list prefix `..`.
9926 // For example, `..var` is valid syntax, but `..[]` and `.._` are not.
9927 pattern_is_literal_list || pattern_is_discard,
9928 ) {
9929 // We normally replace the selected variable with the pattern.
9930 (BoundVariableName::Regular { .. }, _) => matched_variable_span,
9931
9932 // If the selected pattern is not a list, we also replace it normally.
9933 (BoundVariableName::ListTail { .. }, false) => matched_variable_span,
9934 // If the pattern is a list to also remove the list tail prefix.
9935 (BoundVariableName::ListTail { tail_location, .. }, true) => {
9936 // When it's a list literal, we remove the surrounding brackets.
9937 let len = trimmed_contents.len();
9938 if let Some(slice) = new_content.trim().get(1..(len - 1)) {
9939 new_content = slice.to_string();
9940 }
9941
9942 *tail_location
9943 }
9944
9945 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(),
9946 };
9947
9948 let start_of_pattern =
9949 (span_to_replace.start - matched_pattern_span.start) as usize;
9950 let pattern_length = span_to_replace.len();
9951
9952 let end_of_pattern = start_of_pattern + pattern_length;
9953 let replaced_range = start_of_pattern..end_of_pattern;
9954
9955 new_pattern.replace_range(replaced_range, &new_content);
9956 }
9957
9958 BoundVariable {
9959 name: BoundVariableName::ShorthandLabel { .. },
9960 ..
9961 } => {
9962 // But if it's introduced using the shorthand syntax we can't
9963 // just replace it's location with the new pattern: we would be
9964 // removing the label!!
9965 // So we instead insert the pattern right after the label.
9966 new_pattern.insert_str(
9967 (matched_variable_span.end - matched_pattern_span.start) as usize,
9968 &format!(" {new_content}"),
9969 );
9970 }
9971 }
9972
9973 new_pattern
9974 };
9975
9976 let mut new_clauses = vec![];
9977 for clause in inner_clauses {
9978 // Here we take care of unrolling any alterantive patterns: for each
9979 // of the alternatives we build a new pattern and then join
9980 // everything together with ` | `.
9981
9982 let references_to_matched_variable =
9983 FindVariableReferences::new(matched_variable_span, matched_variable.name())
9984 .find(&clause.then);
9985
9986 let new_patterns = iter::once(&clause.pattern)
9987 .chain(&clause.alternative_patterns)
9988 .map(|patterns| {
9989 // If we've reached this point we've already made in the
9990 // traversal that the inner clause is matching on a single
9991 // subject. So this should be safe to expect!
9992 let pattern_location =
9993 patterns.first().expect("must have a pattern").location();
9994
9995 let mut pattern_code = code_at(self.module, pattern_location).to_string();
9996 if !references_to_matched_variable.is_empty() {
9997 pattern_code = format!("{pattern_code} as {}", matched_variable.name());
9998 }
9999 pattern_with_variable(pattern_code)
10000 })
10001 .join(" | ");
10002
10003 let clause_code = code_at(self.module, clause.then.location());
10004 let guard_code = match (outer_guard, &clause.guard) {
10005 (Some(outer), Some(inner)) => {
10006 let mut outer_code = code_at(self.module, outer.location()).to_string();
10007 let mut inner_code = code_at(self.module, inner.location()).to_string();
10008 if ast::BinOp::And.precedence() > outer.precedence() {
10009 outer_code = format!("{{ {outer_code} }}");
10010 }
10011 if ast::BinOp::And.precedence() > inner.precedence() {
10012 inner_code = format!("{{ {inner_code} }}");
10013 }
10014 format!(" if {outer_code} && {inner_code}")
10015 }
10016 (None, Some(guard)) | (Some(guard), None) => {
10017 format!(" if {}", code_at(self.module, guard.location()))
10018 }
10019 (None, None) => "".into(),
10020 };
10021
10022 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}"));
10023 }
10024
10025 let pattern_nesting = self
10026 .edits
10027 .src_span_to_lsp_range(outer_clause_span)
10028 .start
10029 .character;
10030 let indentation = " ".repeat(pattern_nesting as usize);
10031
10032 self.edits.replace(
10033 outer_clause_span,
10034 new_clauses.join(&format!("\n{indentation}")),
10035 );
10036
10037 let mut action = Vec::with_capacity(1);
10038 CodeActionBuilder::new("Collapse nested case")
10039 .kind(CodeActionKind::RefactorRewrite)
10040 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10041 .preferred(false)
10042 .push_to(&mut action);
10043 action
10044 }
10045
10046 /// If the clause can be flattened because it's matching on a single variable
10047 /// defined in it, this function will return the info needed by the language
10048 /// server to flatten that case.
10049 ///
10050 /// We can only flatten a case expression in a very specific case:
10051 /// - This pattern may be introducing multiple variables,
10052 /// - The expression following this branch must be a case, and
10053 /// - It must be matching on one of those variables
10054 ///
10055 /// For example:
10056 ///
10057 /// ```gleam
10058 /// Wibble(a, b, 1) -> case a { ... }
10059 /// Wibble(a, b, 1) -> case b { ... }
10060 /// ```
10061 ///
10062 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> {
10063 let ast::TypedClause {
10064 pattern,
10065 alternative_patterns,
10066 then,
10067 location,
10068 guard,
10069 } = clause;
10070
10071 if !alternative_patterns.is_empty() {
10072 return None;
10073 }
10074
10075 // The `then` clause must be a single case expression matching on a
10076 // single variable.
10077 let Some(TypedExpr::Case {
10078 subjects, clauses, ..
10079 }) = single_expression(then)
10080 else {
10081 return None;
10082 };
10083
10084 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else {
10085 return None;
10086 };
10087
10088 // That variable must be one the variables we brought into scope in this
10089 // branch.
10090 let variable = pattern
10091 .iter()
10092 .flat_map(|pattern| pattern.bound_variables())
10093 .find(|variable| variable.name() == *name)?;
10094
10095 // There's one last condition to trigger the code action: we must
10096 // actually be with the cursor over the pattern or the nested case
10097 // expression!
10098 //
10099 // ```gleam
10100 // case wibble {
10101 // Ok(a) -> case a {
10102 // //^^^^^^^^^^^^^^^ Anywhere over here!
10103 // }
10104 // }
10105 // ```
10106 //
10107 let first_pattern = pattern.first().expect("at least one pattern");
10108 let last_pattern = pattern.last().expect("at least one pattern");
10109 let pattern_location = first_pattern.location().merge(&last_pattern.location());
10110
10111 let last_inner_subject = subjects.last().expect("at least one subject");
10112 let trigger_location = pattern_location.merge(&last_inner_subject.location());
10113 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location);
10114
10115 if within(self.params.range, trigger_range) {
10116 Some(Collapsed {
10117 outer_clause_span: *location,
10118 outer_guard: guard,
10119 matched_variable: variable,
10120 matched_pattern_span: pattern_location,
10121 inner_clauses: clauses,
10122 })
10123 } else {
10124 None
10125 }
10126 }
10127}
10128
10129impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> {
10130 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
10131 if let Some(collapsed) = self.flatten_clause(clause) {
10132 self.collapsed = Some(collapsed);
10133
10134 // We're done, there's no need to keep exploring as we know the
10135 // cursor is over this pattern and it can't be over any other one!
10136 return;
10137 }
10138
10139 ast::visit::visit_typed_clause(self, clause);
10140 }
10141}
10142
10143/// If the expression is a single expression, or a block containing a single
10144/// expression, this function will return it.
10145/// But if the expression is a block with multiple statements, an assignment
10146/// of a use, this will return None.
10147///
10148fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
10149 match expression {
10150 // If a block has a single statement, we can flatten it into a
10151 // single expression if that one statement is an expression.
10152 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() {
10153 ast::Statement::Expression(expression) => single_expression(expression),
10154 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
10155 None
10156 }
10157 },
10158
10159 // If a block has multiple statements then it can't be flattened
10160 // into a single expression.
10161 TypedExpr::Block { .. } => None,
10162
10163 TypedExpr::Int { .. }
10164 | TypedExpr::Float { .. }
10165 | TypedExpr::String { .. }
10166 | TypedExpr::Pipeline { .. }
10167 | TypedExpr::Var { .. }
10168 | TypedExpr::Fn { .. }
10169 | TypedExpr::List { .. }
10170 | TypedExpr::Call { .. }
10171 | TypedExpr::BinOp { .. }
10172 | TypedExpr::Case { .. }
10173 | TypedExpr::RecordAccess { .. }
10174 | TypedExpr::PositionalAccess { .. }
10175 | TypedExpr::ModuleSelect { .. }
10176 | TypedExpr::Tuple { .. }
10177 | TypedExpr::TupleIndex { .. }
10178 | TypedExpr::Todo { .. }
10179 | TypedExpr::Panic { .. }
10180 | TypedExpr::Echo { .. }
10181 | TypedExpr::BitArray { .. }
10182 | TypedExpr::RecordUpdate { .. }
10183 | TypedExpr::NegateBool { .. }
10184 | TypedExpr::NegateInt { .. }
10185 | TypedExpr::Invalid { .. } => Some(expression),
10186 }
10187}
10188
10189/// Code action to remove unreachable clauses from a case expression.
10190///
10191pub struct RemoveUnreachableCaseClauses<'a> {
10192 module: &'a Module,
10193 params: &'a CodeActionParams,
10194 edits: TextEdits<'a>,
10195 /// The source location of the patterns of all the unreachable clauses in
10196 /// the current module.
10197 ///
10198 unreachable_clauses: HashSet<SrcSpan>,
10199 clauses_to_delete: Vec<SrcSpan>,
10200}
10201
10202impl<'a> RemoveUnreachableCaseClauses<'a> {
10203 pub fn new(
10204 module: &'a Module,
10205 line_numbers: &'a LineNumbers,
10206 params: &'a CodeActionParams,
10207 ) -> Self {
10208 let unreachable_clauses = module
10209 .ast
10210 .type_info
10211 .warnings
10212 .iter()
10213 .filter_map(|warning| {
10214 if let type_::Warning::UnreachableCasePattern { location, .. } = warning {
10215 Some(*location)
10216 } else {
10217 None
10218 }
10219 })
10220 .collect();
10221
10222 Self {
10223 unreachable_clauses,
10224 module,
10225 params,
10226 edits: TextEdits::new(line_numbers),
10227 clauses_to_delete: vec![],
10228 }
10229 }
10230
10231 pub fn code_actions(mut self) -> Vec<CodeAction> {
10232 self.visit_typed_module(&self.module.ast);
10233 if self.clauses_to_delete.is_empty() {
10234 return vec![];
10235 }
10236
10237 for branch in self.clauses_to_delete {
10238 self.edits.delete(branch);
10239 }
10240
10241 let mut action = Vec::with_capacity(1);
10242 CodeActionBuilder::new("Remove unreachable clauses")
10243 .kind(CodeActionKind::QuickFix)
10244 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10245 .preferred(true)
10246 .push_to(&mut action);
10247 action
10248 }
10249}
10250
10251impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> {
10252 fn visit_typed_expr_case(
10253 &mut self,
10254 location: &'ast SrcSpan,
10255 type_: &'ast Arc<Type>,
10256 subjects: &'ast [TypedExpr],
10257 clauses: &'ast [ast::TypedClause],
10258 compiled_case: &'ast CompiledCase,
10259 ) {
10260 // We're showing the code action only if we're within one of the
10261 // unreachable patterns. And the code action is going to remove all the
10262 // unreachable patterns for this case.
10263 let is_hovering_clause = clauses.iter().any(|clause| {
10264 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location());
10265 within(self.params.range, pattern_range)
10266 });
10267
10268 // If we're not hovering any of the clauses then we want to
10269 // keep visiting the case expression as the unreachable branch might be
10270 // in one of the nested cases.
10271 if !is_hovering_clause {
10272 ast::visit::visit_typed_expr_case(
10273 self,
10274 location,
10275 type_,
10276 subjects,
10277 clauses,
10278 compiled_case,
10279 );
10280 return;
10281 }
10282
10283 for clause in clauses {
10284 let mut all_patterns_are_unreachable = true;
10285 let mut unreachable_patterns = vec![];
10286 let mut previous_pattern_end = None;
10287 let mut all_previous_patterns_were_deleted = true;
10288
10289 for pattern in clause.patterns() {
10290 let pattern_location = multi_pattern_location(pattern);
10291 if self.unreachable_clauses.contains(&pattern_location) {
10292 // If an alternative is unreachable we want to delete
10293 // everything from the end of the previous alternative to
10294 // the start of this one.
10295 //
10296 // ```gleam
10297 // Error(_) | Error(_) | Ok(_)
10298 // // ^^^^^^^^^^^ We want to delete all of this
10299 // ```
10300 unreachable_patterns.push(
10301 previous_pattern_end.map_or(pattern_location, |previous_end| {
10302 SrcSpan::new(previous_end, pattern_location.end)
10303 }),
10304 );
10305 } else {
10306 // If all the previous alternatives have been deleted and
10307 // this one is reachable there's some final cleanup we need
10308 // to take care of:
10309 //
10310 // ```gleam
10311 // Ok(_) | Ok(_) | Error(_) -> todo
10312 // //^^^^^^^^^^^ All of this has been deleted, but there's
10313 // // that last vertical bar that needs to be
10314 // // taken care of!
10315 // ```
10316 //
10317
10318 if all_previous_patterns_were_deleted && let Some(end) = previous_pattern_end {
10319 self.clauses_to_delete
10320 .push(SrcSpan::new(end, pattern_location.start));
10321 }
10322
10323 all_previous_patterns_were_deleted = false;
10324 all_patterns_are_unreachable = false;
10325 }
10326
10327 previous_pattern_end = pattern.last().map(|pattern| pattern.location().end);
10328 }
10329
10330 if all_patterns_are_unreachable {
10331 // If all the patterns of the clause are unreachable then we
10332 // want to delete the entire branch:
10333 //
10334 // ```gleam
10335 // case a, b {
10336 // _, _ -> todo
10337 // Ok(_), Ok(_) | Error(_), Error(_) -> todo
10338 // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10339 // // we want the entire branch to be deleted!
10340 // }
10341 // ```
10342 self.clauses_to_delete.push(clause.location());
10343 } else {
10344 // If only some of the variants are unreachable but not all
10345 // we want to delete just those.
10346 // case a, b {
10347 // 1, 2 | 1, 2 -> todo
10348 // // ^^^^ just this one should be deleted
10349 // }
10350 self.clauses_to_delete.extend(&unreachable_patterns);
10351 }
10352 }
10353 }
10354}
10355
10356/// Given a pattern with possibly many subjects, this returns the location
10357/// spanning the whole thing.
10358fn multi_pattern_location(alternative: &[Pattern<Arc<Type>>]) -> SrcSpan {
10359 let start = alternative
10360 .first()
10361 .map(|pattern| pattern.location().start)
10362 .unwrap_or_default();
10363 let end = alternative
10364 .last()
10365 .map(|pattern| pattern.location().end)
10366 .unwrap_or_default();
10367
10368 SrcSpan::new(start, end)
10369}
10370
10371/// Code action to remove a record update when all of its fields have been
10372/// provided already:
10373///
10374/// ```gleam
10375/// pub type Wibble { Wibble(one: Int, two: Int) }
10376///
10377/// wibble(..wibble, one:, two:)
10378/// // ^^^^^^^^ This is not needed and raises a warning!
10379/// ```
10380///
10381pub struct RemoveRedundantRecordUpdate<'a> {
10382 module: &'a Module,
10383 params: &'a CodeActionParams,
10384 edits: TextEdits<'a>,
10385}
10386
10387impl<'a> RemoveRedundantRecordUpdate<'a> {
10388 pub fn new(
10389 module: &'a Module,
10390 line_numbers: &'a LineNumbers,
10391 params: &'a CodeActionParams,
10392 ) -> Self {
10393 Self {
10394 module,
10395 params,
10396 edits: TextEdits::new(line_numbers),
10397 }
10398 }
10399
10400 pub fn code_actions(mut self) -> Vec<CodeAction> {
10401 let spread_to_remove = self
10402 .module
10403 .ast
10404 .type_info
10405 .warnings
10406 .iter()
10407 .find_map(|warning| {
10408 if let type_::Warning::AllFieldsRecordUpdate {
10409 location,
10410 record_location,
10411 } = warning
10412 && within(
10413 self.params.range,
10414 self.edits.src_span_to_lsp_range(*location),
10415 )
10416 {
10417 Some(*record_location)
10418 } else {
10419 None
10420 }
10421 });
10422
10423 let Some(spread_to_remove) = spread_to_remove else {
10424 return vec![];
10425 };
10426 self.edits.delete(spread_to_remove);
10427
10428 let mut action = Vec::with_capacity(1);
10429 CodeActionBuilder::new("Remove redundant record update")
10430 .kind(CodeActionKind::QuickFix)
10431 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10432 .preferred(true)
10433 .push_to(&mut action);
10434 action
10435 }
10436}
10437
10438/// Code action to add labels to a constructor/call where all the labels where
10439/// omitted.
10440///
10441pub struct AddOmittedLabels<'a> {
10442 module: &'a Module,
10443 params: &'a CodeActionParams,
10444 edits: TextEdits<'a>,
10445 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>,
10446}
10447
10448struct CallArgumentWithOmittedLabel {
10449 location: SrcSpan,
10450
10451 /// If the argument has a label this will be the label we can use for it.
10452 ///
10453 omitted_label: Option<EcoString>,
10454
10455 /// If the argument is a variable that has the same name as the omitted label
10456 /// and could use the shorthand syntax.
10457 ///
10458 can_use_shorthand_syntax: bool,
10459}
10460
10461impl<'a> AddOmittedLabels<'a> {
10462 pub fn new(
10463 module: &'a Module,
10464 line_numbers: &'a LineNumbers,
10465 params: &'a CodeActionParams,
10466 ) -> Self {
10467 Self {
10468 module,
10469 params,
10470 edits: TextEdits::new(line_numbers),
10471 arguments_and_omitted_labels: None,
10472 }
10473 }
10474
10475 pub fn code_actions(mut self) -> Vec<CodeAction> {
10476 self.visit_typed_module(&self.module.ast);
10477
10478 let Some(call_arguments) = self.arguments_and_omitted_labels else {
10479 return vec![];
10480 };
10481
10482 for call_argument in call_arguments {
10483 let Some(label) = call_argument.omitted_label else {
10484 continue;
10485 };
10486 if call_argument.can_use_shorthand_syntax {
10487 self.edits.insert(call_argument.location.end, ":".into());
10488 } else {
10489 self.edits
10490 .insert(call_argument.location.start, format!("{label}: "));
10491 }
10492 }
10493
10494 let mut action = Vec::with_capacity(1);
10495 CodeActionBuilder::new("Add omitted labels")
10496 .kind(CodeActionKind::RefactorRewrite)
10497 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10498 .preferred(false)
10499 .push_to(&mut action);
10500 action
10501 }
10502}
10503
10504impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
10505 fn visit_typed_expr_call(
10506 &mut self,
10507 location: &'ast SrcSpan,
10508 type_: &'ast Arc<Type>,
10509 fun: &'ast TypedExpr,
10510 arguments: &'ast [TypedCallArg],
10511 open_parenthesis: &'ast Option<u32>,
10512 ) {
10513 let called_function_range = self.edits.src_span_to_lsp_range(fun.location());
10514 if !within(self.params.range, called_function_range) {
10515 ast::visit::visit_typed_expr_call(
10516 self,
10517 location,
10518 type_,
10519 fun,
10520 arguments,
10521 open_parenthesis,
10522 );
10523 return;
10524 }
10525
10526 let Some(field_map) = fun.field_map() else {
10527 ast::visit::visit_typed_expr_call(
10528 self,
10529 location,
10530 type_,
10531 fun,
10532 arguments,
10533 open_parenthesis,
10534 );
10535 return;
10536 };
10537 let argument_index_to_label = field_map.indices_to_labels();
10538
10539 let mut omitted_labels = Vec::with_capacity(arguments.len());
10540 for (index, argument) in arguments.iter().enumerate() {
10541 // If the argument already has a label we don't want to add a label
10542 // for it, so we skip it.
10543 if let Some(label) = &argument.label {
10544 // Though, before skipping, we want to make sure that the label
10545 // is actually right for the function call. If it's not then we
10546 // give up on adding labels because there wouldn't be no way of
10547 // knowing which label to add.
10548 if !field_map.fields.contains_key(label) {
10549 return;
10550 } else {
10551 continue;
10552 }
10553 }
10554 // No labels for pipes, uses, etc!
10555 if argument.is_implicit() {
10556 continue;
10557 }
10558
10559 let label = argument_index_to_label
10560 .get(&(index as u32))
10561 .cloned()
10562 .cloned();
10563
10564 let can_use_shorthand_syntax = match (&label, &argument.value) {
10565 (Some(label), TypedExpr::Var { name, .. }) => name == label,
10566 (Some(_) | None, _) => false,
10567 };
10568
10569 omitted_labels.push(CallArgumentWithOmittedLabel {
10570 location: argument.location,
10571 omitted_label: label,
10572 can_use_shorthand_syntax,
10573 });
10574 }
10575 self.arguments_and_omitted_labels = Some(omitted_labels);
10576 }
10577}
10578
10579/// Code action to extract selected code into a separate function.
10580/// If a user selected a portion of code in a function, we offer a code action
10581/// to extract it into a new one. This can either be a single expression, such
10582/// as in the following example:
10583///
10584/// ```gleam
10585/// pub fn main() {
10586/// let value = {
10587/// // ^ User selects from here
10588/// ...
10589/// }
10590/// //^ Until here
10591/// }
10592/// ```
10593///
10594/// Here, we would extract the selected block expression. It could also be a
10595/// series of statements. For example:
10596///
10597/// ```gleam
10598/// pub fn main() {
10599/// let a = 1
10600/// //^ User selects from here
10601/// let b = 2
10602/// let c = a + b
10603/// // ^ Until here
10604///
10605/// do_more_things(c)
10606/// }
10607/// ```
10608///
10609/// Here, we want to extract the statements inside the user's selection.
10610///
10611pub struct ExtractFunction<'a> {
10612 module: &'a Module,
10613 params: &'a CodeActionParams,
10614 edits: TextEdits<'a>,
10615 function: Option<ExtractedFunction<'a>>,
10616 function_end_position: Option<u32>,
10617 /// Since the `visit_typed_statement` visitor function doesn't tell us when
10618 /// a statement is the last in a block or function, we need to track that
10619 /// manually.
10620 last_statement_location: Option<SrcSpan>,
10621 /// When visiting a pipeline step, this will hold the type of the value
10622 /// returned by the previous step (if any!)
10623 previous_pipeline_assignment_type: Option<Arc<Type>>,
10624}
10625
10626/// Information about a section of code we are extracting as a function.
10627#[derive(Debug)]
10628struct ExtractedFunction<'a> {
10629 /// A list of parameters which need to be passed to the extracted function.
10630 /// These are any variables used in the extracted code, which are defined
10631 /// outside of the extracted code.
10632 parameters: Vec<(EcoString, Arc<Type>)>,
10633 /// A list of values which need to be returned from the extracted function.
10634 /// These are the variables defined in the extracted code which are used
10635 /// outside of the extracted section.
10636 returned_variables: Vec<(EcoString, Arc<Type>)>,
10637 /// The piece of code to be extracted. This is either a single expression or
10638 /// a list of statements, as explained in the documentation of `ExtractFunction`
10639 value: ExtractedValue<'a>,
10640}
10641
10642impl<'a> ExtractedFunction<'a> {
10643 fn new(value: ExtractedValue<'a>) -> Self {
10644 Self {
10645 value,
10646 parameters: Vec::new(),
10647 returned_variables: Vec::new(),
10648 }
10649 }
10650
10651 fn location(&self) -> SrcSpan {
10652 match &self.value {
10653 ExtractedValue::Expression(expression) => expression.location(),
10654 ExtractedValue::Statements { location, .. }
10655 | ExtractedValue::Use { location, .. }
10656 | ExtractedValue::PipelineSteps { location, .. } => *location,
10657 }
10658 }
10659
10660 /// If the extracted function is a series of pipeline steps, this adds to it
10661 /// the given pipeline step, otherwise leaving it unchanged.
10662 /// If the extracted function was indeed a pipeline, this will return `true`,
10663 /// otherwise it returns `false`.
10664 ///
10665 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) {
10666 if let ExtractedFunction {
10667 value:
10668 ExtractedValue::PipelineSteps {
10669 location,
10670 before_first: _,
10671 return_type,
10672 },
10673 ..
10674 } = self
10675 {
10676 // If we're extracting this pipeline and the final step is included
10677 // in the selection we want to add it to the extracted steps
10678 *return_type = step_type;
10679 *location = location.merge(&step_location);
10680 }
10681 }
10682}
10683
10684#[derive(Debug)]
10685enum ExtractedValue<'a> {
10686 Expression(&'a TypedExpr),
10687 Statements {
10688 location: SrcSpan,
10689 position: StatementPosition,
10690 /// The type of the final statement.
10691 type_: Arc<Type>,
10692 },
10693 /// We're extracting a single use statement. We need this special case to
10694 /// properly handle the statements inside of them.
10695 Use {
10696 /// This is the location of the entire use block, including the
10697 /// statements in it.
10698 location: SrcSpan,
10699 /// This is the location of the expression on the right hand side of the use
10700 /// arrow.
10701 ///
10702 /// ```gleam
10703 /// use a <- result.try(result)
10704 /// ^^^^^^^^^^^^^^^^^^
10705 /// ```
10706 ///
10707 use_line_location: SrcSpan,
10708 type_: Arc<Type>,
10709 },
10710 PipelineSteps {
10711 location: SrcSpan,
10712 /// The type of the value produced by the pipeline steps that will be
10713 /// piped into the extracted function. Could be none if the steps we're
10714 /// extracting include the first step, in that case there would be
10715 /// nothing that is fed into it.
10716 before_first: Option<Arc<Type>>,
10717 /// The type returned by the extracted steps.
10718 return_type: Arc<Type>,
10719 },
10720}
10721
10722impl ExtractedValue<'_> {
10723 fn location(&self) -> SrcSpan {
10724 match self {
10725 ExtractedValue::Expression(typed_expr) => typed_expr.location(),
10726 ExtractedValue::Statements { location, .. }
10727 | ExtractedValue::PipelineSteps { location, .. }
10728 | ExtractedValue::Use { location, .. } => *location,
10729 }
10730 }
10731}
10732
10733#[derive(Debug)]
10734enum StatementPosition {
10735 Tail,
10736 NotTail,
10737}
10738
10739impl<'a> ExtractFunction<'a> {
10740 pub fn new(
10741 module: &'a Module,
10742 line_numbers: &'a LineNumbers,
10743 params: &'a CodeActionParams,
10744 ) -> Self {
10745 Self {
10746 module,
10747 params,
10748 edits: TextEdits::new(line_numbers),
10749 function: None,
10750 function_end_position: None,
10751 last_statement_location: None,
10752 previous_pipeline_assignment_type: None,
10753 }
10754 }
10755
10756 pub fn code_actions(mut self) -> Vec<CodeAction> {
10757 // If no code is selected, then there is no function to extract and we
10758 // can return no code actions.
10759 if self.params.range.start == self.params.range.end {
10760 return Vec::new();
10761 }
10762
10763 self.visit_typed_module(&self.module.ast);
10764
10765 let Some(end) = self.function_end_position else {
10766 return Vec::new();
10767 };
10768
10769 // If nothing was found in the selected range, there is no code action.
10770 let Some(extracted) = self.function.take() else {
10771 return Vec::new();
10772 };
10773
10774 match extracted.value {
10775 // If we extract a block, it isn't very helpful to have the body of
10776 // the extracted function just be a single block expression, so
10777 // instead we extract the statements inside the block. For example,
10778 // the following code:
10779 //
10780 // ```gleam
10781 // pub fn main() {
10782 // let x = {
10783 // // ^ Select from here
10784 // let a = 1
10785 // let b = 2
10786 // a + b
10787 // }
10788 // //^ Until here
10789 // x
10790 // }
10791 // ```
10792 //
10793 // Would produce the following extracted function:
10794 //
10795 // ```gleam
10796 // fn function() {
10797 // let a = 1
10798 // let b = 2
10799 // a + b
10800 // }
10801 // ```
10802 //
10803 // Rather than:
10804 //
10805 // ```gleam
10806 // fn function() {
10807 // {
10808 // let a = 1
10809 // let b = 2
10810 // a + b
10811 // }
10812 // }
10813 // ```
10814 //
10815 ExtractedValue::Expression(TypedExpr::Block {
10816 statements,
10817 location: full_location,
10818 }) => {
10819 let location = statements
10820 .first()
10821 .location()
10822 .merge(&statements.last().location());
10823
10824 self.extract_unbound_statements(
10825 *full_location,
10826 location,
10827 extracted.parameters,
10828 statements.last().type_(),
10829 end,
10830 );
10831 }
10832 ExtractedValue::Expression(TypedExpr::Fn {
10833 type_,
10834 location: full_location,
10835 kind: FunctionLiteralKind::Anonymous { .. },
10836 arguments,
10837 body,
10838 ..
10839 }) => {
10840 let location = body.first().location().merge(&body.last().location());
10841 let return_type = type_.return_type().expect("Fn should have a return type");
10842
10843 if extracted.parameters.is_empty() {
10844 self.extract_anonymous_function(
10845 *full_location,
10846 location,
10847 arguments,
10848 return_type,
10849 end,
10850 );
10851 } else if arguments.len() == 1 {
10852 self.extract_anonymous_function_with_capture_hole(
10853 *full_location,
10854 location,
10855 arguments.first().expect("There is exactly one argument"),
10856 extracted.parameters,
10857 return_type,
10858 end,
10859 );
10860 } else {
10861 self.extract_anonymous_function_body(
10862 location,
10863 arguments,
10864 extracted.parameters,
10865 return_type,
10866 end,
10867 );
10868 }
10869 }
10870 ExtractedValue::Expression(expression) => {
10871 let expression_type = if let TypedExpr::Fn {
10872 type_,
10873 kind: FunctionLiteralKind::Use { .. },
10874 ..
10875 } = expression
10876 {
10877 type_.fn_types().expect("use callback to be a function").1
10878 } else {
10879 expression.type_()
10880 };
10881 self.extract_unbound_statements(
10882 expression.location(),
10883 expression.location(),
10884 extracted.parameters,
10885 expression_type,
10886 end,
10887 );
10888 }
10889 ExtractedValue::Statements {
10890 location,
10891 position: StatementPosition::NotTail,
10892 type_,
10893 } => {
10894 if extracted.returned_variables.is_empty() {
10895 self.extract_unbound_statements(
10896 location,
10897 location,
10898 extracted.parameters,
10899 type_,
10900 end,
10901 );
10902 } else {
10903 self.extract_bound_statements(
10904 location,
10905 extracted.parameters,
10906 extracted.returned_variables,
10907 end,
10908 );
10909 }
10910 }
10911
10912 ExtractedValue::Use {
10913 location,
10914 use_line_location: _,
10915 type_,
10916 }
10917 | ExtractedValue::Statements {
10918 location,
10919 position: StatementPosition::Tail,
10920 type_,
10921 } => self.extract_unbound_statements(
10922 location,
10923 location,
10924 extracted.parameters,
10925 type_,
10926 end,
10927 ),
10928 ExtractedValue::PipelineSteps {
10929 location,
10930 before_first,
10931 return_type,
10932 } => self.extract_pipeline_steps(
10933 location,
10934 end,
10935 extracted.parameters,
10936 before_first,
10937 return_type,
10938 ),
10939 }
10940
10941 let mut action = Vec::with_capacity(1);
10942 CodeActionBuilder::new("Extract function")
10943 .kind(CodeActionKind::RefactorExtract)
10944 .changes(self.params.text_document.uri.clone(), self.edits.edits)
10945 .preferred(false)
10946 .push_to(&mut action);
10947 action
10948 }
10949
10950 /// Choose a suitable name for an extracted function to make sure it doesn't
10951 /// clash with existing functions defined in the module and cause an error.
10952 fn function_name(&self) -> EcoString {
10953 if !self.module.ast.type_info.values.contains_key("function") {
10954 return "function".into();
10955 }
10956
10957 let mut number = 2;
10958 loop {
10959 let name = eco_format!("function_{number}");
10960 if !self.module.ast.type_info.values.contains_key(&name) {
10961 return name;
10962 }
10963 number += 1;
10964 }
10965 }
10966
10967 /// For anonymous functions that do not capture any variables from an outer scope.
10968 /// Moves the function so it is defined at the module top-level instead,
10969 /// replacing the original literal with a reference to the new function.
10970 fn extract_anonymous_function(
10971 &mut self,
10972 location: SrcSpan,
10973 code_location: SrcSpan,
10974 arguments: &[TypedArg],
10975 return_type: Arc<Type>,
10976 function_end: u32,
10977 ) {
10978 // --- BEFORE
10979 // ```gleam
10980 // pub fn main() {
10981 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) })
10982 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
10983 // }
10984 // ```
10985 //
10986 // --- AFTER
10987 // ```gleam
10988 // pub fn main() {
10989 // list.each([1, 2, 3], function)
10990 // }
10991 //
10992 // fn function(x: Int) -> Nil {
10993 // io.println(int.to_string(x))
10994 // }
10995 // ```
10996
10997 let name = self.function_name();
10998 self.edits.replace(location, name.to_string());
10999
11000 let mut printer = Printer::new(&self.module.ast.names);
11001
11002 let return_type = printer.print_type(&return_type);
11003 let function_body = code_at(self.module, code_location);
11004 let mut name_generator = NameGenerator::new();
11005 let arguments = arguments
11006 .iter()
11007 .map(|arg| {
11008 if let Some(name) = arg.get_variable_name() {
11009 eco_format!("{name}: {}", printer.print_type(&arg.type_))
11010 } else {
11011 let name = name_generator.generate_name_from_type(&arg.type_);
11012 eco_format!("_{name}: {}", printer.print_type(&arg.type_))
11013 }
11014 })
11015 .join(", ");
11016
11017 let function = format!(
11018 "\n\nfn {name}({arguments}) -> {return_type} {{
11019 {function_body}
11020}}"
11021 );
11022 self.edits.insert(function_end, function);
11023 }
11024
11025 /// For anonymous functions that capture variables from an external scope
11026 /// but only expect a single argument.
11027 /// Uses function caputre syntax to provide a more concise refactoring than
11028 /// `extract_anonymous_function_body`.
11029 fn extract_anonymous_function_with_capture_hole(
11030 &mut self,
11031 location: SrcSpan,
11032 code_location: SrcSpan,
11033 argument: &TypedArg,
11034 extra_parameters: Vec<(EcoString, Arc<Type>)>,
11035 return_type: Arc<Type>,
11036 function_end: u32,
11037 ) {
11038 let name = self.function_name();
11039
11040 // --- BEFORE
11041 // ```gleam
11042 // pub fn main() {
11043 // let needle = 42
11044 // let haystack = [25, 81, 74, 42, 33]
11045 // list.filter(haystack, fn(x) { x == needle })
11046 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
11047 // }
11048 // ```
11049 //
11050 // --- AFTER
11051 // ```gleam
11052 // pub fn main() {
11053 // let needle = 42
11054 // let haystack = [25, 81, 74, 42, 33]
11055 // list.filter(haystack, function(_, needle))
11056 // }
11057 //
11058 // fn function(x: Int, needle: Int) -> Bool {
11059 // x == needle
11060 // }
11061 // ```
11062
11063 let call = format!(
11064 "{name}(_, {})",
11065 extra_parameters.iter().map(|(name, _)| name).join(", ")
11066 );
11067 self.edits.replace(location, call);
11068
11069 let mut printer = Printer::new(&self.module.ast.names);
11070
11071 // build up the code for the newly generated function
11072 let return_type = printer.print_type(&return_type);
11073 let function_body = code_at(self.module, code_location);
11074 let argument = if let Some(name) = argument.get_variable_name() {
11075 eco_format!("{name}: {}", printer.print_type(&argument.type_))
11076 } else {
11077 let name = NameGenerator::new().generate_name_from_type(&argument.type_);
11078 eco_format!("_{name}: {}", printer.print_type(&argument.type_))
11079 };
11080 let extra_parameters = extra_parameters
11081 .iter()
11082 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11083 .join(", ");
11084
11085 let function = format!(
11086 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{
11087 {function_body}
11088}}"
11089 );
11090 self.edits.insert(function_end, function);
11091 }
11092
11093 /// For non-unary anonymous functions that capture variables from an external scope.
11094 /// Replaces just the _function body_ with a call to the newly generated function.
11095 fn extract_anonymous_function_body(
11096 &mut self,
11097 location: SrcSpan,
11098 arguments: &[TypedArg],
11099 extra_parameters: Vec<(EcoString, Arc<Type>)>,
11100 return_type: Arc<Type>,
11101 function_end: u32,
11102 ) {
11103 let name = self.function_name();
11104 // --- BEFORE
11105 // ```gleam
11106 // pub fn main() {
11107 // let factor = 2
11108 // list.fold([], 0, fn(acc, value) { acc + value * factor })
11109 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑
11110 // }
11111 // ```
11112 //
11113 // --- AFTER
11114 // ```gleam
11115 // pub fn main() {
11116 // let factor = 2
11117 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) })
11118 // }
11119 //
11120 // fn function(acc: Int, value: Int, factor: Int) -> Int {
11121 // acc + value * factor
11122 // }
11123 // ```
11124
11125 // if the programmer has ignored an argument, the generated function
11126 // cannot take it as an parameter
11127 let arguments = arguments
11128 .iter()
11129 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_)))
11130 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_)))
11131 .collect::<Vec<(&EcoString, &Arc<Type>)>>();
11132
11133 let call = format!(
11134 "{name}({})",
11135 arguments.iter().map(|(name, _)| name).join(", ")
11136 );
11137 self.edits.replace(location, call);
11138
11139 let mut printer = Printer::new(&self.module.ast.names);
11140
11141 let return_type = printer.print_type(&return_type);
11142 let function_body = code_at(self.module, location);
11143 let arguments = arguments
11144 .iter()
11145 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11146 .join(", ");
11147
11148 let function = format!(
11149 "\n\nfn {name}({arguments}) -> {return_type} {{
11150 {function_body}
11151}}"
11152 );
11153 self.edits.insert(function_end, function);
11154 }
11155
11156 /// Extracts a function whose return value does not get bound to a variable
11157 /// in the calling function.
11158 ///
11159 /// There are two cases:
11160 /// 1. The function is in tail position, and its return value is used as the
11161 /// return value of the calling function.
11162 /// 2. The function's return value is unused in the calling function, and so
11163 /// does not get bound to a variable.
11164 ///
11165 /// # Parameters
11166 ///
11167 /// In most cases, `location` and `code_location` are the same. They differ
11168 /// only when the code being extracted is a single block expression. In
11169 /// that case, the code being replaced in the calling function (`location`)
11170 /// includes the braces around the block, while the code being put into the
11171 /// body of the extracted function (`code_location`) does not.
11172 fn extract_unbound_statements(
11173 &mut self,
11174 location: SrcSpan,
11175 code_location: SrcSpan,
11176 parameters: Vec<(EcoString, Arc<Type>)>,
11177 return_type: Arc<Type>,
11178 function_end: u32,
11179 ) {
11180 let expression_code = code_at(self.module, code_location);
11181
11182 let name = self.function_name();
11183 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
11184 let call = format!("{name}({arguments})");
11185
11186 // Since we are only extracting a single expression, we can just replace
11187 // it with the call and preserve all other semantics; only one value can
11188 // be returned from the expression, unlike when extracting multiple
11189 // statements.
11190 self.edits.replace(location, call);
11191
11192 let mut printer = Printer::new(&self.module.ast.names);
11193
11194 let parameters = parameters
11195 .iter()
11196 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11197 .join(", ");
11198 let return_type = printer.print_type(&return_type);
11199
11200 let function = format!(
11201 "\n\nfn {name}({parameters}) -> {return_type} {{
11202 {expression_code}
11203}}"
11204 );
11205
11206 self.edits.insert(function_end, function);
11207 }
11208
11209 /// Extracts a function that does get bound to one or more variables in the
11210 /// calling function.
11211 ///
11212 /// For example, in the code
11213 ///
11214 /// ```gleam
11215 /// pub fn main() {
11216 /// let a = 10
11217 /// //^ Select from here
11218 /// let b = 20
11219 /// let c = a + b
11220 /// // ^ Until here
11221 ///
11222 /// echo a
11223 /// echo b
11224 /// echo c
11225 /// }
11226 /// ```
11227 ///
11228 /// multiple values defined in the extracted block are used in the calling
11229 /// function. So, the extracted function must return multiple values that
11230 /// are bound to variables: `let #(a, b, c) = function()`.
11231 ///
11232 /// There is a special case when there is only one value being bound, as it
11233 /// is unnecessary to create a single-element tuple. For example:
11234 ///
11235 /// ```gleam
11236 /// pub fn main() {
11237 /// let a = 10
11238 /// //^ Select from here
11239 /// let b = 20
11240 /// let c = a + b
11241 /// // ^ Until here
11242 ///
11243 /// echo c
11244 /// }
11245 /// ```
11246 ///
11247 /// Here, the return value of the extracted function will be bound to a
11248 /// single variable in the calling function: `let c = function()`.
11249 fn extract_bound_statements(
11250 &mut self,
11251 location: SrcSpan,
11252 parameters: Vec<(EcoString, Arc<Type>)>,
11253 returned_variables: Vec<(EcoString, Arc<Type>)>,
11254 function_end: u32,
11255 ) {
11256 let code = code_at(self.module, location);
11257
11258 let (return_type, return_value) = match returned_variables.as_slice() {
11259 [(returned_name, type_)] => (type_.clone(), returned_name.clone()),
11260 _ => {
11261 let returned_names = returned_variables
11262 .iter()
11263 .map(|(name, _type)| name)
11264 .join(", ");
11265 let return_value = eco_format!("#({returned_names})");
11266
11267 let returned_types = returned_variables
11268 .into_iter()
11269 .map(|(_name, type_)| type_)
11270 .collect();
11271 let type_ = type_::tuple(returned_types);
11272
11273 (type_, return_value)
11274 }
11275 };
11276
11277 let name = self.function_name();
11278 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
11279 let call = format!("let {return_value} = {name}({arguments})");
11280
11281 self.edits.replace(location, call);
11282
11283 let mut printer = Printer::new(&self.module.ast.names);
11284
11285 let parameters = parameters
11286 .iter()
11287 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
11288 .join(", ");
11289 let return_type = printer.print_type(&return_type);
11290
11291 let function = format!(
11292 "\n\nfn {name}({parameters}) -> {return_type} {{
11293 {code}
11294 {return_value}
11295}}"
11296 );
11297
11298 self.edits.insert(function_end, function);
11299 }
11300
11301 fn extract_pipeline_steps(
11302 &mut self,
11303 location: SrcSpan,
11304 function_end: u32,
11305 parameters: Vec<(EcoString, Arc<Type>)>,
11306 before_first: Option<Arc<Type>>,
11307 return_type: Arc<Type>,
11308 ) {
11309 let name = self.function_name();
11310 let code = code_at(self.module, location);
11311 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", ");
11312 let replacement = match before_first {
11313 Some(_) if parameters.is_empty() => format!("{name}"),
11314 Some(_) | None => format!("{name}({arguments})"),
11315 };
11316 self.edits.replace(location, replacement);
11317
11318 // When extracting something out of the middle of a pipeline the
11319 // function we produce will produce a single value as output but could
11320 // take multiple values as input:
11321 //
11322 // ```gleam
11323 // wibble
11324 // |> wobble(a) // extracting this
11325 // |> woo(b) //
11326 // |> something
11327 // ```
11328 //
11329 // It will take the type returned by `wibble`, `a`, and `b` as input,
11330 // and produce the value returned by `woo` as output:
11331 //
11332 // ```gleam
11333 // wibble
11334 // |> function(a, b)
11335 // |> something
11336 // ```
11337 //
11338 // If the steps extracted are at the beginning of the pipeline, then it
11339 // won't take that additional argument!
11340 //
11341 // ```gleam
11342 // wibble // extracting these
11343 // |> wobble(a) //
11344 // |> woo(b) //
11345 // |> something
11346 // ```
11347 //
11348 // Becomes:
11349 //
11350 // ```gleam
11351 // function(a, b)
11352 // |> something
11353 // ```
11354
11355 let mut type_printer = Printer::new(&self.module.ast.names);
11356 let return_type = type_printer.print_type(&return_type);
11357 let first_argument = before_first.map(|type_of_first_argument| {
11358 let mut generator = NameGenerator::new();
11359 for (name, _) in parameters.iter() {
11360 generator.add_used_name(name.clone());
11361 }
11362 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument);
11363 (first_argument_name, type_of_first_argument.clone())
11364 });
11365 let parameters = first_argument
11366 .clone()
11367 .into_iter()
11368 .chain(parameters)
11369 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_)))
11370 .join(", ");
11371
11372 let code = if let Some((first_argument_name, _)) = first_argument {
11373 format!("{first_argument_name}\n |> {code}")
11374 } else {
11375 code.trim_start_matches("|>").to_string()
11376 };
11377 let function = format!(
11378 "\n\nfn {name}({parameters}) -> {return_type} {{
11379 {code}
11380}}"
11381 );
11382
11383 self.edits.insert(function_end, function);
11384 }
11385
11386 /// When a variable is referenced, we need to decide if we need to do anything
11387 /// to ensure that the reference is still valid after extracting a function.
11388 /// If the variable is defined outside the extracted function, but used inside
11389 /// it, then we need to add it as a parameter of the function. Similarly, if
11390 /// a variable is defined inside the extracted code, but used outside of it,
11391 /// we need to ensure that value is returned from the function so that it is
11392 /// accessible.
11393 fn register_referenced_variable(
11394 &mut self,
11395 name: &EcoString,
11396 type_: &Arc<Type>,
11397 location: SrcSpan,
11398 definition_location: SrcSpan,
11399 ) {
11400 let Some(extracted) = &mut self.function else {
11401 return;
11402 };
11403
11404 let extracted_location = extracted.location();
11405
11406 // If a variable defined outside the extracted code is referenced inside
11407 // it, we need to add it to the list of parameters.
11408 let variables = if extracted_location.contains_span(location)
11409 && !extracted_location.contains_span(definition_location)
11410 {
11411 &mut extracted.parameters
11412 // If a variable defined inside the extracted code is referenced outside
11413 // it, then we need to ensure that it is returned from the function.
11414 } else if extracted_location.contains_span(definition_location)
11415 && !extracted_location.contains_span(location)
11416 {
11417 &mut extracted.returned_variables
11418 } else {
11419 return;
11420 };
11421
11422 // If the variable has already been tracked, no need to register it again.
11423 // We use a `Vec` here rather than a `HashMap` because we want to ensure
11424 // the order of arguments is consistent; in this case it will be determined
11425 // by the order the variables are used. This isn't always desired, but it's
11426 // better than random order, and makes it easier to write tests too.
11427 // The cost of iterating the list here is minimal; it is unlikely that
11428 // a given function will ever have more than 10 or so parameters.
11429 if variables.iter().any(|(variable, _)| variable == name) {
11430 return;
11431 }
11432
11433 variables.push((name.clone(), type_.clone()));
11434 }
11435
11436 fn can_extract_expression(&self, expression: &TypedExpr) -> bool {
11437 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
11438 let selected_range = self.params.range;
11439
11440 // If the selected range doesn't touch the expression at all, then there
11441 // is no reason to extract it.
11442 if !overlaps(expression_range, selected_range) {
11443 return false;
11444 }
11445
11446 match expression {
11447 TypedExpr::Pipeline {
11448 first_value,
11449 finally,
11450 ..
11451 } => {
11452 // We can extract a pipeline as a whole only if the selection
11453 // spans all of its steps!
11454 let first_step = self.edits.src_span_to_lsp_range(first_value.location);
11455 let last_step = self.edits.src_span_to_lsp_range(finally.location());
11456 position_within(selected_range.start, first_step)
11457 && position_within(selected_range.end, last_step)
11458 }
11459
11460 TypedExpr::Int { .. }
11461 | TypedExpr::Float { .. }
11462 | TypedExpr::String { .. }
11463 | TypedExpr::Block { .. }
11464 | TypedExpr::Var { .. }
11465 | TypedExpr::Fn { .. }
11466 | TypedExpr::List { .. }
11467 | TypedExpr::Call { .. }
11468 | TypedExpr::BinOp { .. }
11469 | TypedExpr::Case { .. }
11470 | TypedExpr::RecordAccess { .. }
11471 | TypedExpr::PositionalAccess { .. }
11472 | TypedExpr::ModuleSelect { .. }
11473 | TypedExpr::Tuple { .. }
11474 | TypedExpr::TupleIndex { .. }
11475 | TypedExpr::Todo { .. }
11476 | TypedExpr::Panic { .. }
11477 | TypedExpr::Echo { .. }
11478 | TypedExpr::BitArray { .. }
11479 | TypedExpr::RecordUpdate { .. }
11480 | TypedExpr::NegateBool { .. }
11481 | TypedExpr::NegateInt { .. }
11482 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range),
11483 }
11484 }
11485
11486 fn can_extract_statement(&self, statement: &TypedStatement) -> bool {
11487 let statement_range = self.edits.src_span_to_lsp_range(statement.location());
11488 let selected_range = self.params.range;
11489
11490 // If the selected range doesn't touch the statement at all, then there
11491 // is no reason to extract it.
11492 if !overlaps(statement_range, selected_range) {
11493 return false;
11494 }
11495
11496 match statement {
11497 ast::Statement::Expression(expression) => self.can_extract_expression(expression),
11498
11499 // Determine whether the selected range falls completely within the
11500 // expression. For example:
11501 // ```gleam
11502 // pub fn main() {
11503 // let something = {
11504 // let a = 1
11505 // let b = 2
11506 // let c = a + b
11507 // //^ The user has selected from here
11508 // let d = a * b
11509 // c / d
11510 // // ^ Until here
11511 // }
11512 // }
11513 // ```
11514 //
11515 // Here, the selected range does overlap with the `let something`
11516 // statement; but we don't want to extract that whole statement! The
11517 // user only wanted to extract the statements inside the block. So if
11518 // the selected range falls completely within the expression, we ignore
11519 // it and traverse the tree further until we find exactly what the user
11520 // selected.
11521 //
11522 // If the selected range is completely within the expression, we don't
11523 // want to extract it.
11524 ast::Statement::Use(_) | ast::Statement::Assert(_) => {
11525 !completely_within(selected_range, statement_range)
11526 }
11527
11528 // We can only extract a whole let statement if the assignment
11529 // part itself is selected. If the only part being selected is the
11530 // expression then the right call is not extracting the whole
11531 // statement but just the expression.
11532 ast::Statement::Assignment(assignment) => {
11533 let value_range = self
11534 .edits
11535 .src_span_to_lsp_range(assignment.value.location());
11536
11537 !within(selected_range, value_range)
11538 && !completely_within(selected_range, statement_range)
11539 }
11540 }
11541 }
11542}
11543
11544impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
11545 fn visit_typed_function(&mut self, function: &'ast TypedFunction) {
11546 let range = self.edits.src_span_to_lsp_range(function.full_location());
11547
11548 if within(self.params.range, range) {
11549 self.function_end_position = Some(function.end_position);
11550 self.last_statement_location = function.body.last().map(|last| last.location());
11551
11552 ast::visit::visit_typed_function(self, function);
11553 }
11554 }
11555
11556 fn visit_typed_expr_block(
11557 &mut self,
11558 location: &'ast SrcSpan,
11559 statements: &'ast [TypedStatement],
11560 ) {
11561 let last_statement_location = self.last_statement_location;
11562 self.last_statement_location = statements.last().map(|last| last.location());
11563
11564 ast::visit::visit_typed_expr_block(self, location, statements);
11565
11566 self.last_statement_location = last_statement_location;
11567 }
11568
11569 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
11570 let last_statement_location = self.last_statement_location;
11571 // The body must be visited first, before the desugared function
11572 if let TypedExpr::Call { arguments, .. } = &*use_.call
11573 && let Some(CallArg {
11574 value: TypedExpr::Fn { body, .. },
11575 ..
11576 }) = arguments.last()
11577 {
11578 self.last_statement_location = Some(body.last().location());
11579 for statement in body {
11580 self.visit_typed_statement(statement);
11581 }
11582 }
11583 ast::visit::visit_typed_use(self, use_);
11584 self.last_statement_location = last_statement_location;
11585 }
11586
11587 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
11588 // If we have already determined what code we want to extract, we don't
11589 // want to extract this instead. This expression would be inside the
11590 // piece of code we already are going to extract, leading to us
11591 // extracting just a single literal in any selection, which is of course
11592 // not desired.
11593 if self.function.is_none() {
11594 // If this expression is fully selected, we mark it as being extracted.
11595 if self.can_extract_expression(expression) {
11596 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
11597 expression,
11598 )));
11599 }
11600 }
11601
11602 // If the expression is a function, then the last statement location
11603 // needs to be updated accordingly
11604 let last_statement_location = self.last_statement_location;
11605 if let TypedExpr::Fn { body, .. } = expression {
11606 self.last_statement_location = Some(body.last().location());
11607 }
11608
11609 ast::visit::visit_typed_expr(self, expression);
11610
11611 self.last_statement_location = last_statement_location;
11612 }
11613
11614 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
11615 let statement_location = statement.location();
11616
11617 if self.can_extract_statement(statement) {
11618 let is_in_tail_position =
11619 self.last_statement_location
11620 .is_some_and(|last_statement_location| {
11621 last_statement_location == statement_location
11622 });
11623
11624 // A use is always eating up the entire block, if we're extracting it,
11625 // it will be in tail position there and the extracted function should
11626 // return its returned value.
11627 let position = if statement.is_use() || is_in_tail_position {
11628 StatementPosition::Tail
11629 } else {
11630 StatementPosition::NotTail
11631 };
11632
11633 match &mut self.function {
11634 None => {
11635 self.function = match statement {
11636 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None,
11637 TypedStatement::Assert(_)
11638 | TypedStatement::Assignment(_)
11639 | TypedStatement::Expression(_) => {
11640 Some(ExtractedFunction::new(ExtractedValue::Statements {
11641 location: statement_location,
11642 position,
11643 type_: statement.type_(),
11644 }))
11645 }
11646 TypedStatement::Use(use_) => {
11647 Some(ExtractedFunction::new(ExtractedValue::Use {
11648 location: use_.call.location(),
11649 use_line_location: use_.location,
11650 type_: statement.type_(),
11651 }))
11652 }
11653 };
11654 }
11655
11656 // If we're extracting something that is within a use expression
11657 // we need to check whether to extract the whole use or just
11658 // statements inside of it
11659 Some(ExtractedFunction {
11660 value:
11661 ExtractedValue::Use {
11662 location,
11663 use_line_location,
11664 ..
11665 },
11666 parameters,
11667 returned_variables,
11668 }) if location.contains_span(statement_location) => {
11669 let use_line_range = self.edits.src_span_to_lsp_range(*use_line_location);
11670
11671 // If the current statement is not a part of the use line,
11672 // and the current selection does not overlap the use line,
11673 // then the outer use is not the correct target.
11674 if !use_line_location.contains_span(statement_location)
11675 && !overlaps(use_line_range, self.params.range)
11676 {
11677 self.function = Some(ExtractedFunction {
11678 value: ExtractedValue::Statements {
11679 location: statement_location,
11680 position,
11681 type_: statement.type_(),
11682 },
11683 parameters: parameters.to_vec(),
11684 returned_variables: returned_variables.to_vec(),
11685 });
11686 }
11687 }
11688 // Otherwise it means we're extracting multiple statements
11689 // _including_ some use expression, we fallback to extracting
11690 // multiple statements
11691 Some(ExtractedFunction {
11692 value: value @ ExtractedValue::Use { .. },
11693 ..
11694 }) => {
11695 *value = ExtractedValue::Statements {
11696 location: value.location().merge(&statement_location),
11697 position,
11698 type_: statement.type_(),
11699 };
11700 }
11701
11702 // If we have already chosen an expression to extract, that means
11703 // that this statement is within the already extracted expression,
11704 // so we don't want to extract this instead.
11705 Some(ExtractedFunction {
11706 value: ExtractedValue::Expression(_),
11707 ..
11708 }) => {}
11709 // If we are selecting multiple statements, this statement should
11710 // be included within that list, so we merge the spans to ensure
11711 // it is included.
11712 Some(ExtractedFunction {
11713 value:
11714 ExtractedValue::Statements {
11715 location,
11716 position: extracted_position,
11717 type_: extracted_type,
11718 },
11719 ..
11720 }) => {
11721 *location = location.merge(&statement_location);
11722 *extracted_position = position;
11723 *extracted_type = statement.type_();
11724 }
11725 Some(ExtractedFunction {
11726 value: value @ ExtractedValue::PipelineSteps { .. },
11727 ..
11728 }) => {
11729 // If we were extracting a pipeline, but end up selecting
11730 // some statement that is not part of it, then we go back to
11731 // selecting a batch of statements.
11732 *value = ExtractedValue::Statements {
11733 location: value.location(),
11734 position,
11735 type_: statement.type_(),
11736 }
11737 }
11738 }
11739 }
11740 ast::visit::visit_typed_statement(self, statement);
11741 }
11742
11743 fn visit_typed_expr_pipeline(
11744 &mut self,
11745 _location: &'ast SrcSpan,
11746 first_value: &'ast TypedPipelineAssignment,
11747 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
11748 finally: &'ast TypedExpr,
11749 _finally_kind: &'ast PipelineAssignmentKind,
11750 ) {
11751 self.previous_pipeline_assignment_type = None;
11752 self.visit_typed_pipeline_assignment(first_value);
11753
11754 self.previous_pipeline_assignment_type = Some(first_value.type_());
11755 for (assignment, _kind) in assignments {
11756 self.visit_typed_pipeline_assignment(assignment);
11757 self.previous_pipeline_assignment_type = Some(assignment.type_());
11758 }
11759
11760 // If we're selecting a pipeline and the selection ends on its final step
11761 // we want to include that as well into the extracted bit.
11762 let final_step_range = self.edits.src_span_to_lsp_range(finally.location());
11763 if let Some(extracted_function) = &mut self.function
11764 && position_within(self.params.range.end, final_step_range)
11765 {
11766 extracted_function.try_add_pipeline_step(finally.type_(), finally.location());
11767 }
11768
11769 self.visit_typed_expr(finally);
11770 self.previous_pipeline_assignment_type = None;
11771 }
11772
11773 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
11774 // In order to be extracted, a pipeline step must be overlapping with
11775 // the cursor selection!
11776 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
11777 if !overlaps(self.params.range, assignment_range) {
11778 return;
11779 }
11780
11781 match &mut self.function {
11782 None => {
11783 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps {
11784 location: assignment.location,
11785 before_first: self.previous_pipeline_assignment_type.clone(),
11786 return_type: assignment.type_(),
11787 }));
11788 }
11789 Some(extracted_function) => {
11790 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location);
11791 }
11792 }
11793 ast::visit::visit_typed_pipeline_assignment(self, assignment);
11794 }
11795
11796 fn visit_typed_expr_var(
11797 &mut self,
11798 location: &'ast SrcSpan,
11799 constructor: &'ast ValueConstructor,
11800 name: &'ast EcoString,
11801 ) {
11802 if let type_::ValueConstructorVariant::LocalVariable {
11803 location: definition_location,
11804 ..
11805 } = &constructor.variant
11806 {
11807 self.register_referenced_variable(
11808 name,
11809 &constructor.type_,
11810 *location,
11811 *definition_location,
11812 );
11813 }
11814 }
11815
11816 fn visit_typed_clause_guard_var(
11817 &mut self,
11818 location: &'ast SrcSpan,
11819 name: &'ast EcoString,
11820 type_: &'ast Arc<Type>,
11821 definition_location: &'ast SrcSpan,
11822 _origin: &'ast VariableOrigin,
11823 ) {
11824 self.register_referenced_variable(name, type_, *location, *definition_location);
11825 }
11826
11827 fn visit_typed_expr_case(
11828 &mut self,
11829 location: &'ast SrcSpan,
11830 type_: &'ast Arc<Type>,
11831 subjects: &'ast [TypedExpr],
11832 clauses: &'ast [ast::TypedClause],
11833 compiled_case: &'ast CompiledCase,
11834 ) {
11835 let was_extracting_already = self.function.is_some();
11836
11837 // We first visit as usual...
11838 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
11839
11840 // But then we need to check we're in a situation where it actually makes
11841 // sense to extract: if the cursor is entirely within the case (so it's
11842 // not part of a bigger extracted chunk) and it spans over one of the
11843 // branches' pattern or guard, then we don't want to allow extracting
11844 // anything. Popping up the action would be confusing.
11845 // For example:
11846 //
11847 // ```gleam
11848 // case wibble {
11849 // Ok(_) -> todo
11850 // //^^^^^^^^^^^^^ If I'm selecting this whole branch it makes no sense
11851 // to propose extracting it as a function
11852 // _ if wibble -> todo
11853 // // ^^^^^^^^^^^ If I'm selecting this it makes no sense to
11854 // // propose extracting it as a function
11855 // }
11856 // ```
11857
11858 // We were already extracting something, we don't want to render that
11859 // choice null, this is just a part of a bigger piece being extracted.
11860 if was_extracting_already {
11861 return;
11862 }
11863
11864 for clause in clauses {
11865 let left_hand_side_location = SrcSpan {
11866 start: clause.pattern_location().start,
11867 end: clause.then.location().start - 1,
11868 };
11869 let left_hand_side_range = self.edits.src_span_to_lsp_range(left_hand_side_location);
11870 // If there's any overlapping with one of the patterns, then we
11871 // don't want to extract anything.
11872 if overlaps(self.params.range, left_hand_side_range) {
11873 self.function = None;
11874 break;
11875 }
11876 }
11877 }
11878
11879 fn visit_typed_bit_array_size_variable(
11880 &mut self,
11881 location: &'ast SrcSpan,
11882 name: &'ast EcoString,
11883 constructor: &'ast Option<Box<ValueConstructor>>,
11884 type_: &'ast Arc<Type>,
11885 ) {
11886 let variant = match constructor {
11887 Some(constructor) => &constructor.variant,
11888 None => return,
11889 };
11890 if let type_::ValueConstructorVariant::LocalVariable {
11891 location: definition_location,
11892 ..
11893 } = variant
11894 {
11895 self.register_referenced_variable(name, type_, *location, *definition_location);
11896 }
11897 }
11898}
11899
11900/// Code action to merge two identical branches together.
11901///
11902pub struct MergeCaseBranches<'a> {
11903 module: &'a Module,
11904 params: &'a CodeActionParams,
11905 edits: TextEdits<'a>,
11906 /// These are the positions of the patterns of all the consecutive branches
11907 /// we've determined can be merged, for example if we're mergin the first
11908 /// two branches here:
11909 ///
11910 /// ```gleam
11911 /// case wibble {
11912 /// 1 -> todo
11913 /// // ^ this location here
11914 /// 20 -> todo
11915 /// // ^^ and this location here
11916 /// _ -> todo
11917 /// }
11918 /// ```
11919 ///
11920 /// We need those to delete all the space between each consecutive pattern,
11921 /// replacing it with the `|` for alternatives
11922 ///
11923 patterns_to_merge: Option<MergeableBranches>,
11924}
11925
11926struct MergeableBranches {
11927 /// The span of the body to keep when merging multiple branches. For
11928 /// example:
11929 ///
11930 /// ```gleam
11931 /// case n {
11932 /// // Imagine we're merging the first three branches together...
11933 /// 1 -> todo
11934 /// 2 -> n * 2
11935 /// // ^^^^^ This would be the location of the one body to keep
11936 /// 3 -> todo
11937 /// _ -> todo
11938 /// }
11939 /// ```
11940 ///
11941 body_to_keep: SrcSpan,
11942
11943 /// The location body of the last of the branches that are going to be
11944 /// merged; that is where we're going to place the code of the body to keep
11945 /// once the action is done. For example:
11946 ///
11947 /// ```gleam
11948 /// case n {
11949 /// // Imagine we're merging the first three branches together...
11950 /// 1 -> todo
11951 /// 2 -> n * 2
11952 /// 3 -> todo
11953 /// // ^^^^ This would be the location of the final body
11954 /// _ -> todo
11955 /// }
11956 /// ```
11957 ///
11958 final_body: SrcSpan,
11959
11960 /// The span of the patterns whose branches are going to be merged. For
11961 /// example:
11962 ///
11963 /// ```gleam
11964 /// case n {
11965 /// // Imagine we're merging the first three branches together...
11966 /// 1 -> todo
11967 /// // ^
11968 /// 2 -> n * 2
11969 /// // ^
11970 /// 3 -> todo
11971 /// // ^ These would be the locations of the patterns
11972 /// _ -> todo
11973 /// }
11974 /// ```
11975 ///
11976 patterns_to_merge: Vec<SrcSpan>,
11977}
11978
11979impl<'a> MergeCaseBranches<'a> {
11980 pub fn new(
11981 module: &'a Module,
11982 line_numbers: &'a LineNumbers,
11983 params: &'a CodeActionParams,
11984 ) -> Self {
11985 Self {
11986 module,
11987 params,
11988 edits: TextEdits::new(line_numbers),
11989 patterns_to_merge: None,
11990 }
11991 }
11992
11993 pub fn code_actions(mut self) -> Vec<CodeAction> {
11994 self.visit_typed_module(&self.module.ast);
11995
11996 let Some(mergeable_branches) = self.patterns_to_merge else {
11997 return vec![];
11998 };
11999
12000 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() {
12001 self.edits
12002 .replace(SrcSpan::new(one.end, next.start), " | ".into());
12003 }
12004
12005 self.edits.replace(
12006 mergeable_branches.final_body,
12007 code_at(self.module, mergeable_branches.body_to_keep).into(),
12008 );
12009
12010 let mut action = Vec::with_capacity(1);
12011 CodeActionBuilder::new("Merge case branches")
12012 .kind(CodeActionKind::RefactorRewrite)
12013 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12014 .preferred(false)
12015 .push_to(&mut action);
12016 action
12017 }
12018
12019 fn select_mergeable_branches(
12020 &self,
12021 clauses: &'a [ast::TypedClause],
12022 ) -> Option<MergeableBranches> {
12023 let mut clauses = clauses
12024 .iter()
12025 // We want to skip all the branches at the beginning of the case
12026 // expression that the cursor is not hovering over. For example:
12027 //
12028 // ```gleam
12029 // case wibble {
12030 // a -> 1 <- we want to skip this one here that is not selected
12031 // b -> 2
12032 // ^^^^ this is the selection
12033 // _ -> 3
12034 // ^^
12035 // }
12036 // ```
12037 .skip_while(|clause| {
12038 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
12039 !overlaps(self.params.range, clause_range)
12040 })
12041 // Then we only want to take the clauses that we're hovering over
12042 // with our selection (even partially!)
12043 // In the provious example they would be `b -> 2` and `_ -> 3`.
12044 .take_while(|clause| {
12045 let clause_range = self.edits.src_span_to_lsp_range(clause.location);
12046 overlaps(self.params.range, clause_range)
12047 });
12048
12049 let first_hovered_clause = clauses.next()?;
12050
12051 // This is the clause we're comparing all the others with. We need to
12052 // make sure that all the clauses we're going to join can be merged with
12053 // this one.
12054 let mut reference_clause = first_hovered_clause;
12055 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()];
12056 let mut final_body = first_hovered_clause.then.location();
12057
12058 for clause in clauses {
12059 // As soon as we find a clause that can't be merged with the current
12060 // reference we know we're done looking for consecutive clauses to
12061 // merge.
12062 if !clauses_can_be_merged(reference_clause, clause) {
12063 break;
12064 }
12065
12066 clause_patterns_to_merge.push(clause.pattern_location());
12067 final_body = clause.then.location();
12068
12069 // If the current reference is a `todo` expression, we want to use
12070 // the newly found mergeable clause as the next reference. The
12071 // reference clause is the one whose body will be kept around, so if
12072 // we can we avoid keeping `todo`s
12073 if reference_clause.then.is_todo_with_no_message() {
12074 reference_clause = clause;
12075 }
12076 }
12077
12078 // We only offer the code action if we have found two or more clauses
12079 // to merge.
12080 if clause_patterns_to_merge.len() >= 2 {
12081 Some(MergeableBranches {
12082 final_body,
12083 body_to_keep: reference_clause.then.location(),
12084 patterns_to_merge: clause_patterns_to_merge,
12085 })
12086 } else {
12087 None
12088 }
12089 }
12090}
12091
12092fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool {
12093 // Two clauses cannot be merged if any of those has an if guard
12094 if one.guard.is_some() || other.guard.is_some() {
12095 return false;
12096 }
12097
12098 // Two clauses can only be merged if they define the same variables,
12099 // otherwise joining them would result in invalid code.
12100 let variables_one = one
12101 .bound_variables()
12102 .map(|variable| (variable.name(), variable.type_))
12103 .collect::<HashMap<_, _>>();
12104
12105 let variables_other = other
12106 .bound_variables()
12107 .map(|variable| (variable.name(), variable.type_))
12108 .collect::<HashMap<_, _>>();
12109
12110 for (name, type_) in variables_one.iter() {
12111 if let Some(type_other) = variables_other.get(name)
12112 && type_other.same_as(type_)
12113 {
12114 continue;
12115 }
12116
12117 // There's a variable that is not defined in the second branch but
12118 // is defined in the first one, or it's defined in the second branch
12119 // but it has an incompatible type.
12120 return false;
12121 }
12122
12123 for (name, _) in variables_other.iter() {
12124 if !variables_one.contains_key(name) {
12125 // There's some variables defined in the second branch that are not
12126 // defined in the first one, so they can't be merged!
12127 return false;
12128 }
12129 }
12130
12131 // Anything can be merged with a simple todo, or the two bodies must be
12132 // syntactically equal.
12133 one.then.is_todo_with_no_message()
12134 || other.then.is_todo_with_no_message()
12135 || one.then.syntactically_eq(&other.then)
12136}
12137
12138impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> {
12139 fn visit_typed_expr_case(
12140 &mut self,
12141 location: &'ast SrcSpan,
12142 type_: &'ast Arc<Type>,
12143 subjects: &'ast [TypedExpr],
12144 clauses: &'ast [ast::TypedClause],
12145 compiled_case: &'ast CompiledCase,
12146 ) {
12147 // We only trigger the code action if we are within a case expression,
12148 // otherwise there's no point in exploring the expression any further.
12149 let case_range = self.edits.src_span_to_lsp_range(*location);
12150 if !within(self.params.range, case_range) {
12151 return;
12152 }
12153
12154 if let result @ Some(_) = self.select_mergeable_branches(clauses) {
12155 self.patterns_to_merge = result;
12156 }
12157
12158 // We still need to visit the case expression in case we want to apply
12159 // the code action to some case expression that is nested in one of its
12160 // branches!
12161 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
12162 }
12163}
12164
12165/// Code action to add a missing type parameter to custom types.
12166/// If a custom type is missing a type parameter, as it is the case
12167/// in the following example, this action will offer to add the
12168/// type parameter to the type definition.
12169///
12170/// Before:
12171/// ```gleam
12172/// type Wibble {
12173/// Wibble(field: t)
12174/// }
12175/// ```
12176///
12177/// After:
12178/// ```gleam
12179/// type Wibble(t) {
12180/// Wibble(field: t)
12181/// }
12182/// ```
12183///
12184pub struct AddMissingTypeParameter<'a> {
12185 module: &'a Module,
12186 params: &'a CodeActionParams,
12187 edits: TextEdits<'a>,
12188 /// The source location where the parameters should be defined.
12189 /// This might be a zero-length span if there are no parameters yet,
12190 /// or it might cover the already existing type parameter definitions.
12191 parameters_location: Option<SrcSpan>,
12192 /// If the type definition already had existing parameters before.
12193 has_existing_parameters: bool,
12194 /// The set of all type parameter names in the different variants of the type
12195 /// that are not already part of the type parameter definition on the type.
12196 missing_parameters: HashSet<EcoString>,
12197}
12198
12199impl<'a> AddMissingTypeParameter<'a> {
12200 pub fn new(
12201 module: &'a Module,
12202 line_numbers: &'a LineNumbers,
12203 params: &'a CodeActionParams,
12204 ) -> Self {
12205 Self {
12206 module,
12207 params,
12208 edits: TextEdits::new(line_numbers),
12209 parameters_location: None,
12210 has_existing_parameters: false,
12211 missing_parameters: HashSet::new(),
12212 }
12213 }
12214
12215 pub fn code_actions(mut self) -> Vec<CodeAction> {
12216 self.visit_typed_module(&self.module.ast);
12217
12218 let Some(type_parameters_location) = self.parameters_location else {
12219 return vec![];
12220 };
12221
12222 if self.missing_parameters.is_empty() {
12223 return vec![];
12224 }
12225
12226 let mut new_parameters = self.missing_parameters.iter().sorted().join(", ");
12227 if self.has_existing_parameters {
12228 let has_trailing_comma = self
12229 .module
12230 .extra
12231 .trailing_commas
12232 .iter()
12233 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma));
12234
12235 if !has_trailing_comma {
12236 new_parameters.insert_str(0, ", ");
12237 }
12238
12239 self.edits
12240 .insert(type_parameters_location.end - 1, new_parameters);
12241 } else {
12242 self.edits
12243 .insert(type_parameters_location.end, format!("({new_parameters})"));
12244 }
12245
12246 let mut action = Vec::with_capacity(1);
12247 CodeActionBuilder::new("Add missing type parameter")
12248 .kind(CodeActionKind::QuickFix)
12249 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12250 .preferred(true)
12251 .push_to(&mut action);
12252 action
12253 }
12254}
12255
12256impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> {
12257 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
12258 let custom_type_range = self
12259 .edits
12260 .src_span_to_lsp_range(custom_type.full_location());
12261
12262 // Only continue, if the action was selected anywhere within the custom type definition.
12263 if !within(self.params.range, custom_type_range) {
12264 return;
12265 }
12266
12267 self.parameters_location = Some(SrcSpan::new(
12268 custom_type.name_location.end,
12269 custom_type.location.end,
12270 ));
12271
12272 self.has_existing_parameters = !custom_type.typed_parameters.is_empty();
12273
12274 let existing_names: HashSet<_> = custom_type
12275 .parameters
12276 .iter()
12277 .map(|(_, name)| name)
12278 .collect();
12279
12280 // Collect the remaining type parameters from the variant constructors.
12281 for record in &custom_type.constructors {
12282 for argument in &record.arguments {
12283 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast
12284 && !existing_names.contains(name)
12285 {
12286 let _ = self.missing_parameters.insert(name.clone());
12287 }
12288 }
12289 }
12290 }
12291}
12292
12293/// Code action to replace a `_` with its actual type in an annotation.
12294///
12295/// Before:
12296/// ```gleam
12297/// fn wibble() -> Ok(_) { Ok(1) }
12298/// // ^ Trigger it here
12299/// ```
12300///
12301/// After:
12302/// ```gleam
12303/// fn wibble() -> Ok(Int) { Ok(1) }
12304/// ```
12305///
12306pub struct ReplaceUnderscoreWithType<'a> {
12307 module: &'a Module,
12308 params: &'a CodeActionParams,
12309 edits: TextEdits<'a>,
12310 hovered_hole: Option<HoveredHole>,
12311}
12312
12313struct HoveredHole {
12314 type_: Arc<Type>,
12315 location: SrcSpan,
12316}
12317
12318impl<'a> ReplaceUnderscoreWithType<'a> {
12319 pub fn new(
12320 module: &'a Module,
12321 line_numbers: &'a LineNumbers,
12322 params: &'a CodeActionParams,
12323 ) -> Self {
12324 Self {
12325 module,
12326 params,
12327 edits: TextEdits::new(line_numbers),
12328 hovered_hole: None,
12329 }
12330 }
12331
12332 pub fn code_actions(mut self) -> Vec<CodeAction> {
12333 self.visit_typed_module(&self.module.ast);
12334
12335 let mut action = Vec::with_capacity(1);
12336
12337 let Some(HoveredHole { type_, location }) = self.hovered_hole else {
12338 return vec![];
12339 };
12340 let mut printer = Printer::new(&self.module.ast.names);
12341 self.edits
12342 .replace(location, format!("{}", printer.print_type(&type_)));
12343
12344 CodeActionBuilder::new("Replace `_` with type")
12345 .kind(CodeActionKind::QuickFix)
12346 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12347 .preferred(true)
12348 .push_to(&mut action);
12349 action
12350 }
12351}
12352
12353impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> {
12354 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) {
12355 // We never traverse a type annotation we're not hovering
12356 let node_location = self.edits.src_span_to_lsp_range(node.location());
12357 if !within(self.params.range, node_location) {
12358 return;
12359 }
12360 ast::visit::visit_type_ast(self, node, inferred_type);
12361 }
12362
12363 fn visit_type_ast_hole(
12364 &mut self,
12365 location: &'ast SrcSpan,
12366 _name: &'ast EcoString,
12367 type_: Option<Arc<Type>>,
12368 ) {
12369 if let Some(type_) = type_ {
12370 self.hovered_hole = Some(HoveredHole {
12371 type_,
12372 location: *location,
12373 });
12374 }
12375 }
12376}
12377
12378/// Code action to turn a function used as a reference into a one-statement anonymous function.
12379///
12380/// For example, if the code action was used on `op` here:
12381///
12382/// ```gleam
12383/// list.map([1, 2, 3], op)
12384/// ```
12385///
12386/// it would become:
12387///
12388/// ```gleam
12389/// list.map([1, 2, 3], fn(int) {
12390/// op(int)
12391/// })
12392/// ```
12393pub struct WrapInAnonymousFunction<'a> {
12394 module: &'a Module,
12395 params: &'a CodeActionParams,
12396 functions: Vec<FunctionToWrap>,
12397 edits: TextEdits<'a>,
12398}
12399
12400/// Helper struct, a target for [WrapInAnonymousFunction].
12401struct FunctionToWrap {
12402 location: SrcSpan,
12403 arguments: Vec<Arc<Type>>,
12404 variables_names: VariablesNames,
12405}
12406
12407impl<'a> WrapInAnonymousFunction<'a> {
12408 pub fn new(
12409 module: &'a Module,
12410 line_numbers: &'a LineNumbers,
12411 params: &'a CodeActionParams,
12412 ) -> Self {
12413 Self {
12414 module,
12415 params,
12416 edits: TextEdits::new(line_numbers),
12417 functions: vec![],
12418 }
12419 }
12420
12421 pub fn code_actions(mut self) -> Vec<CodeAction> {
12422 self.visit_typed_module(&self.module.ast);
12423
12424 let mut actions = Vec::with_capacity(self.functions.len());
12425 for target in self.functions {
12426 let mut name_generator = NameGenerator::new();
12427 name_generator.reserve_variable_names(target.variables_names);
12428 let arguments = target
12429 .arguments
12430 .iter()
12431 .map(|type_| name_generator.generate_name_from_type(type_))
12432 .join(", ");
12433
12434 self.edits
12435 .insert(target.location.start, format!("fn({arguments}) {{ "));
12436 self.edits
12437 .insert(target.location.end, format!("({arguments}) }}"));
12438
12439 CodeActionBuilder::new("Wrap in anonymous function")
12440 .kind(CodeActionKind::RefactorRewrite)
12441 .changes(
12442 self.params.text_document.uri.clone(),
12443 self.edits.edits.drain(..).collect(),
12444 )
12445 .push_to(&mut actions);
12446 }
12447 actions
12448 }
12449}
12450
12451impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> {
12452 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
12453 let expression_range = self.edits.src_span_to_lsp_range(expression.location());
12454 if !within(self.params.range, expression_range) {
12455 return;
12456 }
12457
12458 // Exclude if the expression is already a function literal
12459 let is_excluded = matches!(expression, TypedExpr::Fn { .. });
12460
12461 if let Type::Fn { arguments, .. } = &*expression.type_()
12462 && !is_excluded
12463 {
12464 self.functions.push(FunctionToWrap {
12465 location: expression.location(),
12466 arguments: arguments.clone(),
12467 variables_names: VariablesNames::from_expression(expression),
12468 });
12469 }
12470
12471 ast::visit::visit_typed_expr(self, expression);
12472 }
12473
12474 /// We don't want to apply to functions that are being explicitly called
12475 /// already, so we need to intercept visits to function calls and bounce
12476 /// them out again so they don't end up in our impl for visit_typed_expr.
12477 /// Otherwise this is the same as [].
12478 fn visit_typed_expr_call(
12479 &mut self,
12480 _location: &'ast SrcSpan,
12481 _type: &'ast Arc<Type>,
12482 fun: &'ast TypedExpr,
12483 arguments: &'ast [TypedCallArg],
12484 _argument_parentheses: &'ast Option<u32>,
12485 ) {
12486 // We only need to do this interception for explicit calls, so if any
12487 // of our arguments are explicit we re-enter the visitor as usual.
12488 if arguments.iter().any(|argument| argument.is_implicit()) {
12489 self.visit_typed_expr(fun);
12490 } else {
12491 // We still want to visit other nodes nested in the function being
12492 // called so we bounce the call back out.
12493 ast::visit::visit_typed_expr(self, fun);
12494 }
12495
12496 for argument in arguments {
12497 self.visit_typed_call_arg(argument);
12498 }
12499 }
12500
12501 /// We don't want to apply this to record constructors used in a record
12502 /// update.
12503 fn visit_typed_expr_record_update(
12504 &mut self,
12505 location: &'ast SrcSpan,
12506 spread_start: &'ast u32,
12507 type_: &'ast Arc<Type>,
12508 updated_record: &'ast TypedExpr,
12509 updated_record_assigned_name: &'ast Option<EcoString>,
12510 constructor: &'ast TypedExpr,
12511 arguments: &'ast [TypedCallArg],
12512 ) {
12513 // If we're hovering the record constructor of an update and that is a
12514 // simple record constructor, we don't want to offer the wrap in
12515 // anonymous function code action:
12516 //
12517 // ```gleam
12518 // Wibble(..wibble, a: 1)
12519 // // ^^^ Wrap in anonymous function makes no sense on this!
12520 // ```
12521 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location());
12522 if within(self.params.range, constructor_range)
12523 && constructor.is_record_constructor_function()
12524 {
12525 return;
12526 }
12527
12528 ast::visit::visit_typed_expr_record_update(
12529 self,
12530 location,
12531 spread_start,
12532 type_,
12533 updated_record,
12534 updated_record_assigned_name,
12535 constructor,
12536 arguments,
12537 );
12538 }
12539}
12540
12541/// Code action to unwrap trivial one-statement anonymous functions into just a
12542/// reference to the function called
12543///
12544/// For example, if the code action was used on the anonymous function here:
12545///
12546/// ```gleam
12547/// list.map([1, 2, 3], fn(int) {
12548/// op(int)
12549/// })
12550/// ```
12551///
12552/// it would become:
12553///
12554/// ```gleam
12555/// list.map([1, 2, 3], op)
12556/// ```
12557pub struct UnwrapAnonymousFunction<'a> {
12558 module: &'a Module,
12559 line_numbers: &'a LineNumbers,
12560 params: &'a CodeActionParams,
12561 functions: Vec<FunctionToUnwrap>,
12562}
12563
12564/// Helper struct, a target for [UnwrapAnonymousFunction]
12565struct FunctionToUnwrap {
12566 /// Location of the anonymous function to apply the action to.
12567 outer_function: SrcSpan,
12568 /// Location of the opening brace of the anonymous function.
12569 outer_function_body_start: u32,
12570 /// Location of the function being called inside the anonymous function.
12571 /// This will be all that's left after the action, plus any comments.
12572 inner_function: SrcSpan,
12573 // Location of the opening parenthesis of the inner function's argument list.
12574 inner_function_arguments_start: u32,
12575}
12576
12577impl<'a> UnwrapAnonymousFunction<'a> {
12578 pub fn new(
12579 module: &'a Module,
12580 line_numbers: &'a LineNumbers,
12581 params: &'a CodeActionParams,
12582 ) -> Self {
12583 Self {
12584 module,
12585 line_numbers,
12586 params,
12587 functions: vec![],
12588 }
12589 }
12590
12591 pub fn code_actions(mut self) -> Vec<CodeAction> {
12592 self.visit_typed_module(&self.module.ast);
12593
12594 let mut actions = Vec::with_capacity(self.functions.len());
12595 for function in &self.functions {
12596 let mut edits = TextEdits::new(self.line_numbers);
12597
12598 // We need to delete the anonymous function's head and the opening
12599 // brace but preserve comments between it and the inner function call.
12600 // We set our endpoint at the start of the function body, and move
12601 // it on through any whitespace.
12602 let head_deletion_end =
12603 next_nonwhitespace(&self.module.code, function.outer_function_body_start + 1);
12604 edits.delete(SrcSpan {
12605 start: function.outer_function.start,
12606 end: head_deletion_end,
12607 });
12608
12609 // Delete the inner function call's arguments.
12610 edits.delete(SrcSpan {
12611 start: function.inner_function_arguments_start,
12612 end: function.inner_function.end,
12613 });
12614
12615 // To delete the tail we remove the function end (the '}') and any
12616 // whitespace before it.
12617 let tail_deletion_start =
12618 previous_nonwhitespace(&self.module.code, function.outer_function.end - 1);
12619 edits.delete(SrcSpan {
12620 start: tail_deletion_start,
12621 end: function.outer_function.end,
12622 });
12623
12624 CodeActionBuilder::new("Remove anonymous function wrapper")
12625 .kind(CodeActionKind::RefactorRewrite)
12626 .changes(self.params.text_document.uri.clone(), edits.edits)
12627 .push_to(&mut actions);
12628 }
12629 actions
12630 }
12631
12632 /// If an anonymous function can be unwrapped, save it to our list
12633 ///
12634 /// We need to ensure our subjects:
12635 /// - are anonymous function literals (not captures)
12636 /// - only contain a single statement
12637 /// - that statement is a function call
12638 /// - that call's arguments exactly match the arguments of the enclosing
12639 /// function
12640 fn register_function(
12641 &mut self,
12642 location: &'a SrcSpan,
12643 kind: &'a FunctionLiteralKind,
12644 arguments: &'a [TypedArg],
12645 body: &'a Vec1<TypedStatement>,
12646 ) {
12647 let function_range = src_span_to_lsp_range(*location, self.line_numbers);
12648 if !within(self.params.range, function_range) {
12649 return;
12650 }
12651
12652 let outer_body = match kind {
12653 FunctionLiteralKind::Anonymous { head, .. } => SrcSpan::new(
12654 next_nonwhitespace(&self.module.code, head.end),
12655 location.end,
12656 ),
12657 _ => return,
12658 };
12659
12660 // We can only apply to anonymous functions containing a single function call
12661 let [
12662 TypedStatement::Expression(TypedExpr::Call {
12663 location: call_location,
12664 arguments: call_arguments,
12665 open_parenthesis: Some(arguments_start),
12666 ..
12667 }),
12668 ] = body.as_slice()
12669 else {
12670 return;
12671 };
12672
12673 // We need the existing argument list for the fn to be a 1:1 match for
12674 // the args we pass to the called function, so we need to collect the
12675 // names used in both lists and check they're equal.
12676
12677 let outer_argument_names = arguments.iter().map(|a| match &a.names {
12678 ArgNames::Named { name, .. } => Some(name),
12679 // We can bail out early if any arguments are discarded, since
12680 // they couldn't match those actually used.
12681 ArgNames::Discard { .. } => None,
12682 // Anonymous functions can't have labelled arguments.
12683 ArgNames::NamedLabelled { .. } => unreachable!(),
12684 ArgNames::LabelledDiscard { .. } => unreachable!(),
12685 });
12686
12687 let inner_argument_names = call_arguments.iter().map(|a| match &a.value {
12688 TypedExpr::Var { name, .. } => Some(name),
12689 // We can bail out early if any of these aren't variables, since
12690 // they couldn't match the inputs.
12691 _ => None,
12692 });
12693
12694 if !inner_argument_names.eq(outer_argument_names) {
12695 return;
12696 }
12697
12698 self.functions.push(FunctionToUnwrap {
12699 outer_function: *location,
12700 outer_function_body_start: outer_body.start,
12701 inner_function: *call_location,
12702 inner_function_arguments_start: *arguments_start,
12703 });
12704 }
12705}
12706
12707impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
12708 fn visit_typed_expr_fn(
12709 &mut self,
12710 location: &'ast SrcSpan,
12711 type_: &'ast Arc<Type>,
12712 kind: &'ast FunctionLiteralKind,
12713 arguments: &'ast [TypedArg],
12714 body: &'ast Vec1<TypedStatement>,
12715 return_annotation: &'ast Option<ast::TypeAst>,
12716 ) {
12717 let function_range = src_span_to_lsp_range(*location, self.line_numbers);
12718 if !within(self.params.range, function_range) {
12719 return;
12720 }
12721
12722 self.register_function(location, kind, arguments, body);
12723
12724 ast::visit::visit_typed_expr_fn(
12725 self,
12726 location,
12727 type_,
12728 kind,
12729 arguments,
12730 body,
12731 return_annotation,
12732 );
12733 }
12734}
12735
12736/// Code action to create unknown modules when an import is added for a
12737/// module that doesn't exist.
12738///
12739/// For example, if `import wobble/woo` is added to `src/wiggle.gleam`,
12740/// then a code action to create `src/wobble/woo.gleam` will be presented
12741/// when triggered over `import wobble/woo`.
12742pub struct CreateUnknownModule<'a, IO> {
12743 module: &'a Module,
12744 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
12745 lines: &'a LineNumbers,
12746 params: &'a CodeActionParams,
12747 paths: &'a ProjectPaths,
12748 error: &'a Option<Error>,
12749}
12750
12751impl<'a, IO> CreateUnknownModule<'a, IO> {
12752 pub fn new(
12753 module: &'a Module,
12754 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
12755 lines: &'a LineNumbers,
12756 params: &'a CodeActionParams,
12757 paths: &'a ProjectPaths,
12758 error: &'a Option<Error>,
12759 ) -> Self {
12760 Self {
12761 module,
12762 compiler,
12763 lines,
12764 params,
12765 paths,
12766 error,
12767 }
12768 }
12769
12770 pub fn code_actions(self) -> Vec<CodeAction> {
12771 // This code action can be derived from `UnknownModule` type errors.
12772 // If those errors don't exist for the current module, then we will not
12773 // suggest this action.
12774 let Some(Error::Type { failed_modules, .. }) = self.error else {
12775 return vec![];
12776 };
12777 let Some(failed_module) = failed_modules.get(&self.module.name) else {
12778 return vec![];
12779 };
12780
12781 let mut unknown_module_name = None;
12782 // We then need to find the `UnknownModule` error that is under the
12783 // cursor (if any, otherwise there's no action to suggest)!
12784 for error in &failed_module.errors {
12785 let TypeError::UnknownModule { location, name, .. } = error else {
12786 continue;
12787 };
12788 let error_range = src_span_to_lsp_range(*location, self.lines);
12789 if !within(self.params.range, error_range) {
12790 continue;
12791 }
12792 // We've found the unknown module error!!
12793 unknown_module_name = Some(name);
12794 }
12795
12796 let Some(unknown_module_name) = unknown_module_name else {
12797 return vec![];
12798 };
12799
12800 // Now we need to check the module actually doesn't exist among the
12801 // importable ones! Imagine we've written `timestamp.to_string` and
12802 // `timestamp` is unknown: if there's any importable module in the form
12803 // `.../timestamp` then the most likely scenario is that the programmer
12804 // wanted to import that and not create a new `src/timestamp.gleam`
12805 // file!
12806 // So we check if any of the importable modules ends with the unknown
12807 // name and if that's the case we don't suggest this action.
12808 let importable_modules = self.compiler.project_compiler.get_importable_modules();
12809 let unknown_module_can_be_imported = importable_modules
12810 .keys()
12811 .find(|module_name| module_name.split('/').next_back() == Some(unknown_module_name))
12812 .is_some();
12813 if unknown_module_can_be_imported {
12814 return vec![];
12815 }
12816
12817 // We've made sure the module is not among the importable ones, so now
12818 // we figure out if the generated module needs to go under `src`,
12819 // `test`, or `dev` and we're good to actually generate it!
12820 let origin_directory = match self.module.origin {
12821 Origin::Src => self.paths.src_directory(),
12822 Origin::Test => self.paths.test_directory(),
12823 Origin::Dev => self.paths.dev_directory(),
12824 };
12825
12826 let uri = url_from_path(&format!("{origin_directory}/{}.gleam", unknown_module_name))
12827 .expect("origin directory is absolute");
12828
12829 let mut actions = vec![];
12830 CodeActionBuilder::new(&format!(
12831 "Create {}/{}.gleam",
12832 self.module.origin.folder_name(),
12833 unknown_module_name
12834 ))
12835 .kind(CodeActionKind::QuickFix)
12836 .document_changes(vec![DocumentChange::CreateFile(CreateFile {
12837 uri,
12838 options: Some(CreateFileOptions {
12839 overwrite: Some(false),
12840 ignore_if_exists: Some(true),
12841 }),
12842 annotation_id: None,
12843 })])
12844 .push_to(&mut actions);
12845
12846 actions
12847 }
12848}
12849
12850/// Code action to discard unused variable.
12851pub struct DiscardUnusedVariable<'a> {
12852 module: &'a Module,
12853 params: &'a CodeActionParams,
12854 edits: TextEdits<'a>,
12855}
12856
12857#[derive(Debug)]
12858struct UnusedVariable<'a> {
12859 location: &'a SrcSpan,
12860 origin: &'a VariableOrigin,
12861}
12862
12863impl<'a> DiscardUnusedVariable<'a> {
12864 pub fn new(
12865 module: &'a Module,
12866 line_numbers: &'a LineNumbers,
12867 params: &'a CodeActionParams,
12868 ) -> Self {
12869 Self {
12870 module,
12871 params,
12872 edits: TextEdits::new(line_numbers),
12873 }
12874 }
12875
12876 pub fn code_actions(mut self) -> Vec<CodeAction> {
12877 let unused_variable = self
12878 .module
12879 .ast
12880 .type_info
12881 .warnings
12882 .iter()
12883 .find_map(|warning| {
12884 if let type_::Warning::UnusedVariable { location, origin } = warning
12885 && within(
12886 self.params.range,
12887 self.edits.src_span_to_lsp_range(*location),
12888 )
12889 {
12890 Some(UnusedVariable { location, origin })
12891 } else {
12892 None
12893 }
12894 });
12895
12896 let Some(unused_variable) = unused_variable else {
12897 return vec![];
12898 };
12899
12900 match unused_variable.origin.syntax {
12901 type_::error::VariableSyntax::Variable(_) => {
12902 self.edits
12903 .insert(unused_variable.location.start, ("_").to_string());
12904 }
12905 type_::error::VariableSyntax::LabelShorthand(_) => {
12906 self.edits
12907 .insert(unused_variable.location.end, (" _").to_string());
12908 }
12909 type_::error::VariableSyntax::AssignmentPattern(location) => {
12910 self.edits.delete(SrcSpan {
12911 start: location.start,
12912 end: location.end,
12913 });
12914 }
12915 type_::error::VariableSyntax::Generated => (),
12916 }
12917
12918 let mut action = Vec::with_capacity(1);
12919 CodeActionBuilder::new("Discard unused variable")
12920 .kind(CodeActionKind::QuickFix)
12921 .changes(self.params.text_document.uri.clone(), self.edits.edits)
12922 .preferred(true)
12923 .push_to(&mut action);
12924 action
12925 }
12926}
12927
12928pub fn code_action_fix_deprecated_pipe(
12929 module: &Module,
12930 line_numbers: &LineNumbers,
12931 params: &CodeActionParams,
12932 actions: &mut Vec<CodeAction>,
12933) {
12934 let uri = ¶ms.text_document.uri;
12935 let mut deprecated_pipes: Vec<&SrcSpan> = module
12936 .ast
12937 .type_info
12938 .warnings
12939 .iter()
12940 .filter_map(|warning| {
12941 if let type_::Warning::PipeIntoCallWhichReturnsFunction { location } = warning {
12942 Some(location)
12943 } else {
12944 None
12945 }
12946 })
12947 .collect();
12948
12949 if deprecated_pipes.is_empty() {
12950 return;
12951 }
12952
12953 // Sort spans by start position, with longer spans coming first
12954 deprecated_pipes.sort_by_key(|span| (span.start, -(span.len() as i64)));
12955
12956 for location in deprecated_pipes {
12957 let range = src_span_to_lsp_range(*location, line_numbers);
12958
12959 // Check if the cursor is within this span
12960 if !within(params.range, range) {
12961 continue;
12962 }
12963
12964 let edit = TextEdit {
12965 range: Range::new(range.end, range.end),
12966 new_text: "()".into(),
12967 };
12968
12969 CodeActionBuilder::new("Add extra parentheses")
12970 .kind(CodeActionKind::QuickFix)
12971 .changes(uri.clone(), vec![edit])
12972 .preferred(true)
12973 .push_to(actions);
12974 }
12975}
12976
12977/// Code action to switch between doc and regular comments.
12978pub struct ConvertBetweenDocAndRegularComment<'a> {
12979 module: &'a Module,
12980 lines: &'a LineNumbers,
12981 params: &'a CodeActionParams,
12982}
12983
12984impl<'a> ConvertBetweenDocAndRegularComment<'a> {
12985 pub fn new(module: &'a Module, lines: &'a LineNumbers, params: &'a CodeActionParams) -> Self {
12986 Self {
12987 module,
12988 lines,
12989 params,
12990 }
12991 }
12992
12993 pub fn code_actions(self) -> Vec<CodeAction> {
12994 let line = self.params.range.start.line;
12995 let Some(num_slashes) = self.count_leading_slashes(line) else {
12996 return vec![];
12997 };
12998
12999 if !(2..=4).contains(&num_slashes) {
13000 return vec![];
13001 }
13002
13003 let start_line = self
13004 .find_comment_edge((0..line).rev(), num_slashes)
13005 .unwrap_or(line);
13006 let end_line = self
13007 .find_comment_edge(line + 1.., num_slashes)
13008 .unwrap_or(line);
13009
13010 if self.params.range.end.line > end_line {
13011 return vec![];
13012 }
13013
13014 let comment = match num_slashes {
13015 2 if self.can_be_module_comment(start_line, end_line) => "////",
13016 2 if self.can_have_doc_comment(end_line) => "///",
13017 3 | 4 => "//",
13018 _ => return vec![],
13019 };
13020
13021 let mut edits = TextEdits::new(self.lines);
13022 for line in start_line..=end_line {
13023 let Some(line_start) = self.line_start(line) else {
13024 return vec![];
13025 };
13026 let start = next_nonwhitespace(&self.module.code, line_start);
13027 edits.replace(SrcSpan::new(start, start + num_slashes), comment.to_owned());
13028 }
13029
13030 let action_name = if comment == "//" {
13031 "Convert to regular comment"
13032 } else {
13033 "Convert to documentation comment"
13034 };
13035
13036 let mut action = Vec::with_capacity(1);
13037 CodeActionBuilder::new(action_name)
13038 .kind(CodeActionKind::RefactorRewrite)
13039 .changes(self.params.text_document.uri.clone(), edits.edits)
13040 .push_to(&mut action);
13041 action
13042 }
13043
13044 fn count_leading_slashes(&self, line: u32) -> Option<u32> {
13045 let mut count = 0;
13046 for c in self.module.code[self.line_start(line)? as usize..].chars() {
13047 if c == '/' {
13048 count += 1;
13049 } else if !c.is_whitespace() || c == '\n' || count > 0 {
13050 break;
13051 }
13052 }
13053 Some(count)
13054 }
13055
13056 fn line_start(&self, line: u32) -> Option<u32> {
13057 self.lines.line_starts.get(line as usize).copied()
13058 }
13059
13060 /// Find the last line in the range that is part of the same comment.
13061 fn find_comment_edge(&self, lines: impl Iterator<Item = u32>, num_slashes: u32) -> Option<u32> {
13062 lines
13063 .take_while(|&line| {
13064 self.count_leading_slashes(line)
13065 .is_some_and(|n| n == num_slashes)
13066 })
13067 .last()
13068 }
13069
13070 fn can_be_module_comment(&self, start_line: u32, end_line: u32) -> bool {
13071 let Some(line_start) = self.line_start(start_line) else {
13072 return false;
13073 };
13074 previous_nonwhitespace(&self.module.code, line_start) == 0
13075 && self
13076 .line_start(end_line + 1)
13077 .is_none_or(|position| self.module.extra.empty_lines.contains(&position))
13078 }
13079
13080 /// Check if the comment is before a node that can have a doc comment, e.g. a function.
13081 fn can_have_doc_comment(&self, end_line: u32) -> bool {
13082 let Some(position) = self.line_start(end_line + 1) else {
13083 return false;
13084 };
13085
13086 let next_node = next_nonwhitespace(&self.module.code, position);
13087 let definitions = &self.module.ast.definitions;
13088 definitions
13089 .functions
13090 .iter()
13091 .any(|function| function.location.contains(next_node))
13092 || definitions
13093 .constants
13094 .iter()
13095 .any(|constant| constant.location.contains(next_node))
13096 || definitions
13097 .type_aliases
13098 .iter()
13099 .any(|type_alias| type_alias.location.contains(next_node))
13100 || definitions.custom_types.iter().any(|custom_type| {
13101 custom_type.location.contains(next_node)
13102 || custom_type.constructors.iter().any(|constructor| {
13103 constructor.location.contains(next_node)
13104 || constructor.arguments.iter().any(|argument| {
13105 argument
13106 .label
13107 .as_ref()
13108 .is_some_and(|label| label.0.contains(next_node))
13109 })
13110 })
13111 })
13112 }
13113}
13114
13115/// Code action to convert integers to a different base from the current one.
13116///
13117pub struct ConvertIntToDifferentBase<'a> {
13118 module: &'a Module,
13119 params: &'a CodeActionParams,
13120 edits: TextEdits<'a>,
13121 /// This is gonna hold the base the hovered integer is written in, and its
13122 /// decimal value. And its position in the source code.
13123 int: Option<(SrcSpan, Base, BigInt)>,
13124}
13125
13126enum Base {
13127 Binary,
13128 Octal,
13129 Decimal,
13130 Hexadecimal,
13131}
13132
13133impl<'a> ConvertIntToDifferentBase<'a> {
13134 pub fn new(
13135 module: &'a Module,
13136 line_numbers: &'a LineNumbers,
13137 params: &'a CodeActionParams,
13138 ) -> Self {
13139 Self {
13140 module,
13141 params,
13142 edits: TextEdits::new(line_numbers),
13143 int: None,
13144 }
13145 }
13146
13147 pub fn code_actions(mut self) -> Vec<CodeAction> {
13148 self.visit_typed_module(&self.module.ast);
13149
13150 let Some((location, base, int)) = self.int else {
13151 return vec![];
13152 };
13153
13154 let missing_bases = match base {
13155 Base::Binary => [Base::Decimal, Base::Octal, Base::Hexadecimal],
13156 Base::Octal => [Base::Decimal, Base::Binary, Base::Hexadecimal],
13157 Base::Decimal => [Base::Binary, Base::Octal, Base::Hexadecimal],
13158 Base::Hexadecimal => [Base::Decimal, Base::Binary, Base::Octal],
13159 };
13160
13161 let is_negative = int < BigInt::ZERO;
13162 let int = if is_negative { int.neg() } else { int };
13163
13164 let mut action = Vec::with_capacity(3);
13165 for base in missing_bases {
13166 let minus = if is_negative { "-" } else { "" };
13167 let converted_number = match base {
13168 Base::Binary => format!("{minus}0b{:b}", int),
13169 Base::Octal => format!("{minus}0o{:o}", int),
13170 Base::Hexadecimal => format!("{minus}0x{:x}", int),
13171 Base::Decimal => {
13172 format!("{minus}{}", format_int_with_thousands_separator(&int))
13173 }
13174 };
13175 let title = format!("Convert to `{converted_number}`");
13176 self.edits.replace(location, converted_number);
13177
13178 CodeActionBuilder::new(&title)
13179 .kind(CodeActionKind::RefactorRewrite)
13180 .changes(
13181 self.params.text_document.uri.clone(),
13182 self.edits.edits.drain(..).collect(),
13183 )
13184 .preferred(false)
13185 .push_to(&mut action);
13186 }
13187 action
13188 }
13189}
13190
13191fn format_int_with_thousands_separator(int: &BigInt) -> String {
13192 if int <= &BigInt::from(9999) {
13193 return int.to_string();
13194 }
13195
13196 // We get chunks of three digits. If we start from 1234567
13197 // we will have `765`, `432`, `1`
13198 (int.to_string().chars().rev().chunks(3).into_iter())
13199 // Each chunk is turned into a string and those are joined with a
13200 // separator.
13201 .map(|chunk| chunk.collect::<EcoString>())
13202 .join("_")
13203 // And finally reverse everything to bring it back to normal, the number
13204 // is spelled in reverse right now!
13205 .chars()
13206 .rev()
13207 .collect()
13208}
13209
13210impl<'ast> ast::visit::Visit<'ast> for ConvertIntToDifferentBase<'ast> {
13211 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
13212 // We skip all the functions the cursor is not inside of.
13213 // This is gonna make it faster to find the int we're hovering, if any.
13214 let fun_range = self.edits.src_span_to_lsp_range(fun.full_location());
13215 if within(self.params.range, fun_range) {
13216 ast::visit::visit_typed_function(self, fun);
13217 }
13218 }
13219
13220 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
13221 // We skip all the expression's the cursor is not inside of.
13222 // This is gonna make it faster to find the int we're hovering, if any.
13223 let expression_range = self.edits.src_span_to_lsp_range(expr.location());
13224 if within(self.params.range, expression_range) {
13225 ast::visit::visit_typed_expr(self, expr);
13226 }
13227 }
13228
13229 fn visit_typed_expr_int(
13230 &mut self,
13231 location: &'ast SrcSpan,
13232 _type_: &'ast Arc<Type>,
13233 string_value: &'ast EcoString,
13234 int_value: &'ast BigInt,
13235 ) {
13236 let int_range = self.edits.src_span_to_lsp_range(*location);
13237 if !within(self.params.range, int_range) {
13238 return;
13239 }
13240
13241 let string_value = string_value.trim_start_matches('-');
13242 let base = if string_value.starts_with("0b") {
13243 Base::Binary
13244 } else if string_value.starts_with("0o") {
13245 Base::Octal
13246 } else if string_value.starts_with("0x") {
13247 Base::Hexadecimal
13248 } else {
13249 Base::Decimal
13250 };
13251
13252 self.int = Some((*location, base, int_value.clone()))
13253 }
13254}