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