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

Configure Feed

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

gleam / language-server / src / code_action.rs
427 kB 12255 lines
1use std::{collections::HashSet, iter, sync::Arc}; 2 3use ecow::{EcoString, eco_format}; 4use gleam_core::{ 5 Error, STDLIB_PACKAGE_NAME, 6 analyse::Inferred, 7 ast::{ 8 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable, 9 BoundVariableName, CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import, 10 InvalidExpression, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments, 11 PipelineAssignmentKind, Publicity, RecordConstructor, SrcSpan, TodoKind, 12 TypeAstConstructorName, TypedArg, TypedAssignment, TypedClauseGuard, TypedDefinitions, 13 TypedExpr, TypedFunction, TypedModuleConstant, TypedPattern, TypedPipelineAssignment, 14 TypedRecordConstructor, TypedStatement, TypedTailPattern, TypedUse, visit::Visit as _, 15 }, 16 build::{Located, Module, Origin}, 17 config::PackageConfig, 18 exhaustiveness::CompiledCase, 19 line_numbers::LineNumbers, 20 parse::{extra::ModuleExtra, lexer::string_to_keyword}, 21 paths::ProjectPaths, 22 strings::to_snake_case, 23 type_::{ 24 self, Error as TypeError, FieldMap, ModuleValueConstructor, Opaque, Type, TypeVar, 25 TypedCallArg, ValueConstructor, 26 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin}, 27 printer::Printer, 28 }, 29}; 30use im::HashMap; 31use itertools::Itertools; 32use lsp_types::{ 33 CodeAction, CodeActionKind, CodeActionParams, CreateFile, CreateFileOptions, DocumentChange, 34 Position, Range, TextEdit, Uri as Url, 35}; 36use vec1::{Vec1, vec1}; 37 38use crate::engine::{completely_within, position_within}; 39 40use super::{ 41 TextEdits, 42 compiler::LspProjectCompiler, 43 edits, 44 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import}, 45 engine::{overlaps, within}, 46 files::FileSystemProxy, 47 reference::{FindVariableReferences, VariableReferenceKind}, 48 src_span_to_lsp_range, url_from_path, 49}; 50 51#[derive(Debug)] 52pub struct CodeActionBuilder { 53 action: CodeAction, 54} 55 56impl CodeActionBuilder { 57 pub fn new(title: impl ToString) -> Self { 58 Self { 59 action: CodeAction { 60 title: title.to_string(), 61 kind: None, 62 diagnostics: None, 63 edit: None, 64 command: None, 65 is_preferred: None, 66 disabled: None, 67 data: None, 68 tags: None, 69 }, 70 } 71 } 72 73 pub fn kind(mut self, kind: CodeActionKind) -> Self { 74 self.action.kind = Some(kind); 75 self 76 } 77 78 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self { 79 let mut edit = self.action.edit.take().unwrap_or_default(); 80 let mut changes = edit.changes.take().unwrap_or_default(); 81 _ = changes.insert(uri, edits); 82 83 edit.changes = Some(changes); 84 self.action.edit = Some(edit); 85 self 86 } 87 88 pub fn document_changes(mut self, changes: Vec<DocumentChange>) -> Self { 89 let mut edit = self.action.edit.take().unwrap_or_default(); 90 91 edit.document_changes = Some(changes); 92 93 self.action.edit = Some(edit); 94 self 95 } 96 97 pub fn preferred(mut self, is_preferred: bool) -> Self { 98 self.action.is_preferred = Some(is_preferred); 99 self 100 } 101 102 pub fn push_to(self, actions: &mut Vec<CodeAction>) { 103 actions.push(self.action); 104 } 105} 106 107/// A small helper function to get the indentation at a given position. 108fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize { 109 let mut indent_size = 0; 110 let line_start = *line_numbers 111 .line_starts 112 .get(line as usize) 113 .expect("Line number should be valid"); 114 115 let mut chars = code[line_start as usize..].chars(); 116 while chars.next() == Some(' ') { 117 indent_size += 1; 118 } 119 120 indent_size 121} 122 123// Given a string and a position in it, if the position points to whitespace, 124// this function returns the next position which doesn't. 125fn next_nonwhitespace(string: &EcoString, position: u32) -> u32 { 126 let mut n = position; 127 let mut chars = string[position as usize..].chars(); 128 while chars.next().is_some_and(char::is_whitespace) { 129 n += 1; 130 } 131 n 132} 133 134// Given a string and a position in it, if the position points after whitespace, 135// this function returns the previous position which doesn't. 136fn previous_nonwhitespace(string: &EcoString, position: u32) -> u32 { 137 let mut n = position; 138 let mut chars = string[..position as usize].chars(); 139 while chars.next_back().is_some_and(char::is_whitespace) { 140 n -= 1; 141 } 142 n 143} 144 145/// Code action to remove literal tuples in case subjects, essentially making 146/// the elements of the tuples into the case's subjects. 147/// 148/// The code action is only available for the i'th subject if: 149/// - it is a non-empty tuple, and 150/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses. 151/// 152/// # Basic example: 153/// 154/// The following case expression: 155/// 156/// ```gleam 157/// case #(1, 2) { 158/// #(a, b) -> 0 159/// } 160/// ``` 161/// 162/// Becomes: 163/// 164/// ```gleam 165/// case 1, 2 { 166/// a, b -> 0 167/// } 168/// ``` 169/// 170/// # Another example: 171/// 172/// The following case expression does not produce any code action 173/// 174/// ```gleam 175/// case #(1, 2) { 176/// a -> 0 // <- the pattern is not a tuple 177/// } 178/// ``` 179pub struct RedundantTupleInCaseSubject<'a> { 180 edits: TextEdits<'a>, 181 code: &'a EcoString, 182 extra: &'a ModuleExtra, 183 params: &'a CodeActionParams, 184 module: &'a ast::TypedModule, 185 hovered: bool, 186} 187 188impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> { 189 fn visit_typed_expr_case( 190 &mut self, 191 location: &'ast SrcSpan, 192 type_: &'ast Arc<Type>, 193 subjects: &'ast [TypedExpr], 194 clauses: &'ast [ast::TypedClause], 195 compiled_case: &'ast CompiledCase, 196 ) { 197 for (subject_idx, subject) in subjects.iter().enumerate() { 198 let TypedExpr::Tuple { 199 location, elements, .. 200 } = subject 201 else { 202 continue; 203 }; 204 205 // Ignore empty tuple 206 if elements.is_empty() { 207 continue; 208 } 209 210 // We cannot rewrite clauses whose i-th pattern is not a discard or 211 // tuples. 212 let all_ith_patterns_are_tuples_or_discards = clauses 213 .iter() 214 .map(|clause| clause.pattern.get(subject_idx)) 215 .all(|pattern| { 216 matches!( 217 pattern, 218 Some(Pattern::Tuple { .. } | Pattern::Discard { .. }) 219 ) 220 }); 221 222 if !all_ith_patterns_are_tuples_or_discards { 223 continue; 224 } 225 226 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location())); 227 228 for clause in clauses { 229 match clause.pattern.get(subject_idx) { 230 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens( 231 *location, 232 elements.last().map(|element| element.location()), 233 ), 234 Some(Pattern::Discard { location, .. }) => { 235 self.discard_tuple_items(*location, elements.len()) 236 } 237 _ => panic!("safe: we've just checked all patterns must be discards/tuples"), 238 } 239 } 240 let range = self.edits.src_span_to_lsp_range(*location); 241 self.hovered = self.hovered || overlaps(self.params.range, range); 242 } 243 244 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case) 245 } 246} 247 248impl<'a> RedundantTupleInCaseSubject<'a> { 249 pub fn new( 250 module: &'a Module, 251 line_numbers: &'a LineNumbers, 252 params: &'a CodeActionParams, 253 ) -> Self { 254 Self { 255 edits: TextEdits::new(line_numbers), 256 code: &module.code, 257 extra: &module.extra, 258 params, 259 module: &module.ast, 260 hovered: false, 261 } 262 } 263 264 pub fn code_actions(mut self) -> Vec<CodeAction> { 265 self.visit_typed_module(self.module); 266 if !self.hovered { 267 return vec![]; 268 } 269 270 self.edits.edits.sort_by_key(|edit| edit.range.start); 271 272 let mut actions = vec![]; 273 CodeActionBuilder::new("Remove redundant tuples") 274 .kind(CodeActionKind::RefactorRewrite) 275 .changes(self.params.text_document.uri.clone(), self.edits.edits) 276 .preferred(true) 277 .push_to(&mut actions); 278 279 actions 280 } 281 282 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) { 283 let tuple_code = self 284 .code 285 .get(location.start as usize..location.end as usize) 286 .expect("valid span"); 287 288 // Delete `#` 289 self.edits 290 .delete(SrcSpan::new(location.start, location.start + 1)); 291 292 // Delete `(` 293 let (lparen_offset, _) = tuple_code 294 .match_indices('(') 295 // Ignore in comments 296 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32)) 297 .expect("`(` not found in tuple"); 298 299 self.edits.delete(SrcSpan::new( 300 location.start + lparen_offset as u32, 301 location.start + lparen_offset as u32 + 1, 302 )); 303 304 // Delete trailing `,` (if applicable) 305 if let Some(last_elem_location) = last_elem_location { 306 // Get the code after the last element until the tuple's `)` 307 let code_after_last_elem = self 308 .code 309 .get(last_elem_location.end as usize..location.end as usize) 310 .expect("valid span"); 311 312 if let Some((trailing_comma_offset, _)) = code_after_last_elem 313 .rmatch_indices(',') 314 // Ignore in comments 315 .find(|(i, _)| { 316 !self 317 .extra 318 .is_within_comment(last_elem_location.end + *i as u32) 319 }) 320 { 321 self.edits.delete(SrcSpan::new( 322 last_elem_location.end + trailing_comma_offset as u32, 323 last_elem_location.end + trailing_comma_offset as u32 + 1, 324 )); 325 } 326 } 327 328 // Delete ) 329 self.edits 330 .delete(SrcSpan::new(location.end - 1, location.end)); 331 } 332 333 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) { 334 // Replace the old discard with multiple discard, one for each of the 335 // tuple items. 336 self.edits.replace( 337 discard_location, 338 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(), 339 ) 340 } 341} 342 343/// Builder for code action to convert `let assert` into a case expression. 344/// 345pub struct LetAssertToCase<'a> { 346 module: &'a Module, 347 params: &'a CodeActionParams, 348 actions: Vec<CodeAction>, 349 edits: TextEdits<'a>, 350} 351 352impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> { 353 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 354 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 355 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan { 356 start: assignment.location.start, 357 end: assignment.value.location().start, 358 }); 359 self.visit_typed_expr(&assignment.value); 360 361 // Only offer the code action if the cursor is over the statement and 362 // to prevent weird behaviour when `let assert` statements are nested, 363 // we only check for the code action between the `let` and `=`. 364 if !(within(self.params.range, assignment_range) 365 && overlaps(self.params.range, assignment_start_range)) 366 { 367 return; 368 } 369 370 // This pattern only applies to `let assert` 371 let AssignmentKind::Assert { message, .. } = &assignment.kind else { 372 return; 373 }; 374 375 // Get the source code for the tested expression 376 let location = assignment.value.location(); 377 let expr = self 378 .module 379 .code 380 .get(location.start as usize..location.end as usize) 381 .expect("Location must be valid"); 382 383 // Get the source code for the pattern 384 let pattern_location = assignment.pattern.location(); 385 let pattern = self 386 .module 387 .code 388 .get(pattern_location.start as usize..pattern_location.end as usize) 389 .expect("Location must be valid"); 390 391 let message = message.as_ref().map(|message| { 392 let location = message.location(); 393 self.module 394 .code 395 .get(location.start as usize..location.end as usize) 396 .expect("Location must be valid") 397 }); 398 399 let range = self.edits.src_span_to_lsp_range(assignment.location); 400 401 // Figure out which variables are assigned in the pattern 402 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern); 403 404 let assigned = match variables.len() { 405 0 => "_", 406 1 => variables.first().expect("Variables is length one"), 407 _ => &format!("#({})", variables.join(", ")), 408 }; 409 410 let mut new_text = format!("let {assigned} = "); 411 let panic_message = if let Some(message) = message { 412 &format!("panic as {message}") 413 } else { 414 "panic" 415 }; 416 let clauses = vec![ 417 // The existing pattern 418 CaseClause { 419 pattern, 420 // `_` is not a valid expression, so if we are not 421 // binding any variables in the pattern, we simply return Nil. 422 expression: if assigned == "_" { "Nil" } else { assigned }, 423 }, 424 CaseClause { 425 pattern: "_", 426 expression: panic_message, 427 }, 428 ]; 429 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text); 430 431 let uri = &self.params.text_document.uri; 432 433 CodeActionBuilder::new("Convert to case") 434 .kind(CodeActionKind::RefactorRewrite) 435 .changes(uri.clone(), vec![TextEdit { range, new_text }]) 436 .preferred(false) 437 .push_to(&mut self.actions); 438 } 439} 440 441impl<'a> LetAssertToCase<'a> { 442 pub fn new( 443 module: &'a Module, 444 line_numbers: &'a LineNumbers, 445 params: &'a CodeActionParams, 446 ) -> Self { 447 Self { 448 module, 449 params, 450 actions: Vec::new(), 451 edits: TextEdits::new(line_numbers), 452 } 453 } 454 455 pub fn code_actions(mut self) -> Vec<CodeAction> { 456 self.visit_typed_module(&self.module.ast); 457 self.actions 458 } 459} 460 461struct PatternVariableFinder { 462 pattern_variables: Vec<EcoString>, 463} 464 465impl PatternVariableFinder { 466 fn new() -> Self { 467 Self { 468 pattern_variables: Vec::new(), 469 } 470 } 471 472 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> { 473 let mut finder = Self::new(); 474 finder.visit_typed_pattern(pattern); 475 finder.pattern_variables 476 } 477} 478 479impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder { 480 fn visit_typed_pattern_variable( 481 &mut self, 482 _location: &'ast SrcSpan, 483 name: &'ast EcoString, 484 _type: &'ast Arc<Type>, 485 _origin: &'ast VariableOrigin, 486 ) { 487 self.pattern_variables.push(name.clone()); 488 } 489 490 fn visit_typed_pattern_assign( 491 &mut self, 492 location: &'ast SrcSpan, 493 name: &'ast EcoString, 494 pattern: &'ast TypedPattern, 495 ) { 496 self.pattern_variables.push(name.clone()); 497 ast::visit::visit_typed_pattern_assign(self, location, name, pattern); 498 } 499 500 fn visit_typed_pattern_string_prefix( 501 &mut self, 502 _location: &'ast SrcSpan, 503 _left_location: &'ast SrcSpan, 504 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 505 _right_location: &'ast SrcSpan, 506 _left_side_string: &'ast EcoString, 507 right_side_assignment: &'ast AssignName, 508 ) { 509 if let Some((name, _)) = left_side_assignment { 510 self.pattern_variables.push(name.clone()); 511 } 512 if let AssignName::Variable(name) = right_side_assignment { 513 self.pattern_variables.push(name.clone()); 514 } 515 } 516} 517 518pub fn code_action_inexhaustive_let_to_case( 519 module: &Module, 520 line_numbers: &LineNumbers, 521 params: &CodeActionParams, 522 error: &Option<Error>, 523 actions: &mut Vec<CodeAction>, 524) { 525 let Some(errors) = type_errors_for_module(error, module) else { 526 return; 527 }; 528 529 let inexhaustive_assignments = errors 530 .iter() 531 .filter_map(|error| { 532 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error { 533 Some((*location, missing)) 534 } else { 535 None 536 } 537 }) 538 .collect_vec(); 539 540 if inexhaustive_assignments.is_empty() { 541 return; 542 } 543 544 for (location, missing) in inexhaustive_assignments { 545 let mut text_edits = TextEdits::new(line_numbers); 546 547 let range = text_edits.src_span_to_lsp_range(location); 548 if !within(params.range, range) { 549 return; 550 } 551 552 let Some(Located::Statement(TypedStatement::Assignment(assignment))) = 553 module.find_node(location.start) 554 else { 555 continue; 556 }; 557 558 let TypedAssignment { 559 value, 560 pattern, 561 kind: AssignmentKind::Let, 562 location, 563 compiled_case: _, 564 annotation: _, 565 } = assignment.as_ref() 566 else { 567 continue; 568 }; 569 570 // Get the source code for the tested expression 571 let value_location = value.location(); 572 let expr = module 573 .code 574 .get(value_location.start as usize..value_location.end as usize) 575 .expect("Location must be valid"); 576 577 // Get the source code for the pattern 578 let pattern_location = pattern.location(); 579 let pattern_code = module 580 .code 581 .get(pattern_location.start as usize..pattern_location.end as usize) 582 .expect("Location must be valid"); 583 584 let range = text_edits.src_span_to_lsp_range(*location); 585 586 // Figure out which variables are assigned in the pattern 587 let variables = PatternVariableFinder::find_variables_in_pattern(pattern); 588 589 let assigned = match variables.len() { 590 0 => "_", 591 1 => variables.first().expect("Variables is length one"), 592 _ => &format!("#({})", variables.join(", ")), 593 }; 594 595 let mut new_text = format!("let {assigned} = "); 596 print_case_expression( 597 range.start.character as usize, 598 expr, 599 iter::once(CaseClause { 600 pattern: pattern_code, 601 expression: if assigned == "_" { "Nil" } else { assigned }, 602 }) 603 .chain(missing.iter().map(|pattern| CaseClause { 604 pattern, 605 expression: "todo", 606 })) 607 .collect(), 608 &mut new_text, 609 ); 610 611 let uri = &params.text_document.uri; 612 613 text_edits.replace(*location, new_text); 614 615 CodeActionBuilder::new("Convert to case") 616 .kind(CodeActionKind::QuickFix) 617 .changes(uri.clone(), text_edits.edits) 618 .preferred(true) 619 .push_to(actions); 620 } 621} 622 623pub(crate) fn type_errors_for_module<'a>( 624 error: &'a Option<Error>, 625 module: &Module, 626) -> Option<&'a Vec1<type_::Error>> { 627 let Some(Error::Type { 628 skipped_modules: _, 629 failed_modules, 630 }) = error 631 else { 632 return None; 633 }; 634 Some(&failed_modules.get(&module.name)?.errors) 635} 636 637struct CaseClause<'a> { 638 pattern: &'a str, 639 expression: &'a str, 640} 641 642fn print_case_expression( 643 indent_size: usize, 644 subject: &str, 645 clauses: Vec<CaseClause<'_>>, 646 buffer: &mut String, 647) { 648 let indent = " ".repeat(indent_size); 649 650 // Print the beginning of the expression 651 buffer.push_str("case "); 652 buffer.push_str(subject); 653 buffer.push_str(" {"); 654 655 for clause in clauses.iter() { 656 // Print the newline and indentation for this clause 657 buffer.push('\n'); 658 buffer.push_str(&indent); 659 // Indent this clause one level deeper than the case expression 660 buffer.push_str(" "); 661 662 // Print the clause 663 buffer.push_str(clause.pattern); 664 buffer.push_str(" -> "); 665 buffer.push_str(clause.expression); 666 } 667 668 // If there are no clauses to print, the closing brace should be 669 // on the same line as the opening one, with no space between. 670 if !clauses.is_empty() { 671 buffer.push('\n'); 672 buffer.push_str(&indent); 673 } 674 buffer.push('}'); 675} 676 677/// Builder for code action to apply the label shorthand syntax on arguments 678/// where the label has the same name as the variable. 679/// 680pub struct UseLabelShorthandSyntax<'a> { 681 module: &'a Module, 682 params: &'a CodeActionParams, 683 edits: TextEdits<'a>, 684} 685 686impl<'a> UseLabelShorthandSyntax<'a> { 687 pub fn new( 688 module: &'a Module, 689 line_numbers: &'a LineNumbers, 690 params: &'a CodeActionParams, 691 ) -> Self { 692 Self { 693 module, 694 params, 695 edits: TextEdits::new(line_numbers), 696 } 697 } 698 699 pub fn code_actions(mut self) -> Vec<CodeAction> { 700 self.visit_typed_module(&self.module.ast); 701 if self.edits.edits.is_empty() { 702 return vec![]; 703 } 704 let mut action = Vec::with_capacity(1); 705 CodeActionBuilder::new("Use label shorthand syntax") 706 .kind(CodeActionKind::Refactor) 707 .changes(self.params.text_document.uri.clone(), self.edits.edits) 708 .preferred(false) 709 .push_to(&mut action); 710 action 711 } 712} 713 714impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> { 715 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 716 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 717 let is_selected = overlaps(arg_range, self.params.range); 718 719 match arg { 720 CallArg { 721 label: Some(label), 722 value: TypedExpr::Var { name, location, .. }, 723 .. 724 } if is_selected && !arg.uses_label_shorthand() && label == name => { 725 self.edits.delete(*location) 726 } 727 _ => (), 728 } 729 730 ast::visit::visit_typed_call_arg(self, arg) 731 } 732 733 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 734 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 735 let is_selected = overlaps(arg_range, self.params.range); 736 737 match arg { 738 CallArg { 739 label: Some(label), 740 value: TypedPattern::Variable { name, location, .. }, 741 .. 742 } if is_selected && !arg.uses_label_shorthand() && label == name => { 743 self.edits.delete(*location) 744 } 745 _ => (), 746 } 747 748 ast::visit::visit_typed_pattern_call_arg(self, arg) 749 } 750} 751 752/// Builder for code action to apply the fill in the missing labelled arguments 753/// of the selected function call. 754/// 755pub struct FillInMissingLabelledArgs<'a> { 756 module: &'a Module, 757 params: &'a CodeActionParams, 758 edits: TextEdits<'a>, 759 use_right_hand_side_location: Option<SrcSpan>, 760 selected_call: Option<SelectedCall<'a>>, 761 current_function: Option<&'a TypedFunction>, 762} 763 764struct SelectedCall<'a> { 765 location: SrcSpan, 766 field_map: &'a FieldMap, 767 arguments: Vec<CallArg<()>>, 768 kind: SelectedCallKind, 769 fun_type: Option<Arc<Type>>, 770 enclosing_function: Option<&'a TypedFunction>, 771} 772 773enum SelectedCallKind { 774 Value, 775 Pattern, 776} 777 778impl<'a> FillInMissingLabelledArgs<'a> { 779 pub fn new( 780 module: &'a Module, 781 line_numbers: &'a LineNumbers, 782 params: &'a CodeActionParams, 783 ) -> Self { 784 Self { 785 module, 786 params, 787 edits: TextEdits::new(line_numbers), 788 use_right_hand_side_location: None, 789 selected_call: None, 790 current_function: None, 791 } 792 } 793 794 pub fn code_actions(mut self) -> Vec<CodeAction> { 795 self.visit_typed_module(&self.module.ast); 796 797 if let Some(SelectedCall { 798 location: call_location, 799 field_map, 800 arguments, 801 kind, 802 fun_type, 803 enclosing_function, 804 }) = self.selected_call 805 { 806 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback()); 807 let missing_labels = field_map.missing_labels(&arguments); 808 809 // If we're applying the code action to a use call, then we know 810 // that the last missing argument is going to be implicitly inserted 811 // by the compiler, so in that case we don't want to also add that 812 // last label to the completions. 813 let missing_labels = missing_labels.iter().peekable(); 814 let mut missing_labels = if is_use_call { 815 missing_labels.dropping_back(1) 816 } else { 817 missing_labels 818 }; 819 820 // If we couldn't find any missing label to insert we just return. 821 if missing_labels.peek().is_none() { 822 return vec![]; 823 } 824 825 // A pattern could have been written with no parentheses at all! 826 // So we need to check for the last character to see if parentheses 827 // are there or not before filling the arguments in 828 let has_parentheses = ")" 829 == code_at( 830 self.module, 831 SrcSpan::new(call_location.end - 1, call_location.end), 832 ); 833 let label_insertion_start = if has_parentheses { 834 // If it ends with a parentheses we'll need to start inserting 835 // right before the closing one... 836 call_location.end - 1 837 } else { 838 // ...otherwise we just append the result 839 call_location.end 840 }; 841 842 // Now we need to figure out if there's a comma at the end of the 843 // arguments list: 844 // 845 // call(one, |) 846 // ^ Cursor here, with a comma behind 847 // 848 // call(one|) 849 // ^ Cursor here, no comma behind, we'll have to add one! 850 // 851 let has_comma_after_last_argument = 852 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) { 853 self.module 854 .code 855 .get(last_arg.location.end as usize..=label_insertion_start as usize) 856 .is_some_and(|text| text.contains(',')) 857 } else { 858 false 859 }; 860 861 let variables_in_scope = enclosing_function 862 .map(|fun| { 863 ScopeVariableCollector::new(call_location.start).collect_from_function(fun) 864 }) 865 .unwrap_or_default(); 866 867 let labels_list = missing_labels 868 .map(|label| { 869 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope) 870 }) 871 .join(", "); 872 873 let has_no_explicit_arguments = arguments 874 .iter() 875 .filter(|arg| !arg.is_implicit()) 876 .peekable() 877 .peek() 878 .is_none(); 879 880 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument { 881 labels_list 882 } else { 883 format!(", {labels_list}") 884 }; 885 886 let edit = if has_parentheses { 887 labels_list 888 } else { 889 // If the variant whose arguments we're filling in was written 890 // with no parentheses we need to add those as well to make it a 891 // valid constructor. 892 format!("({labels_list})") 893 }; 894 895 self.edits.insert(label_insertion_start, edit); 896 897 let mut action = Vec::with_capacity(1); 898 CodeActionBuilder::new("Fill labels") 899 .kind(CodeActionKind::QuickFix) 900 .changes(self.params.text_document.uri.clone(), self.edits.edits) 901 .preferred(true) 902 .push_to(&mut action); 903 return action; 904 } 905 906 vec![] 907 } 908 909 /// Formats a label for insertion. Uses `label:` syntax if there's a variable 910 /// in scope with matching name and type, otherwise uses `label: todo`. 911 fn format_label( 912 label: &EcoString, 913 kind: &SelectedCallKind, 914 fun_type: &Option<Arc<Type>>, 915 field_map: &FieldMap, 916 variables_in_scope: &HashMap<EcoString, Arc<Type>>, 917 ) -> String { 918 if matches!(kind, SelectedCallKind::Pattern) { 919 return format!("{label}:"); 920 } 921 922 if let Some(var_type) = variables_in_scope.get(label) { 923 let expected_type = fun_type.as_ref().and_then(|ft| { 924 let (arg_types, _) = ft.fn_types()?; 925 let &index = field_map.fields.get(label)?; 926 arg_types.get(index as usize).cloned() 927 }); 928 929 if let Some(expected) = expected_type 930 && expected.same_as(var_type) 931 { 932 return format!("{label}:"); 933 } 934 } 935 936 format!("{label}: todo") 937 } 938 939 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> { 940 CallArg { 941 label: argument.label.clone(), 942 location: argument.location, 943 value: (), 944 implicit: argument.implicit, 945 } 946 } 947} 948 949impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> { 950 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 951 // We store the current function as the containing function while we traverse 952 // it, allowing handling nested functions correctly. 953 let previous_function = self.current_function; 954 self.current_function = Some(fun); 955 ast::visit::visit_typed_function(self, fun); 956 self.current_function = previous_function; 957 } 958 959 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 960 // If we're adding labels to a use call the correct location of the 961 // function we need to add labels to is `use_right_hand_side_location`. 962 // So we store it for when we're typing the use call. 963 let previous = self.use_right_hand_side_location; 964 self.use_right_hand_side_location = Some(use_.right_hand_side_location); 965 ast::visit::visit_typed_use(self, use_); 966 self.use_right_hand_side_location = previous; 967 } 968 969 fn visit_typed_expr_call( 970 &mut self, 971 location: &'ast SrcSpan, 972 type_: &'ast Arc<Type>, 973 fun: &'ast TypedExpr, 974 arguments: &'ast [TypedCallArg], 975 open_parenthesis: &'ast Option<u32>, 976 ) { 977 let call_range = self.edits.src_span_to_lsp_range(*location); 978 if !within(self.params.range, call_range) { 979 return; 980 } 981 982 if let Some(field_map) = fun.field_map() { 983 let location = self.use_right_hand_side_location.unwrap_or(*location); 984 self.selected_call = Some(SelectedCall { 985 location, 986 field_map, 987 arguments: arguments.iter().map(Self::empty_argument).collect(), 988 kind: SelectedCallKind::Value, 989 fun_type: Some(fun.type_()), 990 enclosing_function: self.current_function, 991 }) 992 } 993 994 // We only want to take into account the innermost function call 995 // containing the current selection so we can't stop at the first call 996 // we find (the outermost one) and have to keep traversing it in case 997 // we're inside a nested call. 998 let previous = self.use_right_hand_side_location; 999 self.use_right_hand_side_location = None; 1000 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis); 1001 self.use_right_hand_side_location = previous; 1002 } 1003 1004 fn visit_typed_pattern_constructor( 1005 &mut self, 1006 location: &'ast SrcSpan, 1007 name_location: &'ast SrcSpan, 1008 name: &'ast EcoString, 1009 arguments: &'ast Vec<CallArg<TypedPattern>>, 1010 module: &'ast Option<(EcoString, SrcSpan)>, 1011 constructor: &'ast Inferred<type_::PatternConstructor>, 1012 spread: &'ast Option<SrcSpan>, 1013 type_: &'ast Arc<Type>, 1014 ) { 1015 let call_range = self.edits.src_span_to_lsp_range(*location); 1016 if !within(self.params.range, call_range) { 1017 return; 1018 } 1019 1020 if let Some(field_map) = constructor.field_map() { 1021 self.selected_call = Some(SelectedCall { 1022 location: *location, 1023 field_map, 1024 arguments: arguments.iter().map(Self::empty_argument).collect(), 1025 kind: SelectedCallKind::Pattern, 1026 fun_type: None, 1027 enclosing_function: self.current_function, 1028 }) 1029 } 1030 1031 ast::visit::visit_typed_pattern_constructor( 1032 self, 1033 location, 1034 name_location, 1035 name, 1036 arguments, 1037 module, 1038 constructor, 1039 spread, 1040 type_, 1041 ); 1042 } 1043} 1044 1045/// Collects variables that are in scope at a given cursor position. 1046struct ScopeVariableCollector { 1047 cursor: u32, 1048 variables: HashMap<EcoString, Arc<Type>>, 1049} 1050 1051impl ScopeVariableCollector { 1052 fn new(cursor: u32) -> Self { 1053 Self { 1054 cursor, 1055 variables: HashMap::new(), 1056 } 1057 } 1058 1059 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> { 1060 for arg in &fun.arguments { 1061 if let Some(name) = arg.get_variable_name() { 1062 _ = self.variables.insert(name.clone(), arg.type_.clone()); 1063 } 1064 } 1065 1066 for statement in &fun.body { 1067 self.visit_typed_statement(statement); 1068 } 1069 1070 self.variables 1071 } 1072} 1073 1074impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector { 1075 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 1076 // We need to consider variables defined only before the cursor 1077 if statement.location().start >= self.cursor { 1078 return; 1079 } 1080 1081 ast::visit::visit_typed_statement(self, statement); 1082 } 1083 1084 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1085 // if cursor is inside the assignment, we only visit the value expression, 1086 // because the variable being defined isn't in scope yet. 1087 if assignment.location.contains(self.cursor) { 1088 self.visit_typed_expr(&assignment.value); 1089 } else { 1090 self.visit_typed_pattern(&assignment.pattern); 1091 } 1092 } 1093 1094 fn visit_typed_expr_fn( 1095 &mut self, 1096 _location: &'ast SrcSpan, 1097 _type_: &'ast Arc<Type>, 1098 _kind: &'ast FunctionLiteralKind, 1099 _arguments: &'ast [TypedArg], 1100 _body: &'ast Vec1<TypedStatement>, 1101 _return_annotation: &'ast Option<ast::TypeAst>, 1102 ) { 1103 // We don't descend into nested functions, as their variables aren't in 1104 // our scope 1105 } 1106 1107 fn visit_typed_expr_block( 1108 &mut self, 1109 location: &'ast SrcSpan, 1110 statements: &'ast [TypedStatement], 1111 ) { 1112 if self.cursor >= location.end { 1113 return; 1114 } 1115 ast::visit::visit_typed_expr_block(self, location, statements); 1116 } 1117 1118 fn visit_typed_pattern_variable( 1119 &mut self, 1120 _location: &'ast SrcSpan, 1121 name: &'ast EcoString, 1122 type_: &'ast Arc<Type>, 1123 _origin: &'ast VariableOrigin, 1124 ) { 1125 if !name.starts_with('_') { 1126 _ = self.variables.insert(name.clone(), type_.clone()); 1127 } 1128 } 1129 1130 fn visit_typed_pattern_assign( 1131 &mut self, 1132 _location: &'ast SrcSpan, 1133 name: &'ast EcoString, 1134 pattern: &'ast Pattern<Arc<Type>>, 1135 ) { 1136 self.visit_typed_pattern(pattern); 1137 if !name.starts_with('_') { 1138 _ = self.variables.insert(name.clone(), pattern.type_().clone()); 1139 } 1140 } 1141} 1142 1143struct MissingImport { 1144 location: SrcSpan, 1145 suggestions: Vec<ImportSuggestion>, 1146} 1147 1148struct ImportSuggestion { 1149 // The name to replace with, if the user made a typo 1150 name: EcoString, 1151 // The optional module to import, if suggesting an importable module 1152 import: Option<EcoString>, 1153} 1154 1155pub fn code_action_import_module( 1156 module: &Module, 1157 line_numbers: &LineNumbers, 1158 params: &CodeActionParams, 1159 error: &Option<Error>, 1160 actions: &mut Vec<CodeAction>, 1161) { 1162 let uri = &params.text_document.uri; 1163 let Some(errors) = type_errors_for_module(error, module) else { 1164 return; 1165 }; 1166 1167 let missing_imports = errors 1168 .into_iter() 1169 .filter_map(|error| { 1170 if let type_::Error::UnknownModule { 1171 location, 1172 suggestions, 1173 .. 1174 } = error 1175 { 1176 suggest_imports(*location, suggestions) 1177 } else { 1178 None 1179 } 1180 }) 1181 .collect_vec(); 1182 1183 if missing_imports.is_empty() { 1184 return; 1185 } 1186 1187 let first_import_pos = position_of_first_definition_if_import(module, line_numbers); 1188 let first_is_import = first_import_pos.is_some(); 1189 let import_location = first_import_pos.unwrap_or_default(); 1190 1191 let after_import_newlines = 1192 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code); 1193 1194 for missing_import in missing_imports { 1195 let range = src_span_to_lsp_range(missing_import.location, line_numbers); 1196 if !overlaps(params.range, range) { 1197 continue; 1198 } 1199 1200 for suggestion in missing_import.suggestions { 1201 let mut edits = vec![TextEdit { 1202 range, 1203 new_text: suggestion.name.to_string(), 1204 }]; 1205 if let Some(import) = &suggestion.import { 1206 edits.push(get_import_edit( 1207 import_location, 1208 import, 1209 &after_import_newlines, 1210 )) 1211 }; 1212 1213 let title = match &suggestion.import { 1214 Some(import) => &format!("Import `{import}`"), 1215 _ => &format!("Did you mean `{}`", suggestion.name), 1216 }; 1217 1218 CodeActionBuilder::new(title) 1219 .kind(CodeActionKind::QuickFix) 1220 .changes(uri.clone(), edits) 1221 .preferred(true) 1222 .push_to(actions); 1223 } 1224 } 1225} 1226 1227fn suggest_imports( 1228 location: SrcSpan, 1229 importable_modules: &[ModuleSuggestion], 1230) -> Option<MissingImport> { 1231 let suggestions = importable_modules 1232 .iter() 1233 .map(|suggestion| { 1234 let imported_name = suggestion.last_name_component(); 1235 match suggestion { 1236 ModuleSuggestion::Importable(name) => ImportSuggestion { 1237 name: imported_name.into(), 1238 import: Some(name.clone()), 1239 }, 1240 ModuleSuggestion::Imported(_) => ImportSuggestion { 1241 name: imported_name.into(), 1242 import: None, 1243 }, 1244 } 1245 }) 1246 .collect_vec(); 1247 1248 if suggestions.is_empty() { 1249 None 1250 } else { 1251 Some(MissingImport { 1252 location, 1253 suggestions, 1254 }) 1255 } 1256} 1257 1258pub fn code_action_add_missing_patterns( 1259 module: &Module, 1260 line_numbers: &LineNumbers, 1261 params: &CodeActionParams, 1262 error: &Option<Error>, 1263 actions: &mut Vec<CodeAction>, 1264) { 1265 let uri = &params.text_document.uri; 1266 let Some(errors) = type_errors_for_module(error, module) else { 1267 return; 1268 }; 1269 let missing_patterns = errors 1270 .iter() 1271 .filter_map(|error| { 1272 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error { 1273 Some((*location, missing)) 1274 } else { 1275 None 1276 } 1277 }) 1278 .collect_vec(); 1279 1280 if missing_patterns.is_empty() { 1281 return; 1282 } 1283 1284 for (location, missing) in missing_patterns { 1285 let mut edits = TextEdits::new(line_numbers); 1286 let range = edits.src_span_to_lsp_range(location); 1287 if !within(params.range, range) { 1288 continue; 1289 } 1290 1291 let Some(Located::Expression { 1292 expression: TypedExpr::Case { 1293 clauses, subjects, .. 1294 }, 1295 .. 1296 }) = module.find_node(location.start) 1297 else { 1298 continue; 1299 }; 1300 1301 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line); 1302 1303 let indent = " ".repeat(indent_size); 1304 1305 // Insert the missing patterns just after the final clause, or just before 1306 // the closing brace if there are no clauses 1307 1308 let insert_at = clauses 1309 .last() 1310 .map(|clause| clause.location.end) 1311 .unwrap_or(location.end - 1); 1312 1313 for pattern in missing { 1314 let new_text = format!("\n{indent} {pattern} -> todo"); 1315 edits.insert(insert_at, new_text); 1316 } 1317 1318 // Add a newline + indent after the last pattern if there are no clauses 1319 // 1320 // This improves the generated code for this case: 1321 // ```gleam 1322 // case True {} 1323 // ``` 1324 // This produces: 1325 // ```gleam 1326 // case True { 1327 // True -> todo 1328 // False -> todo 1329 // } 1330 // ``` 1331 // Instead of: 1332 // ```gleam 1333 // case True { 1334 // True -> todo 1335 // False -> todo} 1336 // ``` 1337 // 1338 if clauses.is_empty() { 1339 let last_subject_location = subjects 1340 .last() 1341 .expect("Case expressions have at least one subject") 1342 .location() 1343 .end; 1344 1345 // Find the opening brace of the case expression 1346 let chars = module.code[last_subject_location as usize..].chars(); 1347 let mut start_brace_location = last_subject_location; 1348 for char in chars { 1349 start_brace_location += 1; 1350 if char == '{' { 1351 break; 1352 } 1353 } 1354 1355 // We remove any blank spaces/lines between the end of the last 1356 // comment inside the case expression and the insertion point. 1357 // If there's no comments we remove all empty space in between the 1358 // two braces 1359 let deletion_start = module 1360 .extra 1361 .last_comment_between(start_brace_location, insert_at) 1362 .map(|src_span| src_span.end) 1363 .unwrap_or(start_brace_location); 1364 1365 edits.delete(SrcSpan::new(deletion_start, insert_at)); 1366 edits.insert(insert_at, format!("\n{indent}")); 1367 } 1368 1369 CodeActionBuilder::new("Add missing patterns") 1370 .kind(CodeActionKind::QuickFix) 1371 .changes(uri.clone(), edits.edits) 1372 .preferred(true) 1373 .push_to(actions); 1374 } 1375} 1376 1377/// Builder for code action to add annotations to an assignment or function 1378/// 1379pub struct AddAnnotations<'a> { 1380 module: &'a Module, 1381 params: &'a CodeActionParams, 1382 edits: TextEdits<'a>, 1383 printer: Printer<'a>, 1384} 1385 1386impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> { 1387 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1388 self.visit_typed_expr(&assignment.value); 1389 1390 // We only offer this code action between `let` and `=`, because 1391 // otherwise it could lead to confusing behaviour if inside a block 1392 // which is part of a let binding. 1393 let pattern_location = assignment.pattern.location(); 1394 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 1395 let code_action_range = self.edits.src_span_to_lsp_range(location); 1396 1397 // Only offer the code action if the cursor is over the statement 1398 if !overlaps(code_action_range, self.params.range) { 1399 return; 1400 } 1401 1402 // We don't need to add an annotation if there already is one 1403 if assignment.annotation.is_some() { 1404 return; 1405 } 1406 1407 // Various expressions such as pipelines and `use` expressions generate assignments 1408 // internally. However, these cannot be annotated and so we don't offer a code action here. 1409 if matches!(assignment.kind, AssignmentKind::Generated) { 1410 return; 1411 } 1412 1413 self.edits.insert( 1414 pattern_location.end, 1415 format!(": {}", self.printer.print_type(&assignment.type_())), 1416 ); 1417 } 1418 1419 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1420 // Since type variable names are local to definitions, any type variables 1421 // in other parts of the module shouldn't affect what we print for the 1422 // annotations of this constant. 1423 self.printer.clear_type_variables(); 1424 1425 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1426 1427 // Only offer the code action if the cursor is over the statement 1428 if !overlaps(code_action_range, self.params.range) { 1429 return; 1430 } 1431 1432 // We don't need to add an annotation if there already is one 1433 if constant.annotation.is_some() { 1434 return; 1435 } 1436 1437 self.edits.insert( 1438 constant.name_location.end, 1439 format!(": {}", self.printer.print_type(&constant.type_)), 1440 ); 1441 } 1442 1443 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 1444 // Since type variable names are local to definitions, any type variables 1445 // in other parts of the module shouldn't affect what we print for the 1446 // annotations of this functions. The only variables which cannot clash 1447 // are ones defined in the signature of this function, which we register 1448 // when we visit the parameters of this function inside `collect_type_variables`. 1449 self.printer.clear_type_variables(); 1450 collect_type_variables(&mut self.printer, fun); 1451 1452 ast::visit::visit_typed_function(self, fun); 1453 1454 let code_action_range = self.edits.src_span_to_lsp_range( 1455 fun.body_start 1456 .map(|body_start| SrcSpan { 1457 start: fun.location.start, 1458 end: body_start, 1459 }) 1460 .unwrap_or(fun.location), 1461 ); 1462 1463 // Only offer the code action if the cursor is over the statement 1464 if !overlaps(code_action_range, self.params.range) { 1465 return; 1466 } 1467 1468 // Annotate each argument separately 1469 for argument in fun.arguments.iter() { 1470 // Don't annotate the argument if it's already annotated 1471 if argument.annotation.is_some() { 1472 continue; 1473 } 1474 1475 self.edits.insert( 1476 argument.location.end, 1477 format!(": {}", self.printer.print_type(&argument.type_)), 1478 ); 1479 } 1480 1481 // Annotate the return type if it isn't already annotated 1482 if fun.return_annotation.is_none() { 1483 self.edits.insert( 1484 fun.location.end, 1485 format!(" -> {}", self.printer.print_type(&fun.return_type)), 1486 ); 1487 } 1488 } 1489 1490 fn visit_typed_expr_fn( 1491 &mut self, 1492 location: &'ast SrcSpan, 1493 type_: &'ast Arc<Type>, 1494 kind: &'ast FunctionLiteralKind, 1495 arguments: &'ast [TypedArg], 1496 body: &'ast Vec1<TypedStatement>, 1497 return_annotation: &'ast Option<ast::TypeAst>, 1498 ) { 1499 ast::visit::visit_typed_expr_fn( 1500 self, 1501 location, 1502 type_, 1503 kind, 1504 arguments, 1505 body, 1506 return_annotation, 1507 ); 1508 1509 // If the function doesn't have a head, we can't annotate it 1510 let location = match kind { 1511 // Function captures don't need any type annotations 1512 FunctionLiteralKind::Capture { .. } => return, 1513 FunctionLiteralKind::Anonymous { head, .. } => head, 1514 FunctionLiteralKind::Use { location } => location, 1515 }; 1516 1517 let code_action_range = self.edits.src_span_to_lsp_range(*location); 1518 1519 // Only offer the code action if the cursor is over the expression 1520 if !overlaps(code_action_range, self.params.range) { 1521 return; 1522 } 1523 1524 // Annotate each argument separately 1525 for argument in arguments.iter() { 1526 // Don't annotate the argument if it's already annotated 1527 if argument.annotation.is_some() { 1528 continue; 1529 } 1530 1531 self.edits.insert( 1532 argument.location.end, 1533 format!(": {}", self.printer.print_type(&argument.type_)), 1534 ); 1535 } 1536 1537 // Annotate the return type if it isn't already annotated, and this is 1538 // an anonymous function. 1539 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) { 1540 let return_type = &type_.return_type().expect("Type must be a function"); 1541 let pretty_type = self.printer.print_type(return_type); 1542 self.edits 1543 .insert(location.end, format!(" -> {pretty_type}")); 1544 } 1545 } 1546} 1547 1548impl<'a> AddAnnotations<'a> { 1549 pub fn new( 1550 module: &'a Module, 1551 line_numbers: &'a LineNumbers, 1552 params: &'a CodeActionParams, 1553 ) -> Self { 1554 Self { 1555 module, 1556 params, 1557 edits: TextEdits::new(line_numbers), 1558 // We need to use the same printer for all the edits because otherwise 1559 // we could get duplicate type variable names. 1560 printer: Printer::new_without_type_variables(&module.ast.names), 1561 } 1562 } 1563 1564 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) { 1565 self.visit_typed_module(&self.module.ast); 1566 1567 let uri = &self.params.text_document.uri; 1568 1569 let title = match self.edits.edits.len() { 1570 // We don't offer a code action if there is no action to perform 1571 0 => return, 1572 1 => "Add type annotation", 1573 _ => "Add type annotations", 1574 }; 1575 1576 CodeActionBuilder::new(title) 1577 .kind(CodeActionKind::Refactor) 1578 .changes(uri.clone(), self.edits.edits) 1579 .preferred(false) 1580 .push_to(actions); 1581 } 1582} 1583 1584/// Code action to add type annotations to all top level definitions 1585/// 1586pub struct AnnotateTopLevelDefinitions<'a> { 1587 module: &'a Module, 1588 params: &'a CodeActionParams, 1589 edits: TextEdits<'a>, 1590 is_hovering_definition_requiring_annotations: bool, 1591} 1592 1593impl<'a> AnnotateTopLevelDefinitions<'a> { 1594 pub fn new( 1595 module: &'a Module, 1596 line_numbers: &'a LineNumbers, 1597 params: &'a CodeActionParams, 1598 ) -> Self { 1599 Self { 1600 module, 1601 params, 1602 edits: TextEdits::new(line_numbers), 1603 is_hovering_definition_requiring_annotations: false, 1604 } 1605 } 1606 1607 pub fn code_actions(mut self) -> Vec<CodeAction> { 1608 self.visit_typed_module(&self.module.ast); 1609 1610 // We only want to trigger the action if we're over one of the definition 1611 // which is lacking some annotations in the module 1612 if !self.is_hovering_definition_requiring_annotations { 1613 return vec![]; 1614 }; 1615 1616 let mut action = Vec::with_capacity(1); 1617 CodeActionBuilder::new("Annotate all top level definitions") 1618 .kind(CodeActionKind::RefactorRewrite) 1619 .changes(self.params.text_document.uri.clone(), self.edits.edits) 1620 .preferred(false) 1621 .push_to(&mut action); 1622 action 1623 } 1624} 1625 1626impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> { 1627 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1628 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1629 1630 // We don't need to add an annotation if there already is one 1631 if constant.annotation.is_some() { 1632 return; 1633 } 1634 1635 // We're hovering definition which needs some annotations 1636 if overlaps(code_action_range, self.params.range) { 1637 self.is_hovering_definition_requiring_annotations = true; 1638 } 1639 1640 self.edits.insert( 1641 constant.name_location.end, 1642 format!( 1643 ": {}", 1644 // Create new printer to ignore type variables from other definitions 1645 Printer::new_without_type_variables(&self.module.ast.names) 1646 .print_type(&constant.type_) 1647 ), 1648 ); 1649 } 1650 1651 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 1652 // Don't annotate already annotated arguments 1653 let arguments_to_annotate = fun 1654 .arguments 1655 .iter() 1656 .filter(|argument| argument.annotation.is_none()) 1657 .collect::<Vec<_>>(); 1658 let needs_return_annotation = fun.return_annotation.is_none(); 1659 1660 if arguments_to_annotate.is_empty() && !needs_return_annotation { 1661 return; 1662 } 1663 1664 let code_action_range = self.edits.src_span_to_lsp_range(fun.location); 1665 if overlaps(code_action_range, self.params.range) { 1666 self.is_hovering_definition_requiring_annotations = true; 1667 } 1668 1669 // Create new printer to ignore type variables from other definitions 1670 let mut printer = Printer::new_without_type_variables(&self.module.ast.names); 1671 collect_type_variables(&mut printer, fun); 1672 1673 // Annotate each argument separately 1674 for argument in arguments_to_annotate { 1675 self.edits.insert( 1676 argument.location.end, 1677 format!(": {}", printer.print_type(&argument.type_)), 1678 ); 1679 } 1680 1681 // Annotate the return type if it isn't already annotated 1682 if needs_return_annotation { 1683 self.edits.insert( 1684 fun.location.end, 1685 format!(" -> {}", printer.print_type(&fun.return_type)), 1686 ); 1687 } 1688 } 1689} 1690 1691struct TypeVariableCollector<'a, 'b> { 1692 printer: &'a mut Printer<'b>, 1693} 1694 1695/// Collect type variables defined within a function and register them for a 1696/// `Printer` 1697fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) { 1698 TypeVariableCollector { printer }.visit_typed_function(function); 1699} 1700 1701impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> { 1702 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) { 1703 // Register this type variable so that we don't duplicate names when 1704 // adding annotations. 1705 self.printer.register_type_variable(name.clone()); 1706 } 1707} 1708 1709pub struct QualifiedConstructor<'a> { 1710 import: &'a Import<EcoString>, 1711 used_name: EcoString, 1712 constructor: EcoString, 1713 layer: ast::Layer, 1714} 1715 1716impl QualifiedConstructor<'_> { 1717 fn constructor_import(&self) -> String { 1718 if self.layer.is_value() { 1719 self.constructor.to_string() 1720 } else { 1721 format!("type {}", self.constructor) 1722 } 1723 } 1724} 1725 1726pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> { 1727 module: &'a Module, 1728 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 1729 params: &'a CodeActionParams, 1730 line_numbers: &'a LineNumbers, 1731 qualified_constructor: Option<QualifiedConstructor<'a>>, 1732} 1733 1734impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> { 1735 fn new( 1736 module: &'a Module, 1737 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 1738 params: &'a CodeActionParams, 1739 line_numbers: &'a LineNumbers, 1740 ) -> Self { 1741 Self { 1742 module, 1743 compiler, 1744 params, 1745 line_numbers, 1746 qualified_constructor: None, 1747 } 1748 } 1749 1750 fn get_module_import( 1751 &self, 1752 module_name: &EcoString, 1753 constructor: &EcoString, 1754 layer: ast::Layer, 1755 ) -> Option<&'a Import<EcoString>> { 1756 let mut matching_import = None; 1757 1758 for import in &self.module.ast.definitions.imports { 1759 if import.used_name().as_deref() == Some(module_name) 1760 && let Some(module) = self.compiler.get_module_interface(&import.module) 1761 { 1762 // If the import is the one we're referring to, we see if the 1763 // referred module exports the type/value we are trying to 1764 // unqualify: we don't want to offer the action indiscriminately if 1765 // it would generate invalid code! 1766 let module_exports_constructor = match layer { 1767 ast::Layer::Value => module.get_importable_value(constructor).is_some(), 1768 ast::Layer::Type => module.get_importable_type(constructor).is_some(), 1769 }; 1770 if module_exports_constructor { 1771 matching_import = Some(import); 1772 } 1773 } else { 1774 // If the import refers to another module we still want to check 1775 // if in its unqualified import list there is a name that's equal 1776 // to the one we're trying to unqualify. In this case we can't 1777 // offer the action as it would generate invalid code. 1778 // 1779 // For example: 1780 // ```gleam 1781 // import wibble.{Some} 1782 // import option 1783 // 1784 // pub fn something() { 1785 // option.Some(1) 1786 // ^^^^ We can't unqualify this because `Some` is already 1787 // imported unqualified from the `wibble` module 1788 // } 1789 // ``` 1790 // 1791 let imported = match layer { 1792 ast::Layer::Value => &import.unqualified_values, 1793 ast::Layer::Type => &import.unqualified_types, 1794 }; 1795 let constructor_already_imported_by_other_module = imported 1796 .iter() 1797 .any(|value| value.used_name() == constructor); 1798 1799 if constructor_already_imported_by_other_module { 1800 return None; 1801 } 1802 } 1803 } 1804 1805 matching_import 1806 } 1807} 1808 1809impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> { 1810 fn visit_type_ast_constructor( 1811 &mut self, 1812 location: &'ast SrcSpan, 1813 name: &'ast TypeAstConstructorName, 1814 arguments: &'ast [ast::TypeAst], 1815 arguments_types: Option<Vec<Arc<Type>>>, 1816 ) { 1817 let range = src_span_to_lsp_range(*location, self.line_numbers); 1818 if within(self.params.range, range) 1819 && let Some(module_alias) = name.module_name() 1820 && let Some(name) = name.name() 1821 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type) 1822 { 1823 self.qualified_constructor = Some(QualifiedConstructor { 1824 import, 1825 used_name: module_alias.clone(), 1826 constructor: name.clone(), 1827 layer: ast::Layer::Type, 1828 }); 1829 } 1830 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 1831 } 1832 1833 fn visit_typed_expr_module_select( 1834 &mut self, 1835 location: &'ast SrcSpan, 1836 field_start: &'ast u32, 1837 type_: &'ast Arc<Type>, 1838 label: &'ast EcoString, 1839 module_name: &'ast EcoString, 1840 module_alias: &'ast EcoString, 1841 constructor: &'ast ModuleValueConstructor, 1842 ) { 1843 // When hovering over a Record Value Constructor, we want to expand the source span to 1844 // include the module name: 1845 // option.Some 1846 // ↑ 1847 // This allows us to offer a code action when hovering over the module name. 1848 let range = src_span_to_lsp_range(*location, self.line_numbers); 1849 if within(self.params.range, range) 1850 && let ModuleValueConstructor::Record { 1851 name: constructor_name, 1852 .. 1853 } = constructor 1854 && let Some(import) = 1855 self.get_module_import(module_alias, constructor_name, ast::Layer::Value) 1856 { 1857 self.qualified_constructor = Some(QualifiedConstructor { 1858 import, 1859 used_name: module_alias.clone(), 1860 constructor: constructor_name.clone(), 1861 layer: ast::Layer::Value, 1862 }); 1863 } 1864 ast::visit::visit_typed_expr_module_select( 1865 self, 1866 location, 1867 field_start, 1868 type_, 1869 label, 1870 module_name, 1871 module_alias, 1872 constructor, 1873 ) 1874 } 1875 1876 fn visit_typed_pattern_constructor( 1877 &mut self, 1878 location: &'ast SrcSpan, 1879 name_location: &'ast SrcSpan, 1880 name: &'ast EcoString, 1881 arguments: &'ast Vec<CallArg<TypedPattern>>, 1882 module: &'ast Option<(EcoString, SrcSpan)>, 1883 constructor: &'ast Inferred<type_::PatternConstructor>, 1884 spread: &'ast Option<SrcSpan>, 1885 type_: &'ast Arc<Type>, 1886 ) { 1887 let range = src_span_to_lsp_range(*location, self.line_numbers); 1888 if within(self.params.range, range) 1889 && let Some((module_alias, _)) = module 1890 && let Inferred::Known(_) = constructor 1891 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 1892 { 1893 self.qualified_constructor = Some(QualifiedConstructor { 1894 import, 1895 used_name: module_alias.clone(), 1896 constructor: name.clone(), 1897 layer: ast::Layer::Value, 1898 }); 1899 } 1900 ast::visit::visit_typed_pattern_constructor( 1901 self, 1902 location, 1903 name_location, 1904 name, 1905 arguments, 1906 module, 1907 constructor, 1908 spread, 1909 type_, 1910 ); 1911 } 1912 1913 fn visit_typed_constant_record( 1914 &mut self, 1915 location: &'ast SrcSpan, 1916 module: &'ast Option<(EcoString, SrcSpan)>, 1917 name: &'ast EcoString, 1918 arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 1919 type_: &'ast Arc<Type>, 1920 field_map: &'ast Inferred<FieldMap>, 1921 record_constructor: &'ast Option<Box<ValueConstructor>>, 1922 ) { 1923 let range = src_span_to_lsp_range(*location, self.line_numbers); 1924 if within(self.params.range, range) 1925 && let Some((module_alias, _)) = module 1926 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 1927 { 1928 self.qualified_constructor = Some(QualifiedConstructor { 1929 import, 1930 used_name: module_alias.clone(), 1931 constructor: name.clone(), 1932 layer: ast::Layer::Value, 1933 }); 1934 } 1935 ast::visit::visit_typed_constant_record( 1936 self, 1937 location, 1938 module, 1939 name, 1940 arguments, 1941 type_, 1942 field_map, 1943 record_constructor, 1944 ); 1945 } 1946 1947 fn visit_typed_constant_var( 1948 &mut self, 1949 location: &'ast SrcSpan, 1950 module: &'ast Option<(EcoString, SrcSpan)>, 1951 name: &'ast EcoString, 1952 constructor: &'ast Option<Box<ValueConstructor>>, 1953 type_: &'ast Arc<Type>, 1954 ) { 1955 let range = src_span_to_lsp_range(*location, self.line_numbers); 1956 if within(self.params.range, range) 1957 && let Some((module_alias, _)) = module 1958 && let Some(constructor) = constructor 1959 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant 1960 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 1961 { 1962 self.qualified_constructor = Some(QualifiedConstructor { 1963 import, 1964 used_name: module_alias.clone(), 1965 constructor: name.clone(), 1966 layer: ast::Layer::Value, 1967 }); 1968 } 1969 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 1970 } 1971} 1972 1973pub struct QualifiedToUnqualifiedImportSecondPass<'a> { 1974 module: &'a Module, 1975 params: &'a CodeActionParams, 1976 edits: TextEdits<'a>, 1977 qualified_constructor: QualifiedConstructor<'a>, 1978} 1979 1980impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> { 1981 pub fn new( 1982 module: &'a Module, 1983 params: &'a CodeActionParams, 1984 line_numbers: &'a LineNumbers, 1985 qualified_constructor: QualifiedConstructor<'a>, 1986 ) -> Self { 1987 Self { 1988 module, 1989 params, 1990 edits: TextEdits::new(line_numbers), 1991 qualified_constructor, 1992 } 1993 } 1994 1995 pub fn code_actions(mut self) -> Vec<CodeAction> { 1996 self.visit_typed_module(&self.module.ast); 1997 if self.edits.edits.is_empty() { 1998 return vec![]; 1999 } 2000 self.edit_import(); 2001 let mut action = Vec::with_capacity(1); 2002 CodeActionBuilder::new(format!( 2003 "Unqualify {}.{}", 2004 self.qualified_constructor.used_name, self.qualified_constructor.constructor 2005 )) 2006 .kind(CodeActionKind::Refactor) 2007 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2008 .preferred(false) 2009 .push_to(&mut action); 2010 action 2011 } 2012 2013 fn remove_module_qualifier(&mut self, location: SrcSpan) { 2014 self.edits.delete(SrcSpan { 2015 start: location.start, 2016 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus . 2017 }) 2018 } 2019 2020 fn edit_import(&mut self) { 2021 let QualifiedConstructor { 2022 constructor, 2023 layer, 2024 import, 2025 .. 2026 } = &self.qualified_constructor; 2027 let is_imported = if layer.is_value() { 2028 import 2029 .unqualified_values 2030 .iter() 2031 .any(|value| value.used_name() == constructor) 2032 } else { 2033 import 2034 .unqualified_types 2035 .iter() 2036 .any(|type_| type_.used_name() == constructor) 2037 }; 2038 if is_imported { 2039 return; 2040 } 2041 let (insert_pos, new_text) = edits::insert_unqualified_import( 2042 import, 2043 &self.module.code, 2044 self.qualified_constructor.constructor_import(), 2045 ); 2046 let span = SrcSpan::new(insert_pos, insert_pos); 2047 self.edits.replace(span, new_text); 2048 } 2049} 2050 2051impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> { 2052 fn visit_type_ast_constructor( 2053 &mut self, 2054 location: &'ast SrcSpan, 2055 name: &'ast TypeAstConstructorName, 2056 arguments: &'ast [ast::TypeAst], 2057 arguments_types: Option<Vec<Arc<Type>>>, 2058 ) { 2059 if let Some(module_name) = name.module_name() 2060 && let Some(name) = name.name() 2061 { 2062 let QualifiedConstructor { 2063 used_name, 2064 constructor, 2065 layer, 2066 .. 2067 } = &self.qualified_constructor; 2068 2069 if !layer.is_value() && used_name == module_name && name == constructor { 2070 self.remove_module_qualifier(*location); 2071 } 2072 } 2073 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2074 } 2075 2076 fn visit_typed_expr_module_select( 2077 &mut self, 2078 location: &'ast SrcSpan, 2079 field_start: &'ast u32, 2080 type_: &'ast Arc<Type>, 2081 label: &'ast EcoString, 2082 module_name: &'ast EcoString, 2083 module_alias: &'ast EcoString, 2084 constructor: &'ast ModuleValueConstructor, 2085 ) { 2086 if let ModuleValueConstructor::Record { name, .. } = constructor { 2087 let QualifiedConstructor { 2088 used_name, 2089 constructor, 2090 layer, 2091 .. 2092 } = &self.qualified_constructor; 2093 2094 if layer.is_value() && used_name == module_alias && name == constructor { 2095 self.remove_module_qualifier(*location); 2096 } 2097 } 2098 ast::visit::visit_typed_expr_module_select( 2099 self, 2100 location, 2101 field_start, 2102 type_, 2103 label, 2104 module_name, 2105 module_alias, 2106 constructor, 2107 ) 2108 } 2109 2110 fn visit_typed_pattern_constructor( 2111 &mut self, 2112 location: &'ast SrcSpan, 2113 name_location: &'ast SrcSpan, 2114 name: &'ast EcoString, 2115 arguments: &'ast Vec<CallArg<TypedPattern>>, 2116 module: &'ast Option<(EcoString, SrcSpan)>, 2117 constructor: &'ast Inferred<type_::PatternConstructor>, 2118 spread: &'ast Option<SrcSpan>, 2119 type_: &'ast Arc<Type>, 2120 ) { 2121 if let Some((module_alias, _)) = module 2122 && let Inferred::Known(_) = constructor 2123 { 2124 let QualifiedConstructor { 2125 used_name, 2126 constructor, 2127 layer, 2128 .. 2129 } = &self.qualified_constructor; 2130 2131 if layer.is_value() && used_name == module_alias && name == constructor { 2132 self.remove_module_qualifier(*location); 2133 } 2134 } 2135 ast::visit::visit_typed_pattern_constructor( 2136 self, 2137 location, 2138 name_location, 2139 name, 2140 arguments, 2141 module, 2142 constructor, 2143 spread, 2144 type_, 2145 ); 2146 } 2147 2148 fn visit_typed_constant_record( 2149 &mut self, 2150 location: &'ast SrcSpan, 2151 module: &'ast Option<(EcoString, SrcSpan)>, 2152 name: &'ast EcoString, 2153 arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 2154 type_: &'ast Arc<Type>, 2155 field_map: &'ast Inferred<FieldMap>, 2156 record_constructor: &'ast Option<Box<ValueConstructor>>, 2157 ) { 2158 if let Some((module_alias, _)) = module { 2159 let QualifiedConstructor { 2160 used_name, 2161 constructor, 2162 layer, 2163 .. 2164 } = &self.qualified_constructor; 2165 2166 if layer.is_value() && used_name == module_alias && name == constructor { 2167 self.remove_module_qualifier(*location); 2168 } 2169 } 2170 ast::visit::visit_typed_constant_record( 2171 self, 2172 location, 2173 module, 2174 name, 2175 arguments, 2176 type_, 2177 field_map, 2178 record_constructor, 2179 ); 2180 } 2181 2182 fn visit_typed_constant_var( 2183 &mut self, 2184 location: &'ast SrcSpan, 2185 module: &'ast Option<(EcoString, SrcSpan)>, 2186 name: &'ast EcoString, 2187 constructor: &'ast Option<Box<ValueConstructor>>, 2188 type_: &'ast Arc<Type>, 2189 ) { 2190 if let Some((module_alias, _)) = module { 2191 let QualifiedConstructor { 2192 used_name, 2193 constructor: wanted_constructor, 2194 layer, 2195 .. 2196 } = &self.qualified_constructor; 2197 2198 if layer.is_value() && used_name == module_alias && name == wanted_constructor { 2199 self.remove_module_qualifier(*location); 2200 } 2201 } 2202 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2203 } 2204} 2205 2206pub fn code_action_convert_qualified_constructor_to_unqualified<IO>( 2207 module: &Module, 2208 compiler: &LspProjectCompiler<FileSystemProxy<IO>>, 2209 line_numbers: &LineNumbers, 2210 params: &CodeActionParams, 2211 actions: &mut Vec<CodeAction>, 2212) { 2213 let mut first_pass = 2214 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers); 2215 first_pass.visit_typed_module(&module.ast); 2216 let Some(qualified_constructor) = first_pass.qualified_constructor else { 2217 return; 2218 }; 2219 let second_pass = QualifiedToUnqualifiedImportSecondPass::new( 2220 module, 2221 params, 2222 line_numbers, 2223 qualified_constructor, 2224 ); 2225 let new_actions = second_pass.code_actions(); 2226 actions.extend(new_actions); 2227} 2228 2229struct UnqualifiedConstructor<'a> { 2230 module_name: EcoString, 2231 constructor: &'a ast::UnqualifiedImport, 2232 layer: ast::Layer, 2233} 2234 2235struct UnqualifiedToQualifiedImportFirstPass<'a> { 2236 module: &'a Module, 2237 params: &'a CodeActionParams, 2238 line_numbers: &'a LineNumbers, 2239 unqualified_constructor: Option<UnqualifiedConstructor<'a>>, 2240} 2241 2242impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> { 2243 fn new( 2244 module: &'a Module, 2245 params: &'a CodeActionParams, 2246 line_numbers: &'a LineNumbers, 2247 ) -> Self { 2248 Self { 2249 module, 2250 params, 2251 line_numbers, 2252 unqualified_constructor: None, 2253 } 2254 } 2255 2256 fn get_module_import_from_value_constructor( 2257 &mut self, 2258 module_name: &EcoString, 2259 constructor_name: &EcoString, 2260 ) { 2261 self.unqualified_constructor = self 2262 .module 2263 .ast 2264 .definitions 2265 .imports 2266 .iter() 2267 .filter(|import| import.module == *module_name) 2268 .find_map(|import| { 2269 import 2270 .unqualified_values 2271 .iter() 2272 .find(|value| value.used_name() == constructor_name) 2273 .and_then(|value| { 2274 Some(UnqualifiedConstructor { 2275 constructor: value, 2276 module_name: import.used_name()?, 2277 layer: ast::Layer::Value, 2278 }) 2279 }) 2280 }) 2281 } 2282 2283 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) { 2284 self.unqualified_constructor = 2285 self.module 2286 .ast 2287 .definitions 2288 .imports 2289 .iter() 2290 .find_map(|import| { 2291 if let Some(ty) = import 2292 .unqualified_types 2293 .iter() 2294 .find(|ty| ty.used_name() == constructor_name) 2295 { 2296 return Some(UnqualifiedConstructor { 2297 constructor: ty, 2298 module_name: import.used_name()?, 2299 layer: ast::Layer::Type, 2300 }); 2301 } 2302 None 2303 }) 2304 } 2305} 2306 2307impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> { 2308 fn visit_type_ast_constructor( 2309 &mut self, 2310 location: &'ast SrcSpan, 2311 name: &'ast TypeAstConstructorName, 2312 arguments: &'ast [ast::TypeAst], 2313 arguments_types: Option<Vec<Arc<Type>>>, 2314 ) { 2315 if !name.is_qualified() 2316 && let Some(name) = name.name() 2317 && within( 2318 self.params.range, 2319 src_span_to_lsp_range(*location, self.line_numbers), 2320 ) 2321 { 2322 self.get_module_import_from_type_constructor(name); 2323 } 2324 2325 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2326 } 2327 2328 fn visit_typed_expr_var( 2329 &mut self, 2330 location: &'ast SrcSpan, 2331 constructor: &'ast ValueConstructor, 2332 name: &'ast EcoString, 2333 ) { 2334 let range = src_span_to_lsp_range(*location, self.line_numbers); 2335 if within(self.params.range, range) 2336 && let Some(module_name) = match &constructor.variant { 2337 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2338 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2339 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2340 2341 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2342 } 2343 { 2344 self.get_module_import_from_value_constructor(module_name, name); 2345 } 2346 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2347 } 2348 2349 fn visit_typed_pattern_constructor( 2350 &mut self, 2351 location: &'ast SrcSpan, 2352 name_location: &'ast SrcSpan, 2353 name: &'ast EcoString, 2354 arguments: &'ast Vec<CallArg<TypedPattern>>, 2355 module: &'ast Option<(EcoString, SrcSpan)>, 2356 constructor: &'ast Inferred<type_::PatternConstructor>, 2357 spread: &'ast Option<SrcSpan>, 2358 type_: &'ast Arc<Type>, 2359 ) { 2360 if module.is_none() 2361 && within( 2362 self.params.range, 2363 src_span_to_lsp_range(*location, self.line_numbers), 2364 ) 2365 && let Inferred::Known(constructor) = constructor 2366 { 2367 self.get_module_import_from_value_constructor(&constructor.module, name); 2368 } 2369 2370 ast::visit::visit_typed_pattern_constructor( 2371 self, 2372 location, 2373 name_location, 2374 name, 2375 arguments, 2376 module, 2377 constructor, 2378 spread, 2379 type_, 2380 ); 2381 } 2382 2383 fn visit_typed_constant_record( 2384 &mut self, 2385 location: &'ast SrcSpan, 2386 module: &'ast Option<(EcoString, SrcSpan)>, 2387 name: &'ast EcoString, 2388 arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 2389 type_: &'ast Arc<Type>, 2390 field_map: &'ast Inferred<FieldMap>, 2391 record_constructor: &'ast Option<Box<ValueConstructor>>, 2392 ) { 2393 if module.is_none() 2394 && within( 2395 self.params.range, 2396 src_span_to_lsp_range(*location, self.line_numbers), 2397 ) 2398 && let Some(record_constructor) = record_constructor 2399 && let Some(module_name) = match &record_constructor.variant { 2400 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2401 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2402 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2403 2404 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2405 } 2406 { 2407 self.get_module_import_from_value_constructor(module_name, name); 2408 } 2409 ast::visit::visit_typed_constant_record( 2410 self, 2411 location, 2412 module, 2413 name, 2414 arguments, 2415 type_, 2416 field_map, 2417 record_constructor, 2418 ); 2419 } 2420 2421 fn visit_typed_constant_var( 2422 &mut self, 2423 location: &'ast SrcSpan, 2424 module: &'ast Option<(EcoString, SrcSpan)>, 2425 name: &'ast EcoString, 2426 constructor: &'ast Option<Box<ValueConstructor>>, 2427 type_: &'ast Arc<Type>, 2428 ) { 2429 if module.is_none() 2430 && within( 2431 self.params.range, 2432 src_span_to_lsp_range(*location, self.line_numbers), 2433 ) 2434 && let Some(constructor) = constructor 2435 && let Some(module_name) = match &constructor.variant { 2436 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2437 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2438 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2439 2440 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2441 } 2442 { 2443 self.get_module_import_from_value_constructor(module_name, name); 2444 } 2445 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2446 } 2447} 2448 2449struct UnqualifiedToQualifiedImportSecondPass<'a> { 2450 module: &'a Module, 2451 params: &'a CodeActionParams, 2452 edits: TextEdits<'a>, 2453 unqualified_constructor: UnqualifiedConstructor<'a>, 2454} 2455 2456impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> { 2457 pub fn new( 2458 module: &'a Module, 2459 params: &'a CodeActionParams, 2460 line_numbers: &'a LineNumbers, 2461 unqualified_constructor: UnqualifiedConstructor<'a>, 2462 ) -> Self { 2463 Self { 2464 module, 2465 params, 2466 edits: TextEdits::new(line_numbers), 2467 unqualified_constructor, 2468 } 2469 } 2470 2471 fn add_module_qualifier(&mut self, location: SrcSpan) { 2472 let src_span = SrcSpan::new( 2473 location.start, 2474 location.start + self.unqualified_constructor.constructor.used_name().len() as u32, 2475 ); 2476 2477 self.edits.replace( 2478 src_span, 2479 format!( 2480 "{}.{}", 2481 self.unqualified_constructor.module_name, 2482 self.unqualified_constructor.constructor.name 2483 ), 2484 ); 2485 } 2486 2487 pub fn code_actions(mut self) -> Vec<CodeAction> { 2488 self.visit_typed_module(&self.module.ast); 2489 if self.edits.edits.is_empty() { 2490 return vec![]; 2491 } 2492 self.edit_import(); 2493 let mut action = Vec::with_capacity(1); 2494 let UnqualifiedConstructor { 2495 module_name, 2496 constructor, 2497 .. 2498 } = self.unqualified_constructor; 2499 CodeActionBuilder::new(format!( 2500 "Qualify {} as {}.{}", 2501 constructor.used_name(), 2502 module_name, 2503 constructor.name, 2504 )) 2505 .kind(CodeActionKind::Refactor) 2506 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2507 .preferred(false) 2508 .push_to(&mut action); 2509 action 2510 } 2511 2512 fn edit_import(&mut self) { 2513 let UnqualifiedConstructor { 2514 constructor: 2515 ast::UnqualifiedImport { 2516 location: constructor_import_span, 2517 .. 2518 }, 2519 .. 2520 } = self.unqualified_constructor; 2521 2522 let mut last_char_pos = constructor_import_span.end as usize; 2523 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2524 last_char_pos += 1; 2525 } 2526 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") { 2527 last_char_pos += 1; 2528 } 2529 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2530 last_char_pos += 1; 2531 } 2532 2533 self.edits.delete(SrcSpan::new( 2534 constructor_import_span.start, 2535 last_char_pos as u32, 2536 )); 2537 } 2538} 2539 2540impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> { 2541 fn visit_type_ast_constructor( 2542 &mut self, 2543 location: &'ast SrcSpan, 2544 name: &'ast TypeAstConstructorName, 2545 arguments: &'ast [ast::TypeAst], 2546 arguments_types: Option<Vec<Arc<Type>>>, 2547 ) { 2548 if !name.is_qualified() 2549 && let Some(name) = name.name() 2550 { 2551 let UnqualifiedConstructor { 2552 constructor, layer, .. 2553 } = &self.unqualified_constructor; 2554 if !layer.is_value() && constructor.used_name() == name { 2555 self.add_module_qualifier(*location); 2556 } 2557 } 2558 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2559 } 2560 2561 fn visit_typed_expr_var( 2562 &mut self, 2563 location: &'ast SrcSpan, 2564 constructor: &'ast ValueConstructor, 2565 name: &'ast EcoString, 2566 ) { 2567 let UnqualifiedConstructor { 2568 constructor: wanted_constructor, 2569 layer, 2570 .. 2571 } = &self.unqualified_constructor; 2572 2573 if layer.is_value() 2574 && wanted_constructor.used_name() == name 2575 && !constructor.is_local_variable() 2576 { 2577 self.add_module_qualifier(*location); 2578 } 2579 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2580 } 2581 2582 fn visit_typed_pattern_constructor( 2583 &mut self, 2584 location: &'ast SrcSpan, 2585 name_location: &'ast SrcSpan, 2586 name: &'ast EcoString, 2587 arguments: &'ast Vec<CallArg<TypedPattern>>, 2588 module: &'ast Option<(EcoString, SrcSpan)>, 2589 constructor: &'ast Inferred<type_::PatternConstructor>, 2590 spread: &'ast Option<SrcSpan>, 2591 type_: &'ast Arc<Type>, 2592 ) { 2593 if module.is_none() { 2594 let UnqualifiedConstructor { 2595 constructor: wanted_constructor, 2596 layer, 2597 .. 2598 } = &self.unqualified_constructor; 2599 if layer.is_value() && wanted_constructor.used_name() == name { 2600 self.add_module_qualifier(*location); 2601 } 2602 } 2603 ast::visit::visit_typed_pattern_constructor( 2604 self, 2605 location, 2606 name_location, 2607 name, 2608 arguments, 2609 module, 2610 constructor, 2611 spread, 2612 type_, 2613 ); 2614 } 2615 2616 fn visit_typed_constant_record( 2617 &mut self, 2618 location: &'ast SrcSpan, 2619 module: &'ast Option<(EcoString, SrcSpan)>, 2620 name: &'ast EcoString, 2621 arguments: &'ast Vec<CallArg<ast::TypedConstant>>, 2622 type_: &'ast Arc<Type>, 2623 field_map: &'ast Inferred<FieldMap>, 2624 record_constructor: &'ast Option<Box<ValueConstructor>>, 2625 ) { 2626 if module.is_none() { 2627 let UnqualifiedConstructor { 2628 constructor: wanted_constructor, 2629 layer, 2630 .. 2631 } = &self.unqualified_constructor; 2632 if layer.is_value() && wanted_constructor.used_name() == name { 2633 self.add_module_qualifier(*location); 2634 } 2635 } 2636 ast::visit::visit_typed_constant_record( 2637 self, 2638 location, 2639 module, 2640 name, 2641 arguments, 2642 type_, 2643 field_map, 2644 record_constructor, 2645 ); 2646 } 2647 2648 fn visit_typed_constant_var( 2649 &mut self, 2650 location: &'ast SrcSpan, 2651 module: &'ast Option<(EcoString, SrcSpan)>, 2652 name: &'ast EcoString, 2653 constructor: &'ast Option<Box<ValueConstructor>>, 2654 type_: &'ast Arc<Type>, 2655 ) { 2656 if module.is_none() { 2657 let UnqualifiedConstructor { 2658 constructor: wanted_constructor, 2659 layer, 2660 .. 2661 } = &self.unqualified_constructor; 2662 if layer.is_value() && wanted_constructor.used_name() == name { 2663 self.add_module_qualifier(*location); 2664 } 2665 } 2666 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2667 } 2668} 2669 2670pub fn code_action_convert_unqualified_constructor_to_qualified( 2671 module: &Module, 2672 line_numbers: &LineNumbers, 2673 params: &CodeActionParams, 2674 actions: &mut Vec<CodeAction>, 2675) { 2676 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers); 2677 first_pass.visit_typed_module(&module.ast); 2678 let Some(unqualified_constructor) = first_pass.unqualified_constructor else { 2679 return; 2680 }; 2681 let second_pass = UnqualifiedToQualifiedImportSecondPass::new( 2682 module, 2683 params, 2684 line_numbers, 2685 unqualified_constructor, 2686 ); 2687 let new_actions = second_pass.code_actions(); 2688 actions.extend(new_actions); 2689} 2690 2691/// Builder for code action to apply the convert from use action, turning a use 2692/// expression into a regular function call. 2693/// 2694pub struct ConvertFromUse<'a> { 2695 module: &'a Module, 2696 params: &'a CodeActionParams, 2697 edits: TextEdits<'a>, 2698 selected_use: Option<&'a TypedUse>, 2699} 2700 2701impl<'a> ConvertFromUse<'a> { 2702 pub fn new( 2703 module: &'a Module, 2704 line_numbers: &'a LineNumbers, 2705 params: &'a CodeActionParams, 2706 ) -> Self { 2707 Self { 2708 module, 2709 params, 2710 edits: TextEdits::new(line_numbers), 2711 selected_use: None, 2712 } 2713 } 2714 2715 pub fn code_actions(mut self) -> Vec<CodeAction> { 2716 self.visit_typed_module(&self.module.ast); 2717 2718 let Some(use_) = self.selected_use else { 2719 return vec![]; 2720 }; 2721 2722 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else { 2723 return vec![]; 2724 }; 2725 2726 // If the use callback we're desugaring is using labels, that means we 2727 // have to add the last argument's label when writing the callback; 2728 // otherwise, it would result in invalid code. 2729 // 2730 // use acc, item <- list.fold(over: list, from: 1) 2731 // todo 2732 // 2733 // Needs to be rewritten as: 2734 // 2735 // list.fold(over: list, from: 1, with: fn(acc, item) { ... }) 2736 // ^^^^^ We cannot forget to add this label back! 2737 // 2738 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) { 2739 fun.field_map() 2740 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned()) 2741 .map(|label| eco_format!("{label}: ")) 2742 .unwrap_or(EcoString::from("")) 2743 } else { 2744 EcoString::from("") 2745 }; 2746 2747 // The use callback is not necessarily the last argument. If you have 2748 // the following function: `wibble(a a, b b) { todo }` 2749 // And use it like this: `use <- wibble(b: 1)`, the first argument `a` 2750 // is going to be the use callback, not the last one! 2751 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback()); 2752 let Some(CallArg { 2753 implicit: Some(ImplicitCallArgOrigin::Use), 2754 value: TypedExpr::Fn { body, type_, .. }, 2755 .. 2756 }) = use_callback 2757 else { 2758 return vec![]; 2759 }; 2760 2761 // If there's arguments on the left hand side of the function we extract 2762 // those so we can paste them back as the anonymous function arguments. 2763 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) { 2764 let assignments_range = 2765 use_.assignments_location.start as usize..use_.assignments_location.end as usize; 2766 self.module 2767 .code 2768 .get(assignments_range) 2769 .expect("use assignments") 2770 } else { 2771 "" 2772 }; 2773 2774 // We first delete everything on the left hand side of use and the use 2775 // arrow. 2776 self.edits.delete(SrcSpan { 2777 start: use_.location.start, 2778 end: use_.right_hand_side_location.start, 2779 }); 2780 2781 let use_line_end = use_.right_hand_side_location.end; 2782 let use_rhs_function_has_some_explicit_arguments = arguments 2783 .iter() 2784 .filter(|argument| !argument.is_use_implicit_callback()) 2785 .peekable() 2786 .peek() 2787 .is_some(); 2788 2789 let use_rhs_function_ends_with_closed_parentheses = self 2790 .module 2791 .code 2792 .get(use_line_end as usize - 1..use_line_end as usize) 2793 == Some(")"); 2794 2795 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit()); 2796 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end); 2797 2798 // This is the piece of code between the end of the last argument and 2799 // the end of the use_expression: 2800 // 2801 // use <- wibble(a, b, ) 2802 // ^^^^^ This piece right here, from `,` included 2803 // up to `)` excluded. 2804 // 2805 let text_after_last_argument = self 2806 .module 2807 .code 2808 .get(last_arg_end as usize..use_line_end as usize - 1); 2809 let use_rhs_has_comma_after_last_argument = 2810 text_after_last_argument.is_some_and(|code| code.contains(',')); 2811 let needs_space_before_callback = 2812 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' ')); 2813 2814 if use_rhs_function_ends_with_closed_parentheses { 2815 // If the function on the right hand side of use ends with a closed 2816 // parentheses then we have to remove it and add it later at the end 2817 // of the anonymous function we're inserting. 2818 // 2819 // use <- wibble() 2820 // ^ To add the fn() we need to first remove this 2821 // 2822 // So here we write over the last closed parentheses to remove it. 2823 let callback_start = format!("{callback_label}fn({assignments}) {{"); 2824 self.edits.replace( 2825 SrcSpan { 2826 start: use_line_end - 1, 2827 end: use_line_end, 2828 }, 2829 // If the function on the rhs of use has other orguments besides 2830 // the implicit fn expression then we need to put a comma after 2831 // the last argument. 2832 if use_rhs_function_has_some_explicit_arguments 2833 && !use_rhs_has_comma_after_last_argument 2834 { 2835 format!(", {callback_start}") 2836 } else if needs_space_before_callback { 2837 format!(" {callback_start}") 2838 } else { 2839 callback_start.to_string() 2840 }, 2841 ) 2842 } else { 2843 // On the other hand, if the function on the right hand side doesn't 2844 // end with a closed parenthese then we have to manually add it. 2845 // 2846 // use <- wibble 2847 // ^ No parentheses 2848 // 2849 self.edits 2850 .insert(use_line_end, format!("(fn({assignments}) {{")) 2851 }; 2852 2853 // Then we have to increase indentation for all the lines of the use 2854 // body. 2855 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location()); 2856 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location()); 2857 2858 for line in first_fn_expression_range.start.line..=use_body_range.end.line { 2859 self.edits.edits.push(TextEdit { 2860 range: Range { 2861 start: Position { line, character: 0 }, 2862 end: Position { line, character: 0 }, 2863 }, 2864 new_text: " ".to_string(), 2865 }) 2866 } 2867 2868 let final_line_indentation = " ".repeat(use_body_range.start.character as usize); 2869 self.edits.insert( 2870 use_.call.location().end, 2871 format!("\n{final_line_indentation}}})"), 2872 ); 2873 2874 let mut action = Vec::with_capacity(1); 2875 CodeActionBuilder::new("Convert from `use`") 2876 .kind(CodeActionKind::RefactorRewrite) 2877 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2878 .preferred(false) 2879 .push_to(&mut action); 2880 action 2881 } 2882} 2883 2884impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> { 2885 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 2886 let use_range = self.edits.src_span_to_lsp_range(use_.location); 2887 2888 // If the use expression is using patterns that are not just variable 2889 // assignments then we can't automatically rewrite it as it would result 2890 // in a syntax error as we can't pattern match in an anonymous function 2891 // head. 2892 // At the same time we can't safely add bindings inside the anonymous 2893 // function body by picking placeholder names as we'd risk shadowing 2894 // variables coming from the outer scope. 2895 // So we just skip those use expressions we can't safely rewrite! 2896 if within(self.params.range, use_range) 2897 && use_ 2898 .assignments 2899 .iter() 2900 .all(|assignment| assignment.pattern.is_variable()) 2901 { 2902 self.selected_use = Some(use_); 2903 } 2904 2905 // We still want to visit the use expression so that we always end up 2906 // picking the innermost, most relevant use under the cursor. 2907 self.visit_typed_expr(&use_.call); 2908 } 2909} 2910 2911/// Builder for code action to apply the convert to use action. 2912/// 2913pub struct ConvertToUse<'a> { 2914 module: &'a Module, 2915 params: &'a CodeActionParams, 2916 edits: TextEdits<'a>, 2917 selected_call: Option<CallLocations>, 2918} 2919 2920/// All the locations we'll need to transform a function call into a use 2921/// expression. 2922/// 2923struct CallLocations { 2924 call_span: SrcSpan, 2925 called_function_span: SrcSpan, 2926 callback_arguments_span: Option<SrcSpan>, 2927 arg_before_callback_span: Option<SrcSpan>, 2928 callback_body_span: SrcSpan, 2929} 2930 2931impl<'a> ConvertToUse<'a> { 2932 pub fn new( 2933 module: &'a Module, 2934 line_numbers: &'a LineNumbers, 2935 params: &'a CodeActionParams, 2936 ) -> Self { 2937 Self { 2938 module, 2939 params, 2940 edits: TextEdits::new(line_numbers), 2941 selected_call: None, 2942 } 2943 } 2944 2945 pub fn code_actions(mut self) -> Vec<CodeAction> { 2946 self.visit_typed_module(&self.module.ast); 2947 2948 let Some(CallLocations { 2949 call_span, 2950 called_function_span, 2951 callback_arguments_span, 2952 arg_before_callback_span, 2953 callback_body_span, 2954 }) = self.selected_call 2955 else { 2956 return vec![]; 2957 }; 2958 2959 // This is the nesting level of the `use` keyword we've inserted, we 2960 // want to move the entire body of the anonymous function to this level. 2961 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character; 2962 let indentation = " ".repeat(use_nesting_level as usize); 2963 2964 // First we move the callback arguments to the left hand side of the 2965 // call and add the `use` keyword. 2966 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span { 2967 let arguments_start = arguments_location.start as usize; 2968 let arguments_end = arguments_location.end as usize; 2969 let arguments_text = self 2970 .module 2971 .code 2972 .get(arguments_start..arguments_end) 2973 .expect("fn args"); 2974 format!("use {arguments_text} <- ") 2975 } else { 2976 "use <- ".into() 2977 }; 2978 2979 self.edits.insert(call_span.start, left_hand_side_text); 2980 2981 match arg_before_callback_span { 2982 // If the function call has no other arguments besides the callback then 2983 // we just have to remove the `fn(...) {` part. 2984 // 2985 // wibble(fn(...) { ... }) 2986 // ^^^^^^^^^^ This goes from the end of the called function 2987 // To the start of the first thing in the anonymous 2988 // function's body. 2989 // 2990 None => self.edits.replace( 2991 SrcSpan::new(called_function_span.end, callback_body_span.start), 2992 format!("\n{indentation}"), 2993 ), 2994 // If it has other arguments we'll have to remove those and add a closed 2995 // parentheses too: 2996 // 2997 // wibble(1, 2, fn(...) { ... }) 2998 // ^^^^^^^^^^^ We have to replace this with a `)`, it 2999 // goes from the end of the second-to-last 3000 // argument to the start of the first thing 3001 // in the anonymous function's body. 3002 // 3003 Some(arg_before_callback) => self.edits.replace( 3004 SrcSpan::new(arg_before_callback.end, callback_body_span.start), 3005 format!(")\n{indentation}"), 3006 ), 3007 }; 3008 3009 // Then we have to remove two spaces of indentation from each line of 3010 // the callback function's body. 3011 let body_range = self.edits.src_span_to_lsp_range(callback_body_span); 3012 for line in body_range.start.line + 1..=body_range.end.line { 3013 self.edits.delete_range(Range::new( 3014 Position { line, character: 0 }, 3015 Position { line, character: 2 }, 3016 )) 3017 } 3018 3019 // Then we have to remove the anonymous fn closing `}` and the call's 3020 // closing `)`. 3021 self.edits 3022 .delete(SrcSpan::new(callback_body_span.end, call_span.end)); 3023 3024 let mut action = Vec::with_capacity(1); 3025 CodeActionBuilder::new("Convert to `use`") 3026 .kind(CodeActionKind::RefactorRewrite) 3027 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3028 .preferred(false) 3029 .push_to(&mut action); 3030 action 3031 } 3032} 3033 3034impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> { 3035 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 3036 // The cursor has to be inside the last statement of the function to 3037 // offer the code action. 3038 if let Some(last) = &fun.body.last() 3039 && within( 3040 self.params.range, 3041 self.edits.src_span_to_lsp_range(last.location()), 3042 ) 3043 && let Some(call_data) = turn_statement_into_use(last) 3044 { 3045 self.selected_call = Some(call_data); 3046 } 3047 3048 ast::visit::visit_typed_function(self, fun) 3049 } 3050 3051 fn visit_typed_expr_fn( 3052 &mut self, 3053 location: &'ast SrcSpan, 3054 type_: &'ast Arc<Type>, 3055 kind: &'ast FunctionLiteralKind, 3056 arguments: &'ast [TypedArg], 3057 body: &'ast Vec1<TypedStatement>, 3058 return_annotation: &'ast Option<ast::TypeAst>, 3059 ) { 3060 // The cursor has to be inside the last statement of the body to 3061 // offer the code action. 3062 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location()); 3063 if within(self.params.range, last_statement_range) 3064 && let Some(call_data) = turn_statement_into_use(body.last()) 3065 { 3066 self.selected_call = Some(call_data); 3067 } 3068 3069 ast::visit::visit_typed_expr_fn( 3070 self, 3071 location, 3072 type_, 3073 kind, 3074 arguments, 3075 body, 3076 return_annotation, 3077 ); 3078 } 3079 3080 fn visit_typed_expr_block( 3081 &mut self, 3082 location: &'ast SrcSpan, 3083 statements: &'ast [TypedStatement], 3084 ) { 3085 let Some(last_statement) = statements.last() else { 3086 return; 3087 }; 3088 3089 // The cursor has to be inside the last statement of the block to offer 3090 // the code action. 3091 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location()); 3092 if within(self.params.range, statement_range) { 3093 // Only the last statement of a block can be turned into a use! 3094 if let Some(selected_call) = turn_statement_into_use(last_statement) { 3095 self.selected_call = Some(selected_call) 3096 } 3097 } 3098 3099 ast::visit::visit_typed_expr_block(self, location, statements); 3100 } 3101} 3102 3103fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> { 3104 match statement { 3105 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None, 3106 ast::Statement::Expression(expression) => turn_expression_into_use(expression), 3107 } 3108} 3109 3110fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> { 3111 let TypedExpr::Call { 3112 arguments, 3113 location: call_span, 3114 fun: called_function, 3115 .. 3116 } = expr 3117 else { 3118 return None; 3119 }; 3120 3121 // The function arguments in the ast are reordered using function's field map. 3122 // This means that in the `args` array they might not appear in the same order 3123 // in which they are written by the user. Since the rest of the code relies 3124 // on their order in the written code we first have to sort them by their 3125 // source position. 3126 let arguments = arguments 3127 .iter() 3128 .sorted_by_key(|argument| argument.location.start) 3129 .collect_vec(); 3130 3131 let CallArg { 3132 value: last_arg, 3133 implicit: None, 3134 .. 3135 } = arguments.last()? 3136 else { 3137 return None; 3138 }; 3139 3140 let TypedExpr::Fn { 3141 arguments: callback_arguments, 3142 body, 3143 .. 3144 } = last_arg 3145 else { 3146 return None; 3147 }; 3148 3149 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) { 3150 (Some(first), Some(last)) => Some(first.location.merge(&last.location)), 3151 _ => None, 3152 }; 3153 3154 let arg_before_callback_span = if arguments.len() >= 2 { 3155 arguments 3156 .get(arguments.len() - 2) 3157 .map(|call_arg| call_arg.location) 3158 } else { 3159 None 3160 }; 3161 3162 let callback_body_span = body.first().location().merge(&body.last().last_location()); 3163 3164 Some(CallLocations { 3165 call_span: *call_span, 3166 called_function_span: called_function.location(), 3167 callback_arguments_span, 3168 arg_before_callback_span, 3169 callback_body_span, 3170 }) 3171} 3172 3173/// Builder for code action to extract expression into a variable. 3174/// The action will wrap the expression in a block if needed in the appropriate scope. 3175/// 3176/// For using the code action on the following selection: 3177/// 3178/// ```gleam 3179/// fn void() { 3180/// case result { 3181/// Ok(value) -> 2 * value + 1 3182/// // ^^^^^^^^^ 3183/// Error(_) -> panic 3184/// } 3185/// } 3186/// ``` 3187/// 3188/// Will result: 3189/// 3190/// ```gleam 3191/// fn void() { 3192/// case result { 3193/// Ok(value) -> { 3194/// let int = 2 * value 3195/// int + 1 3196/// } 3197/// Error(_) -> panic 3198/// } 3199/// } 3200/// ``` 3201pub struct ExtractVariable<'a> { 3202 module: &'a Module, 3203 params: &'a CodeActionParams, 3204 edits: TextEdits<'a>, 3205 position: Option<ExtractVariablePosition>, 3206 selected_expression: Option<ExtractedToVariable>, 3207 statement_before_selected_expression: Option<SrcSpan>, 3208 latest_statement: Option<SrcSpan>, 3209 to_be_wrapped: bool, 3210 name_generator: NameGenerator, 3211} 3212 3213pub enum ExtractedToVariable { 3214 Expression { location: SrcSpan, name: EcoString }, 3215 StartOfPipeline { location: SrcSpan, name: EcoString }, 3216} 3217 3218/// The Position of the selected code 3219#[derive(PartialEq, Eq, Copy, Clone, Debug)] 3220enum ExtractVariablePosition { 3221 InsideCaptureBody, 3222 /// Full statements (i.e. assignments, `use`s, and simple expressions). 3223 TopLevelStatement, 3224 /// The call on the right hand side of a pipe `|>`. 3225 PipelineCall, 3226 /// The right hand side of the `->` in a case expression. 3227 InsideCaseClause, 3228 /// A call argument. This can also be a `use` callback. 3229 CallArg, 3230} 3231 3232impl<'a> ExtractVariable<'a> { 3233 pub fn new( 3234 module: &'a Module, 3235 line_numbers: &'a LineNumbers, 3236 params: &'a CodeActionParams, 3237 ) -> Self { 3238 Self { 3239 module, 3240 params, 3241 edits: TextEdits::new(line_numbers), 3242 position: None, 3243 selected_expression: None, 3244 statement_before_selected_expression: None, 3245 latest_statement: None, 3246 to_be_wrapped: false, 3247 name_generator: NameGenerator::new(), 3248 } 3249 } 3250 3251 pub fn code_actions(mut self) -> Vec<CodeAction> { 3252 self.visit_typed_module(&self.module.ast); 3253 3254 let (Some(extracted_value), Some(insert_location)) = ( 3255 self.selected_expression, 3256 self.statement_before_selected_expression, 3257 ) else { 3258 return vec![]; 3259 }; 3260 3261 let variable_name = match &extracted_value { 3262 ExtractedToVariable::Expression { name, .. } 3263 | ExtractedToVariable::StartOfPipeline { name, .. } => name, 3264 }; 3265 let expression_span = match &extracted_value { 3266 ExtractedToVariable::Expression { location, .. } 3267 | ExtractedToVariable::StartOfPipeline { location, .. } => location, 3268 }; 3269 3270 let content = self 3271 .module 3272 .code 3273 .get(expression_span.start as usize..expression_span.end as usize) 3274 .expect("selected expression"); 3275 3276 let range = self.edits.src_span_to_lsp_range(insert_location); 3277 3278 let indent_size = 3279 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 3280 3281 let mut indent = " ".repeat(indent_size); 3282 3283 // We insert the variable declaration 3284 // Wrap in a block if needed 3285 let mut insertion = match extracted_value { 3286 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"), 3287 ExtractedToVariable::StartOfPipeline { .. } => { 3288 format!("let {variable_name} =\n{indent} {content}\n") 3289 } 3290 }; 3291 3292 if self.to_be_wrapped { 3293 let line_end = self 3294 .edits 3295 .line_numbers 3296 .line_starts 3297 .get((range.end.line + 1) as usize) 3298 .expect("Line number should be valid"); 3299 3300 self.edits.insert(*line_end, format!("{indent}}}\n")); 3301 indent += " "; 3302 insertion = format!("{{\n{indent}{insertion}"); 3303 }; 3304 3305 self.edits 3306 .insert(insert_location.start, format!("{insertion}\n{indent}")); 3307 3308 self.edits 3309 .replace(*expression_span, String::from(variable_name)); 3310 3311 let mut action = Vec::with_capacity(1); 3312 CodeActionBuilder::new("Extract variable") 3313 .kind(CodeActionKind::RefactorExtract) 3314 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3315 .preferred(false) 3316 .push_to(&mut action); 3317 action 3318 } 3319 3320 fn inside_new_scope<F>(&mut self, fun: F) 3321 where 3322 F: Fn(&mut Self), 3323 { 3324 let names = self.name_generator.clone(); 3325 fun(self); 3326 self.name_generator = names; 3327 } 3328 3329 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString { 3330 let name = self.name_generator.generate_name_from_type(&type_); 3331 // When the generator generates a name, it rightfully inserts it in the 3332 // current scope so that it cannot be used again. 3333 // However, in our case it's not what we want: the name we're generating 3334 // is a candidate for what we might use for a single variable, at the 3335 // end of the whole process we're gonna pick just a single name. 3336 // If we were to insert this name into scope, that means that all the 3337 // other candidates would have a suffix `int_2`, `int_3`, ... 3338 // When we finally pick one it would be strange if the picked name had 3339 // a suffix but no `int`, `int_1`, ... were in scope! 3340 let _ = self.name_generator.used_names.remove(&name); 3341 name 3342 } 3343 3344 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F) 3345 where 3346 F: Fn(&mut Self), 3347 { 3348 self.at_optional_position(Some(position), fun); 3349 } 3350 3351 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F) 3352 where 3353 F: Fn(&mut Self), 3354 { 3355 let previous_statement = self.latest_statement; 3356 let previous_position = self.position; 3357 self.position = position; 3358 fun(self); 3359 self.position = previous_position; 3360 self.latest_statement = previous_statement; 3361 } 3362} 3363 3364impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> { 3365 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 3366 let range = self.edits.src_span_to_lsp_range(statement.location()); 3367 if !within(self.params.range, range) { 3368 self.latest_statement = Some(statement.location()); 3369 ast::visit::visit_typed_statement(self, statement); 3370 return; 3371 } 3372 3373 match self.position { 3374 // A capture body is comprised of just a single expression statement 3375 // that is inserted by the compiler, we don't really want to put 3376 // anything before that; so in this case we avoid tracking it. 3377 Some(ExtractVariablePosition::InsideCaptureBody) => {} 3378 Some(ExtractVariablePosition::PipelineCall) => { 3379 // Insert above the pipeline start 3380 self.latest_statement = Some(statement.location()); 3381 } 3382 _ => { 3383 // Insert below the previous statement 3384 self.latest_statement = Some(statement.location()); 3385 self.statement_before_selected_expression = self.latest_statement; 3386 } 3387 } 3388 3389 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3390 ast::visit::visit_typed_statement(this, statement); 3391 }); 3392 } 3393 3394 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 3395 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 3396 start: fun.location.start, 3397 end: fun.end_position, 3398 }); 3399 3400 if !within(self.params.range, fun_range) { 3401 return; 3402 } 3403 3404 // We reset the name generator to purge the variable names from other 3405 // scopes. 3406 // We then reserve the names already used by top level definitions. 3407 self.name_generator = NameGenerator::new(); 3408 self.name_generator 3409 .reserve_module_value_names(&self.module.ast.definitions); 3410 3411 ast::visit::visit_typed_function(self, fun); 3412 } 3413 3414 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 3415 if let Pattern::Variable { name, .. } = &assignment.pattern { 3416 self.name_generator.add_used_name(name.clone()) 3417 }; 3418 ast::visit::visit_typed_assignment(self, assignment); 3419 } 3420 3421 fn visit_typed_expr_pipeline( 3422 &mut self, 3423 location: &'ast SrcSpan, 3424 first_value: &'ast TypedPipelineAssignment, 3425 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 3426 finally: &'ast TypedExpr, 3427 finally_kind: &'ast PipelineAssignmentKind, 3428 ) { 3429 let expr_range = self.edits.src_span_to_lsp_range(*location); 3430 if !within(self.params.range, expr_range) { 3431 ast::visit::visit_typed_expr_pipeline( 3432 self, 3433 location, 3434 first_value, 3435 assignments, 3436 finally, 3437 finally_kind, 3438 ); 3439 return; 3440 }; 3441 3442 // Visiting a pipeline requires a bit of care, we don't want to extract 3443 // intermediate steps as variables (those are function calls)! 3444 // So we start by checking if the selected section contains multiple 3445 // steps including the first one: in that case we can extract all those 3446 // steps as a single variable. 3447 let selection = self.edits.lsp_range_to_src_span(self.params.range); 3448 let is_inside_first_step = first_value.location.contains(selection.start); 3449 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| { 3450 if assignment.location.contains(selection.end) { 3451 Some(assignment) 3452 } else { 3453 None 3454 } 3455 }); 3456 3457 if let Some(last) = last_included_step 3458 && is_inside_first_step 3459 { 3460 let location = first_value.location.merge(&last.value.location()); 3461 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline { 3462 location, 3463 name: self.generate_candidate_name(last.type_()), 3464 }); 3465 return; 3466 } 3467 3468 // Otherwise we visit all the steps individually to see if there's 3469 // something _inside_ a step that might be extracted. 3470 let all_assignments = 3471 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment)); 3472 for assignment in all_assignments { 3473 // With the position as "PipelineCall" we know we can't extract the 3474 // pipeline step itself! 3475 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 3476 this.visit_typed_pipeline_assignment(assignment); 3477 }); 3478 } 3479 3480 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 3481 this.visit_typed_expr(finally) 3482 }); 3483 } 3484 3485 fn visit_typed_expr_call( 3486 &mut self, 3487 location: &'ast SrcSpan, 3488 type_: &'ast Arc<Type>, 3489 fun: &'ast TypedExpr, 3490 arguments: &'ast [TypedCallArg], 3491 open_parenthesis: &'ast Option<u32>, 3492 ) { 3493 // Function calls need some extra care. If we're inspecting a record 3494 // call like this one: `Wibble(wobble, woo)` and the cursor is over the 3495 // constructor itself we never want to allow extracting it, or it would 3496 // result in the following code: 3497 // 3498 // ```gleam 3499 // Wibble(wobble, woo) 3500 // // ^^ Cursor here 3501 // 3502 // let wibble = Wibble 3503 // wibble(wobble, woo) 3504 // // That's a bit silly! 3505 // ``` 3506 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 3507 if within(self.params.range, fun_range) && fun.is_record_constructor_function() { 3508 return; 3509 } 3510 3511 // Otherwise we just keep visiting like usual, no special handling is 3512 // required. 3513 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis); 3514 } 3515 3516 fn visit_typed_expr_record_update( 3517 &mut self, 3518 location: &'ast SrcSpan, 3519 spread_start: &'ast u32, 3520 type_: &'ast Arc<Type>, 3521 updated_record: &'ast TypedExpr, 3522 updated_record_assigned_name: &'ast Option<EcoString>, 3523 constructor: &'ast TypedExpr, 3524 arguments: &'ast [TypedCallArg], 3525 ) { 3526 // Record updates need some extra care. If we're inspecting a record 3527 // update like this one: `Wibble(..wobble, woo)` and the cursor is over 3528 // the constructor itself we never want to allow extracting it, or it 3529 // would result in the following code: 3530 // 3531 // ```gleam 3532 // Wibble(..wobble, woo) 3533 // // ^^ Cursor here 3534 // 3535 // let wibble = Wibble 3536 // wibble(..wobble, woo) 3537 // // That's a bit silly! 3538 // ``` 3539 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location()); 3540 if within(self.params.range, constructor_range) 3541 && constructor.is_record_constructor_function() 3542 { 3543 return; 3544 } 3545 3546 // Otherwise we just keep visiting like usual, no special handling is 3547 // required. 3548 ast::visit::visit_typed_expr_record_update( 3549 self, 3550 location, 3551 spread_start, 3552 type_, 3553 updated_record, 3554 updated_record_assigned_name, 3555 constructor, 3556 arguments, 3557 ); 3558 } 3559 3560 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 3561 let expr_location = expr.location(); 3562 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 3563 if !within(self.params.range, expr_range) { 3564 ast::visit::visit_typed_expr(self, expr); 3565 return; 3566 } 3567 3568 // If the expression is a top level statement we don't want to extract 3569 // it into a variable. It would mean we would turn this: 3570 // 3571 // ```gleam 3572 // pub fn main() { 3573 // let wibble = 1 3574 // // ^ cursor here 3575 // } 3576 // 3577 // // into: 3578 // 3579 // pub fn main() { 3580 // let int = 1 3581 // let wibble = int 3582 // } 3583 // ``` 3584 // 3585 // Not all that useful! 3586 // 3587 match self.position { 3588 Some( 3589 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall, 3590 ) => { 3591 self.at_optional_position(None, |this| { 3592 ast::visit::visit_typed_expr(this, expr); 3593 }); 3594 return; 3595 } 3596 Some( 3597 ExtractVariablePosition::InsideCaptureBody 3598 | ExtractVariablePosition::InsideCaseClause 3599 | ExtractVariablePosition::CallArg, 3600 ) 3601 | None => {} 3602 } 3603 3604 match expr { 3605 TypedExpr::Fn { 3606 kind: FunctionLiteralKind::Anonymous { .. }, 3607 .. 3608 } => { 3609 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3610 ast::visit::visit_typed_expr(this, expr); 3611 }); 3612 return; 3613 } 3614 3615 TypedExpr::Int { location, .. } 3616 | TypedExpr::Float { location, .. } 3617 | TypedExpr::String { location, .. } 3618 | TypedExpr::Pipeline { location, .. } 3619 | TypedExpr::Fn { location, .. } 3620 | TypedExpr::Todo { location, .. } 3621 | TypedExpr::List { location, .. } 3622 | TypedExpr::Call { location, .. } 3623 | TypedExpr::BinOp { location, .. } 3624 | TypedExpr::Case { location, .. } 3625 | TypedExpr::RecordAccess { location, .. } 3626 | TypedExpr::Tuple { location, .. } 3627 | TypedExpr::TupleIndex { location, .. } 3628 | TypedExpr::BitArray { location, .. } 3629 | TypedExpr::RecordUpdate { location, .. } 3630 | TypedExpr::NegateBool { location, .. } 3631 | TypedExpr::NegateInt { location, .. } 3632 // It generally makes no sense to extract variables, the only 3633 // exception is for records with no fields (like `True` and 3634 // `False`): in the AST those are `Var`s with a `Record` 3635 // constructor. Extracting them is allowed! 3636 | TypedExpr::Var { 3637 constructor: 3638 ValueConstructor { 3639 variant: type_::ValueConstructorVariant::Record { .. }, 3640 .. 3641 }, 3642 location, 3643 .. 3644 } => { 3645 if let Some(ExtractVariablePosition::CallArg) = self.position { 3646 // Don't update latest statement, we don't want to insert the extracted 3647 // variable inside the parenthesis where the call argument is located. 3648 } else { 3649 self.statement_before_selected_expression = self.latest_statement; 3650 }; 3651 self.selected_expression = Some(ExtractedToVariable::Expression { 3652 location: *location, 3653 name: self.generate_candidate_name(expr.type_()), 3654 }); 3655 } 3656 3657 // Expressions that don't make sense to extract 3658 TypedExpr::Panic { .. } 3659 | TypedExpr::Echo { .. } 3660 | TypedExpr::Block { .. } 3661 | TypedExpr::ModuleSelect { .. } 3662 | TypedExpr::Invalid { .. } 3663 | TypedExpr::PositionalAccess { .. } 3664 | TypedExpr::Var { .. } => (), 3665 } 3666 3667 ast::visit::visit_typed_expr(self, expr); 3668 } 3669 3670 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3671 let range = self.edits.src_span_to_lsp_range(use_.call.location()); 3672 if !within(self.params.range, range) { 3673 ast::visit::visit_typed_use(self, use_); 3674 return; 3675 } 3676 3677 // Insert code under the `use` 3678 self.statement_before_selected_expression = Some(use_.call.location()); 3679 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3680 ast::visit::visit_typed_use(this, use_); 3681 }); 3682 } 3683 3684 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 3685 let range = self.edits.src_span_to_lsp_range(clause.location()); 3686 if !within(self.params.range, range) { 3687 self.inside_new_scope(|this| { 3688 ast::visit::visit_typed_clause(this, clause); 3689 }); 3690 return; 3691 } 3692 3693 // Insert code after the `->` 3694 self.latest_statement = Some(clause.then.location()); 3695 self.to_be_wrapped = true; 3696 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| { 3697 this.inside_new_scope(|this| { 3698 ast::visit::visit_typed_clause(this, clause); 3699 }); 3700 }); 3701 } 3702 3703 fn visit_typed_expr_block( 3704 &mut self, 3705 location: &'ast SrcSpan, 3706 statements: &'ast [TypedStatement], 3707 ) { 3708 let range = self.edits.src_span_to_lsp_range(*location); 3709 if !within(self.params.range, range) { 3710 self.inside_new_scope(|this| { 3711 ast::visit::visit_typed_expr_block(this, location, statements); 3712 }); 3713 return; 3714 } 3715 3716 // Don't extract block as variable 3717 let mut position = self.position; 3718 if let Some(ExtractVariablePosition::InsideCaseClause) = position { 3719 position = None; 3720 self.to_be_wrapped = false; 3721 } 3722 3723 self.at_optional_position(position, |this| { 3724 this.inside_new_scope(|this| { 3725 ast::visit::visit_typed_expr_block(this, location, statements); 3726 }); 3727 }); 3728 } 3729 3730 fn visit_typed_expr_fn( 3731 &mut self, 3732 location: &'ast SrcSpan, 3733 type_: &'ast Arc<Type>, 3734 kind: &'ast FunctionLiteralKind, 3735 arguments: &'ast [TypedArg], 3736 body: &'ast Vec1<TypedStatement>, 3737 return_annotation: &'ast Option<ast::TypeAst>, 3738 ) { 3739 let range = self.edits.src_span_to_lsp_range(*location); 3740 if !within(self.params.range, range) { 3741 self.inside_new_scope(|this| { 3742 ast::visit::visit_typed_expr_fn( 3743 this, 3744 location, 3745 type_, 3746 kind, 3747 arguments, 3748 body, 3749 return_annotation, 3750 ); 3751 }); 3752 return; 3753 } 3754 3755 let position = match kind { 3756 // If a fn is a capture `int.wibble(1, _)` its body will consist of 3757 // just a single expression statement. When visiting we must record 3758 // we're inside a capture body. 3759 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody), 3760 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement), 3761 FunctionLiteralKind::Anonymous { .. } => self.position, 3762 }; 3763 3764 self.at_optional_position(position, |this| { 3765 this.inside_new_scope(|this| { 3766 ast::visit::visit_typed_expr_fn( 3767 this, 3768 location, 3769 type_, 3770 kind, 3771 arguments, 3772 body, 3773 return_annotation, 3774 ); 3775 }); 3776 }); 3777 } 3778 3779 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 3780 let range = self.edits.src_span_to_lsp_range(arg.location); 3781 if !within(self.params.range, range) { 3782 ast::visit::visit_typed_call_arg(self, arg); 3783 return; 3784 } 3785 3786 // An implicit record update arg in inserted by the compiler, we don't 3787 // want folks to interact with this since it doesn't translate to 3788 // anything in the source code despite having a default position. 3789 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit { 3790 return; 3791 } 3792 3793 let position = if arg.is_use_implicit_callback() { 3794 Some(ExtractVariablePosition::TopLevelStatement) 3795 } else { 3796 Some(ExtractVariablePosition::CallArg) 3797 }; 3798 3799 self.at_optional_position(position, |this| { 3800 ast::visit::visit_typed_call_arg(this, arg); 3801 }); 3802 } 3803 3804 // We don't want to offer the action if the cursor is over some invalid 3805 // piece of code. 3806 fn visit_typed_expr_invalid( 3807 &mut self, 3808 location: &'ast SrcSpan, 3809 _type_: &'ast Arc<Type>, 3810 _extra_information: &'ast Option<InvalidExpression>, 3811 ) { 3812 let invalid_range = self.edits.src_span_to_lsp_range(*location); 3813 if within(self.params.range, invalid_range) { 3814 self.selected_expression = None; 3815 } 3816 } 3817} 3818 3819/// Builder for code action to convert a literal use into a const. 3820/// 3821/// For using the code action on each of the following lines: 3822/// 3823/// ```gleam 3824/// fn void() { 3825/// let var = [1, 2, 3] 3826/// let res = function("Statement", var) 3827/// } 3828/// ``` 3829/// 3830/// Both value literals will become: 3831/// 3832/// ```gleam 3833/// const var = [1, 2, 3] 3834/// const string = "Statement" 3835/// 3836/// fn void() { 3837/// let res = function(string, var) 3838/// } 3839/// ``` 3840pub struct ExtractConstant<'a> { 3841 module: &'a Module, 3842 params: &'a CodeActionParams, 3843 edits: TextEdits<'a>, 3844 /// The whole selected expression 3845 selected_expression: Option<SrcSpan>, 3846 /// The location of the start of the function containing the expression. 3847 /// It includes function's documentation as well 3848 container_function_start: Option<u32>, 3849 /// The variant of the extractable expression being extracted (if any) 3850 variant_of_extractable: Option<ExtractableToConstant>, 3851 /// The name of the newly created constant 3852 name_to_use: Option<EcoString>, 3853 /// The right hand side expression of the newly created constant 3854 value_to_use: Option<EcoString>, 3855} 3856 3857/// Used when an expression can be extracted to a constant 3858enum ExtractableToConstant { 3859 /// Used for collections and operator uses. This means that elements 3860 /// inside, are also extractable as constants. 3861 ComposedValue, 3862 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings 3863 /// and type variants (not records). 3864 SingleValue, 3865 /// Used for whole variable assignments. If the right hand side of the 3866 /// expression can be extracted, the whole expression extracted and use the 3867 /// local variable as a constant. 3868 Assignment, 3869} 3870 3871fn can_be_constant( 3872 module: &Module, 3873 expr: &TypedExpr, 3874 module_constants: Option<&HashSet<&EcoString>>, 3875) -> bool { 3876 // We pass the `module_constants` on recursion to not compute them each time 3877 let module_constants = match module_constants { 3878 Some(module_constants) => module_constants, 3879 None => &module 3880 .ast 3881 .definitions 3882 .constants 3883 .iter() 3884 .map(|constant| &constant.name) 3885 .collect(), 3886 }; 3887 3888 match expr { 3889 // Attempt to extract whole list as long as it's comprised of only literals 3890 TypedExpr::List { elements, tail, .. } => { 3891 elements 3892 .iter() 3893 .all(|element| can_be_constant(module, element, Some(module_constants))) 3894 && tail.is_none() 3895 } 3896 3897 // Attempt to extract whole bit array as long as it's made up of literals 3898 TypedExpr::BitArray { segments, .. } => { 3899 segments 3900 .iter() 3901 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants))) 3902 && segments.iter().all(|segment| { 3903 segment.options.iter().all(|option| match option { 3904 ast::BitArrayOption::Size { value, .. } => { 3905 can_be_constant(module, value, Some(module_constants)) 3906 } 3907 3908 ast::BitArrayOption::Bytes { .. } 3909 | ast::BitArrayOption::Int { .. } 3910 | ast::BitArrayOption::Float { .. } 3911 | ast::BitArrayOption::Bits { .. } 3912 | ast::BitArrayOption::Utf8 { .. } 3913 | ast::BitArrayOption::Utf16 { .. } 3914 | ast::BitArrayOption::Utf32 { .. } 3915 | ast::BitArrayOption::Utf8Codepoint { .. } 3916 | ast::BitArrayOption::Utf16Codepoint { .. } 3917 | ast::BitArrayOption::Utf32Codepoint { .. } 3918 | ast::BitArrayOption::Signed { .. } 3919 | ast::BitArrayOption::Unsigned { .. } 3920 | ast::BitArrayOption::Big { .. } 3921 | ast::BitArrayOption::Little { .. } 3922 | ast::BitArrayOption::Native { .. } 3923 | ast::BitArrayOption::Unit { .. } => true, 3924 }) 3925 }) 3926 } 3927 3928 // Attempt to extract whole tuple as long as it's comprised of only literals 3929 TypedExpr::Tuple { elements, .. } => elements 3930 .iter() 3931 .all(|element| can_be_constant(module, element, Some(module_constants))), 3932 3933 // Extract literals directly 3934 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true, 3935 3936 // Extract non-record types directly 3937 TypedExpr::Var { 3938 constructor, name, .. 3939 } => { 3940 matches!( 3941 constructor.variant, 3942 type_::ValueConstructorVariant::Record { arity: 0, .. } 3943 ) || module_constants.contains(name) 3944 } 3945 3946 // Extract record types as long as arguments can be constant 3947 TypedExpr::Call { arguments, fun, .. } => { 3948 fun.is_record_literal() 3949 && arguments 3950 .iter() 3951 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants))) 3952 } 3953 3954 // Extract concat binary operation if both sides can be constants 3955 TypedExpr::BinOp { 3956 operator, 3957 left, 3958 right, 3959 .. 3960 } => { 3961 matches!(operator, ast::BinOp::Concatenate) 3962 && can_be_constant(module, left, Some(module_constants)) 3963 && can_be_constant(module, right, Some(module_constants)) 3964 } 3965 3966 TypedExpr::Block { .. } 3967 | TypedExpr::Pipeline { .. } 3968 | TypedExpr::Fn { .. } 3969 | TypedExpr::Case { .. } 3970 | TypedExpr::RecordAccess { .. } 3971 | TypedExpr::PositionalAccess { .. } 3972 | TypedExpr::ModuleSelect { .. } 3973 | TypedExpr::TupleIndex { .. } 3974 | TypedExpr::Todo { .. } 3975 | TypedExpr::Panic { .. } 3976 | TypedExpr::Echo { .. } 3977 | TypedExpr::RecordUpdate { .. } 3978 | TypedExpr::NegateBool { .. } 3979 | TypedExpr::NegateInt { .. } 3980 | TypedExpr::Invalid { .. } => false, 3981 } 3982} 3983 3984/// Takes the list of already existing constants and functions and creates a 3985/// name that doesn't conflict with them 3986/// 3987fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString { 3988 let mut name_generator = NameGenerator::new(); 3989 name_generator.reserve_module_value_names(&module.ast.definitions); 3990 name_generator.generate_name_from_type(&expr.type_()) 3991} 3992 3993/// Converts the source start position of a documentation comment's contents into 3994/// the position of the leading slash in its marker ('///'). 3995fn get_doc_marker_position(content_pos: u32) -> u32 { 3996 content_pos.saturating_sub(3) 3997} 3998 3999impl<'a> ExtractConstant<'a> { 4000 pub fn new( 4001 module: &'a Module, 4002 line_numbers: &'a LineNumbers, 4003 params: &'a CodeActionParams, 4004 ) -> Self { 4005 Self { 4006 module, 4007 params, 4008 edits: TextEdits::new(line_numbers), 4009 selected_expression: None, 4010 container_function_start: None, 4011 variant_of_extractable: None, 4012 name_to_use: None, 4013 value_to_use: None, 4014 } 4015 } 4016 4017 pub fn code_actions(mut self) -> Vec<CodeAction> { 4018 self.visit_typed_module(&self.module.ast); 4019 4020 let ( 4021 Some(expr_span), 4022 Some(function_start), 4023 Some(type_of_extractable), 4024 Some(new_const_name), 4025 Some(const_value), 4026 ) = ( 4027 self.selected_expression, 4028 self.container_function_start, 4029 self.variant_of_extractable, 4030 self.name_to_use, 4031 self.value_to_use, 4032 ) 4033 else { 4034 return vec![]; 4035 }; 4036 4037 // We insert the constant declaration 4038 self.edits.insert( 4039 function_start, 4040 format!("const {new_const_name} = {const_value}\n\n"), 4041 ); 4042 4043 // We remove or replace the selected expression 4044 match type_of_extractable { 4045 // The whole expression is deleted for assignments 4046 ExtractableToConstant::Assignment => { 4047 let range = self 4048 .edits 4049 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 4050 4051 let indent_size = 4052 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 4053 4054 let expr_span_with_new_line = SrcSpan { 4055 // We remove leading indentation + 1 to remove the newline with it 4056 start: expr_span.start - (indent_size as u32 + 1), 4057 end: expr_span.end, 4058 }; 4059 self.edits.delete(expr_span_with_new_line); 4060 } 4061 4062 // Only right hand side is replaced for collection or values 4063 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => { 4064 self.edits.replace(expr_span, String::from(new_const_name)); 4065 } 4066 } 4067 4068 let mut action = Vec::with_capacity(1); 4069 CodeActionBuilder::new("Extract constant") 4070 .kind(CodeActionKind::RefactorExtract) 4071 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4072 .preferred(false) 4073 .push_to(&mut action); 4074 action 4075 } 4076} 4077 4078impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> { 4079 /// To get the position of the function containing the value or assignment 4080 /// to extract 4081 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 4082 let fun_location = fun.location; 4083 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 4084 start: fun_location.start, 4085 end: fun.end_position, 4086 }); 4087 4088 if !within(self.params.range, fun_range) { 4089 return; 4090 } 4091 4092 // Here we need to get position of the function, starting from the leading slash in the 4093 // documentation comment's marker ('///'), not from comment's content (of which 4094 // we have the position), so we must convert the content start position 4095 // to the leading slash's position. 4096 self.container_function_start = Some( 4097 fun.documentation 4098 .as_ref() 4099 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 4100 .unwrap_or(fun_location.start), 4101 ); 4102 4103 ast::visit::visit_typed_function(self, fun); 4104 } 4105 4106 /// To extract the whole assignment 4107 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 4108 let expr_location = assignment.location; 4109 4110 // We only offer this code action for extracting the whole assignment 4111 // between `let` and `=`. 4112 let pattern_location = assignment.pattern.location(); 4113 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 4114 let code_action_range = self.edits.src_span_to_lsp_range(location); 4115 4116 if !within(self.params.range, code_action_range) { 4117 ast::visit::visit_typed_assignment(self, assignment); 4118 return; 4119 } 4120 4121 // Has to be variable because patterns can't be constants. 4122 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None) 4123 { 4124 self.variant_of_extractable = Some(ExtractableToConstant::Assignment); 4125 self.selected_expression = Some(expr_location); 4126 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern { 4127 Some(name.clone()) 4128 } else { 4129 None 4130 }; 4131 self.value_to_use = Some(EcoString::from( 4132 self.module 4133 .code 4134 .get( 4135 (assignment.value.location().start as usize) 4136 ..(assignment.location.end as usize), 4137 ) 4138 .expect("selected expression"), 4139 )); 4140 } 4141 } 4142 4143 /// To extract only the literal 4144 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 4145 let expr_location = expr.location(); 4146 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 4147 4148 if !within(self.params.range, expr_range) { 4149 ast::visit::visit_typed_expr(self, expr); 4150 return; 4151 } 4152 4153 // Keep going down recursively if: 4154 // - It's no extractable has been found yet (`None`). 4155 // - It's a collection, which may or may not contain a value that can 4156 // be extracted. 4157 // - It's a binary operator, which may or may not operate on 4158 // extractable values. 4159 if matches!( 4160 self.variant_of_extractable, 4161 None | Some(ExtractableToConstant::ComposedValue) 4162 ) && can_be_constant(self.module, expr, None) 4163 { 4164 self.variant_of_extractable = match expr { 4165 TypedExpr::Var { .. } 4166 | TypedExpr::Int { .. } 4167 | TypedExpr::Float { .. } 4168 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue), 4169 4170 TypedExpr::List { .. } 4171 | TypedExpr::Tuple { .. } 4172 | TypedExpr::BitArray { .. } 4173 | TypedExpr::BinOp { .. } 4174 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue), 4175 4176 TypedExpr::Block { .. } 4177 | TypedExpr::Pipeline { .. } 4178 | TypedExpr::Fn { .. } 4179 | TypedExpr::Case { .. } 4180 | TypedExpr::RecordAccess { .. } 4181 | TypedExpr::PositionalAccess { .. } 4182 | TypedExpr::ModuleSelect { .. } 4183 | TypedExpr::TupleIndex { .. } 4184 | TypedExpr::Todo { .. } 4185 | TypedExpr::Panic { .. } 4186 | TypedExpr::Echo { .. } 4187 | TypedExpr::RecordUpdate { .. } 4188 | TypedExpr::NegateBool { .. } 4189 | TypedExpr::NegateInt { .. } 4190 | TypedExpr::Invalid { .. } => None, 4191 }; 4192 4193 self.selected_expression = Some(expr_location); 4194 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr)); 4195 self.value_to_use = Some(EcoString::from( 4196 self.module 4197 .code 4198 .get((expr_location.start as usize)..(expr_location.end as usize)) 4199 .expect("selected expression"), 4200 )); 4201 } 4202 4203 ast::visit::visit_typed_expr(self, expr); 4204 } 4205} 4206 4207/// Builder for code action to apply the "expand function capture" action. 4208/// 4209pub struct ExpandFunctionCapture<'a> { 4210 module: &'a Module, 4211 params: &'a CodeActionParams, 4212 edits: TextEdits<'a>, 4213 function_capture_data: Option<FunctionCaptureData>, 4214} 4215 4216pub struct FunctionCaptureData { 4217 function_span: SrcSpan, 4218 hole_span: SrcSpan, 4219 hole_type: Arc<Type>, 4220 reserved_names: VariablesNames, 4221} 4222 4223impl<'a> ExpandFunctionCapture<'a> { 4224 pub fn new( 4225 module: &'a Module, 4226 line_numbers: &'a LineNumbers, 4227 params: &'a CodeActionParams, 4228 ) -> Self { 4229 Self { 4230 module, 4231 params, 4232 edits: TextEdits::new(line_numbers), 4233 function_capture_data: None, 4234 } 4235 } 4236 4237 pub fn code_actions(mut self) -> Vec<CodeAction> { 4238 self.visit_typed_module(&self.module.ast); 4239 4240 let Some(FunctionCaptureData { 4241 function_span, 4242 hole_span, 4243 hole_type, 4244 reserved_names, 4245 }) = self.function_capture_data 4246 else { 4247 return vec![]; 4248 }; 4249 4250 let mut name_generator = NameGenerator::new(); 4251 name_generator.reserve_variable_names(reserved_names); 4252 let name = name_generator.generate_name_from_type(&hole_type); 4253 4254 self.edits.replace(hole_span, name.clone().into()); 4255 self.edits.insert(function_span.end, " }".into()); 4256 self.edits 4257 .insert(function_span.start, format!("fn({name}) {{ ")); 4258 4259 let mut action = Vec::with_capacity(1); 4260 CodeActionBuilder::new("Expand function capture") 4261 .kind(CodeActionKind::RefactorRewrite) 4262 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4263 .preferred(false) 4264 .push_to(&mut action); 4265 action 4266 } 4267} 4268 4269impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> { 4270 fn visit_typed_expr_fn( 4271 &mut self, 4272 location: &'ast SrcSpan, 4273 type_: &'ast Arc<Type>, 4274 kind: &'ast FunctionLiteralKind, 4275 arguments: &'ast [TypedArg], 4276 body: &'ast Vec1<TypedStatement>, 4277 return_annotation: &'ast Option<ast::TypeAst>, 4278 ) { 4279 let fn_range = self.edits.src_span_to_lsp_range(*location); 4280 if within(self.params.range, fn_range) 4281 && kind.is_capture() 4282 && let [argument] = arguments 4283 { 4284 self.function_capture_data = Some(FunctionCaptureData { 4285 function_span: *location, 4286 hole_span: argument.location, 4287 hole_type: argument.type_.clone(), 4288 reserved_names: VariablesNames::from_statements(body), 4289 }); 4290 } 4291 4292 ast::visit::visit_typed_expr_fn( 4293 self, 4294 location, 4295 type_, 4296 kind, 4297 arguments, 4298 body, 4299 return_annotation, 4300 ) 4301 } 4302} 4303 4304/// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names]. 4305struct VariablesNames { 4306 names: HashSet<EcoString>, 4307} 4308 4309impl VariablesNames { 4310 /// Creates a `VariableNames` by collecting all variables used in a list of statements. 4311 fn from_statements(statements: &[TypedStatement]) -> Self { 4312 let mut variables = Self { 4313 names: HashSet::new(), 4314 }; 4315 4316 for statement in statements { 4317 variables.visit_typed_statement(statement); 4318 } 4319 variables 4320 } 4321 4322 /// Creates a `VariableNames` by collecting all variables used within an expression. 4323 fn from_expression(expression: &TypedExpr) -> Self { 4324 let mut variables = Self { 4325 names: HashSet::new(), 4326 }; 4327 4328 variables.visit_typed_expr(expression); 4329 variables 4330 } 4331} 4332 4333impl<'ast> ast::visit::Visit<'ast> for VariablesNames { 4334 fn visit_typed_expr_var( 4335 &mut self, 4336 _location: &'ast SrcSpan, 4337 _constructor: &'ast ValueConstructor, 4338 name: &'ast EcoString, 4339 ) { 4340 let _ = self.names.insert(name.clone()); 4341 } 4342} 4343 4344/// Builder for code action to apply the "generate dynamic decoder action. 4345/// 4346pub struct GenerateDynamicDecoder<'a, IO> { 4347 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4348 module: &'a Module, 4349 params: &'a CodeActionParams, 4350 edits: TextEdits<'a>, 4351 printer: Printer<'a>, 4352 actions: &'a mut Vec<CodeAction>, 4353} 4354 4355const DECODE_MODULE: &str = "gleam/dynamic/decode"; 4356 4357impl<'a, IO> GenerateDynamicDecoder<'a, IO> { 4358 pub fn new( 4359 module: &'a Module, 4360 line_numbers: &'a LineNumbers, 4361 params: &'a CodeActionParams, 4362 actions: &'a mut Vec<CodeAction>, 4363 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4364 ) -> Self { 4365 // Since we are generating a new function, type variables from other 4366 // functions and constants are irrelevant to the types we print. 4367 let printer = Printer::new_without_type_variables(&module.ast.names); 4368 Self { 4369 module, 4370 params, 4371 edits: TextEdits::new(line_numbers), 4372 printer, 4373 actions, 4374 compiler, 4375 } 4376 } 4377 4378 pub fn code_actions(&mut self) { 4379 self.visit_typed_module(&self.module.ast); 4380 } 4381 4382 fn custom_type_decoder_body( 4383 &mut self, 4384 custom_type: &CustomType<Arc<Type>>, 4385 ) -> Option<EcoString> { 4386 // We cannot generate a decoder for an external type with no constructors! 4387 let constructors_size = custom_type.constructors.len(); 4388 let (first, rest) = custom_type.constructors.split_first()?; 4389 let mode = EncodingMode::for_custom_type(custom_type); 4390 4391 // We generate a decoder for a type with a single constructor: it does not 4392 // require pattern matching on a tag as there's no variants to tell apart. 4393 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag { 4394 return self.constructor_decoder(mode, custom_type, first, 0); 4395 } 4396 4397 // Otherwise we need to generate a decoder that has to tell apart different 4398 // variants, depending on the mode we might have to decode a type field or 4399 // plain strings! 4400 let module = self.printer.print_module(DECODE_MODULE); 4401 let discriminant = if mode == EncodingMode::PlainString { 4402 eco_format!("use variant <- {module}.then({module}.string)") 4403 } else { 4404 eco_format!("use variant <- {module}.field(\"type\", {module}.string)") 4405 }; 4406 4407 let mut clauses = Vec::with_capacity(constructors_size); 4408 for constructor in iter::once(first).chain(rest) { 4409 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?; 4410 let name = to_snake_case(&constructor.name); 4411 clauses.push(eco_format!(r#" "{name}" -> {body}"#)); 4412 } 4413 4414 let failure_clause = self.failure_clause(custom_type); 4415 4416 let cases = clauses.join("\n"); 4417 Some(eco_format!( 4418 r#"{{ 4419 {discriminant} 4420 case variant {{ 4421{cases} 4422{failure_clause} 4423 }} 4424}}"#, 4425 )) 4426 } 4427 4428 fn constructor_decoder( 4429 &mut self, 4430 mode: EncodingMode, 4431 custom_type: &ast::TypedCustomType, 4432 constructor: &TypedRecordConstructor, 4433 nesting: usize, 4434 ) -> Option<EcoString> { 4435 let decode_module = self.printer.print_module(DECODE_MODULE); 4436 let constructor_name = &constructor.name; 4437 4438 // If the constructor was encoded as a plain string with no additional 4439 // fields it means there's nothing else to decode and we can just 4440 // succeed. 4441 if mode == EncodingMode::PlainString { 4442 return Some(eco_format!("{decode_module}.success({constructor_name})")); 4443 } 4444 4445 // Otherwise we have to decode all the constructor fields to build it. 4446 let mut fields = Vec::with_capacity(constructor.arguments.len()); 4447 for argument in constructor.arguments.iter() { 4448 let (_, name) = argument.label.as_ref()?; 4449 let field = RecordField { 4450 label: RecordLabel::Labeled(name), 4451 type_: &argument.type_, 4452 }; 4453 fields.push(field); 4454 } 4455 4456 let mut decoder_printer = DecoderPrinter::new( 4457 &mut self.printer, 4458 custom_type.name.clone(), 4459 self.module.name.clone(), 4460 self.compiler, 4461 ); 4462 4463 let decoders = fields 4464 .iter() 4465 .map(|field| decoder_printer.decode_field(field, nesting + 2)) 4466 .join("\n"); 4467 4468 let indent = " ".repeat(nesting); 4469 4470 Some(if decoders.is_empty() { 4471 eco_format!("{decode_module}.success({constructor_name})") 4472 } else { 4473 let field_names = fields 4474 .iter() 4475 .map(|field| format!("{}:", field.label.variable_name())) 4476 .join(", "); 4477 4478 eco_format!( 4479 "{{ 4480{decoders} 4481{indent} {decode_module}.success({constructor_name}({field_names})) 4482{indent}}}", 4483 ) 4484 }) 4485 } 4486 4487 /// Generates the failure/catch-all clause in a decoder function for a 4488 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()` 4489 /// clause executed when the `type` field does not match any of the known variants. 4490 /// 4491 /// # Arguments 4492 /// * `custom_type` - The root type we are printing a decoder for 4493 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString { 4494 let decode_module = self.printer.print_module(DECODE_MODULE); 4495 let type_name = &custom_type.name; 4496 4497 let mut decoder_printer = DecoderPrinter::new( 4498 &mut self.printer, 4499 type_name.clone(), 4500 self.module.name.clone(), 4501 self.compiler, 4502 ); 4503 4504 // The construction of the zero value might necessitate importing 4505 // modules that aren't currently in scope. We keep track of them here. 4506 let mut modules_to_import = HashSet::new(); 4507 // We also need to keep track of the path we are tracing through the types 4508 // to make sure we don't get stuck in an infinite loop building a recursive 4509 // type. 4510 let mut path = vec![]; 4511 4512 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type( 4513 &self.module.name, 4514 type_name, 4515 &mut path, 4516 &mut modules_to_import, 4517 ) { 4518 for module_name in modules_to_import { 4519 maybe_import(&mut self.edits, self.module, &module_name); 4520 } 4521 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,) 4522 } else { 4523 eco_format!( 4524 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"# 4525 ) 4526 } 4527 } 4528} 4529 4530impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> { 4531 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 4532 let range = self 4533 .edits 4534 .src_span_to_lsp_range(custom_type.full_location()); 4535 if !within(self.params.range, range) { 4536 return; 4537 } 4538 4539 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name)); 4540 let Some(function_body) = self.custom_type_decoder_body(custom_type) else { 4541 return; 4542 }; 4543 4544 let parameters = match custom_type.parameters.len() { 4545 0 => EcoString::new(), 4546 _ => eco_format!( 4547 "({})", 4548 custom_type 4549 .parameters 4550 .iter() 4551 .map(|(_, name)| name) 4552 .join(", ") 4553 ), 4554 }; 4555 4556 let decoder_type = self.printer.print_type(&Type::Named { 4557 publicity: Publicity::Public, 4558 package: STDLIB_PACKAGE_NAME.into(), 4559 module: DECODE_MODULE.into(), 4560 name: "Decoder".into(), 4561 arguments: vec![], 4562 inferred_variant: None, 4563 }); 4564 4565 let function = format!( 4566 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}", 4567 type_name = custom_type.name, 4568 ); 4569 4570 self.edits.insert(custom_type.end_position, function); 4571 maybe_import(&mut self.edits, self.module, DECODE_MODULE); 4572 4573 CodeActionBuilder::new("Generate dynamic decoder") 4574 .kind(CodeActionKind::Refactor) 4575 .preferred(false) 4576 .changes( 4577 self.params.text_document.uri.clone(), 4578 std::mem::take(&mut self.edits.edits), 4579 ) 4580 .push_to(self.actions); 4581 } 4582} 4583 4584/// If `module_name` is not already imported inside `module`, adds an edit to 4585/// add that import. 4586/// This function also makes sure not to import a module in itself. 4587/// 4588fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) { 4589 if module.ast.names.is_imported(module_name) || module.name == module_name { 4590 return; 4591 } 4592 4593 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers); 4594 let first_is_import = first_import_pos.is_some(); 4595 let import_location = first_import_pos.unwrap_or_default(); 4596 let after_import_newlines = add_newlines_after_import( 4597 import_location, 4598 first_is_import, 4599 edits.line_numbers, 4600 &module.code, 4601 ); 4602 4603 edits.edits.push(get_import_edit( 4604 import_location, 4605 module_name, 4606 &after_import_newlines, 4607 )); 4608} 4609 4610struct DecoderPrinter<'a, 'b, IO> { 4611 printer: &'a mut Printer<'b>, 4612 /// The name of the root type we are printing a decoder for 4613 type_name: EcoString, 4614 /// The module name of the root type we are printing a decoder for 4615 type_module: EcoString, 4616 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4617} 4618 4619const DYNAMIC_MODULE: &str = "gleam/dynamic"; 4620const DICT_MODULE: &str = "gleam/dict"; 4621const OPTION_MODULE: &str = "gleam/option"; 4622 4623struct RecordField<'a> { 4624 label: RecordLabel<'a>, 4625 type_: &'a Type, 4626} 4627 4628enum RecordLabel<'a> { 4629 Labeled(&'a str), 4630 Unlabeled(usize), 4631} 4632 4633impl RecordLabel<'_> { 4634 fn field_key(&self) -> EcoString { 4635 match self { 4636 RecordLabel::Labeled(label) => eco_format!("\"{label}\""), 4637 RecordLabel::Unlabeled(index) => { 4638 eco_format!("{index}") 4639 } 4640 } 4641 } 4642 4643 fn variable_name(&self) -> EcoString { 4644 match self { 4645 RecordLabel::Labeled(label) => (*label).into(), 4646 &RecordLabel::Unlabeled(mut index) => { 4647 let mut characters = Vec::new(); 4648 let alphabet_length = 26; 4649 let alphabet_offset = b'a'; 4650 loop { 4651 let alphabet_index = (index % alphabet_length) as u8; 4652 characters.push((alphabet_offset + alphabet_index) as char); 4653 index /= alphabet_length; 4654 4655 if index == 0 { 4656 break; 4657 } 4658 index -= 1; 4659 } 4660 characters.into_iter().rev().collect() 4661 } 4662 } 4663 } 4664} 4665 4666impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> { 4667 fn new( 4668 printer: &'a mut Printer<'b>, 4669 type_name: EcoString, 4670 type_module: EcoString, 4671 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4672 ) -> Self { 4673 Self { 4674 type_name, 4675 type_module, 4676 printer, 4677 compiler, 4678 } 4679 } 4680 4681 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString { 4682 let module_name = self.printer.print_module(DECODE_MODULE); 4683 if type_.is_bit_array() { 4684 eco_format!("{module_name}.bit_array") 4685 } else if type_.is_bool() { 4686 eco_format!("{module_name}.bool") 4687 } else if type_.is_float() { 4688 eco_format!("{module_name}.float") 4689 } else if type_.is_int() { 4690 eco_format!("{module_name}.int") 4691 } else if type_.is_string() { 4692 eco_format!("{module_name}.string") 4693 } else if type_.is_nil() { 4694 eco_format!("{module_name}.success(Nil)") 4695 } else { 4696 match type_.tuple_types() { 4697 Some(types) => { 4698 let fields = types 4699 .iter() 4700 .enumerate() 4701 .map(|(index, type_)| RecordField { 4702 type_, 4703 label: RecordLabel::Unlabeled(index), 4704 }) 4705 .collect_vec(); 4706 let decoders = fields 4707 .iter() 4708 .map(|field| self.decode_field(field, indent + 2)) 4709 .join("\n"); 4710 let mut field_names = fields.iter().map(|field| field.label.variable_name()); 4711 4712 eco_format!( 4713 "{{ 4714{decoders} 4715 4716{indent} {module_name}.success(#({fields})) 4717{indent}}}", 4718 fields = field_names.join(", "), 4719 indent = " ".repeat(indent) 4720 ) 4721 } 4722 _ => { 4723 let type_information = type_.named_type_information(); 4724 let type_information = 4725 type_information.as_ref().map(|(module, name, arguments)| { 4726 (module.as_str(), name.as_str(), arguments.as_slice()) 4727 }); 4728 4729 match type_information { 4730 Some(("gleam/dynamic", "Dynamic", _)) => { 4731 eco_format!("{module_name}.dynamic") 4732 } 4733 Some(("gleam", "List", [element])) => { 4734 eco_format!("{module_name}.list({})", self.decoder_for(element, indent)) 4735 } 4736 Some(("gleam/option", "Option", [some])) => { 4737 eco_format!( 4738 "{module_name}.optional({})", 4739 self.decoder_for(some, indent) 4740 ) 4741 } 4742 Some(("gleam/dict", "Dict", [key, value])) => { 4743 eco_format!( 4744 "{module_name}.dict({}, {})", 4745 self.decoder_for(key, indent), 4746 self.decoder_for(value, indent) 4747 ) 4748 } 4749 Some((module, name, _)) 4750 if module == self.type_module && name == self.type_name => 4751 { 4752 eco_format!("{}_decoder()", to_snake_case(name)) 4753 } 4754 _ => eco_format!( 4755 r#"todo as "Decoder for {}""#, 4756 self.printer.print_type(type_) 4757 ), 4758 } 4759 } 4760 } 4761 } 4762 } 4763 4764 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4765 let decoder = self.decoder_for(field.type_, indent); 4766 4767 eco_format!( 4768 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#, 4769 indent = " ".repeat(indent), 4770 variable = field.label.variable_name(), 4771 field = field.label.field_key(), 4772 module = self.printer.print_module(DECODE_MODULE) 4773 ) 4774 } 4775 4776 /// Performs best-effort generation of zero values for a given type. 4777 /// Will succeed for all prelude types and most stdlib types. 4778 /// For specifics on how custom user-defined types are handled, see 4779 /// `zero_value_for_custom_type`. 4780 fn zero_value_for_type( 4781 &mut self, 4782 type_: &Type, 4783 // Keeps track of types visited already to ensure we don't end up in an infinite 4784 // loop due to recursive types 4785 path: &mut Vec<(EcoString, EcoString)>, 4786 modules_to_import: &mut HashSet<EcoString>, 4787 ) -> Option<EcoString> { 4788 if type_.is_bit_array() { 4789 return Some("<<>>".into()); 4790 } 4791 if type_.is_bool() { 4792 return Some("False".into()); 4793 } 4794 if type_.is_float() { 4795 return Some("0.0".into()); 4796 } 4797 if type_.is_int() { 4798 return Some("0".into()); 4799 } 4800 if type_.is_string() { 4801 return Some("\"\"".into()); 4802 } 4803 if type_.is_list() { 4804 return Some("[]".into()); 4805 } 4806 if type_.is_nil() { 4807 return Some("Nil".into()); 4808 } 4809 4810 if let Some(types) = type_.tuple_types() { 4811 // We try generating zero values for the tuple members and exit early if any of them fail. 4812 let field_zeroes = types 4813 .iter() 4814 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import)) 4815 .collect_vec(); 4816 4817 if field_zeroes.len() < types.len() { 4818 return None; 4819 } else { 4820 return Some(eco_format!("#({})", field_zeroes.iter().join(", "))); 4821 } 4822 }; 4823 4824 let (module, name, _) = type_.named_type_information()?; 4825 match (module.as_str(), name.as_str()) { 4826 (OPTION_MODULE, "Option") => { 4827 let _ = modules_to_import.insert(OPTION_MODULE.into()); 4828 Some(eco_format!( 4829 "{}.None", 4830 self.printer.print_module(OPTION_MODULE) 4831 )) 4832 } 4833 4834 (DYNAMIC_MODULE, "Dynamic") => { 4835 let _ = modules_to_import.insert(DYNAMIC_MODULE.into()); 4836 Some(eco_format!( 4837 "{}.nil()", 4838 self.printer.print_module(DYNAMIC_MODULE) 4839 )) 4840 } 4841 4842 (DICT_MODULE, "Dict") => { 4843 let _ = modules_to_import.insert(DICT_MODULE.into()); 4844 Some(eco_format!( 4845 "{}.new()", 4846 self.printer.print_module(DICT_MODULE) 4847 )) 4848 } 4849 4850 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import), 4851 } 4852 } 4853 4854 /// Best-effort zero value generation for user-defined types in the 4855 /// current package. 4856 fn zero_value_for_custom_type( 4857 &mut self, 4858 custom_type_module: &EcoString, 4859 custom_type_name: &EcoString, 4860 path: &mut Vec<(EcoString, EcoString)>, 4861 modules_to_import: &mut HashSet<EcoString>, 4862 ) -> Option<EcoString> { 4863 // First we check that we have not already visited this type before to 4864 // avoid cycles. 4865 let already_seen_type = path.iter().any(|(path_module, path_type_name)| { 4866 path_module == custom_type_module && path_type_name == custom_type_name 4867 }); 4868 4869 if already_seen_type { 4870 return None; 4871 }; 4872 4873 let type_is_inside_current_module = &self.type_module == custom_type_module; 4874 4875 let current_module_interface = self 4876 .compiler 4877 .modules 4878 .get(&self.type_module) 4879 .map(|module| &module.ast.type_info)?; 4880 4881 let type_module_interface = if !type_is_inside_current_module { 4882 self.compiler.get_module_interface(custom_type_module)? 4883 } else { 4884 current_module_interface 4885 }; 4886 4887 // We only try and generate zero values for user-defined types in the current 4888 // package. If you are expanding the scope of this functionality to 4889 // remove this limitation, make sure to check for internal modules. 4890 if current_module_interface.package != type_module_interface.package { 4891 return None; 4892 } 4893 4894 let constructors = type_module_interface 4895 .types_value_constructors 4896 .get(custom_type_name)?; 4897 4898 // Opaque types cannot be constructed outside the module they were defined in, 4899 // so we will be unable to produce a zero value. 4900 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque { 4901 return None; 4902 } 4903 4904 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor 4905 // to construct our zero value to reduce visual noise, but this might not always 4906 // be possible. So we check all constructors in increasing order of size to 4907 // find the first one that succeeds, and then short circuit. 4908 constructors 4909 .variants 4910 .iter() 4911 .sorted_by_key(|v| v.parameters.len()) 4912 .find_map(|zero_constructor| { 4913 self.zero_value_for_custom_type_constructor( 4914 custom_type_module, 4915 custom_type_name, 4916 zero_constructor, 4917 type_is_inside_current_module, 4918 path, 4919 modules_to_import, 4920 ) 4921 }) 4922 } 4923 4924 /// Attempts to construct a zero value for one specific constructor 4925 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs 4926 /// _important checks on type visibility that this method does NOT_. 4927 /// This is a helper method extracted for readability. 4928 fn zero_value_for_custom_type_constructor( 4929 &mut self, 4930 custom_type_module: &EcoString, 4931 custom_type_name: &EcoString, 4932 zero_constructor: &type_::TypeValueConstructor, 4933 type_is_inside_current_module: bool, 4934 path: &mut Vec<(EcoString, EcoString)>, 4935 modules_to_import: &mut HashSet<EcoString>, 4936 ) -> Option<EcoString> { 4937 path.push((custom_type_module.clone(), custom_type_name.clone())); 4938 4939 // Try to generate zero values for all fields in the constructor 4940 let zero_params = zero_constructor 4941 .parameters 4942 .iter() 4943 .map_while(|parameter| { 4944 let zero = self.zero_value_for_type(&parameter.type_, path, modules_to_import)?; 4945 4946 if let Some(label) = &parameter.label { 4947 Some(eco_format!("{label}: {zero}")) 4948 } else { 4949 Some(zero) 4950 } 4951 }) 4952 .collect_vec(); 4953 4954 // We need to make sure to clean up the path once we've finished 4955 // "visiting" this type. 4956 let _ = path.pop(); 4957 4958 // Only proceed if we were able to construct every field successfully 4959 if zero_params.len() < zero_constructor.parameters.len() { 4960 return None; 4961 }; 4962 4963 let zero_constructor = if !type_is_inside_current_module { 4964 // Type constructors from other modules need to be qualified appropriately, 4965 // and they might need to be brought into scope. 4966 let _ = modules_to_import.insert(custom_type_module.clone()); 4967 eco_format!( 4968 "{}.{}", 4969 self.printer.print_module(custom_type_module), 4970 zero_constructor.name 4971 ) 4972 } else { 4973 eco_format!("{}", zero_constructor.name) 4974 }; 4975 4976 if zero_params.is_empty() { 4977 Some(eco_format!("{zero_constructor}")) 4978 } else { 4979 let zero_args = zero_params.iter().join(", "); 4980 Some(eco_format!("{zero_constructor}({zero_args})")) 4981 } 4982 } 4983} 4984 4985/// Builder for code action to apply the "Generate to-JSON function" action. 4986/// 4987pub struct GenerateJsonEncoder<'a> { 4988 module: &'a Module, 4989 params: &'a CodeActionParams, 4990 edits: TextEdits<'a>, 4991 printer: Printer<'a>, 4992 actions: &'a mut Vec<CodeAction>, 4993 config: &'a PackageConfig, 4994} 4995 4996const JSON_MODULE: &str = "gleam/json"; 4997const JSON_PACKAGE_NAME: &str = "gleam_json"; 4998 4999#[derive(Eq, PartialEq, Copy, Clone)] 5000enum EncodingMode { 5001 PlainString, 5002 ObjectWithTypeTag, 5003 ObjectWithNoTypeTag, 5004} 5005 5006impl EncodingMode { 5007 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self { 5008 match type_.constructors.as_slice() { 5009 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString, 5010 [_constructor] => EncodingMode::ObjectWithNoTypeTag, 5011 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => { 5012 EncodingMode::PlainString 5013 } 5014 _constructors => EncodingMode::ObjectWithTypeTag, 5015 } 5016 } 5017} 5018 5019impl<'a> GenerateJsonEncoder<'a> { 5020 pub fn new( 5021 module: &'a Module, 5022 line_numbers: &'a LineNumbers, 5023 params: &'a CodeActionParams, 5024 actions: &'a mut Vec<CodeAction>, 5025 config: &'a PackageConfig, 5026 ) -> Self { 5027 // Since we are generating a new function, type variables from other 5028 // functions and constants are irrelevant to the types we print. 5029 let printer = Printer::new_without_type_variables(&module.ast.names); 5030 Self { 5031 module, 5032 params, 5033 edits: TextEdits::new(line_numbers), 5034 printer, 5035 actions, 5036 config, 5037 } 5038 } 5039 5040 pub fn code_actions(&mut self) { 5041 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME) 5042 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME) 5043 { 5044 self.visit_typed_module(&self.module.ast); 5045 } 5046 } 5047 5048 fn custom_type_encoder_body( 5049 &mut self, 5050 record_name: EcoString, 5051 custom_type: &CustomType<Arc<Type>>, 5052 ) -> Option<EcoString> { 5053 // We cannot generate a decoder for an external type with no constructors! 5054 let constructors_size = custom_type.constructors.len(); 5055 let (first, rest) = custom_type.constructors.split_first()?; 5056 let mode = EncodingMode::for_custom_type(custom_type); 5057 5058 // We generate an encoder for a type with a single constructor: it does not 5059 // require pattern matching on the argument as we can access all its fields 5060 // with the usual record access syntax. 5061 if rest.is_empty() { 5062 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?; 5063 let unpacking = if first.arguments.is_empty() { 5064 "" 5065 } else { 5066 &eco_format!( 5067 "let {name}({fields}:) = {record_name}\n ", 5068 name = first.name, 5069 fields = first 5070 .arguments 5071 .iter() 5072 .filter_map(|argument| { 5073 argument.label.as_ref().map(|(_location, label)| label) 5074 }) 5075 .join(":, ") 5076 ) 5077 }; 5078 return Some(eco_format!("{unpacking}{encoder}")); 5079 } 5080 5081 // Otherwise we generate an encoder for a type with multiple constructors: 5082 // it will need to pattern match on the various constructors and encode each 5083 // one separately. 5084 let mut clauses = Vec::with_capacity(constructors_size); 5085 for constructor in iter::once(first).chain(rest) { 5086 let RecordConstructor { name, .. } = constructor; 5087 let encoder = 5088 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?; 5089 if constructor.arguments.is_empty() { 5090 clauses.push(eco_format!(" {name} -> {encoder}")); 5091 } else { 5092 let unpacking = constructor 5093 .arguments 5094 .iter() 5095 .filter_map(|argument| { 5096 argument.label.as_ref().map(|(_location, label)| { 5097 if is_nil_like(&argument.type_) { 5098 eco_format!("{}: _", label) 5099 } else { 5100 eco_format!("{}:", label) 5101 } 5102 }) 5103 }) 5104 .join(", "); 5105 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}")); 5106 } 5107 } 5108 5109 let clauses = clauses.join("\n"); 5110 Some(eco_format!( 5111 "case {record_name} {{ 5112{clauses} 5113 }}", 5114 )) 5115 } 5116 5117 fn constructor_encoder( 5118 &mut self, 5119 mode: EncodingMode, 5120 constructor: &TypedRecordConstructor, 5121 type_name: EcoString, 5122 nesting: usize, 5123 ) -> Option<EcoString> { 5124 let json_module = self.printer.print_module(JSON_MODULE); 5125 let tag = to_snake_case(&constructor.name); 5126 let indent = " ".repeat(nesting); 5127 5128 // If the variant is encoded as a simple json string we just call the 5129 // `json.string` with the variant tag as an argument. 5130 if mode == EncodingMode::PlainString { 5131 return Some(eco_format!("{json_module}.string(\"{tag}\")")); 5132 } 5133 5134 // Otherwise we turn it into an object with a `type` tag field. 5135 let mut encoder_printer = 5136 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone()); 5137 5138 // These are the fields of the json object to encode. 5139 let mut fields = Vec::with_capacity(constructor.arguments.len()); 5140 if mode == EncodingMode::ObjectWithTypeTag { 5141 // Any needed type tag is always going to be the first field in the object 5142 fields.push(eco_format!( 5143 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))" 5144 )); 5145 } 5146 5147 for argument in constructor.arguments.iter() { 5148 let (_, label) = argument.label.as_ref()?; 5149 let field = RecordField { 5150 label: RecordLabel::Labeled(label), 5151 type_: &argument.type_, 5152 }; 5153 let encoder = encoder_printer.encode_field(&field, nesting + 2); 5154 fields.push(encoder); 5155 } 5156 5157 let fields = fields.join(",\n"); 5158 Some(eco_format!( 5159 "{json_module}.object([ 5160{fields}, 5161{indent}])" 5162 )) 5163 } 5164} 5165 5166/// When generating an encoder, we need to know when we can ignore the fields 5167/// destructured from a constructor. 5168/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil` 5169/// types, then we will never need to use the field in our encoder function. 5170fn is_nil_like(type_: &Type) -> bool { 5171 if type_.is_nil() { 5172 return true; 5173 } 5174 5175 match type_.tuple_types() { 5176 Some(types) => types.iter().all(|type_| is_nil_like(type_)), 5177 _ => false, 5178 } 5179} 5180 5181impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> { 5182 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 5183 let range = self 5184 .edits 5185 .src_span_to_lsp_range(custom_type.full_location()); 5186 if !within(self.params.range, range) { 5187 return; 5188 } 5189 5190 let record_name = to_snake_case(&custom_type.name); 5191 let name = eco_format!("{record_name}_to_json"); 5192 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else { 5193 return; 5194 }; 5195 5196 let json_type = self.printer.print_type(&Type::Named { 5197 publicity: Publicity::Public, 5198 package: JSON_PACKAGE_NAME.into(), 5199 module: JSON_MODULE.into(), 5200 name: "Json".into(), 5201 arguments: vec![], 5202 inferred_variant: None, 5203 }); 5204 5205 let type_ = if custom_type.parameters.is_empty() { 5206 custom_type.name.clone() 5207 } else { 5208 let parameters = custom_type 5209 .parameters 5210 .iter() 5211 .map(|(_, name)| name) 5212 .join(", "); 5213 eco_format!("{}({})", custom_type.name, parameters) 5214 }; 5215 5216 let function = format!( 5217 " 5218 5219fn {name}({record_name}: {type_}) -> {json_type} {{ 5220 {encoder} 5221}}", 5222 ); 5223 5224 self.edits.insert(custom_type.end_position, function); 5225 maybe_import(&mut self.edits, self.module, JSON_MODULE); 5226 5227 CodeActionBuilder::new("Generate to-JSON function") 5228 .kind(CodeActionKind::Refactor) 5229 .preferred(false) 5230 .changes( 5231 self.params.text_document.uri.clone(), 5232 std::mem::take(&mut self.edits.edits), 5233 ) 5234 .push_to(self.actions); 5235 } 5236} 5237 5238struct JsonEncoderPrinter<'a, 'b> { 5239 printer: &'a mut Printer<'b>, 5240 /// The name of the root type we are printing an encoder for 5241 type_name: EcoString, 5242 /// The module name of the root type we are printing an encoder for 5243 type_module: EcoString, 5244} 5245 5246impl<'a, 'b> JsonEncoderPrinter<'a, 'b> { 5247 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self { 5248 Self { 5249 type_name, 5250 type_module, 5251 printer, 5252 } 5253 } 5254 5255 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString { 5256 let module_name = self.printer.print_module(JSON_MODULE); 5257 let is_capture = encoded_value == "_"; 5258 let maybe_capture = |mut function: EcoString| { 5259 if is_capture { 5260 function 5261 } else { 5262 function.push('('); 5263 function.push_str(encoded_value); 5264 function.push(')'); 5265 function 5266 } 5267 }; 5268 5269 if type_.is_bool() { 5270 maybe_capture(eco_format!("{module_name}.bool")) 5271 } else if type_.is_float() { 5272 maybe_capture(eco_format!("{module_name}.float")) 5273 } else if type_.is_int() { 5274 maybe_capture(eco_format!("{module_name}.int")) 5275 } else if type_.is_string() { 5276 maybe_capture(eco_format!("{module_name}.string")) 5277 } else if type_.is_nil() { 5278 if is_capture { 5279 eco_format!("fn(_) {{ {module_name}.null() }}") 5280 } else { 5281 eco_format!("{module_name}.null()") 5282 } 5283 } else { 5284 match type_.tuple_types() { 5285 Some(types) => { 5286 let (tuple, new_indent) = if is_capture { 5287 ("value", indent + 4) 5288 } else { 5289 (encoded_value, indent + 2) 5290 }; 5291 5292 // We need to iterate over all of the tuple's fields 5293 // to obtain an encoder for each one, so we reuse the 5294 // iteration to check whether this tuple can be ignored 5295 // in the encoder without calling `is_nil_like`. 5296 let mut encoders = Vec::new(); 5297 let all_values_are_nil = types.iter().enumerate().fold( 5298 true, 5299 |all_values_are_nil, (index, type_)| { 5300 encoders.push(self.encoder_for( 5301 &format!("{tuple}.{index}"), 5302 type_, 5303 new_indent, 5304 )); 5305 all_values_are_nil && is_nil_like(type_) 5306 }, 5307 ); 5308 5309 if is_capture { 5310 eco_format!( 5311 "fn({value}) {{ 5312{indent} {module_name}.preprocessed_array([ 5313{indent} {encoders}, 5314{indent} ]) 5315{indent}}}", 5316 value = if all_values_are_nil { "_" } else { "value" }, 5317 indent = " ".repeat(indent), 5318 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 5319 ) 5320 } else { 5321 eco_format!( 5322 "{module_name}.preprocessed_array([ 5323{indent} {encoders}, 5324{indent}])", 5325 indent = " ".repeat(indent), 5326 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 5327 ) 5328 } 5329 } 5330 _ => { 5331 let type_information = type_.named_type_information(); 5332 let type_information: Option<(&str, &str, &[Arc<Type>])> = 5333 type_information.as_ref().map(|(module, name, arguments)| { 5334 (module.as_str(), name.as_str(), arguments.as_slice()) 5335 }); 5336 5337 match type_information { 5338 Some(("gleam", "List", [element])) => { 5339 eco_format!( 5340 "{module_name}.array({encoded_value}, {map_function})", 5341 map_function = self.encoder_for("_", element, indent) 5342 ) 5343 } 5344 Some(("gleam/option", "Option", [some])) => { 5345 eco_format!( 5346 "case {encoded_value} {{ 5347{indent} {none} -> {module_name}.null() 5348{indent} {some}({value}) -> {encoder} 5349{indent}}}", 5350 indent = " ".repeat(indent), 5351 none = self 5352 .printer 5353 .print_constructor(&"gleam/option".into(), &"None".into()), 5354 some = self 5355 .printer 5356 .print_constructor(&"gleam/option".into(), &"Some".into()), 5357 value = if is_nil_like(some) { "_" } else { "value" }, 5358 encoder = self.encoder_for("value", some, indent + 2) 5359 ) 5360 } 5361 Some(("gleam/dict", "Dict", [key, value])) => { 5362 let stringify_function = match key 5363 .named_type_information() 5364 .as_ref() 5365 .map(|(module, name, arguments)| { 5366 (module.as_str(), name.as_str(), arguments.as_slice()) 5367 }) { 5368 Some(("gleam", "String", [])) => "fn(string) { string }", 5369 _ => &format!( 5370 r#"todo as "Function to stringify {}""#, 5371 self.printer.print_type(key) 5372 ), 5373 }; 5374 eco_format!( 5375 "{module_name}.dict({encoded_value}, {stringify_function}, {})", 5376 self.encoder_for("_", value, indent) 5377 ) 5378 } 5379 Some((module, name, _)) 5380 if module == self.type_module && name == self.type_name => 5381 { 5382 maybe_capture(eco_format!("{}_to_json", to_snake_case(name))) 5383 } 5384 _ => eco_format!( 5385 r#"todo as "Encoder for {}""#, 5386 self.printer.print_type(type_) 5387 ), 5388 } 5389 } 5390 } 5391 } 5392 } 5393 5394 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 5395 let field_name = field.label.variable_name(); 5396 let encoder = self.encoder_for(&field_name, field.type_, indent); 5397 5398 eco_format!( 5399 r#"{indent}#("{field_name}", {encoder})"#, 5400 indent = " ".repeat(indent), 5401 ) 5402 } 5403} 5404 5405/// Builder for code action to pattern match on things like (anonymous) function 5406/// arguments or variables. 5407/// For example: 5408/// 5409/// ```gleam 5410/// pub fn wibble(arg: #(key, value)) { 5411/// // ^ [pattern match on argument] 5412/// } 5413/// 5414/// // Generates 5415/// 5416/// pub fn wibble(arg: #(key, value)) { 5417/// let #(value_0, value_1) = arg 5418/// } 5419/// ``` 5420/// 5421/// Another example with variables: 5422/// 5423/// ```gleam 5424/// pub fn main() { 5425/// let pair = #(1, 3) 5426/// // ^ [pattern match on value] 5427/// } 5428/// 5429/// // Generates 5430/// 5431/// pub fn main() { 5432/// let pair = #(1, 3) 5433/// let #(value_0, value_1) = pair 5434/// } 5435/// ``` 5436/// 5437pub struct PatternMatchOnValue<'a, A> { 5438 module: &'a Module, 5439 params: &'a CodeActionParams, 5440 compiler: &'a LspProjectCompiler<A>, 5441 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>, 5442 selected_value: Option<PatternMatchedValue<'a>>, 5443 edits: TextEdits<'a>, 5444} 5445 5446/// A value we might want to pattern match on. 5447/// Each variant will also contain all the info needed to know how to properly 5448/// print and format the corresponding pattern matching code; that's why you'll 5449/// see `Range`s and `SrcSpan` besides the type of the thing being matched. 5450/// 5451#[derive(Clone)] 5452pub enum PatternMatchedValue<'a> { 5453 FunctionArgument { 5454 /// The argument being pattern matched on. 5455 /// 5456 arg: &'a TypedArg, 5457 /// The first statement inside the function body. Used to correctly 5458 /// position the inserted pattern matching. 5459 /// 5460 first_statement: &'a TypedStatement, 5461 /// The range of the entire function holding the argument. 5462 /// 5463 function_range: Range, 5464 }, 5465 LetVariable { 5466 variable_name: &'a EcoString, 5467 variable_type: Arc<Type>, 5468 /// The location of the entire let assignment the variable is part of, 5469 /// so that we can add the pattern matching _after_ it. 5470 /// 5471 assignment_location: SrcSpan, 5472 }, 5473 /// A variable that is bound in a case branch's pattern. For example: 5474 /// ```gleam 5475 /// case wibble { 5476 /// wobble -> 1 5477 /// // ^^^^^ This! 5478 /// } 5479 /// ``` 5480 /// 5481 ClausePatternVariable { 5482 variable_type: Arc<Type>, 5483 variable_location: PatternLocation, 5484 clause_location: SrcSpan, 5485 /// All the names in the clause that are already taken by pattern variables. 5486 /// We need this to avoid generating invalid code were two pattern variables 5487 /// have the same name. 5488 /// 5489 /// For example: 5490 /// 5491 /// ```gleam 5492 /// case wibble { 5493 /// [first, ..rest] -> todo 5494 /// ^^^^^ When expanding `first` we can't add any variable pattern 5495 /// called `rest` as it would clash with the `rest` tail that is 5496 /// already there. 5497 /// } 5498 /// ``` 5499 /// 5500 bound_variables: Vec<BoundVariable>, 5501 }, 5502 UseVariable { 5503 variable_name: &'a EcoString, 5504 variable_type: Arc<Type>, 5505 /// The location of the entire use expression the variable is part of, 5506 /// so that we can add the pattern matching _after_ it. 5507 /// 5508 use_location: SrcSpan, 5509 }, 5510} 5511 5512#[derive(Clone)] 5513pub enum PatternLocation { 5514 /// Any pattern that doesn't need any special handling. 5515 /// 5516 Regular { location: SrcSpan }, 5517 /// List tails need some care to not generate invalid syntax when pattern 5518 /// matched on in case expressions. 5519 /// 5520 ListTail { 5521 /// This location covers the entire list tail pattern, including the `..` 5522 location: SrcSpan, 5523 }, 5524 /// When the pattern being matched is a discard. For example: 5525 /// ```gleam 5526 /// case wibble { 5527 /// Ok(_) -> todo 5528 /// // ^ Hovering this! 5529 /// } 5530 /// ``` 5531 Discard { location: SrcSpan }, 5532} 5533 5534impl PatternLocation { 5535 fn regular(location: SrcSpan) -> Self { 5536 Self::Regular { location } 5537 } 5538 5539 fn is_discard(&self) -> bool { 5540 match self { 5541 PatternLocation::Regular { .. } | PatternLocation::ListTail { .. } => false, 5542 PatternLocation::Discard { .. } => true, 5543 } 5544 } 5545} 5546 5547impl<'a, IO> PatternMatchOnValue<'a, IO> { 5548 pub fn new( 5549 module: &'a Module, 5550 line_numbers: &'a LineNumbers, 5551 params: &'a CodeActionParams, 5552 compiler: &'a LspProjectCompiler<IO>, 5553 ) -> Self { 5554 Self { 5555 module, 5556 params, 5557 compiler, 5558 selected_value: None, 5559 pattern_variable_under_cursor: None, 5560 edits: TextEdits::new(line_numbers), 5561 } 5562 } 5563 5564 pub fn code_actions(mut self) -> Vec<CodeAction> { 5565 self.visit_typed_module(&self.module.ast); 5566 5567 let action_title = match self.selected_value.clone() { 5568 Some(PatternMatchedValue::FunctionArgument { 5569 arg, 5570 first_statement: function_body, 5571 function_range, 5572 }) => { 5573 self.match_on_function_argument(arg, function_body, function_range); 5574 "Pattern match on argument" 5575 } 5576 Some( 5577 PatternMatchedValue::LetVariable { 5578 variable_name, 5579 variable_type, 5580 assignment_location: location, 5581 } 5582 | PatternMatchedValue::UseVariable { 5583 variable_name, 5584 variable_type, 5585 use_location: location, 5586 }, 5587 ) => { 5588 self.match_on_let_variable(variable_name, variable_type, location); 5589 "Pattern match on variable" 5590 } 5591 5592 Some(PatternMatchedValue::ClausePatternVariable { 5593 variable_type, 5594 variable_location, 5595 clause_location, 5596 bound_variables, 5597 }) => { 5598 let title = if variable_location.is_discard() { 5599 "Pattern match on value" 5600 } else { 5601 "Pattern match on variable" 5602 }; 5603 5604 self.match_on_clause_variable( 5605 variable_type, 5606 variable_location, 5607 clause_location, 5608 &bound_variables, 5609 ); 5610 5611 title 5612 } 5613 5614 None => return vec![], 5615 }; 5616 5617 if self.edits.edits.is_empty() { 5618 return vec![]; 5619 } 5620 5621 let mut action = Vec::with_capacity(1); 5622 CodeActionBuilder::new(action_title) 5623 .kind(CodeActionKind::RefactorRewrite) 5624 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5625 .preferred(false) 5626 .push_to(&mut action); 5627 action 5628 } 5629 5630 fn match_on_function_argument( 5631 &mut self, 5632 arg: &TypedArg, 5633 first_statement: &TypedStatement, 5634 function_range: Range, 5635 ) { 5636 let Some(arg_name) = arg.get_variable_name() else { 5637 return; 5638 }; 5639 5640 let Some(patterns) = 5641 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new()) 5642 else { 5643 return; 5644 }; 5645 5646 let first_statement_location = first_statement.location(); 5647 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location); 5648 5649 // If we're trying to insert the pattern matching on the same 5650 // line as the one where the function is defined we will want to 5651 // put it on a new line instead. So in that case the nesting will 5652 // be the default 2 spaces. 5653 let needs_newline = function_range.start.line == first_statement_range.start.line; 5654 let nesting = if needs_newline { 5655 String::from(" ") 5656 } else { 5657 " ".repeat(first_statement_range.start.character as usize) 5658 }; 5659 5660 let pattern_matching = if patterns.len() == 1 { 5661 let pattern = patterns.first(); 5662 format!("let {pattern} = {arg_name}") 5663 } else { 5664 let patterns = patterns 5665 .iter() 5666 .map(|p| format!(" {nesting}{p} -> todo")) 5667 .join("\n"); 5668 format!("case {arg_name} {{\n{patterns}\n{nesting}}}") 5669 }; 5670 5671 let pattern_matching = if needs_newline { 5672 format!("\n{nesting}{pattern_matching}") 5673 } else { 5674 pattern_matching 5675 }; 5676 5677 let has_empty_body = match first_statement { 5678 ast::Statement::Expression(TypedExpr::Todo { 5679 kind: TodoKind::EmptyFunction { .. }, 5680 .. 5681 }) => true, 5682 ast::Statement::Expression(_) 5683 | ast::Statement::Assignment(_) 5684 | ast::Statement::Use(_) 5685 | ast::Statement::Assert(_) => false, 5686 }; 5687 5688 // If the pattern matching is added to a function with an empty 5689 // body then we do not add any nesting after it, or we would be 5690 // increasing the nesting of the closing `}`! 5691 let pattern_matching = if has_empty_body { 5692 format!("{pattern_matching}\n") 5693 } else { 5694 format!("{pattern_matching}\n{nesting}") 5695 }; 5696 5697 self.edits 5698 .insert(first_statement_location.start, pattern_matching); 5699 } 5700 5701 fn match_on_let_variable( 5702 &mut self, 5703 variable_name: &EcoString, 5704 variable_type: Arc<Type>, 5705 assignment_location: SrcSpan, 5706 ) { 5707 let Some(patterns) = 5708 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new()) 5709 else { 5710 return; 5711 }; 5712 5713 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); 5714 let nesting = " ".repeat(assignment_range.start.character as usize); 5715 5716 let pattern_matching = if patterns.len() == 1 { 5717 let pattern = patterns.first(); 5718 format!("let {pattern} = {variable_name}") 5719 } else { 5720 let patterns = patterns 5721 .iter() 5722 .map(|p| format!(" {nesting}{p} -> todo")) 5723 .join("\n"); 5724 format!("case {variable_name} {{\n{patterns}\n{nesting}}}") 5725 }; 5726 5727 self.edits.insert( 5728 assignment_location.end, 5729 format!("\n{nesting}{pattern_matching}"), 5730 ); 5731 } 5732 5733 fn match_on_clause_variable( 5734 &mut self, 5735 variable_type: Arc<Type>, 5736 variable_location: PatternLocation, 5737 clause_location: SrcSpan, 5738 bound_variables: &[BoundVariable], 5739 ) { 5740 let mut names = NameGenerator::new(); 5741 names.reserve_bound_variables(bound_variables); 5742 5743 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) { 5744 // Here we're dealing with a special case: if someone wants to expand the tail 5745 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`. 5746 // That would result in invalid syntax. So we have to generate list patterns 5747 // that have no square brackets. 5748 let first = names.rename_to_avoid_shadowing("first".into()); 5749 let rest = names.rename_to_avoid_shadowing("rest".into()); 5750 vec1!["".into(), eco_format!("{first}, ..{rest}")] 5751 } else if let Some(patterns) = 5752 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names) 5753 { 5754 patterns 5755 } else { 5756 return; 5757 }; 5758 5759 let clause_range = self.edits.src_span_to_lsp_range(clause_location); 5760 let nesting = " ".repeat(clause_range.start.character as usize); 5761 5762 let variable_location = match variable_location { 5763 PatternLocation::Regular { location } 5764 | PatternLocation::ListTail { location } 5765 | PatternLocation::Discard { location } => location, 5766 }; 5767 5768 let variable_start = (variable_location.start - clause_location.start) as usize; 5769 let variable_end = variable_start + variable_location.len(); 5770 5771 let clause_code = code_at(self.module, clause_location); 5772 let patterns = patterns 5773 .iter() 5774 .map(|pattern| { 5775 let mut clause_code = clause_code.to_string(); 5776 // If we're replacing a variable that's using the shorthand 5777 // syntax we want to add a space to separate it from the 5778 // preceding `:`. 5779 let pattern = if variable_start == variable_end { 5780 &eco_format!(" {pattern}") 5781 } else { 5782 pattern 5783 }; 5784 5785 clause_code.replace_range(variable_start..variable_end, pattern); 5786 clause_code 5787 }) 5788 .join(&format!("\n{nesting}")); 5789 5790 self.edits.replace(clause_location, patterns); 5791 } 5792 5793 /// Will produce a pattern that can be used on the left hand side of a let 5794 /// assignment to destructure a value of the given type. For example given 5795 /// this type: 5796 /// 5797 /// ```gleam 5798 /// pub type Wibble { 5799 /// Wobble(Int, label: String) 5800 /// } 5801 /// ``` 5802 /// 5803 /// The produced pattern will look like this: `Wobble(value_0, label:)`. 5804 /// The pattern will use the correct qualified/unqualified name for the 5805 /// constructor if it comes from another package. 5806 /// 5807 /// The function will only produce a list of patterns that can be used from 5808 /// the current module. So if the type comes from another module it must be 5809 /// public! Otherwise this function will return an empty vec. 5810 /// 5811 fn type_to_destructure_patterns( 5812 &mut self, 5813 type_: &Type, 5814 names: &mut NameGenerator, 5815 ) -> Option<Vec1<EcoString>> { 5816 match type_ { 5817 Type::Fn { .. } => None, 5818 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names), 5819 5820 // We special case lists, they don't have "regular" constructors 5821 // like other types. Instead we always add the two clauses covering 5822 // the empty and non empty list. 5823 Type::Named { .. } if type_.is_list() => { 5824 let first = names.rename_to_avoid_shadowing("first".into()); 5825 let rest = names.rename_to_avoid_shadowing("rest".into()); 5826 Some(vec1![ 5827 EcoString::from("[]"), 5828 eco_format!("[{first}, ..{rest}]") 5829 ]) 5830 } 5831 5832 Type::Named { 5833 module: type_module, 5834 name: type_name, 5835 .. 5836 } => { 5837 let mut patterns = vec![]; 5838 let constructors = 5839 get_type_constructors(self.compiler, &self.module.name, type_module, type_name); 5840 for constructor in constructors { 5841 let names_before = names.clone(); 5842 if let Some(pattern) = 5843 self.record_constructor_to_destructure_pattern(constructor, names) 5844 { 5845 patterns.push(pattern); 5846 } 5847 *names = names_before; 5848 } 5849 5850 Vec1::try_from_vec(patterns).ok() 5851 } 5852 5853 // We don't want to suggest this action for empty tuple as it 5854 // doesn't make a lot of sense to match on those. 5855 Type::Tuple { elements } if elements.is_empty() => None, 5856 Type::Tuple { elements } => { 5857 let elements = elements 5858 .iter() 5859 .map(|element| names.generate_name_from_type(element)) 5860 .join(", "); 5861 Some(vec1![eco_format!("#({elements})")]) 5862 } 5863 } 5864 } 5865 5866 fn type_var_to_destructure_patterns( 5867 &mut self, 5868 type_var: &TypeVar, 5869 names: &mut NameGenerator, 5870 ) -> Option<Vec1<EcoString>> { 5871 match type_var { 5872 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, 5873 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names), 5874 } 5875 } 5876 5877 /// Given the value constructor of a record, returns a string with the 5878 /// pattern used to match on that specific variant. 5879 /// 5880 /// Note how: 5881 /// - If the constructor is internal to another module or comes from another 5882 /// module, then this returns `None` since one cannot pattern match on it. 5883 /// - If the provided `ValueConstructor` is not a record constructor this 5884 /// will return `None`. 5885 /// 5886 fn record_constructor_to_destructure_pattern( 5887 &self, 5888 constructor: &ValueConstructor, 5889 names: &mut NameGenerator, 5890 ) -> Option<EcoString> { 5891 let type_::ValueConstructorVariant::Record { 5892 name: constructor_name, 5893 arity: constructor_arity, 5894 module: constructor_module, 5895 field_map, 5896 .. 5897 } = &constructor.variant 5898 else { 5899 // The constructor should always be a record, in case it's not 5900 // there's not much we can do and just fail. 5901 return None; 5902 }; 5903 5904 // Since the constructor is a record constructor we know that its type 5905 // is either `Named` or a `Fn` type, in either case we have to get the 5906 // arguments types out of it. 5907 let Some(arguments_types) = constructor 5908 .type_ 5909 .fn_types() 5910 .map(|(arguments_types, _return)| arguments_types) 5911 .or_else(|| constructor.type_.constructor_types()) 5912 else { 5913 // This should never happen but just in case we don't want to unwrap 5914 // and panic. 5915 return None; 5916 }; 5917 5918 let index_to_label = match field_map { 5919 None => HashMap::new(), 5920 Some(field_map) => { 5921 names.reserve_all_labels(field_map); 5922 5923 field_map 5924 .fields 5925 .iter() 5926 .map(|(label, index)| (index, label)) 5927 .collect::<HashMap<_, _>>() 5928 } 5929 }; 5930 5931 let mut pattern = 5932 pretty_constructor_name(self.module, constructor_module, constructor_name)?; 5933 5934 if *constructor_arity == 0 { 5935 return Some(pattern); 5936 } 5937 5938 pattern.push('('); 5939 let arguments = (0..*constructor_arity as u32) 5940 .map(|i| match index_to_label.get(&i) { 5941 Some(label) => eco_format!("{label}:"), 5942 None => match arguments_types.get(i as usize) { 5943 None => names.rename_to_avoid_shadowing(EcoString::from("value")), 5944 Some(type_) => names.generate_name_from_type(type_), 5945 }, 5946 }) 5947 .join(", "); 5948 5949 pattern.push_str(&arguments); 5950 pattern.push(')'); 5951 Some(pattern) 5952 } 5953} 5954 5955fn code_at(module: &Module, span: SrcSpan) -> &str { 5956 module 5957 .code 5958 .get(span.start as usize..span.end as usize) 5959 .expect("code location must be valid") 5960} 5961 5962impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> { 5963 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 5964 // If we're not inside the function there's no point in exploring its 5965 // ast further. 5966 let function_span = SrcSpan { 5967 start: fun.location.start, 5968 end: fun.end_position, 5969 }; 5970 let function_range = self.edits.src_span_to_lsp_range(function_span); 5971 if !within(self.params.range, function_range) { 5972 return; 5973 } 5974 5975 for arg in &fun.arguments { 5976 // If the cursor is placed on one of the arguments, then we can try 5977 // and generate code for that one. 5978 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 5979 if within(self.params.range, arg_range) 5980 && let Some(first_statement) = fun.body.first() 5981 { 5982 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 5983 arg, 5984 first_statement, 5985 function_range, 5986 }); 5987 return; 5988 } 5989 } 5990 5991 // If the cursor is not on any of the function arguments then we keep 5992 // exploring the function body as we might want to destructure the 5993 // argument of an expression function! 5994 ast::visit::visit_typed_function(self, fun); 5995 } 5996 5997 fn visit_typed_expr_fn( 5998 &mut self, 5999 location: &'ast SrcSpan, 6000 type_: &'ast Arc<Type>, 6001 kind: &'ast FunctionLiteralKind, 6002 arguments: &'ast [TypedArg], 6003 body: &'ast Vec1<TypedStatement>, 6004 return_annotation: &'ast Option<ast::TypeAst>, 6005 ) { 6006 // If we're not inside the function there's no point in exploring its 6007 // ast further. 6008 let function_range = self.edits.src_span_to_lsp_range(*location); 6009 if !within(self.params.range, function_range) { 6010 return; 6011 } 6012 6013 for argument in arguments { 6014 // If the cursor is placed on one of the arguments, then we can try 6015 // and generate code for that one. 6016 let arg_range = self.edits.src_span_to_lsp_range(argument.location); 6017 if within(self.params.range, arg_range) { 6018 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 6019 arg: argument, 6020 first_statement: body.first(), 6021 function_range, 6022 }); 6023 return; 6024 } 6025 } 6026 6027 // If the cursor is not on any of the function arguments then we keep 6028 // exploring the function body as we might want to destructure the 6029 // argument of an expression function! 6030 ast::visit::visit_typed_expr_fn( 6031 self, 6032 location, 6033 type_, 6034 kind, 6035 arguments, 6036 body, 6037 return_annotation, 6038 ); 6039 } 6040 6041 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 6042 // If we're not inside the assignment there's no point in exploring its 6043 // ast further. 6044 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 6045 if !within(self.params.range, assignment_range) { 6046 return; 6047 } 6048 6049 ast::visit::visit_typed_assignment(self, assignment); 6050 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor 6051 // We must make sure that no other value was selected while visiting 6052 // this. If it were `Some` that means that we have found _another_ 6053 // variable to match on inside the assignmemt itself. For example: 6054 // ```gleam 6055 // let a = { 6056 // let b = todo 6057 // // ^ We're matching on this, not the outer one! 6058 // } 6059 // ``` 6060 && self.selected_value.is_none() 6061 { 6062 self.selected_value = Some(PatternMatchedValue::LetVariable { 6063 variable_name: name, 6064 variable_type: type_.clone(), 6065 assignment_location: assignment.location, 6066 }); 6067 } 6068 } 6069 6070 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 6071 // If we're not inside the clause there's no point in exploring its 6072 // ast further. 6073 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 6074 if !within(self.params.range, clause_range) { 6075 return; 6076 } 6077 6078 for pattern in clause.pattern.iter() { 6079 self.visit_typed_pattern(pattern); 6080 } 6081 for patterns in clause.alternative_patterns.iter() { 6082 for pattern in patterns { 6083 self.visit_typed_pattern(pattern); 6084 } 6085 } 6086 6087 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() { 6088 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable { 6089 variable_type: type_, 6090 variable_location, 6091 clause_location: clause.location(), 6092 bound_variables: clause.bound_variables().collect_vec(), 6093 }); 6094 } else { 6095 self.visit_typed_expr(&clause.then); 6096 } 6097 } 6098 6099 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 6100 if let Some(assignments) = use_.callback_arguments() { 6101 for variable in assignments { 6102 let ast::Arg { 6103 names: ArgNames::Named { name, .. }, 6104 location: variable_location, 6105 type_, 6106 .. 6107 } = variable 6108 else { 6109 continue; 6110 }; 6111 6112 // If we use a pattern in a use assignment, that will end up 6113 // being called `_use` something. We don't want to offer the 6114 // action when hovering a pattern so we ignore those. 6115 if name.starts_with("_use") { 6116 continue; 6117 } 6118 6119 let variable_range = self.edits.src_span_to_lsp_range(*variable_location); 6120 if within(self.params.range, variable_range) { 6121 self.selected_value = Some(PatternMatchedValue::UseVariable { 6122 variable_name: name, 6123 variable_type: type_.clone(), 6124 use_location: use_.location, 6125 }); 6126 // If we've found the variable to pattern match on, there's no 6127 // point in keeping traversing the AST. 6128 return; 6129 } 6130 } 6131 } 6132 6133 ast::visit::visit_typed_use(self, use_); 6134 } 6135 6136 fn visit_typed_pattern_discard( 6137 &mut self, 6138 location: &'ast SrcSpan, 6139 name: &'ast EcoString, 6140 type_: &'ast Arc<Type>, 6141 ) { 6142 if within( 6143 self.params.range, 6144 self.edits.src_span_to_lsp_range(*location), 6145 ) { 6146 let location = PatternLocation::Discard { 6147 location: *location, 6148 }; 6149 self.pattern_variable_under_cursor = Some((name, location, type_.clone())); 6150 } 6151 } 6152 6153 fn visit_typed_pattern_variable( 6154 &mut self, 6155 location: &'ast SrcSpan, 6156 name: &'ast EcoString, 6157 type_: &'ast Arc<Type>, 6158 _origin: &'ast VariableOrigin, 6159 ) { 6160 if within( 6161 self.params.range, 6162 self.edits.src_span_to_lsp_range(*location), 6163 ) { 6164 let location = PatternLocation::regular(*location); 6165 self.pattern_variable_under_cursor = Some((name, location, type_.clone())); 6166 } 6167 } 6168 6169 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 6170 if let Some(name) = arg.label_shorthand_name() 6171 && within( 6172 self.params.range, 6173 self.edits.src_span_to_lsp_range(arg.location), 6174 ) 6175 { 6176 let location = PatternLocation::regular(SrcSpan { 6177 start: arg.location.end, 6178 end: arg.location.end, 6179 }); 6180 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_())); 6181 return; 6182 } 6183 6184 ast::visit::visit_typed_pattern_call_arg(self, arg); 6185 } 6186 6187 fn visit_typed_pattern_string_prefix( 6188 &mut self, 6189 _location: &'ast SrcSpan, 6190 _left_location: &'ast SrcSpan, 6191 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 6192 right_location: &'ast SrcSpan, 6193 _left_side_string: &'ast EcoString, 6194 right_side_assignment: &'ast AssignName, 6195 ) { 6196 if let Some((name, location)) = left_side_assignment 6197 && within( 6198 self.params.range, 6199 self.edits.src_span_to_lsp_range(*location), 6200 ) 6201 { 6202 let location = PatternLocation::regular(*location); 6203 self.pattern_variable_under_cursor = Some((name, location, type_::string())); 6204 } else if let AssignName::Variable(name) = right_side_assignment 6205 && within( 6206 self.params.range, 6207 self.edits.src_span_to_lsp_range(*right_location), 6208 ) 6209 { 6210 let location = PatternLocation::regular(*right_location); 6211 self.pattern_variable_under_cursor = Some((name, location, type_::string())); 6212 } 6213 } 6214 6215 fn visit_typed_pattern_list( 6216 &mut self, 6217 location: &'ast SrcSpan, 6218 elements: &'ast Vec<TypedPattern>, 6219 tail: &'ast Option<Box<TypedTailPattern>>, 6220 type_: &'ast Arc<Type>, 6221 ) { 6222 let (name, tail_location, tail_type) = if let Some(tail) = tail 6223 && let Pattern::Variable { name, type_, .. } = &tail.pattern 6224 { 6225 (name, tail.location, type_) 6226 } else { 6227 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); 6228 return; 6229 }; 6230 6231 let tail_range = self.edits.src_span_to_lsp_range(tail_location); 6232 if !within(self.params.range, tail_range) { 6233 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); 6234 return; 6235 } 6236 6237 let location = PatternLocation::ListTail { 6238 location: tail_location, 6239 }; 6240 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone())) 6241 } 6242} 6243 6244/// Given a type and its module, returns a list of its *importable* 6245/// constructors. 6246/// 6247/// Since this focuses just on importable constructors, if either the module or 6248/// the type are internal the returned array will be empty! 6249/// 6250fn get_type_constructors<'a, 'b, IO>( 6251 compiler: &'a LspProjectCompiler<IO>, 6252 current_module: &'b EcoString, 6253 type_module: &'b EcoString, 6254 type_name: &'b EcoString, 6255) -> Vec<&'a ValueConstructor> { 6256 let type_is_inside_current_module = current_module == type_module; 6257 let module_interface = if !type_is_inside_current_module { 6258 // If the type is outside of the module we're in, we can only pattern 6259 // match on it if the module can be imported. 6260 // The `get_module_interface` already takes care of making this check. 6261 compiler.get_module_interface(type_module) 6262 } else { 6263 // However, if the type is defined in the module we're in, we can always 6264 // pattern match on it. So we get the current module's interface. 6265 compiler 6266 .modules 6267 .get(current_module) 6268 .map(|module| &module.ast.type_info) 6269 }; 6270 6271 let Some(module_interface) = module_interface else { 6272 return vec![]; 6273 }; 6274 6275 // If the type is in an internal module that is not the current one, we 6276 // cannot use its constructors! 6277 if !type_is_inside_current_module && module_interface.is_internal { 6278 return vec![]; 6279 } 6280 6281 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else { 6282 return vec![]; 6283 }; 6284 6285 constructors 6286 .variants 6287 .iter() 6288 .filter_map(|variant| { 6289 let constructor = module_interface.values.get(&variant.name)?; 6290 if type_is_inside_current_module || constructor.publicity.is_public() { 6291 Some(constructor) 6292 } else { 6293 None 6294 } 6295 }) 6296 .collect_vec() 6297} 6298 6299/// Returns a pretty printed record constructor name, the way it would be used 6300/// inside the given `module` (with the correct name and qualification). 6301/// 6302/// If the constructor cannot be used inside the module because it's not 6303/// imported, then this function will return `None`. 6304/// 6305fn pretty_constructor_name( 6306 module: &Module, 6307 constructor_module: &EcoString, 6308 constructor_name: &EcoString, 6309) -> Option<EcoString> { 6310 match module 6311 .ast 6312 .names 6313 .named_constructor(constructor_module, constructor_name) 6314 { 6315 type_::printer::NameContextInformation::Unimported(_, _) => None, 6316 type_::printer::NameContextInformation::Unqualified(constructor_name) => { 6317 Some(eco_format!("{constructor_name}")) 6318 } 6319 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => { 6320 Some(eco_format!("{module_name}.{constructor_name}")) 6321 } 6322 } 6323} 6324 6325/// Builder for the "generate function" code action. 6326/// Whenever someone hovers an invalid expression that is inferred to have a 6327/// function type the language server can generate a function definition for it. 6328/// For example: 6329/// 6330/// ```gleam 6331/// pub fn main() { 6332/// wibble(1, 2, "hello") 6333/// // ^ [generate function] 6334/// } 6335/// ``` 6336/// 6337/// Will generate the following definition: 6338/// 6339/// ```gleam 6340/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a { 6341/// todo 6342/// } 6343/// ``` 6344/// 6345pub struct GenerateFunction<'a> { 6346 module: &'a Module, 6347 modules: &'a std::collections::HashMap<EcoString, Module>, 6348 params: &'a CodeActionParams, 6349 edits: TextEdits<'a>, 6350 last_visited_definition_end: Option<u32>, 6351 function_to_generate: Option<FunctionToGenerate<'a>>, 6352} 6353 6354struct FunctionToGenerate<'a> { 6355 module: Option<&'a str>, 6356 name: &'a str, 6357 arguments_types: Vec<Arc<Type>>, 6358 6359 /// The arguments actually supplied as input to the function, if any. 6360 /// A function to generate might as well be just a name passed as an argument 6361 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually 6362 /// have any actual arguments! 6363 given_arguments: Option<&'a [TypedCallArg]>, 6364 return_type: Arc<Type>, 6365 previous_definition_end: Option<u32>, 6366} 6367 6368impl<'a> GenerateFunction<'a> { 6369 pub fn new( 6370 module: &'a Module, 6371 modules: &'a std::collections::HashMap<EcoString, Module>, 6372 line_numbers: &'a LineNumbers, 6373 params: &'a CodeActionParams, 6374 ) -> Self { 6375 Self { 6376 module, 6377 modules, 6378 params, 6379 edits: TextEdits::new(line_numbers), 6380 last_visited_definition_end: None, 6381 function_to_generate: None, 6382 } 6383 } 6384 6385 pub fn code_actions(mut self) -> Vec<CodeAction> { 6386 self.visit_typed_module(&self.module.ast); 6387 6388 let Some( 6389 function_to_generate @ FunctionToGenerate { 6390 module, 6391 previous_definition_end: Some(insert_at), 6392 .. 6393 }, 6394 ) = self.function_to_generate.take() 6395 else { 6396 return vec![]; 6397 }; 6398 6399 if let Some(module) = module { 6400 if let Some(module) = self.modules.get(module) { 6401 let insert_at = module.code.len() as u32; 6402 self.code_action_for_module( 6403 module, 6404 Publicity::Public, 6405 function_to_generate, 6406 insert_at, 6407 ) 6408 } else { 6409 Vec::new() 6410 } 6411 } else { 6412 let module = self.module; 6413 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at) 6414 } 6415 } 6416 6417 fn code_action_for_module( 6418 mut self, 6419 module: &'a Module, 6420 publicity: Publicity, 6421 function_to_generate: FunctionToGenerate<'a>, 6422 insert_at: u32, 6423 ) -> Vec<CodeAction> { 6424 let FunctionToGenerate { 6425 name, 6426 arguments_types, 6427 given_arguments, 6428 return_type, 6429 .. 6430 } = function_to_generate; 6431 6432 // This might be triggered on variants as well, in that case we don't 6433 // want to offer this action. The "generate variant" action will be 6434 // offered instead. 6435 if !is_valid_lowercase_name(name) { 6436 return vec![]; 6437 } 6438 6439 // Labels do not share the same namespace as argument so we use two 6440 // separate generators to avoid renaming a label in case it shares a 6441 // name with an argument. 6442 let mut label_names = NameGenerator::new(); 6443 let mut argument_names = NameGenerator::new(); 6444 6445 // Since we are generating a new function, type variables from other 6446 // functions and constants are irrelevant to the types we print. 6447 let mut printer = Printer::new_without_type_variables(&module.ast.names); 6448 let arguments = arguments_types 6449 .iter() 6450 .enumerate() 6451 .map(|(index, argument_type)| { 6452 let call_argument = given_arguments.and_then(|arguments| arguments.get(index)); 6453 let (label, name) = 6454 argument_names.generate_label_and_name(call_argument, argument_type); 6455 let pretty_type = printer.print_type(argument_type); 6456 if let Some(label) = label { 6457 let label = label_names.rename_to_avoid_shadowing(label.clone()); 6458 format!("{label} {name}: {pretty_type}") 6459 } else { 6460 format!("{name}: {pretty_type}") 6461 } 6462 }) 6463 .join(", "); 6464 6465 let return_type = printer.print_type(&return_type); 6466 6467 let publicity = if publicity.is_public() { "pub " } else { "" }; 6468 6469 // Make sure we use the line number information of the module we are 6470 // editing, which might not be the module where the code action is 6471 // triggered. 6472 self.edits.line_numbers = &module.ast.type_info.line_numbers; 6473 self.edits.insert( 6474 insert_at, 6475 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"), 6476 ); 6477 6478 let Some(uri) = url_from_path(module.input_path.as_str()) else { 6479 return Vec::new(); 6480 }; 6481 let mut action = Vec::with_capacity(1); 6482 CodeActionBuilder::new("Generate function") 6483 .kind(CodeActionKind::QuickFix) 6484 .changes(uri, self.edits.edits) 6485 .preferred(true) 6486 .push_to(&mut action); 6487 action 6488 } 6489 6490 fn try_save_function_to_generate( 6491 &mut self, 6492 name: &'a EcoString, 6493 function_type: &Arc<Type>, 6494 given_arguments: Option<&'a [TypedCallArg]>, 6495 ) { 6496 match function_type.fn_types() { 6497 None => {} 6498 Some((arguments_types, return_type)) => { 6499 self.function_to_generate = Some(FunctionToGenerate { 6500 name, 6501 arguments_types, 6502 given_arguments, 6503 return_type, 6504 previous_definition_end: self.last_visited_definition_end, 6505 module: None, 6506 }) 6507 } 6508 } 6509 } 6510 6511 fn try_save_function_from_other_module( 6512 &mut self, 6513 module: &'a str, 6514 name: &'a str, 6515 function_type: &Arc<Type>, 6516 given_arguments: Option<&'a [TypedCallArg]>, 6517 ) { 6518 if let Some((arguments_types, return_type)) = function_type.fn_types() 6519 && is_valid_lowercase_name(name) 6520 { 6521 self.function_to_generate = Some(FunctionToGenerate { 6522 name, 6523 arguments_types, 6524 given_arguments, 6525 return_type, 6526 previous_definition_end: self.last_visited_definition_end, 6527 module: Some(module), 6528 }) 6529 } 6530 } 6531} 6532 6533impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> { 6534 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 6535 self.last_visited_definition_end = Some(fun.end_position); 6536 ast::visit::visit_typed_function(self, fun); 6537 } 6538 6539 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 6540 self.last_visited_definition_end = Some(constant.value.location().end); 6541 ast::visit::visit_typed_module_constant(self, constant); 6542 } 6543 6544 fn visit_typed_expr_invalid( 6545 &mut self, 6546 location: &'ast SrcSpan, 6547 type_: &'ast Arc<Type>, 6548 extra_information: &'ast Option<InvalidExpression>, 6549 ) { 6550 let invalid_range = self.edits.src_span_to_lsp_range(*location); 6551 if within(self.params.range, invalid_range) { 6552 match extra_information { 6553 Some(InvalidExpression::ModuleSelect { module_name, label }) => { 6554 self.try_save_function_from_other_module(module_name, label, type_, None) 6555 } 6556 Some(InvalidExpression::UnknownVariable { name }) => { 6557 self.try_save_function_to_generate(name, type_, None) 6558 } 6559 None => {} 6560 } 6561 } 6562 6563 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); 6564 } 6565 6566 fn visit_typed_constant_invalid( 6567 &mut self, 6568 location: &'ast SrcSpan, 6569 type_: &'ast Arc<Type>, 6570 extra_information: &'ast Option<InvalidExpression>, 6571 ) { 6572 let constant_range = self.edits.src_span_to_lsp_range(*location); 6573 if let Some(extra_information) = extra_information 6574 && within(self.params.range, constant_range) 6575 { 6576 match extra_information { 6577 InvalidExpression::ModuleSelect { module_name, label } => { 6578 self.try_save_function_from_other_module(module_name, label, type_, None) 6579 } 6580 InvalidExpression::UnknownVariable { name } => { 6581 self.try_save_function_to_generate(name, type_, None) 6582 } 6583 } 6584 } 6585 } 6586 6587 fn visit_typed_expr_call( 6588 &mut self, 6589 location: &'ast SrcSpan, 6590 type_: &'ast Arc<Type>, 6591 fun: &'ast TypedExpr, 6592 arguments: &'ast [TypedCallArg], 6593 argument_parentheses: &'ast Option<u32>, 6594 ) { 6595 // If the function being called is invalid we need to generate a 6596 // function that has the proper labels. 6597 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 6598 6599 if within(self.params.range, fun_range) { 6600 if !labels_are_correct(arguments) { 6601 return; 6602 } 6603 6604 match fun { 6605 TypedExpr::Invalid { 6606 type_, 6607 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }), 6608 location: _, 6609 } => { 6610 return self.try_save_function_from_other_module( 6611 module_name, 6612 label, 6613 type_, 6614 Some(arguments), 6615 ); 6616 } 6617 TypedExpr::Invalid { 6618 type_, 6619 extra_information: Some(InvalidExpression::UnknownVariable { name }), 6620 location: _, 6621 } => { 6622 return self.try_save_function_to_generate(name, type_, Some(arguments)); 6623 } 6624 TypedExpr::Int { .. } 6625 | TypedExpr::Float { .. } 6626 | TypedExpr::String { .. } 6627 | TypedExpr::Block { .. } 6628 | TypedExpr::Pipeline { .. } 6629 | TypedExpr::Var { .. } 6630 | TypedExpr::Fn { .. } 6631 | TypedExpr::List { .. } 6632 | TypedExpr::Call { .. } 6633 | TypedExpr::BinOp { .. } 6634 | TypedExpr::Case { .. } 6635 | TypedExpr::RecordAccess { .. } 6636 | TypedExpr::PositionalAccess { .. } 6637 | TypedExpr::ModuleSelect { .. } 6638 | TypedExpr::Tuple { .. } 6639 | TypedExpr::TupleIndex { .. } 6640 | TypedExpr::Todo { .. } 6641 | TypedExpr::Panic { .. } 6642 | TypedExpr::Echo { .. } 6643 | TypedExpr::BitArray { .. } 6644 | TypedExpr::RecordUpdate { .. } 6645 | TypedExpr::NegateBool { .. } 6646 | TypedExpr::NegateInt { .. } 6647 | TypedExpr::Invalid { .. } => {} 6648 } 6649 } 6650 ast::visit::visit_typed_expr_call( 6651 self, 6652 location, 6653 type_, 6654 fun, 6655 arguments, 6656 argument_parentheses, 6657 ); 6658 } 6659} 6660 6661/// Builder for the "generate variant" code action. This will generate a variant 6662/// for a type if it can tell the type it should come from. It will work with 6663/// non-existing variants both used as expressions 6664/// 6665/// ```gleam 6666/// let a = IDoNotExist(1) 6667/// // ^^^^^^^^^^^ It would generate this variant here 6668/// ``` 6669/// 6670/// And as patterns: 6671/// 6672/// ```gleam 6673/// let assert IDoNotExist(1) = todo 6674/// ^^^^^^^^^^^ It would generate this variant here 6675/// ``` 6676/// 6677pub struct GenerateVariant<'a, IO> { 6678 module: &'a Module, 6679 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 6680 params: &'a CodeActionParams, 6681 line_numbers: &'a LineNumbers, 6682 variant_to_generate: Option<VariantToGenerate<'a>>, 6683} 6684 6685struct VariantToGenerate<'a> { 6686 name: &'a str, 6687 end_position: u32, 6688 arguments_types: Vec<Arc<Type>>, 6689 6690 /// Wether the type we're adding the variant to is written with braces or 6691 /// not. We need this information to add braces when missing. 6692 /// 6693 type_braces: TypeBraces, 6694 6695 /// The module this variant will be added to. 6696 /// 6697 module_name: EcoString, 6698 6699 /// The arguments actually supplied as input to the variant, if any. 6700 /// A variant to generate might as well be just a name passed as an argument 6701 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually 6702 /// have any actual arguments! 6703 /// 6704 given_arguments: Option<Arguments<'a>>, 6705} 6706 6707#[derive(Debug, Clone, Copy)] 6708enum TypeBraces { 6709 /// If the type is written like this: `pub type Wibble` 6710 HasBraces, 6711 /// If the type is written like this: `pub type Wibble {}` 6712 NoBraces, 6713} 6714 6715/// The arguments to an invalid call or pattern we can use to generate a variant. 6716/// 6717enum Arguments<'a> { 6718 /// These are the arguments provided to the invalid variant constructor 6719 /// when it's used as a function: `let a = Wibble(1, 2)`. 6720 /// 6721 Expressions(&'a [TypedCallArg]), 6722 /// These are the arguments provided to the invalid variant constructor when 6723 /// it's used in a pattern: `let assert Wibble(1, 2) = a` 6724 /// 6725 Patterns(&'a [CallArg<TypedPattern>]), 6726} 6727 6728/// An invalid variant might be used both as a pattern in a case expression or 6729/// as a regular value in an expression. We want to generate the variant in both 6730/// cases, so we use this enum to tell apart the two cases and be able to reuse 6731/// most of the code for both as they are very similar. 6732/// 6733enum Argument<'a> { 6734 Expression(&'a TypedCallArg), 6735 Pattern(&'a CallArg<TypedPattern>), 6736} 6737 6738impl<'a> Arguments<'a> { 6739 fn get(&self, index: usize) -> Option<Argument<'a>> { 6740 match self { 6741 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern), 6742 Arguments::Expressions(call_arguments) => { 6743 call_arguments.get(index).map(Argument::Expression) 6744 } 6745 } 6746 } 6747 6748 fn types(&self) -> Vec<Arc<Type>> { 6749 match self { 6750 Arguments::Expressions(call_arguments) => call_arguments 6751 .iter() 6752 .map(|argument| argument.value.type_()) 6753 .collect_vec(), 6754 6755 Arguments::Patterns(call_arguments) => call_arguments 6756 .iter() 6757 .map(|argument| argument.value.type_()) 6758 .collect_vec(), 6759 } 6760 } 6761} 6762 6763impl Argument<'_> { 6764 fn label(&self) -> Option<EcoString> { 6765 match self { 6766 Argument::Expression(call_arg) => call_arg.label.clone(), 6767 Argument::Pattern(call_arg) => call_arg.label.clone(), 6768 } 6769 } 6770} 6771 6772impl<'a, IO> GenerateVariant<'a, IO> { 6773 pub fn new( 6774 module: &'a Module, 6775 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 6776 line_numbers: &'a LineNumbers, 6777 params: &'a CodeActionParams, 6778 ) -> Self { 6779 Self { 6780 module, 6781 params, 6782 compiler, 6783 line_numbers, 6784 variant_to_generate: None, 6785 } 6786 } 6787 6788 pub fn code_actions(mut self) -> Vec<CodeAction> { 6789 self.visit_typed_module(&self.module.ast); 6790 6791 let Some(VariantToGenerate { 6792 name, 6793 arguments_types, 6794 given_arguments, 6795 module_name, 6796 end_position, 6797 type_braces, 6798 }) = &self.variant_to_generate 6799 else { 6800 return vec![]; 6801 }; 6802 6803 let Some((variant_module, variant_edits)) = self.edits_to_create_variant( 6804 name, 6805 arguments_types, 6806 given_arguments, 6807 module_name, 6808 *end_position, 6809 *type_braces, 6810 ) else { 6811 return vec![]; 6812 }; 6813 6814 let mut action = Vec::with_capacity(1); 6815 CodeActionBuilder::new("Generate variant") 6816 .kind(CodeActionKind::QuickFix) 6817 .changes(variant_module, variant_edits) 6818 .preferred(true) 6819 .push_to(&mut action); 6820 action 6821 } 6822 6823 /// Returns the edits needed to add this new variant to the given module. 6824 /// It also returns the uri of the module the edits should be applied to. 6825 /// 6826 fn edits_to_create_variant( 6827 &self, 6828 variant_name: &str, 6829 arguments_types: &[Arc<Type>], 6830 given_arguments: &Option<Arguments<'_>>, 6831 module_name: &EcoString, 6832 end_position: u32, 6833 type_braces: TypeBraces, 6834 ) -> Option<(Url, Vec<TextEdit>)> { 6835 let mut label_names = NameGenerator::new(); 6836 let mut printer = Printer::new(&self.module.ast.names); 6837 let arguments = arguments_types 6838 .iter() 6839 .enumerate() 6840 .map(|(index, argument_type)| { 6841 let label = given_arguments 6842 .as_ref() 6843 .and_then(|arguments| arguments.get(index)?.label()) 6844 .map(|label| label_names.rename_to_avoid_shadowing(label)); 6845 6846 let pretty_type = printer.print_type(argument_type); 6847 if let Some(arg_label) = label { 6848 format!("{arg_label}: {pretty_type}") 6849 } else { 6850 format!("{pretty_type}") 6851 } 6852 }) 6853 .join(", "); 6854 6855 let variant = if arguments.is_empty() { 6856 variant_name.to_string() 6857 } else { 6858 format!("{variant_name}({arguments})") 6859 }; 6860 6861 let (new_text, insert_at) = match type_braces { 6862 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1), 6863 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position), 6864 }; 6865 6866 if *module_name == self.module.name { 6867 // If we're editing the current module we can use the line numbers that 6868 // were already computed before-hand without wasting any time to add the 6869 // new edit. 6870 let mut edits = TextEdits::new(self.line_numbers); 6871 edits.insert(insert_at, new_text); 6872 Some((self.params.text_document.uri.clone(), edits.edits)) 6873 } else { 6874 // Otherwise we're changing a different module and we need to get its 6875 // code and line numbers to properly apply the new edit. 6876 let module = self 6877 .compiler 6878 .modules 6879 .get(module_name) 6880 .expect("module to exist"); 6881 let line_numbers = LineNumbers::new(&module.code); 6882 let mut edits = TextEdits::new(&line_numbers); 6883 edits.insert(insert_at, new_text); 6884 Some((url_from_path(module.input_path.as_str())?, edits.edits)) 6885 } 6886 } 6887 6888 fn try_save_variant_to_generate( 6889 &mut self, 6890 function_name_location: SrcSpan, 6891 function_type: &Arc<Type>, 6892 given_arguments: Option<Arguments<'a>>, 6893 ) { 6894 let variant_to_generate = 6895 self.variant_to_generate(function_name_location, function_type, given_arguments); 6896 if variant_to_generate.is_some() { 6897 self.variant_to_generate = variant_to_generate; 6898 } 6899 } 6900 6901 fn variant_to_generate( 6902 &mut self, 6903 function_name_location: SrcSpan, 6904 type_: &Arc<Type>, 6905 given_arguments: Option<Arguments<'a>>, 6906 ) -> Option<VariantToGenerate<'a>> { 6907 let name = code_at(self.module, function_name_location); 6908 if !is_valid_uppercase_name(name) { 6909 return None; 6910 } 6911 6912 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) { 6913 (Some(result), _) => result, 6914 (None, Some(arguments)) => (arguments.types(), type_.clone()), 6915 (None, None) => (vec![], type_.clone()), 6916 }; 6917 6918 let (module_name, type_name, _) = custom_type.named_type_information()?; 6919 let module = self.compiler.modules.get(&module_name)?; 6920 let (end_position, type_braces) = (module.ast.definitions.custom_types.iter()) 6921 .filter(|custom_type| custom_type.name == type_name) 6922 .find_map(|custom_type| { 6923 // If there's already a variant with this name then we definitely 6924 // don't want to generate a new variant with the same name! 6925 let variant_with_this_name_already_exists = custom_type 6926 .constructors 6927 .iter() 6928 .map(|constructor| &constructor.name) 6929 .any(|existing_constructor_name| existing_constructor_name == name); 6930 if variant_with_this_name_already_exists { 6931 return None; 6932 } 6933 let type_braces = if custom_type.end_position == custom_type.location.end { 6934 TypeBraces::NoBraces 6935 } else { 6936 TypeBraces::HasBraces 6937 }; 6938 Some((custom_type.end_position, type_braces)) 6939 })?; 6940 6941 Some(VariantToGenerate { 6942 name, 6943 arguments_types, 6944 given_arguments, 6945 module_name, 6946 end_position, 6947 type_braces, 6948 }) 6949 } 6950} 6951 6952impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> { 6953 fn visit_typed_expr_invalid( 6954 &mut self, 6955 location: &'ast SrcSpan, 6956 type_: &'ast Arc<Type>, 6957 extra_information: &'ast Option<InvalidExpression>, 6958 ) { 6959 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 6960 if within(self.params.range, invalid_range) { 6961 self.try_save_variant_to_generate(*location, type_, None); 6962 } 6963 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); 6964 } 6965 6966 fn visit_typed_expr_call( 6967 &mut self, 6968 location: &'ast SrcSpan, 6969 type_: &'ast Arc<Type>, 6970 fun: &'ast TypedExpr, 6971 arguments: &'ast [TypedCallArg], 6972 open_parenthesis: &'ast Option<u32>, 6973 ) { 6974 // If the function being called is invalid we need to generate a 6975 // function that has the proper labels. 6976 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers); 6977 if within(self.params.range, fun_range) && fun.is_invalid() { 6978 if labels_are_correct(arguments) { 6979 self.try_save_variant_to_generate( 6980 fun.location(), 6981 &fun.type_(), 6982 Some(Arguments::Expressions(arguments)), 6983 ); 6984 } 6985 } else { 6986 ast::visit::visit_typed_expr_call( 6987 self, 6988 location, 6989 type_, 6990 fun, 6991 arguments, 6992 open_parenthesis, 6993 ); 6994 } 6995 } 6996 6997 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 6998 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 6999 if within(self.params.range, invalid_range) { 7000 self.try_save_variant_to_generate(*location, type_, None); 7001 } 7002 ast::visit::visit_typed_pattern_invalid(self, location, type_); 7003 } 7004 7005 fn visit_typed_pattern_constructor( 7006 &mut self, 7007 location: &'ast SrcSpan, 7008 name_location: &'ast SrcSpan, 7009 name: &'ast EcoString, 7010 arguments: &'ast Vec<CallArg<TypedPattern>>, 7011 module: &'ast Option<(EcoString, SrcSpan)>, 7012 constructor: &'ast Inferred<type_::PatternConstructor>, 7013 spread: &'ast Option<SrcSpan>, 7014 type_: &'ast Arc<Type>, 7015 ) { 7016 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers); 7017 if within(self.params.range, pattern_range) { 7018 if labels_are_correct(arguments) { 7019 self.try_save_variant_to_generate( 7020 *name_location, 7021 type_, 7022 Some(Arguments::Patterns(arguments)), 7023 ); 7024 } 7025 } else { 7026 ast::visit::visit_typed_pattern_constructor( 7027 self, 7028 location, 7029 name_location, 7030 name, 7031 arguments, 7032 module, 7033 constructor, 7034 spread, 7035 type_, 7036 ); 7037 } 7038 } 7039} 7040 7041#[must_use] 7042/// Checks the labels in the given arguments are correct: that is there's no 7043/// duplicate labels and all labelled arguments come after the unlabelled ones. 7044fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool { 7045 let mut labelled_arg_found = false; 7046 let mut used_labels = HashSet::new(); 7047 7048 for argument in arguments { 7049 match &argument.label { 7050 // Labels are invalid if there's duplicate ones or if an unlabelled 7051 // argument comes after a labelled one. 7052 Some(label) if used_labels.contains(label) => return false, 7053 None if labelled_arg_found => return false, 7054 // Otherwise we just add the label to the used ones. 7055 Some(label) => { 7056 labelled_arg_found = true; 7057 let _ = used_labels.insert(label); 7058 } 7059 None => {} 7060 } 7061 } 7062 7063 true 7064} 7065 7066#[derive(Clone)] 7067struct NameGenerator { 7068 used_names: im::HashSet<EcoString>, 7069} 7070 7071impl NameGenerator { 7072 pub fn new() -> Self { 7073 NameGenerator { 7074 used_names: im::HashSet::new(), 7075 } 7076 } 7077 7078 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString { 7079 let mut i = 1; 7080 let mut candidate_name = base.clone(); 7081 7082 loop { 7083 if self.used_names.contains(&candidate_name) { 7084 i += 1; 7085 candidate_name = eco_format!("{base}_{i}"); 7086 } else { 7087 let _ = self.used_names.insert(candidate_name.clone()); 7088 return candidate_name; 7089 } 7090 } 7091 } 7092 7093 /// Given an argument type and the actual call argument (if any), comes up 7094 /// with a label and a name to use for that argument when generating a 7095 /// function. 7096 /// 7097 pub fn generate_label_and_name( 7098 &mut self, 7099 call_argument: Option<&CallArg<TypedExpr>>, 7100 argument_type: &Arc<Type>, 7101 ) -> (Option<EcoString>, EcoString) { 7102 let label = call_argument.and_then(|argument| argument.label.clone()); 7103 let argument_name = call_argument 7104 // We always favour a name derived from the expression (for example if 7105 // the argument is a variable) 7106 .and_then(|argument| self.generate_name_from_expression(&argument.value)) 7107 // If we don't have such a name and there's a label we use that name. 7108 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?))) 7109 // If all else fails we fallback to using a name derived from the 7110 // argument's type. 7111 .unwrap_or_else(|| self.generate_name_from_type(argument_type)); 7112 7113 (label, argument_name) 7114 } 7115 7116 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString { 7117 let type_to_base_name = |type_: &Arc<Type>| { 7118 type_ 7119 .named_type_name() 7120 .map(|(_type_module, type_name)| to_snake_case(&type_name)) 7121 .filter(|name| is_valid_lowercase_name(name)) 7122 .unwrap_or(EcoString::from("value")) 7123 }; 7124 7125 let base_name = match type_.list_type() { 7126 None => type_to_base_name(type_), 7127 // If we're coming up with a name for a list we want to use the 7128 // plural form for the name of the inner type. For example: 7129 // `List(Pokemon)` should generate `pokemons`. 7130 Some(inner_type) => { 7131 let base_name = type_to_base_name(&inner_type); 7132 // If the inner type name already ends in "s" we leave it as it 7133 // is, or it would look funny. 7134 if base_name.ends_with('s') { 7135 base_name 7136 } else { 7137 eco_format!("{base_name}s") 7138 } 7139 } 7140 }; 7141 7142 self.rename_to_avoid_shadowing(base_name) 7143 } 7144 7145 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> { 7146 match expression { 7147 // If the argument is a record, we can't use it as an argument name. 7148 // Similarly, we don't want to base the variable name off a 7149 // compiler-generated variable like `_pipe`. 7150 TypedExpr::Var { 7151 name, constructor, .. 7152 } if !constructor.variant.is_record() 7153 && !constructor.variant.is_generated_variable() => 7154 { 7155 Some(self.rename_to_avoid_shadowing(name.clone())) 7156 } 7157 7158 // If the argument is a record access, we generate a name from the 7159 // label used. 7160 // For example if we have `wibble.id` we would end up picking `id`. 7161 TypedExpr::RecordAccess { label, .. } => { 7162 Some(self.rename_to_avoid_shadowing(label.clone())) 7163 } 7164 7165 TypedExpr::Int { .. } 7166 | TypedExpr::Float { .. } 7167 | TypedExpr::String { .. } 7168 | TypedExpr::Block { .. } 7169 | TypedExpr::Pipeline { .. } 7170 | TypedExpr::Var { .. } 7171 | TypedExpr::Fn { .. } 7172 | TypedExpr::List { .. } 7173 | TypedExpr::Call { .. } 7174 | TypedExpr::BinOp { .. } 7175 | TypedExpr::Case { .. } 7176 | TypedExpr::PositionalAccess { .. } 7177 | TypedExpr::ModuleSelect { .. } 7178 | TypedExpr::Tuple { .. } 7179 | TypedExpr::TupleIndex { .. } 7180 | TypedExpr::Todo { .. } 7181 | TypedExpr::Panic { .. } 7182 | TypedExpr::Echo { .. } 7183 | TypedExpr::BitArray { .. } 7184 | TypedExpr::RecordUpdate { .. } 7185 | TypedExpr::NegateBool { .. } 7186 | TypedExpr::NegateInt { .. } 7187 | TypedExpr::Invalid { .. } => None, 7188 } 7189 } 7190 7191 /// Given some typed definitions this reserves all the value names defined 7192 /// by all the top level definitions. That is: all function names, constant 7193 /// names, and imported modules names. 7194 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) { 7195 for constant in &definitions.constants { 7196 self.add_used_name(constant.name.clone()); 7197 } 7198 7199 for function in &definitions.functions { 7200 if let Some((_, name)) = &function.name { 7201 self.add_used_name(name.clone()); 7202 } 7203 } 7204 7205 for import in &definitions.imports { 7206 let module_name = match &import.used_name() { 7207 Some(used_name) => used_name.clone(), 7208 None => import.module.clone(), 7209 }; 7210 self.add_used_name(module_name); 7211 } 7212 } 7213 7214 pub fn add_used_name(&mut self, name: EcoString) { 7215 let _ = self.used_names.insert(name); 7216 } 7217 7218 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) { 7219 field_map 7220 .fields 7221 .iter() 7222 .for_each(|(label, _)| self.add_used_name(label.clone())); 7223 } 7224 7225 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) { 7226 variable_names 7227 .names 7228 .iter() 7229 .for_each(|name| self.add_used_name(name.clone())); 7230 } 7231 7232 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) { 7233 for variable in bound_variables { 7234 self.add_used_name(variable.name()); 7235 } 7236 } 7237} 7238 7239#[must_use] 7240fn is_valid_lowercase_name(name: &str) -> bool { 7241 if !name.starts_with(|char: char| char.is_ascii_lowercase()) { 7242 return false; 7243 } 7244 7245 for char in name.chars() { 7246 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_'; 7247 if !is_valid_char { 7248 return false; 7249 } 7250 } 7251 7252 string_to_keyword(name).is_none() 7253} 7254 7255#[must_use] 7256fn is_valid_uppercase_name(name: &str) -> bool { 7257 if !name.starts_with(|char: char| char.is_ascii_uppercase()) { 7258 return false; 7259 } 7260 7261 for char in name.chars() { 7262 if !char.is_ascii_alphanumeric() { 7263 return false; 7264 } 7265 } 7266 7267 true 7268} 7269 7270/// Code action to rewrite a single-step pipeline into a regular function call. 7271/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`. 7272/// 7273pub struct ConvertToFunctionCall<'a> { 7274 module: &'a Module, 7275 params: &'a CodeActionParams, 7276 edits: TextEdits<'a>, 7277 locations: Option<ConvertToFunctionCallLocations>, 7278} 7279 7280/// All the different locations the "Convert to function call" code action needs 7281/// to properly rewrite a pipeline into a function call. 7282/// 7283struct ConvertToFunctionCallLocations { 7284 /// This is the location of the value being piped into a call. 7285 /// 7286 /// ```gleam 7287 /// [1, 2, 3] |> list.length 7288 /// // ^^^^^^^^^ This one here 7289 /// ``` 7290 /// 7291 first_value: SrcSpan, 7292 7293 /// This is the location of the call the value is being piped into. 7294 /// 7295 /// ```gleam 7296 /// [1, 2, 3] |> list.length 7297 /// // ^^^^^^^^^^^ This one here 7298 /// ``` 7299 /// 7300 call: SrcSpan, 7301 7302 /// This is the kind of desugaring that is taking place when piping 7303 /// `first_value` into `call`. 7304 /// 7305 call_kind: PipelineAssignmentKind, 7306} 7307 7308impl<'a> ConvertToFunctionCall<'a> { 7309 pub fn new( 7310 module: &'a Module, 7311 line_numbers: &'a LineNumbers, 7312 params: &'a CodeActionParams, 7313 ) -> Self { 7314 Self { 7315 module, 7316 params, 7317 edits: TextEdits::new(line_numbers), 7318 locations: None, 7319 } 7320 } 7321 7322 pub fn code_actions(mut self) -> Vec<CodeAction> { 7323 self.visit_typed_module(&self.module.ast); 7324 7325 // If we couldn't find a pipeline to rewrite we don't return any action. 7326 let Some(ConvertToFunctionCallLocations { 7327 first_value, 7328 call, 7329 call_kind, 7330 }) = self.locations 7331 else { 7332 return vec![]; 7333 }; 7334 7335 // We first delete the first value of the pipeline as it's going to be 7336 // inlined as a function call argument. 7337 self.edits.delete(SrcSpan { 7338 start: first_value.start, 7339 end: call.start, 7340 }); 7341 7342 // Then we have to insert the piped value in the appropriate position. 7343 // This will change based on how the pipeline is being desugared, we 7344 // know this thanks to the `call_kind` 7345 let first_value_text = self 7346 .module 7347 .code 7348 .get(first_value.start as usize..first_value.end as usize) 7349 .expect("invalid code span") 7350 .to_string(); 7351 7352 match call_kind { 7353 // When piping into a `_` we replace the hole with the piped value: 7354 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`. 7355 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text), 7356 7357 // When piping is desguared as a function call we need to add the 7358 // missing parentheses: 7359 // `[1, 2] |> length` becomes `length([1, 2])` 7360 PipelineAssignmentKind::FunctionCall => { 7361 self.edits.insert(call.end, format!("({first_value_text})")) 7362 } 7363 7364 // When the piped value is inserted as the first argument there's two 7365 // possible scenarios: 7366 // - there's a second argument as well: in that case we insert it 7367 // before the second arg and add a comma 7368 // - there's no other argument: `[1, 2] |> length()` becomes 7369 // `length([1, 2])`, we insert the value between the empty 7370 // parentheses 7371 PipelineAssignmentKind::FirstArgument { 7372 second_argument: Some(SrcSpan { start, .. }), 7373 } => self.edits.insert(start, format!("{first_value_text}, ")), 7374 PipelineAssignmentKind::FirstArgument { 7375 second_argument: None, 7376 } => self.edits.insert(call.end - 1, first_value_text), 7377 7378 // When the value is piped into an echo, to rewrite the pipeline we 7379 // have to insert the value after the `echo` with no parentheses: 7380 // `a |> echo` is rewritten as `echo a`. 7381 PipelineAssignmentKind::Echo => { 7382 self.edits.insert(call.end, format!(" {first_value_text}")) 7383 } 7384 } 7385 7386 let mut action = Vec::with_capacity(1); 7387 CodeActionBuilder::new("Convert to function call") 7388 .kind(CodeActionKind::RefactorRewrite) 7389 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7390 .preferred(false) 7391 .push_to(&mut action); 7392 action 7393 } 7394} 7395 7396impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> { 7397 fn visit_typed_expr_pipeline( 7398 &mut self, 7399 location: &'ast SrcSpan, 7400 first_value: &'ast TypedPipelineAssignment, 7401 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 7402 finally: &'ast TypedExpr, 7403 finally_kind: &'ast PipelineAssignmentKind, 7404 ) { 7405 let pipeline_range = self.edits.src_span_to_lsp_range(*location); 7406 if within(self.params.range, pipeline_range) { 7407 // We will always desugar the pipeline's first step. If there's no 7408 // intermediate assignment it means we're dealing with a single step 7409 // pipeline and the call is `finally`. 7410 let (call, call_kind) = assignments 7411 .first() 7412 .map(|(call, kind)| (call.location, *kind)) 7413 .unwrap_or_else(|| (finally.location(), *finally_kind)); 7414 7415 self.locations = Some(ConvertToFunctionCallLocations { 7416 first_value: first_value.location, 7417 call, 7418 call_kind, 7419 }); 7420 7421 ast::visit::visit_typed_expr_pipeline( 7422 self, 7423 location, 7424 first_value, 7425 assignments, 7426 finally, 7427 finally_kind, 7428 ); 7429 } 7430 } 7431} 7432 7433/// Builder for code action to inline a variable. 7434/// 7435pub struct InlineVariable<'a> { 7436 module: &'a Module, 7437 params: &'a CodeActionParams, 7438 edits: TextEdits<'a>, 7439 actions: Vec<CodeAction>, 7440} 7441 7442impl<'a> InlineVariable<'a> { 7443 pub fn new( 7444 module: &'a Module, 7445 line_numbers: &'a LineNumbers, 7446 params: &'a CodeActionParams, 7447 ) -> Self { 7448 Self { 7449 module, 7450 params, 7451 edits: TextEdits::new(line_numbers), 7452 actions: Vec::new(), 7453 } 7454 } 7455 7456 pub fn code_actions(mut self) -> Vec<CodeAction> { 7457 self.visit_typed_module(&self.module.ast); 7458 7459 self.actions 7460 } 7461 7462 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) { 7463 let references = 7464 FindVariableReferences::new(location, name).find_in_module(&self.module.ast); 7465 let reference = if references.len() == 1 { 7466 references 7467 .into_iter() 7468 .next() 7469 .expect("References has length 1") 7470 } else { 7471 return; 7472 }; 7473 7474 let Some(ast::Statement::Assignment(assignment)) = 7475 self.module.ast.find_statement(location.start) 7476 else { 7477 return; 7478 }; 7479 7480 // If the assignment does not simple bind a variable, for example: 7481 // ```gleam 7482 // let #(first, second, third) 7483 // io.println(first) 7484 // // ^ Inline here 7485 // ``` 7486 // We can't inline it. 7487 if !matches!(assignment.pattern, Pattern::Variable { .. }) { 7488 return; 7489 } 7490 7491 // If the assignment was generated by the compiler, it doesn't have a 7492 // syntactical representation, so we can't inline it. 7493 if matches!(assignment.kind, AssignmentKind::Generated) { 7494 return; 7495 } 7496 7497 let value_location = assignment.value.location(); 7498 let value = self 7499 .module 7500 .code 7501 .get(value_location.start as usize..value_location.end as usize) 7502 .expect("Span is valid"); 7503 7504 match reference.kind { 7505 VariableReferenceKind::Variable => { 7506 self.edits.replace(reference.location, value.into()); 7507 } 7508 VariableReferenceKind::LabelShorthand => { 7509 self.edits 7510 .insert(reference.location.end, format!(" {value}")); 7511 } 7512 } 7513 7514 let mut location = assignment.location; 7515 location.end = next_nonwhitespace(&self.module.code, location.end); 7516 7517 self.edits.delete(location); 7518 7519 CodeActionBuilder::new("Inline variable") 7520 .kind(CodeActionKind::RefactorInline) 7521 .changes( 7522 self.params.text_document.uri.clone(), 7523 std::mem::take(&mut self.edits.edits), 7524 ) 7525 .preferred(false) 7526 .push_to(&mut self.actions); 7527 } 7528} 7529 7530impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> { 7531 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 7532 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else { 7533 ast::visit::visit_typed_assignment(self, assignment); 7534 return; 7535 }; 7536 7537 // We special case assignment variables because we want to trigger the 7538 // code action also if we're over the let keyword: 7539 // 7540 // ```gleam 7541 // let wibble = 11 7542 // // ^^^^^^^^^^ Here! 7543 // ``` 7544 // 7545 let assignment_range = self 7546 .edits 7547 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end)); 7548 if !within(self.params.range, assignment_range) { 7549 ast::visit::visit_typed_assignment(self, assignment); 7550 return; 7551 } 7552 7553 self.maybe_inline(*location, name.clone()); 7554 } 7555 7556 fn visit_typed_expr_var( 7557 &mut self, 7558 location: &'ast SrcSpan, 7559 constructor: &'ast ValueConstructor, 7560 name: &'ast EcoString, 7561 ) { 7562 let range = self.edits.src_span_to_lsp_range(*location); 7563 7564 if !within(self.params.range, range) { 7565 return; 7566 } 7567 7568 let type_::ValueConstructorVariant::LocalVariable { location, origin } = 7569 &constructor.variant 7570 else { 7571 return; 7572 }; 7573 7574 // We can only inline variables assigned by `let` statements, as it 7575 //doesn't make sense to do so with any other kind of variable. 7576 match origin.declaration { 7577 VariableDeclaration::LetPattern => {} 7578 VariableDeclaration::UsePattern 7579 | VariableDeclaration::ClausePattern 7580 | VariableDeclaration::FunctionParameter { .. } 7581 | VariableDeclaration::Generated => return, 7582 } 7583 7584 self.maybe_inline(*location, name.clone()); 7585 } 7586 7587 fn visit_typed_pattern_variable( 7588 &mut self, 7589 location: &'ast SrcSpan, 7590 name: &'ast EcoString, 7591 _type: &'ast Arc<Type>, 7592 origin: &'ast VariableOrigin, 7593 ) { 7594 // We can only inline variables assigned by `let` statements, as it 7595 //doesn't make sense to do so with any other kind of variable. 7596 match origin.declaration { 7597 VariableDeclaration::LetPattern => {} 7598 VariableDeclaration::UsePattern 7599 | VariableDeclaration::ClausePattern 7600 | VariableDeclaration::FunctionParameter { .. } 7601 | VariableDeclaration::Generated => return, 7602 } 7603 7604 let range = self.edits.src_span_to_lsp_range(*location); 7605 7606 if !within(self.params.range, range) { 7607 return; 7608 } 7609 7610 self.maybe_inline(*location, name.clone()); 7611 } 7612} 7613 7614/// Builder for the "convert to pipe" code action. 7615/// 7616/// ```gleam 7617/// pub fn main() { 7618/// wibble(wobble, woo) 7619/// // ^ [convert to pipe] 7620/// } 7621/// ``` 7622/// 7623/// Will turn the code into the following pipeline: 7624/// 7625/// ```gleam 7626/// pub fn main() { 7627/// wobble |> wibble(woo) 7628/// } 7629/// ``` 7630/// 7631pub struct ConvertToPipe<'a> { 7632 module: &'a Module, 7633 params: &'a CodeActionParams, 7634 edits: TextEdits<'a>, 7635 argument_to_pipe: Option<ConvertToPipeArg<'a>>, 7636 visited_item: VisitedItem, 7637} 7638 7639pub enum VisitedItem { 7640 RegularExpression, 7641 UseRightHandSide, 7642 PipelineFinalStep, 7643} 7644 7645/// Holds all the data needed by the "convert to pipe" code action to properly 7646/// rewrite a call into a pipe. Here's what each span means: 7647/// 7648/// ```gleam 7649/// wibble(wobb|le, woo) 7650/// // ^^^^^^^^^^^^^^^^^^^^ call 7651/// // ^^^^^^ called 7652/// // ^^^^^^^ arg 7653/// // ^^^ next arg 7654/// ``` 7655/// 7656/// In this example `position` is 0, since the cursor is over the first 7657/// argument. 7658/// 7659pub struct ConvertToPipeArg<'a> { 7660 /// The span of the called function. 7661 called: SrcSpan, 7662 /// The span of the entire function call. 7663 call: SrcSpan, 7664 /// The position (0-based) of the argument. 7665 position: usize, 7666 /// The argument we have to pipe. 7667 arg: &'a TypedCallArg, 7668 /// The span of the argument following the one we have to pipe, if there's 7669 /// any. 7670 next_arg: Option<SrcSpan>, 7671} 7672 7673impl<'a> ConvertToPipe<'a> { 7674 pub fn new( 7675 module: &'a Module, 7676 line_numbers: &'a LineNumbers, 7677 params: &'a CodeActionParams, 7678 ) -> Self { 7679 Self { 7680 module, 7681 params, 7682 edits: TextEdits::new(line_numbers), 7683 visited_item: VisitedItem::RegularExpression, 7684 argument_to_pipe: None, 7685 } 7686 } 7687 7688 pub fn code_actions(mut self) -> Vec<CodeAction> { 7689 self.visit_typed_module(&self.module.ast); 7690 7691 let Some(ConvertToPipeArg { 7692 called, 7693 call, 7694 position, 7695 arg, 7696 next_arg, 7697 }) = self.argument_to_pipe 7698 else { 7699 return vec![]; 7700 }; 7701 7702 let arg_location = if arg.uses_label_shorthand() { 7703 SrcSpan { 7704 start: arg.location.start, 7705 end: arg.location.end - 1, 7706 } 7707 } else if arg.label.is_some() { 7708 arg.value.location() 7709 } else { 7710 arg.location 7711 }; 7712 7713 let arg_text = code_at(self.module, arg_location); 7714 // If the expression being piped is a binary operation with 7715 // precedence lower than pipes then we have to wrap it in curly 7716 // braces to not mess with the order of operations. 7717 let arg_text = if let TypedExpr::BinOp { operator, .. } = arg.value 7718 && operator.precedence() < PIPE_PRECEDENCE 7719 { 7720 &format!("{{ {arg_text} }}") 7721 } else { 7722 arg_text 7723 }; 7724 7725 match next_arg { 7726 // When extracting an argument we never want to remove any explicit 7727 // label that was written down, so in case it is labelled (be it a 7728 // shorthand or not) we'll always replace the value with a `_` 7729 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()), 7730 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()), 7731 7732 // Now we can deal with unlabelled arguments: 7733 // If we're removing the first argument and there's other arguments 7734 // after it, we need to delete the comma that was separating the 7735 // two. 7736 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan { 7737 start: arg.location.start, 7738 end: next_arg.start, 7739 }), 7740 // Otherwise, if we're deleting the first argument and there's 7741 // no other arguments following it, we remove the call's 7742 // parentheses. 7743 None if position == 0 => self.edits.delete(SrcSpan { 7744 start: called.end, 7745 end: call.end, 7746 }), 7747 // In all other cases we're piping something that is not the first 7748 // argument so we just replace it with an `_`. 7749 _ => self.edits.replace(arg.location, "_".into()), 7750 }; 7751 7752 // Finally we can add the argument that was removed as the first step 7753 // of the newly defined pipeline. 7754 self.edits.insert(call.start, format!("{arg_text} |> ")); 7755 7756 let mut action = Vec::with_capacity(1); 7757 CodeActionBuilder::new("Convert to pipe") 7758 .kind(CodeActionKind::RefactorRewrite) 7759 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7760 .preferred(false) 7761 .push_to(&mut action); 7762 action 7763 } 7764} 7765 7766impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> { 7767 fn visit_typed_expr_call( 7768 &mut self, 7769 location: &'ast SrcSpan, 7770 _type_: &'ast Arc<Type>, 7771 fun: &'ast TypedExpr, 7772 arguments: &'ast [TypedCallArg], 7773 _open_parenthesis: &'ast Option<u32>, 7774 ) { 7775 if arguments.iter().any(|arg| arg.is_capture_hole()) { 7776 return; 7777 } 7778 7779 // If we're visiting the typed function produced by typing a use, we 7780 // skip the thing itself and only visit its arguments and called 7781 // function, that is the body of the use. 7782 match self.visited_item { 7783 VisitedItem::RegularExpression => (), 7784 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => { 7785 self.visited_item = VisitedItem::RegularExpression; 7786 ast::visit::visit_typed_expr(self, fun); 7787 arguments 7788 .iter() 7789 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg)); 7790 return; 7791 } 7792 } 7793 7794 // We only visit a call if the cursor is somewhere within its location, 7795 // otherwise we skip it entirely. 7796 let call_range = self.edits.src_span_to_lsp_range(*location); 7797 if !within(self.params.range, call_range) { 7798 return; 7799 } 7800 7801 // If the cursor is over any of the arguments then we'll use that as 7802 // the one to extract. 7803 // Otherwise the cursor must be over the called function, in that case 7804 // we extract the first argument (if there's one): 7805 // 7806 // ```gleam 7807 // wibble(wobble, woo) 7808 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here 7809 // // ^^^ pipe the second argument if I'm here 7810 // ``` 7811 let argument_to_pipe = arguments 7812 .iter() 7813 .enumerate() 7814 .find_map(|(position, arg)| { 7815 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 7816 if within(self.params.range, arg_range) { 7817 Some((position, arg)) 7818 } else { 7819 None 7820 } 7821 }) 7822 .or_else(|| arguments.first().map(|argument| (0, argument))); 7823 7824 // If we're not hovering over any of the arguments _or_ there's no 7825 // argument to extract at all we just return, there's nothing we can do 7826 // on this call or any of its arguments (since we've determined the 7827 // cursor is not over any of those). 7828 let Some((position, arg)) = argument_to_pipe else { 7829 return; 7830 }; 7831 7832 self.argument_to_pipe = Some(ConvertToPipeArg { 7833 called: fun.location(), 7834 call: *location, 7835 position, 7836 arg, 7837 next_arg: arguments 7838 .get(position + 1) 7839 .map(|argument| argument.location), 7840 }); 7841 7842 // We still want to visit the arguments so that if we're hovering a 7843 // nested pipeline, that's going to be the one we transform: 7844 // 7845 // ```gleam 7846 // wibble(Wobble( 7847 // field: call(other(last(1))) 7848 // // ^^^^ We want to convert this one if we hover over it, 7849 // // not the outer `wibble(Wobble(...))` call 7850 // )) 7851 // ``` 7852 // 7853 for argument in arguments { 7854 ast::visit::visit_typed_call_arg(self, argument); 7855 } 7856 } 7857 7858 fn visit_typed_expr_pipeline( 7859 &mut self, 7860 _location: &'ast SrcSpan, 7861 first_value: &'ast TypedPipelineAssignment, 7862 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 7863 finally: &'ast TypedExpr, 7864 _finally_kind: &'ast PipelineAssignmentKind, 7865 ) { 7866 // We can only apply the action on the first step of a pipeline, so we 7867 // visit just that one and skip all the others. 7868 ast::visit::visit_typed_pipeline_assignment(self, first_value); 7869 self.visited_item = VisitedItem::PipelineFinalStep; 7870 ast::visit::visit_typed_expr(self, finally); 7871 } 7872 7873 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 7874 self.visited_item = VisitedItem::UseRightHandSide; 7875 ast::visit::visit_typed_use(self, use_); 7876 } 7877} 7878 7879/// Code action to interpolate a string. If the cursor is inside the string 7880/// (not selecting anything) the language server will offer to split it: 7881/// 7882/// ```gleam 7883/// "wibble | wobble" 7884/// // ^ [Split string] 7885/// // Will produce the following 7886/// "wibble " <> todo <> " wobble" 7887/// ``` 7888/// 7889/// If the cursor is selecting an entire valid gleam name, then the language 7890/// server will offer to interpolate it as a variable: 7891/// 7892/// ```gleam 7893/// "wibble wobble woo" 7894/// // ^^^^^^ [Interpolate variable] 7895/// // Will produce the following 7896/// "wibble " <> wobble <> " woo" 7897/// ``` 7898/// 7899/// > Note: the cursor won't end up right after the inserted variable/todo. 7900/// > that's a bit annoying, but in a future LSP version we will be able to 7901/// > isnert tab stops to allow one to jump to the newly added variable/todo. 7902/// 7903pub struct InterpolateString<'a> { 7904 module: &'a Module, 7905 params: &'a CodeActionParams, 7906 edits: TextEdits<'a>, 7907 string_interpolation: Option<(SrcSpan, StringInterpolation)>, 7908 string_literal_position: StringLiteralPosition, 7909} 7910 7911#[derive(Debug, Clone, Copy, Eq, PartialEq)] 7912pub enum StringLiteralPosition { 7913 FirstPipelineStep, 7914 Other, 7915} 7916 7917#[derive(Clone, Copy)] 7918enum StringInterpolation { 7919 InterpolateValue { value_location: SrcSpan }, 7920 SplitString { split_at: u32 }, 7921} 7922 7923impl<'a> InterpolateString<'a> { 7924 pub fn new( 7925 module: &'a Module, 7926 line_numbers: &'a LineNumbers, 7927 params: &'a CodeActionParams, 7928 ) -> Self { 7929 Self { 7930 module, 7931 params, 7932 edits: TextEdits::new(line_numbers), 7933 string_interpolation: None, 7934 string_literal_position: StringLiteralPosition::Other, 7935 } 7936 } 7937 7938 pub fn code_actions(mut self) -> Vec<CodeAction> { 7939 self.visit_typed_module(&self.module.ast); 7940 7941 let Some((string_location, interpolation)) = self.string_interpolation else { 7942 return vec![]; 7943 }; 7944 7945 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 7946 self.edits.insert(string_location.start, "{ ".into()); 7947 } 7948 7949 match interpolation { 7950 StringInterpolation::InterpolateValue { value_location } => { 7951 let name = self 7952 .module 7953 .code 7954 .get(value_location.start as usize..value_location.end as usize) 7955 .expect("invalid value range"); 7956 7957 // We trust that the programmer has correctly selected the part 7958 // of the string they want to interpolate and simply "cut it out" 7959 // for them. In future, we could try and parse their selection to 7960 // see if it is a valid expression in Gleam. 7961 if value_location.start == string_location.start + 1 { 7962 self.edits 7963 .insert(string_location.start, format!("{name} <> ")); 7964 } else if value_location.end == string_location.end - 1 { 7965 self.edits 7966 .insert(string_location.end, format!(" <> {name}")); 7967 } else { 7968 self.edits 7969 .insert(value_location.start, format!("\" <> {name} <> \"")); 7970 } 7971 self.edits.delete(value_location); 7972 } 7973 7974 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => { 7975 self.edits.insert(split_at, "\" <> todo <> \"".into()); 7976 } 7977 7978 StringInterpolation::SplitString { .. } => return vec![], 7979 }; 7980 7981 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 7982 self.edits.insert(string_location.end, " }".into()); 7983 } 7984 7985 let mut action = Vec::with_capacity(1); 7986 CodeActionBuilder::new("Interpolate string") 7987 .kind(CodeActionKind::RefactorRewrite) 7988 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7989 .preferred(false) 7990 .push_to(&mut action); 7991 action 7992 } 7993 7994 fn can_split_string_at(&self, at: u32) -> bool { 7995 self.string_interpolation 7996 .is_some_and(|(string_location, _)| { 7997 !(at <= string_location.start + 1 || at >= string_location.end - 1) 7998 }) 7999 } 8000 8001 fn visit_literal_string( 8002 &mut self, 8003 string_location: SrcSpan, 8004 string_position: StringLiteralPosition, 8005 ) { 8006 // We can only interpolate/split a string if the cursor is somewhere 8007 // within its location, otherwise we skip it. 8008 let string_range = self.edits.src_span_to_lsp_range(string_location); 8009 if !within(self.params.range, string_range) { 8010 return; 8011 } 8012 8013 let selection @ SrcSpan { start, end } = 8014 self.edits.lsp_range_to_src_span(self.params.range); 8015 8016 // We can't interpolate/split if the double quotes delimiting the 8017 // string have been selected. 8018 if start == string_location.start || end == string_location.end { 8019 return; 8020 } 8021 8022 let name = self 8023 .module 8024 .code 8025 .get(start as usize..end as usize) 8026 .expect("invalid value range"); 8027 8028 // TUI editors like Helix and Kakoune that use the selection-action edit 8029 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always 8030 // have something selected. For programmers using these editors, the 8031 // smallest selection possible is a 1-character selection. The best we can do 8032 // to provide parity with other editors is to consider a single-character SPACE 8033 // selection as an empty selection, as they most likely want to split the 8034 // string instead of interpolating a variable. 8035 let interpolation = if start == end || (end - start == 1 && name == " ") { 8036 StringInterpolation::SplitString { split_at: start } 8037 } else { 8038 StringInterpolation::InterpolateValue { 8039 value_location: selection, 8040 } 8041 }; 8042 self.string_interpolation = Some((string_location, interpolation)); 8043 self.string_literal_position = string_position; 8044 } 8045} 8046 8047impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> { 8048 fn visit_typed_expr_string( 8049 &mut self, 8050 location: &'ast SrcSpan, 8051 _type_: &'ast Arc<Type>, 8052 _value: &'ast EcoString, 8053 ) { 8054 self.visit_literal_string(*location, StringLiteralPosition::Other); 8055 } 8056 8057 fn visit_typed_expr_pipeline( 8058 &mut self, 8059 _location: &'ast SrcSpan, 8060 first_value: &'ast TypedPipelineAssignment, 8061 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 8062 finally: &'ast TypedExpr, 8063 _finally_kind: &'ast PipelineAssignmentKind, 8064 ) { 8065 if first_value.value.is_literal_string() { 8066 self.visit_literal_string( 8067 first_value.location, 8068 StringLiteralPosition::FirstPipelineStep, 8069 ); 8070 } else { 8071 ast::visit::visit_typed_pipeline_assignment(self, first_value); 8072 } 8073 8074 assignments 8075 .iter() 8076 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a)); 8077 self.visit_typed_expr(finally); 8078 } 8079} 8080 8081/// Code action to replace a `..` in a pattern with all the missing fields that 8082/// have not been explicitly provided; labelled ones are introduced with the 8083/// shorthand syntax. 8084/// 8085/// ```gleam 8086/// pub type Pokemon { 8087/// Pokemon(Int, name: String, moves: List(String)) 8088/// } 8089/// 8090/// pub fn main() { 8091/// let Pokemon(..) = todo 8092/// // ^^ Cursor over the spread 8093/// } 8094/// ``` 8095/// Would become 8096/// ```gleam 8097/// pub fn main() { 8098/// let Pokemon(int, name:, moves:) = todo 8099/// } 8100/// 8101pub struct FillUnusedFields<'a> { 8102 module: &'a Module, 8103 params: &'a CodeActionParams, 8104 edits: TextEdits<'a>, 8105 data: Option<FillUnusedFieldsData>, 8106} 8107 8108pub struct FillUnusedFieldsData { 8109 /// All the missing positional and labelled fields. 8110 positional: Vec<Arc<Type>>, 8111 labelled: Vec<(EcoString, Arc<Type>)>, 8112 /// We need this in order to tell where the missing positional arguments 8113 /// should be inserted. 8114 first_labelled_argument_start: Option<u32>, 8115 /// The end of the final argument before the spread, if there's any. 8116 /// We'll use this to delete everything that comes after the final argument, 8117 /// after adding all the ignored fields. 8118 last_argument_end: Option<u32>, 8119 spread_location: SrcSpan, 8120} 8121 8122impl<'a> FillUnusedFields<'a> { 8123 pub fn new( 8124 module: &'a Module, 8125 line_numbers: &'a LineNumbers, 8126 params: &'a CodeActionParams, 8127 ) -> Self { 8128 Self { 8129 module, 8130 params, 8131 edits: TextEdits::new(line_numbers), 8132 data: None, 8133 } 8134 } 8135 8136 pub fn code_actions(mut self) -> Vec<CodeAction> { 8137 self.visit_typed_module(&self.module.ast); 8138 8139 let Some(FillUnusedFieldsData { 8140 positional, 8141 labelled, 8142 first_labelled_argument_start, 8143 last_argument_end, 8144 spread_location, 8145 }) = self.data 8146 else { 8147 return vec![]; 8148 }; 8149 8150 // Do not suggest this code action if there's no ignored fields at all. 8151 if positional.is_empty() && labelled.is_empty() { 8152 return vec![]; 8153 }; 8154 8155 // We add all the missing positional arguments before the first 8156 // labelled one (and so after all the already existing positional ones). 8157 if !positional.is_empty() { 8158 // We want to make sure that all positional args will have a name 8159 // that's different from any label. So we add those as already used 8160 // names. 8161 let mut names = NameGenerator::new(); 8162 for (label, _) in labelled.iter() { 8163 names.add_used_name(label.clone()); 8164 } 8165 8166 let positional_arguments = positional 8167 .iter() 8168 .map(|type_| names.generate_name_from_type(type_)) 8169 .join(", "); 8170 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); 8171 8172 // The positional arguments are going to be followed by some other 8173 // arguments if there's some already existing labelled args 8174 // (`last_argument_end.is_some`), of if we're adding those labelled args 8175 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the 8176 // final positional argument we're adding to separate it from the ones that 8177 // are going to come after. 8178 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); 8179 let positional_arguments = if has_arguments_after { 8180 format!("{positional_arguments}, ") 8181 } else { 8182 positional_arguments 8183 }; 8184 8185 self.edits.insert(insert_at, positional_arguments); 8186 } 8187 8188 if !labelled.is_empty() { 8189 // If there's labelled arguments to add, we replace the existing spread 8190 // with the arguments to be added. This way commas and all should already 8191 // be correct. 8192 let labelled_arguments = labelled 8193 .iter() 8194 .map(|(label, _)| format!("{label}:")) 8195 .join(", "); 8196 self.edits.replace(spread_location, labelled_arguments); 8197 } else if let Some(delete_start) = last_argument_end { 8198 // However, if there's no labelled arguments to insert we still need 8199 // to delete the entire spread: we start deleting from the end of the 8200 // final argument, if there's one. 8201 // This way we also get rid of any comma separating the last argument 8202 // and the spread to be removed. 8203 self.edits 8204 .delete(SrcSpan::new(delete_start, spread_location.end)) 8205 } else { 8206 // Otherwise we just delete the spread. 8207 self.edits.delete(spread_location) 8208 } 8209 8210 let mut action = Vec::with_capacity(1); 8211 CodeActionBuilder::new("Fill unused fields") 8212 .kind(CodeActionKind::RefactorRewrite) 8213 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8214 .preferred(false) 8215 .push_to(&mut action); 8216 action 8217 } 8218} 8219 8220impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> { 8221 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { 8222 // We can only interpolate/split a string if the cursor is somewhere 8223 // within its location, otherwise we skip it. 8224 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); 8225 if !within(self.params.range, pattern_range) { 8226 return; 8227 } 8228 8229 if let TypedPattern::Constructor { 8230 arguments, 8231 spread: Some(spread_location), 8232 .. 8233 } = pattern 8234 && let Some(PatternUnusedArguments { 8235 positional, 8236 labelled, 8237 }) = pattern.unused_arguments() 8238 { 8239 // If there's any unused argument that's being ignored we want to 8240 // suggest the code action. 8241 let first_labelled_argument_start = arguments 8242 .iter() 8243 .find(|arg| !arg.is_implicit() && arg.label.is_some()) 8244 .map(|arg| arg.location.start); 8245 8246 let last_argument_end = arguments 8247 .iter() 8248 .rfind(|arg| !arg.is_implicit()) 8249 .map(|arg| arg.location.end); 8250 8251 self.data = Some(FillUnusedFieldsData { 8252 positional, 8253 labelled, 8254 first_labelled_argument_start, 8255 last_argument_end, 8256 spread_location: *spread_location, 8257 }); 8258 }; 8259 8260 ast::visit::visit_typed_pattern(self, pattern); 8261 } 8262} 8263 8264/// Code action to remove an echo. 8265/// 8266pub struct RemoveEchos<'a> { 8267 module: &'a Module, 8268 params: &'a CodeActionParams, 8269 edits: TextEdits<'a>, 8270 is_hovering_echo: bool, 8271 echo_spans_to_delete: Vec<SrcSpan>, 8272 // We need to keep a reference to the two latest pipeline assignments we 8273 // run into to properly delete an echo that's inside a pipeline. 8274 latest_pipe_step: Option<SrcSpan>, 8275 second_to_latest_pipe_step: Option<SrcSpan>, 8276} 8277 8278impl<'a> RemoveEchos<'a> { 8279 pub fn new( 8280 module: &'a Module, 8281 line_numbers: &'a LineNumbers, 8282 params: &'a CodeActionParams, 8283 ) -> Self { 8284 Self { 8285 module, 8286 params, 8287 edits: TextEdits::new(line_numbers), 8288 is_hovering_echo: false, 8289 echo_spans_to_delete: vec![], 8290 latest_pipe_step: None, 8291 second_to_latest_pipe_step: None, 8292 } 8293 } 8294 8295 pub fn code_actions(mut self) -> Vec<CodeAction> { 8296 self.visit_typed_module(&self.module.ast); 8297 8298 // We only want to trigger the action if we're over one of the echos in 8299 // the module 8300 if !self.is_hovering_echo { 8301 return vec![]; 8302 }; 8303 8304 for span in self.echo_spans_to_delete { 8305 self.edits.delete(span); 8306 } 8307 8308 let mut action = Vec::with_capacity(1); 8309 CodeActionBuilder::new("Remove all `echo`s from this module") 8310 .kind(CodeActionKind::RefactorRewrite) 8311 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8312 .preferred(false) 8313 .push_to(&mut action); 8314 action 8315 } 8316 8317 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) { 8318 for i in 0..statements.len() { 8319 let statement = statements 8320 .get(i) 8321 .expect("Statement must exist in iteration"); 8322 let next_statement = statements.get(i + 1); 8323 let is_last = i == statements.len() - 1; 8324 8325 match statement { 8326 // We remove any echo that is used as a standalone statement used 8327 // to print a literal value. 8328 // 8329 // ```gleam 8330 // pub fn main() { 8331 // echo "I'm here" 8332 // do_something() 8333 // echo "Safe!" 8334 // do_something_else() 8335 // } 8336 // ``` 8337 // 8338 // Here we want to remove not just the echo but also the literal 8339 // strings they're printing. 8340 // 8341 // It's safe to do this only if echo is not the last expression 8342 // in a function's block (otherwise we might change the function's 8343 // return type by removing the entire line) and the value being 8344 // printed is a literal expression. 8345 // 8346 ast::Statement::Expression(TypedExpr::Echo { 8347 location, 8348 expression, 8349 .. 8350 }) if !is_last 8351 && expression.as_ref().is_some_and(|expression| { 8352 expression.is_literal() || expression.is_var() 8353 }) => 8354 { 8355 let echo_range = self.edits.src_span_to_lsp_range(*location); 8356 if within(self.params.range, echo_range) { 8357 self.is_hovering_echo = true; 8358 } 8359 8360 let end = next_statement 8361 .map(|next| { 8362 let echo_end = location.end; 8363 let next_start = next.location().start; 8364 // We want to remove everything until the start of the 8365 // following statement. However, we have to be careful not to 8366 // delete any comments. So if there's any comment between the 8367 // echo to remove and the next statement, we just delete until 8368 // the comment's start. 8369 self.module 8370 .extra 8371 .first_comment_between(echo_end, next_start) 8372 // For comments we record the start of their content, not of the `//` 8373 // so we're subtracting 2 here to not delete the `//` as well 8374 .map(|comment| comment.start - 2) 8375 .unwrap_or(next_start) 8376 }) 8377 .unwrap_or(location.end); 8378 8379 self.echo_spans_to_delete.push(SrcSpan { 8380 start: location.start, 8381 end, 8382 }); 8383 } 8384 8385 // Otherwise we visit the statement as usual. 8386 ast::Statement::Expression(_) 8387 | ast::Statement::Assignment(_) 8388 | ast::Statement::Use(_) 8389 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement), 8390 } 8391 } 8392 } 8393} 8394 8395impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> { 8396 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 8397 self.visit_function_statements(&fun.body); 8398 } 8399 8400 fn visit_typed_expr_fn( 8401 &mut self, 8402 _location: &'ast SrcSpan, 8403 _type_: &'ast Arc<Type>, 8404 _kind: &'ast FunctionLiteralKind, 8405 _arguments: &'ast [TypedArg], 8406 body: &'ast Vec1<TypedStatement>, 8407 _return_annotation: &'ast Option<ast::TypeAst>, 8408 ) { 8409 self.visit_function_statements(body); 8410 } 8411 8412 fn visit_typed_expr_echo( 8413 &mut self, 8414 location: &'ast SrcSpan, 8415 type_: &'ast Arc<Type>, 8416 expression: &'ast Option<Box<TypedExpr>>, 8417 message: &'ast Option<Box<TypedExpr>>, 8418 ) { 8419 // We also want to trigger the action if we're hovering over the expression 8420 // being printed. So we create a unique span starting from the start of echo 8421 // end ending at the end of the expression. 8422 // 8423 // ``` 8424 // echo 1 + 2 8425 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're 8426 // inside it, not just the keyword 8427 // ``` 8428 // 8429 let echo_range = self.edits.src_span_to_lsp_range(*location); 8430 if within(self.params.range, echo_range) { 8431 self.is_hovering_echo = true; 8432 } 8433 8434 // We also want to remove the echo message! 8435 if message.is_some() { 8436 let start = expression 8437 .as_ref() 8438 .map(|expression| expression.location().end) 8439 .unwrap_or(location.start + 4); 8440 8441 self.echo_spans_to_delete 8442 .push(SrcSpan::new(start, location.end)); 8443 } 8444 8445 if let Some(expression) = expression { 8446 // If there's an expression we delete everything we find until its 8447 // start (excluded). 8448 let span_to_delete = SrcSpan::new(location.start, expression.location().start); 8449 self.echo_spans_to_delete.push(span_to_delete); 8450 } else { 8451 // Othwerise we know we're inside a pipeline, we take the closest step 8452 // that is not echo itself and delete everything from its end until the 8453 // end of the echo keyword: 8454 // 8455 // ```txt 8456 // wibble |> echo |> wobble 8457 // ^^^^^^^^ This span right here 8458 // ``` 8459 let step_preceding_echo = self 8460 .latest_pipe_step 8461 .filter(|l| l != location) 8462 .or(self.second_to_latest_pipe_step); 8463 if let Some(step_preceding_echo) = step_preceding_echo { 8464 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4); 8465 self.echo_spans_to_delete.push(span_to_delete); 8466 } 8467 } 8468 8469 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message); 8470 } 8471 8472 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 8473 if self.latest_pipe_step.is_some() { 8474 self.second_to_latest_pipe_step = self.latest_pipe_step; 8475 } 8476 self.latest_pipe_step = Some(assignment.location); 8477 ast::visit::visit_typed_pipeline_assignment(self, assignment); 8478 } 8479} 8480 8481/// Code action to wrap assignment and case clause values in a block. 8482/// 8483/// ```gleam 8484/// pub type PokemonType { 8485/// Fire 8486/// Water 8487/// } 8488/// 8489/// pub fn main() { 8490/// let pokemon_type: PokemonType = todo 8491/// case pokemon_type { 8492/// Water -> soak() 8493/// ^^^^^^ Cursor over the spread 8494/// Fire -> burn() 8495/// } 8496/// } 8497/// ``` 8498/// Becomes 8499/// ```gleam 8500/// pub type PokemonType { 8501/// Fire 8502/// Water 8503/// } 8504/// 8505/// pub fn main() { 8506/// let pokemon_type: PokemonType = todo 8507/// case pokemon_type { 8508/// Water -> { 8509/// soak() 8510/// } 8511/// Fire -> burn() 8512/// } 8513/// } 8514/// ``` 8515/// 8516pub struct WrapInBlock<'a> { 8517 module: &'a Module, 8518 params: &'a CodeActionParams, 8519 edits: TextEdits<'a>, 8520 selected_expression: Option<SrcSpan>, 8521} 8522 8523impl<'a> WrapInBlock<'a> { 8524 pub fn new( 8525 module: &'a Module, 8526 line_numbers: &'a LineNumbers, 8527 params: &'a CodeActionParams, 8528 ) -> Self { 8529 Self { 8530 module, 8531 params, 8532 edits: TextEdits::new(line_numbers), 8533 selected_expression: None, 8534 } 8535 } 8536 8537 pub fn code_actions(mut self) -> Vec<CodeAction> { 8538 self.visit_typed_module(&self.module.ast); 8539 8540 let Some(expr_span) = self.selected_expression else { 8541 return vec![]; 8542 }; 8543 8544 let Some(expr_string) = self 8545 .module 8546 .code 8547 .get(expr_span.start as usize..(expr_span.end as usize + 1)) 8548 else { 8549 return vec![]; 8550 }; 8551 8552 let range = self 8553 .edits 8554 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 8555 8556 let indent_size = 8557 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 8558 8559 let expr_indent_size = indent_size + 2; 8560 8561 let indent = " ".repeat(indent_size); 8562 let inner_indent = " ".repeat(expr_indent_size); 8563 8564 self.edits.replace( 8565 expr_span, 8566 format!("{{\n{inner_indent}{expr_string}{indent}}}"), 8567 ); 8568 8569 let mut action = Vec::with_capacity(1); 8570 CodeActionBuilder::new("Wrap in block") 8571 .kind(CodeActionKind::RefactorExtract) 8572 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8573 .preferred(false) 8574 .push_to(&mut action); 8575 action 8576 } 8577} 8578 8579impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> { 8580 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 8581 ast::visit::visit_typed_expr(self, &assignment.value); 8582 if !within( 8583 self.params.range, 8584 self.edits 8585 .src_span_to_lsp_range(assignment.value.location()), 8586 ) { 8587 return; 8588 } 8589 match &assignment.value { 8590 // To avoid wrapping the same expression in multiple, nested blocks. 8591 TypedExpr::Block { .. } => {} 8592 TypedExpr::RecordAccess { .. } 8593 | TypedExpr::PositionalAccess { .. } 8594 | TypedExpr::Int { .. } 8595 | TypedExpr::Float { .. } 8596 | TypedExpr::String { .. } 8597 | TypedExpr::Pipeline { .. } 8598 | TypedExpr::Var { .. } 8599 | TypedExpr::Fn { .. } 8600 | TypedExpr::List { .. } 8601 | TypedExpr::Call { .. } 8602 | TypedExpr::BinOp { .. } 8603 | TypedExpr::Case { .. } 8604 | TypedExpr::ModuleSelect { .. } 8605 | TypedExpr::Tuple { .. } 8606 | TypedExpr::TupleIndex { .. } 8607 | TypedExpr::Todo { .. } 8608 | TypedExpr::Panic { .. } 8609 | TypedExpr::Echo { .. } 8610 | TypedExpr::BitArray { .. } 8611 | TypedExpr::RecordUpdate { .. } 8612 | TypedExpr::NegateBool { .. } 8613 | TypedExpr::NegateInt { .. } 8614 | TypedExpr::Invalid { .. } => { 8615 self.selected_expression = Some(assignment.value.location()); 8616 } 8617 }; 8618 ast::visit::visit_typed_assignment(self, assignment); 8619 } 8620 8621 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 8622 ast::visit::visit_typed_clause(self, clause); 8623 8624 if !within( 8625 self.params.range, 8626 self.edits.src_span_to_lsp_range(clause.then.location()), 8627 ) { 8628 return; 8629 } 8630 8631 // To avoid wrapping the same expression in multiple, nested blocks. 8632 if !matches!(clause.then, TypedExpr::Block { .. }) { 8633 self.selected_expression = Some(clause.then.location()); 8634 }; 8635 8636 ast::visit::visit_typed_clause(self, clause); 8637 } 8638} 8639 8640/// Code action to fix wrong binary operators when the compiler can easily tell 8641/// what the correct alternative is. 8642/// 8643/// ```gleam 8644/// 1 +. 2 // becomes 1 + 2 8645/// 1.0 + 2.3 // becomes 1.0 +. 2.3 8646/// ``` 8647/// 8648pub struct FixBinaryOperation<'a> { 8649 module: &'a Module, 8650 params: &'a CodeActionParams, 8651 edits: TextEdits<'a>, 8652 fix: Option<(SrcSpan, ast::BinOp)>, 8653} 8654 8655impl<'a> FixBinaryOperation<'a> { 8656 pub fn new( 8657 module: &'a Module, 8658 line_numbers: &'a LineNumbers, 8659 params: &'a CodeActionParams, 8660 ) -> Self { 8661 Self { 8662 module, 8663 params, 8664 edits: TextEdits::new(line_numbers), 8665 fix: None, 8666 } 8667 } 8668 8669 pub fn code_actions(mut self) -> Vec<CodeAction> { 8670 self.visit_typed_module(&self.module.ast); 8671 8672 let Some((location, replacement)) = self.fix else { 8673 return vec![]; 8674 }; 8675 8676 self.edits.replace(location, replacement.name().into()); 8677 8678 let mut action = Vec::with_capacity(1); 8679 CodeActionBuilder::new(format!("Use `{}`", replacement.name())) 8680 .kind(CodeActionKind::RefactorRewrite) 8681 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8682 .preferred(true) 8683 .push_to(&mut action); 8684 action 8685 } 8686 8687 fn try_fix( 8688 &mut self, 8689 left: Arc<Type>, 8690 right: Arc<Type>, 8691 operator: ast::BinOp, 8692 operator_start: u32, 8693 ) { 8694 let operator_location = SrcSpan::new(operator_start, operator_start + operator.size()); 8695 if operator.is_int_operator() && left.is_float() && right.is_float() { 8696 self.fix = operator 8697 .float_equivalent() 8698 .map(|fix| (operator_location, fix)); 8699 } else if operator.is_float_operator() && left.is_int() && right.is_int() { 8700 self.fix = operator 8701 .int_equivalent() 8702 .map(|fix| (operator_location, fix)) 8703 } else if operator == ast::BinOp::AddInt && left.is_string() && right.is_string() { 8704 self.fix = Some((operator_location, ast::BinOp::Concatenate)) 8705 } 8706 } 8707} 8708 8709impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> { 8710 fn visit_typed_expr_case( 8711 &mut self, 8712 location: &'ast SrcSpan, 8713 type_: &'ast Arc<Type>, 8714 subjects: &'ast [TypedExpr], 8715 clauses: &'ast [ast::TypedClause], 8716 compiled_case: &'ast CompiledCase, 8717 ) { 8718 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 8719 } 8720 fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) { 8721 ast::visit::visit_typed_clause_guard(self, guard); 8722 } 8723 8724 fn visit_typed_clause_guard_bin_op( 8725 &mut self, 8726 left: &'ast TypedClauseGuard, 8727 right: &'ast TypedClauseGuard, 8728 operator: &'ast ast::BinOp, 8729 operator_start: &'ast u32, 8730 location: &'ast SrcSpan, 8731 ) { 8732 let binop_range = self.edits.src_span_to_lsp_range(*location); 8733 if !within(self.params.range, binop_range) { 8734 return; 8735 } 8736 8737 self.try_fix(left.type_(), right.type_(), *operator, *operator_start); 8738 8739 ast::visit::visit_typed_clause_guard_bin_op( 8740 self, 8741 left, 8742 right, 8743 operator, 8744 operator_start, 8745 location, 8746 ); 8747 } 8748 8749 fn visit_typed_expr_bin_op( 8750 &mut self, 8751 location: &'ast SrcSpan, 8752 type_: &'ast Arc<Type>, 8753 operator: &'ast ast::BinOp, 8754 operator_start: &'ast u32, 8755 left: &'ast TypedExpr, 8756 right: &'ast TypedExpr, 8757 ) { 8758 let binop_range = self.edits.src_span_to_lsp_range(*location); 8759 if !within(self.params.range, binop_range) { 8760 return; 8761 } 8762 8763 self.try_fix(left.type_(), right.type_(), *operator, *operator_start); 8764 8765 ast::visit::visit_typed_expr_bin_op( 8766 self, 8767 location, 8768 type_, 8769 operator, 8770 operator_start, 8771 left, 8772 right, 8773 ); 8774 } 8775} 8776 8777/// Code action builder to automatically fix segments that have a value that's 8778/// guaranteed to overflow. 8779/// 8780pub struct FixTruncatedBitArraySegment<'a> { 8781 module: &'a Module, 8782 params: &'a CodeActionParams, 8783 edits: TextEdits<'a>, 8784 truncation: Option<BitArraySegmentTruncation>, 8785} 8786 8787impl<'a> FixTruncatedBitArraySegment<'a> { 8788 pub fn new( 8789 module: &'a Module, 8790 line_numbers: &'a LineNumbers, 8791 params: &'a CodeActionParams, 8792 ) -> Self { 8793 Self { 8794 module, 8795 params, 8796 edits: TextEdits::new(line_numbers), 8797 truncation: None, 8798 } 8799 } 8800 8801 pub fn code_actions(mut self) -> Vec<CodeAction> { 8802 self.visit_typed_module(&self.module.ast); 8803 8804 let Some(truncation) = self.truncation else { 8805 return vec![]; 8806 }; 8807 8808 let replacement = truncation.truncated_into.to_string(); 8809 self.edits 8810 .replace(truncation.value_location, replacement.clone()); 8811 8812 let mut action = Vec::with_capacity(1); 8813 CodeActionBuilder::new(format!("Replace with `{replacement}`")) 8814 .kind(CodeActionKind::RefactorRewrite) 8815 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8816 .preferred(true) 8817 .push_to(&mut action); 8818 action 8819 } 8820} 8821 8822impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> { 8823 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) { 8824 let segment_range = self.edits.src_span_to_lsp_range(segment.location); 8825 if !within(self.params.range, segment_range) { 8826 return; 8827 } 8828 8829 if let Some(truncation) = segment.check_for_truncated_value() { 8830 self.truncation = Some(truncation); 8831 } 8832 8833 ast::visit::visit_typed_expr_bit_array_segment(self, segment); 8834 } 8835} 8836 8837/// Code action builder to remove unused imports and values. 8838/// 8839pub struct RemoveUnusedImports<'a> { 8840 module: &'a Module, 8841 params: &'a CodeActionParams, 8842 edits: TextEdits<'a>, 8843} 8844 8845#[derive(Debug)] 8846enum UnusedImport { 8847 ValueOrType(SrcSpan), 8848 Module(SrcSpan), 8849 ModuleAlias(SrcSpan), 8850} 8851 8852impl UnusedImport { 8853 fn location(&self) -> SrcSpan { 8854 match self { 8855 UnusedImport::ValueOrType(location) 8856 | UnusedImport::Module(location) 8857 | UnusedImport::ModuleAlias(location) => *location, 8858 } 8859 } 8860} 8861 8862impl<'a> RemoveUnusedImports<'a> { 8863 pub fn new( 8864 module: &'a Module, 8865 line_numbers: &'a LineNumbers, 8866 params: &'a CodeActionParams, 8867 ) -> Self { 8868 Self { 8869 module, 8870 params, 8871 edits: TextEdits::new(line_numbers), 8872 } 8873 } 8874 8875 /// Given an import location, returns a list of the spans of all the 8876 /// unqualified values it's importing. Sorted by SrcSpan location. 8877 /// 8878 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> { 8879 self.module 8880 .ast 8881 .definitions 8882 .imports 8883 .iter() 8884 .find(|import| import.location.contains(import_location.start)) 8885 .map(|import| { 8886 let types = import.unqualified_types.iter().map(|type_| type_.location); 8887 let values = import.unqualified_values.iter().map(|value| value.location); 8888 types 8889 .chain(values) 8890 .sorted_by_key(|location| location.start) 8891 .collect_vec() 8892 }) 8893 .unwrap_or_default() 8894 } 8895 8896 pub fn code_actions(mut self) -> Vec<CodeAction> { 8897 // If there's no import in the module then there can't be any unused 8898 // import to remove. 8899 if self.module.ast.definitions.imports.is_empty() { 8900 return vec![]; 8901 } 8902 8903 let unused_imports = self 8904 .module 8905 .ast 8906 .type_info 8907 .warnings 8908 .iter() 8909 .filter_map(|warning| match warning { 8910 type_::Warning::UnusedImportedValue { location, .. } => { 8911 Some(UnusedImport::ValueOrType(*location)) 8912 } 8913 type_::Warning::UnusedType { 8914 location, 8915 imported: true, 8916 .. 8917 } => Some(UnusedImport::ValueOrType(*location)), 8918 type_::Warning::UnusedImportedModule { location, .. } => { 8919 Some(UnusedImport::Module(*location)) 8920 } 8921 type_::Warning::UnusedImportedModuleAlias { location, .. } => { 8922 Some(UnusedImport::ModuleAlias(*location)) 8923 } 8924 type_::Warning::Todo { .. } 8925 | type_::Warning::ImplicitlyDiscardedResult { .. } 8926 | type_::Warning::UnusedLiteral { .. } 8927 | type_::Warning::UnusedValue { .. } 8928 | type_::Warning::NoFieldsRecordUpdate { .. } 8929 | type_::Warning::AllFieldsRecordUpdate { .. } 8930 | type_::Warning::UnusedType { .. } 8931 | type_::Warning::UnusedConstructor { .. } 8932 | type_::Warning::UnusedPrivateModuleConstant { .. } 8933 | type_::Warning::UnusedPrivateFunction { .. } 8934 | type_::Warning::UnusedVariable { .. } 8935 | type_::Warning::UnnecessaryDoubleIntNegation { .. } 8936 | type_::Warning::UnnecessaryDoubleBoolNegation { .. } 8937 | type_::Warning::InefficientEmptyListCheck { .. } 8938 | type_::Warning::TransitiveDependencyImported { .. } 8939 | type_::Warning::DeprecatedItem { .. } 8940 | type_::Warning::UnreachableCasePattern { .. } 8941 | type_::Warning::UnusedDiscardPattern { .. } 8942 | type_::Warning::CaseMatchOnLiteralCollection { .. } 8943 | type_::Warning::CaseMatchOnLiteralValue { .. } 8944 | type_::Warning::OpaqueExternalType { .. } 8945 | type_::Warning::RedundantAssertAssignment { .. } 8946 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. } 8947 | type_::Warning::TodoOrPanicUsedAsFunction { .. } 8948 | type_::Warning::UnreachableCodeAfterPanic { .. } 8949 | type_::Warning::RedundantPipeFunctionCapture { .. } 8950 | type_::Warning::FeatureRequiresHigherGleamVersion { .. } 8951 | type_::Warning::JavaScriptIntUnsafe { .. } 8952 | type_::Warning::AssertLiteralBool { .. } 8953 | type_::Warning::BitArraySegmentTruncatedValue { .. } 8954 | type_::Warning::ModuleImportedTwice { .. } 8955 | type_::Warning::TopLevelDefinitionShadowsImport { .. } 8956 | type_::Warning::RedundantComparison { .. } 8957 | type_::Warning::UnusedRecursiveArgument { .. } 8958 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. } => None, 8959 }) 8960 .sorted_by_key(|import| import.location()) 8961 .collect_vec(); 8962 8963 // If the cursor is not over any of the unused imports then we don't offer 8964 // the code action. 8965 let hovering_unused_import = unused_imports.iter().any(|import| { 8966 let unused_range = self.edits.src_span_to_lsp_range(import.location()); 8967 overlaps(self.params.range, unused_range) 8968 }); 8969 if !hovering_unused_import { 8970 return vec![]; 8971 } 8972 8973 // Otherwise we start removing all unused imports: 8974 for import in &unused_imports { 8975 match import { 8976 // When an entire module is unused we can delete its entire location 8977 // in the source code. 8978 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { 8979 if self.edits.line_numbers.spans_entire_line(location) { 8980 // If the unused module spans over the entire line then 8981 // we also take care of removing the following newline 8982 // characther! 8983 self.edits.delete(SrcSpan { 8984 start: location.start, 8985 end: location.end + 1, 8986 }) 8987 } else { 8988 self.edits.delete(*location) 8989 } 8990 } 8991 8992 // When removing unused imported values we have to be a bit more 8993 // careful: an unused value might be followed or preceded by a 8994 // comma that we also need to remove! 8995 UnusedImport::ValueOrType(location) => { 8996 let imported = self.imported_values(*location); 8997 let unused_index = imported.binary_search(location); 8998 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1); 8999 let next_value = unused_index 9000 .ok() 9001 .and_then(|value_index| imported.get(value_index + 1)); 9002 let previous_value = unused_index.ok().and_then(|value_index| { 9003 value_index 9004 .checked_sub(1) 9005 .and_then(|previous_index| imported.get(previous_index)) 9006 }); 9007 let previous_is_unused = previous_value.is_some_and(|previous| { 9008 unused_imports 9009 .as_slice() 9010 .binary_search_by_key(previous, |import| import.location()) 9011 .is_ok() 9012 }); 9013 9014 match (previous_value, next_value) { 9015 // If there's a value following the unused import we need 9016 // to remove all characters until its start! 9017 // 9018 // ```gleam 9019 // import wibble.{unused, used} 9020 // // ^^^^^^^^^^^ We need to remove all of this! 9021 // ``` 9022 // 9023 (_, Some(next_value)) => self.edits.delete(SrcSpan { 9024 start: location.start, 9025 end: next_value.start, 9026 }), 9027 9028 // If this unused import is the last of the unuqualified 9029 // list and is preceded by another used value then we 9030 // need to do some additional cleanup and remove all 9031 // characters starting from its end. 9032 // (If the previous one is unused as well it will take 9033 // care of removing all the extra space) 9034 // 9035 // ```gleam 9036 // import wibble.{used, unused} 9037 // // ^^^^^^^^^^^^ We need to remove all of this! 9038 // ``` 9039 // 9040 (Some(previous_value), _) if is_last && !previous_is_unused => { 9041 self.edits.delete(SrcSpan { 9042 start: previous_value.end, 9043 end: location.end, 9044 }) 9045 } 9046 9047 // In all other cases it means that this is the only 9048 // item in the import list. We can just remove it. 9049 // 9050 // ```gleam 9051 // import wibble.{unused} 9052 // // ^^^^^^ We remove this import, the formatter will already 9053 // // take care of removing the empty curly braces 9054 // ``` 9055 // 9056 (_, _) => self.edits.delete(*location), 9057 } 9058 } 9059 } 9060 } 9061 9062 let mut action = Vec::with_capacity(1); 9063 CodeActionBuilder::new("Remove unused imports") 9064 .kind(CodeActionKind::RefactorRewrite) 9065 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9066 .preferred(true) 9067 .push_to(&mut action); 9068 action 9069 } 9070} 9071 9072/// Code action to remove a block wrapping a single expression. 9073/// 9074pub struct RemoveBlock<'a> { 9075 module: &'a Module, 9076 params: &'a CodeActionParams, 9077 edits: TextEdits<'a>, 9078 block_span: Option<SrcSpan>, 9079 position: RemoveBlockPosition, 9080} 9081 9082#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] 9083enum RemoveBlockPosition { 9084 InsideBinOp, 9085 OutsideBinOp, 9086} 9087 9088impl<'a> RemoveBlock<'a> { 9089 pub fn new( 9090 module: &'a Module, 9091 line_numbers: &'a LineNumbers, 9092 params: &'a CodeActionParams, 9093 ) -> Self { 9094 Self { 9095 module, 9096 params, 9097 edits: TextEdits::new(line_numbers), 9098 block_span: None, 9099 position: RemoveBlockPosition::OutsideBinOp, 9100 } 9101 } 9102 9103 pub fn code_actions(mut self) -> Vec<CodeAction> { 9104 self.visit_typed_module(&self.module.ast); 9105 9106 let Some(SrcSpan { start, end }) = self.block_span else { 9107 return vec![]; 9108 }; 9109 9110 self.edits.delete(SrcSpan::new(start, start + 1)); 9111 self.edits.delete(SrcSpan::new(end - 1, end)); 9112 9113 let mut action = Vec::with_capacity(1); 9114 CodeActionBuilder::new("Remove block") 9115 .kind(CodeActionKind::RefactorRewrite) 9116 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9117 .preferred(true) 9118 .push_to(&mut action); 9119 action 9120 } 9121} 9122 9123impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> { 9124 fn visit_typed_expr_bin_op( 9125 &mut self, 9126 _location: &'ast SrcSpan, 9127 _type_: &'ast Arc<Type>, 9128 _operator: &'ast ast::BinOp, 9129 _operator_start: &'ast u32, 9130 left: &'ast TypedExpr, 9131 right: &'ast TypedExpr, 9132 ) { 9133 let old_position = self.position; 9134 self.position = RemoveBlockPosition::InsideBinOp; 9135 ast::visit::visit_typed_expr(self, left); 9136 self.position = RemoveBlockPosition::InsideBinOp; 9137 ast::visit::visit_typed_expr(self, right); 9138 self.position = old_position; 9139 } 9140 9141 fn visit_typed_expr_block( 9142 &mut self, 9143 location: &'ast SrcSpan, 9144 statements: &'ast [TypedStatement], 9145 ) { 9146 let block_range = self.edits.src_span_to_lsp_range(*location); 9147 if !within(self.params.range, block_range) { 9148 return; 9149 } 9150 9151 match statements { 9152 [] | [_, _, ..] => (), 9153 [value] => match value { 9154 ast::Statement::Use(_) 9155 | ast::Statement::Assert(_) 9156 | ast::Statement::Assignment(_) => { 9157 ast::visit::visit_typed_expr_block(self, location, statements) 9158 } 9159 9160 ast::Statement::Expression(expr) => match expr { 9161 TypedExpr::Int { .. } 9162 | TypedExpr::Float { .. } 9163 | TypedExpr::String { .. } 9164 | TypedExpr::Block { .. } 9165 | TypedExpr::Var { .. } 9166 | TypedExpr::Fn { .. } 9167 | TypedExpr::List { .. } 9168 | TypedExpr::Call { .. } 9169 | TypedExpr::Case { .. } 9170 | TypedExpr::RecordAccess { .. } 9171 | TypedExpr::PositionalAccess { .. } 9172 | TypedExpr::ModuleSelect { .. } 9173 | TypedExpr::Tuple { .. } 9174 | TypedExpr::TupleIndex { .. } 9175 | TypedExpr::Todo { .. } 9176 | TypedExpr::Panic { .. } 9177 | TypedExpr::Echo { .. } 9178 | TypedExpr::BitArray { .. } 9179 | TypedExpr::RecordUpdate { .. } 9180 | TypedExpr::NegateBool { .. } 9181 | TypedExpr::NegateInt { .. } 9182 | TypedExpr::Invalid { .. } => { 9183 self.block_span = Some(*location); 9184 } 9185 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => { 9186 if self.position == RemoveBlockPosition::OutsideBinOp { 9187 self.block_span = Some(*location); 9188 } 9189 } 9190 }, 9191 }, 9192 } 9193 9194 ast::visit::visit_typed_expr_block(self, location, statements); 9195 } 9196} 9197 9198/// Code action to remove `opaque` from a private type. 9199/// 9200pub struct RemovePrivateOpaque<'a> { 9201 module: &'a Module, 9202 params: &'a CodeActionParams, 9203 edits: TextEdits<'a>, 9204 opaque_span: Option<SrcSpan>, 9205} 9206 9207impl<'a> RemovePrivateOpaque<'a> { 9208 pub fn new( 9209 module: &'a Module, 9210 line_numbers: &'a LineNumbers, 9211 params: &'a CodeActionParams, 9212 ) -> Self { 9213 Self { 9214 module, 9215 params, 9216 edits: TextEdits::new(line_numbers), 9217 opaque_span: None, 9218 } 9219 } 9220 9221 pub fn code_actions(mut self) -> Vec<CodeAction> { 9222 self.visit_typed_module(&self.module.ast); 9223 9224 let Some(opaque_span) = self.opaque_span else { 9225 return vec![]; 9226 }; 9227 9228 self.edits.delete(opaque_span); 9229 9230 let mut action = Vec::with_capacity(1); 9231 CodeActionBuilder::new("Remove opaque from private type") 9232 .kind(CodeActionKind::QuickFix) 9233 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9234 .preferred(true) 9235 .push_to(&mut action); 9236 action 9237 } 9238} 9239 9240impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> { 9241 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 9242 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location); 9243 if !within(self.params.range, custom_type_range) { 9244 return; 9245 } 9246 9247 if custom_type.opaque && custom_type.publicity.is_private() { 9248 self.opaque_span = Some(SrcSpan { 9249 start: custom_type.location.start, 9250 end: custom_type.location.start + 7, 9251 }) 9252 } 9253 } 9254} 9255 9256/// Code action to rewrite a case expression as part of an outer case expression 9257/// branch. For example: 9258/// 9259/// ```gleam 9260/// case wibble { 9261/// Ok(a) -> case a { 9262/// 1 -> todo 9263/// _ -> todo 9264/// } 9265/// Error(_) -> todo 9266/// } 9267/// ``` 9268/// 9269/// Would become: 9270/// 9271/// ```gleam 9272/// case wibble { 9273/// Ok(1) -> todo 9274/// Ok(_) -> todo 9275/// Error(_) -> todo 9276/// } 9277/// ``` 9278/// 9279pub struct CollapseNestedCase<'a> { 9280 module: &'a Module, 9281 params: &'a CodeActionParams, 9282 edits: TextEdits<'a>, 9283 collapsed: Option<Collapsed<'a>>, 9284} 9285 9286/// This holds all the needed data about the pattern to collapse. 9287/// We'll use this piece of code as an example: 9288/// ```gleam 9289/// case something { 9290/// User(username: _, NotAdmin) -> "Stranger!!" 9291/// User(username:, Admin) if wibble -> 9292/// case username { // <- We're collapsing this nested case 9293/// "Joe" -> "Hello, Joe!" 9294/// _ -> "I don't know you, " <> username 9295/// } 9296/// } 9297/// ``` 9298/// 9299struct Collapsed<'a> { 9300 /// This is the span covering the entire clause being collapsed: 9301 /// 9302 /// ```gleam 9303 /// case something { 9304 /// User(username: _, NotAdmin) -> "Stranger!!" 9305 /// User(username:, Admin) if wibble -> 9306 /// ┬ It goes all the way from here... 9307 /// ╭─╯ 9308 /// │ case username { 9309 /// │ "Joe" -> "Hello, Joe!" 9310 /// │ _ -> "I don't know you, " <> username 9311 /// │ } 9312 /// │ ┬ ...to here! 9313 /// ╰───╯ 9314 /// } 9315 /// ``` 9316 /// 9317 outer_clause_span: SrcSpan, 9318 9319 /// The (optional) guard of the outer branch. In this exmaple it's this one: 9320 /// 9321 /// ```gleam 9322 /// case something { 9323 /// User(username: _, NotAdmin) -> "Stranger!!" 9324 /// User(username:, Admin) if wibble -> 9325 /// ┬──────── 9326 /// ╰─ `outer_guard` 9327 /// case username { 9328 /// "Joe" -> "Hello, Joe!" 9329 /// _ -> "I don't know you, " <> username 9330 /// } 9331 /// } 9332 /// ``` 9333 /// 9334 outer_guard: &'a Option<TypedClauseGuard>, 9335 9336 /// The pattern variable being matched on: 9337 /// 9338 /// ```gleam 9339 /// case something { 9340 /// User(username: _, NotAdmin) -> "Stranger!!" 9341 /// User(username:, Admin) if wibble -> 9342 /// ┬─────── 9343 /// ╰─ `matched_variable` 9344 /// case username { 9345 /// "Joe" -> "Hello, Joe!" 9346 /// _ -> "I don't know you, " <> username 9347 /// } 9348 /// } 9349 /// ``` 9350 /// 9351 matched_variable: BoundVariable, 9352 9353 /// The span covering the entire pattern that is bringing the matched 9354 /// variable in scope: 9355 /// 9356 /// ```gleam 9357 /// case something { 9358 /// User(username: _, NotAdmin) -> "Stranger!!" 9359 /// User(username:, Admin) if wibble -> 9360 /// ┬───────────────────── 9361 /// ╰─ `matched_pattern_span` 9362 /// case username { 9363 /// "Joe" -> "Hello, Joe!" 9364 /// _ -> "I don't know you, " <> username 9365 /// } 9366 /// } 9367 /// ``` 9368 /// 9369 matched_pattern_span: SrcSpan, 9370 9371 /// The clauses matching on the `username` variable. In this case they are: 9372 /// ```gleam 9373 /// "Joe" -> "Hello, Joe!" 9374 /// _ -> "I don't know you, " <> username 9375 /// ``` 9376 /// 9377 inner_clauses: &'a Vec<ast::TypedClause>, 9378} 9379 9380impl<'a> CollapseNestedCase<'a> { 9381 pub fn new( 9382 module: &'a Module, 9383 line_numbers: &'a LineNumbers, 9384 params: &'a CodeActionParams, 9385 ) -> Self { 9386 Self { 9387 module, 9388 params, 9389 edits: TextEdits::new(line_numbers), 9390 collapsed: None, 9391 } 9392 } 9393 9394 pub fn code_actions(mut self) -> Vec<CodeAction> { 9395 self.visit_typed_module(&self.module.ast); 9396 9397 let Some(Collapsed { 9398 outer_clause_span, 9399 outer_guard, 9400 ref matched_variable, 9401 matched_pattern_span, 9402 inner_clauses, 9403 }) = self.collapsed 9404 else { 9405 return vec![]; 9406 }; 9407 9408 // Now comes the tricky part: we need to replace the current pattern 9409 // that is bringing the variable into scope with many new patterns, one 9410 // for each of the inner clauses. 9411 // 9412 // Each time we will have to replace the matched variable with the 9413 // pattern used in the inner clause. Let's look at an example: 9414 // 9415 // ```gleam 9416 // Ok(a) -> case a { 9417 // 1 -> wibble 9418 // 2 | 3 -> wobble 9419 // _ -> woo 9420 // } 9421 // ``` 9422 // 9423 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then 9424 // with `2` and `3`, and finally with `_`. Obtaining something like 9425 // this: 9426 // 9427 // ```gleam 9428 // Ok(1) -> wibble 9429 // Ok(2) | Ok(3) -> wobble 9430 // Ok(_) -> woo 9431 // ``` 9432 // 9433 // Notice one key detail: since alternative patterns can't be nested we 9434 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`! 9435 9436 let pattern_text: String = code_at(self.module, matched_pattern_span).into(); 9437 let matched_variable_span = matched_variable.location; 9438 9439 let pattern_with_variable = |mut new_content: String| { 9440 let mut new_pattern = pattern_text.clone(); 9441 9442 match matched_variable { 9443 BoundVariable { 9444 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. }, 9445 .. 9446 } => { 9447 let trimmed_contents = new_content.trim(); 9448 9449 let pattern_is_literal_list = 9450 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]"); 9451 let pattern_is_discard = trimmed_contents == "_"; 9452 9453 let span_to_replace = match ( 9454 &matched_variable.name, 9455 // We verify whether the pattern is compatible with the list prefix `..`. 9456 // For example, `..var` is valid syntax, but `..[]` and `.._` are not. 9457 pattern_is_literal_list || pattern_is_discard, 9458 ) { 9459 // We normally replace the selected variable with the pattern. 9460 (BoundVariableName::Regular { .. }, _) => matched_variable_span, 9461 9462 // If the selected pattern is not a list, we also replace it normally. 9463 (BoundVariableName::ListTail { .. }, false) => matched_variable_span, 9464 // If the pattern is a list to also remove the list tail prefix. 9465 (BoundVariableName::ListTail { tail_location, .. }, true) => { 9466 // When it's a list literal, we remove the surrounding brackets. 9467 let len = trimmed_contents.len(); 9468 if let Some(slice) = new_content.trim().get(1..(len - 1)) { 9469 new_content = slice.to_string() 9470 }; 9471 9472 *tail_location 9473 } 9474 9475 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(), 9476 }; 9477 9478 let start_of_pattern = 9479 (span_to_replace.start - matched_pattern_span.start) as usize; 9480 let pattern_length = span_to_replace.len(); 9481 9482 let end_of_pattern = start_of_pattern + pattern_length; 9483 let replaced_range = start_of_pattern..end_of_pattern; 9484 9485 new_pattern.replace_range(replaced_range, &new_content); 9486 } 9487 9488 BoundVariable { 9489 name: BoundVariableName::ShorthandLabel { .. }, 9490 .. 9491 } => { 9492 // But if it's introduced using the shorthand syntax we can't 9493 // just replace it's location with the new pattern: we would be 9494 // removing the label!! 9495 // So we instead insert the pattern right after the label. 9496 new_pattern.insert_str( 9497 (matched_variable_span.end - matched_pattern_span.start) as usize, 9498 &format!(" {new_content}"), 9499 ); 9500 } 9501 } 9502 9503 new_pattern 9504 }; 9505 9506 let mut new_clauses = vec![]; 9507 for clause in inner_clauses { 9508 // Here we take care of unrolling any alterantive patterns: for each 9509 // of the alternatives we build a new pattern and then join 9510 // everything together with ` | `. 9511 9512 let references_to_matched_variable = 9513 FindVariableReferences::new(matched_variable_span, matched_variable.name()) 9514 .find(&clause.then); 9515 9516 let new_patterns = iter::once(&clause.pattern) 9517 .chain(&clause.alternative_patterns) 9518 .map(|patterns| { 9519 // If we've reached this point we've already made in the 9520 // traversal that the inner clause is matching on a single 9521 // subject. So this should be safe to expect! 9522 let pattern_location = 9523 patterns.first().expect("must have a pattern").location(); 9524 9525 let mut pattern_code = code_at(self.module, pattern_location).to_string(); 9526 if !references_to_matched_variable.is_empty() { 9527 pattern_code = format!("{pattern_code} as {}", matched_variable.name()); 9528 }; 9529 pattern_with_variable(pattern_code) 9530 }) 9531 .join(" | "); 9532 9533 let clause_code = code_at(self.module, clause.then.location()); 9534 let guard_code = match (outer_guard, &clause.guard) { 9535 (Some(outer), Some(inner)) => { 9536 let mut outer_code = code_at(self.module, outer.location()).to_string(); 9537 let mut inner_code = code_at(self.module, inner.location()).to_string(); 9538 if ast::BinOp::And.precedence() > outer.precedence() { 9539 outer_code = format!("{{ {outer_code} }}") 9540 } 9541 if ast::BinOp::And.precedence() > inner.precedence() { 9542 inner_code = format!("{{ {inner_code} }}") 9543 } 9544 format!(" if {outer_code} && {inner_code}") 9545 } 9546 (None, Some(guard)) | (Some(guard), None) => { 9547 format!(" if {}", code_at(self.module, guard.location())) 9548 } 9549 (None, None) => "".into(), 9550 }; 9551 9552 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}")); 9553 } 9554 9555 let pattern_nesting = self 9556 .edits 9557 .src_span_to_lsp_range(outer_clause_span) 9558 .start 9559 .character; 9560 let indentation = " ".repeat(pattern_nesting as usize); 9561 9562 self.edits.replace( 9563 outer_clause_span, 9564 new_clauses.join(&format!("\n{indentation}")), 9565 ); 9566 9567 let mut action = Vec::with_capacity(1); 9568 CodeActionBuilder::new("Collapse nested case") 9569 .kind(CodeActionKind::RefactorRewrite) 9570 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9571 .preferred(false) 9572 .push_to(&mut action); 9573 action 9574 } 9575 9576 /// If the clause can be flattened because it's matching on a single variable 9577 /// defined in it, this function will return the info needed by the language 9578 /// server to flatten that case. 9579 /// 9580 /// We can only flatten a case expression in a very specific case: 9581 /// - This pattern may be introducing multiple variables, 9582 /// - The expression following this branch must be a case, and 9583 /// - It must be matching on one of those variables 9584 /// 9585 /// For example: 9586 /// 9587 /// ```gleam 9588 /// Wibble(a, b, 1) -> case a { ... } 9589 /// Wibble(a, b, 1) -> case b { ... } 9590 /// ``` 9591 /// 9592 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> { 9593 let ast::TypedClause { 9594 pattern, 9595 alternative_patterns, 9596 then, 9597 location, 9598 guard, 9599 } = clause; 9600 9601 if !alternative_patterns.is_empty() { 9602 return None; 9603 } 9604 9605 // The `then` clause must be a single case expression matching on a 9606 // single variable. 9607 let Some(TypedExpr::Case { 9608 subjects, clauses, .. 9609 }) = single_expression(then) 9610 else { 9611 return None; 9612 }; 9613 9614 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else { 9615 return None; 9616 }; 9617 9618 // That variable must be one the variables we brought into scope in this 9619 // branch. 9620 let variable = pattern 9621 .iter() 9622 .flat_map(|pattern| pattern.bound_variables()) 9623 .find(|variable| variable.name() == *name)?; 9624 9625 // There's one last condition to trigger the code action: we must 9626 // actually be with the cursor over the pattern or the nested case 9627 // expression! 9628 // 9629 // ```gleam 9630 // case wibble { 9631 // Ok(a) -> case a { 9632 // //^^^^^^^^^^^^^^^ Anywhere over here! 9633 // } 9634 // } 9635 // ``` 9636 // 9637 let first_pattern = pattern.first().expect("at least one pattern"); 9638 let last_pattern = pattern.last().expect("at least one pattern"); 9639 let pattern_location = first_pattern.location().merge(&last_pattern.location()); 9640 9641 let last_inner_subject = subjects.last().expect("at least one subject"); 9642 let trigger_location = pattern_location.merge(&last_inner_subject.location()); 9643 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location); 9644 9645 if within(self.params.range, trigger_range) { 9646 Some(Collapsed { 9647 outer_clause_span: *location, 9648 outer_guard: guard, 9649 matched_variable: variable, 9650 matched_pattern_span: pattern_location, 9651 inner_clauses: clauses, 9652 }) 9653 } else { 9654 None 9655 } 9656 } 9657} 9658 9659impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> { 9660 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 9661 if let Some(collapsed) = self.flatten_clause(clause) { 9662 self.collapsed = Some(collapsed); 9663 9664 // We're done, there's no need to keep exploring as we know the 9665 // cursor is over this pattern and it can't be over any other one! 9666 return; 9667 }; 9668 9669 ast::visit::visit_typed_clause(self, clause); 9670 } 9671} 9672 9673/// If the expression is a single expression, or a block containing a single 9674/// expression, this function will return it. 9675/// But if the expression is a block with multiple statements, an assignment 9676/// of a use, this will return None. 9677/// 9678fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> { 9679 match expression { 9680 // If a block has a single statement, we can flatten it into a 9681 // single expression if that one statement is an expression. 9682 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() { 9683 ast::Statement::Expression(expression) => single_expression(expression), 9684 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => { 9685 None 9686 } 9687 }, 9688 9689 // If a block has multiple statements then it can't be flattened 9690 // into a single expression. 9691 TypedExpr::Block { .. } => None, 9692 9693 TypedExpr::Int { .. } 9694 | TypedExpr::Float { .. } 9695 | TypedExpr::String { .. } 9696 | TypedExpr::Pipeline { .. } 9697 | TypedExpr::Var { .. } 9698 | TypedExpr::Fn { .. } 9699 | TypedExpr::List { .. } 9700 | TypedExpr::Call { .. } 9701 | TypedExpr::BinOp { .. } 9702 | TypedExpr::Case { .. } 9703 | TypedExpr::RecordAccess { .. } 9704 | TypedExpr::PositionalAccess { .. } 9705 | TypedExpr::ModuleSelect { .. } 9706 | TypedExpr::Tuple { .. } 9707 | TypedExpr::TupleIndex { .. } 9708 | TypedExpr::Todo { .. } 9709 | TypedExpr::Panic { .. } 9710 | TypedExpr::Echo { .. } 9711 | TypedExpr::BitArray { .. } 9712 | TypedExpr::RecordUpdate { .. } 9713 | TypedExpr::NegateBool { .. } 9714 | TypedExpr::NegateInt { .. } 9715 | TypedExpr::Invalid { .. } => Some(expression), 9716 } 9717} 9718 9719/// Code action to remove unreachable clauses from a case expression. 9720/// 9721pub struct RemoveUnreachableCaseClauses<'a> { 9722 module: &'a Module, 9723 params: &'a CodeActionParams, 9724 edits: TextEdits<'a>, 9725 /// The source location of the patterns of all the unreachable clauses in 9726 /// the current module. 9727 /// 9728 unreachable_clauses: HashSet<SrcSpan>, 9729 clauses_to_delete: Vec<SrcSpan>, 9730} 9731 9732impl<'a> RemoveUnreachableCaseClauses<'a> { 9733 pub fn new( 9734 module: &'a Module, 9735 line_numbers: &'a LineNumbers, 9736 params: &'a CodeActionParams, 9737 ) -> Self { 9738 let unreachable_clauses = module 9739 .ast 9740 .type_info 9741 .warnings 9742 .iter() 9743 .filter_map(|warning| { 9744 if let type_::Warning::UnreachableCasePattern { location, .. } = warning { 9745 Some(*location) 9746 } else { 9747 None 9748 } 9749 }) 9750 .collect(); 9751 9752 Self { 9753 unreachable_clauses, 9754 module, 9755 params, 9756 edits: TextEdits::new(line_numbers), 9757 clauses_to_delete: vec![], 9758 } 9759 } 9760 9761 pub fn code_actions(mut self) -> Vec<CodeAction> { 9762 self.visit_typed_module(&self.module.ast); 9763 if self.clauses_to_delete.is_empty() { 9764 return vec![]; 9765 } 9766 9767 for branch in self.clauses_to_delete { 9768 self.edits.delete(branch); 9769 } 9770 9771 let mut action = Vec::with_capacity(1); 9772 CodeActionBuilder::new("Remove unreachable clauses") 9773 .kind(CodeActionKind::QuickFix) 9774 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9775 .preferred(true) 9776 .push_to(&mut action); 9777 action 9778 } 9779} 9780 9781impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> { 9782 fn visit_typed_expr_case( 9783 &mut self, 9784 location: &'ast SrcSpan, 9785 type_: &'ast Arc<Type>, 9786 subjects: &'ast [TypedExpr], 9787 clauses: &'ast [ast::TypedClause], 9788 compiled_case: &'ast CompiledCase, 9789 ) { 9790 // We're showing the code action only if we're within one of the 9791 // unreachable patterns. And the code action is going to remove all the 9792 // unreachable patterns for this case. 9793 let is_hovering_clause = clauses.iter().any(|clause| { 9794 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location()); 9795 within(self.params.range, pattern_range) 9796 }); 9797 if is_hovering_clause { 9798 self.clauses_to_delete = clauses 9799 .iter() 9800 .filter(|clause| { 9801 self.unreachable_clauses 9802 .contains(&clause.pattern_location()) 9803 }) 9804 .map(|clause| clause.location()) 9805 .collect_vec(); 9806 return; 9807 } 9808 9809 // If we're not hovering any of the clauses then we want to 9810 // keep visiting the case expression as the unreachable branch might be 9811 // in one of the nested cases. 9812 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 9813 } 9814} 9815 9816/// Code action to remove a record update when all of its fields have been 9817/// provided already: 9818/// 9819/// ```gleam 9820/// pub type Wibble { Wibble(one: Int, two: Int) } 9821/// 9822/// wibble(..wibble, one:, two:) 9823/// // ^^^^^^^^ This is not needed and raises a warning! 9824/// ``` 9825/// 9826pub struct RemoveRedundantRecordUpdate<'a> { 9827 module: &'a Module, 9828 params: &'a CodeActionParams, 9829 edits: TextEdits<'a>, 9830} 9831 9832impl<'a> RemoveRedundantRecordUpdate<'a> { 9833 pub fn new( 9834 module: &'a Module, 9835 line_numbers: &'a LineNumbers, 9836 params: &'a CodeActionParams, 9837 ) -> Self { 9838 Self { 9839 module, 9840 params, 9841 edits: TextEdits::new(line_numbers), 9842 } 9843 } 9844 9845 pub fn code_actions(mut self) -> Vec<CodeAction> { 9846 let spread_to_remove = self 9847 .module 9848 .ast 9849 .type_info 9850 .warnings 9851 .iter() 9852 .find_map(|warning| { 9853 if let type_::Warning::AllFieldsRecordUpdate { 9854 location, 9855 record_location, 9856 } = warning 9857 && within( 9858 self.params.range, 9859 self.edits.src_span_to_lsp_range(*location), 9860 ) 9861 { 9862 Some(*record_location) 9863 } else { 9864 None 9865 } 9866 }); 9867 9868 let Some(spread_to_remove) = spread_to_remove else { 9869 return vec![]; 9870 }; 9871 self.edits.delete(spread_to_remove); 9872 9873 let mut action = Vec::with_capacity(1); 9874 CodeActionBuilder::new("Remove redundant record update") 9875 .kind(CodeActionKind::QuickFix) 9876 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9877 .preferred(true) 9878 .push_to(&mut action); 9879 action 9880 } 9881} 9882 9883/// Code action to add labels to a constructor/call where all the labels where 9884/// omitted. 9885/// 9886pub struct AddOmittedLabels<'a> { 9887 module: &'a Module, 9888 params: &'a CodeActionParams, 9889 edits: TextEdits<'a>, 9890 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>, 9891} 9892 9893struct CallArgumentWithOmittedLabel { 9894 location: SrcSpan, 9895 9896 /// If the argument has a label this will be the label we can use for it. 9897 /// 9898 omitted_label: Option<EcoString>, 9899 9900 /// If the argument is a variable that has the same name as the omitted label 9901 /// and could use the shorthand syntax. 9902 /// 9903 can_use_shorthand_syntax: bool, 9904} 9905 9906impl<'a> AddOmittedLabels<'a> { 9907 pub fn new( 9908 module: &'a Module, 9909 line_numbers: &'a LineNumbers, 9910 params: &'a CodeActionParams, 9911 ) -> Self { 9912 Self { 9913 module, 9914 params, 9915 edits: TextEdits::new(line_numbers), 9916 arguments_and_omitted_labels: None, 9917 } 9918 } 9919 9920 pub fn code_actions(mut self) -> Vec<CodeAction> { 9921 self.visit_typed_module(&self.module.ast); 9922 9923 let Some(call_arguments) = self.arguments_and_omitted_labels else { 9924 return vec![]; 9925 }; 9926 9927 for call_argument in call_arguments { 9928 let Some(label) = call_argument.omitted_label else { 9929 continue; 9930 }; 9931 if call_argument.can_use_shorthand_syntax { 9932 self.edits.insert(call_argument.location.end, ":".into()); 9933 } else { 9934 self.edits 9935 .insert(call_argument.location.start, format!("{label}: ")) 9936 } 9937 } 9938 9939 let mut action = Vec::with_capacity(1); 9940 CodeActionBuilder::new("Add omitted labels") 9941 .kind(CodeActionKind::RefactorRewrite) 9942 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9943 .preferred(false) 9944 .push_to(&mut action); 9945 action 9946 } 9947} 9948 9949impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> { 9950 fn visit_typed_expr_call( 9951 &mut self, 9952 location: &'ast SrcSpan, 9953 type_: &'ast Arc<Type>, 9954 fun: &'ast TypedExpr, 9955 arguments: &'ast [TypedCallArg], 9956 open_parenthesis: &'ast Option<u32>, 9957 ) { 9958 let called_function_range = self.edits.src_span_to_lsp_range(fun.location()); 9959 if !within(self.params.range, called_function_range) { 9960 ast::visit::visit_typed_expr_call( 9961 self, 9962 location, 9963 type_, 9964 fun, 9965 arguments, 9966 open_parenthesis, 9967 ); 9968 return; 9969 } 9970 9971 let Some(field_map) = fun.field_map() else { 9972 ast::visit::visit_typed_expr_call( 9973 self, 9974 location, 9975 type_, 9976 fun, 9977 arguments, 9978 open_parenthesis, 9979 ); 9980 return; 9981 }; 9982 let argument_index_to_label = field_map.indices_to_labels(); 9983 9984 let mut omitted_labels = Vec::with_capacity(arguments.len()); 9985 for (index, argument) in arguments.iter().enumerate() { 9986 // If the argument already has a label we don't want to add a label 9987 // for it, so we skip it. 9988 if let Some(label) = &argument.label { 9989 // Though, before skipping, we want to make sure that the label 9990 // is actually right for the function call. If it's not then we 9991 // give up on adding labels because there wouldn't be no way of 9992 // knowing which label to add. 9993 if !field_map.fields.contains_key(label) { 9994 return; 9995 } else { 9996 continue; 9997 } 9998 } 9999 // No labels for pipes, uses, etc! 10000 if argument.is_implicit() { 10001 continue; 10002 } 10003 10004 let label = argument_index_to_label 10005 .get(&(index as u32)) 10006 .cloned() 10007 .cloned(); 10008 10009 let can_use_shorthand_syntax = match (&label, &argument.value) { 10010 (Some(label), TypedExpr::Var { name, .. }) => name == label, 10011 (Some(_) | None, _) => false, 10012 }; 10013 10014 omitted_labels.push(CallArgumentWithOmittedLabel { 10015 location: argument.location, 10016 omitted_label: label, 10017 can_use_shorthand_syntax, 10018 }) 10019 } 10020 self.arguments_and_omitted_labels = Some(omitted_labels); 10021 } 10022} 10023 10024/// Code action to extract selected code into a separate function. 10025/// If a user selected a portion of code in a function, we offer a code action 10026/// to extract it into a new one. This can either be a single expression, such 10027/// as in the following example: 10028/// 10029/// ```gleam 10030/// pub fn main() { 10031/// let value = { 10032/// // ^ User selects from here 10033/// ... 10034/// } 10035/// //^ Until here 10036/// } 10037/// ``` 10038/// 10039/// Here, we would extract the selected block expression. It could also be a 10040/// series of statements. For example: 10041/// 10042/// ```gleam 10043/// pub fn main() { 10044/// let a = 1 10045/// //^ User selects from here 10046/// let b = 2 10047/// let c = a + b 10048/// // ^ Until here 10049/// 10050/// do_more_things(c) 10051/// } 10052/// ``` 10053/// 10054/// Here, we want to extract the statements inside the user's selection. 10055/// 10056pub struct ExtractFunction<'a> { 10057 module: &'a Module, 10058 params: &'a CodeActionParams, 10059 edits: TextEdits<'a>, 10060 function: Option<ExtractedFunction<'a>>, 10061 function_end_position: Option<u32>, 10062 /// Since the `visit_typed_statement` visitor function doesn't tell us when 10063 /// a statement is the last in a block or function, we need to track that 10064 /// manually. 10065 last_statement_location: Option<SrcSpan>, 10066 /// When visiting a pipeline step, this will hold the type of the value 10067 /// returned by the previous step (if any!) 10068 previous_pipeline_assignment_type: Option<Arc<Type>>, 10069} 10070 10071/// Information about a section of code we are extracting as a function. 10072#[derive(Debug)] 10073struct ExtractedFunction<'a> { 10074 /// A list of parameters which need to be passed to the extracted function. 10075 /// These are any variables used in the extracted code, which are defined 10076 /// outside of the extracted code. 10077 parameters: Vec<(EcoString, Arc<Type>)>, 10078 /// A list of values which need to be returned from the extracted function. 10079 /// These are the variables defined in the extracted code which are used 10080 /// outside of the extracted section. 10081 returned_variables: Vec<(EcoString, Arc<Type>)>, 10082 /// The piece of code to be extracted. This is either a single expression or 10083 /// a list of statements, as explained in the documentation of `ExtractFunction` 10084 value: ExtractedValue<'a>, 10085} 10086 10087impl<'a> ExtractedFunction<'a> { 10088 fn new(value: ExtractedValue<'a>) -> Self { 10089 Self { 10090 value, 10091 parameters: Vec::new(), 10092 returned_variables: Vec::new(), 10093 } 10094 } 10095 10096 fn location(&self) -> SrcSpan { 10097 match &self.value { 10098 ExtractedValue::Expression(expression) => expression.location(), 10099 ExtractedValue::Statements { location, .. } 10100 | ExtractedValue::Use { location, .. } 10101 | ExtractedValue::PipelineSteps { location, .. } => *location, 10102 } 10103 } 10104 10105 /// If the extracted function is a series of pipeline steps, this adds to it 10106 /// the given pipeline step, otherwise leaving it unchanged. 10107 /// If the extracted function was indeed a pipeline, this will return `true`, 10108 /// otherwise it returns `false`. 10109 /// 10110 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) { 10111 if let ExtractedFunction { 10112 value: 10113 ExtractedValue::PipelineSteps { 10114 location, 10115 before_first: _, 10116 return_type, 10117 }, 10118 .. 10119 } = self 10120 { 10121 // If we're extracting this pipeline and the final step is included 10122 // in the selection we want to add it to the extracted steps 10123 *return_type = step_type; 10124 *location = location.merge(&step_location); 10125 } 10126 } 10127} 10128 10129#[derive(Debug)] 10130enum ExtractedValue<'a> { 10131 Expression(&'a TypedExpr), 10132 Statements { 10133 location: SrcSpan, 10134 position: StatementPosition, 10135 }, 10136 /// We're extracting a single use statement. We need this special case to 10137 /// properly handle the statements inside of them. 10138 Use { 10139 location: SrcSpan, 10140 type_: Arc<Type>, 10141 }, 10142 PipelineSteps { 10143 location: SrcSpan, 10144 /// The type of the value produced by the pipeline steps that will be 10145 /// piped into the extracted function. Could be none if the steps we're 10146 /// extracting include the first step, in that case there would be 10147 /// nothing that is fed into it. 10148 before_first: Option<Arc<Type>>, 10149 /// The type returned by the extracted steps. 10150 return_type: Arc<Type>, 10151 }, 10152} 10153 10154impl ExtractedValue<'_> { 10155 fn location(&self) -> SrcSpan { 10156 match self { 10157 ExtractedValue::Expression(typed_expr) => typed_expr.location(), 10158 ExtractedValue::Statements { location, .. } 10159 | ExtractedValue::PipelineSteps { location, .. } 10160 | ExtractedValue::Use { location, .. } => *location, 10161 } 10162 } 10163} 10164 10165/// When we are extracting multiple statements, there are two possible cases: 10166/// The first is if we are extracting statements in the middle of a function. 10167/// In this case, we will need to return some number of arguments, or `Nil`. 10168/// For example: 10169/// 10170/// ```gleam 10171/// pub fn main() { 10172/// let message = "Hello!" 10173/// let log_message = "[INFO] " <> message 10174/// //^ Select from here 10175/// io.println(log_message) 10176/// // ^ Until here 10177/// 10178/// do_some_more_things() 10179/// } 10180/// ``` 10181/// 10182/// Here, the extracted function doesn't bind any variables which we need 10183/// afterwards, it purely performs side effects. In this case we can just return 10184/// `Nil` from the new function. 10185/// 10186/// However, consider the following: 10187/// 10188/// ```gleam 10189/// pub fn main() { 10190/// let a = 1 10191/// let b = 2 10192/// //^ Select from here 10193/// a + b 10194/// // ^ Until here 10195/// } 10196/// ``` 10197/// 10198/// Here, despite us not needing any variables from the extracted code, there 10199/// is one key difference: the `a + b` expression is at the end of the function, 10200/// and so its value is returned from the entire function. This is known as the 10201/// "tail" position. In that case, we can't return `Nil` as that would make the 10202/// `main` function return `Nil` instead of the result of the addition. If we 10203/// extract the tail-position statement, we need to return that last value rather 10204/// than `Nil`. 10205/// 10206#[derive(Debug)] 10207enum StatementPosition { 10208 Tail { type_: Arc<Type> }, 10209 NotTail, 10210} 10211 10212impl<'a> ExtractFunction<'a> { 10213 pub fn new( 10214 module: &'a Module, 10215 line_numbers: &'a LineNumbers, 10216 params: &'a CodeActionParams, 10217 ) -> Self { 10218 Self { 10219 module, 10220 params, 10221 edits: TextEdits::new(line_numbers), 10222 function: None, 10223 function_end_position: None, 10224 last_statement_location: None, 10225 previous_pipeline_assignment_type: None, 10226 } 10227 } 10228 10229 pub fn code_actions(mut self) -> Vec<CodeAction> { 10230 // If no code is selected, then there is no function to extract and we 10231 // can return no code actions. 10232 if self.params.range.start == self.params.range.end { 10233 return Vec::new(); 10234 } 10235 10236 self.visit_typed_module(&self.module.ast); 10237 10238 let Some(end) = self.function_end_position else { 10239 return Vec::new(); 10240 }; 10241 10242 // If nothing was found in the selected range, there is no code action. 10243 let Some(extracted) = self.function.take() else { 10244 return Vec::new(); 10245 }; 10246 10247 match extracted.value { 10248 // If we extract a block, it isn't very helpful to have the body of 10249 // the extracted function just be a single block expression, so 10250 // instead we extract the statements inside the block. For example, 10251 // the following code: 10252 // 10253 // ```gleam 10254 // pub fn main() { 10255 // let x = { 10256 // // ^ Select from here 10257 // let a = 1 10258 // let b = 2 10259 // a + b 10260 // } 10261 // //^ Until here 10262 // x 10263 // } 10264 // ``` 10265 // 10266 // Would produce the following extracted function: 10267 // 10268 // ```gleam 10269 // fn function() { 10270 // let a = 1 10271 // let b = 2 10272 // a + b 10273 // } 10274 // ``` 10275 // 10276 // Rather than: 10277 // 10278 // ```gleam 10279 // fn function() { 10280 // { 10281 // let a = 1 10282 // let b = 2 10283 // a + b 10284 // } 10285 // } 10286 // ``` 10287 // 10288 ExtractedValue::Expression(TypedExpr::Block { 10289 statements, 10290 location: full_location, 10291 }) => { 10292 let location = statements 10293 .first() 10294 .location() 10295 .merge(&statements.last().location()); 10296 10297 self.extract_code_in_tail_position( 10298 *full_location, 10299 location, 10300 statements.last().type_(), 10301 extracted.parameters, 10302 end, 10303 ) 10304 } 10305 ExtractedValue::Expression(TypedExpr::Fn { 10306 type_, 10307 location: full_location, 10308 kind: FunctionLiteralKind::Anonymous { .. }, 10309 arguments, 10310 body, 10311 .. 10312 }) => { 10313 let location = body.first().location().merge(&body.last().location()); 10314 let return_type = type_.return_type().expect("Fn should have a return type"); 10315 10316 if extracted.parameters.is_empty() { 10317 self.extract_anonymous_function( 10318 *full_location, 10319 location, 10320 arguments, 10321 return_type, 10322 end, 10323 ) 10324 } else if arguments.len() == 1 { 10325 self.extract_anonymous_function_with_capture_hole( 10326 *full_location, 10327 location, 10328 arguments.first().expect("There is exactly one argument"), 10329 extracted.parameters, 10330 return_type, 10331 end, 10332 ) 10333 } else { 10334 self.extract_anonymous_function_body( 10335 location, 10336 arguments, 10337 extracted.parameters, 10338 return_type, 10339 end, 10340 ) 10341 } 10342 } 10343 ExtractedValue::Expression(expression) => { 10344 let expression_type = if let TypedExpr::Fn { 10345 type_, 10346 kind: FunctionLiteralKind::Use { .. }, 10347 .. 10348 } = expression 10349 { 10350 type_.fn_types().expect("use callback to be a function").1 10351 } else { 10352 expression.type_() 10353 }; 10354 self.extract_code_in_tail_position( 10355 expression.location(), 10356 expression.location(), 10357 expression_type, 10358 extracted.parameters, 10359 end, 10360 ) 10361 } 10362 ExtractedValue::Statements { 10363 location, 10364 position: StatementPosition::NotTail, 10365 } => self.extract_statements( 10366 location, 10367 extracted.parameters, 10368 extracted.returned_variables, 10369 end, 10370 ), 10371 10372 ExtractedValue::Use { location, type_ } 10373 | ExtractedValue::Statements { 10374 location, 10375 position: StatementPosition::Tail { type_ }, 10376 } => self.extract_code_in_tail_position( 10377 location, 10378 location, 10379 type_, 10380 extracted.parameters, 10381 end, 10382 ), 10383 ExtractedValue::PipelineSteps { 10384 location, 10385 before_first, 10386 return_type, 10387 } => self.extract_pipeline_steps( 10388 location, 10389 end, 10390 extracted.parameters, 10391 before_first, 10392 return_type, 10393 ), 10394 } 10395 10396 let mut action = Vec::with_capacity(1); 10397 CodeActionBuilder::new("Extract function") 10398 .kind(CodeActionKind::RefactorExtract) 10399 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10400 .preferred(false) 10401 .push_to(&mut action); 10402 action 10403 } 10404 10405 /// Choose a suitable name for an extracted function to make sure it doesn't 10406 /// clash with existing functions defined in the module and cause an error. 10407 fn function_name(&self) -> EcoString { 10408 if !self.module.ast.type_info.values.contains_key("function") { 10409 return "function".into(); 10410 } 10411 10412 let mut number = 2; 10413 loop { 10414 let name = eco_format!("function_{number}"); 10415 if !self.module.ast.type_info.values.contains_key(&name) { 10416 return name; 10417 } 10418 number += 1; 10419 } 10420 } 10421 10422 /// For anonymous functions that do not capture any variables from an outer scope. 10423 /// Moves the function so it is defined at the module top-level instead, 10424 /// replacing the original literal with a reference to the new function. 10425 fn extract_anonymous_function( 10426 &mut self, 10427 location: SrcSpan, 10428 code_location: SrcSpan, 10429 arguments: &[TypedArg], 10430 return_type: Arc<Type>, 10431 function_end: u32, 10432 ) { 10433 // --- BEFORE 10434 // ```gleam 10435 // pub fn main() { 10436 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) }) 10437 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 10438 // } 10439 // ``` 10440 // 10441 // --- AFTER 10442 // ```gleam 10443 // pub fn main() { 10444 // list.each([1, 2, 3], function) 10445 // } 10446 // 10447 // fn function(x: Int) -> Nil { 10448 // io.println(int.to_string(x)) 10449 // } 10450 // ``` 10451 10452 let name = self.function_name(); 10453 self.edits.replace(location, name.to_string()); 10454 10455 let mut printer = Printer::new(&self.module.ast.names); 10456 10457 let return_type = printer.print_type(&return_type); 10458 let function_body = code_at(self.module, code_location); 10459 let mut name_generator = NameGenerator::new(); 10460 let arguments = arguments 10461 .iter() 10462 .map(|arg| { 10463 if let Some(name) = arg.get_variable_name() { 10464 eco_format!("{name}: {}", printer.print_type(&arg.type_)) 10465 } else { 10466 let name = name_generator.generate_name_from_type(&arg.type_); 10467 eco_format!("_{name}: {}", printer.print_type(&arg.type_)) 10468 } 10469 }) 10470 .join(", "); 10471 10472 let function = format!( 10473 "\n\nfn {name}({arguments}) -> {return_type} {{ 10474 {function_body} 10475}}" 10476 ); 10477 self.edits.insert(function_end, function); 10478 } 10479 10480 /// For anonymous functions that capture variables from an external scope 10481 /// but only expect a single argument. 10482 /// Uses function caputre syntax to provide a more concise refactoring than 10483 /// `extract_anonymous_function_body`. 10484 fn extract_anonymous_function_with_capture_hole( 10485 &mut self, 10486 location: SrcSpan, 10487 code_location: SrcSpan, 10488 argument: &TypedArg, 10489 extra_parameters: Vec<(EcoString, Arc<Type>)>, 10490 return_type: Arc<Type>, 10491 function_end: u32, 10492 ) { 10493 let name = self.function_name(); 10494 10495 // --- BEFORE 10496 // ```gleam 10497 // pub fn main() { 10498 // let needle = 42 10499 // let haystack = [25, 81, 74, 42, 33] 10500 // list.filter(haystack, fn(x) { x == needle }) 10501 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 10502 // } 10503 // ``` 10504 // 10505 // --- AFTER 10506 // ```gleam 10507 // pub fn main() { 10508 // let needle = 42 10509 // let haystack = [25, 81, 74, 42, 33] 10510 // list.filter(haystack, function(_, needle)) 10511 // } 10512 // 10513 // fn function(x: Int, needle: Int) -> Bool { 10514 // x == needle 10515 // } 10516 // ``` 10517 10518 let call = format!( 10519 "{name}(_, {})", 10520 extra_parameters.iter().map(|(name, _)| name).join(", ") 10521 ); 10522 self.edits.replace(location, call); 10523 10524 let mut printer = Printer::new(&self.module.ast.names); 10525 10526 // build up the code for the newly generated function 10527 let return_type = printer.print_type(&return_type); 10528 let function_body = code_at(self.module, code_location); 10529 let argument = if let Some(name) = argument.get_variable_name() { 10530 eco_format!("{name}: {}", printer.print_type(&argument.type_)) 10531 } else { 10532 let name = NameGenerator::new().generate_name_from_type(&argument.type_); 10533 eco_format!("_{name}: {}", printer.print_type(&argument.type_)) 10534 }; 10535 let extra_parameters = extra_parameters 10536 .iter() 10537 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 10538 .join(", "); 10539 10540 let function = format!( 10541 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{ 10542 {function_body} 10543}}" 10544 ); 10545 self.edits.insert(function_end, function); 10546 } 10547 10548 /// For non-unary anonymous functions that capture variables from an external scope. 10549 /// Replaces just the _function body_ with a call to the newly generated function. 10550 fn extract_anonymous_function_body( 10551 &mut self, 10552 location: SrcSpan, 10553 arguments: &[TypedArg], 10554 extra_parameters: Vec<(EcoString, Arc<Type>)>, 10555 return_type: Arc<Type>, 10556 function_end: u32, 10557 ) { 10558 let name = self.function_name(); 10559 // --- BEFORE 10560 // ```gleam 10561 // pub fn main() { 10562 // let factor = 2 10563 // list.fold([], 0, fn(acc, value) { acc + value * factor }) 10564 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 10565 // } 10566 // ``` 10567 // 10568 // --- AFTER 10569 // ```gleam 10570 // pub fn main() { 10571 // let factor = 2 10572 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) }) 10573 // } 10574 // 10575 // fn function(acc: Int, value: Int, factor: Int) -> Int { 10576 // acc + value * factor 10577 // } 10578 // ``` 10579 10580 // if the programmer has ignored an argument, the generated function 10581 // cannot take it as an parameter 10582 let arguments = arguments 10583 .iter() 10584 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_))) 10585 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_))) 10586 .collect::<Vec<(&EcoString, &Arc<Type>)>>(); 10587 10588 let call = format!( 10589 "{name}({})", 10590 arguments.iter().map(|(name, _)| name).join(", ") 10591 ); 10592 self.edits.replace(location, call); 10593 10594 let mut printer = Printer::new(&self.module.ast.names); 10595 10596 let return_type = printer.print_type(&return_type); 10597 let function_body = code_at(self.module, location); 10598 let arguments = arguments 10599 .iter() 10600 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 10601 .join(", "); 10602 10603 let function = format!( 10604 "\n\nfn {name}({arguments}) -> {return_type} {{ 10605 {function_body} 10606}}" 10607 ); 10608 self.edits.insert(function_end, function); 10609 } 10610 10611 /// Extracts code from the end of a function or block. This could either be 10612 /// a single expression, or multiple statements followed by a final expression. 10613 fn extract_code_in_tail_position( 10614 &mut self, 10615 location: SrcSpan, 10616 code_location: SrcSpan, 10617 type_: Arc<Type>, 10618 parameters: Vec<(EcoString, Arc<Type>)>, 10619 function_end: u32, 10620 ) { 10621 let expression_code = code_at(self.module, code_location); 10622 10623 let name = self.function_name(); 10624 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 10625 let call = format!("{name}({arguments})"); 10626 10627 // Since we are only extracting a single expression, we can just replace 10628 // it with the call and preserve all other semantics; only one value can 10629 // be returned from the expression, unlike when extracting multiple 10630 // statements. 10631 self.edits.replace(location, call); 10632 10633 let mut printer = Printer::new(&self.module.ast.names); 10634 10635 let parameters = parameters 10636 .iter() 10637 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 10638 .join(", "); 10639 let return_type = printer.print_type(&type_); 10640 10641 let function = format!( 10642 "\n\nfn {name}({parameters}) -> {return_type} {{ 10643 {expression_code} 10644}}" 10645 ); 10646 10647 self.edits.insert(function_end, function); 10648 } 10649 10650 fn extract_statements( 10651 &mut self, 10652 location: SrcSpan, 10653 parameters: Vec<(EcoString, Arc<Type>)>, 10654 returned_variables: Vec<(EcoString, Arc<Type>)>, 10655 function_end: u32, 10656 ) { 10657 let code = code_at(self.module, location); 10658 10659 let returns_anything = !returned_variables.is_empty(); 10660 10661 // Here, we decide what value to return from the function. There are 10662 // three cases: 10663 // The first is when the extracted code is purely for side-effects, and 10664 // does not produce any values which are needed outside of the extracted 10665 // code. For example: 10666 // 10667 // ```gleam 10668 // pub fn main() { 10669 // let message = "Something important" 10670 // //^ Select from here 10671 // io.println("Something important") 10672 // io.println("Something else which is repeated") 10673 // // ^ Until here 10674 // 10675 // do_final_thing() 10676 // } 10677 // ``` 10678 // 10679 // It doesn't make sense to return any values from this function, since 10680 // no values from the extract code are used afterwards, so we simply 10681 // return `Nil`. 10682 // 10683 // The next is when we need just a single value defined in the extracted 10684 // function, such as in this piece of code: 10685 // 10686 // ```gleam 10687 // pub fn main() { 10688 // let a = 10 10689 // //^ Select from here 10690 // let b = 20 10691 // let c = a + b 10692 // // ^ Until here 10693 // 10694 // echo c 10695 // } 10696 // ``` 10697 // 10698 // Here, we can just return the single value, `c`. 10699 // 10700 // The last situation is when we need multiple defined values, such as 10701 // in the following code: 10702 // 10703 // ```gleam 10704 // pub fn main() { 10705 // let a = 10 10706 // //^ Select from here 10707 // let b = 20 10708 // let c = a + b 10709 // // ^ Until here 10710 // 10711 // echo a 10712 // echo b 10713 // echo c 10714 // } 10715 // ``` 10716 // 10717 // In this case, we must return a tuple containing `a`, `b` and `c` in 10718 // order for the calling function to have access to the correct values. 10719 let (return_type, return_value) = match returned_variables.as_slice() { 10720 [] => (type_::nil(), "Nil".into()), 10721 [(name, type_)] => (type_.clone(), name.clone()), 10722 _ => { 10723 let values = returned_variables.iter().map(|(name, _)| name).join(", "); 10724 let type_ = type_::tuple( 10725 returned_variables 10726 .into_iter() 10727 .map(|(_, type_)| type_) 10728 .collect(), 10729 ); 10730 10731 (type_, eco_format!("#({values})")) 10732 } 10733 }; 10734 10735 let name = self.function_name(); 10736 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 10737 10738 // If any values are returned from the extracted function, we need to 10739 // bind them so that they are accessible in the current scope. 10740 let call = if returns_anything { 10741 format!("let {return_value} = {name}({arguments})") 10742 } else { 10743 format!("{name}({arguments})") 10744 }; 10745 self.edits.replace(location, call); 10746 10747 let mut printer = Printer::new(&self.module.ast.names); 10748 10749 let parameters = parameters 10750 .iter() 10751 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 10752 .join(", "); 10753 10754 let return_type = printer.print_type(&return_type); 10755 10756 let function = format!( 10757 "\n\nfn {name}({parameters}) -> {return_type} {{ 10758 {code} 10759 {return_value} 10760}}" 10761 ); 10762 10763 self.edits.insert(function_end, function); 10764 } 10765 10766 fn extract_pipeline_steps( 10767 &mut self, 10768 location: SrcSpan, 10769 function_end: u32, 10770 parameters: Vec<(EcoString, Arc<Type>)>, 10771 before_first: Option<Arc<Type>>, 10772 return_type: Arc<Type>, 10773 ) { 10774 let name = self.function_name(); 10775 let code = code_at(self.module, location); 10776 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", "); 10777 let replacement = match before_first { 10778 Some(_) if parameters.is_empty() => format!("{name}"), 10779 Some(_) | None => format!("{name}({arguments})"), 10780 }; 10781 self.edits.replace(location, replacement); 10782 10783 // When extracting something out of the middle of a pipeline the 10784 // function we produce will produce a single value as output but could 10785 // take multiple values as input: 10786 // 10787 // ```gleam 10788 // wibble 10789 // |> wobble(a) // extracting this 10790 // |> woo(b) // 10791 // |> something 10792 // ``` 10793 // 10794 // It will take the type returned by `wibble`, `a`, and `b` as input, 10795 // and produce the value returned by `woo` as output: 10796 // 10797 // ```gleam 10798 // wibble 10799 // |> function(a, b) 10800 // |> something 10801 // ``` 10802 // 10803 // If the steps extracted are at the beginning of the pipeline, then it 10804 // won't take that additional argument! 10805 // 10806 // ```gleam 10807 // wibble // extracting these 10808 // |> wobble(a) // 10809 // |> woo(b) // 10810 // |> something 10811 // ``` 10812 // 10813 // Becomes: 10814 // 10815 // ```gleam 10816 // function(a, b) 10817 // |> something 10818 // ``` 10819 10820 let mut type_printer = Printer::new(&self.module.ast.names); 10821 let return_type = type_printer.print_type(&return_type); 10822 let first_argument = before_first.map(|type_of_first_argument| { 10823 let mut generator = NameGenerator::new(); 10824 for (name, _) in parameters.iter() { 10825 generator.add_used_name(name.clone()); 10826 } 10827 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument); 10828 (first_argument_name, type_of_first_argument.clone()) 10829 }); 10830 let parameters = first_argument 10831 .clone() 10832 .into_iter() 10833 .chain(parameters) 10834 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_))) 10835 .join(", "); 10836 10837 let code = if let Some((first_argument_name, _)) = first_argument { 10838 format!("{first_argument_name}\n |> {code}") 10839 } else { 10840 code.trim_start_matches("|>").to_string() 10841 }; 10842 let function = format!( 10843 "\n\nfn {name}({parameters}) -> {return_type} {{ 10844 {code} 10845}}" 10846 ); 10847 10848 self.edits.insert(function_end, function); 10849 } 10850 10851 /// When a variable is referenced, we need to decide if we need to do anything 10852 /// to ensure that the reference is still valid after extracting a function. 10853 /// If the variable is defined outside the extracted function, but used inside 10854 /// it, then we need to add it as a parameter of the function. Similarly, if 10855 /// a variable is defined inside the extracted code, but used outside of it, 10856 /// we need to ensure that value is returned from the function so that it is 10857 /// accessible. 10858 fn register_referenced_variable( 10859 &mut self, 10860 name: &EcoString, 10861 type_: &Arc<Type>, 10862 location: SrcSpan, 10863 definition_location: SrcSpan, 10864 ) { 10865 let Some(extracted) = &mut self.function else { 10866 return; 10867 }; 10868 10869 let extracted_location = extracted.location(); 10870 10871 // If a variable defined outside the extracted code is referenced inside 10872 // it, we need to add it to the list of parameters. 10873 let variables = if extracted_location.contains_span(location) 10874 && !extracted_location.contains_span(definition_location) 10875 { 10876 &mut extracted.parameters 10877 // If a variable defined inside the extracted code is referenced outside 10878 // it, then we need to ensure that it is returned from the function. 10879 } else if extracted_location.contains_span(definition_location) 10880 && !extracted_location.contains_span(location) 10881 { 10882 &mut extracted.returned_variables 10883 } else { 10884 return; 10885 }; 10886 10887 // If the variable has already been tracked, no need to register it again. 10888 // We use a `Vec` here rather than a `HashMap` because we want to ensure 10889 // the order of arguments is consistent; in this case it will be determined 10890 // by the order the variables are used. This isn't always desired, but it's 10891 // better than random order, and makes it easier to write tests too. 10892 // The cost of iterating the list here is minimal; it is unlikely that 10893 // a given function will ever have more than 10 or so parameters. 10894 if variables.iter().any(|(variable, _)| variable == name) { 10895 return; 10896 } 10897 10898 variables.push((name.clone(), type_.clone())); 10899 } 10900 10901 fn can_extract_expression(&self, expression: &TypedExpr) -> bool { 10902 let expression_range = self.edits.src_span_to_lsp_range(expression.location()); 10903 let selected_range = self.params.range; 10904 10905 // If the selected range doesn't touch the expression at all, then there 10906 // is no reason to extract it. 10907 if !overlaps(expression_range, selected_range) { 10908 return false; 10909 } 10910 10911 match expression { 10912 TypedExpr::Pipeline { 10913 first_value, 10914 finally, 10915 .. 10916 } => { 10917 // We can extract a pipeline as a whole only if the selection 10918 // spans all of its steps! 10919 let first_step = self.edits.src_span_to_lsp_range(first_value.location); 10920 let last_step = self.edits.src_span_to_lsp_range(finally.location()); 10921 position_within(selected_range.start, first_step) 10922 && position_within(selected_range.end, last_step) 10923 } 10924 10925 TypedExpr::Int { .. } 10926 | TypedExpr::Float { .. } 10927 | TypedExpr::String { .. } 10928 | TypedExpr::Block { .. } 10929 | TypedExpr::Var { .. } 10930 | TypedExpr::Fn { .. } 10931 | TypedExpr::List { .. } 10932 | TypedExpr::Call { .. } 10933 | TypedExpr::BinOp { .. } 10934 | TypedExpr::Case { .. } 10935 | TypedExpr::RecordAccess { .. } 10936 | TypedExpr::PositionalAccess { .. } 10937 | TypedExpr::ModuleSelect { .. } 10938 | TypedExpr::Tuple { .. } 10939 | TypedExpr::TupleIndex { .. } 10940 | TypedExpr::Todo { .. } 10941 | TypedExpr::Panic { .. } 10942 | TypedExpr::Echo { .. } 10943 | TypedExpr::BitArray { .. } 10944 | TypedExpr::RecordUpdate { .. } 10945 | TypedExpr::NegateBool { .. } 10946 | TypedExpr::NegateInt { .. } 10947 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range), 10948 } 10949 } 10950 10951 fn can_extract_statement(&self, statement: &TypedStatement) -> bool { 10952 let statement_range = self.edits.src_span_to_lsp_range(statement.location()); 10953 let selected_range = self.params.range; 10954 10955 // If the selected range doesn't touch the statement at all, then there 10956 // is no reason to extract it. 10957 if !overlaps(statement_range, selected_range) { 10958 return false; 10959 } 10960 10961 match statement { 10962 ast::Statement::Expression(expression) => self.can_extract_expression(expression), 10963 10964 // Determine whether the selected range falls completely within the 10965 // expression. For example: 10966 // ```gleam 10967 // pub fn main() { 10968 // let something = { 10969 // let a = 1 10970 // let b = 2 10971 // let c = a + b 10972 // //^ The user has selected from here 10973 // let d = a * b 10974 // c / d 10975 // // ^ Until here 10976 // } 10977 // } 10978 // ``` 10979 // 10980 // Here, the selected range does overlap with the `let something` 10981 // statement; but we don't want to extract that whole statement! The 10982 // user only wanted to extract the statements inside the block. So if 10983 // the selected range falls completely within the expression, we ignore 10984 // it and traverse the tree further until we find exactly what the user 10985 // selected. 10986 // 10987 // If the selected range is completely within the expression, we don't 10988 // want to extract it. 10989 ast::Statement::Use(_) | ast::Statement::Assert(_) => { 10990 !completely_within(selected_range, statement_range) 10991 } 10992 10993 // We can only extract a whole let statement if the assignment 10994 // part itself is selected. If the only part being selected is the 10995 // expression then the right call is not extracting the whole 10996 // statement but just the expression. 10997 ast::Statement::Assignment(assignment) => { 10998 let value_range = self 10999 .edits 11000 .src_span_to_lsp_range(assignment.value.location()); 11001 11002 !within(selected_range, value_range) 11003 && !completely_within(selected_range, statement_range) 11004 } 11005 } 11006 } 11007} 11008 11009impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> { 11010 fn visit_typed_function(&mut self, function: &'ast TypedFunction) { 11011 let range = self.edits.src_span_to_lsp_range(function.full_location()); 11012 11013 if within(self.params.range, range) { 11014 self.function_end_position = Some(function.end_position); 11015 self.last_statement_location = function.body.last().map(|last| last.location()); 11016 11017 ast::visit::visit_typed_function(self, function); 11018 } 11019 } 11020 11021 fn visit_typed_expr_block( 11022 &mut self, 11023 location: &'ast SrcSpan, 11024 statements: &'ast [TypedStatement], 11025 ) { 11026 let last_statement_location = self.last_statement_location; 11027 self.last_statement_location = statements.last().map(|last| last.location()); 11028 11029 ast::visit::visit_typed_expr_block(self, location, statements); 11030 11031 self.last_statement_location = last_statement_location; 11032 } 11033 11034 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 11035 // If we have already determined what code we want to extract, we don't 11036 // want to extract this instead. This expression would be inside the 11037 // piece of code we already are going to extract, leading to us 11038 // extracting just a single literal in any selection, which is of course 11039 // not desired. 11040 if self.function.is_none() { 11041 // If this expression is fully selected, we mark it as being extracted. 11042 if self.can_extract_expression(expression) { 11043 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression( 11044 expression, 11045 ))); 11046 } 11047 } 11048 ast::visit::visit_typed_expr(self, expression); 11049 } 11050 11051 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 11052 let statement_location = statement.location(); 11053 11054 if self.can_extract_statement(statement) { 11055 let is_in_tail_position = 11056 self.last_statement_location 11057 .is_some_and(|last_statement_location| { 11058 last_statement_location == statement_location 11059 }); 11060 11061 // A use is always eating up the entire block, if we're extracting it, 11062 // it will be in tail position there and the extracted function should 11063 // return its returned value. 11064 let position = if statement.is_use() || is_in_tail_position { 11065 StatementPosition::Tail { 11066 type_: statement.type_(), 11067 } 11068 } else { 11069 StatementPosition::NotTail 11070 }; 11071 11072 match &mut self.function { 11073 None => { 11074 self.function = match statement { 11075 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None, 11076 TypedStatement::Assert(_) 11077 | TypedStatement::Assignment(_) 11078 | TypedStatement::Expression(_) => { 11079 Some(ExtractedFunction::new(ExtractedValue::Statements { 11080 location: statement_location, 11081 position, 11082 })) 11083 } 11084 TypedStatement::Use(use_) => { 11085 Some(ExtractedFunction::new(ExtractedValue::Use { 11086 location: use_.call.location(), 11087 type_: statement.type_(), 11088 })) 11089 } 11090 } 11091 } 11092 11093 // If we're extracting something that is withing a use expression 11094 // we don't need to change anything 11095 Some(ExtractedFunction { 11096 value: ExtractedValue::Use { location, .. }, 11097 .. 11098 }) if location.contains_span(statement_location) => {} 11099 // Otherwise it means we're extracting multiple statements 11100 // _including_ some use expression, we fallback to extracting 11101 // multiple statements 11102 Some(ExtractedFunction { 11103 value: value @ ExtractedValue::Use { .. }, 11104 .. 11105 }) => { 11106 *value = ExtractedValue::Statements { 11107 location: value.location().merge(&statement_location), 11108 position, 11109 }; 11110 } 11111 11112 // If we have already chosen an expression to extract, that means 11113 // that this statement is within the already extracted expression, 11114 // so we don't want to extract this instead. 11115 Some(ExtractedFunction { 11116 value: ExtractedValue::Expression(_), 11117 .. 11118 }) => {} 11119 // If we are selecting multiple statements, this statement should 11120 // be included within that list, so we merge the spans to ensure 11121 // it is included. 11122 Some(ExtractedFunction { 11123 value: 11124 ExtractedValue::Statements { 11125 location, 11126 position: extracted_position, 11127 }, 11128 .. 11129 }) => { 11130 *location = location.merge(&statement_location); 11131 *extracted_position = position; 11132 } 11133 Some(ExtractedFunction { 11134 value: value @ ExtractedValue::PipelineSteps { .. }, 11135 .. 11136 }) => { 11137 // If we were extracting a pipeline, but end up selecting 11138 // some statement that is not part of it, then we go back to 11139 // selecting a batch of statements. 11140 *value = ExtractedValue::Statements { 11141 location: value.location(), 11142 position, 11143 } 11144 } 11145 } 11146 } 11147 ast::visit::visit_typed_statement(self, statement); 11148 } 11149 11150 fn visit_typed_expr_pipeline( 11151 &mut self, 11152 _location: &'ast SrcSpan, 11153 first_value: &'ast TypedPipelineAssignment, 11154 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 11155 finally: &'ast TypedExpr, 11156 _finally_kind: &'ast PipelineAssignmentKind, 11157 ) { 11158 self.previous_pipeline_assignment_type = None; 11159 self.visit_typed_pipeline_assignment(first_value); 11160 11161 self.previous_pipeline_assignment_type = Some(first_value.type_()); 11162 for (assignment, _kind) in assignments { 11163 self.visit_typed_pipeline_assignment(assignment); 11164 self.previous_pipeline_assignment_type = Some(assignment.type_()); 11165 } 11166 11167 // If we're selecting a pipeline and the selection ends on its final step 11168 // we want to include that as well into the extracted bit. 11169 let final_step_range = self.edits.src_span_to_lsp_range(finally.location()); 11170 if let Some(extracted_function) = &mut self.function 11171 && position_within(self.params.range.end, final_step_range) 11172 { 11173 extracted_function.try_add_pipeline_step(finally.type_(), finally.location()); 11174 }; 11175 11176 self.visit_typed_expr(finally); 11177 self.previous_pipeline_assignment_type = None; 11178 } 11179 11180 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 11181 // In order to be extracted, a pipeline step must be overlapping with 11182 // the cursor selection! 11183 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 11184 if !overlaps(self.params.range, assignment_range) { 11185 return; 11186 } 11187 11188 match &mut self.function { 11189 None => { 11190 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps { 11191 location: assignment.location, 11192 before_first: self.previous_pipeline_assignment_type.clone(), 11193 return_type: assignment.type_(), 11194 })); 11195 } 11196 Some(extracted_function) => { 11197 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location) 11198 } 11199 } 11200 ast::visit::visit_typed_pipeline_assignment(self, assignment); 11201 } 11202 11203 fn visit_typed_expr_var( 11204 &mut self, 11205 location: &'ast SrcSpan, 11206 constructor: &'ast ValueConstructor, 11207 name: &'ast EcoString, 11208 ) { 11209 if let type_::ValueConstructorVariant::LocalVariable { 11210 location: definition_location, 11211 .. 11212 } = &constructor.variant 11213 { 11214 self.register_referenced_variable( 11215 name, 11216 &constructor.type_, 11217 *location, 11218 *definition_location, 11219 ); 11220 } 11221 } 11222 11223 fn visit_typed_clause_guard_var( 11224 &mut self, 11225 location: &'ast SrcSpan, 11226 name: &'ast EcoString, 11227 type_: &'ast Arc<Type>, 11228 definition_location: &'ast SrcSpan, 11229 _origin: &'ast VariableOrigin, 11230 ) { 11231 self.register_referenced_variable(name, type_, *location, *definition_location); 11232 } 11233 11234 fn visit_typed_expr_case( 11235 &mut self, 11236 location: &'ast SrcSpan, 11237 type_: &'ast Arc<Type>, 11238 subjects: &'ast [TypedExpr], 11239 clauses: &'ast [ast::TypedClause], 11240 compiled_case: &'ast CompiledCase, 11241 ) { 11242 let was_extracting_already = self.function.is_some(); 11243 11244 // We first visit as usual... 11245 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 11246 11247 // But then we need to check we're in a situation where it actually makes 11248 // sense to extract: if the cursor is entirely within the case (so it's 11249 // not part of a bigger extracted chunk) and it spans over one of the 11250 // branches' pattern or guard, then we don't want to allow extracting 11251 // anything. Popping up the action would be confusing. 11252 // For example: 11253 // 11254 // ```gleam 11255 // case wibble { 11256 // Ok(_) -> todo 11257 // //^^^^^^^^^^^^^ If I'm selecting this whole branch it makes no sense 11258 // to propose extracting it as a function 11259 // _ if wibble -> todo 11260 // // ^^^^^^^^^^^ If I'm selecting this it makes no sense to 11261 // // propose extracting it as a function 11262 // } 11263 // ``` 11264 11265 // We were already extracting something, we don't want to render that 11266 // choice null, this is just a part of a bigger piece being extracted. 11267 if was_extracting_already { 11268 return; 11269 } 11270 11271 for clause in clauses { 11272 let left_hand_side_location = SrcSpan { 11273 start: clause.pattern_location().start, 11274 end: clause.then.location().start - 1, 11275 }; 11276 let left_hand_side_range = self.edits.src_span_to_lsp_range(left_hand_side_location); 11277 // If there's any overlapping with one of the patterns, then we 11278 // don't want to extract anything. 11279 if overlaps(self.params.range, left_hand_side_range) { 11280 self.function = None; 11281 break; 11282 } 11283 } 11284 } 11285 11286 fn visit_typed_bit_array_size_variable( 11287 &mut self, 11288 location: &'ast SrcSpan, 11289 name: &'ast EcoString, 11290 constructor: &'ast Option<Box<ValueConstructor>>, 11291 type_: &'ast Arc<Type>, 11292 ) { 11293 let variant = match constructor { 11294 Some(constructor) => &constructor.variant, 11295 None => return, 11296 }; 11297 if let type_::ValueConstructorVariant::LocalVariable { 11298 location: definition_location, 11299 .. 11300 } = variant 11301 { 11302 self.register_referenced_variable(name, type_, *location, *definition_location); 11303 } 11304 } 11305} 11306 11307/// Code action to merge two identical branches together. 11308/// 11309pub struct MergeCaseBranches<'a> { 11310 module: &'a Module, 11311 params: &'a CodeActionParams, 11312 edits: TextEdits<'a>, 11313 /// These are the positions of the patterns of all the consecutive branches 11314 /// we've determined can be merged, for example if we're mergin the first 11315 /// two branches here: 11316 /// 11317 /// ```gleam 11318 /// case wibble { 11319 /// 1 -> todo 11320 /// // ^ this location here 11321 /// 20 -> todo 11322 /// // ^^ and this location here 11323 /// _ -> todo 11324 /// } 11325 /// ``` 11326 /// 11327 /// We need those to delete all the space between each consecutive pattern, 11328 /// replacing it with the `|` for alternatives 11329 /// 11330 patterns_to_merge: Option<MergeableBranches>, 11331} 11332 11333struct MergeableBranches { 11334 /// The span of the body to keep when merging multiple branches. For 11335 /// example: 11336 /// 11337 /// ```gleam 11338 /// case n { 11339 /// // Imagine we're merging the first three branches together... 11340 /// 1 -> todo 11341 /// 2 -> n * 2 11342 /// // ^^^^^ This would be the location of the one body to keep 11343 /// 3 -> todo 11344 /// _ -> todo 11345 /// } 11346 /// ``` 11347 /// 11348 body_to_keep: SrcSpan, 11349 11350 /// The location body of the last of the branches that are going to be 11351 /// merged; that is where we're going to place the code of the body to keep 11352 /// once the action is done. For example: 11353 /// 11354 /// ```gleam 11355 /// case n { 11356 /// // Imagine we're merging the first three branches together... 11357 /// 1 -> todo 11358 /// 2 -> n * 2 11359 /// 3 -> todo 11360 /// // ^^^^ This would be the location of the final body 11361 /// _ -> todo 11362 /// } 11363 /// ``` 11364 /// 11365 final_body: SrcSpan, 11366 11367 /// The span of the patterns whose branches are going to be merged. For 11368 /// example: 11369 /// 11370 /// ```gleam 11371 /// case n { 11372 /// // Imagine we're merging the first three branches together... 11373 /// 1 -> todo 11374 /// // ^ 11375 /// 2 -> n * 2 11376 /// // ^ 11377 /// 3 -> todo 11378 /// // ^ These would be the locations of the patterns 11379 /// _ -> todo 11380 /// } 11381 /// ``` 11382 /// 11383 patterns_to_merge: Vec<SrcSpan>, 11384} 11385 11386impl<'a> MergeCaseBranches<'a> { 11387 pub fn new( 11388 module: &'a Module, 11389 line_numbers: &'a LineNumbers, 11390 params: &'a CodeActionParams, 11391 ) -> Self { 11392 Self { 11393 module, 11394 params, 11395 edits: TextEdits::new(line_numbers), 11396 patterns_to_merge: None, 11397 } 11398 } 11399 11400 pub fn code_actions(mut self) -> Vec<CodeAction> { 11401 self.visit_typed_module(&self.module.ast); 11402 11403 let Some(mergeable_branches) = self.patterns_to_merge else { 11404 return vec![]; 11405 }; 11406 11407 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() { 11408 self.edits 11409 .replace(SrcSpan::new(one.end, next.start), " | ".into()); 11410 } 11411 11412 self.edits.replace( 11413 mergeable_branches.final_body, 11414 code_at(self.module, mergeable_branches.body_to_keep).into(), 11415 ); 11416 11417 let mut action = Vec::with_capacity(1); 11418 CodeActionBuilder::new("Merge case branches") 11419 .kind(CodeActionKind::RefactorRewrite) 11420 .changes(self.params.text_document.uri.clone(), self.edits.edits) 11421 .preferred(false) 11422 .push_to(&mut action); 11423 action 11424 } 11425 11426 fn select_mergeable_branches( 11427 &self, 11428 clauses: &'a [ast::TypedClause], 11429 ) -> Option<MergeableBranches> { 11430 let mut clauses = clauses 11431 .iter() 11432 // We want to skip all the branches at the beginning of the case 11433 // expression that the cursor is not hovering over. For example: 11434 // 11435 // ```gleam 11436 // case wibble { 11437 // a -> 1 <- we want to skip this one here that is not selected 11438 // b -> 2 11439 // ^^^^ this is the selection 11440 // _ -> 3 11441 // ^^ 11442 // } 11443 // ``` 11444 .skip_while(|clause| { 11445 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 11446 !overlaps(self.params.range, clause_range) 11447 }) 11448 // Then we only want to take the clauses that we're hovering over 11449 // with our selection (even partially!) 11450 // In the provious example they would be `b -> 2` and `_ -> 3`. 11451 .take_while(|clause| { 11452 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 11453 overlaps(self.params.range, clause_range) 11454 }); 11455 11456 let first_hovered_clause = clauses.next()?; 11457 11458 // This is the clause we're comparing all the others with. We need to 11459 // make sure that all the clauses we're going to join can be merged with 11460 // this one. 11461 let mut reference_clause = first_hovered_clause; 11462 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()]; 11463 let mut final_body = first_hovered_clause.then.location(); 11464 11465 for clause in clauses { 11466 // As soon as we find a clause that can't be merged with the current 11467 // reference we know we're done looking for consecutive clauses to 11468 // merge. 11469 if !clauses_can_be_merged(reference_clause, clause) { 11470 break; 11471 } 11472 11473 clause_patterns_to_merge.push(clause.pattern_location()); 11474 final_body = clause.then.location(); 11475 11476 // If the current reference is a `todo` expression, we want to use 11477 // the newly found mergeable clause as the next reference. The 11478 // reference clause is the one whose body will be kept around, so if 11479 // we can we avoid keeping `todo`s 11480 if reference_clause.then.is_todo_with_no_message() { 11481 reference_clause = clause; 11482 } 11483 } 11484 11485 // We only offer the code action if we have found two or more clauses 11486 // to merge. 11487 if clause_patterns_to_merge.len() >= 2 { 11488 Some(MergeableBranches { 11489 final_body, 11490 body_to_keep: reference_clause.then.location(), 11491 patterns_to_merge: clause_patterns_to_merge, 11492 }) 11493 } else { 11494 None 11495 } 11496 } 11497} 11498 11499fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool { 11500 // Two clauses cannot be merged if any of those has an if guard 11501 if one.guard.is_some() || other.guard.is_some() { 11502 return false; 11503 } 11504 11505 // Two clauses can only be merged if they define the same variables, 11506 // otherwise joining them would result in invalid code. 11507 let variables_one = one 11508 .bound_variables() 11509 .map(|variable| (variable.name(), variable.type_)) 11510 .collect::<HashMap<_, _>>(); 11511 11512 let variables_other = other 11513 .bound_variables() 11514 .map(|variable| (variable.name(), variable.type_)) 11515 .collect::<HashMap<_, _>>(); 11516 11517 for (name, type_) in variables_one.iter() { 11518 if let Some(type_other) = variables_other.get(name) 11519 && type_other.same_as(type_) 11520 { 11521 continue; 11522 } 11523 11524 // There's a variable that is not defined in the second branch but 11525 // is defined in the first one, or it's defined in the second branch 11526 // but it has an incompatible type. 11527 return false; 11528 } 11529 11530 for (name, _) in variables_other.iter() { 11531 if !variables_one.contains_key(name) { 11532 // There's some variables defined in the second branch that are not 11533 // defined in the first one, so they can't be merged! 11534 return false; 11535 } 11536 } 11537 11538 // Anything can be merged with a simple todo, or the two bodies must be 11539 // syntactically equal. 11540 one.then.is_todo_with_no_message() 11541 || other.then.is_todo_with_no_message() 11542 || one.then.syntactically_eq(&other.then) 11543} 11544 11545impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> { 11546 fn visit_typed_expr_case( 11547 &mut self, 11548 location: &'ast SrcSpan, 11549 type_: &'ast Arc<Type>, 11550 subjects: &'ast [TypedExpr], 11551 clauses: &'ast [ast::TypedClause], 11552 compiled_case: &'ast CompiledCase, 11553 ) { 11554 // We only trigger the code action if we are within a case expression, 11555 // otherwise there's no point in exploring the expression any further. 11556 let case_range = self.edits.src_span_to_lsp_range(*location); 11557 if !within(self.params.range, case_range) { 11558 return; 11559 } 11560 11561 if let result @ Some(_) = self.select_mergeable_branches(clauses) { 11562 self.patterns_to_merge = result 11563 } 11564 11565 // We still need to visit the case expression in case we want to apply 11566 // the code action to some case expression that is nested in one of its 11567 // branches! 11568 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 11569 } 11570} 11571 11572/// Code action to add a missing type parameter to custom types. 11573/// If a custom type is missing a type parameter, as it is the case 11574/// in the following example, this action will offer to add the 11575/// type parameter to the type definition. 11576/// 11577/// Before: 11578/// ```gleam 11579/// type Wibble { 11580/// Wibble(field: t) 11581/// } 11582/// ``` 11583/// 11584/// After: 11585/// ```gleam 11586/// type Wibble(t) { 11587/// Wibble(field: t) 11588/// } 11589/// ``` 11590/// 11591pub struct AddMissingTypeParameter<'a> { 11592 module: &'a Module, 11593 params: &'a CodeActionParams, 11594 edits: TextEdits<'a>, 11595 /// The source location where the parameters should be defined. 11596 /// This might be a zero-length span if there are no parameters yet, 11597 /// or it might cover the already existing type parameter definitions. 11598 parameters_location: Option<SrcSpan>, 11599 /// If the type definition already had existing parameters before. 11600 has_existing_parameters: bool, 11601 /// The set of all type parameter names in the different variants of the type 11602 /// that are not already part of the type parameter definition on the type. 11603 missing_parameters: HashSet<EcoString>, 11604} 11605 11606impl<'a> AddMissingTypeParameter<'a> { 11607 pub fn new( 11608 module: &'a Module, 11609 line_numbers: &'a LineNumbers, 11610 params: &'a CodeActionParams, 11611 ) -> Self { 11612 Self { 11613 module, 11614 params, 11615 edits: TextEdits::new(line_numbers), 11616 parameters_location: None, 11617 has_existing_parameters: false, 11618 missing_parameters: HashSet::new(), 11619 } 11620 } 11621 11622 pub fn code_actions(mut self) -> Vec<CodeAction> { 11623 self.visit_typed_module(&self.module.ast); 11624 11625 let Some(type_parameters_location) = self.parameters_location else { 11626 return vec![]; 11627 }; 11628 11629 if self.missing_parameters.is_empty() { 11630 return vec![]; 11631 } 11632 11633 let mut new_parameters = self.missing_parameters.iter().sorted().join(", "); 11634 if self.has_existing_parameters { 11635 let has_trailing_comma = self 11636 .module 11637 .extra 11638 .trailing_commas 11639 .iter() 11640 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma)); 11641 11642 if !has_trailing_comma { 11643 new_parameters.insert_str(0, ", "); 11644 } 11645 11646 self.edits 11647 .insert(type_parameters_location.end - 1, new_parameters); 11648 } else { 11649 self.edits 11650 .insert(type_parameters_location.end, format!("({new_parameters})")); 11651 } 11652 11653 let mut action = Vec::with_capacity(1); 11654 CodeActionBuilder::new("Add missing type parameter") 11655 .kind(CodeActionKind::QuickFix) 11656 .changes(self.params.text_document.uri.clone(), self.edits.edits) 11657 .preferred(true) 11658 .push_to(&mut action); 11659 action 11660 } 11661} 11662 11663impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> { 11664 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 11665 let custom_type_range = self 11666 .edits 11667 .src_span_to_lsp_range(custom_type.full_location()); 11668 11669 // Only continue, if the action was selected anywhere within the custom type definition. 11670 if !within(self.params.range, custom_type_range) { 11671 return; 11672 } 11673 11674 self.parameters_location = Some(SrcSpan::new( 11675 custom_type.name_location.end, 11676 custom_type.location.end, 11677 )); 11678 11679 self.has_existing_parameters = !custom_type.typed_parameters.is_empty(); 11680 11681 let existing_names: HashSet<_> = custom_type 11682 .parameters 11683 .iter() 11684 .map(|(_, name)| name) 11685 .collect(); 11686 11687 // Collect the remaining type parameters from the variant constructors. 11688 for record in &custom_type.constructors { 11689 for argument in &record.arguments { 11690 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast 11691 && !existing_names.contains(name) 11692 { 11693 let _ = self.missing_parameters.insert(name.clone()); 11694 } 11695 } 11696 } 11697 } 11698} 11699 11700/// Code action to replace a `_` with its actual type in an annotation. 11701/// 11702/// Before: 11703/// ```gleam 11704/// fn wibble() -> Ok(_) { Ok(1) } 11705/// // ^ Trigger it here 11706/// ``` 11707/// 11708/// After: 11709/// ```gleam 11710/// fn wibble() -> Ok(Int) { Ok(1) } 11711/// ``` 11712/// 11713pub struct ReplaceUnderscoreWithType<'a> { 11714 module: &'a Module, 11715 params: &'a CodeActionParams, 11716 edits: TextEdits<'a>, 11717 hovered_hole: Option<HoveredHole>, 11718} 11719 11720struct HoveredHole { 11721 type_: Arc<Type>, 11722 location: SrcSpan, 11723} 11724 11725impl<'a> ReplaceUnderscoreWithType<'a> { 11726 pub fn new( 11727 module: &'a Module, 11728 line_numbers: &'a LineNumbers, 11729 params: &'a CodeActionParams, 11730 ) -> Self { 11731 Self { 11732 module, 11733 params, 11734 edits: TextEdits::new(line_numbers), 11735 hovered_hole: None, 11736 } 11737 } 11738 11739 pub fn code_actions(mut self) -> Vec<CodeAction> { 11740 self.visit_typed_module(&self.module.ast); 11741 11742 let mut action = Vec::with_capacity(1); 11743 11744 let Some(HoveredHole { type_, location }) = self.hovered_hole else { 11745 return vec![]; 11746 }; 11747 let mut printer = Printer::new(&self.module.ast.names); 11748 self.edits 11749 .replace(location, format!("{}", printer.print_type(&type_))); 11750 11751 CodeActionBuilder::new("Replace `_` with type") 11752 .kind(CodeActionKind::QuickFix) 11753 .changes(self.params.text_document.uri.clone(), self.edits.edits) 11754 .preferred(true) 11755 .push_to(&mut action); 11756 action 11757 } 11758} 11759 11760impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> { 11761 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) { 11762 // We never traverse a type annotation we're not hovering 11763 let node_location = self.edits.src_span_to_lsp_range(node.location()); 11764 if !within(self.params.range, node_location) { 11765 return; 11766 } 11767 ast::visit::visit_type_ast(self, node, inferred_type); 11768 } 11769 11770 fn visit_type_ast_hole( 11771 &mut self, 11772 location: &'ast SrcSpan, 11773 _name: &'ast EcoString, 11774 type_: Option<Arc<Type>>, 11775 ) { 11776 if let Some(type_) = type_ { 11777 self.hovered_hole = Some(HoveredHole { 11778 type_, 11779 location: *location, 11780 }) 11781 } 11782 } 11783} 11784 11785/// Code action to turn a function used as a reference into a one-statement anonymous function. 11786/// 11787/// For example, if the code action was used on `op` here: 11788/// 11789/// ```gleam 11790/// list.map([1, 2, 3], op) 11791/// ``` 11792/// 11793/// it would become: 11794/// 11795/// ```gleam 11796/// list.map([1, 2, 3], fn(int) { 11797/// op(int) 11798/// }) 11799/// ``` 11800pub struct WrapInAnonymousFunction<'a> { 11801 module: &'a Module, 11802 params: &'a CodeActionParams, 11803 functions: Vec<FunctionToWrap>, 11804 edits: TextEdits<'a>, 11805} 11806 11807/// Helper struct, a target for [WrapInAnonymousFunction]. 11808struct FunctionToWrap { 11809 location: SrcSpan, 11810 arguments: Vec<Arc<Type>>, 11811 variables_names: VariablesNames, 11812} 11813 11814impl<'a> WrapInAnonymousFunction<'a> { 11815 pub fn new( 11816 module: &'a Module, 11817 line_numbers: &'a LineNumbers, 11818 params: &'a CodeActionParams, 11819 ) -> Self { 11820 Self { 11821 module, 11822 params, 11823 edits: TextEdits::new(line_numbers), 11824 functions: vec![], 11825 } 11826 } 11827 11828 pub fn code_actions(mut self) -> Vec<CodeAction> { 11829 self.visit_typed_module(&self.module.ast); 11830 11831 let mut actions = Vec::with_capacity(self.functions.len()); 11832 for target in self.functions { 11833 let mut name_generator = NameGenerator::new(); 11834 name_generator.reserve_variable_names(target.variables_names); 11835 let arguments = target 11836 .arguments 11837 .iter() 11838 .map(|type_| name_generator.generate_name_from_type(type_)) 11839 .join(", "); 11840 11841 self.edits 11842 .insert(target.location.start, format!("fn({arguments}) {{ ")); 11843 self.edits 11844 .insert(target.location.end, format!("({arguments}) }}")); 11845 11846 CodeActionBuilder::new("Wrap in anonymous function") 11847 .kind(CodeActionKind::RefactorRewrite) 11848 .changes( 11849 self.params.text_document.uri.clone(), 11850 self.edits.edits.drain(..).collect(), 11851 ) 11852 .push_to(&mut actions); 11853 } 11854 actions 11855 } 11856} 11857 11858impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> { 11859 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 11860 let expression_range = self.edits.src_span_to_lsp_range(expression.location()); 11861 if !within(self.params.range, expression_range) { 11862 return; 11863 } 11864 11865 // Exclude if the expression is already a function literal 11866 let is_excluded = matches!(expression, TypedExpr::Fn { .. }); 11867 11868 if let Type::Fn { arguments, .. } = &*expression.type_() 11869 && !is_excluded 11870 { 11871 self.functions.push(FunctionToWrap { 11872 location: expression.location(), 11873 arguments: arguments.clone(), 11874 variables_names: VariablesNames::from_expression(expression), 11875 }); 11876 } 11877 11878 ast::visit::visit_typed_expr(self, expression); 11879 } 11880 11881 /// We don't want to apply to functions that are being explicitly called 11882 /// already, so we need to intercept visits to function calls and bounce 11883 /// them out again so they don't end up in our impl for visit_typed_expr. 11884 /// Otherwise this is the same as []. 11885 fn visit_typed_expr_call( 11886 &mut self, 11887 _location: &'ast SrcSpan, 11888 _type: &'ast Arc<Type>, 11889 fun: &'ast TypedExpr, 11890 arguments: &'ast [TypedCallArg], 11891 _argument_parentheses: &'ast Option<u32>, 11892 ) { 11893 // We only need to do this interception for explicit calls, so if any 11894 // of our arguments are explicit we re-enter the visitor as usual. 11895 if arguments.iter().any(|argument| argument.is_implicit()) { 11896 self.visit_typed_expr(fun); 11897 } else { 11898 // We still want to visit other nodes nested in the function being 11899 // called so we bounce the call back out. 11900 ast::visit::visit_typed_expr(self, fun); 11901 } 11902 11903 for argument in arguments { 11904 self.visit_typed_call_arg(argument); 11905 } 11906 } 11907 11908 /// We don't want to apply this to record constructors used in a record 11909 /// update. 11910 fn visit_typed_expr_record_update( 11911 &mut self, 11912 location: &'ast SrcSpan, 11913 spread_start: &'ast u32, 11914 type_: &'ast Arc<Type>, 11915 updated_record: &'ast TypedExpr, 11916 updated_record_assigned_name: &'ast Option<EcoString>, 11917 constructor: &'ast TypedExpr, 11918 arguments: &'ast [TypedCallArg], 11919 ) { 11920 // If we're hovering the record constructor of an update and that is a 11921 // simple record constructor, we don't want to offer the wrap in 11922 // anonymous function code action: 11923 // 11924 // ```gleam 11925 // Wibble(..wibble, a: 1) 11926 // // ^^^ Wrap in anonymous function makes no sense on this! 11927 // ``` 11928 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location()); 11929 if within(self.params.range, constructor_range) 11930 && constructor.is_record_constructor_function() 11931 { 11932 return; 11933 } 11934 11935 ast::visit::visit_typed_expr_record_update( 11936 self, 11937 location, 11938 spread_start, 11939 type_, 11940 updated_record, 11941 updated_record_assigned_name, 11942 constructor, 11943 arguments, 11944 ); 11945 } 11946} 11947 11948/// Code action to unwrap trivial one-statement anonymous functions into just a 11949/// reference to the function called 11950/// 11951/// For example, if the code action was used on the anonymous function here: 11952/// 11953/// ```gleam 11954/// list.map([1, 2, 3], fn(int) { 11955/// op(int) 11956/// }) 11957/// ``` 11958/// 11959/// it would become: 11960/// 11961/// ```gleam 11962/// list.map([1, 2, 3], op) 11963/// ``` 11964pub struct UnwrapAnonymousFunction<'a> { 11965 module: &'a Module, 11966 line_numbers: &'a LineNumbers, 11967 params: &'a CodeActionParams, 11968 functions: Vec<FunctionToUnwrap>, 11969} 11970 11971/// Helper struct, a target for [UnwrapAnonymousFunction] 11972struct FunctionToUnwrap { 11973 /// Location of the anonymous function to apply the action to. 11974 outer_function: SrcSpan, 11975 /// Location of the opening brace of the anonymous function. 11976 outer_function_body_start: u32, 11977 /// Location of the function being called inside the anonymous function. 11978 /// This will be all that's left after the action, plus any comments. 11979 inner_function: SrcSpan, 11980 // Location of the opening parenthesis of the inner function's argument list. 11981 inner_function_arguments_start: u32, 11982} 11983 11984impl<'a> UnwrapAnonymousFunction<'a> { 11985 pub fn new( 11986 module: &'a Module, 11987 line_numbers: &'a LineNumbers, 11988 params: &'a CodeActionParams, 11989 ) -> Self { 11990 Self { 11991 module, 11992 line_numbers, 11993 params, 11994 functions: vec![], 11995 } 11996 } 11997 11998 pub fn code_actions(mut self) -> Vec<CodeAction> { 11999 self.visit_typed_module(&self.module.ast); 12000 12001 let mut actions = Vec::with_capacity(self.functions.len()); 12002 for function in &self.functions { 12003 let mut edits = TextEdits::new(self.line_numbers); 12004 12005 // We need to delete the anonymous function's head and the opening 12006 // brace but preserve comments between it and the inner function call. 12007 // We set our endpoint at the start of the function body, and move 12008 // it on through any whitespace. 12009 let head_deletion_end = 12010 next_nonwhitespace(&self.module.code, function.outer_function_body_start + 1); 12011 edits.delete(SrcSpan { 12012 start: function.outer_function.start, 12013 end: head_deletion_end, 12014 }); 12015 12016 // Delete the inner function call's arguments. 12017 edits.delete(SrcSpan { 12018 start: function.inner_function_arguments_start, 12019 end: function.inner_function.end, 12020 }); 12021 12022 // To delete the tail we remove the function end (the '}') and any 12023 // whitespace before it. 12024 let tail_deletion_start = 12025 previous_nonwhitespace(&self.module.code, function.outer_function.end - 1); 12026 edits.delete(SrcSpan { 12027 start: tail_deletion_start, 12028 end: function.outer_function.end, 12029 }); 12030 12031 CodeActionBuilder::new("Remove anonymous function wrapper") 12032 .kind(CodeActionKind::RefactorRewrite) 12033 .changes(self.params.text_document.uri.clone(), edits.edits) 12034 .push_to(&mut actions); 12035 } 12036 actions 12037 } 12038 12039 /// If an anonymous function can be unwrapped, save it to our list 12040 /// 12041 /// We need to ensure our subjects: 12042 /// - are anonymous function literals (not captures) 12043 /// - only contain a single statement 12044 /// - that statement is a function call 12045 /// - that call's arguments exactly match the arguments of the enclosing 12046 /// function 12047 fn register_function( 12048 &mut self, 12049 location: &'a SrcSpan, 12050 kind: &'a FunctionLiteralKind, 12051 arguments: &'a [TypedArg], 12052 body: &'a Vec1<TypedStatement>, 12053 ) { 12054 let function_range = src_span_to_lsp_range(*location, self.line_numbers); 12055 if !within(self.params.range, function_range) { 12056 return; 12057 } 12058 12059 let outer_body = match kind { 12060 FunctionLiteralKind::Anonymous { head, .. } => SrcSpan::new( 12061 next_nonwhitespace(&self.module.code, head.end), 12062 location.end, 12063 ), 12064 _ => return, 12065 }; 12066 12067 // We can only apply to anonymous functions containing a single function call 12068 let [ 12069 TypedStatement::Expression(TypedExpr::Call { 12070 location: call_location, 12071 arguments: call_arguments, 12072 open_parenthesis: Some(arguments_start), 12073 .. 12074 }), 12075 ] = body.as_slice() 12076 else { 12077 return; 12078 }; 12079 12080 // We need the existing argument list for the fn to be a 1:1 match for 12081 // the args we pass to the called function, so we need to collect the 12082 // names used in both lists and check they're equal. 12083 12084 let outer_argument_names = arguments.iter().map(|a| match &a.names { 12085 ArgNames::Named { name, .. } => Some(name), 12086 // We can bail out early if any arguments are discarded, since 12087 // they couldn't match those actually used. 12088 ArgNames::Discard { .. } => None, 12089 // Anonymous functions can't have labelled arguments. 12090 ArgNames::NamedLabelled { .. } => unreachable!(), 12091 ArgNames::LabelledDiscard { .. } => unreachable!(), 12092 }); 12093 12094 let inner_argument_names = call_arguments.iter().map(|a| match &a.value { 12095 TypedExpr::Var { name, .. } => Some(name), 12096 // We can bail out early if any of these aren't variables, since 12097 // they couldn't match the inputs. 12098 _ => None, 12099 }); 12100 12101 if !inner_argument_names.eq(outer_argument_names) { 12102 return; 12103 } 12104 12105 self.functions.push(FunctionToUnwrap { 12106 outer_function: *location, 12107 outer_function_body_start: outer_body.start, 12108 inner_function: *call_location, 12109 inner_function_arguments_start: *arguments_start, 12110 }) 12111 } 12112} 12113 12114impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> { 12115 fn visit_typed_expr_fn( 12116 &mut self, 12117 location: &'ast SrcSpan, 12118 type_: &'ast Arc<Type>, 12119 kind: &'ast FunctionLiteralKind, 12120 arguments: &'ast [TypedArg], 12121 body: &'ast Vec1<TypedStatement>, 12122 return_annotation: &'ast Option<ast::TypeAst>, 12123 ) { 12124 let function_range = src_span_to_lsp_range(*location, self.line_numbers); 12125 if !within(self.params.range, function_range) { 12126 return; 12127 } 12128 12129 self.register_function(location, kind, arguments, body); 12130 12131 ast::visit::visit_typed_expr_fn( 12132 self, 12133 location, 12134 type_, 12135 kind, 12136 arguments, 12137 body, 12138 return_annotation, 12139 ) 12140 } 12141} 12142 12143/// Code action to create unknown modules when an import is added for a 12144/// module that doesn't exist. 12145/// 12146/// For example, if `import wobble/woo` is added to `src/wiggle.gleam`, 12147/// then a code action to create `src/wobble/woo.gleam` will be presented 12148/// when triggered over `import wobble/woo`. 12149pub struct CreateUnknownModule<'a, IO> { 12150 module: &'a Module, 12151 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 12152 lines: &'a LineNumbers, 12153 params: &'a CodeActionParams, 12154 paths: &'a ProjectPaths, 12155 error: &'a Option<Error>, 12156} 12157 12158impl<'a, IO> CreateUnknownModule<'a, IO> { 12159 pub fn new( 12160 module: &'a Module, 12161 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 12162 lines: &'a LineNumbers, 12163 params: &'a CodeActionParams, 12164 paths: &'a ProjectPaths, 12165 error: &'a Option<Error>, 12166 ) -> Self { 12167 Self { 12168 module, 12169 compiler, 12170 lines, 12171 params, 12172 paths, 12173 error, 12174 } 12175 } 12176 12177 pub fn code_actions(self) -> Vec<CodeAction> { 12178 // This code action can be derived from `UnknownModule` type errors. 12179 // If those errors don't exist for the current module, then we will not 12180 // suggest this action. 12181 let Some(Error::Type { failed_modules, .. }) = self.error else { 12182 return vec![]; 12183 }; 12184 let Some(failed_module) = failed_modules.get(&self.module.name) else { 12185 return vec![]; 12186 }; 12187 12188 let mut unknown_module_name = None; 12189 // We then need to find the `UnknownModule` error that is under the 12190 // cursor (if any, otherwise there's no action to suggest)! 12191 for error in &failed_module.errors { 12192 let TypeError::UnknownModule { location, name, .. } = error else { 12193 continue; 12194 }; 12195 let error_range = src_span_to_lsp_range(*location, self.lines); 12196 if !within(self.params.range, error_range) { 12197 continue; 12198 } 12199 // We've found the unknown module error!! 12200 unknown_module_name = Some(name); 12201 } 12202 12203 let Some(unknown_module_name) = unknown_module_name else { 12204 return vec![]; 12205 }; 12206 12207 // Now we need to check the module actually doesn't exist among the 12208 // importable ones! Imagine we've written `timestamp.to_string` and 12209 // `timestamp` is unknown: if there's any importable module in the form 12210 // `.../timestamp` then the most likely scenario is that the programmer 12211 // wanted to import that and not create a new `src/timestamp.gleam` 12212 // file! 12213 // So we check if any of the importable modules ends with the unknown 12214 // name and if that's the case we don't suggest this action. 12215 let importable_modules = self.compiler.project_compiler.get_importable_modules(); 12216 let unknown_module_can_be_imported = importable_modules 12217 .keys() 12218 .find(|module_name| module_name.split('/').next_back() == Some(unknown_module_name)) 12219 .is_some(); 12220 if unknown_module_can_be_imported { 12221 return vec![]; 12222 } 12223 12224 // We've made sure the module is not among the importable ones, so now 12225 // we figure out if the generated module needs to go under `src`, 12226 // `test`, or `dev` and we're good to actually generate it! 12227 let origin_directory = match self.module.origin { 12228 Origin::Src => self.paths.src_directory(), 12229 Origin::Test => self.paths.test_directory(), 12230 Origin::Dev => self.paths.dev_directory(), 12231 }; 12232 12233 let uri = url_from_path(&format!("{origin_directory}/{}.gleam", unknown_module_name)) 12234 .expect("origin directory is absolute"); 12235 12236 let mut actions = vec![]; 12237 CodeActionBuilder::new(format!( 12238 "Create {}/{}.gleam", 12239 self.module.origin.folder_name(), 12240 unknown_module_name 12241 )) 12242 .kind(CodeActionKind::QuickFix) 12243 .document_changes(vec![DocumentChange::CreateFile(CreateFile { 12244 uri, 12245 options: Some(CreateFileOptions { 12246 overwrite: Some(false), 12247 ignore_if_exists: Some(true), 12248 }), 12249 annotation_id: None, 12250 })]) 12251 .push_to(&mut actions); 12252 12253 actions 12254 } 12255}