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