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