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

Configure Feed

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

gleam / compiler-core / src / language_server / code_action.rs
252 kB 7328 lines
1use std::{collections::HashSet, iter, sync::Arc}; 2 3use crate::{ 4 Error, STDLIB_PACKAGE_NAME, analyse, 5 ast::{ 6 self, 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 def in &self.module.ast.definitions { 1356 if let ast::Definition::Import(import) = def { 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(|def| match def { 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(|def| match def { 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 match expr { 3017 // We don't extract variables, they're already good. 3018 // And we don't extract module selects by themselves but always 3019 // want to consider those as part of a function call. 3020 TypedExpr::Var { .. } | TypedExpr::ModuleSelect { .. } => (), 3021 _ => { 3022 self.selected_expression = Some((expr_location, expr.type_())); 3023 3024 if !matches!(self.position, Some(ExtractVariablePosition::CallArg)) { 3025 self.statement_before_selected_expression = self.latest_statement; 3026 } 3027 } 3028 } 3029 } 3030 } 3031 3032 match expr { 3033 // Expressions that don't make sense to extract 3034 TypedExpr::Panic { .. } 3035 | TypedExpr::Echo { .. } 3036 | TypedExpr::Block { .. } 3037 | TypedExpr::ModuleSelect { .. } 3038 | TypedExpr::Invalid { .. } 3039 | TypedExpr::Var { .. } => (), 3040 3041 TypedExpr::Int { location, .. } 3042 | TypedExpr::Float { location, .. } 3043 | TypedExpr::String { location, .. } 3044 | TypedExpr::Pipeline { location, .. } 3045 | TypedExpr::Fn { location, .. } 3046 | TypedExpr::Todo { location, .. } 3047 | TypedExpr::List { location, .. } 3048 | TypedExpr::Call { location, .. } 3049 | TypedExpr::BinOp { location, .. } 3050 | TypedExpr::Case { location, .. } 3051 | TypedExpr::RecordAccess { location, .. } 3052 | TypedExpr::Tuple { location, .. } 3053 | TypedExpr::TupleIndex { location, .. } 3054 | TypedExpr::BitArray { location, .. } 3055 | TypedExpr::RecordUpdate { location, .. } 3056 | TypedExpr::NegateBool { location, .. } 3057 | TypedExpr::NegateInt { location, .. } => { 3058 if let Some(ExtractVariablePosition::CallArg) = self.position { 3059 // Don't update latest statement, we don't want to insert the extracted 3060 // variable inside the parenthesis where the call argument is located. 3061 } else { 3062 self.statement_before_selected_expression = self.latest_statement; 3063 }; 3064 self.selected_expression = Some((*location, expr.type_())); 3065 } 3066 } 3067 3068 ast::visit::visit_typed_expr(self, expr); 3069 } 3070 3071 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3072 let range = self.edits.src_span_to_lsp_range(use_.call.location()); 3073 if !within(self.params.range, range) { 3074 ast::visit::visit_typed_use(self, use_); 3075 return; 3076 } 3077 3078 // Insert code under the `use` 3079 self.statement_before_selected_expression = Some(use_.call.location()); 3080 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3081 ast::visit::visit_typed_use(this, use_); 3082 }); 3083 } 3084 3085 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 3086 let range = self.edits.src_span_to_lsp_range(clause.location()); 3087 if !within(self.params.range, range) { 3088 ast::visit::visit_typed_clause(self, clause); 3089 return; 3090 } 3091 3092 // Insert code after the `->` 3093 self.latest_statement = Some(clause.then.location()); 3094 self.to_be_wrapped = true; 3095 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| { 3096 ast::visit::visit_typed_clause(this, clause); 3097 }); 3098 } 3099 3100 fn visit_typed_expr_block( 3101 &mut self, 3102 location: &'ast SrcSpan, 3103 statements: &'ast [TypedStatement], 3104 ) { 3105 let range = self.edits.src_span_to_lsp_range(*location); 3106 if !within(self.params.range, range) { 3107 ast::visit::visit_typed_expr_block(self, location, statements); 3108 return; 3109 } 3110 3111 // Don't extract block as variable 3112 let mut position = self.position; 3113 if let Some(ExtractVariablePosition::InsideCaseClause) = position { 3114 position = None; 3115 self.to_be_wrapped = false; 3116 } 3117 3118 self.at_optional_position(position, |this| { 3119 ast::visit::visit_typed_expr_block(this, location, statements); 3120 }); 3121 } 3122 3123 fn visit_typed_expr_fn( 3124 &mut self, 3125 location: &'ast SrcSpan, 3126 type_: &'ast Arc<Type>, 3127 kind: &'ast FunctionLiteralKind, 3128 args: &'ast [TypedArg], 3129 body: &'ast Vec1<TypedStatement>, 3130 return_annotation: &'ast Option<ast::TypeAst>, 3131 ) { 3132 let range = self.edits.src_span_to_lsp_range(*location); 3133 if !within(self.params.range, range) { 3134 ast::visit::visit_typed_expr_fn( 3135 self, 3136 location, 3137 type_, 3138 kind, 3139 args, 3140 body, 3141 return_annotation, 3142 ); 3143 return; 3144 } 3145 3146 let position = match kind { 3147 // If a fn is a capture `int.wibble(1, _)` its body will consist of 3148 // just a single expression statement. When visiting we must record 3149 // we're inside a capture body. 3150 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody), 3151 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement), 3152 FunctionLiteralKind::Anonymous { .. } => self.position, 3153 }; 3154 3155 self.at_optional_position(position, |this| { 3156 ast::visit::visit_typed_expr_fn( 3157 this, 3158 location, 3159 type_, 3160 kind, 3161 args, 3162 body, 3163 return_annotation, 3164 ); 3165 }); 3166 } 3167 3168 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 3169 let range = self.edits.src_span_to_lsp_range(arg.location); 3170 if !within(self.params.range, range) { 3171 ast::visit::visit_typed_call_arg(self, arg); 3172 return; 3173 } 3174 3175 let position = if arg.is_use_implicit_callback() { 3176 Some(ExtractVariablePosition::TopLevelStatement) 3177 } else { 3178 Some(ExtractVariablePosition::CallArg) 3179 }; 3180 3181 self.at_optional_position(position, |this| { 3182 ast::visit::visit_typed_call_arg(this, arg); 3183 }); 3184 } 3185 3186 // We don't want to offer the action if the cursor is over some invalid 3187 // piece of code. 3188 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, _type_: &'ast Arc<Type>) { 3189 let invalid_range = self.edits.src_span_to_lsp_range(*location); 3190 if within(self.params.range, invalid_range) { 3191 self.selected_expression = None; 3192 } 3193 } 3194} 3195 3196/// Builder for code action to convert a literal use into a const. 3197/// 3198/// For using the code action on each of the following lines: 3199/// 3200/// ```gleam 3201/// fn void() { 3202/// let var = [1, 2, 3] 3203/// let res = function("Statement", var) 3204/// } 3205/// ``` 3206/// 3207/// Both value literals will become: 3208/// 3209/// ```gleam 3210/// const var = [1, 2, 3] 3211/// const string = "Statement" 3212/// 3213/// fn void() { 3214/// let res = function(string, var) 3215/// } 3216/// ``` 3217pub struct ExtractConstant<'a> { 3218 module: &'a Module, 3219 params: &'a CodeActionParams, 3220 edits: TextEdits<'a>, 3221 /// The whole selected expression 3222 selected_expression: Option<SrcSpan>, 3223 /// The location of the start of the function containing the expression 3224 container_function_start: Option<u32>, 3225 /// The variant of the extractable expression being extracted (if any) 3226 variant_of_extractable: Option<ExtractableToConstant>, 3227 /// The name of the newly created constant 3228 name_to_use: Option<EcoString>, 3229 /// The right hand side expression of the newly created constant 3230 value_to_use: Option<EcoString>, 3231} 3232 3233/// Used when an expression can be extracted to a constant 3234enum ExtractableToConstant { 3235 /// Used for collections and operator uses. This means that elements 3236 /// inside, are also extractable as constants. 3237 ComposedValue, 3238 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings 3239 /// and type variants (not records). 3240 SingleValue, 3241 /// Used for whole variable assignments. If the right hand side of the 3242 /// expression can be extracted, the whole expression extracted and use the 3243 /// local variable as a constant. 3244 Assignment, 3245} 3246 3247fn can_be_constant( 3248 module: &Module, 3249 expr: &TypedExpr, 3250 module_constants: Option<&HashSet<&EcoString>>, 3251) -> bool { 3252 // We pass the `module_constants` on recursion to not compute them each time 3253 let mc = match module_constants { 3254 None => &module 3255 .ast 3256 .definitions 3257 .iter() 3258 .filter_map(|definition| match definition { 3259 ast::Definition::ModuleConstant(module_constant) => Some(&module_constant.name), 3260 3261 ast::Definition::Function(_) 3262 | ast::Definition::TypeAlias(_) 3263 | ast::Definition::CustomType(_) 3264 | ast::Definition::Import(_) => None, 3265 }) 3266 .collect(), 3267 Some(mc) => mc, 3268 }; 3269 3270 match expr { 3271 // Attempt to extract whole list as long as it's comprised of only literals 3272 TypedExpr::List { elements, tail, .. } => { 3273 elements 3274 .iter() 3275 .all(|element| can_be_constant(module, element, Some(mc))) 3276 && tail.is_none() 3277 } 3278 3279 // Attempt to extract whole bit array as long as it's made up of literals 3280 TypedExpr::BitArray { segments, .. } => { 3281 segments 3282 .iter() 3283 .all(|segment| can_be_constant(module, &segment.value, Some(mc))) 3284 && segments.iter().all(|segment| { 3285 segment.options.iter().all(|option| match option { 3286 ast::BitArrayOption::Size { value, .. } => { 3287 can_be_constant(module, value, Some(mc)) 3288 } 3289 3290 ast::BitArrayOption::Bytes { .. } 3291 | ast::BitArrayOption::Int { .. } 3292 | ast::BitArrayOption::Float { .. } 3293 | ast::BitArrayOption::Bits { .. } 3294 | ast::BitArrayOption::Utf8 { .. } 3295 | ast::BitArrayOption::Utf16 { .. } 3296 | ast::BitArrayOption::Utf32 { .. } 3297 | ast::BitArrayOption::Utf8Codepoint { .. } 3298 | ast::BitArrayOption::Utf16Codepoint { .. } 3299 | ast::BitArrayOption::Utf32Codepoint { .. } 3300 | ast::BitArrayOption::Signed { .. } 3301 | ast::BitArrayOption::Unsigned { .. } 3302 | ast::BitArrayOption::Big { .. } 3303 | ast::BitArrayOption::Little { .. } 3304 | ast::BitArrayOption::Native { .. } 3305 | ast::BitArrayOption::Unit { .. } => true, 3306 }) 3307 }) 3308 } 3309 3310 // Attempt to extract whole tuple as long as it's comprised of only literals 3311 TypedExpr::Tuple { elements, .. } => elements 3312 .iter() 3313 .all(|element| can_be_constant(module, element, Some(mc))), 3314 3315 // Extract literals directly 3316 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true, 3317 3318 // Extract non-record types directly 3319 TypedExpr::Var { 3320 constructor, name, .. 3321 } => { 3322 matches!( 3323 constructor.variant, 3324 type_::ValueConstructorVariant::Record { arity: 0, .. } 3325 ) || mc.contains(name) 3326 } 3327 3328 // Extract record types as long as arguments can be constant 3329 TypedExpr::Call { args, fun, .. } => { 3330 fun.is_record_builder() 3331 && args 3332 .iter() 3333 .all(|arg| can_be_constant(module, &arg.value, module_constants)) 3334 } 3335 3336 // Extract concat binary operation if both sides can be constants 3337 TypedExpr::BinOp { 3338 name, left, right, .. 3339 } => { 3340 matches!(name, ast::BinOp::Concatenate) 3341 && can_be_constant(module, left, Some(mc)) 3342 && can_be_constant(module, right, Some(mc)) 3343 } 3344 3345 TypedExpr::Block { .. } 3346 | TypedExpr::Pipeline { .. } 3347 | TypedExpr::Fn { .. } 3348 | TypedExpr::Case { .. } 3349 | TypedExpr::RecordAccess { .. } 3350 | TypedExpr::ModuleSelect { .. } 3351 | TypedExpr::TupleIndex { .. } 3352 | TypedExpr::Todo { .. } 3353 | TypedExpr::Panic { .. } 3354 | TypedExpr::Echo { .. } 3355 | TypedExpr::RecordUpdate { .. } 3356 | TypedExpr::NegateBool { .. } 3357 | TypedExpr::NegateInt { .. } 3358 | TypedExpr::Invalid { .. } => false, 3359 } 3360} 3361 3362/// Takes the list of already existing constants and functions and creates a 3363/// name that doesn't conflict with them 3364/// 3365fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString { 3366 let mut name_generator = NameGenerator::new(); 3367 let already_taken_names = VariablesNames { 3368 names: module 3369 .ast 3370 .definitions 3371 .iter() 3372 .filter_map(|definition| match definition { 3373 ast::Definition::ModuleConstant(constant) => Some(constant.name.clone()), 3374 ast::Definition::Function(function) => function.name.as_ref().map(|n| n.1.clone()), 3375 3376 ast::Definition::TypeAlias(_) 3377 | ast::Definition::CustomType(_) 3378 | ast::Definition::Import(_) => None, 3379 }) 3380 .collect(), 3381 }; 3382 name_generator.reserve_variable_names(already_taken_names); 3383 3384 name_generator.generate_name_from_type(&expr.type_()) 3385} 3386 3387impl<'a> ExtractConstant<'a> { 3388 pub fn new( 3389 module: &'a Module, 3390 line_numbers: &'a LineNumbers, 3391 params: &'a CodeActionParams, 3392 ) -> Self { 3393 Self { 3394 module, 3395 params, 3396 edits: TextEdits::new(line_numbers), 3397 selected_expression: None, 3398 container_function_start: None, 3399 variant_of_extractable: None, 3400 name_to_use: None, 3401 value_to_use: None, 3402 } 3403 } 3404 3405 pub fn code_actions(mut self) -> Vec<CodeAction> { 3406 self.visit_typed_module(&self.module.ast); 3407 3408 let ( 3409 Some(expr_span), 3410 Some(function_start), 3411 Some(type_of_extractable), 3412 Some(new_const_name), 3413 Some(const_value), 3414 ) = ( 3415 self.selected_expression, 3416 self.container_function_start, 3417 self.variant_of_extractable, 3418 self.name_to_use, 3419 self.value_to_use, 3420 ) 3421 else { 3422 return vec![]; 3423 }; 3424 3425 // We insert the constant declaration 3426 self.edits.insert( 3427 function_start, 3428 format!("const {new_const_name} = {const_value}\n\n"), 3429 ); 3430 3431 // We remove or replace the selected expression 3432 match type_of_extractable { 3433 // The whole expression is deleted for assignments 3434 ExtractableToConstant::Assignment => { 3435 let range = self 3436 .edits 3437 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 3438 3439 let indent_size = 3440 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 3441 3442 let expr_span_with_new_line = SrcSpan { 3443 // We remove leading indentation + 1 to remove the newline with it 3444 start: expr_span.start - (indent_size as u32 + 1), 3445 end: expr_span.end, 3446 }; 3447 self.edits.delete(expr_span_with_new_line); 3448 } 3449 3450 // Only right hand side is replaced for collection or values 3451 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => { 3452 self.edits.replace(expr_span, String::from(new_const_name)); 3453 } 3454 } 3455 3456 let mut action = Vec::with_capacity(1); 3457 CodeActionBuilder::new("Extract constant") 3458 .kind(CodeActionKind::REFACTOR_EXTRACT) 3459 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3460 .preferred(false) 3461 .push_to(&mut action); 3462 action 3463 } 3464} 3465 3466impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> { 3467 /// To get the position of the function containing the value or assignment 3468 /// to extract 3469 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 3470 let fun_location = fun.location; 3471 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 3472 start: fun_location.start, 3473 end: fun.end_position, 3474 }); 3475 3476 if !within(self.params.range, fun_range) { 3477 return; 3478 } 3479 self.container_function_start = Some(fun.location.start); 3480 3481 ast::visit::visit_typed_function(self, fun); 3482 } 3483 3484 /// To extract the whole assignment 3485 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 3486 let expr_location = assignment.location; 3487 3488 // We only offer this code action for extracting the whole assignment 3489 // between `let` and `=`. 3490 let pattern_location = assignment.pattern.location(); 3491 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 3492 let code_action_range = self.edits.src_span_to_lsp_range(location); 3493 3494 if !within(self.params.range, code_action_range) { 3495 ast::visit::visit_typed_assignment(self, assignment); 3496 return; 3497 } 3498 3499 // Has to be variable because patterns can't be constants. 3500 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None) 3501 { 3502 self.variant_of_extractable = Some(ExtractableToConstant::Assignment); 3503 self.selected_expression = Some(expr_location); 3504 self.name_to_use = match &assignment.pattern { 3505 Pattern::Variable { name, .. } => Some(name.clone()), 3506 _ => None, 3507 }; 3508 self.value_to_use = Some(EcoString::from( 3509 self.module 3510 .code 3511 .get( 3512 (assignment.value.location().start as usize) 3513 ..(assignment.location.end as usize), 3514 ) 3515 .expect("selected expression"), 3516 )); 3517 } 3518 } 3519 3520 /// To extract only the literal 3521 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 3522 let expr_location = expr.location(); 3523 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 3524 3525 if !within(self.params.range, expr_range) { 3526 ast::visit::visit_typed_expr(self, expr); 3527 return; 3528 } 3529 3530 // Keep going down recursively if: 3531 // - It's no extractable has been found yet (`None`). 3532 // - It's a collection, which may or may not contain a value that can 3533 // be extracted. 3534 // - It's a binary operator, which may or may not operate on 3535 // extractable values. 3536 if matches!( 3537 self.variant_of_extractable, 3538 None | Some(ExtractableToConstant::ComposedValue) 3539 ) && can_be_constant(self.module, expr, None) 3540 { 3541 self.variant_of_extractable = match expr { 3542 TypedExpr::Var { .. } 3543 | TypedExpr::Int { .. } 3544 | TypedExpr::Float { .. } 3545 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue), 3546 3547 TypedExpr::List { .. } 3548 | TypedExpr::Tuple { .. } 3549 | TypedExpr::BitArray { .. } 3550 | TypedExpr::BinOp { .. } 3551 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue), 3552 3553 TypedExpr::Block { .. } 3554 | TypedExpr::Pipeline { .. } 3555 | TypedExpr::Fn { .. } 3556 | TypedExpr::Case { .. } 3557 | TypedExpr::RecordAccess { .. } 3558 | TypedExpr::ModuleSelect { .. } 3559 | TypedExpr::TupleIndex { .. } 3560 | TypedExpr::Todo { .. } 3561 | TypedExpr::Panic { .. } 3562 | TypedExpr::Echo { .. } 3563 | TypedExpr::RecordUpdate { .. } 3564 | TypedExpr::NegateBool { .. } 3565 | TypedExpr::NegateInt { .. } 3566 | TypedExpr::Invalid { .. } => None, 3567 }; 3568 3569 self.selected_expression = Some(expr_location); 3570 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr)); 3571 self.value_to_use = Some(EcoString::from( 3572 self.module 3573 .code 3574 .get((expr_location.start as usize)..(expr_location.end as usize)) 3575 .expect("selected expression"), 3576 )); 3577 } 3578 3579 ast::visit::visit_typed_expr(self, expr); 3580 } 3581} 3582 3583/// Builder for code action to apply the "expand function capture" action. 3584/// 3585pub struct ExpandFunctionCapture<'a> { 3586 module: &'a Module, 3587 params: &'a CodeActionParams, 3588 edits: TextEdits<'a>, 3589 function_capture_data: Option<FunctionCaptureData>, 3590} 3591 3592pub struct FunctionCaptureData { 3593 function_span: SrcSpan, 3594 hole_span: SrcSpan, 3595 hole_type: Arc<Type>, 3596 reserved_names: VariablesNames, 3597} 3598 3599impl<'a> ExpandFunctionCapture<'a> { 3600 pub fn new( 3601 module: &'a Module, 3602 line_numbers: &'a LineNumbers, 3603 params: &'a CodeActionParams, 3604 ) -> Self { 3605 Self { 3606 module, 3607 params, 3608 edits: TextEdits::new(line_numbers), 3609 function_capture_data: None, 3610 } 3611 } 3612 3613 pub fn code_actions(mut self) -> Vec<CodeAction> { 3614 self.visit_typed_module(&self.module.ast); 3615 3616 let Some(FunctionCaptureData { 3617 function_span, 3618 hole_span, 3619 hole_type, 3620 reserved_names, 3621 }) = self.function_capture_data 3622 else { 3623 return vec![]; 3624 }; 3625 3626 let mut name_generator = NameGenerator::new(); 3627 name_generator.reserve_variable_names(reserved_names); 3628 let name = name_generator.generate_name_from_type(&hole_type); 3629 3630 self.edits.replace(hole_span, name.clone().into()); 3631 self.edits.insert(function_span.end, " }".into()); 3632 self.edits 3633 .insert(function_span.start, format!("fn({name}) {{ ")); 3634 3635 let mut action = Vec::with_capacity(1); 3636 CodeActionBuilder::new("Expand function capture") 3637 .kind(CodeActionKind::REFACTOR_REWRITE) 3638 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3639 .preferred(false) 3640 .push_to(&mut action); 3641 action 3642 } 3643} 3644 3645impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> { 3646 fn visit_typed_expr_fn( 3647 &mut self, 3648 location: &'ast SrcSpan, 3649 type_: &'ast Arc<Type>, 3650 kind: &'ast FunctionLiteralKind, 3651 args: &'ast [TypedArg], 3652 body: &'ast Vec1<TypedStatement>, 3653 return_annotation: &'ast Option<ast::TypeAst>, 3654 ) { 3655 let fn_range = self.edits.src_span_to_lsp_range(*location); 3656 if within(self.params.range, fn_range) && kind.is_capture() { 3657 if let [arg] = args { 3658 self.function_capture_data = Some(FunctionCaptureData { 3659 function_span: *location, 3660 hole_span: arg.location, 3661 hole_type: arg.type_.clone(), 3662 reserved_names: VariablesNames::from_statements(body), 3663 }); 3664 } 3665 } 3666 3667 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation) 3668 } 3669} 3670 3671struct VariablesNames { 3672 names: HashSet<EcoString>, 3673} 3674 3675impl VariablesNames { 3676 fn from_statements(statements: &[TypedStatement]) -> Self { 3677 let mut variables = Self { 3678 names: HashSet::new(), 3679 }; 3680 3681 for statement in statements { 3682 variables.visit_typed_statement(statement); 3683 } 3684 variables 3685 } 3686} 3687 3688impl<'ast> ast::visit::Visit<'ast> for VariablesNames { 3689 fn visit_typed_expr_var( 3690 &mut self, 3691 _location: &'ast SrcSpan, 3692 _constructor: &'ast ValueConstructor, 3693 name: &'ast EcoString, 3694 ) { 3695 let _ = self.names.insert(name.clone()); 3696 } 3697} 3698 3699/// Builder for code action to apply the "generate dynamic decoder action. 3700/// 3701pub struct GenerateDynamicDecoder<'a> { 3702 module: &'a Module, 3703 params: &'a CodeActionParams, 3704 edits: TextEdits<'a>, 3705 printer: Printer<'a>, 3706 actions: &'a mut Vec<CodeAction>, 3707} 3708 3709const DECODE_MODULE: &str = "gleam/dynamic/decode"; 3710 3711impl<'a> GenerateDynamicDecoder<'a> { 3712 pub fn new( 3713 module: &'a Module, 3714 line_numbers: &'a LineNumbers, 3715 params: &'a CodeActionParams, 3716 actions: &'a mut Vec<CodeAction>, 3717 ) -> Self { 3718 let printer = Printer::new(&module.ast.names); 3719 Self { 3720 module, 3721 params, 3722 edits: TextEdits::new(line_numbers), 3723 printer, 3724 actions, 3725 } 3726 } 3727 3728 pub fn code_actions(&mut self) { 3729 self.visit_typed_module(&self.module.ast); 3730 } 3731 3732 fn custom_type_decoder_body( 3733 &mut self, 3734 custom_type: &CustomType<Arc<Type>>, 3735 ) -> Option<EcoString> { 3736 // We cannot generate a decoder for an external type with no constructors! 3737 let constructors_size = custom_type.constructors.len(); 3738 let (first, rest) = custom_type.constructors.split_first()?; 3739 let mode = EncodingMode::for_custom_type(custom_type); 3740 3741 // We generate a decoder for a type with a single constructor: it does not 3742 // require pattern matching on a tag as there's no variants to tell apart. 3743 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag { 3744 return self.constructor_decoder(mode, custom_type, first, 0); 3745 } 3746 3747 // Otherwise we need to generate a decoder that has to tell apart different 3748 // variants, depending on the mode we might have to decode a type field or 3749 // plain strings! 3750 let module = self.printer.print_module(DECODE_MODULE); 3751 let discriminant = if mode == EncodingMode::PlainString { 3752 eco_format!("use variant <- {module}.then({module}.string)") 3753 } else { 3754 eco_format!("use variant <- {module}.field(\"type\", {module}.string)") 3755 }; 3756 3757 let mut branches = Vec::with_capacity(constructors_size); 3758 for constructor in iter::once(first).chain(rest) { 3759 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?; 3760 let name = to_snake_case(&constructor.name); 3761 branches.push(eco_format!(r#" "{name}" -> {body}"#)); 3762 } 3763 3764 let cases = branches.join("\n"); 3765 let type_name = &custom_type.name; 3766 Some(eco_format!( 3767 r#"{{ 3768 {discriminant} 3769 case variant {{ 3770{cases} 3771 _ -> {module}.failure(todo as "Zero value for {type_name}", "{type_name}") 3772 }} 3773}}"#, 3774 )) 3775 } 3776 3777 fn constructor_decoder( 3778 &mut self, 3779 mode: EncodingMode, 3780 custom_type: &ast::TypedCustomType, 3781 constructor: &TypedRecordConstructor, 3782 nesting: usize, 3783 ) -> Option<EcoString> { 3784 let decode_module = self.printer.print_module(DECODE_MODULE); 3785 let constructor_name = &constructor.name; 3786 3787 // If the constructor was encoded as a plain string with no additional 3788 // fields it means there's nothing else to decode and we can just 3789 // succeed. 3790 if mode == EncodingMode::PlainString { 3791 return Some(eco_format!("{decode_module}.success({constructor_name})")); 3792 } 3793 3794 // Otherwise we have to decode all the constructor fields to build it. 3795 let mut fields = Vec::with_capacity(constructor.arguments.len()); 3796 for argument in constructor.arguments.iter() { 3797 let (_, name) = argument.label.as_ref()?; 3798 let field = RecordField { 3799 label: RecordLabel::Labeled(name), 3800 type_: &argument.type_, 3801 }; 3802 fields.push(field); 3803 } 3804 3805 let mut decoder_printer = DecoderPrinter::new( 3806 &self.module.ast.names, 3807 custom_type.name.clone(), 3808 self.module.name.clone(), 3809 ); 3810 3811 let decoders = fields 3812 .iter() 3813 .map(|field| decoder_printer.decode_field(field, nesting + 2)) 3814 .join("\n"); 3815 3816 let indent = " ".repeat(nesting); 3817 3818 Some(if decoders.is_empty() { 3819 eco_format!("{decode_module}.success({constructor_name})") 3820 } else { 3821 let field_names = fields 3822 .iter() 3823 .map(|field| format!("{}:", field.label.variable_name())) 3824 .join(", "); 3825 3826 eco_format!( 3827 "{{ 3828{decoders} 3829{indent} {decode_module}.success({constructor_name}({field_names})) 3830{indent}}}", 3831 ) 3832 }) 3833 } 3834} 3835 3836impl<'ast> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast> { 3837 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 3838 let range = self.edits.src_span_to_lsp_range(custom_type.location); 3839 if !overlaps(self.params.range, range) { 3840 return; 3841 } 3842 3843 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name)); 3844 let Some(function_body) = self.custom_type_decoder_body(custom_type) else { 3845 return; 3846 }; 3847 3848 let parameters = match custom_type.parameters.len() { 3849 0 => EcoString::new(), 3850 _ => eco_format!( 3851 "({})", 3852 custom_type 3853 .parameters 3854 .iter() 3855 .map(|(_, name)| name) 3856 .join(", ") 3857 ), 3858 }; 3859 3860 let decoder_type = self.printer.print_type(&Type::Named { 3861 publicity: ast::Publicity::Public, 3862 package: STDLIB_PACKAGE_NAME.into(), 3863 module: DECODE_MODULE.into(), 3864 name: "Decoder".into(), 3865 args: vec![], 3866 inferred_variant: None, 3867 }); 3868 3869 let function = format!( 3870 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}", 3871 type_name = custom_type.name, 3872 ); 3873 3874 self.edits.insert(custom_type.end_position, function); 3875 maybe_import(&mut self.edits, self.module, DECODE_MODULE); 3876 3877 CodeActionBuilder::new("Generate dynamic decoder") 3878 .kind(CodeActionKind::REFACTOR) 3879 .preferred(false) 3880 .changes( 3881 self.params.text_document.uri.clone(), 3882 std::mem::take(&mut self.edits.edits), 3883 ) 3884 .push_to(self.actions); 3885 } 3886} 3887 3888/// If `module_name` is not already imported inside `module`, adds an edit to 3889/// add that import. 3890/// This function also makes sure not to import a module in itself. 3891/// 3892fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) { 3893 if module.ast.names.is_imported(module_name) || module.name == module_name { 3894 return; 3895 } 3896 3897 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers); 3898 let first_is_import = first_import_pos.is_some(); 3899 let import_location = first_import_pos.unwrap_or_default(); 3900 let after_import_newlines = add_newlines_after_import( 3901 import_location, 3902 first_is_import, 3903 edits.line_numbers, 3904 &module.code, 3905 ); 3906 3907 edits.edits.push(get_import_edit( 3908 import_location, 3909 module_name, 3910 &after_import_newlines, 3911 )); 3912} 3913 3914struct DecoderPrinter<'a> { 3915 printer: Printer<'a>, 3916 /// The name of the root type we are printing a decoder for 3917 type_name: EcoString, 3918 /// The module name of the root type we are printing a decoder for 3919 type_module: EcoString, 3920} 3921 3922struct RecordField<'a> { 3923 label: RecordLabel<'a>, 3924 type_: &'a Type, 3925} 3926 3927enum RecordLabel<'a> { 3928 Labeled(&'a str), 3929 Unlabeled(usize), 3930} 3931 3932impl RecordLabel<'_> { 3933 fn field_key(&self) -> EcoString { 3934 match self { 3935 RecordLabel::Labeled(label) => eco_format!("\"{label}\""), 3936 RecordLabel::Unlabeled(index) => { 3937 eco_format!("{index}") 3938 } 3939 } 3940 } 3941 3942 fn variable_name(&self) -> EcoString { 3943 match self { 3944 RecordLabel::Labeled(label) => (*label).into(), 3945 &RecordLabel::Unlabeled(mut index) => { 3946 let mut characters = Vec::new(); 3947 let alphabet_length = 26; 3948 let alphabet_offset = b'a'; 3949 loop { 3950 let alphabet_index = (index % alphabet_length) as u8; 3951 characters.push((alphabet_offset + alphabet_index) as char); 3952 index /= alphabet_length; 3953 3954 if index == 0 { 3955 break; 3956 } 3957 index -= 1; 3958 } 3959 characters.into_iter().rev().collect() 3960 } 3961 } 3962 } 3963} 3964 3965impl<'a> DecoderPrinter<'a> { 3966 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self { 3967 Self { 3968 type_name, 3969 type_module, 3970 printer: Printer::new(names), 3971 } 3972 } 3973 3974 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString { 3975 let module_name = self.printer.print_module(DECODE_MODULE); 3976 if type_.is_bit_array() { 3977 eco_format!("{module_name}.bit_array") 3978 } else if type_.is_bool() { 3979 eco_format!("{module_name}.bool") 3980 } else if type_.is_float() { 3981 eco_format!("{module_name}.float") 3982 } else if type_.is_int() { 3983 eco_format!("{module_name}.int") 3984 } else if type_.is_string() { 3985 eco_format!("{module_name}.string") 3986 } else { 3987 match type_.tuple_types() { 3988 Some(types) => { 3989 let fields = types 3990 .iter() 3991 .enumerate() 3992 .map(|(index, type_)| RecordField { 3993 type_, 3994 label: RecordLabel::Unlabeled(index), 3995 }) 3996 .collect_vec(); 3997 let decoders = fields 3998 .iter() 3999 .map(|field| self.decode_field(field, indent + 2)) 4000 .join("\n"); 4001 let mut field_names = fields.iter().map(|field| field.label.variable_name()); 4002 4003 eco_format!( 4004 "{{ 4005{decoders} 4006 4007{indent} {module_name}.success(#({fields})) 4008{indent}}}", 4009 fields = field_names.join(", "), 4010 indent = " ".repeat(indent) 4011 ) 4012 } 4013 _ => { 4014 let type_information = type_.named_type_information(); 4015 let type_information = 4016 type_information.as_ref().map(|(module, name, arguments)| { 4017 (module.as_str(), name.as_str(), arguments.as_slice()) 4018 }); 4019 4020 match type_information { 4021 Some(("gleam/dynamic", "Dynamic", _)) => { 4022 eco_format!("{module_name}.dynamic") 4023 } 4024 Some(("gleam", "List", [element])) => { 4025 eco_format!("{module_name}.list({})", self.decoder_for(element, indent)) 4026 } 4027 Some(("gleam/option", "Option", [some])) => { 4028 eco_format!( 4029 "{module_name}.optional({})", 4030 self.decoder_for(some, indent) 4031 ) 4032 } 4033 Some(("gleam/dict", "Dict", [key, value])) => { 4034 eco_format!( 4035 "{module_name}.dict({}, {})", 4036 self.decoder_for(key, indent), 4037 self.decoder_for(value, indent) 4038 ) 4039 } 4040 Some((module, name, _)) 4041 if module == self.type_module && name == self.type_name => 4042 { 4043 eco_format!("{}_decoder()", to_snake_case(name)) 4044 } 4045 _ => eco_format!( 4046 r#"todo as "Decoder for {}""#, 4047 self.printer.print_type(type_) 4048 ), 4049 } 4050 } 4051 } 4052 } 4053 } 4054 4055 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4056 let decoder = self.decoder_for(field.type_, indent); 4057 4058 eco_format!( 4059 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#, 4060 indent = " ".repeat(indent), 4061 variable = field.label.variable_name(), 4062 field = field.label.field_key(), 4063 module = self.printer.print_module(DECODE_MODULE) 4064 ) 4065 } 4066} 4067 4068/// Builder for code action to apply the "Generate to-JSON function" action. 4069/// 4070pub struct GenerateJsonEncoder<'a> { 4071 module: &'a Module, 4072 params: &'a CodeActionParams, 4073 edits: TextEdits<'a>, 4074 printer: Printer<'a>, 4075 actions: &'a mut Vec<CodeAction>, 4076 config: &'a PackageConfig, 4077} 4078 4079const JSON_MODULE: &str = "gleam/json"; 4080const JSON_PACKAGE_NAME: &str = "gleam_json"; 4081 4082#[derive(Eq, PartialEq, Copy, Clone)] 4083enum EncodingMode { 4084 PlainString, 4085 ObjectWithTypeTag, 4086 ObjectWithNoTypeTag, 4087} 4088 4089impl EncodingMode { 4090 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self { 4091 match type_.constructors.as_slice() { 4092 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString, 4093 [_constructor] => EncodingMode::ObjectWithNoTypeTag, 4094 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => { 4095 EncodingMode::PlainString 4096 } 4097 _constructors => EncodingMode::ObjectWithTypeTag, 4098 } 4099 } 4100} 4101 4102impl<'a> GenerateJsonEncoder<'a> { 4103 pub fn new( 4104 module: &'a Module, 4105 line_numbers: &'a LineNumbers, 4106 params: &'a CodeActionParams, 4107 actions: &'a mut Vec<CodeAction>, 4108 config: &'a PackageConfig, 4109 ) -> Self { 4110 let printer = Printer::new(&module.ast.names); 4111 Self { 4112 module, 4113 params, 4114 edits: TextEdits::new(line_numbers), 4115 printer, 4116 actions, 4117 config, 4118 } 4119 } 4120 4121 pub fn code_actions(&mut self) { 4122 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME) 4123 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME) 4124 { 4125 self.visit_typed_module(&self.module.ast); 4126 } 4127 } 4128 4129 fn custom_type_encoder_body( 4130 &mut self, 4131 record_name: EcoString, 4132 custom_type: &CustomType<Arc<Type>>, 4133 ) -> Option<EcoString> { 4134 // We cannot generate a decoder for an external type with no constructors! 4135 let constructors_size = custom_type.constructors.len(); 4136 let (first, rest) = custom_type.constructors.split_first()?; 4137 let mode = EncodingMode::for_custom_type(custom_type); 4138 4139 // We generate an encoder for a type with a single constructor: it does not 4140 // require pattern matching on the argument as we can access all its fields 4141 // with the usual record access syntax. 4142 if rest.is_empty() { 4143 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?; 4144 let unpacking = if first.arguments.is_empty() { 4145 "" 4146 } else { 4147 &eco_format!( 4148 "let {name}({fields}:) = {record_name}\n ", 4149 name = first.name, 4150 fields = first 4151 .arguments 4152 .iter() 4153 .filter_map(|argument| { 4154 argument.label.as_ref().map(|(_location, label)| label) 4155 }) 4156 .join(":, ") 4157 ) 4158 }; 4159 return Some(eco_format!("{unpacking}{encoder}")); 4160 } 4161 4162 // Otherwise we generate an encoder for a type with multiple constructors: 4163 // it will need to pattern match on the various constructors and encode each 4164 // one separately. 4165 let mut branches = Vec::with_capacity(constructors_size); 4166 for constructor in iter::once(first).chain(rest) { 4167 let RecordConstructor { name, .. } = constructor; 4168 let encoder = 4169 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?; 4170 let unpacking = if constructor.arguments.is_empty() { 4171 "" 4172 } else { 4173 &eco_format!( 4174 "({}:)", 4175 constructor 4176 .arguments 4177 .iter() 4178 .filter_map(|argument| { 4179 argument.label.as_ref().map(|(_location, label)| label) 4180 }) 4181 .join(":, ") 4182 ) 4183 }; 4184 branches.push(eco_format!(" {name}{unpacking} -> {encoder}")); 4185 } 4186 4187 let branches = branches.join("\n"); 4188 Some(eco_format!( 4189 "case {record_name} {{ 4190{branches} 4191 }}", 4192 )) 4193 } 4194 4195 fn constructor_encoder( 4196 &mut self, 4197 mode: EncodingMode, 4198 constructor: &TypedRecordConstructor, 4199 type_name: EcoString, 4200 nesting: usize, 4201 ) -> Option<EcoString> { 4202 let json_module = self.printer.print_module(JSON_MODULE); 4203 let tag = to_snake_case(&constructor.name); 4204 let indent = " ".repeat(nesting); 4205 4206 // If the variant is encoded as a simple json string we just call the 4207 // `json.string` with the variant tag as an argument. 4208 if mode == EncodingMode::PlainString { 4209 return Some(eco_format!("{json_module}.string(\"{tag}\")")); 4210 } 4211 4212 // Otherwise we turn it into an object with a `type` tag field. 4213 let mut encoder_printer = 4214 JsonEncoderPrinter::new(&self.module.ast.names, type_name, self.module.name.clone()); 4215 4216 // These are the fields of the json object to encode. 4217 let mut fields = Vec::with_capacity(constructor.arguments.len()); 4218 if mode == EncodingMode::ObjectWithTypeTag { 4219 // Any needed type tag is always going to be the first field in the object 4220 fields.push(eco_format!( 4221 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))" 4222 )); 4223 } 4224 4225 for argument in constructor.arguments.iter() { 4226 let (_, label) = argument.label.as_ref()?; 4227 let field = RecordField { 4228 label: RecordLabel::Labeled(label), 4229 type_: &argument.type_, 4230 }; 4231 let encoder = encoder_printer.encode_field(&field, nesting + 2); 4232 fields.push(encoder); 4233 } 4234 4235 let fields = fields.join(",\n"); 4236 Some(eco_format!( 4237 "{json_module}.object([ 4238{fields}, 4239{indent}])" 4240 )) 4241 } 4242} 4243 4244impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> { 4245 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 4246 let range = self.edits.src_span_to_lsp_range(custom_type.location); 4247 if !overlaps(self.params.range, range) { 4248 return; 4249 } 4250 4251 let record_name = to_snake_case(&custom_type.name); 4252 let name = eco_format!("{record_name}_to_json"); 4253 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else { 4254 return; 4255 }; 4256 4257 let json_type = self.printer.print_type(&Type::Named { 4258 publicity: ast::Publicity::Public, 4259 package: JSON_PACKAGE_NAME.into(), 4260 module: JSON_MODULE.into(), 4261 name: "Json".into(), 4262 args: vec![], 4263 inferred_variant: None, 4264 }); 4265 4266 let type_ = if custom_type.parameters.is_empty() { 4267 custom_type.name.clone() 4268 } else { 4269 let parameters = custom_type 4270 .parameters 4271 .iter() 4272 .map(|(_, name)| name) 4273 .join(", "); 4274 eco_format!("{}({})", custom_type.name, parameters) 4275 }; 4276 4277 let function = format!( 4278 " 4279 4280fn {name}({record_name}: {type_}) -> {json_type} {{ 4281 {encoder} 4282}}", 4283 ); 4284 4285 self.edits.insert(custom_type.end_position, function); 4286 maybe_import(&mut self.edits, self.module, JSON_MODULE); 4287 4288 CodeActionBuilder::new("Generate to-JSON function") 4289 .kind(CodeActionKind::REFACTOR) 4290 .preferred(false) 4291 .changes( 4292 self.params.text_document.uri.clone(), 4293 std::mem::take(&mut self.edits.edits), 4294 ) 4295 .push_to(self.actions); 4296 } 4297} 4298 4299struct JsonEncoderPrinter<'a> { 4300 printer: Printer<'a>, 4301 /// The name of the root type we are printing an encoder for 4302 type_name: EcoString, 4303 /// The module name of the root type we are printing an encoder for 4304 type_module: EcoString, 4305} 4306 4307impl<'a> JsonEncoderPrinter<'a> { 4308 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self { 4309 Self { 4310 type_name, 4311 type_module, 4312 printer: Printer::new(names), 4313 } 4314 } 4315 4316 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString { 4317 let module_name = self.printer.print_module(JSON_MODULE); 4318 let is_capture = encoded_value == "_"; 4319 let maybe_capture = |mut function: EcoString| { 4320 if is_capture { 4321 function 4322 } else { 4323 function.push('('); 4324 function.push_str(encoded_value); 4325 function.push(')'); 4326 function 4327 } 4328 }; 4329 4330 if type_.is_bool() { 4331 maybe_capture(eco_format!("{module_name}.bool")) 4332 } else if type_.is_float() { 4333 maybe_capture(eco_format!("{module_name}.float")) 4334 } else if type_.is_int() { 4335 maybe_capture(eco_format!("{module_name}.int")) 4336 } else if type_.is_string() { 4337 maybe_capture(eco_format!("{module_name}.string")) 4338 } else { 4339 match type_.tuple_types() { 4340 Some(types) => { 4341 let (tuple, new_indent) = if is_capture { 4342 ("value", indent + 4) 4343 } else { 4344 (encoded_value, indent + 2) 4345 }; 4346 4347 let encoders = types 4348 .iter() 4349 .enumerate() 4350 .map(|(index, type_)| { 4351 self.encoder_for(&format!("{tuple}.{index}"), type_, new_indent) 4352 }) 4353 .collect_vec(); 4354 4355 if is_capture { 4356 eco_format!( 4357 "fn(value) {{ 4358{indent} {module_name}.preprocessed_array([ 4359{indent} {encoders}, 4360{indent} ]) 4361{indent}}}", 4362 indent = " ".repeat(indent), 4363 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 4364 ) 4365 } else { 4366 eco_format!( 4367 "{module_name}.preprocessed_array([ 4368{indent} {encoders}, 4369{indent}])", 4370 indent = " ".repeat(indent), 4371 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 4372 ) 4373 } 4374 } 4375 _ => { 4376 let type_information = type_.named_type_information(); 4377 let type_information: Option<(&str, &str, &[Arc<Type>])> = 4378 type_information.as_ref().map(|(module, name, arguments)| { 4379 (module.as_str(), name.as_str(), arguments.as_slice()) 4380 }); 4381 4382 match type_information { 4383 Some(("gleam", "List", [element])) => { 4384 eco_format!( 4385 "{module_name}.array({encoded_value}, {map_function})", 4386 map_function = self.encoder_for("_", element, indent) 4387 ) 4388 } 4389 Some(("gleam/option", "Option", [some])) => { 4390 eco_format!( 4391 "case {encoded_value} {{ 4392{indent} {none} -> {module_name}.null() 4393{indent} {some}(value) -> {encoder} 4394{indent}}}", 4395 indent = " ".repeat(indent), 4396 none = self 4397 .printer 4398 .print_constructor(&"gleam/option".into(), &"None".into()), 4399 some = self 4400 .printer 4401 .print_constructor(&"gleam/option".into(), &"Some".into()), 4402 encoder = self.encoder_for("value", some, indent + 2) 4403 ) 4404 } 4405 Some(("gleam/dict", "Dict", [key, value])) => { 4406 let stringify_function = match key 4407 .named_type_information() 4408 .as_ref() 4409 .map(|(module, name, arguments)| { 4410 (module.as_str(), name.as_str(), arguments.as_slice()) 4411 }) { 4412 Some(("gleam", "String", [])) => "fn(string) { string }", 4413 _ => &format!( 4414 r#"todo as "Function to stringify {}""#, 4415 self.printer.print_type(key) 4416 ), 4417 }; 4418 eco_format!( 4419 "{module_name}.dict({encoded_value}, {stringify_function}, {})", 4420 self.encoder_for("_", value, indent) 4421 ) 4422 } 4423 Some((module, name, _)) 4424 if module == self.type_module && name == self.type_name => 4425 { 4426 maybe_capture(eco_format!("{}_to_json", to_snake_case(name))) 4427 } 4428 _ => eco_format!( 4429 r#"todo as "Encoder for {}""#, 4430 self.printer.print_type(type_) 4431 ), 4432 } 4433 } 4434 } 4435 } 4436 } 4437 4438 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4439 let field_name = field.label.variable_name(); 4440 let encoder = self.encoder_for(&field_name, field.type_, indent); 4441 4442 eco_format!( 4443 r#"{indent}#("{field_name}", {encoder})"#, 4444 indent = " ".repeat(indent), 4445 ) 4446 } 4447} 4448 4449/// Builder for code action to pattern match on things like (anonymous) function 4450/// arguments or variables. 4451/// For example: 4452/// 4453/// ```gleam 4454/// pub fn wibble(arg: #(key, value)) { 4455/// // ^ [pattern match on argument] 4456/// } 4457/// 4458/// // Generates 4459/// 4460/// pub fn wibble(arg: #(key, value)) { 4461/// let #(value_0, value_1) = arg 4462/// } 4463/// ``` 4464/// 4465/// Another example with variables: 4466/// 4467/// ```gleam 4468/// pub fn main() { 4469/// let pair = #(1, 3) 4470/// // ^ [pattern match on value] 4471/// } 4472/// 4473/// // Generates 4474/// 4475/// pub fn main() { 4476/// let pair = #(1, 3) 4477/// let #(value_0, value_1) = pair 4478/// } 4479/// ``` 4480/// 4481pub struct PatternMatchOnValue<'a, A> { 4482 module: &'a Module, 4483 params: &'a CodeActionParams, 4484 compiler: &'a LspProjectCompiler<A>, 4485 selected_value: Option<PatternMatchedValue<'a>>, 4486 edits: TextEdits<'a>, 4487} 4488 4489/// A value we might want to pattern match on. 4490/// Each variant will also contain all the info needed to know how to properly 4491/// print and format the corresponding pattern matching code; that's why you'll 4492/// see `Range`s and `SrcSpan` besides the type of the thing being matched. 4493/// 4494pub enum PatternMatchedValue<'a> { 4495 FunctionArgument { 4496 /// The argument being pattern matched on. 4497 /// 4498 arg: &'a TypedArg, 4499 /// The first statement inside the function body. Used to correctly 4500 /// position the inserted pattern matching. 4501 /// 4502 first_statement: &'a TypedStatement, 4503 /// The range of the entire function holding the argument. 4504 /// 4505 function_range: Range, 4506 }, 4507 LetVariable { 4508 variable_name: &'a EcoString, 4509 variable_type: &'a Arc<Type>, 4510 /// The location of the entire let assignment the variable is part of, 4511 /// so that we can add the pattern matching _after_ it. 4512 /// 4513 assignment_location: SrcSpan, 4514 }, 4515} 4516 4517impl<'a, IO> PatternMatchOnValue<'a, IO> 4518where 4519 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, 4520{ 4521 pub fn new( 4522 module: &'a Module, 4523 line_numbers: &'a LineNumbers, 4524 params: &'a CodeActionParams, 4525 compiler: &'a LspProjectCompiler<IO>, 4526 ) -> Self { 4527 Self { 4528 module, 4529 params, 4530 compiler, 4531 selected_value: None, 4532 edits: TextEdits::new(line_numbers), 4533 } 4534 } 4535 4536 pub fn code_actions(mut self) -> Vec<CodeAction> { 4537 self.visit_typed_module(&self.module.ast); 4538 4539 let action_title = match self.selected_value { 4540 Some(PatternMatchedValue::FunctionArgument { 4541 arg, 4542 first_statement: function_body, 4543 function_range, 4544 }) => { 4545 self.match_on_function_argument(arg, function_body, function_range); 4546 "Pattern match on argument" 4547 } 4548 Some(PatternMatchedValue::LetVariable { 4549 variable_name, 4550 variable_type, 4551 assignment_location, 4552 }) => { 4553 self.match_on_let_variable(variable_name, variable_type, assignment_location); 4554 "Pattern match on variable" 4555 } 4556 None => return vec![], 4557 }; 4558 4559 if self.edits.edits.is_empty() { 4560 return vec![]; 4561 } 4562 4563 let mut action = Vec::with_capacity(1); 4564 CodeActionBuilder::new(action_title) 4565 .kind(CodeActionKind::REFACTOR_REWRITE) 4566 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4567 .preferred(false) 4568 .push_to(&mut action); 4569 action 4570 } 4571 4572 fn match_on_function_argument( 4573 &mut self, 4574 arg: &TypedArg, 4575 first_statement: &TypedStatement, 4576 function_range: Range, 4577 ) { 4578 let Some(arg_name) = arg.get_variable_name() else { 4579 return; 4580 }; 4581 4582 let Some(patterns) = self.type_to_destructure_patterns(arg.type_.as_ref()) else { 4583 return; 4584 }; 4585 4586 let first_statement_location = first_statement.location(); 4587 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location); 4588 4589 // If we're trying to insert the pattern matching on the same 4590 // line as the one where the function is defined we will want to 4591 // put it on a new line instead. So in that case the nesting will 4592 // be the default 2 spaces. 4593 let needs_newline = function_range.start.line == first_statement_range.start.line; 4594 let nesting = if needs_newline { 4595 String::from(" ") 4596 } else { 4597 " ".repeat(first_statement_range.start.character as usize) 4598 }; 4599 4600 let pattern_matching = if patterns.len() == 1 { 4601 let pattern = patterns.first(); 4602 format!("let {pattern} = {arg_name}") 4603 } else { 4604 let patterns = patterns 4605 .iter() 4606 .map(|p| format!(" {nesting}{p} -> todo")) 4607 .join("\n"); 4608 format!("case {arg_name} {{\n{patterns}\n{nesting}}}") 4609 }; 4610 4611 let pattern_matching = if needs_newline { 4612 format!("\n{nesting}{pattern_matching}") 4613 } else { 4614 pattern_matching 4615 }; 4616 4617 let has_empty_body = match first_statement { 4618 ast::Statement::Expression(TypedExpr::Todo { 4619 kind: TodoKind::EmptyFunction { .. }, 4620 .. 4621 }) => true, 4622 _ => false, 4623 }; 4624 4625 // If the pattern matching is added to a function with an empty 4626 // body then we do not add any nesting after it, or we would be 4627 // increasing the nesting of the closing `}`! 4628 let pattern_matching = if has_empty_body { 4629 format!("{pattern_matching}\n") 4630 } else { 4631 format!("{pattern_matching}\n{nesting}") 4632 }; 4633 4634 self.edits 4635 .insert(first_statement_location.start, pattern_matching); 4636 } 4637 4638 fn match_on_let_variable( 4639 &mut self, 4640 variable_name: &EcoString, 4641 variable_type: &Arc<Type>, 4642 assignment_location: SrcSpan, 4643 ) { 4644 let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref()) else { 4645 return; 4646 }; 4647 4648 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); 4649 let nesting = " ".repeat(assignment_range.start.character as usize); 4650 4651 let pattern_matching = if patterns.len() == 1 { 4652 let pattern = patterns.first(); 4653 format!("let {pattern} = {variable_name}") 4654 } else { 4655 let patterns = patterns 4656 .iter() 4657 .map(|p| format!(" {nesting}{p} -> todo")) 4658 .join("\n"); 4659 format!("case {variable_name} {{\n{patterns}\n{nesting}}}") 4660 }; 4661 4662 self.edits.insert( 4663 assignment_location.end, 4664 format!("\n{nesting}{pattern_matching}"), 4665 ); 4666 } 4667 4668 /// Will produce a pattern that can be used on the left hand side of a let 4669 /// assignment to destructure a value of the given type. For example given 4670 /// this type: 4671 /// 4672 /// ```gleam 4673 /// pub type Wibble { 4674 /// Wobble(Int, label: String) 4675 /// } 4676 /// ``` 4677 /// 4678 /// The produced pattern will look like this: `Wobble(value_0, label:)`. 4679 /// The pattern will use the correct qualified/unqualified name for the 4680 /// constructor if it comes from another package. 4681 /// 4682 /// The function will only produce a list of patterns that can be used from 4683 /// the current module. So if the type comes from another module it must be 4684 /// public! Otherwise this function will return an empty vec. 4685 /// 4686 fn type_to_destructure_patterns(&mut self, type_: &Type) -> Option<Vec1<EcoString>> { 4687 match type_ { 4688 Type::Fn { .. } => None, 4689 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow()), 4690 Type::Named { 4691 module: type_module, 4692 name: type_name, 4693 .. 4694 } => { 4695 let patterns = 4696 get_type_constructors(self.compiler, &self.module.name, type_module, type_name) 4697 .iter() 4698 .filter_map(|c| self.record_constructor_to_destructure_pattern(c)) 4699 .collect_vec(); 4700 4701 Vec1::try_from_vec(patterns).ok() 4702 } 4703 // We don't want to suggest this action for empty tuple as it 4704 // doesn't make a lot of sense to match on those. 4705 Type::Tuple { elements } if elements.is_empty() => None, 4706 Type::Tuple { elements } => Some(vec1![eco_format!( 4707 "#({})", 4708 (0..elements.len() as u32) 4709 .map(|i| format!("value_{i}")) 4710 .join(", ") 4711 )]), 4712 } 4713 } 4714 4715 fn type_var_to_destructure_patterns(&mut self, type_var: &TypeVar) -> Option<Vec1<EcoString>> { 4716 match type_var { 4717 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, 4718 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_), 4719 } 4720 } 4721 4722 /// Given the value constructor of a record, returns a string with the 4723 /// pattern used to match on that specific variant. 4724 /// 4725 /// Note how: 4726 /// - If the constructor is internal to another module or comes from another 4727 /// module, then this returns `None` since one cannot pattern match on it. 4728 /// - If the provided `ValueConstructor` is not a record constructor this 4729 /// will return `None`. 4730 /// 4731 fn record_constructor_to_destructure_pattern( 4732 &self, 4733 constructor: &ValueConstructor, 4734 ) -> Option<EcoString> { 4735 let type_::ValueConstructorVariant::Record { 4736 name: constructor_name, 4737 arity: constructor_arity, 4738 module: constructor_module, 4739 field_map, 4740 .. 4741 } = &constructor.variant 4742 else { 4743 // The constructor should always be a record, in case it's not 4744 // there's not much we can do and just fail. 4745 return None; 4746 }; 4747 4748 // Since the constructor is a record constructor we know that its type 4749 // is either `Named` or a `Fn` type, in either case we have to get the 4750 // arguments types out of it. 4751 let Some(arguments_types) = constructor 4752 .type_ 4753 .fn_types() 4754 .map(|(arguments_types, _return)| arguments_types) 4755 .or_else(|| constructor.type_.constructor_types()) 4756 else { 4757 // This should never happen but just in case we don't want to unwrap 4758 // and panic. 4759 return None; 4760 }; 4761 4762 let mut name_generator = NameGenerator::new(); 4763 let index_to_label = match field_map { 4764 None => HashMap::new(), 4765 Some(field_map) => { 4766 name_generator.reserve_all_labels(field_map); 4767 4768 field_map 4769 .fields 4770 .iter() 4771 .map(|(label, index)| (index, label)) 4772 .collect::<HashMap<_, _>>() 4773 } 4774 }; 4775 4776 let mut pattern = 4777 pretty_constructor_name(self.module, constructor_module, constructor_name)?; 4778 4779 if *constructor_arity == 0 { 4780 return Some(pattern); 4781 } 4782 4783 pattern.push('('); 4784 let args = (0..*constructor_arity as u32) 4785 .map(|i| match index_to_label.get(&i) { 4786 Some(label) => eco_format!("{label}:"), 4787 None => match arguments_types.get(i as usize) { 4788 None => name_generator.rename_to_avoid_shadowing(EcoString::from("value")), 4789 Some(type_) => name_generator.generate_name_from_type(type_), 4790 }, 4791 }) 4792 .join(", "); 4793 4794 pattern.push_str(&args); 4795 pattern.push(')'); 4796 Some(pattern) 4797 } 4798} 4799 4800impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> 4801where 4802 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, 4803{ 4804 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 4805 // If we're not inside the function there's no point in exploring its 4806 // ast further. 4807 let function_span = SrcSpan { 4808 start: fun.location.start, 4809 end: fun.end_position, 4810 }; 4811 let function_range = self.edits.src_span_to_lsp_range(function_span); 4812 if !within(self.params.range, function_range) { 4813 return; 4814 } 4815 4816 for arg in &fun.arguments { 4817 // If the cursor is placed on one of the arguments, then we can try 4818 // and generate code for that one. 4819 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 4820 if within(self.params.range, arg_range) { 4821 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 4822 arg, 4823 first_statement: fun.body.first(), 4824 function_range, 4825 }); 4826 return; 4827 } 4828 } 4829 4830 // If the cursor is not on any of the function arguments then we keep 4831 // exploring the function body as we might want to destructure the 4832 // argument of an expression function! 4833 ast::visit::visit_typed_function(self, fun); 4834 } 4835 4836 fn visit_typed_expr_fn( 4837 &mut self, 4838 location: &'ast SrcSpan, 4839 type_: &'ast Arc<Type>, 4840 kind: &'ast FunctionLiteralKind, 4841 args: &'ast [TypedArg], 4842 body: &'ast Vec1<TypedStatement>, 4843 return_annotation: &'ast Option<ast::TypeAst>, 4844 ) { 4845 // If we're not inside the function there's no point in exploring its 4846 // ast further. 4847 let function_range = self.edits.src_span_to_lsp_range(*location); 4848 if !within(self.params.range, function_range) { 4849 return; 4850 } 4851 4852 for arg in args { 4853 // If the cursor is placed on one of the arguments, then we can try 4854 // and generate code for that one. 4855 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 4856 if within(self.params.range, arg_range) { 4857 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 4858 arg, 4859 first_statement: body.first(), 4860 function_range, 4861 }); 4862 return; 4863 } 4864 } 4865 4866 // If the cursor is not on any of the function arguments then we keep 4867 // exploring the function body as we might want to destructure the 4868 // argument of an expression function! 4869 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 4870 } 4871 4872 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 4873 if let Pattern::Variable { 4874 name, 4875 location, 4876 type_, 4877 .. 4878 } = &assignment.pattern 4879 { 4880 let variable_range = self.edits.src_span_to_lsp_range(*location); 4881 if within(self.params.range, variable_range) { 4882 self.selected_value = Some(PatternMatchedValue::LetVariable { 4883 variable_name: name, 4884 variable_type: type_, 4885 assignment_location: assignment.location, 4886 }); 4887 // If we've found the variable to pattern match on, there's no 4888 // point in keeping traversing the AST. 4889 return; 4890 } 4891 } 4892 4893 ast::visit::visit_typed_assignment(self, assignment); 4894 } 4895} 4896 4897/// Given a type and its module, returns a list of its *importable* 4898/// constructors. 4899/// 4900/// Since this focuses just on importable constructors, if either the module or 4901/// the type are internal the returned array will be empty! 4902/// 4903fn get_type_constructors<'a, 'b, IO>( 4904 compiler: &'a LspProjectCompiler<IO>, 4905 current_module: &'b EcoString, 4906 type_module: &'b EcoString, 4907 type_name: &'b EcoString, 4908) -> Vec<&'a ValueConstructor> 4909where 4910 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, 4911{ 4912 let type_is_inside_current_module = current_module == type_module; 4913 let module_interface = if !type_is_inside_current_module { 4914 // If the type is outside of the module we're in, we can only pattern 4915 // match on it if the module can be imported. 4916 // The `get_module_interface` already takes care of making this check. 4917 compiler.get_module_interface(type_module) 4918 } else { 4919 // However, if the type is defined in the module we're in, we can always 4920 // pattern match on it. So we get the current module's interface. 4921 compiler 4922 .modules 4923 .get(current_module) 4924 .map(|module| &module.ast.type_info) 4925 }; 4926 4927 let Some(module_interface) = module_interface else { 4928 return vec![]; 4929 }; 4930 4931 // If the type is in an internal module that is not the current one, we 4932 // cannot use its constructors! 4933 if !type_is_inside_current_module && module_interface.is_internal { 4934 return vec![]; 4935 } 4936 4937 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else { 4938 return vec![]; 4939 }; 4940 4941 constructors 4942 .variants 4943 .iter() 4944 .filter_map(|variant| { 4945 let constructor = module_interface.values.get(&variant.name)?; 4946 if type_is_inside_current_module || constructor.publicity.is_public() { 4947 Some(constructor) 4948 } else { 4949 None 4950 } 4951 }) 4952 .collect_vec() 4953} 4954 4955/// Returns a pretty printed record constructor name, the way it would be used 4956/// inside the given `module` (with the correct name and qualification). 4957/// 4958/// If the constructor cannot be used inside the module because it's not 4959/// imported, then this function will return `None`. 4960/// 4961fn pretty_constructor_name( 4962 module: &Module, 4963 constructor_module: &EcoString, 4964 constructor_name: &EcoString, 4965) -> Option<EcoString> { 4966 match module 4967 .ast 4968 .names 4969 .named_constructor(constructor_module, constructor_name) 4970 { 4971 type_::printer::NameContextInformation::Unimported(_) => None, 4972 type_::printer::NameContextInformation::Unqualified(constructor_name) => { 4973 Some(eco_format!("{constructor_name}")) 4974 } 4975 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => { 4976 Some(eco_format!("{module_name}.{constructor_name}")) 4977 } 4978 } 4979} 4980 4981/// Builder for the "generate function" code action. 4982/// Whenever someone hovers an invalid expression that is inferred to have a 4983/// function type the language server can generate a function definition for it. 4984/// For example: 4985/// 4986/// ```gleam 4987/// pub fn main() { 4988/// wibble(1, 2, "hello") 4989/// // ^ [generate function] 4990/// } 4991/// ``` 4992/// 4993/// Will generate the following definition: 4994/// 4995/// ```gleam 4996/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a { 4997/// todo 4998/// } 4999/// ``` 5000/// 5001pub struct GenerateFunction<'a> { 5002 module: &'a Module, 5003 params: &'a CodeActionParams, 5004 edits: TextEdits<'a>, 5005 last_visited_function_end: Option<u32>, 5006 function_to_generate: Option<FunctionToGenerate<'a>>, 5007} 5008 5009struct FunctionToGenerate<'a> { 5010 name: &'a str, 5011 arguments_types: Vec<Arc<Type>>, 5012 5013 /// The arguments actually supplied as input to the function, if any. 5014 /// A function to generate might as well be just a name passed as an argument 5015 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually 5016 /// have any actual arguments! 5017 given_arguments: Option<&'a [TypedCallArg]>, 5018 return_type: Arc<Type>, 5019 previous_function_end: Option<u32>, 5020} 5021 5022impl<'a> GenerateFunction<'a> { 5023 pub fn new( 5024 module: &'a Module, 5025 line_numbers: &'a LineNumbers, 5026 params: &'a CodeActionParams, 5027 ) -> Self { 5028 Self { 5029 module, 5030 params, 5031 edits: TextEdits::new(line_numbers), 5032 last_visited_function_end: None, 5033 function_to_generate: None, 5034 } 5035 } 5036 5037 pub fn code_actions(mut self) -> Vec<CodeAction> { 5038 self.visit_typed_module(&self.module.ast); 5039 5040 let Some(FunctionToGenerate { 5041 name, 5042 arguments_types, 5043 given_arguments, 5044 previous_function_end: Some(insert_at), 5045 return_type, 5046 }) = self.function_to_generate 5047 else { 5048 return vec![]; 5049 }; 5050 5051 // Labels do not share the same namespace as argument so we use two separate 5052 // generators to avoid renaming a label in case it shares a name with an argument. 5053 let mut label_names = NameGenerator::new(); 5054 let mut argument_names = NameGenerator::new(); 5055 let mut printer = Printer::new(&self.module.ast.names); 5056 let arguments = arguments_types 5057 .iter() 5058 .enumerate() 5059 .map(|(index, argument_type)| { 5060 let call_argument = given_arguments.and_then(|arguments| arguments.get(index)); 5061 let (label, name) = 5062 argument_names.generate_label_and_name(call_argument, argument_type); 5063 let pretty_type = printer.print_type(argument_type); 5064 if let Some(label) = label { 5065 let label = label_names.rename_to_avoid_shadowing(label.clone()); 5066 format!("{label} {name}: {pretty_type}") 5067 } else { 5068 format!("{name}: {pretty_type}") 5069 } 5070 }) 5071 .join(", "); 5072 5073 let return_type = printer.print_type(&return_type); 5074 5075 self.edits.insert( 5076 insert_at, 5077 format!("\n\nfn {name}({arguments}) -> {return_type} {{\n todo\n}}"), 5078 ); 5079 5080 let mut action = Vec::with_capacity(1); 5081 CodeActionBuilder::new("Generate function") 5082 .kind(CodeActionKind::REFACTOR_REWRITE) 5083 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5084 .preferred(false) 5085 .push_to(&mut action); 5086 action 5087 } 5088 5089 fn try_save_function_to_generate( 5090 &mut self, 5091 function_name_location: SrcSpan, 5092 function_type: &Arc<Type>, 5093 given_arguments: Option<&'a [TypedCallArg]>, 5094 ) { 5095 let name_range = function_name_location.start as usize..function_name_location.end as usize; 5096 let candidate_name = self.module.code.get(name_range); 5097 match (candidate_name, function_type.fn_types()) { 5098 (None, _) | (_, None) => (), 5099 (Some(name), _) if !is_valid_lowercase_name(name) => (), 5100 (Some(name), Some((arguments_types, return_type))) => { 5101 self.function_to_generate = Some(FunctionToGenerate { 5102 name, 5103 arguments_types, 5104 given_arguments, 5105 return_type, 5106 previous_function_end: self.last_visited_function_end, 5107 }) 5108 } 5109 } 5110 } 5111} 5112 5113impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> { 5114 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 5115 self.last_visited_function_end = Some(fun.end_position); 5116 ast::visit::visit_typed_function(self, fun); 5117 } 5118 5119 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 5120 let invalid_range = self.edits.src_span_to_lsp_range(*location); 5121 if within(self.params.range, invalid_range) { 5122 self.try_save_function_to_generate(*location, type_, None); 5123 } 5124 5125 ast::visit::visit_typed_expr_invalid(self, location, type_); 5126 } 5127 5128 fn visit_typed_expr_call( 5129 &mut self, 5130 location: &'ast SrcSpan, 5131 type_: &'ast Arc<Type>, 5132 fun: &'ast TypedExpr, 5133 args: &'ast [TypedCallArg], 5134 ) { 5135 // If the function being called is invalid we need to generate a 5136 // function that has the proper labels. 5137 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 5138 5139 if within(self.params.range, fun_range) && fun.is_invalid() { 5140 if labels_are_correct(args) { 5141 self.try_save_function_to_generate(fun.location(), &fun.type_(), Some(args)); 5142 } 5143 } else { 5144 ast::visit::visit_typed_expr_call(self, location, type_, fun, args); 5145 } 5146 } 5147} 5148 5149/// Builder for the "generate variant" code action. This will generate a variant 5150/// for a type if it can tell the type it should come from. It will work with 5151/// non-existing variants both used as expressions 5152/// 5153/// ```gleam 5154/// let a = IDoNotExist(1) 5155/// // ^^^^^^^^^^^ It would generate this variant here 5156/// ``` 5157/// 5158/// And as patterns: 5159/// 5160/// ```gleam 5161/// let assert IDoNotExist(1) = todo 5162/// ^^^^^^^^^^^ It would generate this variant here 5163/// ``` 5164/// 5165pub struct GenerateVariant<'a, IO> { 5166 module: &'a Module, 5167 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 5168 params: &'a CodeActionParams, 5169 line_numbers: &'a LineNumbers, 5170 variant_to_generate: Option<VariantToGenerate<'a>>, 5171} 5172 5173struct VariantToGenerate<'a> { 5174 name: &'a str, 5175 end_position: u32, 5176 arguments_types: Vec<Arc<Type>>, 5177 5178 /// Wether the type we're adding the variant to is written with braces or 5179 /// not. We need this information to add braces when missing. 5180 /// 5181 type_braces: TypeBraces, 5182 5183 /// The module this variant will be added to. 5184 /// 5185 module_name: EcoString, 5186 5187 /// The arguments actually supplied as input to the variant, if any. 5188 /// A variant to generate might as well be just a name passed as an argument 5189 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually 5190 /// have any actual arguments! 5191 /// 5192 given_arguments: Option<Arguments<'a>>, 5193} 5194 5195#[derive(Debug, Clone, Copy)] 5196enum TypeBraces { 5197 /// If the type is written like this: `pub type Wibble` 5198 HasBraces, 5199 /// If the type is written like this: `pub type Wibble {}` 5200 NoBraces, 5201} 5202 5203/// The arguments to an invalid call or pattern we can use to generate a variant. 5204/// 5205enum Arguments<'a> { 5206 /// These are the arguments provided to the invalid variant constructor 5207 /// when it's used as a function: `let a = Wibble(1, 2)`. 5208 /// 5209 Expressions(&'a [TypedCallArg]), 5210 /// These are the arguments provided to the invalid variant constructor when 5211 /// it's used in a pattern: `let assert Wibble(1, 2) = a` 5212 /// 5213 Patterns(&'a [CallArg<TypedPattern>]), 5214} 5215 5216/// An invalid variant might be used both as a pattern in a case expression or 5217/// as a regular value in an expression. We want to generate the variant in both 5218/// cases, so we use this enum to tell apart the two cases and be able to reuse 5219/// most of the code for both as they are very similar. 5220/// 5221enum Argument<'a> { 5222 Expression(&'a TypedCallArg), 5223 Pattern(&'a CallArg<TypedPattern>), 5224} 5225 5226impl<'a> Arguments<'a> { 5227 fn get(&self, index: usize) -> Option<Argument<'a>> { 5228 match self { 5229 Arguments::Patterns(call_args) => call_args.get(index).map(Argument::Pattern), 5230 Arguments::Expressions(call_args) => call_args.get(index).map(Argument::Expression), 5231 } 5232 } 5233 5234 fn types(&self) -> Vec<Arc<Type>> { 5235 match self { 5236 Arguments::Expressions(call_args) => call_args 5237 .iter() 5238 .map(|argument| argument.value.type_()) 5239 .collect_vec(), 5240 5241 Arguments::Patterns(call_args) => call_args 5242 .iter() 5243 .map(|argument| argument.value.type_()) 5244 .collect_vec(), 5245 } 5246 } 5247} 5248 5249impl Argument<'_> { 5250 fn label(&self) -> Option<EcoString> { 5251 match self { 5252 Argument::Expression(call_arg) => call_arg.label.clone(), 5253 Argument::Pattern(call_arg) => call_arg.label.clone(), 5254 } 5255 } 5256} 5257 5258impl<'a, IO> GenerateVariant<'a, IO> 5259where 5260 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone, 5261{ 5262 pub fn new( 5263 module: &'a Module, 5264 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 5265 line_numbers: &'a LineNumbers, 5266 params: &'a CodeActionParams, 5267 ) -> Self { 5268 Self { 5269 module, 5270 params, 5271 compiler, 5272 line_numbers, 5273 variant_to_generate: None, 5274 } 5275 } 5276 5277 pub fn code_actions(mut self) -> Vec<CodeAction> { 5278 self.visit_typed_module(&self.module.ast); 5279 5280 let Some(VariantToGenerate { 5281 name, 5282 arguments_types, 5283 given_arguments, 5284 module_name, 5285 end_position, 5286 type_braces, 5287 }) = &self.variant_to_generate 5288 else { 5289 return vec![]; 5290 }; 5291 5292 let Some((variant_module, variant_edits)) = self.edits_to_create_variant( 5293 name, 5294 arguments_types, 5295 given_arguments, 5296 module_name, 5297 *end_position, 5298 *type_braces, 5299 ) else { 5300 return vec![]; 5301 }; 5302 5303 let mut action = Vec::with_capacity(1); 5304 CodeActionBuilder::new("Generate variant") 5305 .kind(CodeActionKind::REFACTOR_REWRITE) 5306 .changes(variant_module, variant_edits) 5307 .preferred(false) 5308 .push_to(&mut action); 5309 action 5310 } 5311 5312 /// Returns the edits needed to add this new variant to the given module. 5313 /// It also returns the uri of the module the edits should be applied to. 5314 /// 5315 fn edits_to_create_variant( 5316 &self, 5317 variant_name: &str, 5318 arguments_types: &[Arc<Type>], 5319 given_arguments: &Option<Arguments<'_>>, 5320 module_name: &EcoString, 5321 end_position: u32, 5322 type_braces: TypeBraces, 5323 ) -> Option<(Url, Vec<TextEdit>)> { 5324 let mut label_names = NameGenerator::new(); 5325 let mut printer = Printer::new(&self.module.ast.names); 5326 let arguments = arguments_types 5327 .iter() 5328 .enumerate() 5329 .map(|(index, argument_type)| { 5330 let label = given_arguments 5331 .as_ref() 5332 .and_then(|arguments| arguments.get(index)?.label()) 5333 .map(|label| label_names.rename_to_avoid_shadowing(label)); 5334 5335 let pretty_type = printer.print_type(argument_type); 5336 if let Some(arg_label) = label { 5337 format!("{arg_label}: {pretty_type}") 5338 } else { 5339 format!("{pretty_type}") 5340 } 5341 }) 5342 .join(", "); 5343 5344 let variant = if arguments.is_empty() { 5345 variant_name.to_string() 5346 } else { 5347 format!("{variant_name}({arguments})") 5348 }; 5349 5350 let (new_text, insert_at) = match type_braces { 5351 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1), 5352 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position), 5353 }; 5354 5355 if *module_name == self.module.name { 5356 // If we're editing the current module we can use the line numbers that 5357 // were already computed before-hand without wasting any time to add the 5358 // new edit. 5359 let mut edits = TextEdits::new(self.line_numbers); 5360 edits.insert(insert_at, new_text); 5361 Some((self.params.text_document.uri.clone(), edits.edits)) 5362 } else { 5363 // Otherwise we're changing a different module and we need to get its 5364 // code and line numbers to properly apply the new edit. 5365 let module = self 5366 .compiler 5367 .modules 5368 .get(module_name) 5369 .expect("module to exist"); 5370 let line_numbers = LineNumbers::new(&module.code); 5371 let mut edits = TextEdits::new(&line_numbers); 5372 edits.insert(insert_at, new_text); 5373 Some((url_from_path(module.input_path.as_str())?, edits.edits)) 5374 } 5375 } 5376 5377 fn try_save_variant_to_generate( 5378 &mut self, 5379 function_name_location: SrcSpan, 5380 function_type: &Arc<Type>, 5381 given_arguments: Option<Arguments<'a>>, 5382 ) { 5383 let variant_to_generate = 5384 self.variant_to_generate(function_name_location, function_type, given_arguments); 5385 if variant_to_generate.is_some() { 5386 self.variant_to_generate = variant_to_generate; 5387 } 5388 } 5389 5390 fn variant_to_generate( 5391 &mut self, 5392 function_name_location: SrcSpan, 5393 type_: &Arc<Type>, 5394 given_arguments: Option<Arguments<'a>>, 5395 ) -> Option<VariantToGenerate<'a>> { 5396 let name_range = function_name_location.start as usize..function_name_location.end as usize; 5397 let name = self.module.code.get(name_range).expect("valid code range"); 5398 if !is_valid_uppercase_name(name) { 5399 return None; 5400 } 5401 5402 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) { 5403 (Some(result), _) => result, 5404 (None, Some(arguments)) => (arguments.types(), type_.clone()), 5405 (None, None) => (vec![], type_.clone()), 5406 }; 5407 5408 let (module_name, type_name, _) = custom_type.named_type_information()?; 5409 let module = self.compiler.modules.get(&module_name)?; 5410 let (end_position, type_braces) = 5411 (module.ast.definitions.iter()).find_map(|definition| match definition { 5412 ast::Definition::CustomType(custom_type) if custom_type.name == type_name => { 5413 // If there's already a variant with this name then we definitely 5414 // don't want to generate a new variant with the same name! 5415 let variant_with_this_name_already_exists = custom_type 5416 .constructors 5417 .iter() 5418 .map(|constructor| &constructor.name) 5419 .any(|existing_constructor_name| existing_constructor_name == name); 5420 if variant_with_this_name_already_exists { 5421 return None; 5422 } 5423 let type_braces = if custom_type.end_position == custom_type.location.end { 5424 TypeBraces::NoBraces 5425 } else { 5426 TypeBraces::HasBraces 5427 }; 5428 Some((custom_type.end_position, type_braces)) 5429 } 5430 _ => None, 5431 })?; 5432 5433 Some(VariantToGenerate { 5434 name, 5435 arguments_types, 5436 given_arguments, 5437 module_name, 5438 end_position, 5439 type_braces, 5440 }) 5441 } 5442} 5443 5444impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> 5445where 5446 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone, 5447{ 5448 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 5449 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 5450 if within(self.params.range, invalid_range) { 5451 self.try_save_variant_to_generate(*location, type_, None); 5452 } 5453 ast::visit::visit_typed_expr_invalid(self, location, type_); 5454 } 5455 5456 fn visit_typed_expr_call( 5457 &mut self, 5458 location: &'ast SrcSpan, 5459 type_: &'ast Arc<Type>, 5460 fun: &'ast TypedExpr, 5461 args: &'ast [TypedCallArg], 5462 ) { 5463 // If the function being called is invalid we need to generate a 5464 // function that has the proper labels. 5465 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers); 5466 if within(self.params.range, fun_range) && fun.is_invalid() { 5467 if labels_are_correct(args) { 5468 self.try_save_variant_to_generate( 5469 fun.location(), 5470 &fun.type_(), 5471 Some(Arguments::Expressions(args)), 5472 ); 5473 } 5474 } else { 5475 ast::visit::visit_typed_expr_call(self, location, type_, fun, args); 5476 } 5477 } 5478 5479 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 5480 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 5481 if within(self.params.range, invalid_range) { 5482 self.try_save_variant_to_generate(*location, type_, None); 5483 } 5484 ast::visit::visit_typed_pattern_invalid(self, location, type_); 5485 } 5486 5487 fn visit_typed_pattern_constructor( 5488 &mut self, 5489 location: &'ast SrcSpan, 5490 name_location: &'ast SrcSpan, 5491 name: &'ast EcoString, 5492 arguments: &'ast Vec<CallArg<TypedPattern>>, 5493 module: &'ast Option<(EcoString, SrcSpan)>, 5494 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 5495 spread: &'ast Option<SrcSpan>, 5496 type_: &'ast Arc<Type>, 5497 ) { 5498 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers); 5499 // TODO)) Solo se il pattern non è valido!!!!! 5500 if within(self.params.range, pattern_range) { 5501 if labels_are_correct(arguments) { 5502 self.try_save_variant_to_generate( 5503 *name_location, 5504 type_, 5505 Some(Arguments::Patterns(arguments)), 5506 ); 5507 } 5508 } else { 5509 ast::visit::visit_typed_pattern_constructor( 5510 self, 5511 location, 5512 name_location, 5513 name, 5514 arguments, 5515 module, 5516 constructor, 5517 spread, 5518 type_, 5519 ); 5520 } 5521 } 5522} 5523 5524#[must_use] 5525/// Checks the labels in the given arguments are correct: that is there's no 5526/// duplicate labels and all labelled arguments come after the unlabelled ones. 5527fn labels_are_correct<A>(args: &[CallArg<A>]) -> bool { 5528 let mut labelled_arg_found = false; 5529 let mut used_labels = HashSet::new(); 5530 5531 for arg in args { 5532 match &arg.label { 5533 // Labels are invalid if there's duplicate ones or if an unlabelled 5534 // argument comes after a labelled one. 5535 Some(label) if used_labels.contains(label) => return false, 5536 None if labelled_arg_found => return false, 5537 // Otherwise we just add the label to the used ones. 5538 Some(label) => { 5539 labelled_arg_found = true; 5540 let _ = used_labels.insert(label); 5541 } 5542 None => {} 5543 } 5544 } 5545 5546 true 5547} 5548 5549struct NameGenerator { 5550 used_names: HashSet<EcoString>, 5551} 5552 5553impl NameGenerator { 5554 pub fn new() -> Self { 5555 NameGenerator { 5556 used_names: HashSet::new(), 5557 } 5558 } 5559 5560 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString { 5561 let mut i = 1; 5562 let mut candidate_name = base.clone(); 5563 5564 loop { 5565 if self.used_names.contains(&candidate_name) { 5566 i += 1; 5567 candidate_name = eco_format!("{base}_{i}"); 5568 } else { 5569 let _ = self.used_names.insert(candidate_name.clone()); 5570 return candidate_name; 5571 } 5572 } 5573 } 5574 5575 /// Given an argument type and the actual call argument (if any), comes up 5576 /// with a label and a name to use for that argument when generating a 5577 /// function. 5578 /// 5579 pub fn generate_label_and_name( 5580 &mut self, 5581 call_argument: Option<&CallArg<TypedExpr>>, 5582 argument_type: &Arc<Type>, 5583 ) -> (Option<EcoString>, EcoString) { 5584 let label = call_argument.and_then(|argument| argument.label.clone()); 5585 let argument_name = call_argument 5586 // We always favour a name derived from the expression (for example if 5587 // the argument is a variable) 5588 .and_then(|argument| self.generate_name_from_expression(&argument.value)) 5589 // If we don't have such a name and there's a label we use that name. 5590 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?))) 5591 // If all else fails we fallback to using a name derived from the 5592 // argument's type. 5593 .unwrap_or_else(|| self.generate_name_from_type(argument_type)); 5594 5595 (label, argument_name) 5596 } 5597 5598 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString { 5599 let type_to_base_name = |type_: &Arc<Type>| { 5600 type_ 5601 .named_type_name() 5602 .map(|(_type_module, type_name)| to_snake_case(&type_name)) 5603 .filter(|name| is_valid_lowercase_name(name)) 5604 .unwrap_or(EcoString::from("value")) 5605 }; 5606 5607 let base_name = match type_.list_type() { 5608 None => type_to_base_name(type_), 5609 // If we're coming up with a name for a list we want to use the 5610 // plural form for the name of the inner type. For example: 5611 // `List(Pokemon)` should generate `pokemons`. 5612 Some(inner_type) => { 5613 let base_name = type_to_base_name(&inner_type); 5614 // If the inner type name already ends in "s" we leave it as it 5615 // is, or it would look funny. 5616 if base_name.ends_with('s') { 5617 base_name 5618 } else { 5619 eco_format!("{base_name}s") 5620 } 5621 } 5622 }; 5623 5624 self.rename_to_avoid_shadowing(base_name) 5625 } 5626 5627 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> { 5628 match expression { 5629 // If the argument is a record, we can't use it as an argument name. 5630 // Similarly, we don't want to base the variable name off a 5631 // compiler-generated variable like `_pipe`. 5632 TypedExpr::Var { 5633 name, constructor, .. 5634 } if !constructor.variant.is_record() 5635 && !constructor.variant.is_generated_variable() => 5636 { 5637 Some(self.rename_to_avoid_shadowing(name.clone())) 5638 } 5639 _ => None, 5640 } 5641 } 5642 5643 pub fn add_used_name(&mut self, name: EcoString) { 5644 let _ = self.used_names.insert(name); 5645 } 5646 5647 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) { 5648 field_map 5649 .fields 5650 .iter() 5651 .for_each(|(label, _)| self.add_used_name(label.clone())); 5652 } 5653 5654 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) { 5655 variable_names 5656 .names 5657 .iter() 5658 .for_each(|name| self.add_used_name(name.clone())); 5659 } 5660} 5661 5662#[must_use] 5663fn is_valid_lowercase_name(name: &str) -> bool { 5664 if !name.starts_with(|char: char| char.is_ascii_lowercase()) { 5665 return false; 5666 } 5667 5668 for char in name.chars() { 5669 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_'; 5670 if !is_valid_char { 5671 return false; 5672 } 5673 } 5674 5675 str_to_keyword(name).is_none() 5676} 5677 5678#[must_use] 5679fn is_valid_uppercase_name(name: &str) -> bool { 5680 if !name.starts_with(|char: char| char.is_ascii_uppercase()) { 5681 return false; 5682 } 5683 5684 for char in name.chars() { 5685 if !char.is_ascii_alphanumeric() { 5686 return false; 5687 } 5688 } 5689 5690 true 5691} 5692 5693/// Code action to rewrite a single-step pipeline into a regular function call. 5694/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`. 5695/// 5696pub struct ConvertToFunctionCall<'a> { 5697 module: &'a Module, 5698 params: &'a CodeActionParams, 5699 edits: TextEdits<'a>, 5700 locations: Option<ConvertToFunctionCallLocations>, 5701} 5702 5703/// All the different locations the "Convert to function call" code action needs 5704/// to properly rewrite a pipeline into a function call. 5705/// 5706struct ConvertToFunctionCallLocations { 5707 /// This is the location of the value being piped into a call. 5708 /// 5709 /// ```gleam 5710 /// [1, 2, 3] |> list.length 5711 /// // ^^^^^^^^^ This one here 5712 /// ``` 5713 /// 5714 first_value: SrcSpan, 5715 5716 /// This is the location of the call the value is being piped into. 5717 /// 5718 /// ```gleam 5719 /// [1, 2, 3] |> list.length 5720 /// // ^^^^^^^^^^^ This one here 5721 /// ``` 5722 /// 5723 call: SrcSpan, 5724 5725 /// This is the kind of desugaring that is taking place when piping 5726 /// `first_value` into `call`. 5727 /// 5728 call_kind: PipelineAssignmentKind, 5729} 5730 5731impl<'a> ConvertToFunctionCall<'a> { 5732 pub fn new( 5733 module: &'a Module, 5734 line_numbers: &'a LineNumbers, 5735 params: &'a CodeActionParams, 5736 ) -> Self { 5737 Self { 5738 module, 5739 params, 5740 edits: TextEdits::new(line_numbers), 5741 locations: None, 5742 } 5743 } 5744 5745 pub fn code_actions(mut self) -> Vec<CodeAction> { 5746 self.visit_typed_module(&self.module.ast); 5747 5748 // If we couldn't find a pipeline to rewrite we don't return any action. 5749 let Some(ConvertToFunctionCallLocations { 5750 first_value, 5751 call, 5752 call_kind, 5753 }) = self.locations 5754 else { 5755 return vec![]; 5756 }; 5757 5758 // We first delete the first value of the pipeline as it's going to be 5759 // inlined as a function call argument. 5760 self.edits.delete(SrcSpan { 5761 start: first_value.start, 5762 end: call.start, 5763 }); 5764 5765 // Then we have to insert the piped value in the appropriate position. 5766 // This will change based on how the pipeline is being desugared, we 5767 // know this thanks to the `call_kind` 5768 let first_value_text = self 5769 .module 5770 .code 5771 .get(first_value.start as usize..first_value.end as usize) 5772 .expect("invalid code span") 5773 .to_string(); 5774 5775 match call_kind { 5776 // When piping into a `_` we replace the hole with the piped value: 5777 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`. 5778 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text), 5779 5780 // When piping is desguared as a function call we need to add the 5781 // missing parentheses: 5782 // `[1, 2] |> length` becomes `length([1, 2])` 5783 PipelineAssignmentKind::FunctionCall => { 5784 self.edits.insert(call.end, format!("({first_value_text})")) 5785 } 5786 5787 // When the piped value is inserted as the first argument there's two 5788 // possible scenarios: 5789 // - there's a second argument as well: in that case we insert it 5790 // before the second arg and add a comma 5791 // - there's no other argument: `[1, 2] |> length()` becomes 5792 // `length([1, 2])`, we insert the value between the empty 5793 // parentheses 5794 PipelineAssignmentKind::FirstArgument { 5795 second_argument: Some(SrcSpan { start, .. }), 5796 } => self.edits.insert(start, format!("{first_value_text}, ")), 5797 PipelineAssignmentKind::FirstArgument { 5798 second_argument: None, 5799 } => self.edits.insert(call.end - 1, first_value_text), 5800 5801 // When the value is piped into an echo, to rewrite the pipeline we 5802 // have to insert the value after the `echo` with no parentheses: 5803 // `a |> echo` is rewritten as `echo a`. 5804 PipelineAssignmentKind::Echo => { 5805 self.edits.insert(call.end, format!(" {first_value_text}")) 5806 } 5807 } 5808 5809 let mut action = Vec::with_capacity(1); 5810 CodeActionBuilder::new("Convert to function call") 5811 .kind(CodeActionKind::REFACTOR_REWRITE) 5812 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5813 .preferred(false) 5814 .push_to(&mut action); 5815 action 5816 } 5817} 5818 5819impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> { 5820 fn visit_typed_expr_pipeline( 5821 &mut self, 5822 location: &'ast SrcSpan, 5823 first_value: &'ast TypedPipelineAssignment, 5824 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 5825 finally: &'ast TypedExpr, 5826 finally_kind: &'ast PipelineAssignmentKind, 5827 ) { 5828 let pipeline_range = self.edits.src_span_to_lsp_range(*location); 5829 if within(self.params.range, pipeline_range) { 5830 // We will always desugar the pipeline's first step. If there's no 5831 // intermediate assignment it means we're dealing with a single step 5832 // pipeline and the call is `finally`. 5833 let (call, call_kind) = assignments 5834 .first() 5835 .map(|(call, kind)| (call.location, *kind)) 5836 .unwrap_or_else(|| (finally.location(), *finally_kind)); 5837 5838 self.locations = Some(ConvertToFunctionCallLocations { 5839 first_value: first_value.location, 5840 call, 5841 call_kind, 5842 }); 5843 5844 ast::visit::visit_typed_expr_pipeline( 5845 self, 5846 location, 5847 first_value, 5848 assignments, 5849 finally, 5850 finally_kind, 5851 ); 5852 } 5853 } 5854} 5855 5856/// Builder for code action to inline a variable. 5857/// 5858pub struct InlineVariable<'a> { 5859 module: &'a Module, 5860 params: &'a CodeActionParams, 5861 edits: TextEdits<'a>, 5862 actions: Vec<CodeAction>, 5863} 5864 5865impl<'a> InlineVariable<'a> { 5866 pub fn new( 5867 module: &'a Module, 5868 line_numbers: &'a LineNumbers, 5869 params: &'a CodeActionParams, 5870 ) -> Self { 5871 Self { 5872 module, 5873 params, 5874 edits: TextEdits::new(line_numbers), 5875 actions: Vec::new(), 5876 } 5877 } 5878 5879 pub fn code_actions(mut self) -> Vec<CodeAction> { 5880 self.visit_typed_module(&self.module.ast); 5881 5882 self.actions 5883 } 5884 5885 fn maybe_inline(&mut self, location: SrcSpan) { 5886 let reference = match find_variable_references(&self.module.ast, location).as_slice() { 5887 [only_reference] => *only_reference, 5888 _ => return, 5889 }; 5890 5891 let Some(ast::Statement::Assignment(assignment)) = 5892 self.module.ast.find_statement(location.start) 5893 else { 5894 return; 5895 }; 5896 5897 // If the assignment does not simple bind a variable, for example: 5898 // ```gleam 5899 // let #(first, second, third) 5900 // io.println(first) 5901 // // ^ Inline here 5902 // ``` 5903 // We can't inline it. 5904 if !matches!(assignment.pattern, Pattern::Variable { .. }) { 5905 return; 5906 } 5907 5908 // If the assignment was generated by the compiler, it doesn't have a 5909 // syntactical representation, so we can't inline it. 5910 if matches!(assignment.kind, AssignmentKind::Generated) { 5911 return; 5912 } 5913 5914 let value_location = assignment.value.location(); 5915 let value = self 5916 .module 5917 .code 5918 .get(value_location.start as usize..value_location.end as usize) 5919 .expect("Span is valid"); 5920 5921 match reference.kind { 5922 VariableReferenceKind::Variable => { 5923 self.edits.replace(reference.location, value.into()); 5924 } 5925 VariableReferenceKind::LabelShorthand => { 5926 self.edits 5927 .insert(reference.location.end, format!(" {value}")); 5928 } 5929 } 5930 5931 let mut location = assignment.location; 5932 5933 let mut chars = self.module.code[location.end as usize..].chars(); 5934 // Delete any whitespace after the removed statement 5935 while chars.next().is_some_and(char::is_whitespace) { 5936 location.end += 1; 5937 } 5938 5939 self.edits.delete(location); 5940 5941 CodeActionBuilder::new("Inline variable") 5942 .kind(CodeActionKind::REFACTOR_INLINE) 5943 .changes( 5944 self.params.text_document.uri.clone(), 5945 std::mem::take(&mut self.edits.edits), 5946 ) 5947 .preferred(false) 5948 .push_to(&mut self.actions); 5949 } 5950} 5951 5952impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> { 5953 fn visit_typed_expr_var( 5954 &mut self, 5955 location: &'ast SrcSpan, 5956 constructor: &'ast ValueConstructor, 5957 _name: &'ast EcoString, 5958 ) { 5959 let range = self.edits.src_span_to_lsp_range(*location); 5960 5961 if !overlaps(self.params.range, range) { 5962 return; 5963 } 5964 5965 let type_::ValueConstructorVariant::LocalVariable { location, origin } = 5966 &constructor.variant 5967 else { 5968 return; 5969 }; 5970 5971 // We can only inline variables assigned by `let` statements, as it 5972 //doesn't make sense to do so with any other kind of variable. 5973 match origin.declaration { 5974 VariableDeclaration::LetPattern => {} 5975 VariableDeclaration::UsePattern 5976 | VariableDeclaration::ClausePattern 5977 | VariableDeclaration::FunctionParameter 5978 | VariableDeclaration::Generated => return, 5979 } 5980 5981 self.maybe_inline(*location); 5982 } 5983 5984 fn visit_typed_pattern_variable( 5985 &mut self, 5986 location: &'ast SrcSpan, 5987 _name: &'ast EcoString, 5988 _type: &'ast Arc<Type>, 5989 origin: &'ast VariableOrigin, 5990 ) { 5991 // We can only inline variables assigned by `let` statements, as it 5992 //doesn't make sense to do so with any other kind of variable. 5993 match origin.declaration { 5994 VariableDeclaration::LetPattern => {} 5995 VariableDeclaration::UsePattern 5996 | VariableDeclaration::ClausePattern 5997 | VariableDeclaration::FunctionParameter 5998 | VariableDeclaration::Generated => return, 5999 } 6000 6001 let range = self.edits.src_span_to_lsp_range(*location); 6002 6003 if !overlaps(self.params.range, range) { 6004 return; 6005 } 6006 6007 self.maybe_inline(*location); 6008 } 6009} 6010 6011/// Builder for the "convert to pipe" code action. 6012/// 6013/// ```gleam 6014/// pub fn main() { 6015/// wibble(wobble, woo) 6016/// // ^ [convert to pipe] 6017/// } 6018/// ``` 6019/// 6020/// Will turn the code into the following pipeline: 6021/// 6022/// ```gleam 6023/// pub fn main() { 6024/// wobble |> wibble(woo) 6025/// } 6026/// ``` 6027/// 6028pub struct ConvertToPipe<'a> { 6029 module: &'a Module, 6030 params: &'a CodeActionParams, 6031 edits: TextEdits<'a>, 6032 argument_to_pipe: Option<ConvertToPipeArg<'a>>, 6033 /// this will be true if we're visiting the call on the right hand side of a 6034 /// use expression. So we can skip it and not try to turn it into a 6035 /// function. 6036 visiting_use_call: bool, 6037} 6038 6039/// Holds all the data needed by the "convert to pipe" code action to properly 6040/// rewrite a call into a pipe. Here's what each span means: 6041/// 6042/// ```gleam 6043/// wibble(wobb|le, woo) 6044/// // ^^^^^^^^^^^^^^^^^^^^ call 6045/// // ^^^^^^ called 6046/// // ^^^^^^^ arg 6047/// // ^^^ next arg 6048/// ``` 6049/// 6050/// In this example `position` is 0, since the cursor is over the first 6051/// argument. 6052/// 6053pub struct ConvertToPipeArg<'a> { 6054 /// The span of the called function. 6055 called: SrcSpan, 6056 /// The span of the entire function call. 6057 call: SrcSpan, 6058 /// The position (0-based) of the argument. 6059 position: usize, 6060 /// The argument we have to pipe. 6061 arg: &'a TypedCallArg, 6062 /// The span of the argument following the one we have to pipe, if there's 6063 /// any. 6064 next_arg: Option<SrcSpan>, 6065} 6066 6067impl<'a> ConvertToPipe<'a> { 6068 pub fn new( 6069 module: &'a Module, 6070 line_numbers: &'a LineNumbers, 6071 params: &'a CodeActionParams, 6072 ) -> Self { 6073 Self { 6074 module, 6075 params, 6076 edits: TextEdits::new(line_numbers), 6077 visiting_use_call: false, 6078 argument_to_pipe: None, 6079 } 6080 } 6081 6082 pub fn code_actions(mut self) -> Vec<CodeAction> { 6083 self.visit_typed_module(&self.module.ast); 6084 6085 let Some(ConvertToPipeArg { 6086 called, 6087 call, 6088 position, 6089 arg, 6090 next_arg, 6091 }) = self.argument_to_pipe 6092 else { 6093 return vec![]; 6094 }; 6095 6096 let arg_range = if arg.uses_label_shorthand() { 6097 arg.location.start as usize..arg.location.end as usize - 1 6098 } else if arg.label.is_some() { 6099 let value = arg.value.location(); 6100 value.start as usize..value.end as usize 6101 } else { 6102 arg.location.start as usize..arg.location.end as usize 6103 }; 6104 6105 let arg_text = self.module.code.get(arg_range).expect("invalid srcspan"); 6106 let arg_text = match arg.value { 6107 // If the expression being piped is a binary operation with 6108 // precedence lower than pipes then we have to wrap it in curly 6109 // braces to not mess with the order of operations. 6110 TypedExpr::BinOp { name, .. } if name.precedence() < PIPE_PRECEDENCE => { 6111 &format!("{{ {arg_text} }}") 6112 } 6113 _ => arg_text, 6114 }; 6115 6116 match next_arg { 6117 // When extracting an argument we never want to remove any explicit 6118 // label that was written down, so in case it is labelled (be it a 6119 // shorthand or not) we'll always replace the value with a `_` 6120 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()), 6121 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()), 6122 6123 // Now we can deal with unlabelled arguments: 6124 // If we're removing the first argument and there's other arguments 6125 // after it, we need to delete the comma that was separating the 6126 // two. 6127 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan { 6128 start: arg.location.start, 6129 end: next_arg.start, 6130 }), 6131 // Otherwise, if we're deleting the first argument and there's 6132 // no other arguments following it, we remove the call's 6133 // parentheses. 6134 None if position == 0 => self.edits.delete(SrcSpan { 6135 start: called.end, 6136 end: call.end, 6137 }), 6138 // In all other cases we're piping something that is not the first 6139 // argument so we just replace it with an `_`. 6140 _ => self.edits.replace(arg.location, "_".into()), 6141 }; 6142 6143 // Finally we can add the argument that was removed as the first step 6144 // of the newly defined pipeline. 6145 self.edits.insert(call.start, format!("{arg_text} |> ")); 6146 6147 let mut action = Vec::with_capacity(1); 6148 CodeActionBuilder::new("Convert to pipe") 6149 .kind(CodeActionKind::REFACTOR_REWRITE) 6150 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6151 .preferred(false) 6152 .push_to(&mut action); 6153 action 6154 } 6155} 6156 6157impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> { 6158 fn visit_typed_expr_call( 6159 &mut self, 6160 location: &'ast SrcSpan, 6161 _type_: &'ast Arc<Type>, 6162 fun: &'ast TypedExpr, 6163 args: &'ast [TypedCallArg], 6164 ) { 6165 if args.iter().any(|arg| arg.is_capture_hole()) { 6166 return; 6167 } 6168 6169 // If we're visiting the typed function produced by typing a use, we 6170 // skip the thing itself and only visit its arguments and called 6171 // function, that is the body of the use. 6172 if self.visiting_use_call { 6173 self.visiting_use_call = false; 6174 ast::visit::visit_typed_expr(self, fun); 6175 args.iter() 6176 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg)); 6177 return; 6178 } 6179 6180 // We only visit a call if the cursor is somewhere within its location, 6181 // otherwise we skip it entirely. 6182 let call_range = self.edits.src_span_to_lsp_range(*location); 6183 if !within(self.params.range, call_range) { 6184 return; 6185 } 6186 6187 // If the cursor is over any of the arguments then we'll use that as 6188 // the one to extract. 6189 // Otherwise the cursor must be over the called function, in that case 6190 // we extract the first argument (if there's one): 6191 // 6192 // ```gleam 6193 // wibble(wobble, woo) 6194 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here 6195 // // ^^^ pipe the second argument if I'm here 6196 // ``` 6197 let argument_to_pipe = args 6198 .iter() 6199 .enumerate() 6200 .find_map(|(position, arg)| { 6201 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 6202 if within(self.params.range, arg_range) { 6203 Some((position, arg)) 6204 } else { 6205 None 6206 } 6207 }) 6208 .or_else(|| args.first().map(|arg| (0, arg))); 6209 6210 // If we're not hovering over any of the arguments _or_ there's no 6211 // argument to extract at all we just return, there's nothing we can do 6212 // on this call or any of its arguments (since we've determined the 6213 // cursor is not over any of those). 6214 let Some((position, arg)) = argument_to_pipe else { 6215 return; 6216 }; 6217 6218 self.argument_to_pipe = Some(ConvertToPipeArg { 6219 called: fun.location(), 6220 call: *location, 6221 position, 6222 arg, 6223 next_arg: args.get(position + 1).map(|arg| arg.location), 6224 }) 6225 } 6226 6227 fn visit_typed_expr_pipeline( 6228 &mut self, 6229 _location: &'ast SrcSpan, 6230 first_value: &'ast TypedPipelineAssignment, 6231 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 6232 _finally: &'ast TypedExpr, 6233 _finally_kind: &'ast PipelineAssignmentKind, 6234 ) { 6235 // We can only apply the action on the first step of a pipeline, so we 6236 // visit just that one and skip all the others. 6237 ast::visit::visit_typed_pipeline_assignment(self, first_value); 6238 } 6239 6240 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 6241 self.visiting_use_call = true; 6242 ast::visit::visit_typed_use(self, use_); 6243 } 6244} 6245 6246/// Code action to interpolate a string. If the cursor is inside the string 6247/// (not selecting anything) the language server will offer to split it: 6248/// 6249/// ```gleam 6250/// "wibble | wobble" 6251/// // ^ [Split string] 6252/// // Will produce the following 6253/// "wibble " <> todo <> " wobble" 6254/// ``` 6255/// 6256/// If the cursor is selecting an entire valid gleam name, then the language 6257/// server will offer to interpolate it as a variable: 6258/// 6259/// ```gleam 6260/// "wibble wobble woo" 6261/// // ^^^^^^ [Interpolate variable] 6262/// // Will produce the following 6263/// "wibble " <> wobble <> " woo" 6264/// ``` 6265/// 6266/// > Note: the cursor won't end up right after the inserted variable/todo. 6267/// > that's a bit annoying, but in a future LSP version we will be able to 6268/// > isnert tab stops to allow one to jump to the newly added variable/todo. 6269/// 6270pub struct InterpolateString<'a> { 6271 module: &'a Module, 6272 params: &'a CodeActionParams, 6273 edits: TextEdits<'a>, 6274 string_interpolation: Option<(SrcSpan, StringInterpolation)>, 6275 string_literal_position: StringLiteralPosition, 6276} 6277 6278#[derive(Debug, Clone, Copy, Eq, PartialEq)] 6279pub enum StringLiteralPosition { 6280 FirstPipelineStep, 6281 Other, 6282} 6283 6284#[derive(Clone, Copy)] 6285enum StringInterpolation { 6286 InterpolateValue { value_location: SrcSpan }, 6287 SplitString { split_at: u32 }, 6288} 6289 6290impl<'a> InterpolateString<'a> { 6291 pub fn new( 6292 module: &'a Module, 6293 line_numbers: &'a LineNumbers, 6294 params: &'a CodeActionParams, 6295 ) -> Self { 6296 Self { 6297 module, 6298 params, 6299 edits: TextEdits::new(line_numbers), 6300 string_interpolation: None, 6301 string_literal_position: StringLiteralPosition::Other, 6302 } 6303 } 6304 6305 pub fn code_actions(mut self) -> Vec<CodeAction> { 6306 self.visit_typed_module(&self.module.ast); 6307 6308 let Some((string_location, interpolation)) = self.string_interpolation else { 6309 return vec![]; 6310 }; 6311 6312 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 6313 self.edits.insert(string_location.start, "{ ".into()); 6314 } 6315 6316 match interpolation { 6317 StringInterpolation::InterpolateValue { value_location } => { 6318 let name = self 6319 .module 6320 .code 6321 .get(value_location.start as usize..value_location.end as usize) 6322 .expect("invalid value range"); 6323 6324 if is_valid_lowercase_name(name) { 6325 self.edits 6326 .insert(value_location.start, format!("\" <> {name} <> \"")); 6327 self.edits.delete(value_location); 6328 } else if self.can_split_string_at(value_location.end) { 6329 // If the string is not a valid name we just try and split 6330 // the string at the end of the selection. 6331 self.edits 6332 .insert(value_location.end, "\" <> todo <> \"".into()); 6333 } else { 6334 // Otherwise there's no meaningful action we can do. 6335 return vec![]; 6336 } 6337 } 6338 6339 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => { 6340 self.edits.insert(split_at, "\" <> todo <> \"".into()); 6341 } 6342 6343 StringInterpolation::SplitString { .. } => return vec![], 6344 }; 6345 6346 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 6347 self.edits.insert(string_location.end, " }".into()); 6348 } 6349 6350 let mut action = Vec::with_capacity(1); 6351 CodeActionBuilder::new("Interpolate string") 6352 .kind(CodeActionKind::REFACTOR_REWRITE) 6353 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6354 .preferred(false) 6355 .push_to(&mut action); 6356 action 6357 } 6358 6359 fn can_split_string_at(&self, at: u32) -> bool { 6360 self.string_interpolation 6361 .is_some_and(|(string_location, _)| { 6362 !(at <= string_location.start + 1 || at >= string_location.end - 1) 6363 }) 6364 } 6365 6366 fn visit_literal_string( 6367 &mut self, 6368 string_location: SrcSpan, 6369 string_position: StringLiteralPosition, 6370 ) { 6371 // We can only interpolate/split a string if the cursor is somewhere 6372 // within its location, otherwise we skip it. 6373 let string_range = self.edits.src_span_to_lsp_range(string_location); 6374 if !within(self.params.range, string_range) { 6375 return; 6376 } 6377 6378 let selection @ SrcSpan { start, end } = 6379 self.edits.lsp_range_to_src_span(self.params.range); 6380 6381 let interpolation = if start == end { 6382 StringInterpolation::SplitString { split_at: start } 6383 } else { 6384 StringInterpolation::InterpolateValue { 6385 value_location: selection, 6386 } 6387 }; 6388 self.string_interpolation = Some((string_location, interpolation)); 6389 self.string_literal_position = string_position; 6390 } 6391} 6392 6393impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> { 6394 fn visit_typed_expr_string( 6395 &mut self, 6396 location: &'ast SrcSpan, 6397 _type_: &'ast Arc<Type>, 6398 _value: &'ast EcoString, 6399 ) { 6400 self.visit_literal_string(*location, StringLiteralPosition::Other); 6401 } 6402 6403 fn visit_typed_expr_pipeline( 6404 &mut self, 6405 _location: &'ast SrcSpan, 6406 first_value: &'ast TypedPipelineAssignment, 6407 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 6408 finally: &'ast TypedExpr, 6409 _finally_kind: &'ast PipelineAssignmentKind, 6410 ) { 6411 if first_value.value.is_literal_string() { 6412 self.visit_literal_string( 6413 first_value.location, 6414 StringLiteralPosition::FirstPipelineStep, 6415 ); 6416 } else { 6417 ast::visit::visit_typed_pipeline_assignment(self, first_value); 6418 } 6419 6420 assignments 6421 .iter() 6422 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a)); 6423 self.visit_typed_expr(finally); 6424 } 6425} 6426 6427/// Code action to replace a `..` in a pattern with all the missing fields that 6428/// have not been explicitly provided; labelled ones are introduced with the 6429/// shorthand syntax. 6430/// 6431/// ```gleam 6432/// pub type Pokemon { 6433/// Pokemon(Int, name: String, moves: List(String)) 6434/// } 6435/// 6436/// pub fn main() { 6437/// let Pokemon(..) = todo 6438/// // ^^ Cursor over the spread 6439/// } 6440/// ``` 6441/// Would become 6442/// ```gleam 6443/// pub fn main() { 6444/// let Pokemon(int, name:, moves:) = todo 6445/// } 6446/// 6447pub struct FillUnusedFields<'a> { 6448 module: &'a Module, 6449 params: &'a CodeActionParams, 6450 edits: TextEdits<'a>, 6451 data: Option<FillUnusedFieldsData>, 6452} 6453 6454pub struct FillUnusedFieldsData { 6455 /// All the missing positional and labelled fields. 6456 positional: Vec<Arc<Type>>, 6457 labelled: Vec<(EcoString, Arc<Type>)>, 6458 /// We need this in order to tell where the missing positional arguments 6459 /// should be inserted. 6460 first_labelled_argument_start: Option<u32>, 6461 /// The end of the final argument before the spread, if there's any. 6462 /// We'll use this to delete everything that comes after the final argument, 6463 /// after adding all the ignored fields. 6464 last_argument_end: Option<u32>, 6465 spread_location: SrcSpan, 6466} 6467 6468impl<'a> FillUnusedFields<'a> { 6469 pub fn new( 6470 module: &'a Module, 6471 line_numbers: &'a LineNumbers, 6472 params: &'a CodeActionParams, 6473 ) -> Self { 6474 Self { 6475 module, 6476 params, 6477 edits: TextEdits::new(line_numbers), 6478 data: None, 6479 } 6480 } 6481 6482 pub fn code_actions(mut self) -> Vec<CodeAction> { 6483 self.visit_typed_module(&self.module.ast); 6484 6485 let Some(FillUnusedFieldsData { 6486 positional, 6487 labelled, 6488 first_labelled_argument_start, 6489 last_argument_end, 6490 spread_location, 6491 }) = self.data 6492 else { 6493 return vec![]; 6494 }; 6495 6496 // Do not suggest this code action if there's no ignored fields at all. 6497 if positional.is_empty() && labelled.is_empty() { 6498 return vec![]; 6499 }; 6500 6501 // We add all the missing positional arguments before the first 6502 // labelled one (and so after all the already existing positional ones). 6503 if !positional.is_empty() { 6504 // We want to make sure that all positional args will have a name 6505 // that's different from any label. So we add those as already used 6506 // names. 6507 let mut names = NameGenerator::new(); 6508 for (label, _) in labelled.iter() { 6509 names.add_used_name(label.clone()); 6510 } 6511 6512 let positional_args = positional 6513 .iter() 6514 .map(|type_| names.generate_name_from_type(type_)) 6515 .join(", "); 6516 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); 6517 6518 // The positional arguments are going to be followed by some other 6519 // arguments if there's some already existing labelled args 6520 // (`last_argument_end.is_some`), of if we're adding those labelled args 6521 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the 6522 // final positional argument we're adding to separate it from the ones that 6523 // are going to come after. 6524 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); 6525 let positional_args = if has_arguments_after { 6526 format!("{positional_args}, ") 6527 } else { 6528 positional_args 6529 }; 6530 6531 self.edits.insert(insert_at, positional_args); 6532 } 6533 6534 if !labelled.is_empty() { 6535 // If there's labelled arguments to add, we replace the existing spread 6536 // with the arguments to be added. This way commas and all should already 6537 // be correct. 6538 let labelled_args = labelled 6539 .iter() 6540 .map(|(label, _)| format!("{label}:")) 6541 .join(", "); 6542 self.edits.replace(spread_location, labelled_args); 6543 } else if let Some(delete_start) = last_argument_end { 6544 // However, if there's no labelled arguments to insert we still need 6545 // to delete the entire spread: we start deleting from the end of the 6546 // final argument, if there's one. 6547 // This way we also get rid of any comma separating the last argument 6548 // and the spread to be removed. 6549 self.edits 6550 .delete(SrcSpan::new(delete_start, spread_location.end)) 6551 } else { 6552 // Otherwise we just delete the spread. 6553 self.edits.delete(spread_location) 6554 } 6555 6556 let mut action = Vec::with_capacity(1); 6557 CodeActionBuilder::new("Fill unused fields") 6558 .kind(CodeActionKind::REFACTOR_REWRITE) 6559 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6560 .preferred(false) 6561 .push_to(&mut action); 6562 action 6563 } 6564} 6565 6566impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> { 6567 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { 6568 // We can only interpolate/split a string if the cursor is somewhere 6569 // within its location, otherwise we skip it. 6570 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); 6571 if !within(self.params.range, pattern_range) { 6572 return; 6573 } 6574 6575 if let TypedPattern::Constructor { 6576 arguments, 6577 spread: Some(spread_location), 6578 .. 6579 } = pattern 6580 { 6581 if let Some(PatternUnusedArguments { 6582 positional, 6583 labelled, 6584 }) = pattern.unused_arguments() 6585 { 6586 // If there's any unused argument that's being ignored we want to 6587 // suggest the code action. 6588 let first_labelled_argument_start = arguments 6589 .iter() 6590 .find(|arg| !arg.is_implicit() && arg.label.is_some()) 6591 .map(|arg| arg.location.start); 6592 6593 let last_argument_end = arguments 6594 .iter() 6595 .filter(|arg| !arg.is_implicit()) 6596 .next_back() 6597 .map(|arg| arg.location.end); 6598 6599 self.data = Some(FillUnusedFieldsData { 6600 positional, 6601 labelled, 6602 first_labelled_argument_start, 6603 last_argument_end, 6604 spread_location: *spread_location, 6605 }); 6606 }; 6607 } 6608 6609 ast::visit::visit_typed_pattern(self, pattern); 6610 } 6611} 6612 6613/// Code action to remove an echo. 6614/// 6615pub struct RemoveEchos<'a> { 6616 module: &'a Module, 6617 params: &'a CodeActionParams, 6618 edits: TextEdits<'a>, 6619 is_hovering_echo: bool, 6620 echo_spans_to_delete: Vec<SrcSpan>, 6621 // We need to keep a reference to the two latest pipeline assignments we 6622 // run into to properly delete an echo that's inside a pipeline. 6623 latest_pipe_step: Option<SrcSpan>, 6624 second_to_latest_pipe_step: Option<SrcSpan>, 6625} 6626 6627impl<'a> RemoveEchos<'a> { 6628 pub fn new( 6629 module: &'a Module, 6630 line_numbers: &'a LineNumbers, 6631 params: &'a CodeActionParams, 6632 ) -> Self { 6633 Self { 6634 module, 6635 params, 6636 edits: TextEdits::new(line_numbers), 6637 is_hovering_echo: false, 6638 echo_spans_to_delete: vec![], 6639 latest_pipe_step: None, 6640 second_to_latest_pipe_step: None, 6641 } 6642 } 6643 6644 pub fn code_actions(mut self) -> Vec<CodeAction> { 6645 self.visit_typed_module(&self.module.ast); 6646 6647 // We only want to trigger the action if we're over one of the echos in 6648 // the module 6649 if !self.is_hovering_echo { 6650 return vec![]; 6651 }; 6652 6653 for span in self.echo_spans_to_delete { 6654 self.edits.delete(span); 6655 } 6656 6657 let mut action = Vec::with_capacity(1); 6658 CodeActionBuilder::new("Remove all `echo`s from this module") 6659 .kind(CodeActionKind::REFACTOR_REWRITE) 6660 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6661 .preferred(false) 6662 .push_to(&mut action); 6663 action 6664 } 6665 6666 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) { 6667 for i in 0..statements.len() { 6668 let statement = statements 6669 .get(i) 6670 .expect("Statement must exist in iteration"); 6671 let next_statement = statements.get(i + 1); 6672 let is_last = i == statements.len() - 1; 6673 6674 match statement { 6675 // We remove any echo that is used as a standalone statement used 6676 // to print a literal value. 6677 // 6678 // ```gleam 6679 // pub fn main() { 6680 // echo "I'm here" 6681 // do_something() 6682 // echo "Safe!" 6683 // do_something_else() 6684 // } 6685 // ``` 6686 // 6687 // Here we want to remove not just the echo but also the literal 6688 // strings they're printing. 6689 // 6690 // It's safe to do this only if echo is not the last expression 6691 // in a function's block (otherwise we might change the function's 6692 // return type by removing the entire line) and the value being 6693 // printed is a literal expression. 6694 // 6695 ast::Statement::Expression(TypedExpr::Echo { 6696 location, 6697 expression, 6698 .. 6699 }) if !is_last 6700 && expression.as_ref().is_some_and(|expression| { 6701 expression.is_literal() || expression.is_var() 6702 }) => 6703 { 6704 let echo_range = self.edits.src_span_to_lsp_range(*location); 6705 if within(self.params.range, echo_range) { 6706 self.is_hovering_echo = true; 6707 } 6708 6709 let end = next_statement 6710 .map(|next| { 6711 let echo_end = location.end; 6712 let next_start = next.location().start; 6713 // We want to remove everything until the start of the 6714 // following statement. However, we have to be careful not to 6715 // delete any comments. So if there's any comment between the 6716 // echo to remove and the next statement, we just delete until 6717 // the comment's start. 6718 self.module 6719 .extra 6720 .first_comment_between(echo_end, next_start) 6721 // For comments we record the start of their content, not of the `//` 6722 // so we're subtracting 2 here to not delete the `//` as well 6723 .map(|comment| comment.start - 2) 6724 .unwrap_or(next_start) 6725 }) 6726 .unwrap_or(location.end); 6727 6728 self.echo_spans_to_delete.push(SrcSpan { 6729 start: location.start, 6730 end, 6731 }); 6732 } 6733 6734 // Otherwise we visit the statement as usual. 6735 ast::Statement::Expression(_) 6736 | ast::Statement::Assignment(_) 6737 | ast::Statement::Use(_) 6738 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement), 6739 } 6740 } 6741 } 6742} 6743 6744impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> { 6745 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 6746 self.visit_function_statements(&fun.body); 6747 } 6748 6749 fn visit_typed_expr_fn( 6750 &mut self, 6751 _location: &'ast SrcSpan, 6752 _type_: &'ast Arc<Type>, 6753 _kind: &'ast FunctionLiteralKind, 6754 _args: &'ast [TypedArg], 6755 body: &'ast Vec1<TypedStatement>, 6756 _return_annotation: &'ast Option<ast::TypeAst>, 6757 ) { 6758 self.visit_function_statements(body); 6759 } 6760 6761 fn visit_typed_expr_echo( 6762 &mut self, 6763 location: &'ast SrcSpan, 6764 type_: &'ast Arc<Type>, 6765 expression: &'ast Option<Box<TypedExpr>>, 6766 ) { 6767 // We also want to trigger the action if we're hovering over the expression 6768 // being printed. So we create a unique span starting from the start of echo 6769 // end ending at the end of the expression. 6770 // 6771 // ``` 6772 // echo 1 + 2 6773 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're 6774 // inside it, not just the keyword 6775 // ``` 6776 // 6777 let echo_range = self.edits.src_span_to_lsp_range(*location); 6778 if within(self.params.range, echo_range) { 6779 self.is_hovering_echo = true; 6780 } 6781 6782 if let Some(expression) = expression { 6783 // If there's an expression we delete everything we find until its 6784 // start (excluded). 6785 let span_to_delete = SrcSpan::new(location.start, expression.location().start); 6786 self.echo_spans_to_delete.push(span_to_delete); 6787 } else { 6788 // Othwerise we know we're inside a pipeline, we take the closest step 6789 // that is not echo itself and delete everything from its end until the 6790 // end of the echo keyword: 6791 // 6792 // ```txt 6793 // wibble |> echo |> wobble 6794 // ^^^^^^^^ This span right here 6795 // ``` 6796 let step_preceding_echo = self 6797 .latest_pipe_step 6798 .filter(|l| l != location) 6799 .or(self.second_to_latest_pipe_step); 6800 if let Some(step_preceding_echo) = step_preceding_echo { 6801 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4); 6802 self.echo_spans_to_delete.push(span_to_delete); 6803 } 6804 } 6805 6806 ast::visit::visit_typed_expr_echo(self, location, type_, expression); 6807 } 6808 6809 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 6810 if self.latest_pipe_step.is_some() { 6811 self.second_to_latest_pipe_step = self.latest_pipe_step; 6812 } 6813 self.latest_pipe_step = Some(assignment.location); 6814 ast::visit::visit_typed_pipeline_assignment(self, assignment); 6815 } 6816} 6817 6818/// Code action to wrap assignment and case clause values in a block. 6819/// 6820/// ```gleam 6821/// pub type PokemonType { 6822/// Fire 6823/// Water 6824/// } 6825/// 6826/// pub fn main() { 6827/// let pokemon_type: PokemonType = todo 6828/// case pokemon_type { 6829/// Water -> soak() 6830/// ^^^^^^ Cursor over the spread 6831/// Fire -> burn() 6832/// } 6833/// } 6834/// ``` 6835/// Becomes 6836/// ```gleam 6837/// pub type PokemonType { 6838/// Fire 6839/// Water 6840/// } 6841/// 6842/// pub fn main() { 6843/// let pokemon_type: PokemonType = todo 6844/// case pokemon_type { 6845/// Water -> { 6846/// soak() 6847/// } 6848/// Fire -> burn() 6849/// } 6850/// } 6851/// ``` 6852/// 6853pub struct WrapInBlock<'a> { 6854 module: &'a Module, 6855 params: &'a CodeActionParams, 6856 edits: TextEdits<'a>, 6857 selected_expression: Option<SrcSpan>, 6858} 6859 6860impl<'a> WrapInBlock<'a> { 6861 pub fn new( 6862 module: &'a Module, 6863 line_numbers: &'a LineNumbers, 6864 params: &'a CodeActionParams, 6865 ) -> Self { 6866 Self { 6867 module, 6868 params, 6869 edits: TextEdits::new(line_numbers), 6870 selected_expression: None, 6871 } 6872 } 6873 6874 pub fn code_actions(mut self) -> Vec<CodeAction> { 6875 self.visit_typed_module(&self.module.ast); 6876 6877 let Some(expr_span) = self.selected_expression else { 6878 return vec![]; 6879 }; 6880 6881 let Some(expr_string) = self 6882 .module 6883 .code 6884 .get(expr_span.start as usize..(expr_span.end as usize + 1)) 6885 else { 6886 return vec![]; 6887 }; 6888 6889 let range = self 6890 .edits 6891 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 6892 6893 let indent_size = 6894 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 6895 6896 let expr_indent_size = indent_size + 2; 6897 6898 let indent = " ".repeat(indent_size); 6899 let inner_indent = " ".repeat(expr_indent_size); 6900 6901 self.edits.replace( 6902 expr_span, 6903 format!("{{\n{inner_indent}{expr_string}{indent}}}"), 6904 ); 6905 6906 let mut action = Vec::with_capacity(1); 6907 CodeActionBuilder::new("Wrap in block") 6908 .kind(CodeActionKind::REFACTOR_EXTRACT) 6909 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6910 .preferred(false) 6911 .push_to(&mut action); 6912 action 6913 } 6914} 6915 6916impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> { 6917 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 6918 ast::visit::visit_typed_expr(self, &assignment.value); 6919 if !within( 6920 self.params.range, 6921 self.edits 6922 .src_span_to_lsp_range(assignment.value.location()), 6923 ) { 6924 return; 6925 } 6926 match &assignment.value { 6927 // To avoid wrapping the same expression in multiple, nested blocks. 6928 TypedExpr::Block { .. } => {} 6929 TypedExpr::RecordAccess { .. } 6930 | TypedExpr::Int { .. } 6931 | TypedExpr::Float { .. } 6932 | TypedExpr::String { .. } 6933 | TypedExpr::Pipeline { .. } 6934 | TypedExpr::Var { .. } 6935 | TypedExpr::Fn { .. } 6936 | TypedExpr::List { .. } 6937 | TypedExpr::Call { .. } 6938 | TypedExpr::BinOp { .. } 6939 | TypedExpr::Case { .. } 6940 | TypedExpr::ModuleSelect { .. } 6941 | TypedExpr::Tuple { .. } 6942 | TypedExpr::TupleIndex { .. } 6943 | TypedExpr::Todo { .. } 6944 | TypedExpr::Panic { .. } 6945 | TypedExpr::Echo { .. } 6946 | TypedExpr::BitArray { .. } 6947 | TypedExpr::RecordUpdate { .. } 6948 | TypedExpr::NegateBool { .. } 6949 | TypedExpr::NegateInt { .. } 6950 | TypedExpr::Invalid { .. } => { 6951 self.selected_expression = Some(assignment.value.location()); 6952 } 6953 }; 6954 ast::visit::visit_typed_assignment(self, assignment); 6955 } 6956 6957 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 6958 ast::visit::visit_typed_clause(self, clause); 6959 6960 if !within( 6961 self.params.range, 6962 self.edits.src_span_to_lsp_range(clause.then.location()), 6963 ) { 6964 return; 6965 } 6966 6967 // To avoid wrapping the same expression in multiple, nested blocks. 6968 if !matches!(clause.then, TypedExpr::Block { .. }) { 6969 self.selected_expression = Some(clause.then.location()); 6970 }; 6971 6972 ast::visit::visit_typed_clause(self, clause); 6973 } 6974} 6975 6976/// Code action to fix wrong binary operators when the compiler can easily tell 6977/// what the correct alternative is. 6978/// 6979/// ```gleam 6980/// 6981/// ``` 6982/// 6983pub struct FixBinaryOperation<'a> { 6984 module: &'a Module, 6985 params: &'a CodeActionParams, 6986 edits: TextEdits<'a>, 6987 fix: Option<(SrcSpan, ast::BinOp)>, 6988} 6989 6990impl<'a> FixBinaryOperation<'a> { 6991 pub fn new( 6992 module: &'a Module, 6993 line_numbers: &'a LineNumbers, 6994 params: &'a CodeActionParams, 6995 ) -> Self { 6996 Self { 6997 module, 6998 params, 6999 edits: TextEdits::new(line_numbers), 7000 fix: None, 7001 } 7002 } 7003 7004 pub fn code_actions(mut self) -> Vec<CodeAction> { 7005 self.visit_typed_module(&self.module.ast); 7006 7007 let Some((location, replacement)) = self.fix else { 7008 return vec![]; 7009 }; 7010 7011 self.edits.replace(location, replacement.name().into()); 7012 7013 let mut action = Vec::with_capacity(1); 7014 CodeActionBuilder::new(&format!("Use `{}`", replacement.name())) 7015 .kind(CodeActionKind::REFACTOR_REWRITE) 7016 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7017 .preferred(true) 7018 .push_to(&mut action); 7019 action 7020 } 7021} 7022 7023impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> { 7024 fn visit_typed_expr_bin_op( 7025 &mut self, 7026 location: &'ast SrcSpan, 7027 type_: &'ast Arc<Type>, 7028 name: &'ast ast::BinOp, 7029 name_location: &'ast SrcSpan, 7030 left: &'ast TypedExpr, 7031 right: &'ast TypedExpr, 7032 ) { 7033 let binop_range = self.edits.src_span_to_lsp_range(*location); 7034 if !within(self.params.range, binop_range) { 7035 return; 7036 } 7037 7038 if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() { 7039 self.fix = name.float_equivalent().map(|fix| (*name_location, fix)); 7040 } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() { 7041 self.fix = name.int_equivalent().map(|fix| (*name_location, fix)) 7042 } else if *name == ast::BinOp::AddInt 7043 && left.type_().is_string() 7044 && right.type_().is_string() 7045 { 7046 self.fix = Some((*name_location, ast::BinOp::Concatenate)) 7047 } 7048 7049 ast::visit::visit_typed_expr_bin_op( 7050 self, 7051 location, 7052 type_, 7053 name, 7054 name_location, 7055 left, 7056 right, 7057 ); 7058 } 7059} 7060 7061/// Code action builder to automatically fix segments that have a value that's 7062/// guaranteed to overflow. 7063/// 7064pub struct FixTruncatedBitArraySegment<'a> { 7065 module: &'a Module, 7066 params: &'a CodeActionParams, 7067 edits: TextEdits<'a>, 7068 truncation: Option<BitArraySegmentTruncation>, 7069} 7070 7071impl<'a> FixTruncatedBitArraySegment<'a> { 7072 pub fn new( 7073 module: &'a Module, 7074 line_numbers: &'a LineNumbers, 7075 params: &'a CodeActionParams, 7076 ) -> Self { 7077 Self { 7078 module, 7079 params, 7080 edits: TextEdits::new(line_numbers), 7081 truncation: None, 7082 } 7083 } 7084 7085 pub fn code_actions(mut self) -> Vec<CodeAction> { 7086 self.visit_typed_module(&self.module.ast); 7087 7088 let Some(truncation) = self.truncation else { 7089 return vec![]; 7090 }; 7091 7092 let replacement = truncation.truncated_into.to_string(); 7093 self.edits 7094 .replace(truncation.value_location, replacement.clone()); 7095 7096 let mut action = Vec::with_capacity(1); 7097 CodeActionBuilder::new(&format!("Replace with `{}`", replacement)) 7098 .kind(CodeActionKind::REFACTOR_REWRITE) 7099 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7100 .preferred(true) 7101 .push_to(&mut action); 7102 action 7103 } 7104} 7105 7106impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> { 7107 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) { 7108 let segment_range = self.edits.src_span_to_lsp_range(segment.location); 7109 if !within(self.params.range, segment_range) { 7110 return; 7111 } 7112 7113 if let Some(truncation) = segment.check_for_truncated_value() { 7114 self.truncation = Some(truncation); 7115 } 7116 7117 ast::visit::visit_typed_expr_bit_array_segment(self, segment); 7118 } 7119} 7120 7121/// Code action builder to remove unused imports and values. 7122/// 7123pub struct RemoveUnusedImports<'a> { 7124 module: &'a Module, 7125 params: &'a CodeActionParams, 7126 imports: Vec<&'a Import<EcoString>>, 7127 edits: TextEdits<'a>, 7128} 7129 7130#[derive(Debug)] 7131enum UnusedImport { 7132 ValueOrType(SrcSpan), 7133 Module(SrcSpan), 7134 ModuleAlias(SrcSpan), 7135} 7136 7137impl UnusedImport { 7138 fn location(&self) -> SrcSpan { 7139 match self { 7140 UnusedImport::ValueOrType(location) 7141 | UnusedImport::Module(location) 7142 | UnusedImport::ModuleAlias(location) => *location, 7143 } 7144 } 7145} 7146 7147impl<'a> RemoveUnusedImports<'a> { 7148 pub fn new( 7149 module: &'a Module, 7150 line_numbers: &'a LineNumbers, 7151 params: &'a CodeActionParams, 7152 ) -> Self { 7153 Self { 7154 module, 7155 params, 7156 edits: TextEdits::new(line_numbers), 7157 imports: vec![], 7158 } 7159 } 7160 7161 /// Given an import location, returns a list of the spans of all the 7162 /// unqualified values it's importing. Sorted by SrcSpan location. 7163 /// 7164 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> { 7165 self.imports 7166 .iter() 7167 .find(|import| import.location.contains(import_location.start)) 7168 .map(|import| { 7169 let types = import.unqualified_types.iter().map(|type_| type_.location); 7170 let values = import.unqualified_values.iter().map(|value| value.location); 7171 types 7172 .chain(values) 7173 .sorted_by_key(|location| location.start) 7174 .collect_vec() 7175 }) 7176 .unwrap_or_default() 7177 } 7178 7179 pub fn code_actions(mut self) -> Vec<CodeAction> { 7180 // If there's no import in the module then there can't be any unused 7181 // import to remove. 7182 self.visit_typed_module(&self.module.ast); 7183 if self.imports.is_empty() { 7184 return vec![]; 7185 } 7186 7187 let unused_imports = (self.module.ast.type_info.warnings.iter()) 7188 .filter_map(|warning| match warning { 7189 type_::Warning::UnusedImportedValue { location, .. } => { 7190 Some(UnusedImport::ValueOrType(*location)) 7191 } 7192 type_::Warning::UnusedType { 7193 location, 7194 imported: true, 7195 .. 7196 } => Some(UnusedImport::ValueOrType(*location)), 7197 type_::Warning::UnusedImportedModule { location, .. } => { 7198 Some(UnusedImport::Module(*location)) 7199 } 7200 type_::Warning::UnusedImportedModuleAlias { location, .. } => { 7201 Some(UnusedImport::ModuleAlias(*location)) 7202 } 7203 _ => None, 7204 }) 7205 .sorted_by_key(|import| import.location()) 7206 .collect_vec(); 7207 7208 // If the cursor is not over any of the unused imports then we don't offer 7209 // the code action. 7210 let hovering_unused_import = unused_imports.iter().any(|import| { 7211 let unused_range = self.edits.src_span_to_lsp_range(import.location()); 7212 overlaps(self.params.range, unused_range) 7213 }); 7214 if !hovering_unused_import { 7215 return vec![]; 7216 } 7217 7218 // Otherwise we start removing all unused imports: 7219 for import in &unused_imports { 7220 match import { 7221 // When an entire module is unused we can delete its entire location 7222 // in the source code. 7223 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { 7224 if self.edits.line_numbers.spans_entire_line(location) { 7225 // If the unused module spans over the entire line then 7226 // we also take care of removing the following newline 7227 // characther! 7228 self.edits.delete(SrcSpan { 7229 start: location.start, 7230 end: location.end + 1, 7231 }) 7232 } else { 7233 self.edits.delete(*location) 7234 } 7235 } 7236 7237 // When removing unused imported values we have to be a bit more 7238 // careful: an unused value might be followed or preceded by a 7239 // comma that we also need to remove! 7240 UnusedImport::ValueOrType(location) => { 7241 let imported = self.imported_values(*location); 7242 let unused_index = imported.binary_search(location); 7243 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1); 7244 let next_value = unused_index 7245 .ok() 7246 .and_then(|value_index| imported.get(value_index + 1)); 7247 let previous_value = unused_index.ok().and_then(|value_index| { 7248 value_index 7249 .checked_sub(1) 7250 .and_then(|previous_index| imported.get(previous_index)) 7251 }); 7252 let previous_is_unused = previous_value.is_some_and(|previous| { 7253 unused_imports 7254 .as_slice() 7255 .binary_search_by_key(previous, |import| import.location()) 7256 .is_ok() 7257 }); 7258 7259 match (previous_value, next_value) { 7260 // If there's a value following the unused import we need 7261 // to remove all characters until its start! 7262 // 7263 // ```gleam 7264 // import wibble.{unused, used} 7265 // // ^^^^^^^^^^^ We need to remove all of this! 7266 // ``` 7267 // 7268 (_, Some(next_value)) => self.edits.delete(SrcSpan { 7269 start: location.start, 7270 end: next_value.start, 7271 }), 7272 7273 // If this unused import is the last of the unuqualified 7274 // list and is preceded by another used value then we 7275 // need to do some additional cleanup and remove all 7276 // characters starting from its end. 7277 // (If the previous one is unused as well it will take 7278 // care of removing all the extra space) 7279 // 7280 // ```gleam 7281 // import wibble.{used, unused} 7282 // // ^^^^^^^^^^^^ We need to remove all of this! 7283 // ``` 7284 // 7285 (Some(previous_value), _) if is_last && !previous_is_unused => { 7286 self.edits.delete(SrcSpan { 7287 start: previous_value.end, 7288 end: location.end, 7289 }) 7290 } 7291 7292 // In all other cases it means that this is the only 7293 // item in the import list. We can just remove it. 7294 // 7295 // ```gleam 7296 // import wibble.{unused} 7297 // // ^^^^^^ We remove this import, the formatter will already 7298 // // take care of removing the empty curly braces 7299 // ``` 7300 // 7301 (_, _) => self.edits.delete(*location), 7302 } 7303 } 7304 } 7305 } 7306 7307 let mut action = Vec::with_capacity(1); 7308 CodeActionBuilder::new("Remove unused imports") 7309 .kind(CodeActionKind::REFACTOR_REWRITE) 7310 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7311 .preferred(true) 7312 .push_to(&mut action); 7313 action 7314 } 7315} 7316 7317impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> { 7318 fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) { 7319 self.imports = module 7320 .definitions 7321 .iter() 7322 .filter_map(|definition| match definition { 7323 ast::Definition::Import(import) => Some(import), 7324 _ => None, 7325 }) 7326 .collect_vec(); 7327 } 7328}