Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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