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

Configure Feed

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

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