this repo has no description
1use std::{collections::HashSet, iter, sync::Arc};
2
3use crate::{
4 Error, STDLIB_PACKAGE_NAME, analyse,
5 ast::{
6 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable,
7 CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import, InvalidExpression,
8 PIPE_PRECEDENCE, Pattern, PatternUnusedArguments, PipelineAssignmentKind, Publicity,
9 RecordConstructor, SrcSpan, TodoKind, TypedArg, TypedAssignment, TypedClauseGuard,
10 TypedExpr, TypedModuleConstant, TypedPattern, TypedPipelineAssignment,
11 TypedRecordConstructor, TypedStatement, TypedUse, visit::Visit as _,
12 },
13 build::{Located, Module},
14 config::PackageConfig,
15 exhaustiveness::CompiledCase,
16 language_server::{edits, reference::FindVariableReferences},
17 line_numbers::LineNumbers,
18 parse::{extra::ModuleExtra, lexer::str_to_keyword},
19 strings::to_snake_case,
20 type_::{
21 self, FieldMap, ModuleValueConstructor, Type, TypeVar, TypedCallArg, ValueConstructor,
22 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin},
23 printer::Printer,
24 },
25};
26use ecow::{EcoString, eco_format};
27use im::HashMap;
28use itertools::Itertools;
29use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
30use vec1::{Vec1, vec1};
31
32use super::{
33 TextEdits,
34 compiler::LspProjectCompiler,
35 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
36 engine::{overlaps, within},
37 files::FileSystemProxy,
38 reference::VariableReferenceKind,
39 src_span_to_lsp_range, url_from_path,
40};
41
42#[derive(Debug)]
43pub struct CodeActionBuilder {
44 action: CodeAction,
45}
46
47impl CodeActionBuilder {
48 pub fn new(title: &str) -> Self {
49 Self {
50 action: CodeAction {
51 title: title.to_string(),
52 kind: None,
53 diagnostics: None,
54 edit: None,
55 command: None,
56 is_preferred: None,
57 disabled: None,
58 data: None,
59 },
60 }
61 }
62
63 pub fn kind(mut self, kind: CodeActionKind) -> Self {
64 self.action.kind = Some(kind);
65 self
66 }
67
68 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
69 let mut edit = self.action.edit.take().unwrap_or_default();
70 let mut changes = edit.changes.take().unwrap_or_default();
71 _ = changes.insert(uri, edits);
72
73 edit.changes = Some(changes);
74 self.action.edit = Some(edit);
75 self
76 }
77
78 pub fn preferred(mut self, is_preferred: bool) -> Self {
79 self.action.is_preferred = Some(is_preferred);
80 self
81 }
82
83 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
84 actions.push(self.action);
85 }
86}
87
88/// A small helper function to get the indentation at a given position.
89fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
90 let mut indent_size = 0;
91 let line_start = *line_numbers
92 .line_starts
93 .get(line as usize)
94 .expect("Line number should be valid");
95
96 let mut chars = code[line_start as usize..].chars();
97 while chars.next() == Some(' ') {
98 indent_size += 1;
99 }
100
101 indent_size
102}
103
104/// Code action to remove literal tuples in case subjects, essentially making
105/// the elements of the tuples into the case's subjects.
106///
107/// The code action is only available for the i'th subject if:
108/// - it is a non-empty tuple, and
109/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
110///
111/// # Basic example:
112///
113/// The following case expression:
114///
115/// ```gleam
116/// case #(1, 2) {
117/// #(a, b) -> 0
118/// }
119/// ```
120///
121/// Becomes:
122///
123/// ```gleam
124/// case 1, 2 {
125/// a, b -> 0
126/// }
127/// ```
128///
129/// # Another example:
130///
131/// The following case expression does not produce any code action
132///
133/// ```gleam
134/// case #(1, 2) {
135/// a -> 0 // <- the pattern is not a tuple
136/// }
137/// ```
138pub struct RedundantTupleInCaseSubject<'a> {
139 edits: TextEdits<'a>,
140 code: &'a EcoString,
141 extra: &'a ModuleExtra,
142 params: &'a CodeActionParams,
143 module: &'a ast::TypedModule,
144 hovered: bool,
145}
146
147impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
148 fn visit_typed_expr_case(
149 &mut self,
150 location: &'ast SrcSpan,
151 type_: &'ast Arc<Type>,
152 subjects: &'ast [TypedExpr],
153 clauses: &'ast [ast::TypedClause],
154 compiled_case: &'ast CompiledCase,
155 ) {
156 for (subject_idx, subject) in subjects.iter().enumerate() {
157 let TypedExpr::Tuple {
158 location, elements, ..
159 } = subject
160 else {
161 continue;
162 };
163
164 // Ignore empty tuple
165 if elements.is_empty() {
166 continue;
167 }
168
169 // We cannot rewrite clauses whose i-th pattern is not a discard or
170 // tuples.
171 let all_ith_patterns_are_tuples_or_discards = clauses
172 .iter()
173 .map(|clause| clause.pattern.get(subject_idx))
174 .all(|pattern| {
175 matches!(
176 pattern,
177 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
178 )
179 });
180
181 if !all_ith_patterns_are_tuples_or_discards {
182 continue;
183 }
184
185 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
186
187 for clause in clauses {
188 match clause.pattern.get(subject_idx) {
189 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
190 *location,
191 elements.last().map(|element| element.location()),
192 ),
193 Some(Pattern::Discard { location, .. }) => {
194 self.discard_tuple_items(*location, elements.len())
195 }
196 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
197 }
198 }
199 let range = self.edits.src_span_to_lsp_range(*location);
200 self.hovered = self.hovered || overlaps(self.params.range, range);
201 }
202
203 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case)
204 }
205}
206
207impl<'a> RedundantTupleInCaseSubject<'a> {
208 pub fn new(
209 module: &'a Module,
210 line_numbers: &'a LineNumbers,
211 params: &'a CodeActionParams,
212 ) -> Self {
213 Self {
214 edits: TextEdits::new(line_numbers),
215 code: &module.code,
216 extra: &module.extra,
217 params,
218 module: &module.ast,
219 hovered: false,
220 }
221 }
222
223 pub fn code_actions(mut self) -> Vec<CodeAction> {
224 self.visit_typed_module(self.module);
225 if !self.hovered {
226 return vec![];
227 }
228
229 self.edits.edits.sort_by_key(|edit| edit.range.start);
230
231 let mut actions = vec![];
232 CodeActionBuilder::new("Remove redundant tuples")
233 .kind(CodeActionKind::REFACTOR_REWRITE)
234 .changes(self.params.text_document.uri.clone(), self.edits.edits)
235 .preferred(true)
236 .push_to(&mut actions);
237
238 actions
239 }
240
241 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
242 let tuple_code = self
243 .code
244 .get(location.start as usize..location.end as usize)
245 .expect("valid span");
246
247 // Delete `#`
248 self.edits
249 .delete(SrcSpan::new(location.start, location.start + 1));
250
251 // Delete `(`
252 let (lparen_offset, _) = tuple_code
253 .match_indices('(')
254 // Ignore in comments
255 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
256 .expect("`(` not found in tuple");
257
258 self.edits.delete(SrcSpan::new(
259 location.start + lparen_offset as u32,
260 location.start + lparen_offset as u32 + 1,
261 ));
262
263 // Delete trailing `,` (if applicable)
264 if let Some(last_elem_location) = last_elem_location {
265 // Get the code after the last element until the tuple's `)`
266 let code_after_last_elem = self
267 .code
268 .get(last_elem_location.end as usize..location.end as usize)
269 .expect("valid span");
270
271 if let Some((trailing_comma_offset, _)) = code_after_last_elem
272 .rmatch_indices(',')
273 // Ignore in comments
274 .find(|(i, _)| {
275 !self
276 .extra
277 .is_within_comment(last_elem_location.end + *i as u32)
278 })
279 {
280 self.edits.delete(SrcSpan::new(
281 last_elem_location.end + trailing_comma_offset as u32,
282 last_elem_location.end + trailing_comma_offset as u32 + 1,
283 ));
284 }
285 }
286
287 // Delete )
288 self.edits
289 .delete(SrcSpan::new(location.end - 1, location.end));
290 }
291
292 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
293 // Replace the old discard with multiple discard, one for each of the
294 // tuple items.
295 self.edits.replace(
296 discard_location,
297 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
298 )
299 }
300}
301
302/// Builder for code action to convert `let assert` into a case expression.
303///
304pub struct LetAssertToCase<'a> {
305 module: &'a Module,
306 params: &'a CodeActionParams,
307 actions: Vec<CodeAction>,
308 edits: TextEdits<'a>,
309}
310
311impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
312 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
313 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location);
314 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan {
315 start: assignment.location.start,
316 end: assignment.value.location().start,
317 });
318 self.visit_typed_expr(&assignment.value);
319
320 // Only offer the code action if the cursor is over the statement and
321 // to prevent weird behaviour when `let assert` statements are nested,
322 // we only check for the code action between the `let` and `=`.
323 if !(within(self.params.range, assignment_range)
324 && overlaps(self.params.range, assignment_start_range))
325 {
326 return;
327 }
328
329 // This pattern only applies to `let assert`
330 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
331 return;
332 };
333
334 // Get the source code for the tested expression
335 let location = assignment.value.location();
336 let expr = self
337 .module
338 .code
339 .get(location.start as usize..location.end as usize)
340 .expect("Location must be valid");
341
342 // Get the source code for the pattern
343 let pattern_location = assignment.pattern.location();
344 let pattern = self
345 .module
346 .code
347 .get(pattern_location.start as usize..pattern_location.end as usize)
348 .expect("Location must be valid");
349
350 let message = message.as_ref().map(|message| {
351 let location = message.location();
352 self.module
353 .code
354 .get(location.start as usize..location.end as usize)
355 .expect("Location must be valid")
356 });
357
358 let range = self.edits.src_span_to_lsp_range(assignment.location);
359
360 // Figure out which variables are assigned in the pattern
361 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
362
363 let assigned = match variables.len() {
364 0 => "_",
365 1 => variables.first().expect("Variables is length one"),
366 _ => &format!("#({})", variables.join(", ")),
367 };
368
369 let mut new_text = format!("let {assigned} = ");
370 let panic_message = if let Some(message) = message {
371 &format!("panic as {message}")
372 } else {
373 "panic"
374 };
375 let clauses = vec![
376 // The existing pattern
377 CaseClause {
378 pattern,
379 // `_` is not a valid expression, so if we are not
380 // binding any variables in the pattern, we simply return Nil.
381 expression: if assigned == "_" { "Nil" } else { assigned },
382 },
383 CaseClause {
384 pattern: "_",
385 expression: panic_message,
386 },
387 ];
388 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
389
390 let uri = &self.params.text_document.uri;
391
392 CodeActionBuilder::new("Convert to case")
393 .kind(CodeActionKind::REFACTOR_REWRITE)
394 .changes(uri.clone(), vec![TextEdit { range, new_text }])
395 .preferred(false)
396 .push_to(&mut self.actions);
397 }
398}
399
400impl<'a> LetAssertToCase<'a> {
401 pub fn new(
402 module: &'a Module,
403 line_numbers: &'a LineNumbers,
404 params: &'a CodeActionParams,
405 ) -> Self {
406 Self {
407 module,
408 params,
409 actions: Vec::new(),
410 edits: TextEdits::new(line_numbers),
411 }
412 }
413
414 pub fn code_actions(mut self) -> Vec<CodeAction> {
415 self.visit_typed_module(&self.module.ast);
416 self.actions
417 }
418}
419
420struct PatternVariableFinder {
421 pattern_variables: Vec<EcoString>,
422}
423
424impl PatternVariableFinder {
425 fn new() -> Self {
426 Self {
427 pattern_variables: Vec::new(),
428 }
429 }
430
431 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
432 let mut finder = Self::new();
433 finder.visit_typed_pattern(pattern);
434 finder.pattern_variables
435 }
436}
437
438impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
439 fn visit_typed_pattern_variable(
440 &mut self,
441 _location: &'ast SrcSpan,
442 name: &'ast EcoString,
443 _type: &'ast Arc<Type>,
444 _origin: &'ast VariableOrigin,
445 ) {
446 self.pattern_variables.push(name.clone());
447 }
448
449 fn visit_typed_pattern_assign(
450 &mut self,
451 location: &'ast SrcSpan,
452 name: &'ast EcoString,
453 pattern: &'ast TypedPattern,
454 ) {
455 self.pattern_variables.push(name.clone());
456 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
457 }
458
459 fn visit_typed_pattern_string_prefix(
460 &mut self,
461 _location: &'ast SrcSpan,
462 _left_location: &'ast SrcSpan,
463 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
464 _right_location: &'ast SrcSpan,
465 _left_side_string: &'ast EcoString,
466 right_side_assignment: &'ast AssignName,
467 ) {
468 if let Some((name, _)) = left_side_assignment {
469 self.pattern_variables.push(name.clone());
470 }
471 if let AssignName::Variable(name) = right_side_assignment {
472 self.pattern_variables.push(name.clone());
473 }
474 }
475}
476
477pub fn code_action_inexhaustive_let_to_case(
478 module: &Module,
479 line_numbers: &LineNumbers,
480 params: &CodeActionParams,
481 error: &Option<Error>,
482 actions: &mut Vec<CodeAction>,
483) {
484 let Some(Error::Type { errors, .. }) = error else {
485 return;
486 };
487 let inexhaustive_assignments = errors
488 .iter()
489 .filter_map(|error| match error {
490 type_::Error::InexhaustiveLetAssignment { location, missing } => {
491 Some((*location, missing))
492 }
493 _ => None,
494 })
495 .collect_vec();
496
497 if inexhaustive_assignments.is_empty() {
498 return;
499 }
500
501 for (location, missing) in inexhaustive_assignments {
502 let mut text_edits = TextEdits::new(line_numbers);
503
504 let range = text_edits.src_span_to_lsp_range(location);
505 if !overlaps(params.range, range) {
506 return;
507 }
508
509 let Some(Located::Statement(TypedStatement::Assignment(assignment))) =
510 module.find_node(location.start)
511 else {
512 continue;
513 };
514
515 let TypedAssignment {
516 value,
517 pattern,
518 kind: AssignmentKind::Let,
519 location,
520 compiled_case: _,
521 annotation: _,
522 } = assignment.as_ref()
523 else {
524 continue;
525 };
526
527 // Get the source code for the tested expression
528 let value_location = value.location();
529 let expr = module
530 .code
531 .get(value_location.start as usize..value_location.end as usize)
532 .expect("Location must be valid");
533
534 // Get the source code for the pattern
535 let pattern_location = pattern.location();
536 let pattern_code = module
537 .code
538 .get(pattern_location.start as usize..pattern_location.end as usize)
539 .expect("Location must be valid");
540
541 let range = text_edits.src_span_to_lsp_range(*location);
542
543 // Figure out which variables are assigned in the pattern
544 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
545
546 let assigned = match variables.len() {
547 0 => "_",
548 1 => variables.first().expect("Variables is length one"),
549 _ => &format!("#({})", variables.join(", ")),
550 };
551
552 let mut new_text = format!("let {assigned} = ");
553 print_case_expression(
554 range.start.character as usize,
555 expr,
556 iter::once(CaseClause {
557 pattern: pattern_code,
558 expression: if assigned == "_" { "Nil" } else { assigned },
559 })
560 .chain(missing.iter().map(|pattern| CaseClause {
561 pattern,
562 expression: "todo",
563 }))
564 .collect(),
565 &mut new_text,
566 );
567
568 let uri = ¶ms.text_document.uri;
569
570 text_edits.replace(*location, new_text);
571
572 CodeActionBuilder::new("Convert to case")
573 .kind(CodeActionKind::QUICKFIX)
574 .changes(uri.clone(), text_edits.edits)
575 .preferred(true)
576 .push_to(actions);
577 }
578}
579
580struct CaseClause<'a> {
581 pattern: &'a str,
582 expression: &'a str,
583}
584
585fn print_case_expression(
586 indent_size: usize,
587 subject: &str,
588 clauses: Vec<CaseClause<'_>>,
589 buffer: &mut String,
590) {
591 let indent = " ".repeat(indent_size);
592
593 // Print the beginning of the expression
594 buffer.push_str("case ");
595 buffer.push_str(subject);
596 buffer.push_str(" {");
597
598 for clause in clauses.iter() {
599 // Print the newline and indentation for this clause
600 buffer.push('\n');
601 buffer.push_str(&indent);
602 // Indent this clause one level deeper than the case expression
603 buffer.push_str(" ");
604
605 // Print the clause
606 buffer.push_str(clause.pattern);
607 buffer.push_str(" -> ");
608 buffer.push_str(clause.expression);
609 }
610
611 // If there are no clauses to print, the closing brace should be
612 // on the same line as the opening one, with no space between.
613 if !clauses.is_empty() {
614 buffer.push('\n');
615 buffer.push_str(&indent);
616 }
617 buffer.push('}');
618}
619
620/// Builder for code action to apply the label shorthand syntax on arguments
621/// where the label has the same name as the variable.
622///
623pub struct UseLabelShorthandSyntax<'a> {
624 module: &'a Module,
625 params: &'a CodeActionParams,
626 edits: TextEdits<'a>,
627}
628
629impl<'a> UseLabelShorthandSyntax<'a> {
630 pub fn new(
631 module: &'a Module,
632 line_numbers: &'a LineNumbers,
633 params: &'a CodeActionParams,
634 ) -> Self {
635 Self {
636 module,
637 params,
638 edits: TextEdits::new(line_numbers),
639 }
640 }
641
642 pub fn code_actions(mut self) -> Vec<CodeAction> {
643 self.visit_typed_module(&self.module.ast);
644 if self.edits.edits.is_empty() {
645 return vec![];
646 }
647 let mut action = Vec::with_capacity(1);
648 CodeActionBuilder::new("Use label shorthand syntax")
649 .kind(CodeActionKind::REFACTOR)
650 .changes(self.params.text_document.uri.clone(), self.edits.edits)
651 .preferred(false)
652 .push_to(&mut action);
653 action
654 }
655}
656
657impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
658 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
659 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
660 let is_selected = overlaps(arg_range, self.params.range);
661
662 match arg {
663 CallArg {
664 label: Some(label),
665 value: TypedExpr::Var { name, location, .. },
666 ..
667 } if is_selected && !arg.uses_label_shorthand() && label == name => {
668 self.edits.delete(*location)
669 }
670 _ => (),
671 }
672
673 ast::visit::visit_typed_call_arg(self, arg)
674 }
675
676 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
677 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
678 let is_selected = overlaps(arg_range, self.params.range);
679
680 match arg {
681 CallArg {
682 label: Some(label),
683 value: TypedPattern::Variable { name, location, .. },
684 ..
685 } if is_selected && !arg.uses_label_shorthand() && label == name => {
686 self.edits.delete(*location)
687 }
688 _ => (),
689 }
690
691 ast::visit::visit_typed_pattern_call_arg(self, arg)
692 }
693}
694
695/// Builder for code action to apply the fill in the missing labelled arguments
696/// of the selected function call.
697///
698pub struct FillInMissingLabelledArgs<'a> {
699 module: &'a Module,
700 params: &'a CodeActionParams,
701 edits: TextEdits<'a>,
702 use_right_hand_side_location: Option<SrcSpan>,
703 selected_call: Option<SelectedCall<'a>>,
704}
705
706struct SelectedCall<'a> {
707 location: SrcSpan,
708 field_map: &'a FieldMap,
709 arguments: Vec<CallArg<()>>,
710 kind: SelectedCallKind,
711}
712
713enum SelectedCallKind {
714 Value,
715 Pattern,
716}
717
718impl<'a> FillInMissingLabelledArgs<'a> {
719 pub fn new(
720 module: &'a Module,
721 line_numbers: &'a LineNumbers,
722 params: &'a CodeActionParams,
723 ) -> Self {
724 Self {
725 module,
726 params,
727 edits: TextEdits::new(line_numbers),
728 use_right_hand_side_location: None,
729 selected_call: None,
730 }
731 }
732
733 pub fn code_actions(mut self) -> Vec<CodeAction> {
734 self.visit_typed_module(&self.module.ast);
735
736 if let Some(SelectedCall {
737 location: call_location,
738 field_map,
739 arguments,
740 kind,
741 }) = self.selected_call
742 {
743 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
744 let missing_labels = field_map.missing_labels(&arguments);
745
746 // If we're applying the code action to a use call, then we know
747 // that the last missing argument is going to be implicitly inserted
748 // by the compiler, so in that case we don't want to also add that
749 // last label to the completions.
750 let missing_labels = missing_labels.iter().peekable();
751 let mut missing_labels = if is_use_call {
752 missing_labels.dropping_back(1)
753 } else {
754 missing_labels
755 };
756
757 // If we couldn't find any missing label to insert we just return.
758 if missing_labels.peek().is_none() {
759 return vec![];
760 }
761
762 // A pattern could have been written with no parentheses at all!
763 // So we need to check for the last character to see if parentheses
764 // are there or not before filling the arguments in
765 let has_parentheses = ")"
766 == code_at(
767 self.module,
768 SrcSpan::new(call_location.end - 1, call_location.end),
769 );
770 let label_insertion_start = if has_parentheses {
771 // If it ends with a parentheses we'll need to start inserting
772 // right before the closing one...
773 call_location.end - 1
774 } else {
775 // ...otherwise we just append the result
776 call_location.end
777 };
778
779 // Now we need to figure out if there's a comma at the end of the
780 // arguments list:
781 //
782 // call(one, |)
783 // ^ Cursor here, with a comma behind
784 //
785 // call(one|)
786 // ^ Cursor here, no comma behind, we'll have to add one!
787 //
788 let has_comma_after_last_argument = if let Some(last_arg) = arguments
789 .iter()
790 .filter(|arg| !arg.is_implicit())
791 .next_back()
792 {
793 self.module
794 .code
795 .get(last_arg.location.end as usize..=label_insertion_start as usize)
796 .is_some_and(|text| text.contains(','))
797 } else {
798 false
799 };
800
801 let format_label = match kind {
802 SelectedCallKind::Value => |label| format!("{label}: todo"),
803 SelectedCallKind::Pattern => |label| format!("{label}:"),
804 };
805
806 let labels_list = missing_labels.map(format_label).join(", ");
807
808 let has_no_explicit_arguments = arguments
809 .iter()
810 .filter(|arg| !arg.is_implicit())
811 .peekable()
812 .peek()
813 .is_none();
814
815 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
816 labels_list
817 } else {
818 format!(", {labels_list}")
819 };
820
821 let edit = if has_parentheses {
822 labels_list
823 } else {
824 // If the variant whose arguments we're filling in was written
825 // with no parentheses we need to add those as well to make it a
826 // valid constructor.
827 format!("({labels_list})")
828 };
829
830 self.edits.insert(label_insertion_start, edit);
831
832 let mut action = Vec::with_capacity(1);
833 CodeActionBuilder::new("Fill labels")
834 .kind(CodeActionKind::QUICKFIX)
835 .changes(self.params.text_document.uri.clone(), self.edits.edits)
836 .preferred(true)
837 .push_to(&mut action);
838 return action;
839 }
840
841 vec![]
842 }
843
844 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
845 CallArg {
846 label: argument.label.clone(),
847 location: argument.location,
848 value: (),
849 implicit: argument.implicit,
850 }
851 }
852}
853
854impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
855 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
856 // If we're adding labels to a use call the correct location of the
857 // function we need to add labels to is `use_right_hand_side_location`.
858 // So we store it for when we're typing the use call.
859 let previous = self.use_right_hand_side_location;
860 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
861 ast::visit::visit_typed_use(self, use_);
862 self.use_right_hand_side_location = previous;
863 }
864
865 fn visit_typed_expr_call(
866 &mut self,
867 location: &'ast SrcSpan,
868 type_: &'ast Arc<Type>,
869 fun: &'ast TypedExpr,
870 arguments: &'ast [TypedCallArg],
871 ) {
872 let call_range = self.edits.src_span_to_lsp_range(*location);
873 if !within(self.params.range, call_range) {
874 return;
875 }
876
877 if let Some(field_map) = fun.field_map() {
878 let location = self.use_right_hand_side_location.unwrap_or(*location);
879 self.selected_call = Some(SelectedCall {
880 location,
881 field_map,
882 arguments: arguments.iter().map(Self::empty_argument).collect(),
883 kind: SelectedCallKind::Value,
884 })
885 }
886
887 // We only want to take into account the innermost function call
888 // containing the current selection so we can't stop at the first call
889 // we find (the outermost one) and have to keep traversing it in case
890 // we're inside a nested call.
891 let previous = self.use_right_hand_side_location;
892 self.use_right_hand_side_location = None;
893 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
894 self.use_right_hand_side_location = previous;
895 }
896
897 fn visit_typed_pattern_constructor(
898 &mut self,
899 location: &'ast SrcSpan,
900 name_location: &'ast SrcSpan,
901 name: &'ast EcoString,
902 arguments: &'ast Vec<CallArg<TypedPattern>>,
903 module: &'ast Option<(EcoString, SrcSpan)>,
904 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
905 spread: &'ast Option<SrcSpan>,
906 type_: &'ast Arc<Type>,
907 ) {
908 let call_range = self.edits.src_span_to_lsp_range(*location);
909 if !within(self.params.range, call_range) {
910 return;
911 }
912
913 if let Some(field_map) = constructor.field_map() {
914 self.selected_call = Some(SelectedCall {
915 location: *location,
916 field_map,
917 arguments: arguments.iter().map(Self::empty_argument).collect(),
918 kind: SelectedCallKind::Pattern,
919 })
920 }
921
922 ast::visit::visit_typed_pattern_constructor(
923 self,
924 location,
925 name_location,
926 name,
927 arguments,
928 module,
929 constructor,
930 spread,
931 type_,
932 );
933 }
934}
935
936struct MissingImport {
937 location: SrcSpan,
938 suggestions: Vec<ImportSuggestion>,
939}
940
941struct ImportSuggestion {
942 // The name to replace with, if the user made a typo
943 name: EcoString,
944 // The optional module to import, if suggesting an importable module
945 import: Option<EcoString>,
946}
947
948pub fn code_action_import_module(
949 module: &Module,
950 line_numbers: &LineNumbers,
951 params: &CodeActionParams,
952 error: &Option<Error>,
953 actions: &mut Vec<CodeAction>,
954) {
955 let uri = ¶ms.text_document.uri;
956 let Some(Error::Type { errors, .. }) = error else {
957 return;
958 };
959
960 let missing_imports = errors
961 .into_iter()
962 .filter_map(|e| match e {
963 type_::Error::UnknownModule {
964 location,
965 suggestions,
966 ..
967 } => suggest_imports(*location, suggestions),
968 _ => None,
969 })
970 .collect_vec();
971
972 if missing_imports.is_empty() {
973 return;
974 }
975
976 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
977 let first_is_import = first_import_pos.is_some();
978 let import_location = first_import_pos.unwrap_or_default();
979
980 let after_import_newlines =
981 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
982
983 for missing_import in missing_imports {
984 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
985 if !overlaps(params.range, range) {
986 continue;
987 }
988
989 for suggestion in missing_import.suggestions {
990 let mut edits = vec![TextEdit {
991 range,
992 new_text: suggestion.name.to_string(),
993 }];
994 if let Some(import) = &suggestion.import {
995 edits.push(get_import_edit(
996 import_location,
997 import,
998 &after_import_newlines,
999 ))
1000 };
1001
1002 let title = match &suggestion.import {
1003 Some(import) => &format!("Import `{import}`"),
1004 _ => &format!("Did you mean `{}`", suggestion.name),
1005 };
1006
1007 CodeActionBuilder::new(title)
1008 .kind(CodeActionKind::QUICKFIX)
1009 .changes(uri.clone(), edits)
1010 .preferred(true)
1011 .push_to(actions);
1012 }
1013 }
1014}
1015
1016fn suggest_imports(
1017 location: SrcSpan,
1018 importable_modules: &[ModuleSuggestion],
1019) -> Option<MissingImport> {
1020 let suggestions = importable_modules
1021 .iter()
1022 .map(|suggestion| {
1023 let imported_name = suggestion.last_name_component();
1024 match suggestion {
1025 ModuleSuggestion::Importable(name) => ImportSuggestion {
1026 name: imported_name.into(),
1027 import: Some(name.clone()),
1028 },
1029 ModuleSuggestion::Imported(_) => ImportSuggestion {
1030 name: imported_name.into(),
1031 import: None,
1032 },
1033 }
1034 })
1035 .collect_vec();
1036
1037 if suggestions.is_empty() {
1038 None
1039 } else {
1040 Some(MissingImport {
1041 location,
1042 suggestions,
1043 })
1044 }
1045}
1046
1047pub fn code_action_add_missing_patterns(
1048 module: &Module,
1049 line_numbers: &LineNumbers,
1050 params: &CodeActionParams,
1051 error: &Option<Error>,
1052 actions: &mut Vec<CodeAction>,
1053) {
1054 let uri = ¶ms.text_document.uri;
1055 let Some(Error::Type { errors, .. }) = error else {
1056 return;
1057 };
1058 let missing_patterns = errors
1059 .iter()
1060 .filter_map(|error| match error {
1061 type_::Error::InexhaustiveCaseExpression { location, missing } => {
1062 Some((*location, missing))
1063 }
1064 _ => None,
1065 })
1066 .collect_vec();
1067
1068 if missing_patterns.is_empty() {
1069 return;
1070 }
1071
1072 for (location, missing) in missing_patterns {
1073 let mut edits = TextEdits::new(line_numbers);
1074 let range = edits.src_span_to_lsp_range(location);
1075 if !overlaps(params.range, range) {
1076 return;
1077 }
1078
1079 let Some(Located::Expression {
1080 expression: TypedExpr::Case {
1081 clauses, subjects, ..
1082 },
1083 ..
1084 }) = module.find_node(location.start)
1085 else {
1086 continue;
1087 };
1088
1089 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1090
1091 let indent = " ".repeat(indent_size);
1092
1093 // Insert the missing patterns just after the final clause, or just before
1094 // the closing brace if there are no clauses
1095
1096 let insert_at = clauses
1097 .last()
1098 .map(|clause| clause.location.end)
1099 .unwrap_or(location.end - 1);
1100
1101 for pattern in missing {
1102 let new_text = format!("\n{indent} {pattern} -> todo");
1103 edits.insert(insert_at, new_text);
1104 }
1105
1106 // Add a newline + indent after the last pattern if there are no clauses
1107 //
1108 // This improves the generated code for this case:
1109 // ```gleam
1110 // case True {}
1111 // ```
1112 // This produces:
1113 // ```gleam
1114 // case True {
1115 // True -> todo
1116 // False -> todo
1117 // }
1118 // ```
1119 // Instead of:
1120 // ```gleam
1121 // case True {
1122 // True -> todo
1123 // False -> todo}
1124 // ```
1125 //
1126 if clauses.is_empty() {
1127 let last_subject_location = subjects
1128 .last()
1129 .expect("Case expressions have at least one subject")
1130 .location()
1131 .end;
1132
1133 // Find the opening brace of the case expression
1134 let chars = module.code[last_subject_location as usize..].chars();
1135 let mut start_brace_location = last_subject_location;
1136 for char in chars {
1137 start_brace_location += 1;
1138 if char == '{' {
1139 break;
1140 }
1141 }
1142
1143 // Remove any blank spaces/lines between the start brace and end brace
1144 edits.delete(SrcSpan::new(start_brace_location, insert_at));
1145 edits.insert(insert_at, format!("\n{indent}"));
1146 }
1147
1148 CodeActionBuilder::new("Add missing patterns")
1149 .kind(CodeActionKind::QUICKFIX)
1150 .changes(uri.clone(), edits.edits)
1151 .preferred(true)
1152 .push_to(actions);
1153 }
1154}
1155
1156/// Builder for code action to add annotations to an assignment or function
1157///
1158pub struct AddAnnotations<'a> {
1159 module: &'a Module,
1160 params: &'a CodeActionParams,
1161 edits: TextEdits<'a>,
1162 printer: Printer<'a>,
1163}
1164
1165impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1166 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1167 self.visit_typed_expr(&assignment.value);
1168
1169 // We only offer this code action between `let` and `=`, because
1170 // otherwise it could lead to confusing behaviour if inside a block
1171 // which is part of a let binding.
1172 let pattern_location = assignment.pattern.location();
1173 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1174 let code_action_range = self.edits.src_span_to_lsp_range(location);
1175
1176 // Only offer the code action if the cursor is over the statement
1177 if !overlaps(code_action_range, self.params.range) {
1178 return;
1179 }
1180
1181 // We don't need to add an annotation if there already is one
1182 if assignment.annotation.is_some() {
1183 return;
1184 }
1185
1186 // Various expressions such as pipelines and `use` expressions generate assignments
1187 // internally. However, these cannot be annotated and so we don't offer a code action here.
1188 if matches!(assignment.kind, AssignmentKind::Generated) {
1189 return;
1190 }
1191
1192 self.edits.insert(
1193 pattern_location.end,
1194 format!(": {}", self.printer.print_type(&assignment.type_())),
1195 );
1196 }
1197
1198 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1199 // Since type variable names are local to definitions, any type variables
1200 // in other parts of the module shouldn't affect what we print for the
1201 // annotations of this constant.
1202 self.printer.clear_type_variables();
1203
1204 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1205
1206 // Only offer the code action if the cursor is over the statement
1207 if !overlaps(code_action_range, self.params.range) {
1208 return;
1209 }
1210
1211 // We don't need to add an annotation if there already is one
1212 if constant.annotation.is_some() {
1213 return;
1214 }
1215
1216 self.edits.insert(
1217 constant.name_location.end,
1218 format!(": {}", self.printer.print_type(&constant.type_)),
1219 );
1220 }
1221
1222 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1223 // Since type variable names are local to definitions, any type variables
1224 // in other parts of the module shouldn't affect what we print for the
1225 // annotations of this functions. The only variables which cannot clash
1226 // are ones defined in the signature of this function, which we register
1227 // when we visit the parameters of this function inside `collect_type_variables`.
1228 self.printer.clear_type_variables();
1229 collect_type_variables(&mut self.printer, fun);
1230
1231 ast::visit::visit_typed_function(self, fun);
1232
1233 let code_action_range = self.edits.src_span_to_lsp_range(
1234 fun.body_start
1235 .map(|body_start| SrcSpan {
1236 start: fun.location.start,
1237 end: body_start,
1238 })
1239 .unwrap_or(fun.location),
1240 );
1241
1242 // Only offer the code action if the cursor is over the statement
1243 if !overlaps(code_action_range, self.params.range) {
1244 return;
1245 }
1246
1247 // Annotate each argument separately
1248 for argument in fun.arguments.iter() {
1249 // Don't annotate the argument if it's already annotated
1250 if argument.annotation.is_some() {
1251 continue;
1252 }
1253
1254 self.edits.insert(
1255 argument.location.end,
1256 format!(": {}", self.printer.print_type(&argument.type_)),
1257 );
1258 }
1259
1260 // Annotate the return type if it isn't already annotated
1261 if fun.return_annotation.is_none() {
1262 self.edits.insert(
1263 fun.location.end,
1264 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1265 );
1266 }
1267 }
1268
1269 fn visit_typed_expr_fn(
1270 &mut self,
1271 location: &'ast SrcSpan,
1272 type_: &'ast Arc<Type>,
1273 kind: &'ast FunctionLiteralKind,
1274 arguments: &'ast [TypedArg],
1275 body: &'ast Vec1<TypedStatement>,
1276 return_annotation: &'ast Option<ast::TypeAst>,
1277 ) {
1278 ast::visit::visit_typed_expr_fn(
1279 self,
1280 location,
1281 type_,
1282 kind,
1283 arguments,
1284 body,
1285 return_annotation,
1286 );
1287
1288 // If the function doesn't have a head, we can't annotate it
1289 let location = match kind {
1290 // Function captures don't need any type annotations
1291 FunctionLiteralKind::Capture { .. } => return,
1292 FunctionLiteralKind::Anonymous { head } => head,
1293 FunctionLiteralKind::Use { location } => location,
1294 };
1295
1296 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1297
1298 // Only offer the code action if the cursor is over the expression
1299 if !overlaps(code_action_range, self.params.range) {
1300 return;
1301 }
1302
1303 // Annotate each argument separately
1304 for argument in arguments.iter() {
1305 // Don't annotate the argument if it's already annotated
1306 if argument.annotation.is_some() {
1307 continue;
1308 }
1309
1310 self.edits.insert(
1311 argument.location.end,
1312 format!(": {}", self.printer.print_type(&argument.type_)),
1313 );
1314 }
1315
1316 // Annotate the return type if it isn't already annotated, and this is
1317 // an anonymous function.
1318 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1319 let return_type = &type_.return_type().expect("Type must be a function");
1320 let pretty_type = self.printer.print_type(return_type);
1321 self.edits
1322 .insert(location.end, format!(" -> {pretty_type}"));
1323 }
1324 }
1325}
1326
1327impl<'a> AddAnnotations<'a> {
1328 pub fn new(
1329 module: &'a Module,
1330 line_numbers: &'a LineNumbers,
1331 params: &'a CodeActionParams,
1332 ) -> Self {
1333 Self {
1334 module,
1335 params,
1336 edits: TextEdits::new(line_numbers),
1337 // We need to use the same printer for all the edits because otherwise
1338 // we could get duplicate type variable names.
1339 printer: Printer::new_without_type_variables(&module.ast.names),
1340 }
1341 }
1342
1343 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1344 self.visit_typed_module(&self.module.ast);
1345
1346 let uri = &self.params.text_document.uri;
1347
1348 let title = match self.edits.edits.len() {
1349 // We don't offer a code action if there is no action to perform
1350 0 => return,
1351 1 => "Add type annotation",
1352 _ => "Add type annotations",
1353 };
1354
1355 CodeActionBuilder::new(title)
1356 .kind(CodeActionKind::REFACTOR)
1357 .changes(uri.clone(), self.edits.edits)
1358 .preferred(false)
1359 .push_to(actions);
1360 }
1361}
1362
1363struct TypeVariableCollector<'a, 'b> {
1364 printer: &'a mut Printer<'b>,
1365}
1366
1367/// Collect type variables defined within a function and register them for a
1368/// `Printer`
1369fn collect_type_variables(printer: &mut Printer<'_>, function: &ast::TypedFunction) {
1370 TypeVariableCollector { printer }.visit_typed_function(function);
1371}
1372
1373impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> {
1374 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) {
1375 // Register this type variable so that we don't duplicate names when
1376 // adding annotations.
1377 self.printer.register_type_variable(name.clone());
1378 }
1379}
1380
1381pub struct QualifiedConstructor<'a> {
1382 import: &'a Import<EcoString>,
1383 used_name: EcoString,
1384 constructor: EcoString,
1385 layer: ast::Layer,
1386}
1387
1388impl QualifiedConstructor<'_> {
1389 fn constructor_import(&self) -> String {
1390 if self.layer.is_value() {
1391 self.constructor.to_string()
1392 } else {
1393 format!("type {}", self.constructor)
1394 }
1395 }
1396}
1397
1398pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1399 module: &'a Module,
1400 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1401 params: &'a CodeActionParams,
1402 line_numbers: &'a LineNumbers,
1403 qualified_constructor: Option<QualifiedConstructor<'a>>,
1404}
1405
1406impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> {
1407 fn new(
1408 module: &'a Module,
1409 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
1410 params: &'a CodeActionParams,
1411 line_numbers: &'a LineNumbers,
1412 ) -> Self {
1413 Self {
1414 module,
1415 compiler,
1416 params,
1417 line_numbers,
1418 qualified_constructor: None,
1419 }
1420 }
1421
1422 fn get_module_import(
1423 &self,
1424 module_name: &EcoString,
1425 constructor: &EcoString,
1426 layer: ast::Layer,
1427 ) -> Option<&'a Import<EcoString>> {
1428 let mut matching_import = None;
1429
1430 for definition in &self.module.ast.definitions {
1431 let ast::Definition::Import(import) = definition else {
1432 continue;
1433 };
1434
1435 if import.used_name().as_deref() == Some(module_name)
1436 && let Some(module) = self.compiler.get_module_interface(&import.module)
1437 {
1438 // If the import is the one we're referring to, we see if the
1439 // referred module exports the type/value we are trying to
1440 // unqualify: we don't want to offer the action indiscriminately if
1441 // it would generate invalid code!
1442 let module_exports_constructor = match layer {
1443 ast::Layer::Value => module.get_public_value(constructor).is_some(),
1444 ast::Layer::Type => module.get_public_type(constructor).is_some(),
1445 };
1446 if module_exports_constructor {
1447 matching_import = Some(import);
1448 }
1449 } else {
1450 // If the import refers to another module we still want to check
1451 // if in its unqualified import list there is a name that's equal
1452 // to the one we're trying to unqualify. In this case we can't
1453 // offer the action as it would generate invalid code.
1454 //
1455 // For example:
1456 // ```gleam
1457 // import wibble.{Some}
1458 // import option
1459 //
1460 // pub fn something() {
1461 // option.Some(1)
1462 // ^^^^ We can't unqualify this because `Some` is already
1463 // imported unqualified from the `wibble` module
1464 // }
1465 // ```
1466 //
1467 let imported = match layer {
1468 ast::Layer::Value => &import.unqualified_values,
1469 ast::Layer::Type => &import.unqualified_types,
1470 };
1471 let constructor_already_imported_by_other_module = imported
1472 .iter()
1473 .any(|value| value.used_name() == constructor);
1474
1475 if constructor_already_imported_by_other_module {
1476 return None;
1477 }
1478 }
1479 }
1480
1481 matching_import
1482 }
1483}
1484
1485impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> {
1486 fn visit_type_ast_constructor(
1487 &mut self,
1488 location: &'ast SrcSpan,
1489 name_location: &'ast SrcSpan,
1490 module: &'ast Option<(EcoString, SrcSpan)>,
1491 name: &'ast EcoString,
1492 arguments: &'ast Vec<ast::TypeAst>,
1493 ) {
1494 let range = src_span_to_lsp_range(*location, self.line_numbers);
1495 if overlaps(self.params.range, range)
1496 && let Some((module_alias, _)) = module
1497 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type)
1498 {
1499 self.qualified_constructor = Some(QualifiedConstructor {
1500 import,
1501 used_name: module_alias.clone(),
1502 constructor: name.clone(),
1503 layer: ast::Layer::Type,
1504 });
1505 }
1506 ast::visit::visit_type_ast_constructor(
1507 self,
1508 location,
1509 name_location,
1510 module,
1511 name,
1512 arguments,
1513 );
1514 }
1515
1516 fn visit_typed_expr_module_select(
1517 &mut self,
1518 location: &'ast SrcSpan,
1519 field_start: &'ast u32,
1520 type_: &'ast Arc<Type>,
1521 label: &'ast EcoString,
1522 module_name: &'ast EcoString,
1523 module_alias: &'ast EcoString,
1524 constructor: &'ast ModuleValueConstructor,
1525 ) {
1526 // When hovering over a Record Value Constructor, we want to expand the source span to
1527 // include the module name:
1528 // option.Some
1529 // ↑
1530 // This allows us to offer a code action when hovering over the module name.
1531 let range = src_span_to_lsp_range(*location, self.line_numbers);
1532 if overlaps(self.params.range, range)
1533 && let ModuleValueConstructor::Record {
1534 name: constructor_name,
1535 ..
1536 } = constructor
1537 && let Some(import) =
1538 self.get_module_import(module_alias, constructor_name, ast::Layer::Value)
1539 {
1540 self.qualified_constructor = Some(QualifiedConstructor {
1541 import,
1542 used_name: module_alias.clone(),
1543 constructor: constructor_name.clone(),
1544 layer: ast::Layer::Value,
1545 });
1546 }
1547 ast::visit::visit_typed_expr_module_select(
1548 self,
1549 location,
1550 field_start,
1551 type_,
1552 label,
1553 module_name,
1554 module_alias,
1555 constructor,
1556 )
1557 }
1558
1559 fn visit_typed_pattern_constructor(
1560 &mut self,
1561 location: &'ast SrcSpan,
1562 name_location: &'ast SrcSpan,
1563 name: &'ast EcoString,
1564 arguments: &'ast Vec<CallArg<TypedPattern>>,
1565 module: &'ast Option<(EcoString, SrcSpan)>,
1566 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1567 spread: &'ast Option<SrcSpan>,
1568 type_: &'ast Arc<Type>,
1569 ) {
1570 let range = src_span_to_lsp_range(*location, self.line_numbers);
1571 if overlaps(self.params.range, range)
1572 && let Some((module_alias, _)) = module
1573 && let analyse::Inferred::Known(_) = constructor
1574 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value)
1575 {
1576 self.qualified_constructor = Some(QualifiedConstructor {
1577 import,
1578 used_name: module_alias.clone(),
1579 constructor: name.clone(),
1580 layer: ast::Layer::Value,
1581 });
1582 }
1583 ast::visit::visit_typed_pattern_constructor(
1584 self,
1585 location,
1586 name_location,
1587 name,
1588 arguments,
1589 module,
1590 constructor,
1591 spread,
1592 type_,
1593 );
1594 }
1595}
1596
1597pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
1598 module: &'a Module,
1599 params: &'a CodeActionParams,
1600 edits: TextEdits<'a>,
1601 qualified_constructor: QualifiedConstructor<'a>,
1602}
1603
1604impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
1605 pub fn new(
1606 module: &'a Module,
1607 params: &'a CodeActionParams,
1608 line_numbers: &'a LineNumbers,
1609 qualified_constructor: QualifiedConstructor<'a>,
1610 ) -> Self {
1611 Self {
1612 module,
1613 params,
1614 edits: TextEdits::new(line_numbers),
1615 qualified_constructor,
1616 }
1617 }
1618
1619 pub fn code_actions(mut self) -> Vec<CodeAction> {
1620 self.visit_typed_module(&self.module.ast);
1621 if self.edits.edits.is_empty() {
1622 return vec![];
1623 }
1624 self.edit_import();
1625 let mut action = Vec::with_capacity(1);
1626 CodeActionBuilder::new(&format!(
1627 "Unqualify {}.{}",
1628 self.qualified_constructor.used_name, self.qualified_constructor.constructor
1629 ))
1630 .kind(CodeActionKind::REFACTOR)
1631 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1632 .preferred(false)
1633 .push_to(&mut action);
1634 action
1635 }
1636
1637 fn remove_module_qualifier(&mut self, location: SrcSpan) {
1638 self.edits.delete(SrcSpan {
1639 start: location.start,
1640 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
1641 })
1642 }
1643
1644 fn edit_import(&mut self) {
1645 let QualifiedConstructor {
1646 constructor,
1647 layer,
1648 import,
1649 ..
1650 } = &self.qualified_constructor;
1651 let is_imported = if layer.is_value() {
1652 import
1653 .unqualified_values
1654 .iter()
1655 .any(|value| value.used_name() == constructor)
1656 } else {
1657 import
1658 .unqualified_types
1659 .iter()
1660 .any(|type_| type_.used_name() == constructor)
1661 };
1662 if is_imported {
1663 return;
1664 }
1665 let (insert_pos, new_text) = edits::insert_unqualified_import(
1666 import,
1667 &self.module.code,
1668 self.qualified_constructor.constructor_import(),
1669 );
1670 let span = SrcSpan::new(insert_pos, insert_pos);
1671 self.edits.replace(span, new_text);
1672 }
1673}
1674
1675impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
1676 fn visit_type_ast_constructor(
1677 &mut self,
1678 location: &'ast SrcSpan,
1679 name_location: &'ast SrcSpan,
1680 module: &'ast Option<(EcoString, SrcSpan)>,
1681 name: &'ast EcoString,
1682 arguments: &'ast Vec<ast::TypeAst>,
1683 ) {
1684 if let Some((module_name, _)) = module {
1685 let QualifiedConstructor {
1686 used_name,
1687 constructor,
1688 layer,
1689 ..
1690 } = &self.qualified_constructor;
1691
1692 if !layer.is_value() && used_name == module_name && name == constructor {
1693 self.remove_module_qualifier(*location);
1694 }
1695 }
1696 ast::visit::visit_type_ast_constructor(
1697 self,
1698 location,
1699 name_location,
1700 module,
1701 name,
1702 arguments,
1703 );
1704 }
1705
1706 fn visit_typed_expr_module_select(
1707 &mut self,
1708 location: &'ast SrcSpan,
1709 field_start: &'ast u32,
1710 type_: &'ast Arc<Type>,
1711 label: &'ast EcoString,
1712 module_name: &'ast EcoString,
1713 module_alias: &'ast EcoString,
1714 constructor: &'ast ModuleValueConstructor,
1715 ) {
1716 if let ModuleValueConstructor::Record { name, .. } = constructor {
1717 let QualifiedConstructor {
1718 used_name,
1719 constructor,
1720 layer,
1721 ..
1722 } = &self.qualified_constructor;
1723
1724 if layer.is_value() && used_name == module_alias && name == constructor {
1725 self.remove_module_qualifier(*location);
1726 }
1727 }
1728 ast::visit::visit_typed_expr_module_select(
1729 self,
1730 location,
1731 field_start,
1732 type_,
1733 label,
1734 module_name,
1735 module_alias,
1736 constructor,
1737 )
1738 }
1739
1740 fn visit_typed_pattern_constructor(
1741 &mut self,
1742 location: &'ast SrcSpan,
1743 name_location: &'ast SrcSpan,
1744 name: &'ast EcoString,
1745 arguments: &'ast Vec<CallArg<TypedPattern>>,
1746 module: &'ast Option<(EcoString, SrcSpan)>,
1747 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1748 spread: &'ast Option<SrcSpan>,
1749 type_: &'ast Arc<Type>,
1750 ) {
1751 if let Some((module_alias, _)) = module
1752 && let analyse::Inferred::Known(_) = constructor
1753 {
1754 let QualifiedConstructor {
1755 used_name,
1756 constructor,
1757 layer,
1758 ..
1759 } = &self.qualified_constructor;
1760
1761 if layer.is_value() && used_name == module_alias && name == constructor {
1762 self.remove_module_qualifier(*location);
1763 }
1764 }
1765 ast::visit::visit_typed_pattern_constructor(
1766 self,
1767 location,
1768 name_location,
1769 name,
1770 arguments,
1771 module,
1772 constructor,
1773 spread,
1774 type_,
1775 );
1776 }
1777}
1778
1779pub fn code_action_convert_qualified_constructor_to_unqualified<IO>(
1780 module: &Module,
1781 compiler: &LspProjectCompiler<FileSystemProxy<IO>>,
1782 line_numbers: &LineNumbers,
1783 params: &CodeActionParams,
1784 actions: &mut Vec<CodeAction>,
1785) {
1786 let mut first_pass =
1787 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers);
1788 first_pass.visit_typed_module(&module.ast);
1789 let Some(qualified_constructor) = first_pass.qualified_constructor else {
1790 return;
1791 };
1792 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
1793 module,
1794 params,
1795 line_numbers,
1796 qualified_constructor,
1797 );
1798 let new_actions = second_pass.code_actions();
1799 actions.extend(new_actions);
1800}
1801
1802struct UnqualifiedConstructor<'a> {
1803 module_name: EcoString,
1804 constructor: &'a ast::UnqualifiedImport,
1805 layer: ast::Layer,
1806}
1807
1808struct UnqualifiedToQualifiedImportFirstPass<'a> {
1809 module: &'a Module,
1810 params: &'a CodeActionParams,
1811 line_numbers: &'a LineNumbers,
1812 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
1813}
1814
1815impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
1816 fn new(
1817 module: &'a Module,
1818 params: &'a CodeActionParams,
1819 line_numbers: &'a LineNumbers,
1820 ) -> Self {
1821 Self {
1822 module,
1823 params,
1824 line_numbers,
1825 unqualified_constructor: None,
1826 }
1827 }
1828
1829 fn get_module_import_from_value_constructor(
1830 &mut self,
1831 module_name: &EcoString,
1832 constructor_name: &EcoString,
1833 ) {
1834 self.unqualified_constructor =
1835 self.module
1836 .ast
1837 .definitions
1838 .iter()
1839 .find_map(|definition| match definition {
1840 ast::Definition::Import(import) if import.module == *module_name => import
1841 .unqualified_values
1842 .iter()
1843 .find(|value| value.used_name() == constructor_name)
1844 .and_then(|value| {
1845 Some(UnqualifiedConstructor {
1846 constructor: value,
1847 module_name: import.used_name()?,
1848 layer: ast::Layer::Value,
1849 })
1850 }),
1851 _ => None,
1852 })
1853 }
1854
1855 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
1856 self.unqualified_constructor =
1857 self.module
1858 .ast
1859 .definitions
1860 .iter()
1861 .find_map(|definition| match definition {
1862 ast::Definition::Import(import) => {
1863 if let Some(ty) = import
1864 .unqualified_types
1865 .iter()
1866 .find(|ty| ty.used_name() == constructor_name)
1867 {
1868 return Some(UnqualifiedConstructor {
1869 constructor: ty,
1870 module_name: import.used_name()?,
1871 layer: ast::Layer::Type,
1872 });
1873 }
1874 None
1875 }
1876 _ => None,
1877 })
1878 }
1879}
1880
1881impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
1882 fn visit_type_ast_constructor(
1883 &mut self,
1884 location: &'ast SrcSpan,
1885 name_location: &'ast SrcSpan,
1886 module: &'ast Option<(EcoString, SrcSpan)>,
1887 name: &'ast EcoString,
1888 arguments: &'ast Vec<ast::TypeAst>,
1889 ) {
1890 if module.is_none()
1891 && overlaps(
1892 self.params.range,
1893 src_span_to_lsp_range(*location, self.line_numbers),
1894 )
1895 {
1896 self.get_module_import_from_type_constructor(name);
1897 }
1898
1899 ast::visit::visit_type_ast_constructor(
1900 self,
1901 location,
1902 name_location,
1903 module,
1904 name,
1905 arguments,
1906 );
1907 }
1908
1909 fn visit_typed_expr_var(
1910 &mut self,
1911 location: &'ast SrcSpan,
1912 constructor: &'ast ValueConstructor,
1913 name: &'ast EcoString,
1914 ) {
1915 let range = src_span_to_lsp_range(*location, self.line_numbers);
1916 if overlaps(self.params.range, range)
1917 && let Some(module_name) = match &constructor.variant {
1918 type_::ValueConstructorVariant::ModuleConstant { module, .. }
1919 | type_::ValueConstructorVariant::ModuleFn { module, .. }
1920 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
1921
1922 type_::ValueConstructorVariant::LocalVariable { .. }
1923 | type_::ValueConstructorVariant::LocalConstant { .. } => None,
1924 }
1925 {
1926 self.get_module_import_from_value_constructor(module_name, name);
1927 }
1928 ast::visit::visit_typed_expr_var(self, location, constructor, name);
1929 }
1930
1931 fn visit_typed_pattern_constructor(
1932 &mut self,
1933 location: &'ast SrcSpan,
1934 name_location: &'ast SrcSpan,
1935 name: &'ast EcoString,
1936 arguments: &'ast Vec<CallArg<TypedPattern>>,
1937 module: &'ast Option<(EcoString, SrcSpan)>,
1938 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1939 spread: &'ast Option<SrcSpan>,
1940 type_: &'ast Arc<Type>,
1941 ) {
1942 if module.is_none()
1943 && overlaps(
1944 self.params.range,
1945 src_span_to_lsp_range(*location, self.line_numbers),
1946 )
1947 && let analyse::Inferred::Known(constructor) = constructor
1948 {
1949 self.get_module_import_from_value_constructor(&constructor.module, name);
1950 }
1951
1952 ast::visit::visit_typed_pattern_constructor(
1953 self,
1954 location,
1955 name_location,
1956 name,
1957 arguments,
1958 module,
1959 constructor,
1960 spread,
1961 type_,
1962 );
1963 }
1964}
1965
1966struct UnqualifiedToQualifiedImportSecondPass<'a> {
1967 module: &'a Module,
1968 params: &'a CodeActionParams,
1969 edits: TextEdits<'a>,
1970 unqualified_constructor: UnqualifiedConstructor<'a>,
1971}
1972
1973impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
1974 pub fn new(
1975 module: &'a Module,
1976 params: &'a CodeActionParams,
1977 line_numbers: &'a LineNumbers,
1978 unqualified_constructor: UnqualifiedConstructor<'a>,
1979 ) -> Self {
1980 Self {
1981 module,
1982 params,
1983 edits: TextEdits::new(line_numbers),
1984 unqualified_constructor,
1985 }
1986 }
1987
1988 fn add_module_qualifier(&mut self, location: SrcSpan) {
1989 let src_span = SrcSpan::new(
1990 location.start,
1991 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
1992 );
1993
1994 self.edits.replace(
1995 src_span,
1996 format!(
1997 "{}.{}",
1998 self.unqualified_constructor.module_name,
1999 self.unqualified_constructor.constructor.name
2000 ),
2001 );
2002 }
2003
2004 pub fn code_actions(mut self) -> Vec<CodeAction> {
2005 self.visit_typed_module(&self.module.ast);
2006 if self.edits.edits.is_empty() {
2007 return vec![];
2008 }
2009 self.edit_import();
2010 let mut action = Vec::with_capacity(1);
2011 let UnqualifiedConstructor {
2012 module_name,
2013 constructor,
2014 ..
2015 } = self.unqualified_constructor;
2016 CodeActionBuilder::new(&format!(
2017 "Qualify {} as {}.{}",
2018 constructor.used_name(),
2019 module_name,
2020 constructor.name,
2021 ))
2022 .kind(CodeActionKind::REFACTOR)
2023 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2024 .preferred(false)
2025 .push_to(&mut action);
2026 action
2027 }
2028
2029 fn edit_import(&mut self) {
2030 let UnqualifiedConstructor {
2031 constructor:
2032 ast::UnqualifiedImport {
2033 location: constructor_import_span,
2034 ..
2035 },
2036 ..
2037 } = self.unqualified_constructor;
2038
2039 let mut last_char_pos = constructor_import_span.end as usize;
2040 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2041 last_char_pos += 1;
2042 }
2043 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2044 last_char_pos += 1;
2045 }
2046 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2047 last_char_pos += 1;
2048 }
2049
2050 self.edits.delete(SrcSpan::new(
2051 constructor_import_span.start,
2052 last_char_pos as u32,
2053 ));
2054 }
2055}
2056
2057impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2058 fn visit_type_ast_constructor(
2059 &mut self,
2060 location: &'ast SrcSpan,
2061 name_location: &'ast SrcSpan,
2062 module: &'ast Option<(EcoString, SrcSpan)>,
2063 name: &'ast EcoString,
2064 arguments: &'ast Vec<ast::TypeAst>,
2065 ) {
2066 if module.is_none() {
2067 let UnqualifiedConstructor {
2068 constructor, layer, ..
2069 } = &self.unqualified_constructor;
2070 if !layer.is_value() && constructor.used_name() == name {
2071 self.add_module_qualifier(*location);
2072 }
2073 }
2074 ast::visit::visit_type_ast_constructor(
2075 self,
2076 location,
2077 name_location,
2078 module,
2079 name,
2080 arguments,
2081 );
2082 }
2083
2084 fn visit_typed_expr_var(
2085 &mut self,
2086 location: &'ast SrcSpan,
2087 constructor: &'ast ValueConstructor,
2088 name: &'ast EcoString,
2089 ) {
2090 let UnqualifiedConstructor {
2091 constructor: wanted_constructor,
2092 layer,
2093 ..
2094 } = &self.unqualified_constructor;
2095
2096 if layer.is_value()
2097 && wanted_constructor.used_name() == name
2098 && !constructor.is_local_variable()
2099 {
2100 self.add_module_qualifier(*location);
2101 }
2102 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2103 }
2104
2105 fn visit_typed_pattern_constructor(
2106 &mut self,
2107 location: &'ast SrcSpan,
2108 name_location: &'ast SrcSpan,
2109 name: &'ast EcoString,
2110 arguments: &'ast Vec<CallArg<TypedPattern>>,
2111 module: &'ast Option<(EcoString, SrcSpan)>,
2112 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
2113 spread: &'ast Option<SrcSpan>,
2114 type_: &'ast Arc<Type>,
2115 ) {
2116 if module.is_none() {
2117 let UnqualifiedConstructor {
2118 constructor: wanted_constructor,
2119 layer,
2120 ..
2121 } = &self.unqualified_constructor;
2122 if layer.is_value() && wanted_constructor.used_name() == name {
2123 self.add_module_qualifier(*location);
2124 }
2125 }
2126 ast::visit::visit_typed_pattern_constructor(
2127 self,
2128 location,
2129 name_location,
2130 name,
2131 arguments,
2132 module,
2133 constructor,
2134 spread,
2135 type_,
2136 );
2137 }
2138}
2139
2140pub fn code_action_convert_unqualified_constructor_to_qualified(
2141 module: &Module,
2142 line_numbers: &LineNumbers,
2143 params: &CodeActionParams,
2144 actions: &mut Vec<CodeAction>,
2145) {
2146 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2147 first_pass.visit_typed_module(&module.ast);
2148 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2149 return;
2150 };
2151 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2152 module,
2153 params,
2154 line_numbers,
2155 unqualified_constructor,
2156 );
2157 let new_actions = second_pass.code_actions();
2158 actions.extend(new_actions);
2159}
2160
2161/// Builder for code action to apply the convert from use action, turning a use
2162/// expression into a regular function call.
2163///
2164pub struct ConvertFromUse<'a> {
2165 module: &'a Module,
2166 params: &'a CodeActionParams,
2167 edits: TextEdits<'a>,
2168 selected_use: Option<&'a TypedUse>,
2169}
2170
2171impl<'a> ConvertFromUse<'a> {
2172 pub fn new(
2173 module: &'a Module,
2174 line_numbers: &'a LineNumbers,
2175 params: &'a CodeActionParams,
2176 ) -> Self {
2177 Self {
2178 module,
2179 params,
2180 edits: TextEdits::new(line_numbers),
2181 selected_use: None,
2182 }
2183 }
2184
2185 pub fn code_actions(mut self) -> Vec<CodeAction> {
2186 self.visit_typed_module(&self.module.ast);
2187
2188 let Some(use_) = self.selected_use else {
2189 return vec![];
2190 };
2191
2192 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else {
2193 return vec![];
2194 };
2195
2196 // If the use callback we're desugaring is using labels, that means we
2197 // have to add the last argument's label when writing the callback;
2198 // otherwise, it would result in invalid code.
2199 //
2200 // use acc, item <- list.fold(over: list, from: 1)
2201 // todo
2202 //
2203 // Needs to be rewritten as:
2204 //
2205 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2206 // ^^^^^ We cannot forget to add this label back!
2207 //
2208 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) {
2209 fun.field_map()
2210 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned())
2211 .map(|label| eco_format!("{label}: "))
2212 .unwrap_or(EcoString::from(""))
2213 } else {
2214 EcoString::from("")
2215 };
2216
2217 // The use callback is not necessarily the last argument. If you have
2218 // the following function: `wibble(a a, b b) { todo }`
2219 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2220 // is going to be the use callback, not the last one!
2221 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback());
2222 let Some(CallArg {
2223 implicit: Some(ImplicitCallArgOrigin::Use),
2224 value: TypedExpr::Fn { body, type_, .. },
2225 ..
2226 }) = use_callback
2227 else {
2228 return vec![];
2229 };
2230
2231 // If there's arguments on the left hand side of the function we extract
2232 // those so we can paste them back as the anonymous function arguments.
2233 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2234 let assignments_range =
2235 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2236 self.module
2237 .code
2238 .get(assignments_range)
2239 .expect("use assignments")
2240 } else {
2241 ""
2242 };
2243
2244 // We first delete everything on the left hand side of use and the use
2245 // arrow.
2246 self.edits.delete(SrcSpan {
2247 start: use_.location.start,
2248 end: use_.right_hand_side_location.start,
2249 });
2250
2251 let use_line_end = use_.right_hand_side_location.end;
2252 let use_rhs_function_has_some_explicit_arguments = arguments
2253 .iter()
2254 .filter(|argument| !argument.is_use_implicit_callback())
2255 .peekable()
2256 .peek()
2257 .is_some();
2258
2259 let use_rhs_function_ends_with_closed_parentheses = self
2260 .module
2261 .code
2262 .get(use_line_end as usize - 1..use_line_end as usize)
2263 == Some(")");
2264
2265 let last_explicit_arg = arguments
2266 .iter()
2267 .filter(|argument| !argument.is_implicit())
2268 .next_back();
2269 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2270
2271 // This is the piece of code between the end of the last argument and
2272 // the end of the use_expression:
2273 //
2274 // use <- wibble(a, b, )
2275 // ^^^^^ This piece right here, from `,` included
2276 // up to `)` excluded.
2277 //
2278 let text_after_last_argument = self
2279 .module
2280 .code
2281 .get(last_arg_end as usize..use_line_end as usize - 1);
2282 let use_rhs_has_comma_after_last_argument =
2283 text_after_last_argument.is_some_and(|code| code.contains(','));
2284 let needs_space_before_callback =
2285 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2286
2287 if use_rhs_function_ends_with_closed_parentheses {
2288 // If the function on the right hand side of use ends with a closed
2289 // parentheses then we have to remove it and add it later at the end
2290 // of the anonymous function we're inserting.
2291 //
2292 // use <- wibble()
2293 // ^ To add the fn() we need to first remove this
2294 //
2295 // So here we write over the last closed parentheses to remove it.
2296 let callback_start = format!("{callback_label}fn({assignments}) {{");
2297 self.edits.replace(
2298 SrcSpan {
2299 start: use_line_end - 1,
2300 end: use_line_end,
2301 },
2302 // If the function on the rhs of use has other orguments besides
2303 // the implicit fn expression then we need to put a comma after
2304 // the last argument.
2305 if use_rhs_function_has_some_explicit_arguments
2306 && !use_rhs_has_comma_after_last_argument
2307 {
2308 format!(", {callback_start}")
2309 } else if needs_space_before_callback {
2310 format!(" {callback_start}")
2311 } else {
2312 callback_start.to_string()
2313 },
2314 )
2315 } else {
2316 // On the other hand, if the function on the right hand side doesn't
2317 // end with a closed parenthese then we have to manually add it.
2318 //
2319 // use <- wibble
2320 // ^ No parentheses
2321 //
2322 self.edits
2323 .insert(use_line_end, format!("(fn({assignments}) {{"))
2324 };
2325
2326 // Then we have to increase indentation for all the lines of the use
2327 // body.
2328 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
2329 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
2330
2331 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
2332 self.edits.edits.push(TextEdit {
2333 range: Range {
2334 start: Position { line, character: 0 },
2335 end: Position { line, character: 0 },
2336 },
2337 new_text: " ".to_string(),
2338 })
2339 }
2340
2341 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
2342 self.edits.insert(
2343 use_.call.location().end,
2344 format!("\n{final_line_indentation}}})"),
2345 );
2346
2347 let mut action = Vec::with_capacity(1);
2348 CodeActionBuilder::new("Convert from `use`")
2349 .kind(CodeActionKind::REFACTOR_REWRITE)
2350 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2351 .preferred(false)
2352 .push_to(&mut action);
2353 action
2354 }
2355}
2356
2357impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
2358 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
2359 let use_range = self.edits.src_span_to_lsp_range(use_.location);
2360
2361 // If the use expression is using patterns that are not just variable
2362 // assignments then we can't automatically rewrite it as it would result
2363 // in a syntax error as we can't pattern match in an anonymous function
2364 // head.
2365 // At the same time we can't safely add bindings inside the anonymous
2366 // function body by picking placeholder names as we'd risk shadowing
2367 // variables coming from the outer scope.
2368 // So we just skip those use expressions we can't safely rewrite!
2369 if within(self.params.range, use_range)
2370 && use_
2371 .assignments
2372 .iter()
2373 .all(|assignment| assignment.pattern.is_variable())
2374 {
2375 self.selected_use = Some(use_);
2376 }
2377
2378 // We still want to visit the use expression so that we always end up
2379 // picking the innermost, most relevant use under the cursor.
2380 self.visit_typed_expr(&use_.call);
2381 }
2382}
2383
2384/// Builder for code action to apply the convert to use action.
2385///
2386pub struct ConvertToUse<'a> {
2387 module: &'a Module,
2388 params: &'a CodeActionParams,
2389 edits: TextEdits<'a>,
2390 selected_call: Option<CallLocations>,
2391}
2392
2393/// All the locations we'll need to transform a function call into a use
2394/// expression.
2395///
2396struct CallLocations {
2397 call_span: SrcSpan,
2398 called_function_span: SrcSpan,
2399 callback_arguments_span: Option<SrcSpan>,
2400 arg_before_callback_span: Option<SrcSpan>,
2401 callback_body_span: SrcSpan,
2402}
2403
2404impl<'a> ConvertToUse<'a> {
2405 pub fn new(
2406 module: &'a Module,
2407 line_numbers: &'a LineNumbers,
2408 params: &'a CodeActionParams,
2409 ) -> Self {
2410 Self {
2411 module,
2412 params,
2413 edits: TextEdits::new(line_numbers),
2414 selected_call: None,
2415 }
2416 }
2417
2418 pub fn code_actions(mut self) -> Vec<CodeAction> {
2419 self.visit_typed_module(&self.module.ast);
2420
2421 let Some(CallLocations {
2422 call_span,
2423 called_function_span,
2424 callback_arguments_span,
2425 arg_before_callback_span,
2426 callback_body_span,
2427 }) = self.selected_call
2428 else {
2429 return vec![];
2430 };
2431
2432 // This is the nesting level of the `use` keyword we've inserted, we
2433 // want to move the entire body of the anonymous function to this level.
2434 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
2435 let indentation = " ".repeat(use_nesting_level as usize);
2436
2437 // First we move the callback arguments to the left hand side of the
2438 // call and add the `use` keyword.
2439 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span {
2440 let arguments_start = arguments_location.start as usize;
2441 let arguments_end = arguments_location.end as usize;
2442 let arguments_text = self
2443 .module
2444 .code
2445 .get(arguments_start..arguments_end)
2446 .expect("fn args");
2447 format!("use {arguments_text} <- ")
2448 } else {
2449 "use <- ".into()
2450 };
2451
2452 self.edits.insert(call_span.start, left_hand_side_text);
2453
2454 match arg_before_callback_span {
2455 // If the function call has no other arguments besides the callback then
2456 // we just have to remove the `fn(...) {` part.
2457 //
2458 // wibble(fn(...) { ... })
2459 // ^^^^^^^^^^ This goes from the end of the called function
2460 // To the start of the first thing in the anonymous
2461 // function's body.
2462 //
2463 None => self.edits.replace(
2464 SrcSpan::new(called_function_span.end, callback_body_span.start),
2465 format!("\n{indentation}"),
2466 ),
2467 // If it has other arguments we'll have to remove those and add a closed
2468 // parentheses too:
2469 //
2470 // wibble(1, 2, fn(...) { ... })
2471 // ^^^^^^^^^^^ We have to replace this with a `)`, it
2472 // goes from the end of the second-to-last
2473 // argument to the start of the first thing
2474 // in the anonymous function's body.
2475 //
2476 Some(arg_before_callback) => self.edits.replace(
2477 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
2478 format!(")\n{indentation}"),
2479 ),
2480 };
2481
2482 // Then we have to remove two spaces of indentation from each line of
2483 // the callback function's body.
2484 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
2485 for line in body_range.start.line + 1..=body_range.end.line {
2486 self.edits.delete_range(Range::new(
2487 Position { line, character: 0 },
2488 Position { line, character: 2 },
2489 ))
2490 }
2491
2492 // Then we have to remove the anonymous fn closing `}` and the call's
2493 // closing `)`.
2494 self.edits
2495 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
2496
2497 let mut action = Vec::with_capacity(1);
2498 CodeActionBuilder::new("Convert to `use`")
2499 .kind(CodeActionKind::REFACTOR_REWRITE)
2500 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2501 .preferred(false)
2502 .push_to(&mut action);
2503 action
2504 }
2505}
2506
2507impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
2508 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
2509 // The cursor has to be inside the last statement of the function to
2510 // offer the code action.
2511 if let Some(last) = &fun.body.last()
2512 && within(
2513 self.params.range,
2514 self.edits.src_span_to_lsp_range(last.location()),
2515 )
2516 && let Some(call_data) = turn_statement_into_use(last)
2517 {
2518 self.selected_call = Some(call_data);
2519 }
2520
2521 ast::visit::visit_typed_function(self, fun)
2522 }
2523
2524 fn visit_typed_expr_fn(
2525 &mut self,
2526 location: &'ast SrcSpan,
2527 type_: &'ast Arc<Type>,
2528 kind: &'ast FunctionLiteralKind,
2529 arguments: &'ast [TypedArg],
2530 body: &'ast Vec1<TypedStatement>,
2531 return_annotation: &'ast Option<ast::TypeAst>,
2532 ) {
2533 // The cursor has to be inside the last statement of the body to
2534 // offer the code action.
2535 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
2536 if within(self.params.range, last_statement_range)
2537 && let Some(call_data) = turn_statement_into_use(body.last())
2538 {
2539 self.selected_call = Some(call_data);
2540 }
2541
2542 ast::visit::visit_typed_expr_fn(
2543 self,
2544 location,
2545 type_,
2546 kind,
2547 arguments,
2548 body,
2549 return_annotation,
2550 );
2551 }
2552
2553 fn visit_typed_expr_block(
2554 &mut self,
2555 location: &'ast SrcSpan,
2556 statements: &'ast [TypedStatement],
2557 ) {
2558 let Some(last_statement) = statements.last() else {
2559 return;
2560 };
2561
2562 // The cursor has to be inside the last statement of the block to offer
2563 // the code action.
2564 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
2565 if within(self.params.range, statement_range) {
2566 // Only the last statement of a block can be turned into a use!
2567 if let Some(selected_call) = turn_statement_into_use(last_statement) {
2568 self.selected_call = Some(selected_call)
2569 }
2570 }
2571
2572 ast::visit::visit_typed_expr_block(self, location, statements);
2573 }
2574}
2575
2576fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
2577 match statement {
2578 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
2579 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
2580 }
2581}
2582
2583fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
2584 let TypedExpr::Call {
2585 arguments,
2586 location: call_span,
2587 fun: called_function,
2588 ..
2589 } = expr
2590 else {
2591 return None;
2592 };
2593
2594 // The function arguments in the ast are reordered using function's field map.
2595 // This means that in the `args` array they might not appear in the same order
2596 // in which they are written by the user. Since the rest of the code relies
2597 // on their order in the written code we first have to sort them by their
2598 // source position.
2599 let arguments = arguments
2600 .iter()
2601 .sorted_by_key(|argument| argument.location.start)
2602 .collect_vec();
2603
2604 let CallArg {
2605 value: last_arg,
2606 implicit: None,
2607 ..
2608 } = arguments.last()?
2609 else {
2610 return None;
2611 };
2612
2613 let TypedExpr::Fn {
2614 arguments: callback_arguments,
2615 body,
2616 ..
2617 } = last_arg
2618 else {
2619 return None;
2620 };
2621
2622 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) {
2623 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
2624 _ => None,
2625 };
2626
2627 let arg_before_callback_span = if arguments.len() >= 2 {
2628 arguments
2629 .get(arguments.len() - 2)
2630 .map(|call_arg| call_arg.location)
2631 } else {
2632 None
2633 };
2634
2635 let callback_body_span = body.first().location().merge(&body.last().last_location());
2636
2637 Some(CallLocations {
2638 call_span: *call_span,
2639 called_function_span: called_function.location(),
2640 callback_arguments_span,
2641 arg_before_callback_span,
2642 callback_body_span,
2643 })
2644}
2645
2646/// Builder for code action to extract expression into a variable.
2647/// The action will wrap the expression in a block if needed in the appropriate scope.
2648///
2649/// For using the code action on the following selection:
2650///
2651/// ```gleam
2652/// fn void() {
2653/// case result {
2654/// Ok(value) -> 2 * value + 1
2655/// // ^^^^^^^^^
2656/// Error(_) -> panic
2657/// }
2658/// }
2659/// ```
2660///
2661/// Will result:
2662///
2663/// ```gleam
2664/// fn void() {
2665/// case result {
2666/// Ok(value) -> {
2667/// let int = 2 * value
2668/// int + 1
2669/// }
2670/// Error(_) -> panic
2671/// }
2672/// }
2673/// ```
2674pub struct ExtractVariable<'a> {
2675 module: &'a Module,
2676 params: &'a CodeActionParams,
2677 edits: TextEdits<'a>,
2678 position: Option<ExtractVariablePosition>,
2679 selected_expression: Option<(SrcSpan, Arc<Type>)>,
2680 statement_before_selected_expression: Option<SrcSpan>,
2681 latest_statement: Option<SrcSpan>,
2682 to_be_wrapped: bool,
2683 name_generator: NameGenerator,
2684}
2685
2686/// The Position of the selected code
2687#[derive(PartialEq, Eq, Copy, Clone, Debug)]
2688enum ExtractVariablePosition {
2689 InsideCaptureBody,
2690 /// Full statements (i.e. assignments, `use`s, and simple expressions).
2691 TopLevelStatement,
2692 /// The call on the right hand side of a pipe `|>`.
2693 PipelineCall,
2694 /// The right hand side of the `->` in a case expression.
2695 InsideCaseClause,
2696 // A call argument. This can also be a `use` callback.
2697 CallArg,
2698}
2699
2700impl<'a> ExtractVariable<'a> {
2701 pub fn new(
2702 module: &'a Module,
2703 line_numbers: &'a LineNumbers,
2704 params: &'a CodeActionParams,
2705 ) -> Self {
2706 Self {
2707 module,
2708 params,
2709 edits: TextEdits::new(line_numbers),
2710 position: None,
2711 selected_expression: None,
2712 latest_statement: None,
2713 statement_before_selected_expression: None,
2714 to_be_wrapped: false,
2715 name_generator: NameGenerator::new(),
2716 }
2717 }
2718
2719 pub fn code_actions(mut self) -> Vec<CodeAction> {
2720 self.visit_typed_module(&self.module.ast);
2721
2722 let (Some((expression_span, expression_type)), Some(insert_location)) = (
2723 self.selected_expression,
2724 self.statement_before_selected_expression,
2725 ) else {
2726 return vec![];
2727 };
2728
2729 let variable_name = self
2730 .name_generator
2731 .generate_name_from_type(&expression_type);
2732
2733 let content = self
2734 .module
2735 .code
2736 .get(expression_span.start as usize..expression_span.end as usize)
2737 .expect("selected expression");
2738
2739 let range = self.edits.src_span_to_lsp_range(insert_location);
2740
2741 let indent_size =
2742 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
2743
2744 let mut indent = " ".repeat(indent_size);
2745
2746 // We insert the variable declaration
2747 // Wrap in a block if needed
2748 let mut insertion = format!("let {variable_name} = {content}");
2749 if self.to_be_wrapped {
2750 let line_end = self
2751 .edits
2752 .line_numbers
2753 .line_starts
2754 .get((range.end.line + 1) as usize)
2755 .expect("Line number should be valid");
2756
2757 self.edits.insert(*line_end, format!("{indent}}}\n"));
2758 indent += " ";
2759 insertion = format!("{{\n{indent}{insertion}");
2760 };
2761 self.edits
2762 .insert(insert_location.start, insertion + &format!("\n{indent}"));
2763 self.edits
2764 .replace(expression_span, String::from(variable_name));
2765
2766 let mut action = Vec::with_capacity(1);
2767 CodeActionBuilder::new("Extract variable")
2768 .kind(CodeActionKind::REFACTOR_EXTRACT)
2769 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2770 .preferred(false)
2771 .push_to(&mut action);
2772 action
2773 }
2774
2775 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
2776 where
2777 F: Fn(&mut Self),
2778 {
2779 self.at_optional_position(Some(position), fun);
2780 }
2781
2782 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
2783 where
2784 F: Fn(&mut Self),
2785 {
2786 let previous_statement = self.latest_statement;
2787 let previous_position = self.position;
2788 self.position = position;
2789 fun(self);
2790 self.position = previous_position;
2791 self.latest_statement = previous_statement;
2792 }
2793}
2794
2795impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
2796 fn visit_typed_statement(&mut self, stmt: &'ast TypedStatement) {
2797 let range = self.edits.src_span_to_lsp_range(stmt.location());
2798 if !within(self.params.range, range) {
2799 self.latest_statement = Some(stmt.location());
2800 ast::visit::visit_typed_statement(self, stmt);
2801 return;
2802 }
2803
2804 match self.position {
2805 // A capture body is comprised of just a single expression statement
2806 // that is inserted by the compiler, we don't really want to put
2807 // anything before that; so in this case we avoid tracking it.
2808 Some(ExtractVariablePosition::InsideCaptureBody) => {}
2809 Some(ExtractVariablePosition::PipelineCall) => {
2810 // Insert above the pipeline start
2811 self.latest_statement = Some(stmt.location());
2812 }
2813 _ => {
2814 // Insert below the previous statement
2815 self.latest_statement = Some(stmt.location());
2816 self.statement_before_selected_expression = self.latest_statement;
2817 }
2818 }
2819
2820 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
2821 ast::visit::visit_typed_statement(this, stmt);
2822 });
2823 }
2824
2825 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
2826 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
2827 start: fun.location.start,
2828 end: fun.end_position,
2829 });
2830
2831 if !within(self.params.range, fun_range) {
2832 return;
2833 }
2834
2835 // We reset the name generator to purge the variable names from other scopes.
2836 // We then add the reserve the constant names.
2837 self.name_generator = NameGenerator::new();
2838 self.module
2839 .ast
2840 .definitions
2841 .iter()
2842 .for_each(|def| match def {
2843 ast::Definition::ModuleConstant(constant) => {
2844 self.name_generator.add_used_name(constant.name.clone());
2845 }
2846 ast::Definition::Function(function) => {
2847 if let Some((_, function_name)) = &function.name {
2848 self.name_generator.add_used_name(function_name.clone());
2849 }
2850 }
2851 ast::Definition::Import(import) => {
2852 let module_name = match &import.used_name() {
2853 Some(used_name) => used_name.clone(),
2854 _ => import.module.clone(),
2855 };
2856 self.name_generator.add_used_name(module_name);
2857 }
2858 ast::Definition::TypeAlias(_) | ast::Definition::CustomType(_) => (),
2859 });
2860 ast::visit::visit_typed_function(self, fun);
2861 }
2862
2863 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
2864 if let Pattern::Variable { name, .. } = &assignment.pattern {
2865 self.name_generator.add_used_name(name.clone())
2866 };
2867 ast::visit::visit_typed_assignment(self, assignment);
2868 }
2869
2870 fn visit_typed_expr_pipeline(
2871 &mut self,
2872 location: &'ast SrcSpan,
2873 first_value: &'ast TypedPipelineAssignment,
2874 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
2875 finally: &'ast TypedExpr,
2876 finally_kind: &'ast PipelineAssignmentKind,
2877 ) {
2878 let expr_range = self.edits.src_span_to_lsp_range(*location);
2879 if !within(self.params.range, expr_range) {
2880 ast::visit::visit_typed_expr_pipeline(
2881 self,
2882 location,
2883 first_value,
2884 assignments,
2885 finally,
2886 finally_kind,
2887 );
2888 return;
2889 };
2890
2891 // When visiting the assignments or the final pipeline call we want to
2892 // keep track of out position so that we can avoid extracting those.
2893 let all_assignments =
2894 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
2895
2896 for assignment in all_assignments {
2897 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
2898 this.visit_typed_pipeline_assignment(assignment);
2899 });
2900 }
2901
2902 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
2903 this.visit_typed_expr(finally)
2904 });
2905 }
2906
2907 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
2908 let expr_location = expr.location();
2909 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
2910 if !within(self.params.range, expr_range) {
2911 ast::visit::visit_typed_expr(self, expr);
2912 return;
2913 }
2914
2915 // If the expression is a top level statement we don't want to extract
2916 // it into a variable. It would mean we would turn this:
2917 //
2918 // ```gleam
2919 // pub fn main() {
2920 // let wibble = 1
2921 // // ^ cursor here
2922 // }
2923 //
2924 // // into:
2925 //
2926 // pub fn main() {
2927 // let int = 1
2928 // let wibble = int
2929 // }
2930 // ```
2931 //
2932 // Not all that useful!
2933 //
2934 match self.position {
2935 Some(
2936 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
2937 ) => {
2938 self.at_optional_position(None, |this| {
2939 ast::visit::visit_typed_expr(this, expr);
2940 });
2941 return;
2942 }
2943 Some(
2944 ExtractVariablePosition::InsideCaptureBody
2945 | ExtractVariablePosition::InsideCaseClause
2946 | ExtractVariablePosition::CallArg,
2947 )
2948 | None => {}
2949 }
2950
2951 match expr {
2952 TypedExpr::Fn {
2953 kind: FunctionLiteralKind::Anonymous { .. },
2954 ..
2955 } => {
2956 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
2957 ast::visit::visit_typed_expr(this, expr);
2958 });
2959 return;
2960 }
2961
2962 // Expressions that don't make sense to extract
2963 TypedExpr::Panic { .. }
2964 | TypedExpr::Echo { .. }
2965 | TypedExpr::Block { .. }
2966 | TypedExpr::ModuleSelect { .. }
2967 | TypedExpr::Invalid { .. }
2968 | TypedExpr::Var { .. } => (),
2969
2970 TypedExpr::Int { location, .. }
2971 | TypedExpr::Float { location, .. }
2972 | TypedExpr::String { location, .. }
2973 | TypedExpr::Pipeline { location, .. }
2974 | TypedExpr::Fn { location, .. }
2975 | TypedExpr::Todo { location, .. }
2976 | TypedExpr::List { location, .. }
2977 | TypedExpr::Call { location, .. }
2978 | TypedExpr::BinOp { location, .. }
2979 | TypedExpr::Case { location, .. }
2980 | TypedExpr::RecordAccess { location, .. }
2981 | TypedExpr::Tuple { location, .. }
2982 | TypedExpr::TupleIndex { location, .. }
2983 | TypedExpr::BitArray { location, .. }
2984 | TypedExpr::RecordUpdate { location, .. }
2985 | TypedExpr::NegateBool { location, .. }
2986 | TypedExpr::NegateInt { location, .. } => {
2987 if let Some(ExtractVariablePosition::CallArg) = self.position {
2988 // Don't update latest statement, we don't want to insert the extracted
2989 // variable inside the parenthesis where the call argument is located.
2990 } else {
2991 self.statement_before_selected_expression = self.latest_statement;
2992 };
2993 self.selected_expression = Some((*location, expr.type_()));
2994 }
2995 }
2996
2997 ast::visit::visit_typed_expr(self, expr);
2998 }
2999
3000 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3001 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3002 if !within(self.params.range, range) {
3003 ast::visit::visit_typed_use(self, use_);
3004 return;
3005 }
3006
3007 // Insert code under the `use`
3008 self.statement_before_selected_expression = Some(use_.call.location());
3009 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3010 ast::visit::visit_typed_use(this, use_);
3011 });
3012 }
3013
3014 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3015 let range = self.edits.src_span_to_lsp_range(clause.location());
3016 if !within(self.params.range, range) {
3017 ast::visit::visit_typed_clause(self, clause);
3018 return;
3019 }
3020
3021 // Insert code after the `->`
3022 self.latest_statement = Some(clause.then.location());
3023 self.to_be_wrapped = true;
3024 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3025 ast::visit::visit_typed_clause(this, clause);
3026 });
3027 }
3028
3029 fn visit_typed_expr_block(
3030 &mut self,
3031 location: &'ast SrcSpan,
3032 statements: &'ast [TypedStatement],
3033 ) {
3034 let range = self.edits.src_span_to_lsp_range(*location);
3035 if !within(self.params.range, range) {
3036 ast::visit::visit_typed_expr_block(self, location, statements);
3037 return;
3038 }
3039
3040 // Don't extract block as variable
3041 let mut position = self.position;
3042 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3043 position = None;
3044 self.to_be_wrapped = false;
3045 }
3046
3047 self.at_optional_position(position, |this| {
3048 ast::visit::visit_typed_expr_block(this, location, statements);
3049 });
3050 }
3051
3052 fn visit_typed_expr_fn(
3053 &mut self,
3054 location: &'ast SrcSpan,
3055 type_: &'ast Arc<Type>,
3056 kind: &'ast FunctionLiteralKind,
3057 arguments: &'ast [TypedArg],
3058 body: &'ast Vec1<TypedStatement>,
3059 return_annotation: &'ast Option<ast::TypeAst>,
3060 ) {
3061 let range = self.edits.src_span_to_lsp_range(*location);
3062 if !within(self.params.range, range) {
3063 ast::visit::visit_typed_expr_fn(
3064 self,
3065 location,
3066 type_,
3067 kind,
3068 arguments,
3069 body,
3070 return_annotation,
3071 );
3072 return;
3073 }
3074
3075 let position = match kind {
3076 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3077 // just a single expression statement. When visiting we must record
3078 // we're inside a capture body.
3079 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3080 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3081 FunctionLiteralKind::Anonymous { .. } => self.position,
3082 };
3083
3084 self.at_optional_position(position, |this| {
3085 ast::visit::visit_typed_expr_fn(
3086 this,
3087 location,
3088 type_,
3089 kind,
3090 arguments,
3091 body,
3092 return_annotation,
3093 );
3094 });
3095 }
3096
3097 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3098 let range = self.edits.src_span_to_lsp_range(arg.location);
3099 if !within(self.params.range, range) {
3100 ast::visit::visit_typed_call_arg(self, arg);
3101 return;
3102 }
3103
3104 // An implicit record update arg in inserted by the compiler, we don't
3105 // want folks to interact with this since it doesn't translate to
3106 // anything in the source code despite having a default position.
3107 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit {
3108 return;
3109 }
3110
3111 let position = if arg.is_use_implicit_callback() {
3112 Some(ExtractVariablePosition::TopLevelStatement)
3113 } else {
3114 Some(ExtractVariablePosition::CallArg)
3115 };
3116
3117 self.at_optional_position(position, |this| {
3118 ast::visit::visit_typed_call_arg(this, arg);
3119 });
3120 }
3121
3122 // We don't want to offer the action if the cursor is over some invalid
3123 // piece of code.
3124 fn visit_typed_expr_invalid(
3125 &mut self,
3126 location: &'ast SrcSpan,
3127 _type_: &'ast Arc<Type>,
3128 _extra_information: &'ast Option<InvalidExpression>,
3129 ) {
3130 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3131 if within(self.params.range, invalid_range) {
3132 self.selected_expression = None;
3133 }
3134 }
3135}
3136
3137/// Builder for code action to convert a literal use into a const.
3138///
3139/// For using the code action on each of the following lines:
3140///
3141/// ```gleam
3142/// fn void() {
3143/// let var = [1, 2, 3]
3144/// let res = function("Statement", var)
3145/// }
3146/// ```
3147///
3148/// Both value literals will become:
3149///
3150/// ```gleam
3151/// const var = [1, 2, 3]
3152/// const string = "Statement"
3153///
3154/// fn void() {
3155/// let res = function(string, var)
3156/// }
3157/// ```
3158pub struct ExtractConstant<'a> {
3159 module: &'a Module,
3160 params: &'a CodeActionParams,
3161 edits: TextEdits<'a>,
3162 /// The whole selected expression
3163 selected_expression: Option<SrcSpan>,
3164 /// The location of the start of the function containing the expression
3165 container_function_start: Option<u32>,
3166 /// The variant of the extractable expression being extracted (if any)
3167 variant_of_extractable: Option<ExtractableToConstant>,
3168 /// The name of the newly created constant
3169 name_to_use: Option<EcoString>,
3170 /// The right hand side expression of the newly created constant
3171 value_to_use: Option<EcoString>,
3172}
3173
3174/// Used when an expression can be extracted to a constant
3175enum ExtractableToConstant {
3176 /// Used for collections and operator uses. This means that elements
3177 /// inside, are also extractable as constants.
3178 ComposedValue,
3179 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
3180 /// and type variants (not records).
3181 SingleValue,
3182 /// Used for whole variable assignments. If the right hand side of the
3183 /// expression can be extracted, the whole expression extracted and use the
3184 /// local variable as a constant.
3185 Assignment,
3186}
3187
3188fn can_be_constant(
3189 module: &Module,
3190 expr: &TypedExpr,
3191 module_constants: Option<&HashSet<&EcoString>>,
3192) -> bool {
3193 // We pass the `module_constants` on recursion to not compute them each time
3194 let mc = match module_constants {
3195 None => &module
3196 .ast
3197 .definitions
3198 .iter()
3199 .filter_map(|definition| match definition {
3200 ast::Definition::ModuleConstant(module_constant) => Some(&module_constant.name),
3201
3202 ast::Definition::Function(_)
3203 | ast::Definition::TypeAlias(_)
3204 | ast::Definition::CustomType(_)
3205 | ast::Definition::Import(_) => None,
3206 })
3207 .collect(),
3208 Some(mc) => mc,
3209 };
3210
3211 match expr {
3212 // Attempt to extract whole list as long as it's comprised of only literals
3213 TypedExpr::List { elements, tail, .. } => {
3214 elements
3215 .iter()
3216 .all(|element| can_be_constant(module, element, Some(mc)))
3217 && tail.is_none()
3218 }
3219
3220 // Attempt to extract whole bit array as long as it's made up of literals
3221 TypedExpr::BitArray { segments, .. } => {
3222 segments
3223 .iter()
3224 .all(|segment| can_be_constant(module, &segment.value, Some(mc)))
3225 && segments.iter().all(|segment| {
3226 segment.options.iter().all(|option| match option {
3227 ast::BitArrayOption::Size { value, .. } => {
3228 can_be_constant(module, value, Some(mc))
3229 }
3230
3231 ast::BitArrayOption::Bytes { .. }
3232 | ast::BitArrayOption::Int { .. }
3233 | ast::BitArrayOption::Float { .. }
3234 | ast::BitArrayOption::Bits { .. }
3235 | ast::BitArrayOption::Utf8 { .. }
3236 | ast::BitArrayOption::Utf16 { .. }
3237 | ast::BitArrayOption::Utf32 { .. }
3238 | ast::BitArrayOption::Utf8Codepoint { .. }
3239 | ast::BitArrayOption::Utf16Codepoint { .. }
3240 | ast::BitArrayOption::Utf32Codepoint { .. }
3241 | ast::BitArrayOption::Signed { .. }
3242 | ast::BitArrayOption::Unsigned { .. }
3243 | ast::BitArrayOption::Big { .. }
3244 | ast::BitArrayOption::Little { .. }
3245 | ast::BitArrayOption::Native { .. }
3246 | ast::BitArrayOption::Unit { .. } => true,
3247 })
3248 })
3249 }
3250
3251 // Attempt to extract whole tuple as long as it's comprised of only literals
3252 TypedExpr::Tuple { elements, .. } => elements
3253 .iter()
3254 .all(|element| can_be_constant(module, element, Some(mc))),
3255
3256 // Extract literals directly
3257 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
3258
3259 // Extract non-record types directly
3260 TypedExpr::Var {
3261 constructor, name, ..
3262 } => {
3263 matches!(
3264 constructor.variant,
3265 type_::ValueConstructorVariant::Record { arity: 0, .. }
3266 ) || mc.contains(name)
3267 }
3268
3269 // Extract record types as long as arguments can be constant
3270 TypedExpr::Call { arguments, fun, .. } => {
3271 fun.is_record_builder()
3272 && arguments
3273 .iter()
3274 .all(|arg| can_be_constant(module, &arg.value, module_constants))
3275 }
3276
3277 // Extract concat binary operation if both sides can be constants
3278 TypedExpr::BinOp {
3279 name, left, right, ..
3280 } => {
3281 matches!(name, ast::BinOp::Concatenate)
3282 && can_be_constant(module, left, Some(mc))
3283 && can_be_constant(module, right, Some(mc))
3284 }
3285
3286 TypedExpr::Block { .. }
3287 | TypedExpr::Pipeline { .. }
3288 | TypedExpr::Fn { .. }
3289 | TypedExpr::Case { .. }
3290 | TypedExpr::RecordAccess { .. }
3291 | TypedExpr::ModuleSelect { .. }
3292 | TypedExpr::TupleIndex { .. }
3293 | TypedExpr::Todo { .. }
3294 | TypedExpr::Panic { .. }
3295 | TypedExpr::Echo { .. }
3296 | TypedExpr::RecordUpdate { .. }
3297 | TypedExpr::NegateBool { .. }
3298 | TypedExpr::NegateInt { .. }
3299 | TypedExpr::Invalid { .. } => false,
3300 }
3301}
3302
3303/// Takes the list of already existing constants and functions and creates a
3304/// name that doesn't conflict with them
3305///
3306fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
3307 let mut name_generator = NameGenerator::new();
3308 let already_taken_names = VariablesNames {
3309 names: module
3310 .ast
3311 .definitions
3312 .iter()
3313 .filter_map(|definition| match definition {
3314 ast::Definition::ModuleConstant(constant) => Some(constant.name.clone()),
3315 ast::Definition::Function(function) => function.name.as_ref().map(|n| n.1.clone()),
3316
3317 ast::Definition::TypeAlias(_)
3318 | ast::Definition::CustomType(_)
3319 | ast::Definition::Import(_) => None,
3320 })
3321 .collect(),
3322 };
3323 name_generator.reserve_variable_names(already_taken_names);
3324
3325 name_generator.generate_name_from_type(&expr.type_())
3326}
3327
3328impl<'a> ExtractConstant<'a> {
3329 pub fn new(
3330 module: &'a Module,
3331 line_numbers: &'a LineNumbers,
3332 params: &'a CodeActionParams,
3333 ) -> Self {
3334 Self {
3335 module,
3336 params,
3337 edits: TextEdits::new(line_numbers),
3338 selected_expression: None,
3339 container_function_start: None,
3340 variant_of_extractable: None,
3341 name_to_use: None,
3342 value_to_use: None,
3343 }
3344 }
3345
3346 pub fn code_actions(mut self) -> Vec<CodeAction> {
3347 self.visit_typed_module(&self.module.ast);
3348
3349 let (
3350 Some(expr_span),
3351 Some(function_start),
3352 Some(type_of_extractable),
3353 Some(new_const_name),
3354 Some(const_value),
3355 ) = (
3356 self.selected_expression,
3357 self.container_function_start,
3358 self.variant_of_extractable,
3359 self.name_to_use,
3360 self.value_to_use,
3361 )
3362 else {
3363 return vec![];
3364 };
3365
3366 // We insert the constant declaration
3367 self.edits.insert(
3368 function_start,
3369 format!("const {new_const_name} = {const_value}\n\n"),
3370 );
3371
3372 // We remove or replace the selected expression
3373 match type_of_extractable {
3374 // The whole expression is deleted for assignments
3375 ExtractableToConstant::Assignment => {
3376 let range = self
3377 .edits
3378 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
3379
3380 let indent_size =
3381 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3382
3383 let expr_span_with_new_line = SrcSpan {
3384 // We remove leading indentation + 1 to remove the newline with it
3385 start: expr_span.start - (indent_size as u32 + 1),
3386 end: expr_span.end,
3387 };
3388 self.edits.delete(expr_span_with_new_line);
3389 }
3390
3391 // Only right hand side is replaced for collection or values
3392 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
3393 self.edits.replace(expr_span, String::from(new_const_name));
3394 }
3395 }
3396
3397 let mut action = Vec::with_capacity(1);
3398 CodeActionBuilder::new("Extract constant")
3399 .kind(CodeActionKind::REFACTOR_EXTRACT)
3400 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3401 .preferred(false)
3402 .push_to(&mut action);
3403 action
3404 }
3405}
3406
3407impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
3408 /// To get the position of the function containing the value or assignment
3409 /// to extract
3410 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
3411 let fun_location = fun.location;
3412 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3413 start: fun_location.start,
3414 end: fun.end_position,
3415 });
3416
3417 if !within(self.params.range, fun_range) {
3418 return;
3419 }
3420 self.container_function_start = Some(fun.location.start);
3421
3422 ast::visit::visit_typed_function(self, fun);
3423 }
3424
3425 /// To extract the whole assignment
3426 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3427 let expr_location = assignment.location;
3428
3429 // We only offer this code action for extracting the whole assignment
3430 // between `let` and `=`.
3431 let pattern_location = assignment.pattern.location();
3432 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
3433 let code_action_range = self.edits.src_span_to_lsp_range(location);
3434
3435 if !within(self.params.range, code_action_range) {
3436 ast::visit::visit_typed_assignment(self, assignment);
3437 return;
3438 }
3439
3440 // Has to be variable because patterns can't be constants.
3441 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
3442 {
3443 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
3444 self.selected_expression = Some(expr_location);
3445 self.name_to_use = match &assignment.pattern {
3446 Pattern::Variable { name, .. } => Some(name.clone()),
3447 _ => None,
3448 };
3449 self.value_to_use = Some(EcoString::from(
3450 self.module
3451 .code
3452 .get(
3453 (assignment.value.location().start as usize)
3454 ..(assignment.location.end as usize),
3455 )
3456 .expect("selected expression"),
3457 ));
3458 }
3459 }
3460
3461 /// To extract only the literal
3462 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3463 let expr_location = expr.location();
3464 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3465
3466 if !within(self.params.range, expr_range) {
3467 ast::visit::visit_typed_expr(self, expr);
3468 return;
3469 }
3470
3471 // Keep going down recursively if:
3472 // - It's no extractable has been found yet (`None`).
3473 // - It's a collection, which may or may not contain a value that can
3474 // be extracted.
3475 // - It's a binary operator, which may or may not operate on
3476 // extractable values.
3477 if matches!(
3478 self.variant_of_extractable,
3479 None | Some(ExtractableToConstant::ComposedValue)
3480 ) && can_be_constant(self.module, expr, None)
3481 {
3482 self.variant_of_extractable = match expr {
3483 TypedExpr::Var { .. }
3484 | TypedExpr::Int { .. }
3485 | TypedExpr::Float { .. }
3486 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
3487
3488 TypedExpr::List { .. }
3489 | TypedExpr::Tuple { .. }
3490 | TypedExpr::BitArray { .. }
3491 | TypedExpr::BinOp { .. }
3492 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
3493
3494 TypedExpr::Block { .. }
3495 | TypedExpr::Pipeline { .. }
3496 | TypedExpr::Fn { .. }
3497 | TypedExpr::Case { .. }
3498 | TypedExpr::RecordAccess { .. }
3499 | TypedExpr::ModuleSelect { .. }
3500 | TypedExpr::TupleIndex { .. }
3501 | TypedExpr::Todo { .. }
3502 | TypedExpr::Panic { .. }
3503 | TypedExpr::Echo { .. }
3504 | TypedExpr::RecordUpdate { .. }
3505 | TypedExpr::NegateBool { .. }
3506 | TypedExpr::NegateInt { .. }
3507 | TypedExpr::Invalid { .. } => None,
3508 };
3509
3510 self.selected_expression = Some(expr_location);
3511 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
3512 self.value_to_use = Some(EcoString::from(
3513 self.module
3514 .code
3515 .get((expr_location.start as usize)..(expr_location.end as usize))
3516 .expect("selected expression"),
3517 ));
3518 }
3519
3520 ast::visit::visit_typed_expr(self, expr);
3521 }
3522}
3523
3524/// Builder for code action to apply the "expand function capture" action.
3525///
3526pub struct ExpandFunctionCapture<'a> {
3527 module: &'a Module,
3528 params: &'a CodeActionParams,
3529 edits: TextEdits<'a>,
3530 function_capture_data: Option<FunctionCaptureData>,
3531}
3532
3533pub struct FunctionCaptureData {
3534 function_span: SrcSpan,
3535 hole_span: SrcSpan,
3536 hole_type: Arc<Type>,
3537 reserved_names: VariablesNames,
3538}
3539
3540impl<'a> ExpandFunctionCapture<'a> {
3541 pub fn new(
3542 module: &'a Module,
3543 line_numbers: &'a LineNumbers,
3544 params: &'a CodeActionParams,
3545 ) -> Self {
3546 Self {
3547 module,
3548 params,
3549 edits: TextEdits::new(line_numbers),
3550 function_capture_data: None,
3551 }
3552 }
3553
3554 pub fn code_actions(mut self) -> Vec<CodeAction> {
3555 self.visit_typed_module(&self.module.ast);
3556
3557 let Some(FunctionCaptureData {
3558 function_span,
3559 hole_span,
3560 hole_type,
3561 reserved_names,
3562 }) = self.function_capture_data
3563 else {
3564 return vec![];
3565 };
3566
3567 let mut name_generator = NameGenerator::new();
3568 name_generator.reserve_variable_names(reserved_names);
3569 let name = name_generator.generate_name_from_type(&hole_type);
3570
3571 self.edits.replace(hole_span, name.clone().into());
3572 self.edits.insert(function_span.end, " }".into());
3573 self.edits
3574 .insert(function_span.start, format!("fn({name}) {{ "));
3575
3576 let mut action = Vec::with_capacity(1);
3577 CodeActionBuilder::new("Expand function capture")
3578 .kind(CodeActionKind::REFACTOR_REWRITE)
3579 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3580 .preferred(false)
3581 .push_to(&mut action);
3582 action
3583 }
3584}
3585
3586impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
3587 fn visit_typed_expr_fn(
3588 &mut self,
3589 location: &'ast SrcSpan,
3590 type_: &'ast Arc<Type>,
3591 kind: &'ast FunctionLiteralKind,
3592 arguments: &'ast [TypedArg],
3593 body: &'ast Vec1<TypedStatement>,
3594 return_annotation: &'ast Option<ast::TypeAst>,
3595 ) {
3596 let fn_range = self.edits.src_span_to_lsp_range(*location);
3597 if within(self.params.range, fn_range)
3598 && kind.is_capture()
3599 && let [argument] = arguments
3600 {
3601 self.function_capture_data = Some(FunctionCaptureData {
3602 function_span: *location,
3603 hole_span: argument.location,
3604 hole_type: argument.type_.clone(),
3605 reserved_names: VariablesNames::from_statements(body),
3606 });
3607 }
3608
3609 ast::visit::visit_typed_expr_fn(
3610 self,
3611 location,
3612 type_,
3613 kind,
3614 arguments,
3615 body,
3616 return_annotation,
3617 )
3618 }
3619}
3620
3621struct VariablesNames {
3622 names: HashSet<EcoString>,
3623}
3624
3625impl VariablesNames {
3626 fn from_statements(statements: &[TypedStatement]) -> Self {
3627 let mut variables = Self {
3628 names: HashSet::new(),
3629 };
3630
3631 for statement in statements {
3632 variables.visit_typed_statement(statement);
3633 }
3634 variables
3635 }
3636}
3637
3638impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
3639 fn visit_typed_expr_var(
3640 &mut self,
3641 _location: &'ast SrcSpan,
3642 _constructor: &'ast ValueConstructor,
3643 name: &'ast EcoString,
3644 ) {
3645 let _ = self.names.insert(name.clone());
3646 }
3647}
3648
3649/// Builder for code action to apply the "generate dynamic decoder action.
3650///
3651pub struct GenerateDynamicDecoder<'a> {
3652 module: &'a Module,
3653 params: &'a CodeActionParams,
3654 edits: TextEdits<'a>,
3655 printer: Printer<'a>,
3656 actions: &'a mut Vec<CodeAction>,
3657}
3658
3659const DECODE_MODULE: &str = "gleam/dynamic/decode";
3660
3661impl<'a> GenerateDynamicDecoder<'a> {
3662 pub fn new(
3663 module: &'a Module,
3664 line_numbers: &'a LineNumbers,
3665 params: &'a CodeActionParams,
3666 actions: &'a mut Vec<CodeAction>,
3667 ) -> Self {
3668 // Since we are generating a new function, type variables from other
3669 // functions and constants are irrelevant to the types we print.
3670 let printer = Printer::new_without_type_variables(&module.ast.names);
3671 Self {
3672 module,
3673 params,
3674 edits: TextEdits::new(line_numbers),
3675 printer,
3676 actions,
3677 }
3678 }
3679
3680 pub fn code_actions(&mut self) {
3681 self.visit_typed_module(&self.module.ast);
3682 }
3683
3684 fn custom_type_decoder_body(
3685 &mut self,
3686 custom_type: &CustomType<Arc<Type>>,
3687 ) -> Option<EcoString> {
3688 // We cannot generate a decoder for an external type with no constructors!
3689 let constructors_size = custom_type.constructors.len();
3690 let (first, rest) = custom_type.constructors.split_first()?;
3691 let mode = EncodingMode::for_custom_type(custom_type);
3692
3693 // We generate a decoder for a type with a single constructor: it does not
3694 // require pattern matching on a tag as there's no variants to tell apart.
3695 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
3696 return self.constructor_decoder(mode, custom_type, first, 0);
3697 }
3698
3699 // Otherwise we need to generate a decoder that has to tell apart different
3700 // variants, depending on the mode we might have to decode a type field or
3701 // plain strings!
3702 let module = self.printer.print_module(DECODE_MODULE);
3703 let discriminant = if mode == EncodingMode::PlainString {
3704 eco_format!("use variant <- {module}.then({module}.string)")
3705 } else {
3706 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
3707 };
3708
3709 let mut branches = Vec::with_capacity(constructors_size);
3710 for constructor in iter::once(first).chain(rest) {
3711 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
3712 let name = to_snake_case(&constructor.name);
3713 branches.push(eco_format!(r#" "{name}" -> {body}"#));
3714 }
3715
3716 let cases = branches.join("\n");
3717 let type_name = &custom_type.name;
3718 Some(eco_format!(
3719 r#"{{
3720 {discriminant}
3721 case variant {{
3722{cases}
3723 _ -> {module}.failure(todo as "Zero value for {type_name}", "{type_name}")
3724 }}
3725}}"#,
3726 ))
3727 }
3728
3729 fn constructor_decoder(
3730 &mut self,
3731 mode: EncodingMode,
3732 custom_type: &ast::TypedCustomType,
3733 constructor: &TypedRecordConstructor,
3734 nesting: usize,
3735 ) -> Option<EcoString> {
3736 let decode_module = self.printer.print_module(DECODE_MODULE);
3737 let constructor_name = &constructor.name;
3738
3739 // If the constructor was encoded as a plain string with no additional
3740 // fields it means there's nothing else to decode and we can just
3741 // succeed.
3742 if mode == EncodingMode::PlainString {
3743 return Some(eco_format!("{decode_module}.success({constructor_name})"));
3744 }
3745
3746 // Otherwise we have to decode all the constructor fields to build it.
3747 let mut fields = Vec::with_capacity(constructor.arguments.len());
3748 for argument in constructor.arguments.iter() {
3749 let (_, name) = argument.label.as_ref()?;
3750 let field = RecordField {
3751 label: RecordLabel::Labeled(name),
3752 type_: &argument.type_,
3753 };
3754 fields.push(field);
3755 }
3756
3757 let mut decoder_printer = DecoderPrinter::new(
3758 &mut self.printer,
3759 custom_type.name.clone(),
3760 self.module.name.clone(),
3761 );
3762
3763 let decoders = fields
3764 .iter()
3765 .map(|field| decoder_printer.decode_field(field, nesting + 2))
3766 .join("\n");
3767
3768 let indent = " ".repeat(nesting);
3769
3770 Some(if decoders.is_empty() {
3771 eco_format!("{decode_module}.success({constructor_name})")
3772 } else {
3773 let field_names = fields
3774 .iter()
3775 .map(|field| format!("{}:", field.label.variable_name()))
3776 .join(", ");
3777
3778 eco_format!(
3779 "{{
3780{decoders}
3781{indent} {decode_module}.success({constructor_name}({field_names}))
3782{indent}}}",
3783 )
3784 })
3785 }
3786}
3787
3788impl<'ast> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast> {
3789 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
3790 let range = self.edits.src_span_to_lsp_range(custom_type.location);
3791 if !overlaps(self.params.range, range) {
3792 return;
3793 }
3794
3795 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
3796 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
3797 return;
3798 };
3799
3800 let parameters = match custom_type.parameters.len() {
3801 0 => EcoString::new(),
3802 _ => eco_format!(
3803 "({})",
3804 custom_type
3805 .parameters
3806 .iter()
3807 .map(|(_, name)| name)
3808 .join(", ")
3809 ),
3810 };
3811
3812 let decoder_type = self.printer.print_type(&Type::Named {
3813 publicity: Publicity::Public,
3814 package: STDLIB_PACKAGE_NAME.into(),
3815 module: DECODE_MODULE.into(),
3816 name: "Decoder".into(),
3817 arguments: vec![],
3818 inferred_variant: None,
3819 });
3820
3821 let function = format!(
3822 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
3823 type_name = custom_type.name,
3824 );
3825
3826 self.edits.insert(custom_type.end_position, function);
3827 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
3828
3829 CodeActionBuilder::new("Generate dynamic decoder")
3830 .kind(CodeActionKind::REFACTOR)
3831 .preferred(false)
3832 .changes(
3833 self.params.text_document.uri.clone(),
3834 std::mem::take(&mut self.edits.edits),
3835 )
3836 .push_to(self.actions);
3837 }
3838}
3839
3840/// If `module_name` is not already imported inside `module`, adds an edit to
3841/// add that import.
3842/// This function also makes sure not to import a module in itself.
3843///
3844fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
3845 if module.ast.names.is_imported(module_name) || module.name == module_name {
3846 return;
3847 }
3848
3849 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
3850 let first_is_import = first_import_pos.is_some();
3851 let import_location = first_import_pos.unwrap_or_default();
3852 let after_import_newlines = add_newlines_after_import(
3853 import_location,
3854 first_is_import,
3855 edits.line_numbers,
3856 &module.code,
3857 );
3858
3859 edits.edits.push(get_import_edit(
3860 import_location,
3861 module_name,
3862 &after_import_newlines,
3863 ));
3864}
3865
3866struct DecoderPrinter<'a, 'b> {
3867 printer: &'a mut Printer<'b>,
3868 /// The name of the root type we are printing a decoder for
3869 type_name: EcoString,
3870 /// The module name of the root type we are printing a decoder for
3871 type_module: EcoString,
3872}
3873
3874struct RecordField<'a> {
3875 label: RecordLabel<'a>,
3876 type_: &'a Type,
3877}
3878
3879enum RecordLabel<'a> {
3880 Labeled(&'a str),
3881 Unlabeled(usize),
3882}
3883
3884impl RecordLabel<'_> {
3885 fn field_key(&self) -> EcoString {
3886 match self {
3887 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
3888 RecordLabel::Unlabeled(index) => {
3889 eco_format!("{index}")
3890 }
3891 }
3892 }
3893
3894 fn variable_name(&self) -> EcoString {
3895 match self {
3896 RecordLabel::Labeled(label) => (*label).into(),
3897 &RecordLabel::Unlabeled(mut index) => {
3898 let mut characters = Vec::new();
3899 let alphabet_length = 26;
3900 let alphabet_offset = b'a';
3901 loop {
3902 let alphabet_index = (index % alphabet_length) as u8;
3903 characters.push((alphabet_offset + alphabet_index) as char);
3904 index /= alphabet_length;
3905
3906 if index == 0 {
3907 break;
3908 }
3909 index -= 1;
3910 }
3911 characters.into_iter().rev().collect()
3912 }
3913 }
3914 }
3915}
3916
3917impl<'a, 'b> DecoderPrinter<'a, 'b> {
3918 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self {
3919 Self {
3920 type_name,
3921 type_module,
3922 printer,
3923 }
3924 }
3925
3926 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
3927 let module_name = self.printer.print_module(DECODE_MODULE);
3928 if type_.is_bit_array() {
3929 eco_format!("{module_name}.bit_array")
3930 } else if type_.is_bool() {
3931 eco_format!("{module_name}.bool")
3932 } else if type_.is_float() {
3933 eco_format!("{module_name}.float")
3934 } else if type_.is_int() {
3935 eco_format!("{module_name}.int")
3936 } else if type_.is_string() {
3937 eco_format!("{module_name}.string")
3938 } else {
3939 match type_.tuple_types() {
3940 Some(types) => {
3941 let fields = types
3942 .iter()
3943 .enumerate()
3944 .map(|(index, type_)| RecordField {
3945 type_,
3946 label: RecordLabel::Unlabeled(index),
3947 })
3948 .collect_vec();
3949 let decoders = fields
3950 .iter()
3951 .map(|field| self.decode_field(field, indent + 2))
3952 .join("\n");
3953 let mut field_names = fields.iter().map(|field| field.label.variable_name());
3954
3955 eco_format!(
3956 "{{
3957{decoders}
3958
3959{indent} {module_name}.success(#({fields}))
3960{indent}}}",
3961 fields = field_names.join(", "),
3962 indent = " ".repeat(indent)
3963 )
3964 }
3965 _ => {
3966 let type_information = type_.named_type_information();
3967 let type_information =
3968 type_information.as_ref().map(|(module, name, arguments)| {
3969 (module.as_str(), name.as_str(), arguments.as_slice())
3970 });
3971
3972 match type_information {
3973 Some(("gleam/dynamic", "Dynamic", _)) => {
3974 eco_format!("{module_name}.dynamic")
3975 }
3976 Some(("gleam", "List", [element])) => {
3977 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
3978 }
3979 Some(("gleam/option", "Option", [some])) => {
3980 eco_format!(
3981 "{module_name}.optional({})",
3982 self.decoder_for(some, indent)
3983 )
3984 }
3985 Some(("gleam/dict", "Dict", [key, value])) => {
3986 eco_format!(
3987 "{module_name}.dict({}, {})",
3988 self.decoder_for(key, indent),
3989 self.decoder_for(value, indent)
3990 )
3991 }
3992 Some((module, name, _))
3993 if module == self.type_module && name == self.type_name =>
3994 {
3995 eco_format!("{}_decoder()", to_snake_case(name))
3996 }
3997 _ => eco_format!(
3998 r#"todo as "Decoder for {}""#,
3999 self.printer.print_type(type_)
4000 ),
4001 }
4002 }
4003 }
4004 }
4005 }
4006
4007 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4008 let decoder = self.decoder_for(field.type_, indent);
4009
4010 eco_format!(
4011 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4012 indent = " ".repeat(indent),
4013 variable = field.label.variable_name(),
4014 field = field.label.field_key(),
4015 module = self.printer.print_module(DECODE_MODULE)
4016 )
4017 }
4018}
4019
4020/// Builder for code action to apply the "Generate to-JSON function" action.
4021///
4022pub struct GenerateJsonEncoder<'a> {
4023 module: &'a Module,
4024 params: &'a CodeActionParams,
4025 edits: TextEdits<'a>,
4026 printer: Printer<'a>,
4027 actions: &'a mut Vec<CodeAction>,
4028 config: &'a PackageConfig,
4029}
4030
4031const JSON_MODULE: &str = "gleam/json";
4032const JSON_PACKAGE_NAME: &str = "gleam_json";
4033
4034#[derive(Eq, PartialEq, Copy, Clone)]
4035enum EncodingMode {
4036 PlainString,
4037 ObjectWithTypeTag,
4038 ObjectWithNoTypeTag,
4039}
4040
4041impl EncodingMode {
4042 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
4043 match type_.constructors.as_slice() {
4044 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
4045 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
4046 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
4047 EncodingMode::PlainString
4048 }
4049 _constructors => EncodingMode::ObjectWithTypeTag,
4050 }
4051 }
4052}
4053
4054impl<'a> GenerateJsonEncoder<'a> {
4055 pub fn new(
4056 module: &'a Module,
4057 line_numbers: &'a LineNumbers,
4058 params: &'a CodeActionParams,
4059 actions: &'a mut Vec<CodeAction>,
4060 config: &'a PackageConfig,
4061 ) -> Self {
4062 // Since we are generating a new function, type variables from other
4063 // functions and constants are irrelevant to the types we print.
4064 let printer = Printer::new_without_type_variables(&module.ast.names);
4065 Self {
4066 module,
4067 params,
4068 edits: TextEdits::new(line_numbers),
4069 printer,
4070 actions,
4071 config,
4072 }
4073 }
4074
4075 pub fn code_actions(&mut self) {
4076 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
4077 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
4078 {
4079 self.visit_typed_module(&self.module.ast);
4080 }
4081 }
4082
4083 fn custom_type_encoder_body(
4084 &mut self,
4085 record_name: EcoString,
4086 custom_type: &CustomType<Arc<Type>>,
4087 ) -> Option<EcoString> {
4088 // We cannot generate a decoder for an external type with no constructors!
4089 let constructors_size = custom_type.constructors.len();
4090 let (first, rest) = custom_type.constructors.split_first()?;
4091 let mode = EncodingMode::for_custom_type(custom_type);
4092
4093 // We generate an encoder for a type with a single constructor: it does not
4094 // require pattern matching on the argument as we can access all its fields
4095 // with the usual record access syntax.
4096 if rest.is_empty() {
4097 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
4098 let unpacking = if first.arguments.is_empty() {
4099 ""
4100 } else {
4101 &eco_format!(
4102 "let {name}({fields}:) = {record_name}\n ",
4103 name = first.name,
4104 fields = first
4105 .arguments
4106 .iter()
4107 .filter_map(|argument| {
4108 argument.label.as_ref().map(|(_location, label)| label)
4109 })
4110 .join(":, ")
4111 )
4112 };
4113 return Some(eco_format!("{unpacking}{encoder}"));
4114 }
4115
4116 // Otherwise we generate an encoder for a type with multiple constructors:
4117 // it will need to pattern match on the various constructors and encode each
4118 // one separately.
4119 let mut branches = Vec::with_capacity(constructors_size);
4120 for constructor in iter::once(first).chain(rest) {
4121 let RecordConstructor { name, .. } = constructor;
4122 let encoder =
4123 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
4124 let unpacking = if constructor.arguments.is_empty() {
4125 ""
4126 } else {
4127 &eco_format!(
4128 "({}:)",
4129 constructor
4130 .arguments
4131 .iter()
4132 .filter_map(|argument| {
4133 argument.label.as_ref().map(|(_location, label)| label)
4134 })
4135 .join(":, ")
4136 )
4137 };
4138 branches.push(eco_format!(" {name}{unpacking} -> {encoder}"));
4139 }
4140
4141 let branches = branches.join("\n");
4142 Some(eco_format!(
4143 "case {record_name} {{
4144{branches}
4145 }}",
4146 ))
4147 }
4148
4149 fn constructor_encoder(
4150 &mut self,
4151 mode: EncodingMode,
4152 constructor: &TypedRecordConstructor,
4153 type_name: EcoString,
4154 nesting: usize,
4155 ) -> Option<EcoString> {
4156 let json_module = self.printer.print_module(JSON_MODULE);
4157 let tag = to_snake_case(&constructor.name);
4158 let indent = " ".repeat(nesting);
4159
4160 // If the variant is encoded as a simple json string we just call the
4161 // `json.string` with the variant tag as an argument.
4162 if mode == EncodingMode::PlainString {
4163 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
4164 }
4165
4166 // Otherwise we turn it into an object with a `type` tag field.
4167 let mut encoder_printer =
4168 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone());
4169
4170 // These are the fields of the json object to encode.
4171 let mut fields = Vec::with_capacity(constructor.arguments.len());
4172 if mode == EncodingMode::ObjectWithTypeTag {
4173 // Any needed type tag is always going to be the first field in the object
4174 fields.push(eco_format!(
4175 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
4176 ));
4177 }
4178
4179 for argument in constructor.arguments.iter() {
4180 let (_, label) = argument.label.as_ref()?;
4181 let field = RecordField {
4182 label: RecordLabel::Labeled(label),
4183 type_: &argument.type_,
4184 };
4185 let encoder = encoder_printer.encode_field(&field, nesting + 2);
4186 fields.push(encoder);
4187 }
4188
4189 let fields = fields.join(",\n");
4190 Some(eco_format!(
4191 "{json_module}.object([
4192{fields},
4193{indent}])"
4194 ))
4195 }
4196}
4197
4198impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
4199 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4200 let range = self.edits.src_span_to_lsp_range(custom_type.location);
4201 if !overlaps(self.params.range, range) {
4202 return;
4203 }
4204
4205 let record_name = to_snake_case(&custom_type.name);
4206 let name = eco_format!("{record_name}_to_json");
4207 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
4208 return;
4209 };
4210
4211 let json_type = self.printer.print_type(&Type::Named {
4212 publicity: Publicity::Public,
4213 package: JSON_PACKAGE_NAME.into(),
4214 module: JSON_MODULE.into(),
4215 name: "Json".into(),
4216 arguments: vec![],
4217 inferred_variant: None,
4218 });
4219
4220 let type_ = if custom_type.parameters.is_empty() {
4221 custom_type.name.clone()
4222 } else {
4223 let parameters = custom_type
4224 .parameters
4225 .iter()
4226 .map(|(_, name)| name)
4227 .join(", ");
4228 eco_format!("{}({})", custom_type.name, parameters)
4229 };
4230
4231 let function = format!(
4232 "
4233
4234fn {name}({record_name}: {type_}) -> {json_type} {{
4235 {encoder}
4236}}",
4237 );
4238
4239 self.edits.insert(custom_type.end_position, function);
4240 maybe_import(&mut self.edits, self.module, JSON_MODULE);
4241
4242 CodeActionBuilder::new("Generate to-JSON function")
4243 .kind(CodeActionKind::REFACTOR)
4244 .preferred(false)
4245 .changes(
4246 self.params.text_document.uri.clone(),
4247 std::mem::take(&mut self.edits.edits),
4248 )
4249 .push_to(self.actions);
4250 }
4251}
4252
4253struct JsonEncoderPrinter<'a, 'b> {
4254 printer: &'a mut Printer<'b>,
4255 /// The name of the root type we are printing an encoder for
4256 type_name: EcoString,
4257 /// The module name of the root type we are printing an encoder for
4258 type_module: EcoString,
4259}
4260
4261impl<'a, 'b> JsonEncoderPrinter<'a, 'b> {
4262 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self {
4263 Self {
4264 type_name,
4265 type_module,
4266 printer,
4267 }
4268 }
4269
4270 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
4271 let module_name = self.printer.print_module(JSON_MODULE);
4272 let is_capture = encoded_value == "_";
4273 let maybe_capture = |mut function: EcoString| {
4274 if is_capture {
4275 function
4276 } else {
4277 function.push('(');
4278 function.push_str(encoded_value);
4279 function.push(')');
4280 function
4281 }
4282 };
4283
4284 if type_.is_bool() {
4285 maybe_capture(eco_format!("{module_name}.bool"))
4286 } else if type_.is_float() {
4287 maybe_capture(eco_format!("{module_name}.float"))
4288 } else if type_.is_int() {
4289 maybe_capture(eco_format!("{module_name}.int"))
4290 } else if type_.is_string() {
4291 maybe_capture(eco_format!("{module_name}.string"))
4292 } else {
4293 match type_.tuple_types() {
4294 Some(types) => {
4295 let (tuple, new_indent) = if is_capture {
4296 ("value", indent + 4)
4297 } else {
4298 (encoded_value, indent + 2)
4299 };
4300
4301 let encoders = types
4302 .iter()
4303 .enumerate()
4304 .map(|(index, type_)| {
4305 self.encoder_for(&format!("{tuple}.{index}"), type_, new_indent)
4306 })
4307 .collect_vec();
4308
4309 if is_capture {
4310 eco_format!(
4311 "fn(value) {{
4312{indent} {module_name}.preprocessed_array([
4313{indent} {encoders},
4314{indent} ])
4315{indent}}}",
4316 indent = " ".repeat(indent),
4317 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
4318 )
4319 } else {
4320 eco_format!(
4321 "{module_name}.preprocessed_array([
4322{indent} {encoders},
4323{indent}])",
4324 indent = " ".repeat(indent),
4325 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
4326 )
4327 }
4328 }
4329 _ => {
4330 let type_information = type_.named_type_information();
4331 let type_information: Option<(&str, &str, &[Arc<Type>])> =
4332 type_information.as_ref().map(|(module, name, arguments)| {
4333 (module.as_str(), name.as_str(), arguments.as_slice())
4334 });
4335
4336 match type_information {
4337 Some(("gleam", "List", [element])) => {
4338 eco_format!(
4339 "{module_name}.array({encoded_value}, {map_function})",
4340 map_function = self.encoder_for("_", element, indent)
4341 )
4342 }
4343 Some(("gleam/option", "Option", [some])) => {
4344 eco_format!(
4345 "case {encoded_value} {{
4346{indent} {none} -> {module_name}.null()
4347{indent} {some}(value) -> {encoder}
4348{indent}}}",
4349 indent = " ".repeat(indent),
4350 none = self
4351 .printer
4352 .print_constructor(&"gleam/option".into(), &"None".into()),
4353 some = self
4354 .printer
4355 .print_constructor(&"gleam/option".into(), &"Some".into()),
4356 encoder = self.encoder_for("value", some, indent + 2)
4357 )
4358 }
4359 Some(("gleam/dict", "Dict", [key, value])) => {
4360 let stringify_function = match key
4361 .named_type_information()
4362 .as_ref()
4363 .map(|(module, name, arguments)| {
4364 (module.as_str(), name.as_str(), arguments.as_slice())
4365 }) {
4366 Some(("gleam", "String", [])) => "fn(string) { string }",
4367 _ => &format!(
4368 r#"todo as "Function to stringify {}""#,
4369 self.printer.print_type(key)
4370 ),
4371 };
4372 eco_format!(
4373 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
4374 self.encoder_for("_", value, indent)
4375 )
4376 }
4377 Some((module, name, _))
4378 if module == self.type_module && name == self.type_name =>
4379 {
4380 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
4381 }
4382 _ => eco_format!(
4383 r#"todo as "Encoder for {}""#,
4384 self.printer.print_type(type_)
4385 ),
4386 }
4387 }
4388 }
4389 }
4390 }
4391
4392 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4393 let field_name = field.label.variable_name();
4394 let encoder = self.encoder_for(&field_name, field.type_, indent);
4395
4396 eco_format!(
4397 r#"{indent}#("{field_name}", {encoder})"#,
4398 indent = " ".repeat(indent),
4399 )
4400 }
4401}
4402
4403/// Builder for code action to pattern match on things like (anonymous) function
4404/// arguments or variables.
4405/// For example:
4406///
4407/// ```gleam
4408/// pub fn wibble(arg: #(key, value)) {
4409/// // ^ [pattern match on argument]
4410/// }
4411///
4412/// // Generates
4413///
4414/// pub fn wibble(arg: #(key, value)) {
4415/// let #(value_0, value_1) = arg
4416/// }
4417/// ```
4418///
4419/// Another example with variables:
4420///
4421/// ```gleam
4422/// pub fn main() {
4423/// let pair = #(1, 3)
4424/// // ^ [pattern match on value]
4425/// }
4426///
4427/// // Generates
4428///
4429/// pub fn main() {
4430/// let pair = #(1, 3)
4431/// let #(value_0, value_1) = pair
4432/// }
4433/// ```
4434///
4435pub struct PatternMatchOnValue<'a, A> {
4436 module: &'a Module,
4437 params: &'a CodeActionParams,
4438 compiler: &'a LspProjectCompiler<A>,
4439 pattern_variable_under_cursor: Option<(&'a EcoString, SrcSpan, Arc<Type>)>,
4440 selected_value: Option<PatternMatchedValue<'a>>,
4441 edits: TextEdits<'a>,
4442}
4443
4444/// A value we might want to pattern match on.
4445/// Each variant will also contain all the info needed to know how to properly
4446/// print and format the corresponding pattern matching code; that's why you'll
4447/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
4448///
4449#[derive(Clone)]
4450pub enum PatternMatchedValue<'a> {
4451 FunctionArgument {
4452 /// The argument being pattern matched on.
4453 ///
4454 arg: &'a TypedArg,
4455 /// The first statement inside the function body. Used to correctly
4456 /// position the inserted pattern matching.
4457 ///
4458 first_statement: &'a TypedStatement,
4459 /// The range of the entire function holding the argument.
4460 ///
4461 function_range: Range,
4462 },
4463 LetVariable {
4464 variable_name: &'a EcoString,
4465 variable_type: Arc<Type>,
4466 /// The location of the entire let assignment the variable is part of,
4467 /// so that we can add the pattern matching _after_ it.
4468 ///
4469 assignment_location: SrcSpan,
4470 },
4471 /// A variable that is bound in a case branch's pattern. For example:
4472 /// ```gleam
4473 /// case wibble {
4474 /// wobble -> 1
4475 /// // ^^^^^ This!
4476 /// }
4477 /// ```
4478 ///
4479 ClausePatternVariable {
4480 variable_type: Arc<Type>,
4481 variable_location: SrcSpan,
4482 clause_location: SrcSpan,
4483 },
4484 UseVariable {
4485 variable_name: &'a EcoString,
4486 variable_type: Arc<Type>,
4487 /// The location of the entire use expression the variable is part of,
4488 /// so that we can add the pattern matching _after_ it.
4489 ///
4490 use_location: SrcSpan,
4491 },
4492}
4493
4494impl<'a, IO> PatternMatchOnValue<'a, IO> {
4495 pub fn new(
4496 module: &'a Module,
4497 line_numbers: &'a LineNumbers,
4498 params: &'a CodeActionParams,
4499 compiler: &'a LspProjectCompiler<IO>,
4500 ) -> Self {
4501 Self {
4502 module,
4503 params,
4504 compiler,
4505 selected_value: None,
4506 pattern_variable_under_cursor: None,
4507 edits: TextEdits::new(line_numbers),
4508 }
4509 }
4510
4511 pub fn code_actions(mut self) -> Vec<CodeAction> {
4512 self.visit_typed_module(&self.module.ast);
4513
4514 let action_title = match self.selected_value.clone() {
4515 Some(PatternMatchedValue::FunctionArgument {
4516 arg,
4517 first_statement: function_body,
4518 function_range,
4519 }) => {
4520 self.match_on_function_argument(arg, function_body, function_range);
4521 "Pattern match on argument"
4522 }
4523 Some(
4524 PatternMatchedValue::LetVariable {
4525 variable_name,
4526 variable_type,
4527 assignment_location: location,
4528 }
4529 | PatternMatchedValue::UseVariable {
4530 variable_name,
4531 variable_type,
4532 use_location: location,
4533 },
4534 ) => {
4535 self.match_on_let_variable(variable_name, variable_type, location);
4536 "Pattern match on variable"
4537 }
4538
4539 Some(PatternMatchedValue::ClausePatternVariable {
4540 variable_type,
4541 variable_location,
4542 clause_location,
4543 }) => {
4544 self.match_on_clause_variable(variable_type, variable_location, clause_location);
4545 "Pattern match on variable"
4546 }
4547
4548 None => return vec![],
4549 };
4550
4551 if self.edits.edits.is_empty() {
4552 return vec![];
4553 }
4554
4555 let mut action = Vec::with_capacity(1);
4556 CodeActionBuilder::new(action_title)
4557 .kind(CodeActionKind::REFACTOR_REWRITE)
4558 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4559 .preferred(false)
4560 .push_to(&mut action);
4561 action
4562 }
4563
4564 fn match_on_function_argument(
4565 &mut self,
4566 arg: &TypedArg,
4567 first_statement: &TypedStatement,
4568 function_range: Range,
4569 ) {
4570 let Some(arg_name) = arg.get_variable_name() else {
4571 return;
4572 };
4573
4574 let Some(patterns) = self.type_to_destructure_patterns(arg.type_.as_ref()) else {
4575 return;
4576 };
4577
4578 let first_statement_location = first_statement.location();
4579 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
4580
4581 // If we're trying to insert the pattern matching on the same
4582 // line as the one where the function is defined we will want to
4583 // put it on a new line instead. So in that case the nesting will
4584 // be the default 2 spaces.
4585 let needs_newline = function_range.start.line == first_statement_range.start.line;
4586 let nesting = if needs_newline {
4587 String::from(" ")
4588 } else {
4589 " ".repeat(first_statement_range.start.character as usize)
4590 };
4591
4592 let pattern_matching = if patterns.len() == 1 {
4593 let pattern = patterns.first();
4594 format!("let {pattern} = {arg_name}")
4595 } else {
4596 let patterns = patterns
4597 .iter()
4598 .map(|p| format!(" {nesting}{p} -> todo"))
4599 .join("\n");
4600 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
4601 };
4602
4603 let pattern_matching = if needs_newline {
4604 format!("\n{nesting}{pattern_matching}")
4605 } else {
4606 pattern_matching
4607 };
4608
4609 let has_empty_body = match first_statement {
4610 ast::Statement::Expression(TypedExpr::Todo {
4611 kind: TodoKind::EmptyFunction { .. },
4612 ..
4613 }) => true,
4614 _ => false,
4615 };
4616
4617 // If the pattern matching is added to a function with an empty
4618 // body then we do not add any nesting after it, or we would be
4619 // increasing the nesting of the closing `}`!
4620 let pattern_matching = if has_empty_body {
4621 format!("{pattern_matching}\n")
4622 } else {
4623 format!("{pattern_matching}\n{nesting}")
4624 };
4625
4626 self.edits
4627 .insert(first_statement_location.start, pattern_matching);
4628 }
4629
4630 fn match_on_let_variable(
4631 &mut self,
4632 variable_name: &EcoString,
4633 variable_type: Arc<Type>,
4634 assignment_location: SrcSpan,
4635 ) {
4636 let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref()) else {
4637 return;
4638 };
4639
4640 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
4641 let nesting = " ".repeat(assignment_range.start.character as usize);
4642
4643 let pattern_matching = if patterns.len() == 1 {
4644 let pattern = patterns.first();
4645 format!("let {pattern} = {variable_name}")
4646 } else {
4647 let patterns = patterns
4648 .iter()
4649 .map(|p| format!(" {nesting}{p} -> todo"))
4650 .join("\n");
4651 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
4652 };
4653
4654 self.edits.insert(
4655 assignment_location.end,
4656 format!("\n{nesting}{pattern_matching}"),
4657 );
4658 }
4659
4660 fn match_on_clause_variable(
4661 &mut self,
4662 variable_type: Arc<Type>,
4663 variable_location: SrcSpan,
4664 clause_location: SrcSpan,
4665 ) {
4666 let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref()) else {
4667 return;
4668 };
4669
4670 let clause_range = self.edits.src_span_to_lsp_range(clause_location);
4671 let nesting = " ".repeat(clause_range.start.character as usize);
4672
4673 let variable_start = (variable_location.start - clause_location.start) as usize;
4674 let variable_end =
4675 variable_start + (variable_location.end - variable_location.start) as usize;
4676
4677 let clause_code = code_at(self.module, clause_location);
4678 let patterns = patterns
4679 .iter()
4680 .map(|pattern| {
4681 let mut clause_code = clause_code.to_string();
4682 // If we're replacing a variable that's using the shorthand
4683 // syntax we want to add a space to separate it from the
4684 // preceding `:`.
4685 let pattern = if variable_start == variable_end {
4686 &eco_format!(" {pattern}")
4687 } else {
4688 pattern
4689 };
4690
4691 clause_code.replace_range(variable_start..variable_end, pattern);
4692 clause_code
4693 })
4694 .join(&format!("\n{nesting}"));
4695
4696 self.edits.replace(clause_location, patterns);
4697 }
4698
4699 /// Will produce a pattern that can be used on the left hand side of a let
4700 /// assignment to destructure a value of the given type. For example given
4701 /// this type:
4702 ///
4703 /// ```gleam
4704 /// pub type Wibble {
4705 /// Wobble(Int, label: String)
4706 /// }
4707 /// ```
4708 ///
4709 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
4710 /// The pattern will use the correct qualified/unqualified name for the
4711 /// constructor if it comes from another package.
4712 ///
4713 /// The function will only produce a list of patterns that can be used from
4714 /// the current module. So if the type comes from another module it must be
4715 /// public! Otherwise this function will return an empty vec.
4716 ///
4717 fn type_to_destructure_patterns(&mut self, type_: &Type) -> Option<Vec1<EcoString>> {
4718 match type_ {
4719 Type::Fn { .. } => None,
4720 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow()),
4721
4722 // We special case lists, they don't have "regular" constructors
4723 // like other types. Instead we always add the two branches covering
4724 // the empty and non empty list.
4725 Type::Named { .. } if type_.is_list() => {
4726 Some(vec1!["[]".into(), "[first, ..rest]".into()])
4727 }
4728
4729 Type::Named {
4730 module: type_module,
4731 name: type_name,
4732 ..
4733 } => {
4734 let patterns =
4735 get_type_constructors(self.compiler, &self.module.name, type_module, type_name)
4736 .iter()
4737 .filter_map(|c| self.record_constructor_to_destructure_pattern(c))
4738 .collect_vec();
4739
4740 Vec1::try_from_vec(patterns).ok()
4741 }
4742 // We don't want to suggest this action for empty tuple as it
4743 // doesn't make a lot of sense to match on those.
4744 Type::Tuple { elements } if elements.is_empty() => None,
4745 Type::Tuple { elements } => Some(vec1![eco_format!(
4746 "#({})",
4747 (0..elements.len() as u32)
4748 .map(|i| format!("value_{i}"))
4749 .join(", ")
4750 )]),
4751 }
4752 }
4753
4754 fn type_var_to_destructure_patterns(&mut self, type_var: &TypeVar) -> Option<Vec1<EcoString>> {
4755 match type_var {
4756 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
4757 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_),
4758 }
4759 }
4760
4761 /// Given the value constructor of a record, returns a string with the
4762 /// pattern used to match on that specific variant.
4763 ///
4764 /// Note how:
4765 /// - If the constructor is internal to another module or comes from another
4766 /// module, then this returns `None` since one cannot pattern match on it.
4767 /// - If the provided `ValueConstructor` is not a record constructor this
4768 /// will return `None`.
4769 ///
4770 fn record_constructor_to_destructure_pattern(
4771 &self,
4772 constructor: &ValueConstructor,
4773 ) -> Option<EcoString> {
4774 let type_::ValueConstructorVariant::Record {
4775 name: constructor_name,
4776 arity: constructor_arity,
4777 module: constructor_module,
4778 field_map,
4779 ..
4780 } = &constructor.variant
4781 else {
4782 // The constructor should always be a record, in case it's not
4783 // there's not much we can do and just fail.
4784 return None;
4785 };
4786
4787 // Since the constructor is a record constructor we know that its type
4788 // is either `Named` or a `Fn` type, in either case we have to get the
4789 // arguments types out of it.
4790 let Some(arguments_types) = constructor
4791 .type_
4792 .fn_types()
4793 .map(|(arguments_types, _return)| arguments_types)
4794 .or_else(|| constructor.type_.constructor_types())
4795 else {
4796 // This should never happen but just in case we don't want to unwrap
4797 // and panic.
4798 return None;
4799 };
4800
4801 let mut name_generator = NameGenerator::new();
4802 let index_to_label = match field_map {
4803 None => HashMap::new(),
4804 Some(field_map) => {
4805 name_generator.reserve_all_labels(field_map);
4806
4807 field_map
4808 .fields
4809 .iter()
4810 .map(|(label, index)| (index, label))
4811 .collect::<HashMap<_, _>>()
4812 }
4813 };
4814
4815 let mut pattern =
4816 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
4817
4818 if *constructor_arity == 0 {
4819 return Some(pattern);
4820 }
4821
4822 pattern.push('(');
4823 let arguments = (0..*constructor_arity as u32)
4824 .map(|i| match index_to_label.get(&i) {
4825 Some(label) => eco_format!("{label}:"),
4826 None => match arguments_types.get(i as usize) {
4827 None => name_generator.rename_to_avoid_shadowing(EcoString::from("value")),
4828 Some(type_) => name_generator.generate_name_from_type(type_),
4829 },
4830 })
4831 .join(", ");
4832
4833 pattern.push_str(&arguments);
4834 pattern.push(')');
4835 Some(pattern)
4836 }
4837}
4838
4839fn code_at(module: &Module, span: SrcSpan) -> &str {
4840 module
4841 .code
4842 .get(span.start as usize..span.end as usize)
4843 .expect("code location must be valid")
4844}
4845
4846impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> {
4847 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
4848 // If we're not inside the function there's no point in exploring its
4849 // ast further.
4850 let function_span = SrcSpan {
4851 start: fun.location.start,
4852 end: fun.end_position,
4853 };
4854 let function_range = self.edits.src_span_to_lsp_range(function_span);
4855 if !within(self.params.range, function_range) {
4856 return;
4857 }
4858
4859 for arg in &fun.arguments {
4860 // If the cursor is placed on one of the arguments, then we can try
4861 // and generate code for that one.
4862 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
4863 if within(self.params.range, arg_range)
4864 && let Some(first_statement) = fun.body.first()
4865 {
4866 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
4867 arg,
4868 first_statement,
4869 function_range,
4870 });
4871 return;
4872 }
4873 }
4874
4875 // If the cursor is not on any of the function arguments then we keep
4876 // exploring the function body as we might want to destructure the
4877 // argument of an expression function!
4878 ast::visit::visit_typed_function(self, fun);
4879 }
4880
4881 fn visit_typed_expr_fn(
4882 &mut self,
4883 location: &'ast SrcSpan,
4884 type_: &'ast Arc<Type>,
4885 kind: &'ast FunctionLiteralKind,
4886 arguments: &'ast [TypedArg],
4887 body: &'ast Vec1<TypedStatement>,
4888 return_annotation: &'ast Option<ast::TypeAst>,
4889 ) {
4890 // If we're not inside the function there's no point in exploring its
4891 // ast further.
4892 let function_range = self.edits.src_span_to_lsp_range(*location);
4893 if !within(self.params.range, function_range) {
4894 return;
4895 }
4896
4897 for argument in arguments {
4898 // If the cursor is placed on one of the arguments, then we can try
4899 // and generate code for that one.
4900 let arg_range = self.edits.src_span_to_lsp_range(argument.location);
4901 if within(self.params.range, arg_range) {
4902 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
4903 arg: argument,
4904 first_statement: body.first(),
4905 function_range,
4906 });
4907 return;
4908 }
4909 }
4910
4911 // If the cursor is not on any of the function arguments then we keep
4912 // exploring the function body as we might want to destructure the
4913 // argument of an expression function!
4914 ast::visit::visit_typed_expr_fn(
4915 self,
4916 location,
4917 type_,
4918 kind,
4919 arguments,
4920 body,
4921 return_annotation,
4922 );
4923 }
4924
4925 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
4926 ast::visit::visit_typed_assignment(self, assignment);
4927 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor {
4928 self.selected_value = Some(PatternMatchedValue::LetVariable {
4929 variable_name: name,
4930 variable_type: type_.clone(),
4931 assignment_location: assignment.location,
4932 });
4933 }
4934 }
4935
4936 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
4937 for pattern in clause.pattern.iter() {
4938 self.visit_typed_pattern(pattern);
4939 }
4940 for patterns in clause.alternative_patterns.iter() {
4941 for pattern in patterns {
4942 self.visit_typed_pattern(pattern);
4943 }
4944 }
4945
4946 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() {
4947 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable {
4948 variable_type: type_,
4949 variable_location,
4950 clause_location: clause.location(),
4951 });
4952 } else {
4953 self.visit_typed_expr(&clause.then);
4954 }
4955 }
4956
4957 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
4958 if let Some(assignments) = use_.callback_arguments() {
4959 for variable in assignments {
4960 let ast::Arg {
4961 names: ArgNames::Named { name, .. },
4962 location: variable_location,
4963 type_,
4964 ..
4965 } = variable
4966 else {
4967 continue;
4968 };
4969
4970 // If we use a pattern in a use assignment, that will end up
4971 // being called `_use` something. We don't want to offer the
4972 // action when hovering a pattern so we ignore those.
4973 if name.starts_with("_use") {
4974 continue;
4975 }
4976
4977 let variable_range = self.edits.src_span_to_lsp_range(*variable_location);
4978 if within(self.params.range, variable_range) {
4979 self.selected_value = Some(PatternMatchedValue::UseVariable {
4980 variable_name: name,
4981 variable_type: type_.clone(),
4982 use_location: use_.location,
4983 });
4984 // If we've found the variable to pattern match on, there's no
4985 // point in keeping traversing the AST.
4986 return;
4987 }
4988 }
4989 }
4990
4991 ast::visit::visit_typed_use(self, use_);
4992 }
4993
4994 fn visit_typed_pattern_variable(
4995 &mut self,
4996 location: &'ast SrcSpan,
4997 name: &'ast EcoString,
4998 type_: &'ast Arc<Type>,
4999 _origin: &'ast VariableOrigin,
5000 ) {
5001 if within(
5002 self.params.range,
5003 self.edits.src_span_to_lsp_range(*location),
5004 ) {
5005 self.pattern_variable_under_cursor = Some((name, *location, type_.clone()));
5006 }
5007 }
5008
5009 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
5010 if let Some(name) = arg.label_shorthand_name()
5011 && within(
5012 self.params.range,
5013 self.edits.src_span_to_lsp_range(arg.location),
5014 )
5015 {
5016 let location = SrcSpan {
5017 start: arg.location.end,
5018 end: arg.location.end,
5019 };
5020 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_()));
5021 return;
5022 }
5023
5024 ast::visit::visit_typed_pattern_call_arg(self, arg);
5025 }
5026
5027 fn visit_typed_pattern_string_prefix(
5028 &mut self,
5029 _location: &'ast SrcSpan,
5030 _left_location: &'ast SrcSpan,
5031 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
5032 right_location: &'ast SrcSpan,
5033 _left_side_string: &'ast EcoString,
5034 right_side_assignment: &'ast AssignName,
5035 ) {
5036 if let Some((name, location)) = left_side_assignment
5037 && within(
5038 self.params.range,
5039 self.edits.src_span_to_lsp_range(*location),
5040 )
5041 {
5042 self.pattern_variable_under_cursor = Some((name, *location, type_::string()));
5043 } else if let AssignName::Variable(name) = right_side_assignment
5044 && within(
5045 self.params.range,
5046 self.edits.src_span_to_lsp_range(*right_location),
5047 )
5048 {
5049 self.pattern_variable_under_cursor = Some((name, *right_location, type_::string()));
5050 }
5051 }
5052}
5053
5054/// Given a type and its module, returns a list of its *importable*
5055/// constructors.
5056///
5057/// Since this focuses just on importable constructors, if either the module or
5058/// the type are internal the returned array will be empty!
5059///
5060fn get_type_constructors<'a, 'b, IO>(
5061 compiler: &'a LspProjectCompiler<IO>,
5062 current_module: &'b EcoString,
5063 type_module: &'b EcoString,
5064 type_name: &'b EcoString,
5065) -> Vec<&'a ValueConstructor> {
5066 let type_is_inside_current_module = current_module == type_module;
5067 let module_interface = if !type_is_inside_current_module {
5068 // If the type is outside of the module we're in, we can only pattern
5069 // match on it if the module can be imported.
5070 // The `get_module_interface` already takes care of making this check.
5071 compiler.get_module_interface(type_module)
5072 } else {
5073 // However, if the type is defined in the module we're in, we can always
5074 // pattern match on it. So we get the current module's interface.
5075 compiler
5076 .modules
5077 .get(current_module)
5078 .map(|module| &module.ast.type_info)
5079 };
5080
5081 let Some(module_interface) = module_interface else {
5082 return vec![];
5083 };
5084
5085 // If the type is in an internal module that is not the current one, we
5086 // cannot use its constructors!
5087 if !type_is_inside_current_module && module_interface.is_internal {
5088 return vec![];
5089 }
5090
5091 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
5092 return vec![];
5093 };
5094
5095 constructors
5096 .variants
5097 .iter()
5098 .filter_map(|variant| {
5099 let constructor = module_interface.values.get(&variant.name)?;
5100 if type_is_inside_current_module || constructor.publicity.is_public() {
5101 Some(constructor)
5102 } else {
5103 None
5104 }
5105 })
5106 .collect_vec()
5107}
5108
5109/// Returns a pretty printed record constructor name, the way it would be used
5110/// inside the given `module` (with the correct name and qualification).
5111///
5112/// If the constructor cannot be used inside the module because it's not
5113/// imported, then this function will return `None`.
5114///
5115fn pretty_constructor_name(
5116 module: &Module,
5117 constructor_module: &EcoString,
5118 constructor_name: &EcoString,
5119) -> Option<EcoString> {
5120 match module
5121 .ast
5122 .names
5123 .named_constructor(constructor_module, constructor_name)
5124 {
5125 type_::printer::NameContextInformation::Unimported(_, _) => None,
5126 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
5127 Some(eco_format!("{constructor_name}"))
5128 }
5129 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
5130 Some(eco_format!("{module_name}.{constructor_name}"))
5131 }
5132 }
5133}
5134
5135/// Builder for the "generate function" code action.
5136/// Whenever someone hovers an invalid expression that is inferred to have a
5137/// function type the language server can generate a function definition for it.
5138/// For example:
5139///
5140/// ```gleam
5141/// pub fn main() {
5142/// wibble(1, 2, "hello")
5143/// // ^ [generate function]
5144/// }
5145/// ```
5146///
5147/// Will generate the following definition:
5148///
5149/// ```gleam
5150/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
5151/// todo
5152/// }
5153/// ```
5154///
5155pub struct GenerateFunction<'a> {
5156 module: &'a Module,
5157 modules: &'a std::collections::HashMap<EcoString, Module>,
5158 params: &'a CodeActionParams,
5159 edits: TextEdits<'a>,
5160 last_visited_function_end: Option<u32>,
5161 function_to_generate: Option<FunctionToGenerate<'a>>,
5162}
5163
5164struct FunctionToGenerate<'a> {
5165 module: Option<&'a str>,
5166 name: &'a str,
5167 arguments_types: Vec<Arc<Type>>,
5168
5169 /// The arguments actually supplied as input to the function, if any.
5170 /// A function to generate might as well be just a name passed as an argument
5171 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
5172 /// have any actual arguments!
5173 given_arguments: Option<&'a [TypedCallArg]>,
5174 return_type: Arc<Type>,
5175 previous_function_end: Option<u32>,
5176}
5177
5178impl<'a> GenerateFunction<'a> {
5179 pub fn new(
5180 module: &'a Module,
5181 modules: &'a std::collections::HashMap<EcoString, Module>,
5182 line_numbers: &'a LineNumbers,
5183 params: &'a CodeActionParams,
5184 ) -> Self {
5185 Self {
5186 module,
5187 modules,
5188 params,
5189 edits: TextEdits::new(line_numbers),
5190 last_visited_function_end: None,
5191 function_to_generate: None,
5192 }
5193 }
5194
5195 pub fn code_actions(mut self) -> Vec<CodeAction> {
5196 self.visit_typed_module(&self.module.ast);
5197
5198 let Some(
5199 function_to_generate @ FunctionToGenerate {
5200 module,
5201 previous_function_end: Some(insert_at),
5202 ..
5203 },
5204 ) = self.function_to_generate.take()
5205 else {
5206 return vec![];
5207 };
5208
5209 if let Some(module) = module {
5210 if let Some(module) = self.modules.get(module) {
5211 let insert_at = if module.code.is_empty() {
5212 0
5213 } else {
5214 (module.code.len() - 1) as u32
5215 };
5216 self.code_action_for_module(
5217 module,
5218 Publicity::Public,
5219 function_to_generate,
5220 insert_at,
5221 )
5222 } else {
5223 Vec::new()
5224 }
5225 } else {
5226 let module = self.module;
5227 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at)
5228 }
5229 }
5230
5231 fn code_action_for_module(
5232 mut self,
5233 module: &Module,
5234 publicity: Publicity,
5235 function_to_generate: FunctionToGenerate<'a>,
5236 insert_at: u32,
5237 ) -> Vec<CodeAction> {
5238 let FunctionToGenerate {
5239 name,
5240 arguments_types,
5241 given_arguments,
5242 return_type,
5243 ..
5244 } = function_to_generate;
5245
5246 // Labels do not share the same namespace as argument so we use two separate
5247 // generators to avoid renaming a label in case it shares a name with an argument.
5248 let mut label_names = NameGenerator::new();
5249 let mut argument_names = NameGenerator::new();
5250
5251 // Since we are generating a new function, type variables from other
5252 // functions and constants are irrelevant to the types we print.
5253 let mut printer = Printer::new_without_type_variables(&module.ast.names);
5254 let arguments = arguments_types
5255 .iter()
5256 .enumerate()
5257 .map(|(index, argument_type)| {
5258 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
5259 let (label, name) =
5260 argument_names.generate_label_and_name(call_argument, argument_type);
5261 let pretty_type = printer.print_type(argument_type);
5262 if let Some(label) = label {
5263 let label = label_names.rename_to_avoid_shadowing(label.clone());
5264 format!("{label} {name}: {pretty_type}")
5265 } else {
5266 format!("{name}: {pretty_type}")
5267 }
5268 })
5269 .join(", ");
5270
5271 let return_type = printer.print_type(&return_type);
5272
5273 let publicity = if publicity.is_public() { "pub " } else { "" };
5274
5275 self.edits.insert(
5276 insert_at,
5277 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
5278 );
5279
5280 let Some(uri) = url_from_path(module.input_path.as_str()) else {
5281 return Vec::new();
5282 };
5283 let mut action = Vec::with_capacity(1);
5284 CodeActionBuilder::new("Generate function")
5285 .kind(CodeActionKind::QUICKFIX)
5286 .changes(uri, self.edits.edits)
5287 .preferred(true)
5288 .push_to(&mut action);
5289 action
5290 }
5291
5292 fn try_save_function_to_generate(
5293 &mut self,
5294 function_name_location: SrcSpan,
5295 function_type: &Arc<Type>,
5296 given_arguments: Option<&'a [TypedCallArg]>,
5297 ) {
5298 let candidate_name = code_at(self.module, function_name_location);
5299 match (candidate_name, function_type.fn_types()) {
5300 (_, None) => (),
5301 (name, _) if !is_valid_lowercase_name(name) => (),
5302 (name, Some((arguments_types, return_type))) => {
5303 self.function_to_generate = Some(FunctionToGenerate {
5304 name,
5305 arguments_types,
5306 given_arguments,
5307 return_type,
5308 previous_function_end: self.last_visited_function_end,
5309 module: None,
5310 })
5311 }
5312 }
5313 }
5314
5315 fn try_save_function_from_other_module(
5316 &mut self,
5317 module: &'a str,
5318 name: &'a str,
5319 function_type: &Arc<Type>,
5320 given_arguments: Option<&'a [TypedCallArg]>,
5321 ) {
5322 if let Some((arguments_types, return_type)) = function_type.fn_types()
5323 && is_valid_lowercase_name(name)
5324 {
5325 self.function_to_generate = Some(FunctionToGenerate {
5326 name,
5327 arguments_types,
5328 given_arguments,
5329 return_type,
5330 previous_function_end: self.last_visited_function_end,
5331 module: Some(module),
5332 })
5333 }
5334 }
5335}
5336
5337impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
5338 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
5339 self.last_visited_function_end = Some(fun.end_position);
5340 ast::visit::visit_typed_function(self, fun);
5341 }
5342
5343 fn visit_typed_expr_invalid(
5344 &mut self,
5345 location: &'ast SrcSpan,
5346 type_: &'ast Arc<Type>,
5347 extra_information: &'ast Option<InvalidExpression>,
5348 ) {
5349 let invalid_range = self.edits.src_span_to_lsp_range(*location);
5350 if within(self.params.range, invalid_range) {
5351 match extra_information {
5352 Some(InvalidExpression::ModuleSelect { module_name, label }) => {
5353 self.try_save_function_from_other_module(module_name, label, type_, None)
5354 }
5355 None => self.try_save_function_to_generate(*location, type_, None),
5356 }
5357 }
5358
5359 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
5360 }
5361
5362 fn visit_typed_expr_call(
5363 &mut self,
5364 location: &'ast SrcSpan,
5365 type_: &'ast Arc<Type>,
5366 fun: &'ast TypedExpr,
5367 arguments: &'ast [TypedCallArg],
5368 ) {
5369 // If the function being called is invalid we need to generate a
5370 // function that has the proper labels.
5371 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
5372
5373 if within(self.params.range, fun_range) {
5374 if !labels_are_correct(arguments) {
5375 return;
5376 }
5377
5378 match fun {
5379 TypedExpr::Invalid {
5380 type_,
5381 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }),
5382 location: _,
5383 } => {
5384 return self.try_save_function_from_other_module(
5385 module_name,
5386 label,
5387 type_,
5388 Some(arguments),
5389 );
5390 }
5391 TypedExpr::Invalid {
5392 type_,
5393 location,
5394 extra_information: _,
5395 } => {
5396 return self.try_save_function_to_generate(*location, type_, Some(arguments));
5397 }
5398 _ => {}
5399 }
5400 }
5401
5402 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
5403 }
5404}
5405
5406/// Builder for the "generate variant" code action. This will generate a variant
5407/// for a type if it can tell the type it should come from. It will work with
5408/// non-existing variants both used as expressions
5409///
5410/// ```gleam
5411/// let a = IDoNotExist(1)
5412/// // ^^^^^^^^^^^ It would generate this variant here
5413/// ```
5414///
5415/// And as patterns:
5416///
5417/// ```gleam
5418/// let assert IDoNotExist(1) = todo
5419/// ^^^^^^^^^^^ It would generate this variant here
5420/// ```
5421///
5422pub struct GenerateVariant<'a, IO> {
5423 module: &'a Module,
5424 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
5425 params: &'a CodeActionParams,
5426 line_numbers: &'a LineNumbers,
5427 variant_to_generate: Option<VariantToGenerate<'a>>,
5428}
5429
5430struct VariantToGenerate<'a> {
5431 name: &'a str,
5432 end_position: u32,
5433 arguments_types: Vec<Arc<Type>>,
5434
5435 /// Wether the type we're adding the variant to is written with braces or
5436 /// not. We need this information to add braces when missing.
5437 ///
5438 type_braces: TypeBraces,
5439
5440 /// The module this variant will be added to.
5441 ///
5442 module_name: EcoString,
5443
5444 /// The arguments actually supplied as input to the variant, if any.
5445 /// A variant to generate might as well be just a name passed as an argument
5446 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
5447 /// have any actual arguments!
5448 ///
5449 given_arguments: Option<Arguments<'a>>,
5450}
5451
5452#[derive(Debug, Clone, Copy)]
5453enum TypeBraces {
5454 /// If the type is written like this: `pub type Wibble`
5455 HasBraces,
5456 /// If the type is written like this: `pub type Wibble {}`
5457 NoBraces,
5458}
5459
5460/// The arguments to an invalid call or pattern we can use to generate a variant.
5461///
5462enum Arguments<'a> {
5463 /// These are the arguments provided to the invalid variant constructor
5464 /// when it's used as a function: `let a = Wibble(1, 2)`.
5465 ///
5466 Expressions(&'a [TypedCallArg]),
5467 /// These are the arguments provided to the invalid variant constructor when
5468 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
5469 ///
5470 Patterns(&'a [CallArg<TypedPattern>]),
5471}
5472
5473/// An invalid variant might be used both as a pattern in a case expression or
5474/// as a regular value in an expression. We want to generate the variant in both
5475/// cases, so we use this enum to tell apart the two cases and be able to reuse
5476/// most of the code for both as they are very similar.
5477///
5478enum Argument<'a> {
5479 Expression(&'a TypedCallArg),
5480 Pattern(&'a CallArg<TypedPattern>),
5481}
5482
5483impl<'a> Arguments<'a> {
5484 fn get(&self, index: usize) -> Option<Argument<'a>> {
5485 match self {
5486 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern),
5487 Arguments::Expressions(call_arguments) => {
5488 call_arguments.get(index).map(Argument::Expression)
5489 }
5490 }
5491 }
5492
5493 fn types(&self) -> Vec<Arc<Type>> {
5494 match self {
5495 Arguments::Expressions(call_arguments) => call_arguments
5496 .iter()
5497 .map(|argument| argument.value.type_())
5498 .collect_vec(),
5499
5500 Arguments::Patterns(call_arguments) => call_arguments
5501 .iter()
5502 .map(|argument| argument.value.type_())
5503 .collect_vec(),
5504 }
5505 }
5506}
5507
5508impl Argument<'_> {
5509 fn label(&self) -> Option<EcoString> {
5510 match self {
5511 Argument::Expression(call_arg) => call_arg.label.clone(),
5512 Argument::Pattern(call_arg) => call_arg.label.clone(),
5513 }
5514 }
5515}
5516
5517impl<'a, IO> GenerateVariant<'a, IO> {
5518 pub fn new(
5519 module: &'a Module,
5520 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
5521 line_numbers: &'a LineNumbers,
5522 params: &'a CodeActionParams,
5523 ) -> Self {
5524 Self {
5525 module,
5526 params,
5527 compiler,
5528 line_numbers,
5529 variant_to_generate: None,
5530 }
5531 }
5532
5533 pub fn code_actions(mut self) -> Vec<CodeAction> {
5534 self.visit_typed_module(&self.module.ast);
5535
5536 let Some(VariantToGenerate {
5537 name,
5538 arguments_types,
5539 given_arguments,
5540 module_name,
5541 end_position,
5542 type_braces,
5543 }) = &self.variant_to_generate
5544 else {
5545 return vec![];
5546 };
5547
5548 let Some((variant_module, variant_edits)) = self.edits_to_create_variant(
5549 name,
5550 arguments_types,
5551 given_arguments,
5552 module_name,
5553 *end_position,
5554 *type_braces,
5555 ) else {
5556 return vec![];
5557 };
5558
5559 let mut action = Vec::with_capacity(1);
5560 CodeActionBuilder::new("Generate variant")
5561 .kind(CodeActionKind::QUICKFIX)
5562 .changes(variant_module, variant_edits)
5563 .preferred(true)
5564 .push_to(&mut action);
5565 action
5566 }
5567
5568 /// Returns the edits needed to add this new variant to the given module.
5569 /// It also returns the uri of the module the edits should be applied to.
5570 ///
5571 fn edits_to_create_variant(
5572 &self,
5573 variant_name: &str,
5574 arguments_types: &[Arc<Type>],
5575 given_arguments: &Option<Arguments<'_>>,
5576 module_name: &EcoString,
5577 end_position: u32,
5578 type_braces: TypeBraces,
5579 ) -> Option<(Url, Vec<TextEdit>)> {
5580 let mut label_names = NameGenerator::new();
5581 let mut printer = Printer::new(&self.module.ast.names);
5582 let arguments = arguments_types
5583 .iter()
5584 .enumerate()
5585 .map(|(index, argument_type)| {
5586 let label = given_arguments
5587 .as_ref()
5588 .and_then(|arguments| arguments.get(index)?.label())
5589 .map(|label| label_names.rename_to_avoid_shadowing(label));
5590
5591 let pretty_type = printer.print_type(argument_type);
5592 if let Some(arg_label) = label {
5593 format!("{arg_label}: {pretty_type}")
5594 } else {
5595 format!("{pretty_type}")
5596 }
5597 })
5598 .join(", ");
5599
5600 let variant = if arguments.is_empty() {
5601 variant_name.to_string()
5602 } else {
5603 format!("{variant_name}({arguments})")
5604 };
5605
5606 let (new_text, insert_at) = match type_braces {
5607 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
5608 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
5609 };
5610
5611 if *module_name == self.module.name {
5612 // If we're editing the current module we can use the line numbers that
5613 // were already computed before-hand without wasting any time to add the
5614 // new edit.
5615 let mut edits = TextEdits::new(self.line_numbers);
5616 edits.insert(insert_at, new_text);
5617 Some((self.params.text_document.uri.clone(), edits.edits))
5618 } else {
5619 // Otherwise we're changing a different module and we need to get its
5620 // code and line numbers to properly apply the new edit.
5621 let module = self
5622 .compiler
5623 .modules
5624 .get(module_name)
5625 .expect("module to exist");
5626 let line_numbers = LineNumbers::new(&module.code);
5627 let mut edits = TextEdits::new(&line_numbers);
5628 edits.insert(insert_at, new_text);
5629 Some((url_from_path(module.input_path.as_str())?, edits.edits))
5630 }
5631 }
5632
5633 fn try_save_variant_to_generate(
5634 &mut self,
5635 function_name_location: SrcSpan,
5636 function_type: &Arc<Type>,
5637 given_arguments: Option<Arguments<'a>>,
5638 ) {
5639 let variant_to_generate =
5640 self.variant_to_generate(function_name_location, function_type, given_arguments);
5641 if variant_to_generate.is_some() {
5642 self.variant_to_generate = variant_to_generate;
5643 }
5644 }
5645
5646 fn variant_to_generate(
5647 &mut self,
5648 function_name_location: SrcSpan,
5649 type_: &Arc<Type>,
5650 given_arguments: Option<Arguments<'a>>,
5651 ) -> Option<VariantToGenerate<'a>> {
5652 let name = code_at(self.module, function_name_location);
5653 if !is_valid_uppercase_name(name) {
5654 return None;
5655 }
5656
5657 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
5658 (Some(result), _) => result,
5659 (None, Some(arguments)) => (arguments.types(), type_.clone()),
5660 (None, None) => (vec![], type_.clone()),
5661 };
5662
5663 let (module_name, type_name, _) = custom_type.named_type_information()?;
5664 let module = self.compiler.modules.get(&module_name)?;
5665 let (end_position, type_braces) =
5666 (module.ast.definitions.iter()).find_map(|definition| match definition {
5667 ast::Definition::CustomType(custom_type) if custom_type.name == type_name => {
5668 // If there's already a variant with this name then we definitely
5669 // don't want to generate a new variant with the same name!
5670 let variant_with_this_name_already_exists = custom_type
5671 .constructors
5672 .iter()
5673 .map(|constructor| &constructor.name)
5674 .any(|existing_constructor_name| existing_constructor_name == name);
5675 if variant_with_this_name_already_exists {
5676 return None;
5677 }
5678 let type_braces = if custom_type.end_position == custom_type.location.end {
5679 TypeBraces::NoBraces
5680 } else {
5681 TypeBraces::HasBraces
5682 };
5683 Some((custom_type.end_position, type_braces))
5684 }
5685 _ => None,
5686 })?;
5687
5688 Some(VariantToGenerate {
5689 name,
5690 arguments_types,
5691 given_arguments,
5692 module_name,
5693 end_position,
5694 type_braces,
5695 })
5696 }
5697}
5698
5699impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> {
5700 fn visit_typed_expr_invalid(
5701 &mut self,
5702 location: &'ast SrcSpan,
5703 type_: &'ast Arc<Type>,
5704 extra_information: &'ast Option<InvalidExpression>,
5705 ) {
5706 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
5707 if within(self.params.range, invalid_range) {
5708 self.try_save_variant_to_generate(*location, type_, None);
5709 }
5710 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information);
5711 }
5712
5713 fn visit_typed_expr_call(
5714 &mut self,
5715 location: &'ast SrcSpan,
5716 type_: &'ast Arc<Type>,
5717 fun: &'ast TypedExpr,
5718 arguments: &'ast [TypedCallArg],
5719 ) {
5720 // If the function being called is invalid we need to generate a
5721 // function that has the proper labels.
5722 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
5723 if within(self.params.range, fun_range) && fun.is_invalid() {
5724 if labels_are_correct(arguments) {
5725 self.try_save_variant_to_generate(
5726 fun.location(),
5727 &fun.type_(),
5728 Some(Arguments::Expressions(arguments)),
5729 );
5730 }
5731 } else {
5732 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
5733 }
5734 }
5735
5736 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5737 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
5738 if within(self.params.range, invalid_range) {
5739 self.try_save_variant_to_generate(*location, type_, None);
5740 }
5741 ast::visit::visit_typed_pattern_invalid(self, location, type_);
5742 }
5743
5744 fn visit_typed_pattern_constructor(
5745 &mut self,
5746 location: &'ast SrcSpan,
5747 name_location: &'ast SrcSpan,
5748 name: &'ast EcoString,
5749 arguments: &'ast Vec<CallArg<TypedPattern>>,
5750 module: &'ast Option<(EcoString, SrcSpan)>,
5751 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
5752 spread: &'ast Option<SrcSpan>,
5753 type_: &'ast Arc<Type>,
5754 ) {
5755 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
5756 if within(self.params.range, pattern_range) {
5757 if labels_are_correct(arguments) {
5758 self.try_save_variant_to_generate(
5759 *name_location,
5760 type_,
5761 Some(Arguments::Patterns(arguments)),
5762 );
5763 }
5764 } else {
5765 ast::visit::visit_typed_pattern_constructor(
5766 self,
5767 location,
5768 name_location,
5769 name,
5770 arguments,
5771 module,
5772 constructor,
5773 spread,
5774 type_,
5775 );
5776 }
5777 }
5778}
5779
5780#[must_use]
5781/// Checks the labels in the given arguments are correct: that is there's no
5782/// duplicate labels and all labelled arguments come after the unlabelled ones.
5783fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool {
5784 let mut labelled_arg_found = false;
5785 let mut used_labels = HashSet::new();
5786
5787 for argument in arguments {
5788 match &argument.label {
5789 // Labels are invalid if there's duplicate ones or if an unlabelled
5790 // argument comes after a labelled one.
5791 Some(label) if used_labels.contains(label) => return false,
5792 None if labelled_arg_found => return false,
5793 // Otherwise we just add the label to the used ones.
5794 Some(label) => {
5795 labelled_arg_found = true;
5796 let _ = used_labels.insert(label);
5797 }
5798 None => {}
5799 }
5800 }
5801
5802 true
5803}
5804
5805struct NameGenerator {
5806 used_names: HashSet<EcoString>,
5807}
5808
5809impl NameGenerator {
5810 pub fn new() -> Self {
5811 NameGenerator {
5812 used_names: HashSet::new(),
5813 }
5814 }
5815
5816 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
5817 let mut i = 1;
5818 let mut candidate_name = base.clone();
5819
5820 loop {
5821 if self.used_names.contains(&candidate_name) {
5822 i += 1;
5823 candidate_name = eco_format!("{base}_{i}");
5824 } else {
5825 let _ = self.used_names.insert(candidate_name.clone());
5826 return candidate_name;
5827 }
5828 }
5829 }
5830
5831 /// Given an argument type and the actual call argument (if any), comes up
5832 /// with a label and a name to use for that argument when generating a
5833 /// function.
5834 ///
5835 pub fn generate_label_and_name(
5836 &mut self,
5837 call_argument: Option<&CallArg<TypedExpr>>,
5838 argument_type: &Arc<Type>,
5839 ) -> (Option<EcoString>, EcoString) {
5840 let label = call_argument.and_then(|argument| argument.label.clone());
5841 let argument_name = call_argument
5842 // We always favour a name derived from the expression (for example if
5843 // the argument is a variable)
5844 .and_then(|argument| self.generate_name_from_expression(&argument.value))
5845 // If we don't have such a name and there's a label we use that name.
5846 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
5847 // If all else fails we fallback to using a name derived from the
5848 // argument's type.
5849 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
5850
5851 (label, argument_name)
5852 }
5853
5854 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
5855 let type_to_base_name = |type_: &Arc<Type>| {
5856 type_
5857 .named_type_name()
5858 .map(|(_type_module, type_name)| to_snake_case(&type_name))
5859 .filter(|name| is_valid_lowercase_name(name))
5860 .unwrap_or(EcoString::from("value"))
5861 };
5862
5863 let base_name = match type_.list_type() {
5864 None => type_to_base_name(type_),
5865 // If we're coming up with a name for a list we want to use the
5866 // plural form for the name of the inner type. For example:
5867 // `List(Pokemon)` should generate `pokemons`.
5868 Some(inner_type) => {
5869 let base_name = type_to_base_name(&inner_type);
5870 // If the inner type name already ends in "s" we leave it as it
5871 // is, or it would look funny.
5872 if base_name.ends_with('s') {
5873 base_name
5874 } else {
5875 eco_format!("{base_name}s")
5876 }
5877 }
5878 };
5879
5880 self.rename_to_avoid_shadowing(base_name)
5881 }
5882
5883 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
5884 match expression {
5885 // If the argument is a record, we can't use it as an argument name.
5886 // Similarly, we don't want to base the variable name off a
5887 // compiler-generated variable like `_pipe`.
5888 TypedExpr::Var {
5889 name, constructor, ..
5890 } if !constructor.variant.is_record()
5891 && !constructor.variant.is_generated_variable() =>
5892 {
5893 Some(self.rename_to_avoid_shadowing(name.clone()))
5894 }
5895 _ => None,
5896 }
5897 }
5898
5899 pub fn add_used_name(&mut self, name: EcoString) {
5900 let _ = self.used_names.insert(name);
5901 }
5902
5903 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
5904 field_map
5905 .fields
5906 .iter()
5907 .for_each(|(label, _)| self.add_used_name(label.clone()));
5908 }
5909
5910 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
5911 variable_names
5912 .names
5913 .iter()
5914 .for_each(|name| self.add_used_name(name.clone()));
5915 }
5916}
5917
5918#[must_use]
5919fn is_valid_lowercase_name(name: &str) -> bool {
5920 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
5921 return false;
5922 }
5923
5924 for char in name.chars() {
5925 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
5926 if !is_valid_char {
5927 return false;
5928 }
5929 }
5930
5931 str_to_keyword(name).is_none()
5932}
5933
5934#[must_use]
5935fn is_valid_uppercase_name(name: &str) -> bool {
5936 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
5937 return false;
5938 }
5939
5940 for char in name.chars() {
5941 if !char.is_ascii_alphanumeric() {
5942 return false;
5943 }
5944 }
5945
5946 true
5947}
5948
5949/// Code action to rewrite a single-step pipeline into a regular function call.
5950/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
5951///
5952pub struct ConvertToFunctionCall<'a> {
5953 module: &'a Module,
5954 params: &'a CodeActionParams,
5955 edits: TextEdits<'a>,
5956 locations: Option<ConvertToFunctionCallLocations>,
5957}
5958
5959/// All the different locations the "Convert to function call" code action needs
5960/// to properly rewrite a pipeline into a function call.
5961///
5962struct ConvertToFunctionCallLocations {
5963 /// This is the location of the value being piped into a call.
5964 ///
5965 /// ```gleam
5966 /// [1, 2, 3] |> list.length
5967 /// // ^^^^^^^^^ This one here
5968 /// ```
5969 ///
5970 first_value: SrcSpan,
5971
5972 /// This is the location of the call the value is being piped into.
5973 ///
5974 /// ```gleam
5975 /// [1, 2, 3] |> list.length
5976 /// // ^^^^^^^^^^^ This one here
5977 /// ```
5978 ///
5979 call: SrcSpan,
5980
5981 /// This is the kind of desugaring that is taking place when piping
5982 /// `first_value` into `call`.
5983 ///
5984 call_kind: PipelineAssignmentKind,
5985}
5986
5987impl<'a> ConvertToFunctionCall<'a> {
5988 pub fn new(
5989 module: &'a Module,
5990 line_numbers: &'a LineNumbers,
5991 params: &'a CodeActionParams,
5992 ) -> Self {
5993 Self {
5994 module,
5995 params,
5996 edits: TextEdits::new(line_numbers),
5997 locations: None,
5998 }
5999 }
6000
6001 pub fn code_actions(mut self) -> Vec<CodeAction> {
6002 self.visit_typed_module(&self.module.ast);
6003
6004 // If we couldn't find a pipeline to rewrite we don't return any action.
6005 let Some(ConvertToFunctionCallLocations {
6006 first_value,
6007 call,
6008 call_kind,
6009 }) = self.locations
6010 else {
6011 return vec![];
6012 };
6013
6014 // We first delete the first value of the pipeline as it's going to be
6015 // inlined as a function call argument.
6016 self.edits.delete(SrcSpan {
6017 start: first_value.start,
6018 end: call.start,
6019 });
6020
6021 // Then we have to insert the piped value in the appropriate position.
6022 // This will change based on how the pipeline is being desugared, we
6023 // know this thanks to the `call_kind`
6024 let first_value_text = self
6025 .module
6026 .code
6027 .get(first_value.start as usize..first_value.end as usize)
6028 .expect("invalid code span")
6029 .to_string();
6030
6031 match call_kind {
6032 // When piping into a `_` we replace the hole with the piped value:
6033 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
6034 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
6035
6036 // When piping is desguared as a function call we need to add the
6037 // missing parentheses:
6038 // `[1, 2] |> length` becomes `length([1, 2])`
6039 PipelineAssignmentKind::FunctionCall => {
6040 self.edits.insert(call.end, format!("({first_value_text})"))
6041 }
6042
6043 // When the piped value is inserted as the first argument there's two
6044 // possible scenarios:
6045 // - there's a second argument as well: in that case we insert it
6046 // before the second arg and add a comma
6047 // - there's no other argument: `[1, 2] |> length()` becomes
6048 // `length([1, 2])`, we insert the value between the empty
6049 // parentheses
6050 PipelineAssignmentKind::FirstArgument {
6051 second_argument: Some(SrcSpan { start, .. }),
6052 } => self.edits.insert(start, format!("{first_value_text}, ")),
6053 PipelineAssignmentKind::FirstArgument {
6054 second_argument: None,
6055 } => self.edits.insert(call.end - 1, first_value_text),
6056
6057 // When the value is piped into an echo, to rewrite the pipeline we
6058 // have to insert the value after the `echo` with no parentheses:
6059 // `a |> echo` is rewritten as `echo a`.
6060 PipelineAssignmentKind::Echo => {
6061 self.edits.insert(call.end, format!(" {first_value_text}"))
6062 }
6063 }
6064
6065 let mut action = Vec::with_capacity(1);
6066 CodeActionBuilder::new("Convert to function call")
6067 .kind(CodeActionKind::REFACTOR_REWRITE)
6068 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6069 .preferred(false)
6070 .push_to(&mut action);
6071 action
6072 }
6073}
6074
6075impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
6076 fn visit_typed_expr_pipeline(
6077 &mut self,
6078 location: &'ast SrcSpan,
6079 first_value: &'ast TypedPipelineAssignment,
6080 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6081 finally: &'ast TypedExpr,
6082 finally_kind: &'ast PipelineAssignmentKind,
6083 ) {
6084 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
6085 if within(self.params.range, pipeline_range) {
6086 // We will always desugar the pipeline's first step. If there's no
6087 // intermediate assignment it means we're dealing with a single step
6088 // pipeline and the call is `finally`.
6089 let (call, call_kind) = assignments
6090 .first()
6091 .map(|(call, kind)| (call.location, *kind))
6092 .unwrap_or_else(|| (finally.location(), *finally_kind));
6093
6094 self.locations = Some(ConvertToFunctionCallLocations {
6095 first_value: first_value.location,
6096 call,
6097 call_kind,
6098 });
6099
6100 ast::visit::visit_typed_expr_pipeline(
6101 self,
6102 location,
6103 first_value,
6104 assignments,
6105 finally,
6106 finally_kind,
6107 );
6108 }
6109 }
6110}
6111
6112/// Builder for code action to inline a variable.
6113///
6114pub struct InlineVariable<'a> {
6115 module: &'a Module,
6116 params: &'a CodeActionParams,
6117 edits: TextEdits<'a>,
6118 actions: Vec<CodeAction>,
6119}
6120
6121impl<'a> InlineVariable<'a> {
6122 pub fn new(
6123 module: &'a Module,
6124 line_numbers: &'a LineNumbers,
6125 params: &'a CodeActionParams,
6126 ) -> Self {
6127 Self {
6128 module,
6129 params,
6130 edits: TextEdits::new(line_numbers),
6131 actions: Vec::new(),
6132 }
6133 }
6134
6135 pub fn code_actions(mut self) -> Vec<CodeAction> {
6136 self.visit_typed_module(&self.module.ast);
6137
6138 self.actions
6139 }
6140
6141 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) {
6142 let references =
6143 FindVariableReferences::new(location, name).find_in_module(&self.module.ast);
6144 let reference = if references.len() == 1 {
6145 references
6146 .into_iter()
6147 .next()
6148 .expect("References has length 1")
6149 } else {
6150 return;
6151 };
6152
6153 let Some(ast::Statement::Assignment(assignment)) =
6154 self.module.ast.find_statement(location.start)
6155 else {
6156 return;
6157 };
6158
6159 // If the assignment does not simple bind a variable, for example:
6160 // ```gleam
6161 // let #(first, second, third)
6162 // io.println(first)
6163 // // ^ Inline here
6164 // ```
6165 // We can't inline it.
6166 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
6167 return;
6168 }
6169
6170 // If the assignment was generated by the compiler, it doesn't have a
6171 // syntactical representation, so we can't inline it.
6172 if matches!(assignment.kind, AssignmentKind::Generated) {
6173 return;
6174 }
6175
6176 let value_location = assignment.value.location();
6177 let value = self
6178 .module
6179 .code
6180 .get(value_location.start as usize..value_location.end as usize)
6181 .expect("Span is valid");
6182
6183 match reference.kind {
6184 VariableReferenceKind::Variable => {
6185 self.edits.replace(reference.location, value.into());
6186 }
6187 VariableReferenceKind::LabelShorthand => {
6188 self.edits
6189 .insert(reference.location.end, format!(" {value}"));
6190 }
6191 }
6192
6193 let mut location = assignment.location;
6194
6195 let mut chars = self.module.code[location.end as usize..].chars();
6196 // Delete any whitespace after the removed statement
6197 while chars.next().is_some_and(char::is_whitespace) {
6198 location.end += 1;
6199 }
6200
6201 self.edits.delete(location);
6202
6203 CodeActionBuilder::new("Inline variable")
6204 .kind(CodeActionKind::REFACTOR_INLINE)
6205 .changes(
6206 self.params.text_document.uri.clone(),
6207 std::mem::take(&mut self.edits.edits),
6208 )
6209 .preferred(false)
6210 .push_to(&mut self.actions);
6211 }
6212}
6213
6214impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
6215 fn visit_typed_expr_var(
6216 &mut self,
6217 location: &'ast SrcSpan,
6218 constructor: &'ast ValueConstructor,
6219 name: &'ast EcoString,
6220 ) {
6221 let range = self.edits.src_span_to_lsp_range(*location);
6222
6223 if !within(self.params.range, range) {
6224 return;
6225 }
6226
6227 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
6228 &constructor.variant
6229 else {
6230 return;
6231 };
6232
6233 // We can only inline variables assigned by `let` statements, as it
6234 //doesn't make sense to do so with any other kind of variable.
6235 match origin.declaration {
6236 VariableDeclaration::LetPattern => {}
6237 VariableDeclaration::UsePattern
6238 | VariableDeclaration::ClausePattern
6239 | VariableDeclaration::FunctionParameter { .. }
6240 | VariableDeclaration::Generated => return,
6241 }
6242
6243 self.maybe_inline(*location, name.clone());
6244 }
6245
6246 fn visit_typed_pattern_variable(
6247 &mut self,
6248 location: &'ast SrcSpan,
6249 name: &'ast EcoString,
6250 _type: &'ast Arc<Type>,
6251 origin: &'ast VariableOrigin,
6252 ) {
6253 // We can only inline variables assigned by `let` statements, as it
6254 //doesn't make sense to do so with any other kind of variable.
6255 match origin.declaration {
6256 VariableDeclaration::LetPattern => {}
6257 VariableDeclaration::UsePattern
6258 | VariableDeclaration::ClausePattern
6259 | VariableDeclaration::FunctionParameter { .. }
6260 | VariableDeclaration::Generated => return,
6261 }
6262
6263 let range = self.edits.src_span_to_lsp_range(*location);
6264
6265 if !within(self.params.range, range) {
6266 return;
6267 }
6268
6269 self.maybe_inline(*location, name.clone());
6270 }
6271}
6272
6273/// Builder for the "convert to pipe" code action.
6274///
6275/// ```gleam
6276/// pub fn main() {
6277/// wibble(wobble, woo)
6278/// // ^ [convert to pipe]
6279/// }
6280/// ```
6281///
6282/// Will turn the code into the following pipeline:
6283///
6284/// ```gleam
6285/// pub fn main() {
6286/// wobble |> wibble(woo)
6287/// }
6288/// ```
6289///
6290pub struct ConvertToPipe<'a> {
6291 module: &'a Module,
6292 params: &'a CodeActionParams,
6293 edits: TextEdits<'a>,
6294 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
6295 /// this will be true if we're visiting the call on the right hand side of a
6296 /// use expression. So we can skip it and not try to turn it into a
6297 /// function.
6298 visiting_use_call: bool,
6299}
6300
6301/// Holds all the data needed by the "convert to pipe" code action to properly
6302/// rewrite a call into a pipe. Here's what each span means:
6303///
6304/// ```gleam
6305/// wibble(wobb|le, woo)
6306/// // ^^^^^^^^^^^^^^^^^^^^ call
6307/// // ^^^^^^ called
6308/// // ^^^^^^^ arg
6309/// // ^^^ next arg
6310/// ```
6311///
6312/// In this example `position` is 0, since the cursor is over the first
6313/// argument.
6314///
6315pub struct ConvertToPipeArg<'a> {
6316 /// The span of the called function.
6317 called: SrcSpan,
6318 /// The span of the entire function call.
6319 call: SrcSpan,
6320 /// The position (0-based) of the argument.
6321 position: usize,
6322 /// The argument we have to pipe.
6323 arg: &'a TypedCallArg,
6324 /// The span of the argument following the one we have to pipe, if there's
6325 /// any.
6326 next_arg: Option<SrcSpan>,
6327}
6328
6329impl<'a> ConvertToPipe<'a> {
6330 pub fn new(
6331 module: &'a Module,
6332 line_numbers: &'a LineNumbers,
6333 params: &'a CodeActionParams,
6334 ) -> Self {
6335 Self {
6336 module,
6337 params,
6338 edits: TextEdits::new(line_numbers),
6339 visiting_use_call: false,
6340 argument_to_pipe: None,
6341 }
6342 }
6343
6344 pub fn code_actions(mut self) -> Vec<CodeAction> {
6345 self.visit_typed_module(&self.module.ast);
6346
6347 let Some(ConvertToPipeArg {
6348 called,
6349 call,
6350 position,
6351 arg,
6352 next_arg,
6353 }) = self.argument_to_pipe
6354 else {
6355 return vec![];
6356 };
6357
6358 let arg_location = if arg.uses_label_shorthand() {
6359 SrcSpan {
6360 start: arg.location.start,
6361 end: arg.location.end - 1,
6362 }
6363 } else if arg.label.is_some() {
6364 arg.value.location()
6365 } else {
6366 arg.location
6367 };
6368
6369 let arg_text = code_at(self.module, arg_location);
6370 let arg_text = match arg.value {
6371 // If the expression being piped is a binary operation with
6372 // precedence lower than pipes then we have to wrap it in curly
6373 // braces to not mess with the order of operations.
6374 TypedExpr::BinOp { name, .. } if name.precedence() < PIPE_PRECEDENCE => {
6375 &format!("{{ {arg_text} }}")
6376 }
6377 _ => arg_text,
6378 };
6379
6380 match next_arg {
6381 // When extracting an argument we never want to remove any explicit
6382 // label that was written down, so in case it is labelled (be it a
6383 // shorthand or not) we'll always replace the value with a `_`
6384 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
6385 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
6386
6387 // Now we can deal with unlabelled arguments:
6388 // If we're removing the first argument and there's other arguments
6389 // after it, we need to delete the comma that was separating the
6390 // two.
6391 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
6392 start: arg.location.start,
6393 end: next_arg.start,
6394 }),
6395 // Otherwise, if we're deleting the first argument and there's
6396 // no other arguments following it, we remove the call's
6397 // parentheses.
6398 None if position == 0 => self.edits.delete(SrcSpan {
6399 start: called.end,
6400 end: call.end,
6401 }),
6402 // In all other cases we're piping something that is not the first
6403 // argument so we just replace it with an `_`.
6404 _ => self.edits.replace(arg.location, "_".into()),
6405 };
6406
6407 // Finally we can add the argument that was removed as the first step
6408 // of the newly defined pipeline.
6409 self.edits.insert(call.start, format!("{arg_text} |> "));
6410
6411 let mut action = Vec::with_capacity(1);
6412 CodeActionBuilder::new("Convert to pipe")
6413 .kind(CodeActionKind::REFACTOR_REWRITE)
6414 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6415 .preferred(false)
6416 .push_to(&mut action);
6417 action
6418 }
6419}
6420
6421impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
6422 fn visit_typed_expr_call(
6423 &mut self,
6424 location: &'ast SrcSpan,
6425 _type_: &'ast Arc<Type>,
6426 fun: &'ast TypedExpr,
6427 arguments: &'ast [TypedCallArg],
6428 ) {
6429 if arguments.iter().any(|arg| arg.is_capture_hole()) {
6430 return;
6431 }
6432
6433 // If we're visiting the typed function produced by typing a use, we
6434 // skip the thing itself and only visit its arguments and called
6435 // function, that is the body of the use.
6436 if self.visiting_use_call {
6437 self.visiting_use_call = false;
6438 ast::visit::visit_typed_expr(self, fun);
6439 arguments
6440 .iter()
6441 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
6442 return;
6443 }
6444
6445 // We only visit a call if the cursor is somewhere within its location,
6446 // otherwise we skip it entirely.
6447 let call_range = self.edits.src_span_to_lsp_range(*location);
6448 if !within(self.params.range, call_range) {
6449 return;
6450 }
6451
6452 // If the cursor is over any of the arguments then we'll use that as
6453 // the one to extract.
6454 // Otherwise the cursor must be over the called function, in that case
6455 // we extract the first argument (if there's one):
6456 //
6457 // ```gleam
6458 // wibble(wobble, woo)
6459 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
6460 // // ^^^ pipe the second argument if I'm here
6461 // ```
6462 let argument_to_pipe = arguments
6463 .iter()
6464 .enumerate()
6465 .find_map(|(position, arg)| {
6466 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
6467 if within(self.params.range, arg_range) {
6468 Some((position, arg))
6469 } else {
6470 None
6471 }
6472 })
6473 .or_else(|| arguments.first().map(|argument| (0, argument)));
6474
6475 // If we're not hovering over any of the arguments _or_ there's no
6476 // argument to extract at all we just return, there's nothing we can do
6477 // on this call or any of its arguments (since we've determined the
6478 // cursor is not over any of those).
6479 let Some((position, arg)) = argument_to_pipe else {
6480 return;
6481 };
6482
6483 self.argument_to_pipe = Some(ConvertToPipeArg {
6484 called: fun.location(),
6485 call: *location,
6486 position,
6487 arg,
6488 next_arg: arguments
6489 .get(position + 1)
6490 .map(|argument| argument.location),
6491 })
6492 }
6493
6494 fn visit_typed_expr_pipeline(
6495 &mut self,
6496 _location: &'ast SrcSpan,
6497 first_value: &'ast TypedPipelineAssignment,
6498 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6499 _finally: &'ast TypedExpr,
6500 _finally_kind: &'ast PipelineAssignmentKind,
6501 ) {
6502 // We can only apply the action on the first step of a pipeline, so we
6503 // visit just that one and skip all the others.
6504 ast::visit::visit_typed_pipeline_assignment(self, first_value);
6505 }
6506
6507 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
6508 self.visiting_use_call = true;
6509 ast::visit::visit_typed_use(self, use_);
6510 }
6511}
6512
6513/// Code action to interpolate a string. If the cursor is inside the string
6514/// (not selecting anything) the language server will offer to split it:
6515///
6516/// ```gleam
6517/// "wibble | wobble"
6518/// // ^ [Split string]
6519/// // Will produce the following
6520/// "wibble " <> todo <> " wobble"
6521/// ```
6522///
6523/// If the cursor is selecting an entire valid gleam name, then the language
6524/// server will offer to interpolate it as a variable:
6525///
6526/// ```gleam
6527/// "wibble wobble woo"
6528/// // ^^^^^^ [Interpolate variable]
6529/// // Will produce the following
6530/// "wibble " <> wobble <> " woo"
6531/// ```
6532///
6533/// > Note: the cursor won't end up right after the inserted variable/todo.
6534/// > that's a bit annoying, but in a future LSP version we will be able to
6535/// > isnert tab stops to allow one to jump to the newly added variable/todo.
6536///
6537pub struct InterpolateString<'a> {
6538 module: &'a Module,
6539 params: &'a CodeActionParams,
6540 edits: TextEdits<'a>,
6541 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
6542 string_literal_position: StringLiteralPosition,
6543}
6544
6545#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6546pub enum StringLiteralPosition {
6547 FirstPipelineStep,
6548 Other,
6549}
6550
6551#[derive(Clone, Copy)]
6552enum StringInterpolation {
6553 InterpolateValue { value_location: SrcSpan },
6554 SplitString { split_at: u32 },
6555}
6556
6557impl<'a> InterpolateString<'a> {
6558 pub fn new(
6559 module: &'a Module,
6560 line_numbers: &'a LineNumbers,
6561 params: &'a CodeActionParams,
6562 ) -> Self {
6563 Self {
6564 module,
6565 params,
6566 edits: TextEdits::new(line_numbers),
6567 string_interpolation: None,
6568 string_literal_position: StringLiteralPosition::Other,
6569 }
6570 }
6571
6572 pub fn code_actions(mut self) -> Vec<CodeAction> {
6573 self.visit_typed_module(&self.module.ast);
6574
6575 let Some((string_location, interpolation)) = self.string_interpolation else {
6576 return vec![];
6577 };
6578
6579 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
6580 self.edits.insert(string_location.start, "{ ".into());
6581 }
6582
6583 match interpolation {
6584 StringInterpolation::InterpolateValue { value_location } => {
6585 let name = self
6586 .module
6587 .code
6588 .get(value_location.start as usize..value_location.end as usize)
6589 .expect("invalid value range");
6590
6591 if is_valid_lowercase_name(name) {
6592 self.edits
6593 .insert(value_location.start, format!("\" <> {name} <> \""));
6594 self.edits.delete(value_location);
6595 } else if self.can_split_string_at(value_location.end) {
6596 // If the string is not a valid name we just try and split
6597 // the string at the end of the selection.
6598 self.edits
6599 .insert(value_location.end, "\" <> todo <> \"".into());
6600 } else {
6601 // Otherwise there's no meaningful action we can do.
6602 return vec![];
6603 }
6604 }
6605
6606 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
6607 self.edits.insert(split_at, "\" <> todo <> \"".into());
6608 }
6609
6610 StringInterpolation::SplitString { .. } => return vec![],
6611 };
6612
6613 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
6614 self.edits.insert(string_location.end, " }".into());
6615 }
6616
6617 let mut action = Vec::with_capacity(1);
6618 CodeActionBuilder::new("Interpolate string")
6619 .kind(CodeActionKind::REFACTOR_REWRITE)
6620 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6621 .preferred(false)
6622 .push_to(&mut action);
6623 action
6624 }
6625
6626 fn can_split_string_at(&self, at: u32) -> bool {
6627 self.string_interpolation
6628 .is_some_and(|(string_location, _)| {
6629 !(at <= string_location.start + 1 || at >= string_location.end - 1)
6630 })
6631 }
6632
6633 fn visit_literal_string(
6634 &mut self,
6635 string_location: SrcSpan,
6636 string_position: StringLiteralPosition,
6637 ) {
6638 // We can only interpolate/split a string if the cursor is somewhere
6639 // within its location, otherwise we skip it.
6640 let string_range = self.edits.src_span_to_lsp_range(string_location);
6641 if !within(self.params.range, string_range) {
6642 return;
6643 }
6644
6645 let selection @ SrcSpan { start, end } =
6646 self.edits.lsp_range_to_src_span(self.params.range);
6647
6648 let interpolation = if start == end {
6649 StringInterpolation::SplitString { split_at: start }
6650 } else {
6651 StringInterpolation::InterpolateValue {
6652 value_location: selection,
6653 }
6654 };
6655 self.string_interpolation = Some((string_location, interpolation));
6656 self.string_literal_position = string_position;
6657 }
6658}
6659
6660impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
6661 fn visit_typed_expr_string(
6662 &mut self,
6663 location: &'ast SrcSpan,
6664 _type_: &'ast Arc<Type>,
6665 _value: &'ast EcoString,
6666 ) {
6667 self.visit_literal_string(*location, StringLiteralPosition::Other);
6668 }
6669
6670 fn visit_typed_expr_pipeline(
6671 &mut self,
6672 _location: &'ast SrcSpan,
6673 first_value: &'ast TypedPipelineAssignment,
6674 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6675 finally: &'ast TypedExpr,
6676 _finally_kind: &'ast PipelineAssignmentKind,
6677 ) {
6678 if first_value.value.is_literal_string() {
6679 self.visit_literal_string(
6680 first_value.location,
6681 StringLiteralPosition::FirstPipelineStep,
6682 );
6683 } else {
6684 ast::visit::visit_typed_pipeline_assignment(self, first_value);
6685 }
6686
6687 assignments
6688 .iter()
6689 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
6690 self.visit_typed_expr(finally);
6691 }
6692}
6693
6694/// Code action to replace a `..` in a pattern with all the missing fields that
6695/// have not been explicitly provided; labelled ones are introduced with the
6696/// shorthand syntax.
6697///
6698/// ```gleam
6699/// pub type Pokemon {
6700/// Pokemon(Int, name: String, moves: List(String))
6701/// }
6702///
6703/// pub fn main() {
6704/// let Pokemon(..) = todo
6705/// // ^^ Cursor over the spread
6706/// }
6707/// ```
6708/// Would become
6709/// ```gleam
6710/// pub fn main() {
6711/// let Pokemon(int, name:, moves:) = todo
6712/// }
6713///
6714pub struct FillUnusedFields<'a> {
6715 module: &'a Module,
6716 params: &'a CodeActionParams,
6717 edits: TextEdits<'a>,
6718 data: Option<FillUnusedFieldsData>,
6719}
6720
6721pub struct FillUnusedFieldsData {
6722 /// All the missing positional and labelled fields.
6723 positional: Vec<Arc<Type>>,
6724 labelled: Vec<(EcoString, Arc<Type>)>,
6725 /// We need this in order to tell where the missing positional arguments
6726 /// should be inserted.
6727 first_labelled_argument_start: Option<u32>,
6728 /// The end of the final argument before the spread, if there's any.
6729 /// We'll use this to delete everything that comes after the final argument,
6730 /// after adding all the ignored fields.
6731 last_argument_end: Option<u32>,
6732 spread_location: SrcSpan,
6733}
6734
6735impl<'a> FillUnusedFields<'a> {
6736 pub fn new(
6737 module: &'a Module,
6738 line_numbers: &'a LineNumbers,
6739 params: &'a CodeActionParams,
6740 ) -> Self {
6741 Self {
6742 module,
6743 params,
6744 edits: TextEdits::new(line_numbers),
6745 data: None,
6746 }
6747 }
6748
6749 pub fn code_actions(mut self) -> Vec<CodeAction> {
6750 self.visit_typed_module(&self.module.ast);
6751
6752 let Some(FillUnusedFieldsData {
6753 positional,
6754 labelled,
6755 first_labelled_argument_start,
6756 last_argument_end,
6757 spread_location,
6758 }) = self.data
6759 else {
6760 return vec![];
6761 };
6762
6763 // Do not suggest this code action if there's no ignored fields at all.
6764 if positional.is_empty() && labelled.is_empty() {
6765 return vec![];
6766 };
6767
6768 // We add all the missing positional arguments before the first
6769 // labelled one (and so after all the already existing positional ones).
6770 if !positional.is_empty() {
6771 // We want to make sure that all positional args will have a name
6772 // that's different from any label. So we add those as already used
6773 // names.
6774 let mut names = NameGenerator::new();
6775 for (label, _) in labelled.iter() {
6776 names.add_used_name(label.clone());
6777 }
6778
6779 let positional_arguments = positional
6780 .iter()
6781 .map(|type_| names.generate_name_from_type(type_))
6782 .join(", ");
6783 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
6784
6785 // The positional arguments are going to be followed by some other
6786 // arguments if there's some already existing labelled args
6787 // (`last_argument_end.is_some`), of if we're adding those labelled args
6788 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
6789 // final positional argument we're adding to separate it from the ones that
6790 // are going to come after.
6791 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
6792 let positional_arguments = if has_arguments_after {
6793 format!("{positional_arguments}, ")
6794 } else {
6795 positional_arguments
6796 };
6797
6798 self.edits.insert(insert_at, positional_arguments);
6799 }
6800
6801 if !labelled.is_empty() {
6802 // If there's labelled arguments to add, we replace the existing spread
6803 // with the arguments to be added. This way commas and all should already
6804 // be correct.
6805 let labelled_arguments = labelled
6806 .iter()
6807 .map(|(label, _)| format!("{label}:"))
6808 .join(", ");
6809 self.edits.replace(spread_location, labelled_arguments);
6810 } else if let Some(delete_start) = last_argument_end {
6811 // However, if there's no labelled arguments to insert we still need
6812 // to delete the entire spread: we start deleting from the end of the
6813 // final argument, if there's one.
6814 // This way we also get rid of any comma separating the last argument
6815 // and the spread to be removed.
6816 self.edits
6817 .delete(SrcSpan::new(delete_start, spread_location.end))
6818 } else {
6819 // Otherwise we just delete the spread.
6820 self.edits.delete(spread_location)
6821 }
6822
6823 let mut action = Vec::with_capacity(1);
6824 CodeActionBuilder::new("Fill unused fields")
6825 .kind(CodeActionKind::REFACTOR_REWRITE)
6826 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6827 .preferred(false)
6828 .push_to(&mut action);
6829 action
6830 }
6831}
6832
6833impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
6834 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
6835 // We can only interpolate/split a string if the cursor is somewhere
6836 // within its location, otherwise we skip it.
6837 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
6838 if !within(self.params.range, pattern_range) {
6839 return;
6840 }
6841
6842 if let TypedPattern::Constructor {
6843 arguments,
6844 spread: Some(spread_location),
6845 ..
6846 } = pattern
6847 && let Some(PatternUnusedArguments {
6848 positional,
6849 labelled,
6850 }) = pattern.unused_arguments()
6851 {
6852 // If there's any unused argument that's being ignored we want to
6853 // suggest the code action.
6854 let first_labelled_argument_start = arguments
6855 .iter()
6856 .find(|arg| !arg.is_implicit() && arg.label.is_some())
6857 .map(|arg| arg.location.start);
6858
6859 let last_argument_end = arguments
6860 .iter()
6861 .filter(|arg| !arg.is_implicit())
6862 .next_back()
6863 .map(|arg| arg.location.end);
6864
6865 self.data = Some(FillUnusedFieldsData {
6866 positional,
6867 labelled,
6868 first_labelled_argument_start,
6869 last_argument_end,
6870 spread_location: *spread_location,
6871 });
6872 };
6873
6874 ast::visit::visit_typed_pattern(self, pattern);
6875 }
6876}
6877
6878/// Code action to remove an echo.
6879///
6880pub struct RemoveEchos<'a> {
6881 module: &'a Module,
6882 params: &'a CodeActionParams,
6883 edits: TextEdits<'a>,
6884 is_hovering_echo: bool,
6885 echo_spans_to_delete: Vec<SrcSpan>,
6886 // We need to keep a reference to the two latest pipeline assignments we
6887 // run into to properly delete an echo that's inside a pipeline.
6888 latest_pipe_step: Option<SrcSpan>,
6889 second_to_latest_pipe_step: Option<SrcSpan>,
6890}
6891
6892impl<'a> RemoveEchos<'a> {
6893 pub fn new(
6894 module: &'a Module,
6895 line_numbers: &'a LineNumbers,
6896 params: &'a CodeActionParams,
6897 ) -> Self {
6898 Self {
6899 module,
6900 params,
6901 edits: TextEdits::new(line_numbers),
6902 is_hovering_echo: false,
6903 echo_spans_to_delete: vec![],
6904 latest_pipe_step: None,
6905 second_to_latest_pipe_step: None,
6906 }
6907 }
6908
6909 pub fn code_actions(mut self) -> Vec<CodeAction> {
6910 self.visit_typed_module(&self.module.ast);
6911
6912 // We only want to trigger the action if we're over one of the echos in
6913 // the module
6914 if !self.is_hovering_echo {
6915 return vec![];
6916 };
6917
6918 for span in self.echo_spans_to_delete {
6919 self.edits.delete(span);
6920 }
6921
6922 let mut action = Vec::with_capacity(1);
6923 CodeActionBuilder::new("Remove all `echo`s from this module")
6924 .kind(CodeActionKind::REFACTOR_REWRITE)
6925 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6926 .preferred(false)
6927 .push_to(&mut action);
6928 action
6929 }
6930
6931 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
6932 for i in 0..statements.len() {
6933 let statement = statements
6934 .get(i)
6935 .expect("Statement must exist in iteration");
6936 let next_statement = statements.get(i + 1);
6937 let is_last = i == statements.len() - 1;
6938
6939 match statement {
6940 // We remove any echo that is used as a standalone statement used
6941 // to print a literal value.
6942 //
6943 // ```gleam
6944 // pub fn main() {
6945 // echo "I'm here"
6946 // do_something()
6947 // echo "Safe!"
6948 // do_something_else()
6949 // }
6950 // ```
6951 //
6952 // Here we want to remove not just the echo but also the literal
6953 // strings they're printing.
6954 //
6955 // It's safe to do this only if echo is not the last expression
6956 // in a function's block (otherwise we might change the function's
6957 // return type by removing the entire line) and the value being
6958 // printed is a literal expression.
6959 //
6960 ast::Statement::Expression(TypedExpr::Echo {
6961 location,
6962 expression,
6963 ..
6964 }) if !is_last
6965 && expression.as_ref().is_some_and(|expression| {
6966 expression.is_literal() || expression.is_var()
6967 }) =>
6968 {
6969 let echo_range = self.edits.src_span_to_lsp_range(*location);
6970 if within(self.params.range, echo_range) {
6971 self.is_hovering_echo = true;
6972 }
6973
6974 let end = next_statement
6975 .map(|next| {
6976 let echo_end = location.end;
6977 let next_start = next.location().start;
6978 // We want to remove everything until the start of the
6979 // following statement. However, we have to be careful not to
6980 // delete any comments. So if there's any comment between the
6981 // echo to remove and the next statement, we just delete until
6982 // the comment's start.
6983 self.module
6984 .extra
6985 .first_comment_between(echo_end, next_start)
6986 // For comments we record the start of their content, not of the `//`
6987 // so we're subtracting 2 here to not delete the `//` as well
6988 .map(|comment| comment.start - 2)
6989 .unwrap_or(next_start)
6990 })
6991 .unwrap_or(location.end);
6992
6993 self.echo_spans_to_delete.push(SrcSpan {
6994 start: location.start,
6995 end,
6996 });
6997 }
6998
6999 // Otherwise we visit the statement as usual.
7000 ast::Statement::Expression(_)
7001 | ast::Statement::Assignment(_)
7002 | ast::Statement::Use(_)
7003 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement),
7004 }
7005 }
7006 }
7007}
7008
7009impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
7010 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
7011 self.visit_function_statements(&fun.body);
7012 }
7013
7014 fn visit_typed_expr_fn(
7015 &mut self,
7016 _location: &'ast SrcSpan,
7017 _type_: &'ast Arc<Type>,
7018 _kind: &'ast FunctionLiteralKind,
7019 _arguments: &'ast [TypedArg],
7020 body: &'ast Vec1<TypedStatement>,
7021 _return_annotation: &'ast Option<ast::TypeAst>,
7022 ) {
7023 self.visit_function_statements(body);
7024 }
7025
7026 fn visit_typed_expr_echo(
7027 &mut self,
7028 location: &'ast SrcSpan,
7029 type_: &'ast Arc<Type>,
7030 expression: &'ast Option<Box<TypedExpr>>,
7031 message: &'ast Option<Box<TypedExpr>>,
7032 ) {
7033 // We also want to trigger the action if we're hovering over the expression
7034 // being printed. So we create a unique span starting from the start of echo
7035 // end ending at the end of the expression.
7036 //
7037 // ```
7038 // echo 1 + 2
7039 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
7040 // inside it, not just the keyword
7041 // ```
7042 //
7043 let echo_range = self.edits.src_span_to_lsp_range(*location);
7044 if within(self.params.range, echo_range) {
7045 self.is_hovering_echo = true;
7046 }
7047
7048 // We also want to remove the echo message!
7049 if message.is_some() {
7050 let start = expression
7051 .as_ref()
7052 .map(|expression| expression.location().end)
7053 .unwrap_or(location.start + 4);
7054
7055 self.echo_spans_to_delete
7056 .push(SrcSpan::new(start, location.end));
7057 }
7058
7059 if let Some(expression) = expression {
7060 // If there's an expression we delete everything we find until its
7061 // start (excluded).
7062 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
7063 self.echo_spans_to_delete.push(span_to_delete);
7064 } else {
7065 // Othwerise we know we're inside a pipeline, we take the closest step
7066 // that is not echo itself and delete everything from its end until the
7067 // end of the echo keyword:
7068 //
7069 // ```txt
7070 // wibble |> echo |> wobble
7071 // ^^^^^^^^ This span right here
7072 // ```
7073 let step_preceding_echo = self
7074 .latest_pipe_step
7075 .filter(|l| l != location)
7076 .or(self.second_to_latest_pipe_step);
7077 if let Some(step_preceding_echo) = step_preceding_echo {
7078 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
7079 self.echo_spans_to_delete.push(span_to_delete);
7080 }
7081 }
7082
7083 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message);
7084 }
7085
7086 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
7087 if self.latest_pipe_step.is_some() {
7088 self.second_to_latest_pipe_step = self.latest_pipe_step;
7089 }
7090 self.latest_pipe_step = Some(assignment.location);
7091 ast::visit::visit_typed_pipeline_assignment(self, assignment);
7092 }
7093}
7094
7095/// Code action to wrap assignment and case clause values in a block.
7096///
7097/// ```gleam
7098/// pub type PokemonType {
7099/// Fire
7100/// Water
7101/// }
7102///
7103/// pub fn main() {
7104/// let pokemon_type: PokemonType = todo
7105/// case pokemon_type {
7106/// Water -> soak()
7107/// ^^^^^^ Cursor over the spread
7108/// Fire -> burn()
7109/// }
7110/// }
7111/// ```
7112/// Becomes
7113/// ```gleam
7114/// pub type PokemonType {
7115/// Fire
7116/// Water
7117/// }
7118///
7119/// pub fn main() {
7120/// let pokemon_type: PokemonType = todo
7121/// case pokemon_type {
7122/// Water -> {
7123/// soak()
7124/// }
7125/// Fire -> burn()
7126/// }
7127/// }
7128/// ```
7129///
7130pub struct WrapInBlock<'a> {
7131 module: &'a Module,
7132 params: &'a CodeActionParams,
7133 edits: TextEdits<'a>,
7134 selected_expression: Option<SrcSpan>,
7135}
7136
7137impl<'a> WrapInBlock<'a> {
7138 pub fn new(
7139 module: &'a Module,
7140 line_numbers: &'a LineNumbers,
7141 params: &'a CodeActionParams,
7142 ) -> Self {
7143 Self {
7144 module,
7145 params,
7146 edits: TextEdits::new(line_numbers),
7147 selected_expression: None,
7148 }
7149 }
7150
7151 pub fn code_actions(mut self) -> Vec<CodeAction> {
7152 self.visit_typed_module(&self.module.ast);
7153
7154 let Some(expr_span) = self.selected_expression else {
7155 return vec![];
7156 };
7157
7158 let Some(expr_string) = self
7159 .module
7160 .code
7161 .get(expr_span.start as usize..(expr_span.end as usize + 1))
7162 else {
7163 return vec![];
7164 };
7165
7166 let range = self
7167 .edits
7168 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
7169
7170 let indent_size =
7171 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
7172
7173 let expr_indent_size = indent_size + 2;
7174
7175 let indent = " ".repeat(indent_size);
7176 let inner_indent = " ".repeat(expr_indent_size);
7177
7178 self.edits.replace(
7179 expr_span,
7180 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
7181 );
7182
7183 let mut action = Vec::with_capacity(1);
7184 CodeActionBuilder::new("Wrap in block")
7185 .kind(CodeActionKind::REFACTOR_EXTRACT)
7186 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7187 .preferred(false)
7188 .push_to(&mut action);
7189 action
7190 }
7191}
7192
7193impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
7194 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
7195 ast::visit::visit_typed_expr(self, &assignment.value);
7196 if !within(
7197 self.params.range,
7198 self.edits
7199 .src_span_to_lsp_range(assignment.value.location()),
7200 ) {
7201 return;
7202 }
7203 match &assignment.value {
7204 // To avoid wrapping the same expression in multiple, nested blocks.
7205 TypedExpr::Block { .. } => {}
7206 TypedExpr::RecordAccess { .. }
7207 | TypedExpr::Int { .. }
7208 | TypedExpr::Float { .. }
7209 | TypedExpr::String { .. }
7210 | TypedExpr::Pipeline { .. }
7211 | TypedExpr::Var { .. }
7212 | TypedExpr::Fn { .. }
7213 | TypedExpr::List { .. }
7214 | TypedExpr::Call { .. }
7215 | TypedExpr::BinOp { .. }
7216 | TypedExpr::Case { .. }
7217 | TypedExpr::ModuleSelect { .. }
7218 | TypedExpr::Tuple { .. }
7219 | TypedExpr::TupleIndex { .. }
7220 | TypedExpr::Todo { .. }
7221 | TypedExpr::Panic { .. }
7222 | TypedExpr::Echo { .. }
7223 | TypedExpr::BitArray { .. }
7224 | TypedExpr::RecordUpdate { .. }
7225 | TypedExpr::NegateBool { .. }
7226 | TypedExpr::NegateInt { .. }
7227 | TypedExpr::Invalid { .. } => {
7228 self.selected_expression = Some(assignment.value.location());
7229 }
7230 };
7231 ast::visit::visit_typed_assignment(self, assignment);
7232 }
7233
7234 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
7235 ast::visit::visit_typed_clause(self, clause);
7236
7237 if !within(
7238 self.params.range,
7239 self.edits.src_span_to_lsp_range(clause.then.location()),
7240 ) {
7241 return;
7242 }
7243
7244 // To avoid wrapping the same expression in multiple, nested blocks.
7245 if !matches!(clause.then, TypedExpr::Block { .. }) {
7246 self.selected_expression = Some(clause.then.location());
7247 };
7248
7249 ast::visit::visit_typed_clause(self, clause);
7250 }
7251}
7252
7253/// Code action to fix wrong binary operators when the compiler can easily tell
7254/// what the correct alternative is.
7255///
7256/// ```gleam
7257/// 1 +. 2 // becomes 1 + 2
7258/// 1.0 + 2.3 // becomes 1.0 +. 2.3
7259/// ```
7260///
7261pub struct FixBinaryOperation<'a> {
7262 module: &'a Module,
7263 params: &'a CodeActionParams,
7264 edits: TextEdits<'a>,
7265 fix: Option<(SrcSpan, ast::BinOp)>,
7266}
7267
7268impl<'a> FixBinaryOperation<'a> {
7269 pub fn new(
7270 module: &'a Module,
7271 line_numbers: &'a LineNumbers,
7272 params: &'a CodeActionParams,
7273 ) -> Self {
7274 Self {
7275 module,
7276 params,
7277 edits: TextEdits::new(line_numbers),
7278 fix: None,
7279 }
7280 }
7281
7282 pub fn code_actions(mut self) -> Vec<CodeAction> {
7283 self.visit_typed_module(&self.module.ast);
7284
7285 let Some((location, replacement)) = self.fix else {
7286 return vec![];
7287 };
7288
7289 self.edits.replace(location, replacement.name().into());
7290
7291 let mut action = Vec::with_capacity(1);
7292 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
7293 .kind(CodeActionKind::REFACTOR_REWRITE)
7294 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7295 .preferred(true)
7296 .push_to(&mut action);
7297 action
7298 }
7299}
7300
7301impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
7302 fn visit_typed_expr_bin_op(
7303 &mut self,
7304 location: &'ast SrcSpan,
7305 type_: &'ast Arc<Type>,
7306 name: &'ast ast::BinOp,
7307 name_location: &'ast SrcSpan,
7308 left: &'ast TypedExpr,
7309 right: &'ast TypedExpr,
7310 ) {
7311 let binop_range = self.edits.src_span_to_lsp_range(*location);
7312 if !within(self.params.range, binop_range) {
7313 return;
7314 }
7315
7316 if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() {
7317 self.fix = name.float_equivalent().map(|fix| (*name_location, fix));
7318 } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() {
7319 self.fix = name.int_equivalent().map(|fix| (*name_location, fix))
7320 } else if *name == ast::BinOp::AddInt
7321 && left.type_().is_string()
7322 && right.type_().is_string()
7323 {
7324 self.fix = Some((*name_location, ast::BinOp::Concatenate))
7325 }
7326
7327 ast::visit::visit_typed_expr_bin_op(
7328 self,
7329 location,
7330 type_,
7331 name,
7332 name_location,
7333 left,
7334 right,
7335 );
7336 }
7337}
7338
7339/// Code action builder to automatically fix segments that have a value that's
7340/// guaranteed to overflow.
7341///
7342pub struct FixTruncatedBitArraySegment<'a> {
7343 module: &'a Module,
7344 params: &'a CodeActionParams,
7345 edits: TextEdits<'a>,
7346 truncation: Option<BitArraySegmentTruncation>,
7347}
7348
7349impl<'a> FixTruncatedBitArraySegment<'a> {
7350 pub fn new(
7351 module: &'a Module,
7352 line_numbers: &'a LineNumbers,
7353 params: &'a CodeActionParams,
7354 ) -> Self {
7355 Self {
7356 module,
7357 params,
7358 edits: TextEdits::new(line_numbers),
7359 truncation: None,
7360 }
7361 }
7362
7363 pub fn code_actions(mut self) -> Vec<CodeAction> {
7364 self.visit_typed_module(&self.module.ast);
7365
7366 let Some(truncation) = self.truncation else {
7367 return vec![];
7368 };
7369
7370 let replacement = truncation.truncated_into.to_string();
7371 self.edits
7372 .replace(truncation.value_location, replacement.clone());
7373
7374 let mut action = Vec::with_capacity(1);
7375 CodeActionBuilder::new(&format!("Replace with `{replacement}`"))
7376 .kind(CodeActionKind::REFACTOR_REWRITE)
7377 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7378 .preferred(true)
7379 .push_to(&mut action);
7380 action
7381 }
7382}
7383
7384impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
7385 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
7386 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
7387 if !within(self.params.range, segment_range) {
7388 return;
7389 }
7390
7391 if let Some(truncation) = segment.check_for_truncated_value() {
7392 self.truncation = Some(truncation);
7393 }
7394
7395 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
7396 }
7397}
7398
7399/// Code action builder to remove unused imports and values.
7400///
7401pub struct RemoveUnusedImports<'a> {
7402 module: &'a Module,
7403 params: &'a CodeActionParams,
7404 imports: Vec<&'a Import<EcoString>>,
7405 edits: TextEdits<'a>,
7406}
7407
7408#[derive(Debug)]
7409enum UnusedImport {
7410 ValueOrType(SrcSpan),
7411 Module(SrcSpan),
7412 ModuleAlias(SrcSpan),
7413}
7414
7415impl UnusedImport {
7416 fn location(&self) -> SrcSpan {
7417 match self {
7418 UnusedImport::ValueOrType(location)
7419 | UnusedImport::Module(location)
7420 | UnusedImport::ModuleAlias(location) => *location,
7421 }
7422 }
7423}
7424
7425impl<'a> RemoveUnusedImports<'a> {
7426 pub fn new(
7427 module: &'a Module,
7428 line_numbers: &'a LineNumbers,
7429 params: &'a CodeActionParams,
7430 ) -> Self {
7431 Self {
7432 module,
7433 params,
7434 edits: TextEdits::new(line_numbers),
7435 imports: vec![],
7436 }
7437 }
7438
7439 /// Given an import location, returns a list of the spans of all the
7440 /// unqualified values it's importing. Sorted by SrcSpan location.
7441 ///
7442 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> {
7443 self.imports
7444 .iter()
7445 .find(|import| import.location.contains(import_location.start))
7446 .map(|import| {
7447 let types = import.unqualified_types.iter().map(|type_| type_.location);
7448 let values = import.unqualified_values.iter().map(|value| value.location);
7449 types
7450 .chain(values)
7451 .sorted_by_key(|location| location.start)
7452 .collect_vec()
7453 })
7454 .unwrap_or_default()
7455 }
7456
7457 pub fn code_actions(mut self) -> Vec<CodeAction> {
7458 // If there's no import in the module then there can't be any unused
7459 // import to remove.
7460 self.visit_typed_module(&self.module.ast);
7461 if self.imports.is_empty() {
7462 return vec![];
7463 }
7464
7465 let unused_imports = (self.module.ast.type_info.warnings.iter())
7466 .filter_map(|warning| match warning {
7467 type_::Warning::UnusedImportedValue { location, .. } => {
7468 Some(UnusedImport::ValueOrType(*location))
7469 }
7470 type_::Warning::UnusedType {
7471 location,
7472 imported: true,
7473 ..
7474 } => Some(UnusedImport::ValueOrType(*location)),
7475 type_::Warning::UnusedImportedModule { location, .. } => {
7476 Some(UnusedImport::Module(*location))
7477 }
7478 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
7479 Some(UnusedImport::ModuleAlias(*location))
7480 }
7481 _ => None,
7482 })
7483 .sorted_by_key(|import| import.location())
7484 .collect_vec();
7485
7486 // If the cursor is not over any of the unused imports then we don't offer
7487 // the code action.
7488 let hovering_unused_import = unused_imports.iter().any(|import| {
7489 let unused_range = self.edits.src_span_to_lsp_range(import.location());
7490 overlaps(self.params.range, unused_range)
7491 });
7492 if !hovering_unused_import {
7493 return vec![];
7494 }
7495
7496 // Otherwise we start removing all unused imports:
7497 for import in &unused_imports {
7498 match import {
7499 // When an entire module is unused we can delete its entire location
7500 // in the source code.
7501 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
7502 if self.edits.line_numbers.spans_entire_line(location) {
7503 // If the unused module spans over the entire line then
7504 // we also take care of removing the following newline
7505 // characther!
7506 self.edits.delete(SrcSpan {
7507 start: location.start,
7508 end: location.end + 1,
7509 })
7510 } else {
7511 self.edits.delete(*location)
7512 }
7513 }
7514
7515 // When removing unused imported values we have to be a bit more
7516 // careful: an unused value might be followed or preceded by a
7517 // comma that we also need to remove!
7518 UnusedImport::ValueOrType(location) => {
7519 let imported = self.imported_values(*location);
7520 let unused_index = imported.binary_search(location);
7521 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1);
7522 let next_value = unused_index
7523 .ok()
7524 .and_then(|value_index| imported.get(value_index + 1));
7525 let previous_value = unused_index.ok().and_then(|value_index| {
7526 value_index
7527 .checked_sub(1)
7528 .and_then(|previous_index| imported.get(previous_index))
7529 });
7530 let previous_is_unused = previous_value.is_some_and(|previous| {
7531 unused_imports
7532 .as_slice()
7533 .binary_search_by_key(previous, |import| import.location())
7534 .is_ok()
7535 });
7536
7537 match (previous_value, next_value) {
7538 // If there's a value following the unused import we need
7539 // to remove all characters until its start!
7540 //
7541 // ```gleam
7542 // import wibble.{unused, used}
7543 // // ^^^^^^^^^^^ We need to remove all of this!
7544 // ```
7545 //
7546 (_, Some(next_value)) => self.edits.delete(SrcSpan {
7547 start: location.start,
7548 end: next_value.start,
7549 }),
7550
7551 // If this unused import is the last of the unuqualified
7552 // list and is preceded by another used value then we
7553 // need to do some additional cleanup and remove all
7554 // characters starting from its end.
7555 // (If the previous one is unused as well it will take
7556 // care of removing all the extra space)
7557 //
7558 // ```gleam
7559 // import wibble.{used, unused}
7560 // // ^^^^^^^^^^^^ We need to remove all of this!
7561 // ```
7562 //
7563 (Some(previous_value), _) if is_last && !previous_is_unused => {
7564 self.edits.delete(SrcSpan {
7565 start: previous_value.end,
7566 end: location.end,
7567 })
7568 }
7569
7570 // In all other cases it means that this is the only
7571 // item in the import list. We can just remove it.
7572 //
7573 // ```gleam
7574 // import wibble.{unused}
7575 // // ^^^^^^ We remove this import, the formatter will already
7576 // // take care of removing the empty curly braces
7577 // ```
7578 //
7579 (_, _) => self.edits.delete(*location),
7580 }
7581 }
7582 }
7583 }
7584
7585 let mut action = Vec::with_capacity(1);
7586 CodeActionBuilder::new("Remove unused imports")
7587 .kind(CodeActionKind::REFACTOR_REWRITE)
7588 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7589 .preferred(true)
7590 .push_to(&mut action);
7591 action
7592 }
7593}
7594
7595impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> {
7596 fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) {
7597 self.imports = module
7598 .definitions
7599 .iter()
7600 .filter_map(|definition| match definition {
7601 ast::Definition::Import(import) => Some(import),
7602 _ => None,
7603 })
7604 .collect_vec();
7605 }
7606}
7607
7608/// Code action to remove a block wrapping a single expression.
7609///
7610pub struct RemoveBlock<'a> {
7611 module: &'a Module,
7612 params: &'a CodeActionParams,
7613 edits: TextEdits<'a>,
7614 block_span: Option<SrcSpan>,
7615 position: RemoveBlockPosition,
7616}
7617
7618#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
7619enum RemoveBlockPosition {
7620 InsideBinOp,
7621 OutsideBinOp,
7622}
7623
7624impl<'a> RemoveBlock<'a> {
7625 pub fn new(
7626 module: &'a Module,
7627 line_numbers: &'a LineNumbers,
7628 params: &'a CodeActionParams,
7629 ) -> Self {
7630 Self {
7631 module,
7632 params,
7633 edits: TextEdits::new(line_numbers),
7634 block_span: None,
7635 position: RemoveBlockPosition::OutsideBinOp,
7636 }
7637 }
7638
7639 pub fn code_actions(mut self) -> Vec<CodeAction> {
7640 self.visit_typed_module(&self.module.ast);
7641
7642 let Some(SrcSpan { start, end }) = self.block_span else {
7643 return vec![];
7644 };
7645
7646 self.edits.delete(SrcSpan::new(start, start + 1));
7647 self.edits.delete(SrcSpan::new(end - 1, end));
7648
7649 let mut action = Vec::with_capacity(1);
7650 CodeActionBuilder::new("Remove block")
7651 .kind(CodeActionKind::REFACTOR_REWRITE)
7652 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7653 .preferred(true)
7654 .push_to(&mut action);
7655 action
7656 }
7657}
7658
7659impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> {
7660 fn visit_typed_expr_bin_op(
7661 &mut self,
7662 _location: &'ast SrcSpan,
7663 _type_: &'ast Arc<Type>,
7664 _name: &'ast ast::BinOp,
7665 _name_location: &'ast SrcSpan,
7666 left: &'ast TypedExpr,
7667 right: &'ast TypedExpr,
7668 ) {
7669 let old_position = self.position;
7670 self.position = RemoveBlockPosition::InsideBinOp;
7671 ast::visit::visit_typed_expr(self, left);
7672 self.position = RemoveBlockPosition::InsideBinOp;
7673 ast::visit::visit_typed_expr(self, right);
7674 self.position = old_position;
7675 }
7676
7677 fn visit_typed_expr_block(
7678 &mut self,
7679 location: &'ast SrcSpan,
7680 statements: &'ast [TypedStatement],
7681 ) {
7682 let block_range = self.edits.src_span_to_lsp_range(*location);
7683 if !within(self.params.range, block_range) {
7684 return;
7685 }
7686
7687 match statements {
7688 [] | [_, _, ..] => (),
7689 [value] => match value {
7690 ast::Statement::Use(_)
7691 | ast::Statement::Assert(_)
7692 | ast::Statement::Assignment(_) => {
7693 ast::visit::visit_typed_expr_block(self, location, statements)
7694 }
7695
7696 ast::Statement::Expression(expr) => match expr {
7697 TypedExpr::Int { .. }
7698 | TypedExpr::Float { .. }
7699 | TypedExpr::String { .. }
7700 | TypedExpr::Block { .. }
7701 | TypedExpr::Var { .. }
7702 | TypedExpr::Fn { .. }
7703 | TypedExpr::List { .. }
7704 | TypedExpr::Call { .. }
7705 | TypedExpr::Case { .. }
7706 | TypedExpr::RecordAccess { .. }
7707 | TypedExpr::ModuleSelect { .. }
7708 | TypedExpr::Tuple { .. }
7709 | TypedExpr::TupleIndex { .. }
7710 | TypedExpr::Todo { .. }
7711 | TypedExpr::Panic { .. }
7712 | TypedExpr::Echo { .. }
7713 | TypedExpr::BitArray { .. }
7714 | TypedExpr::RecordUpdate { .. }
7715 | TypedExpr::NegateBool { .. }
7716 | TypedExpr::NegateInt { .. }
7717 | TypedExpr::Invalid { .. } => {
7718 self.block_span = Some(*location);
7719 }
7720 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => {
7721 if self.position == RemoveBlockPosition::OutsideBinOp {
7722 self.block_span = Some(*location);
7723 }
7724 }
7725 },
7726 },
7727 }
7728
7729 ast::visit::visit_typed_expr_block(self, location, statements);
7730 }
7731}
7732
7733/// Code action to remove `opaque` from a private type.
7734///
7735pub struct RemovePrivateOpaque<'a> {
7736 module: &'a Module,
7737 params: &'a CodeActionParams,
7738 edits: TextEdits<'a>,
7739 opaque_span: Option<SrcSpan>,
7740}
7741
7742impl<'a> RemovePrivateOpaque<'a> {
7743 pub fn new(
7744 module: &'a Module,
7745 line_numbers: &'a LineNumbers,
7746 params: &'a CodeActionParams,
7747 ) -> Self {
7748 Self {
7749 module,
7750 params,
7751 edits: TextEdits::new(line_numbers),
7752 opaque_span: None,
7753 }
7754 }
7755
7756 pub fn code_actions(mut self) -> Vec<CodeAction> {
7757 self.visit_typed_module(&self.module.ast);
7758
7759 let Some(opaque_span) = self.opaque_span else {
7760 return vec![];
7761 };
7762
7763 self.edits.delete(opaque_span);
7764
7765 let mut action = Vec::with_capacity(1);
7766 CodeActionBuilder::new("Remove opaque from private type")
7767 .kind(CodeActionKind::QUICKFIX)
7768 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7769 .preferred(true)
7770 .push_to(&mut action);
7771 action
7772 }
7773}
7774
7775impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> {
7776 fn visit_typed_definition(&mut self, def: &'ast ast::TypedDefinition) {
7777 // This code action is only relevant for type definitions, so we don't
7778 // waste any time visiting definitions that are not relevant.
7779 match def {
7780 ast::Definition::Function(_)
7781 | ast::Definition::TypeAlias(_)
7782 | ast::Definition::Import(_)
7783 | ast::Definition::ModuleConstant(_) => (),
7784 ast::Definition::CustomType(custom_type) => {
7785 self.visit_typed_custom_type(custom_type);
7786 }
7787 }
7788 }
7789
7790 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
7791 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location);
7792 if !within(self.params.range, custom_type_range) {
7793 return;
7794 }
7795
7796 if custom_type.opaque && custom_type.publicity.is_private() {
7797 self.opaque_span = Some(SrcSpan {
7798 start: custom_type.location.start,
7799 end: custom_type.location.start + 7,
7800 })
7801 }
7802 }
7803}
7804
7805/// Code action to rewrite a case expression as part of an outer case expression
7806/// branch. For example:
7807///
7808/// ```gleam
7809/// case wibble {
7810/// Ok(a) -> case a {
7811/// 1 -> todo
7812/// _ -> todo
7813/// }
7814/// Error(_) -> todo
7815/// }
7816/// ```
7817///
7818/// Would become:
7819///
7820/// ```gleam
7821/// case wibble {
7822/// Ok(1) -> todo
7823/// Ok(_) -> todo
7824/// Error(_) -> todo
7825/// }
7826/// ```
7827///
7828pub struct CollapseNestedCase<'a> {
7829 module: &'a Module,
7830 params: &'a CodeActionParams,
7831 edits: TextEdits<'a>,
7832 collapsed: Option<Collapsed<'a>>,
7833}
7834
7835/// This holds all the needed data about the pattern to collapse.
7836/// We'll use this piece of code as an example:
7837/// ```gleam
7838/// case something {
7839/// User(username: _, NotAdmin) -> "Stranger!!"
7840/// User(username:, Admin) if wibble ->
7841/// case username { // <- We're collapsing this nested case
7842/// "Joe" -> "Hello, Joe!"
7843/// _ -> "I don't know you, " <> username
7844/// }
7845/// }
7846/// ```
7847///
7848struct Collapsed<'a> {
7849 /// This is the span covering the entire clause being collapsed:
7850 ///
7851 /// ```gleam
7852 /// case something {
7853 /// User(username: _, NotAdmin) -> "Stranger!!"
7854 /// User(username:, Admin) if wibble ->
7855 /// ┬ It goes all the way from here...
7856 /// ╭─╯
7857 /// │ case username {
7858 /// │ "Joe" -> "Hello, Joe!"
7859 /// │ _ -> "I don't know you, " <> username
7860 /// │ }
7861 /// │ ┬ ...to here!
7862 /// ╰───╯
7863 /// }
7864 /// ```
7865 ///
7866 outer_clause_span: SrcSpan,
7867
7868 /// The (optional) guard of the outer branch. In this exmaple it's this one:
7869 ///
7870 /// ```gleam
7871 /// case something {
7872 /// User(username: _, NotAdmin) -> "Stranger!!"
7873 /// User(username:, Admin) if wibble ->
7874 /// ┬────────
7875 /// ╰─ `outer_guard`
7876 /// case username {
7877 /// "Joe" -> "Hello, Joe!"
7878 /// _ -> "I don't know you, " <> username
7879 /// }
7880 /// }
7881 /// ```
7882 ///
7883 outer_guard: &'a Option<TypedClauseGuard>,
7884
7885 /// The pattern variable being matched on:
7886 ///
7887 /// ```gleam
7888 /// case something {
7889 /// User(username: _, NotAdmin) -> "Stranger!!"
7890 /// User(username:, Admin) if wibble ->
7891 /// ┬───────
7892 /// ╰─ `matched_variable`
7893 /// case username {
7894 /// "Joe" -> "Hello, Joe!"
7895 /// _ -> "I don't know you, " <> username
7896 /// }
7897 /// }
7898 /// ```
7899 ///
7900 matched_variable: BoundVariable,
7901
7902 /// The span covering the entire pattern that is bringing the matched
7903 /// variable in scope:
7904 ///
7905 /// ```gleam
7906 /// case something {
7907 /// User(username: _, NotAdmin) -> "Stranger!!"
7908 /// User(username:, Admin) if wibble ->
7909 /// ┬─────────────────────
7910 /// ╰─ `matched_pattern_span`
7911 /// case username {
7912 /// "Joe" -> "Hello, Joe!"
7913 /// _ -> "I don't know you, " <> username
7914 /// }
7915 /// }
7916 /// ```
7917 ///
7918 matched_pattern_span: SrcSpan,
7919
7920 /// The clauses matching on the `username` variable. In this case they are:
7921 /// ```gleam
7922 /// "Joe" -> "Hello, Joe!"
7923 /// _ -> "I don't know you, " <> username
7924 /// ```
7925 ///
7926 inner_clauses: &'a Vec<ast::TypedClause>,
7927}
7928
7929impl<'a> CollapseNestedCase<'a> {
7930 pub fn new(
7931 module: &'a Module,
7932 line_numbers: &'a LineNumbers,
7933 params: &'a CodeActionParams,
7934 ) -> Self {
7935 Self {
7936 module,
7937 params,
7938 edits: TextEdits::new(line_numbers),
7939 collapsed: None,
7940 }
7941 }
7942
7943 pub fn code_actions(mut self) -> Vec<CodeAction> {
7944 self.visit_typed_module(&self.module.ast);
7945
7946 let Some(Collapsed {
7947 outer_clause_span,
7948 outer_guard,
7949 ref matched_variable,
7950 matched_pattern_span,
7951 inner_clauses,
7952 }) = self.collapsed
7953 else {
7954 return vec![];
7955 };
7956
7957 // Now comes the tricky part: we need to replace the current pattern
7958 // that is bringing the variable into scope with many new patterns, one
7959 // for each of the inner clauses.
7960 //
7961 // Each time we will have to replace the matched variable with the
7962 // pattern used in the inner clause. Let's look at an example:
7963 //
7964 // ```gleam
7965 // Ok(a) -> case a {
7966 // 1 -> wibble
7967 // 2 | 3 -> wobble
7968 // _ -> woo
7969 // }
7970 // ```
7971 //
7972 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then
7973 // with `2` and `3`, and finally with `_`. Obtaining something like
7974 // this:
7975 //
7976 // ```gleam
7977 // Ok(1) -> wibble
7978 // Ok(2) | Ok(3) -> wobble
7979 // Ok(_) -> woo
7980 // ```
7981 //
7982 // Notice one key detail: since alternative patterns can't be nested we
7983 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`!
7984
7985 let pattern_text: String = code_at(self.module, matched_pattern_span).into();
7986 let matched_variable_span = matched_variable.location();
7987
7988 let pattern_with_variable = |new_content: String| {
7989 let mut new_pattern = pattern_text.clone();
7990
7991 match matched_variable {
7992 BoundVariable::Regular { .. } => {
7993 // If the variable is a regular variable we'll have to replace
7994 // it entirely with the new pattern taking its place.
7995 let variable_start_in_pattern =
7996 matched_variable_span.start - matched_pattern_span.start;
7997 let variable_length = matched_variable_span.end - matched_variable_span.start;
7998 let variable_end_in_pattern = variable_start_in_pattern + variable_length;
7999 let replaced_range =
8000 variable_start_in_pattern as usize..variable_end_in_pattern as usize;
8001
8002 new_pattern.replace_range(replaced_range, &new_content);
8003 }
8004
8005 BoundVariable::ShorthandLabel { .. } => {
8006 // But if it's introduced using the shorthand syntax we can't
8007 // just replace it's location with the new pattern: we would be
8008 // removing the label!!
8009 // So we instead insert the pattern right after the label.
8010 new_pattern.insert_str(
8011 (matched_variable_span.end - matched_pattern_span.start) as usize,
8012 &format!(" {new_content}"),
8013 );
8014 }
8015 }
8016
8017 new_pattern
8018 };
8019
8020 let mut new_clauses = vec![];
8021 for clause in inner_clauses {
8022 // Here we take care of unrolling any alterantive patterns: for each
8023 // of the alternatives we build a new pattern and then join
8024 // everything together with ` | `.
8025
8026 let references_to_matched_variable =
8027 FindVariableReferences::new(matched_variable_span, matched_variable.name())
8028 .find(&clause.then);
8029
8030 let new_patterns = iter::once(&clause.pattern)
8031 .chain(&clause.alternative_patterns)
8032 .map(|patterns| {
8033 // If we've reached this point we've already made in the
8034 // traversal that the inner clause is matching on a single
8035 // subject. So this should be safe to expect!
8036 let pattern_location =
8037 patterns.first().expect("must have a pattern").location();
8038
8039 let mut pattern_code = code_at(self.module, pattern_location).to_string();
8040 if !references_to_matched_variable.is_empty() {
8041 pattern_code = format!("{pattern_code} as {}", matched_variable.name());
8042 };
8043 pattern_with_variable(pattern_code)
8044 })
8045 .join(" | ");
8046
8047 let clause_code = code_at(self.module, clause.then.location());
8048 let guard_code = match (outer_guard, &clause.guard) {
8049 (Some(outer), Some(inner)) => {
8050 let mut outer_code = code_at(self.module, outer.location()).to_string();
8051 let mut inner_code = code_at(self.module, inner.location()).to_string();
8052 if ast::BinOp::And.precedence() > outer.precedence() {
8053 outer_code = format!("{{ {outer_code} }}")
8054 }
8055 if ast::BinOp::And.precedence() > inner.precedence() {
8056 inner_code = format!("{{ {inner_code} }}")
8057 }
8058 format!(" if {outer_code} && {inner_code}")
8059 }
8060 (None, Some(guard)) | (Some(guard), None) => {
8061 format!(" if {}", code_at(self.module, guard.location()))
8062 }
8063 (None, None) => "".into(),
8064 };
8065
8066 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}"));
8067 }
8068
8069 let pattern_nesting = self
8070 .edits
8071 .src_span_to_lsp_range(outer_clause_span)
8072 .start
8073 .character;
8074 let indentation = " ".repeat(pattern_nesting as usize);
8075
8076 self.edits.replace(
8077 outer_clause_span,
8078 new_clauses.join(&format!("\n{indentation}")),
8079 );
8080
8081 let mut action = Vec::with_capacity(1);
8082 CodeActionBuilder::new("Collapse nested case")
8083 .kind(CodeActionKind::REFACTOR_REWRITE)
8084 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8085 .preferred(false)
8086 .push_to(&mut action);
8087 action
8088 }
8089
8090 /// If the clause can be flattened because it's matching on a single variable
8091 /// defined in it, this function will return the info needed by the language
8092 /// server to flatten that case.
8093 ///
8094 /// We can only flatten a case expression in a very specific case:
8095 /// - This pattern may be introducing multiple variables,
8096 /// - The expression following this branch must be a case, and
8097 /// - It must be matching on one of those variables
8098 ///
8099 /// For example:
8100 ///
8101 /// ```gleam
8102 /// Wibble(a, b, 1) -> case a { ... }
8103 /// Wibble(a, b, 1) -> case b { ... }
8104 /// ```
8105 ///
8106 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> {
8107 let ast::TypedClause {
8108 pattern,
8109 alternative_patterns,
8110 then,
8111 location,
8112 guard,
8113 } = clause;
8114
8115 if !alternative_patterns.is_empty() {
8116 return None;
8117 }
8118
8119 // The `then` clause must be a single case expression matching on a
8120 // single variable.
8121 let Some(TypedExpr::Case {
8122 subjects, clauses, ..
8123 }) = single_expression(then)
8124 else {
8125 return None;
8126 };
8127
8128 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else {
8129 return None;
8130 };
8131
8132 // That variable must be one the variables we brought into scope in this
8133 // branch.
8134 let variable = pattern
8135 .iter()
8136 .flat_map(|pattern| pattern.bound_variables())
8137 .find(|variable| variable.name() == *name)?;
8138
8139 // There's one last condition to trigger the code action: we must
8140 // actually be with the cursor over the pattern or the nested case
8141 // expression!
8142 //
8143 // ```gleam
8144 // case wibble {
8145 // Ok(a) -> case a {
8146 // //^^^^^^^^^^^^^^^ Anywhere over here!
8147 // }
8148 // }
8149 // ```
8150 //
8151 let first_pattern = pattern.first().expect("at least one pattern");
8152 let last_pattern = pattern.last().expect("at least one pattern");
8153 let pattern_location = first_pattern.location().merge(&last_pattern.location());
8154
8155 let last_inner_subject = subjects.last().expect("at least one subject");
8156 let trigger_location = pattern_location.merge(&last_inner_subject.location());
8157 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location);
8158
8159 if within(self.params.range, trigger_range) {
8160 Some(Collapsed {
8161 outer_clause_span: *location,
8162 outer_guard: guard,
8163 matched_variable: variable,
8164 matched_pattern_span: pattern_location,
8165 inner_clauses: clauses,
8166 })
8167 } else {
8168 None
8169 }
8170 }
8171}
8172
8173impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> {
8174 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
8175 if let Some(collapsed) = self.flatten_clause(clause) {
8176 self.collapsed = Some(collapsed);
8177
8178 // We're done, there's no need to keep exploring as we know the
8179 // cursor is over this pattern and it can't be over any other one!
8180 return;
8181 };
8182
8183 ast::visit::visit_typed_clause(self, clause);
8184 }
8185}
8186
8187/// If the expression is a single expression, or a block containing a single
8188/// expression, this function will return it.
8189/// But if the expression is a block with multiple statements, an assignment
8190/// of a use, this will return None.
8191///
8192fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
8193 match expression {
8194 // If a block has a single statement, we can flatten it into a
8195 // single expression if that one statement is an expression.
8196 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() {
8197 ast::Statement::Expression(expression) => single_expression(expression),
8198 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => {
8199 None
8200 }
8201 },
8202
8203 // If a block has multiple statements then it can't be flattened
8204 // into a single expression.
8205 TypedExpr::Block { .. } => None,
8206
8207 expression => Some(expression),
8208 }
8209}
8210
8211/// Code action to remove `opaque` from a private type.
8212///
8213pub struct RemoveUnreachableBranches<'a> {
8214 module: &'a Module,
8215 params: &'a CodeActionParams,
8216 edits: TextEdits<'a>,
8217 /// The source location of the patterns of all the unreachable branches in
8218 /// the current module.
8219 ///
8220 unreachable_branches: HashSet<SrcSpan>,
8221 branches_to_delete: Vec<SrcSpan>,
8222}
8223
8224impl<'a> RemoveUnreachableBranches<'a> {
8225 pub fn new(
8226 module: &'a Module,
8227 line_numbers: &'a LineNumbers,
8228 params: &'a CodeActionParams,
8229 ) -> Self {
8230 let unreachable_branches = (module.ast.type_info.warnings.iter())
8231 .filter_map(|warning| match warning {
8232 type_::Warning::UnreachableCasePattern { location, .. } => Some(*location),
8233 _ => None,
8234 })
8235 .collect();
8236
8237 Self {
8238 unreachable_branches,
8239 module,
8240 params,
8241 edits: TextEdits::new(line_numbers),
8242 branches_to_delete: vec![],
8243 }
8244 }
8245
8246 pub fn code_actions(mut self) -> Vec<CodeAction> {
8247 self.visit_typed_module(&self.module.ast);
8248 if self.branches_to_delete.is_empty() {
8249 return vec![];
8250 }
8251
8252 for branch in self.branches_to_delete {
8253 self.edits.delete(branch);
8254 }
8255
8256 let mut action = Vec::with_capacity(1);
8257 CodeActionBuilder::new("Remove unreachable branches")
8258 .kind(CodeActionKind::QUICKFIX)
8259 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8260 .preferred(true)
8261 .push_to(&mut action);
8262 action
8263 }
8264}
8265
8266impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableBranches<'ast> {
8267 fn visit_typed_expr_case(
8268 &mut self,
8269 location: &'ast SrcSpan,
8270 type_: &'ast Arc<Type>,
8271 subjects: &'ast [TypedExpr],
8272 clauses: &'ast [ast::TypedClause],
8273 compiled_case: &'ast CompiledCase,
8274 ) {
8275 // We're showing the code action only if we're within one of the
8276 // unreachable patterns. And the code action is going to remove all the
8277 // unreachable patterns for this case.
8278 let is_hovering_clause = clauses.iter().any(|clause| {
8279 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location());
8280 within(self.params.range, pattern_range)
8281 });
8282 if is_hovering_clause {
8283 self.branches_to_delete = clauses
8284 .iter()
8285 .filter(|clause| {
8286 self.unreachable_branches
8287 .contains(&clause.pattern_location())
8288 })
8289 .map(|clause| clause.location())
8290 .collect_vec();
8291 return;
8292 }
8293
8294 // If we're not hovering any of the branches clauses then we want to
8295 // keep visiting the case expression as the unreachable branch might be
8296 // in one of the nested cases.
8297 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
8298 }
8299}
8300
8301/// Code action to add labels to a constructor/call where all the labels where
8302/// omitted.
8303///
8304pub struct AddOmittedLabels<'a> {
8305 module: &'a Module,
8306 params: &'a CodeActionParams,
8307 edits: TextEdits<'a>,
8308 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>,
8309}
8310
8311struct CallArgumentWithOmittedLabel {
8312 location: SrcSpan,
8313
8314 /// If the argument has a label this will be the label we can use for it.
8315 ///
8316 omitted_label: Option<EcoString>,
8317
8318 /// If the argument is a variable that has the same name as the omitted label
8319 /// and could use the shorthand syntax.
8320 ///
8321 can_use_shorthand_syntax: bool,
8322}
8323
8324impl<'a> AddOmittedLabels<'a> {
8325 pub fn new(
8326 module: &'a Module,
8327 line_numbers: &'a LineNumbers,
8328 params: &'a CodeActionParams,
8329 ) -> Self {
8330 Self {
8331 module,
8332 params,
8333 edits: TextEdits::new(line_numbers),
8334 arguments_and_omitted_labels: None,
8335 }
8336 }
8337
8338 pub fn code_actions(mut self) -> Vec<CodeAction> {
8339 self.visit_typed_module(&self.module.ast);
8340
8341 let Some(call_arguments) = self.arguments_and_omitted_labels else {
8342 return vec![];
8343 };
8344
8345 for call_argument in call_arguments {
8346 let Some(label) = call_argument.omitted_label else {
8347 continue;
8348 };
8349 if call_argument.can_use_shorthand_syntax {
8350 self.edits.insert(call_argument.location.end, ":".into());
8351 } else {
8352 self.edits
8353 .insert(call_argument.location.start, format!("{label}: "))
8354 }
8355 }
8356
8357 let mut action = Vec::with_capacity(1);
8358 CodeActionBuilder::new("Add omitted labels")
8359 .kind(CodeActionKind::REFACTOR_REWRITE)
8360 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8361 .preferred(false)
8362 .push_to(&mut action);
8363 action
8364 }
8365}
8366
8367impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> {
8368 fn visit_typed_expr_call(
8369 &mut self,
8370 location: &'ast SrcSpan,
8371 type_: &'ast Arc<Type>,
8372 fun: &'ast TypedExpr,
8373 arguments: &'ast [TypedCallArg],
8374 ) {
8375 let called_function_range = self.edits.src_span_to_lsp_range(fun.location());
8376 if !within(self.params.range, called_function_range) {
8377 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
8378 return;
8379 }
8380
8381 let Some(field_map) = fun.field_map() else {
8382 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments);
8383 return;
8384 };
8385 let argument_index_to_label = field_map.indices_to_labels();
8386
8387 let mut omitted_labels = Vec::with_capacity(arguments.len());
8388 for (index, argument) in arguments.iter().enumerate() {
8389 // We can't apply this code action to calls where any of the
8390 // arguments have a label explicitly provided.
8391 if argument.label.is_some() {
8392 return;
8393 }
8394
8395 let label = argument_index_to_label
8396 .get(&(index as u32))
8397 .cloned()
8398 .cloned();
8399
8400 let can_use_shorthand_syntax = match (&label, &argument.value) {
8401 (Some(label), TypedExpr::Var { name, .. }) => name == label,
8402 (Some(_) | None, _) => false,
8403 };
8404
8405 omitted_labels.push(CallArgumentWithOmittedLabel {
8406 location: argument.location,
8407 omitted_label: label,
8408 can_use_shorthand_syntax,
8409 })
8410 }
8411 self.arguments_and_omitted_labels = Some(omitted_labels);
8412 }
8413}
8414
8415/// Code action to extract selected code into a separate function.
8416/// If a user selected a portion of code in a function, we offer a code action
8417/// to extract it into a new one. This can either be a single expression, such
8418/// as in the following example:
8419///
8420/// ```gleam
8421/// pub fn main() {
8422/// let value = {
8423/// // ^ User selects from here
8424/// ...
8425/// }
8426/// //^ Until here
8427/// }
8428/// ```
8429///
8430/// Here, we would extract the selected block expression. It could also be a
8431/// series of statements. For example:
8432///
8433/// ```gleam
8434/// pub fn main() {
8435/// let a = 1
8436/// //^ User selects from here
8437/// let b = 2
8438/// let c = a + b
8439/// // ^ Until here
8440///
8441/// do_more_things(c)
8442/// }
8443/// ```
8444///
8445/// Here, we want to extract the statements inside the user's selection.
8446///
8447pub struct ExtractFunction<'a> {
8448 module: &'a Module,
8449 params: &'a CodeActionParams,
8450 edits: TextEdits<'a>,
8451 function: Option<ExtractedFunction<'a>>,
8452 function_end_position: Option<u32>,
8453 /// Since the `visit_typed_statement` visitor function doesn't tell us when
8454 /// a statement is the last in a block or function, we need to track that
8455 /// manually.
8456 last_statement_location: Option<SrcSpan>,
8457}
8458
8459/// Information about a section of code we are extracting as a function.
8460struct ExtractedFunction<'a> {
8461 /// A list of parameters which need to be passed to the extracted function.
8462 /// These are any variables used in the extracted code, which are defined
8463 /// outside of the extracted code.
8464 parameters: Vec<(EcoString, Arc<Type>)>,
8465 /// A list of values which need to be returned from the extracted function.
8466 /// These are the variables defined in the extracted code which are used
8467 /// outside of the extracted section.
8468 returned_variables: Vec<(EcoString, Arc<Type>)>,
8469 /// The piece of code to be extracted. This is either a single expression or
8470 /// a list of statements, as explained in the documentation of `ExtractFunction`
8471 value: ExtractedValue<'a>,
8472}
8473
8474impl<'a> ExtractedFunction<'a> {
8475 fn new(value: ExtractedValue<'a>) -> Self {
8476 Self {
8477 value,
8478 parameters: Vec::new(),
8479 returned_variables: Vec::new(),
8480 }
8481 }
8482
8483 fn location(&self) -> SrcSpan {
8484 match &self.value {
8485 ExtractedValue::Expression(expression) => expression.location(),
8486 ExtractedValue::Statements { location, .. } => *location,
8487 }
8488 }
8489}
8490
8491#[derive(Debug)]
8492enum ExtractedValue<'a> {
8493 Expression(&'a TypedExpr),
8494 Statements {
8495 location: SrcSpan,
8496 position: StatementPosition,
8497 },
8498}
8499
8500/// When we are extracting multiple statements, there are two possible cases:
8501/// The first is if we are extracting statements in the middle of a function.
8502/// In this case, we will need to return some number of arguments, or `Nil`.
8503/// For example:
8504///
8505/// ```gleam
8506/// pub fn main() {
8507/// let message = "Hello!"
8508/// let log_message = "[INFO] " <> message
8509/// //^ Select from here
8510/// io.println(log_message)
8511/// // ^ Until here
8512///
8513/// do_some_more_things()
8514/// }
8515/// ```
8516///
8517/// Here, the extracted function doesn't bind any variables which we need
8518/// afterwards, it purely performs side effects. In this case we can just return
8519/// `Nil` from the new function.
8520///
8521/// However, consider the following:
8522///
8523/// ```gleam
8524/// pub fn main() {
8525/// let a = 1
8526/// let b = 2
8527/// //^ Select from here
8528/// a + b
8529/// // ^ Until here
8530/// }
8531/// ```
8532///
8533/// Here, despite us not needing any variables from the extracted code, there
8534/// is one key difference: the `a + b` expression is at the end of the function,
8535/// and so its value is returned from the entire function. This is known as the
8536/// "tail" position. In that case, we can't return `Nil` as that would make the
8537/// `main` function return `Nil` instead of the result of the addition. If we
8538/// extract the tail-position statement, we need to return that last value rather
8539/// than `Nil`.
8540///
8541#[derive(Debug)]
8542enum StatementPosition {
8543 Tail { type_: Arc<Type> },
8544 NotTail,
8545}
8546
8547impl<'a> ExtractFunction<'a> {
8548 pub fn new(
8549 module: &'a Module,
8550 line_numbers: &'a LineNumbers,
8551 params: &'a CodeActionParams,
8552 ) -> Self {
8553 Self {
8554 module,
8555 params,
8556 edits: TextEdits::new(line_numbers),
8557 function: None,
8558 function_end_position: None,
8559 last_statement_location: None,
8560 }
8561 }
8562
8563 pub fn code_actions(mut self) -> Vec<CodeAction> {
8564 // If no code is selected, then there is no function to extract and we
8565 // can return no code actions.
8566 if self.params.range.start == self.params.range.end {
8567 return Vec::new();
8568 }
8569
8570 self.visit_typed_module(&self.module.ast);
8571
8572 let Some(end) = self.function_end_position else {
8573 return Vec::new();
8574 };
8575
8576 // If nothing was found in the selected range, there is no code action.
8577 let Some(extracted) = self.function.take() else {
8578 return Vec::new();
8579 };
8580
8581 match extracted.value {
8582 // If we extract a block, it isn't very helpful to have the body of the
8583 // extracted function just be a single block expression, so instead we
8584 // extract the statements inside the block. For example, the following
8585 // code:
8586 //
8587 // ```gleam
8588 // pub fn main() {
8589 // let x = {
8590 // // ^ Select from here
8591 // let a = 1
8592 // let b = 2
8593 // a + b
8594 // }
8595 // //^ Until here
8596 // x
8597 // }
8598 // ```
8599 //
8600 // Would produce the following extracted function:
8601 //
8602 // ```gleam
8603 // fn function() {
8604 // let a = 1
8605 // let b = 2
8606 // a + b
8607 // }
8608 // ```
8609 //
8610 // Rather than:
8611 //
8612 // ```gleam
8613 // fn function() {
8614 // {
8615 // let a = 1
8616 // let b = 2
8617 // a + b
8618 // }
8619 // }
8620 // ```
8621 //
8622 ExtractedValue::Expression(TypedExpr::Block {
8623 statements,
8624 location: full_location,
8625 }) => {
8626 let location = SrcSpan::new(
8627 statements.first().location().start,
8628 statements.last().location().end,
8629 );
8630 self.extract_code_in_tail_position(
8631 *full_location,
8632 location,
8633 statements.last().type_(),
8634 extracted.parameters,
8635 end,
8636 )
8637 }
8638 ExtractedValue::Expression(expression) => self.extract_code_in_tail_position(
8639 expression.location(),
8640 expression.location(),
8641 expression.type_(),
8642 extracted.parameters,
8643 end,
8644 ),
8645 ExtractedValue::Statements {
8646 location,
8647 position: StatementPosition::NotTail,
8648 } => self.extract_statements(
8649 location,
8650 extracted.parameters,
8651 extracted.returned_variables,
8652 end,
8653 ),
8654 ExtractedValue::Statements {
8655 location,
8656 position: StatementPosition::Tail { type_ },
8657 } => self.extract_code_in_tail_position(
8658 location,
8659 location,
8660 type_,
8661 extracted.parameters,
8662 end,
8663 ),
8664 }
8665
8666 let mut action = Vec::with_capacity(1);
8667 CodeActionBuilder::new("Extract function")
8668 .kind(CodeActionKind::REFACTOR_EXTRACT)
8669 .changes(self.params.text_document.uri.clone(), self.edits.edits)
8670 .preferred(false)
8671 .push_to(&mut action);
8672 action
8673 }
8674
8675 /// Choose a suitable name for an extracted function to make sure it doesn't
8676 /// clash with existing functions defined in the module and cause an error.
8677 fn function_name(&self) -> EcoString {
8678 if !self.module.ast.type_info.values.contains_key("function") {
8679 return "function".into();
8680 }
8681
8682 let mut number = 2;
8683 loop {
8684 let name = eco_format!("function_{number}");
8685 if !self.module.ast.type_info.values.contains_key(&name) {
8686 return name;
8687 }
8688 number += 1;
8689 }
8690 }
8691
8692 /// Extracts code from the end of a function or block. This could either be
8693 /// a single expression, or multiple statements followed by a final expression.
8694 fn extract_code_in_tail_position(
8695 &mut self,
8696 location: SrcSpan,
8697 code_location: SrcSpan,
8698 type_: Arc<Type>,
8699 parameters: Vec<(EcoString, Arc<Type>)>,
8700 function_end: u32,
8701 ) {
8702 let expression_code = code_at(self.module, code_location);
8703
8704 let name = self.function_name();
8705 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
8706 let call = format!("{name}({arguments})");
8707
8708 // Since we are only extracting a single expression, we can just replace
8709 // it with the call and preserve all other semantics; only one value can
8710 // be returned from the expression, unlike when extracting multiple
8711 // statements.
8712 self.edits.replace(location, call);
8713
8714 let mut printer = Printer::new(&self.module.ast.names);
8715
8716 let parameters = parameters
8717 .iter()
8718 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
8719 .join(", ");
8720 let return_type = printer.print_type(&type_);
8721
8722 let function = format!(
8723 "\n\nfn {name}({parameters}) -> {return_type} {{
8724 {expression_code}
8725}}"
8726 );
8727
8728 self.edits.insert(function_end, function);
8729 }
8730
8731 fn extract_statements(
8732 &mut self,
8733 location: SrcSpan,
8734 parameters: Vec<(EcoString, Arc<Type>)>,
8735 returned_variables: Vec<(EcoString, Arc<Type>)>,
8736 function_end: u32,
8737 ) {
8738 let code = code_at(self.module, location);
8739
8740 let returns_anything = !returned_variables.is_empty();
8741
8742 // Here, we decide what value to return from the function. There are
8743 // three cases:
8744 // The first is when the extracted code is purely for side-effects, and
8745 // does not produce any values which are needed outside of the extracted
8746 // code. For example:
8747 //
8748 // ```gleam
8749 // pub fn main() {
8750 // let message = "Something important"
8751 // //^ Select from here
8752 // io.println("Something important")
8753 // io.println("Something else which is repeated")
8754 // // ^ Until here
8755 //
8756 // do_final_thing()
8757 // }
8758 // ```
8759 //
8760 // It doesn't make sense to return any values from this function, since
8761 // no values from the extract code are used afterwards, so we simply
8762 // return `Nil`.
8763 //
8764 // The next is when we need just a single value defined in the extracted
8765 // function, such as in this piece of code:
8766 //
8767 // ```gleam
8768 // pub fn main() {
8769 // let a = 10
8770 // //^ Select from here
8771 // let b = 20
8772 // let c = a + b
8773 // // ^ Until here
8774 //
8775 // echo c
8776 // }
8777 // ```
8778 //
8779 // Here, we can just return the single value, `c`.
8780 //
8781 // The last situation is when we need multiple defined values, such as
8782 // in the following code:
8783 //
8784 // ```gleam
8785 // pub fn main() {
8786 // let a = 10
8787 // //^ Select from here
8788 // let b = 20
8789 // let c = a + b
8790 // // ^ Until here
8791 //
8792 // echo a
8793 // echo b
8794 // echo c
8795 // }
8796 // ```
8797 //
8798 // In this case, we must return a tuple containing `a`, `b` and `c` in
8799 // order for the calling function to have access to the correct values.
8800 let (return_type, return_value) = match returned_variables.as_slice() {
8801 [] => (type_::nil(), "Nil".into()),
8802 [(name, type_)] => (type_.clone(), name.clone()),
8803 _ => {
8804 let values = returned_variables.iter().map(|(name, _)| name).join(", ");
8805 let type_ = type_::tuple(
8806 returned_variables
8807 .into_iter()
8808 .map(|(_, type_)| type_)
8809 .collect(),
8810 );
8811
8812 (type_, eco_format!("#({values})"))
8813 }
8814 };
8815
8816 let name = self.function_name();
8817 let arguments = parameters.iter().map(|(name, _)| name).join(", ");
8818
8819 // If any values are returned from the extracted function, we need to
8820 // bind them so that they are accessible in the current scope.
8821 let call = if returns_anything {
8822 format!("let {return_value} = {name}({arguments})")
8823 } else {
8824 format!("{name}({arguments})")
8825 };
8826 self.edits.replace(location, call);
8827
8828 let mut printer = Printer::new(&self.module.ast.names);
8829
8830 let parameters = parameters
8831 .iter()
8832 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
8833 .join(", ");
8834
8835 let return_type = printer.print_type(&return_type);
8836
8837 let function = format!(
8838 "\n\nfn {name}({parameters}) -> {return_type} {{
8839 {code}
8840 {return_value}
8841}}"
8842 );
8843
8844 self.edits.insert(function_end, function);
8845 }
8846
8847 /// When a variable is referenced, we need to decide if we need to do anything
8848 /// to ensure that the reference is still valid after extracting a function.
8849 /// If the variable is defined outside the extracted function, but used inside
8850 /// it, then we need to add it as a parameter of the function. Similarly, if
8851 /// a variable is defined inside the extracted code, but used outside of it,
8852 /// we need to ensure that value is returned from the function so that it is
8853 /// accessible.
8854 fn register_referenced_variable(
8855 &mut self,
8856 name: &EcoString,
8857 type_: &Arc<Type>,
8858 location: SrcSpan,
8859 definition_location: SrcSpan,
8860 ) {
8861 let Some(extracted) = &mut self.function else {
8862 return;
8863 };
8864
8865 let extracted_location = extracted.location();
8866
8867 // If a variable defined outside the extracted code is referenced inside
8868 // it, we need to add it to the list of parameters.
8869 let variables = if extracted_location.contains_span(location)
8870 && !extracted_location.contains_span(definition_location)
8871 {
8872 &mut extracted.parameters
8873 // If a variable defined inside the extracted code is referenced outside
8874 // it, then we need to ensure that it is returned from the function.
8875 } else if extracted_location.contains_span(definition_location)
8876 && !extracted_location.contains_span(location)
8877 {
8878 &mut extracted.returned_variables
8879 } else {
8880 return;
8881 };
8882
8883 // If the variable has already been tracked, no need to register it again.
8884 // We use a `Vec` here rather than a `HashMap` because we want to ensure
8885 // the order of arguments is consistent; in this case it will be determined
8886 // by the order the variables are used. This isn't always desired, but it's
8887 // better than random order, and makes it easier to write tests too.
8888 // The cost of iterating the list here is minimal; it is unlikely that
8889 // a given function will ever have more than 10 or so parameters.
8890 if variables.iter().any(|(variable, _)| variable == name) {
8891 return;
8892 }
8893
8894 variables.push((name.clone(), type_.clone()));
8895 }
8896
8897 fn can_extract(&self, location: SrcSpan) -> bool {
8898 let expression_range = self.edits.src_span_to_lsp_range(location);
8899 let selected_range = self.params.range;
8900
8901 // If the selected range doesn't touch the expression at all, then there
8902 // is no reason to extract it.
8903 if !overlaps(expression_range, selected_range) {
8904 return false;
8905 }
8906
8907 // Determine whether the selected range falls completely within the
8908 // expression. For example:
8909 // ```gleam
8910 // pub fn main() {
8911 // let something = {
8912 // let a = 1
8913 // let b = 2
8914 // let c = a + b
8915 // //^ The user has selected from here
8916 // let d = a * b
8917 // c / d
8918 // // ^ Until here
8919 // }
8920 // }
8921 // ```
8922 //
8923 // Here, the selected range does overlap with the `let something`
8924 // statement; but we don't want to extract that whole statement! The
8925 // user only wanted to extract the statements inside the block. So if
8926 // the selected range falls completely within the expression, we ignore
8927 // it and traverse the tree further until we find exactly what the user
8928 // selected.
8929 //
8930 let selected_within_expression = selected_range.start > expression_range.start
8931 && selected_range.start < expression_range.end
8932 && selected_range.end > expression_range.start
8933 && selected_range.end < expression_range.end;
8934
8935 // If the selected range is completely within the expression, we don't
8936 // want to extract it.
8937 !selected_within_expression
8938 }
8939}
8940
8941impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
8942 fn visit_typed_function(&mut self, function: &'ast ast::TypedFunction) {
8943 let range = self.edits.src_span_to_lsp_range(function.full_location());
8944
8945 if within(self.params.range, range) {
8946 self.function_end_position = Some(function.end_position);
8947 self.last_statement_location = function.body.last().map(|last| last.location());
8948
8949 ast::visit::visit_typed_function(self, function);
8950 }
8951 }
8952
8953 fn visit_typed_expr_block(
8954 &mut self,
8955 location: &'ast SrcSpan,
8956 statements: &'ast [TypedStatement],
8957 ) {
8958 let last_statement_location = self.last_statement_location;
8959 self.last_statement_location = statements.last().map(|last| last.location());
8960
8961 ast::visit::visit_typed_expr_block(self, location, statements);
8962
8963 self.last_statement_location = last_statement_location;
8964 }
8965
8966 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
8967 // If we have already determined what code we want to extract, we don't
8968 // want to extract this instead. This expression would be inside the
8969 // piece of code we already are going to extract, leading to us
8970 // extracting just a single literal in any selection, which is of course
8971 // not desired.
8972 if self.function.is_none() {
8973 // If this expression is fully selected, we mark it as being extracted.
8974 if self.can_extract(expression.location()) {
8975 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression(
8976 expression,
8977 )));
8978 }
8979 }
8980 ast::visit::visit_typed_expr(self, expression);
8981 }
8982
8983 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
8984 let location = statement.location();
8985 if self.can_extract(location) {
8986 let position = if let Some(last_statement_location) = self.last_statement_location
8987 && location == last_statement_location
8988 {
8989 StatementPosition::Tail {
8990 type_: statement.type_(),
8991 }
8992 } else {
8993 StatementPosition::NotTail
8994 };
8995
8996 match &mut self.function {
8997 None => {
8998 self.function = Some(ExtractedFunction::new(ExtractedValue::Statements {
8999 location,
9000 position,
9001 }));
9002 }
9003 // If we have already chosen an expression to extract, that means
9004 // that this statement is within the already extracted expression,
9005 // so we don't want to extract this instead.
9006 Some(ExtractedFunction {
9007 value: ExtractedValue::Expression(_),
9008 ..
9009 }) => {}
9010 // If we are selecting multiple statements, this statement should
9011 // be included within list, so we merge th spans to ensure it is
9012 // included.
9013 Some(ExtractedFunction {
9014 value:
9015 ExtractedValue::Statements {
9016 location,
9017 position: extracted_position,
9018 },
9019 ..
9020 }) => {
9021 *location = location.merge(&statement.location());
9022 *extracted_position = position;
9023 }
9024 }
9025 }
9026 ast::visit::visit_typed_statement(self, statement);
9027 }
9028
9029 fn visit_typed_expr_var(
9030 &mut self,
9031 location: &'ast SrcSpan,
9032 constructor: &'ast ValueConstructor,
9033 name: &'ast EcoString,
9034 ) {
9035 if let type_::ValueConstructorVariant::LocalVariable {
9036 location: definition_location,
9037 ..
9038 } = &constructor.variant
9039 {
9040 self.register_referenced_variable(
9041 name,
9042 &constructor.type_,
9043 *location,
9044 *definition_location,
9045 );
9046 }
9047 }
9048
9049 fn visit_typed_clause_guard_var(
9050 &mut self,
9051 location: &'ast SrcSpan,
9052 name: &'ast EcoString,
9053 type_: &'ast Arc<Type>,
9054 definition_location: &'ast SrcSpan,
9055 ) {
9056 self.register_referenced_variable(name, type_, *location, *definition_location);
9057 }
9058
9059 fn visit_typed_bit_array_size_variable(
9060 &mut self,
9061 location: &'ast SrcSpan,
9062 name: &'ast EcoString,
9063 constructor: &'ast Option<Box<ValueConstructor>>,
9064 type_: &'ast Arc<Type>,
9065 ) {
9066 let variant = match constructor {
9067 Some(constructor) => &constructor.variant,
9068 None => return,
9069 };
9070 if let type_::ValueConstructorVariant::LocalVariable {
9071 location: definition_location,
9072 ..
9073 } = variant
9074 {
9075 self.register_referenced_variable(name, type_, *location, *definition_location);
9076 }
9077 }
9078}