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