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
462 kB 13254 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use std::{collections::HashSet, iter, ops::Neg, sync::Arc}; 5 6use ecow::{EcoString, eco_format}; 7use gleam_core::{ 8 Error, STDLIB_PACKAGE_NAME, 9 analyse::Inferred, 10 ast::{ 11 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable, 12 BoundVariableName, CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import, 13 InvalidExpression, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments, 14 PipelineAssignmentKind, Publicity, RecordConstructor, SrcSpan, TodoKind, 15 TypeAstConstructorName, TypedArg, TypedAssignment, TypedClauseGuard, TypedDefinitions, 16 TypedExpr, TypedFunction, TypedModuleConstant, TypedPattern, TypedPipelineAssignment, 17 TypedRecordConstructor, TypedStatement, TypedTailPattern, TypedUse, visit::Visit as _, 18 }, 19 build::{Located, Module, Origin}, 20 config::PackageConfig, 21 exhaustiveness::CompiledCase, 22 line_numbers::LineNumbers, 23 parse::{extra::ModuleExtra, lexer::string_to_keyword}, 24 paths::ProjectPaths, 25 strings::to_snake_case, 26 type_::{ 27 self, Error as TypeError, FieldMap, ModuleValueConstructor, Opaque, Type, TypeVar, 28 TypedCallArg, ValueConstructor, 29 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin}, 30 printer::Printer, 31 }, 32}; 33use im::HashMap; 34use itertools::Itertools; 35use lsp_types::{ 36 CodeAction, CodeActionKind, CodeActionParams, CreateFile, CreateFileOptions, DocumentChange, 37 Position, Range, TextEdit, Uri as Url, 38}; 39use num_bigint::BigInt; 40use vec1::{Vec1, vec1}; 41 42use crate::engine::{completely_within, position_within}; 43 44use super::{ 45 TextEdits, 46 compiler::LspProjectCompiler, 47 edits, 48 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import}, 49 engine::{overlaps, within}, 50 files::FileSystemProxy, 51 reference::{FindVariableReferences, VariableReferenceKind}, 52 src_span_to_lsp_range, url_from_path, 53}; 54 55#[derive(Debug)] 56pub struct CodeActionBuilder { 57 action: CodeAction, 58} 59 60impl CodeActionBuilder { 61 pub fn new(title: &str) -> Self { 62 Self { 63 action: CodeAction { 64 title: title.to_string(), 65 kind: None, 66 diagnostics: None, 67 edit: None, 68 command: None, 69 is_preferred: None, 70 disabled: None, 71 data: None, 72 tags: None, 73 }, 74 } 75 } 76 77 pub fn kind(mut self, kind: CodeActionKind) -> Self { 78 self.action.kind = Some(kind); 79 self 80 } 81 82 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self { 83 let mut edit = self.action.edit.take().unwrap_or_default(); 84 let mut changes = edit.changes.take().unwrap_or_default(); 85 _ = changes.insert(uri, edits); 86 87 edit.changes = Some(changes); 88 self.action.edit = Some(edit); 89 self 90 } 91 92 pub fn document_changes(mut self, changes: Vec<DocumentChange>) -> Self { 93 let mut edit = self.action.edit.take().unwrap_or_default(); 94 95 edit.document_changes = Some(changes); 96 97 self.action.edit = Some(edit); 98 self 99 } 100 101 pub fn preferred(mut self, is_preferred: bool) -> Self { 102 self.action.is_preferred = Some(is_preferred); 103 self 104 } 105 106 pub fn push_to(self, actions: &mut Vec<CodeAction>) { 107 actions.push(self.action); 108 } 109} 110 111/// A small helper function to get the indentation at a given position. 112fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize { 113 let mut indent_size = 0; 114 let line_start = *line_numbers 115 .line_starts 116 .get(line as usize) 117 .expect("Line number should be valid"); 118 119 let mut chars = code[line_start as usize..].chars(); 120 while chars.next() == Some(' ') { 121 indent_size += 1; 122 } 123 124 indent_size 125} 126 127// Given a string and a position in it, if the position points to whitespace, 128// this function returns the next position which doesn't. 129fn next_nonwhitespace(string: &EcoString, position: u32) -> u32 { 130 let mut n = position; 131 let mut chars = string[position as usize..].chars(); 132 while chars.next().is_some_and(char::is_whitespace) { 133 n += 1; 134 } 135 n 136} 137 138// Given a string and a position in it, if the position points after whitespace, 139// this function returns the previous position which doesn't. 140fn previous_nonwhitespace(string: &EcoString, position: u32) -> u32 { 141 let mut n = position; 142 let mut chars = string[..position as usize].chars(); 143 while chars.next_back().is_some_and(char::is_whitespace) { 144 n -= 1; 145 } 146 n 147} 148 149/// Code action to remove literal tuples in case subjects, essentially making 150/// the elements of the tuples into the case's subjects. 151/// 152/// The code action is only available for the i'th subject if: 153/// - it is a non-empty tuple, and 154/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses. 155/// 156/// # Basic example: 157/// 158/// The following case expression: 159/// 160/// ```gleam 161/// case #(1, 2) { 162/// #(a, b) -> 0 163/// } 164/// ``` 165/// 166/// Becomes: 167/// 168/// ```gleam 169/// case 1, 2 { 170/// a, b -> 0 171/// } 172/// ``` 173/// 174/// # Another example: 175/// 176/// The following case expression does not produce any code action 177/// 178/// ```gleam 179/// case #(1, 2) { 180/// a -> 0 // <- the pattern is not a tuple 181/// } 182/// ``` 183pub struct RedundantTupleInCaseSubject<'a> { 184 edits: TextEdits<'a>, 185 code: &'a EcoString, 186 extra: &'a ModuleExtra, 187 params: &'a CodeActionParams, 188 module: &'a ast::TypedModule, 189 hovered: bool, 190} 191 192impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> { 193 fn visit_typed_expr_case( 194 &mut self, 195 location: &'ast SrcSpan, 196 type_: &'ast Arc<Type>, 197 subjects: &'ast [TypedExpr], 198 clauses: &'ast [ast::TypedClause], 199 compiled_case: &'ast CompiledCase, 200 ) { 201 for (subject_idx, subject) in subjects.iter().enumerate() { 202 let TypedExpr::Tuple { 203 location, elements, .. 204 } = subject 205 else { 206 continue; 207 }; 208 209 // Ignore empty tuple 210 if elements.is_empty() { 211 continue; 212 } 213 214 // We cannot rewrite clauses whose i-th pattern is not a discard or 215 // tuples. 216 let all_ith_patterns_are_tuples_or_discards = clauses 217 .iter() 218 .map(|clause| clause.pattern.get(subject_idx)) 219 .all(|pattern| { 220 matches!( 221 pattern, 222 Some(Pattern::Tuple { .. } | Pattern::Discard { .. }) 223 ) 224 }); 225 226 if !all_ith_patterns_are_tuples_or_discards { 227 continue; 228 } 229 230 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location())); 231 232 for clause in clauses { 233 match clause.pattern.get(subject_idx) { 234 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens( 235 *location, 236 elements.last().map(|element| element.location()), 237 ), 238 Some(Pattern::Discard { location, .. }) => { 239 self.discard_tuple_items(*location, elements.len()); 240 } 241 _ => panic!("safe: we've just checked all patterns must be discards/tuples"), 242 } 243 } 244 let range = self.edits.src_span_to_lsp_range(*location); 245 self.hovered = self.hovered || overlaps(self.params.range, range); 246 } 247 248 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 249 } 250} 251 252impl<'a> RedundantTupleInCaseSubject<'a> { 253 pub fn new( 254 module: &'a Module, 255 line_numbers: &'a LineNumbers, 256 params: &'a CodeActionParams, 257 ) -> Self { 258 Self { 259 edits: TextEdits::new(line_numbers), 260 code: &module.code, 261 extra: &module.extra, 262 params, 263 module: &module.ast, 264 hovered: false, 265 } 266 } 267 268 pub fn code_actions(mut self) -> Vec<CodeAction> { 269 self.visit_typed_module(self.module); 270 if !self.hovered { 271 return vec![]; 272 } 273 274 self.edits.edits.sort_by_key(|edit| edit.range.start); 275 276 let mut actions = vec![]; 277 CodeActionBuilder::new("Remove redundant tuples") 278 .kind(CodeActionKind::RefactorRewrite) 279 .changes(self.params.text_document.uri.clone(), self.edits.edits) 280 .preferred(true) 281 .push_to(&mut actions); 282 283 actions 284 } 285 286 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) { 287 let tuple_code = self 288 .code 289 .get(location.start as usize..location.end as usize) 290 .expect("valid span"); 291 292 // Delete `#` 293 self.edits 294 .delete(SrcSpan::new(location.start, location.start + 1)); 295 296 // Delete `(` 297 let (lparen_offset, _) = tuple_code 298 .match_indices('(') 299 // Ignore in comments 300 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32)) 301 .expect("`(` not found in tuple"); 302 303 self.edits.delete(SrcSpan::new( 304 location.start + lparen_offset as u32, 305 location.start + lparen_offset as u32 + 1, 306 )); 307 308 // Delete trailing `,` (if applicable) 309 if let Some(last_elem_location) = last_elem_location { 310 // Get the code after the last element until the tuple's `)` 311 let code_after_last_elem = self 312 .code 313 .get(last_elem_location.end as usize..location.end as usize) 314 .expect("valid span"); 315 316 if let Some((trailing_comma_offset, _)) = code_after_last_elem 317 .rmatch_indices(',') 318 // Ignore in comments 319 .find(|(i, _)| { 320 !self 321 .extra 322 .is_within_comment(last_elem_location.end + *i as u32) 323 }) 324 { 325 self.edits.delete(SrcSpan::new( 326 last_elem_location.end + trailing_comma_offset as u32, 327 last_elem_location.end + trailing_comma_offset as u32 + 1, 328 )); 329 } 330 } 331 332 // Delete ) 333 self.edits 334 .delete(SrcSpan::new(location.end - 1, location.end)); 335 } 336 337 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) { 338 // Replace the old discard with multiple discard, one for each of the 339 // tuple items. 340 self.edits.replace( 341 discard_location, 342 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(), 343 ); 344 } 345} 346 347/// Builder for code action to convert `let assert` into a case expression. 348/// 349pub struct LetAssertToCase<'a> { 350 module: &'a Module, 351 params: &'a CodeActionParams, 352 actions: Vec<CodeAction>, 353 edits: TextEdits<'a>, 354} 355 356impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> { 357 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 358 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 359 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan { 360 start: assignment.location.start, 361 end: assignment.value.location().start, 362 }); 363 self.visit_typed_expr(&assignment.value); 364 365 // Only offer the code action if the cursor is over the statement and 366 // to prevent weird behaviour when `let assert` statements are nested, 367 // we only check for the code action between the `let` and `=`. 368 if !(within(self.params.range, assignment_range) 369 && overlaps(self.params.range, assignment_start_range)) 370 { 371 return; 372 } 373 374 // This pattern only applies to `let assert` 375 let AssignmentKind::Assert { message, .. } = &assignment.kind else { 376 return; 377 }; 378 379 // Get the source code for the tested expression 380 let location = assignment.value.location(); 381 let expr = self 382 .module 383 .code 384 .get(location.start as usize..location.end as usize) 385 .expect("Location must be valid"); 386 387 // Get the source code for the pattern 388 let pattern_location = assignment.pattern.location(); 389 let pattern = self 390 .module 391 .code 392 .get(pattern_location.start as usize..pattern_location.end as usize) 393 .expect("Location must be valid"); 394 395 let message = message.as_ref().map(|message| { 396 let location = message.location(); 397 self.module 398 .code 399 .get(location.start as usize..location.end as usize) 400 .expect("Location must be valid") 401 }); 402 403 let range = self.edits.src_span_to_lsp_range(assignment.location); 404 405 // Figure out which variables are assigned in the pattern 406 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern); 407 408 let assigned = match variables.len() { 409 0 => "_", 410 1 => variables.first().expect("Variables is length one"), 411 _ => &format!("#({})", variables.join(", ")), 412 }; 413 414 let mut new_text = format!("let {assigned} = "); 415 let panic_message = if let Some(message) = message { 416 &format!("panic as {message}") 417 } else { 418 "panic" 419 }; 420 let clauses = vec![ 421 // The existing pattern 422 CaseClause { 423 pattern, 424 // `_` is not a valid expression, so if we are not 425 // binding any variables in the pattern, we simply return Nil. 426 expression: if assigned == "_" { "Nil" } else { assigned }, 427 }, 428 CaseClause { 429 pattern: "_", 430 expression: panic_message, 431 }, 432 ]; 433 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text); 434 435 let uri = &self.params.text_document.uri; 436 437 CodeActionBuilder::new("Convert to case") 438 .kind(CodeActionKind::RefactorRewrite) 439 .changes(uri.clone(), vec![TextEdit { range, new_text }]) 440 .preferred(false) 441 .push_to(&mut self.actions); 442 } 443} 444 445impl<'a> LetAssertToCase<'a> { 446 pub fn new( 447 module: &'a Module, 448 line_numbers: &'a LineNumbers, 449 params: &'a CodeActionParams, 450 ) -> Self { 451 Self { 452 module, 453 params, 454 actions: Vec::new(), 455 edits: TextEdits::new(line_numbers), 456 } 457 } 458 459 pub fn code_actions(mut self) -> Vec<CodeAction> { 460 self.visit_typed_module(&self.module.ast); 461 self.actions 462 } 463} 464 465struct PatternVariableFinder { 466 pattern_variables: Vec<EcoString>, 467} 468 469impl PatternVariableFinder { 470 fn new() -> Self { 471 Self { 472 pattern_variables: Vec::new(), 473 } 474 } 475 476 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> { 477 let mut finder = Self::new(); 478 finder.visit_typed_pattern(pattern); 479 finder.pattern_variables 480 } 481} 482 483impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder { 484 fn visit_typed_pattern_variable( 485 &mut self, 486 _location: &'ast SrcSpan, 487 name: &'ast EcoString, 488 _type: &'ast Arc<Type>, 489 _origin: &'ast VariableOrigin, 490 ) { 491 self.pattern_variables.push(name.clone()); 492 } 493 494 fn visit_typed_pattern_assign( 495 &mut self, 496 location: &'ast SrcSpan, 497 name: &'ast EcoString, 498 pattern: &'ast TypedPattern, 499 ) { 500 self.pattern_variables.push(name.clone()); 501 ast::visit::visit_typed_pattern_assign(self, location, name, pattern); 502 } 503 504 fn visit_typed_pattern_string_prefix( 505 &mut self, 506 _location: &'ast SrcSpan, 507 _left_location: &'ast SrcSpan, 508 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 509 _right_location: &'ast SrcSpan, 510 _left_side_string: &'ast EcoString, 511 right_side_assignment: &'ast AssignName, 512 ) { 513 if let Some((name, _)) = left_side_assignment { 514 self.pattern_variables.push(name.clone()); 515 } 516 if let AssignName::Variable(name) = right_side_assignment { 517 self.pattern_variables.push(name.clone()); 518 } 519 } 520} 521 522pub fn code_action_inexhaustive_let_to_case( 523 module: &Module, 524 line_numbers: &LineNumbers, 525 params: &CodeActionParams, 526 error: &Option<Error>, 527 actions: &mut Vec<CodeAction>, 528) { 529 let Some(errors) = type_errors_for_module(error, module) else { 530 return; 531 }; 532 533 let inexhaustive_assignments = errors 534 .iter() 535 .filter_map(|error| { 536 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error { 537 Some((*location, missing)) 538 } else { 539 None 540 } 541 }) 542 .collect_vec(); 543 544 if inexhaustive_assignments.is_empty() { 545 return; 546 } 547 548 for (location, missing) in inexhaustive_assignments { 549 let mut text_edits = TextEdits::new(line_numbers); 550 551 let range = text_edits.src_span_to_lsp_range(location); 552 if !within(params.range, range) { 553 continue; 554 } 555 556 let Some(Located::Statement(TypedStatement::Assignment(assignment))) = 557 module.find_node(location.start) 558 else { 559 continue; 560 }; 561 562 let TypedAssignment { 563 value, 564 pattern, 565 kind: AssignmentKind::Let, 566 location, 567 compiled_case: _, 568 annotation: _, 569 } = assignment.as_ref() 570 else { 571 continue; 572 }; 573 574 // Get the source code for the tested expression 575 let value_location = value.location(); 576 let expr = module 577 .code 578 .get(value_location.start as usize..value_location.end as usize) 579 .expect("Location must be valid"); 580 581 // Get the source code for the pattern 582 let pattern_location = pattern.location(); 583 let pattern_code = module 584 .code 585 .get(pattern_location.start as usize..pattern_location.end as usize) 586 .expect("Location must be valid"); 587 588 let range = text_edits.src_span_to_lsp_range(*location); 589 590 // Figure out which variables are assigned in the pattern 591 let variables = PatternVariableFinder::find_variables_in_pattern(pattern); 592 593 let assigned = match variables.len() { 594 0 => "_", 595 1 => variables.first().expect("Variables is length one"), 596 _ => &format!("#({})", variables.join(", ")), 597 }; 598 599 let mut new_text = format!("let {assigned} = "); 600 print_case_expression( 601 range.start.character as usize, 602 expr, 603 iter::once(CaseClause { 604 pattern: pattern_code, 605 expression: if assigned == "_" { "Nil" } else { assigned }, 606 }) 607 .chain(missing.iter().map(|pattern| CaseClause { 608 pattern, 609 expression: "todo", 610 })) 611 .collect(), 612 &mut new_text, 613 ); 614 615 let uri = &params.text_document.uri; 616 617 text_edits.replace(*location, new_text); 618 619 CodeActionBuilder::new("Convert to case") 620 .kind(CodeActionKind::QuickFix) 621 .changes(uri.clone(), text_edits.edits) 622 .preferred(true) 623 .push_to(actions); 624 } 625} 626 627pub(crate) fn type_errors_for_module<'a>( 628 error: &'a Option<Error>, 629 module: &Module, 630) -> Option<&'a Vec1<type_::Error>> { 631 let Some(Error::Type { 632 skipped_modules: _, 633 failed_modules, 634 }) = error 635 else { 636 return None; 637 }; 638 Some(&failed_modules.get(&module.name)?.errors) 639} 640 641struct CaseClause<'a> { 642 pattern: &'a str, 643 expression: &'a str, 644} 645 646fn print_case_expression( 647 indent_size: usize, 648 subject: &str, 649 clauses: Vec<CaseClause<'_>>, 650 buffer: &mut String, 651) { 652 let indent = " ".repeat(indent_size); 653 654 // Print the beginning of the expression 655 buffer.push_str("case "); 656 buffer.push_str(subject); 657 buffer.push_str(" {"); 658 659 for clause in clauses.iter() { 660 // Print the newline and indentation for this clause 661 buffer.push('\n'); 662 buffer.push_str(&indent); 663 // Indent this clause one level deeper than the case expression 664 buffer.push_str(" "); 665 666 // Print the clause 667 buffer.push_str(clause.pattern); 668 buffer.push_str(" -> "); 669 buffer.push_str(clause.expression); 670 } 671 672 // If there are no clauses to print, the closing brace should be 673 // on the same line as the opening one, with no space between. 674 if !clauses.is_empty() { 675 buffer.push('\n'); 676 buffer.push_str(&indent); 677 } 678 buffer.push('}'); 679} 680 681/// Builder for code action to apply the label shorthand syntax on arguments 682/// where the label has the same name as the variable. 683/// 684pub struct UseLabelShorthandSyntax<'a> { 685 module: &'a Module, 686 params: &'a CodeActionParams, 687 edits: TextEdits<'a>, 688} 689 690impl<'a> UseLabelShorthandSyntax<'a> { 691 pub fn new( 692 module: &'a Module, 693 line_numbers: &'a LineNumbers, 694 params: &'a CodeActionParams, 695 ) -> Self { 696 Self { 697 module, 698 params, 699 edits: TextEdits::new(line_numbers), 700 } 701 } 702 703 pub fn code_actions(mut self) -> Vec<CodeAction> { 704 self.visit_typed_module(&self.module.ast); 705 if self.edits.edits.is_empty() { 706 return vec![]; 707 } 708 let mut action = Vec::with_capacity(1); 709 CodeActionBuilder::new("Use label shorthand syntax") 710 .kind(CodeActionKind::Refactor) 711 .changes(self.params.text_document.uri.clone(), self.edits.edits) 712 .preferred(false) 713 .push_to(&mut action); 714 action 715 } 716} 717 718impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> { 719 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 720 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 721 let is_selected = overlaps(arg_range, self.params.range); 722 723 match arg { 724 CallArg { 725 label: Some(label), 726 value: TypedExpr::Var { name, location, .. }, 727 .. 728 } if is_selected && !arg.uses_label_shorthand() && label == name => { 729 self.edits.delete(*location); 730 } 731 _ => (), 732 } 733 734 ast::visit::visit_typed_call_arg(self, arg); 735 } 736 737 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 738 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 739 let is_selected = overlaps(arg_range, self.params.range); 740 741 match arg { 742 CallArg { 743 label: Some(label), 744 value: TypedPattern::Variable { name, location, .. }, 745 .. 746 } if is_selected && !arg.uses_label_shorthand() && label == name => { 747 self.edits.delete(*location); 748 } 749 _ => (), 750 } 751 752 ast::visit::visit_typed_pattern_call_arg(self, arg); 753 } 754} 755 756/// Builder for code action to apply the fill in the missing labelled arguments 757/// of the selected function call. 758/// 759pub struct FillInMissingLabelledArgs<'a> { 760 module: &'a Module, 761 params: &'a CodeActionParams, 762 edits: TextEdits<'a>, 763 use_right_hand_side_location: Option<SrcSpan>, 764 selected_call: Option<SelectedCall<'a>>, 765 current_function: Option<&'a TypedFunction>, 766} 767 768struct SelectedCall<'a> { 769 location: SrcSpan, 770 field_map: &'a FieldMap, 771 arguments: Vec<CallArg<()>>, 772 kind: SelectedCallKind, 773 fun_type: Option<Arc<Type>>, 774 enclosing_function: Option<&'a TypedFunction>, 775} 776 777enum SelectedCallKind { 778 Value, 779 Pattern, 780} 781 782impl<'a> FillInMissingLabelledArgs<'a> { 783 pub fn new( 784 module: &'a Module, 785 line_numbers: &'a LineNumbers, 786 params: &'a CodeActionParams, 787 ) -> Self { 788 Self { 789 module, 790 params, 791 edits: TextEdits::new(line_numbers), 792 use_right_hand_side_location: None, 793 selected_call: None, 794 current_function: None, 795 } 796 } 797 798 pub fn code_actions(mut self) -> Vec<CodeAction> { 799 self.visit_typed_module(&self.module.ast); 800 let Some(SelectedCall { 801 location: call_location, 802 field_map, 803 arguments, 804 kind, 805 fun_type, 806 enclosing_function, 807 }) = self.selected_call 808 else { 809 return vec![]; 810 }; 811 812 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback()); 813 let missing_labels = field_map.missing_labels(&arguments); 814 815 // If we're applying the code action to a use call, then we know 816 // that the last missing argument is going to be implicitly inserted 817 // by the compiler, so in that case we don't want to also add that 818 // last label to the completions. 819 let missing_labels = missing_labels.iter().peekable(); 820 let mut missing_labels = if is_use_call { 821 missing_labels.dropping_back(1) 822 } else { 823 missing_labels 824 }; 825 826 // If we couldn't find any missing label to insert we just return. 827 if missing_labels.peek().is_none() { 828 return vec![]; 829 } 830 831 // A pattern could have been written with no parentheses at all! 832 // So we need to check for the last character to see if parentheses 833 // are there or not before filling the arguments in 834 let has_parentheses = ")" 835 == code_at( 836 self.module, 837 SrcSpan::new(call_location.end - 1, call_location.end), 838 ); 839 let label_insertion_start = if has_parentheses { 840 // If it ends with a parentheses we'll need to start inserting 841 // right before the closing one... 842 call_location.end - 1 843 } else { 844 // ...otherwise we just append the result 845 call_location.end 846 }; 847 848 // Now we need to figure out if there's a comma at the end of the 849 // arguments list: 850 // 851 // call(one, |) 852 // ^ Cursor here, with a comma behind 853 // 854 // call(one|) 855 // ^ Cursor here, no comma behind, we'll have to add one! 856 // 857 let has_comma_after_last_argument = 858 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) { 859 self.module 860 .code 861 .get(last_arg.location.end as usize..=label_insertion_start as usize) 862 .is_some_and(|text| text.contains(',')) 863 } else { 864 false 865 }; 866 867 let scope_variables = 868 ScopeVariableCollector::new(call_location.start, &self.module.ast.definitions); 869 let variables_in_scope = match enclosing_function { 870 Some(function) => scope_variables.collect_from_function(function), 871 None => scope_variables.variables, 872 }; 873 874 let labels_list = missing_labels 875 .map(|label| { 876 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope) 877 }) 878 .join(", "); 879 880 let has_no_explicit_arguments = arguments 881 .iter() 882 .filter(|arg| !arg.is_implicit()) 883 .peekable() 884 .peek() 885 .is_none(); 886 887 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument { 888 labels_list 889 } else { 890 format!(", {labels_list}") 891 }; 892 893 let edit = if has_parentheses { 894 labels_list 895 } else { 896 // If the variant whose arguments we're filling in was written 897 // with no parentheses we need to add those as well to make it a 898 // valid constructor. 899 format!("({labels_list})") 900 }; 901 902 self.edits.insert(label_insertion_start, edit); 903 904 let mut action = Vec::with_capacity(1); 905 CodeActionBuilder::new("Fill labels") 906 .kind(CodeActionKind::QuickFix) 907 .changes(self.params.text_document.uri.clone(), self.edits.edits) 908 .preferred(true) 909 .push_to(&mut action); 910 action 911 } 912 913 /// Formats a label for insertion. Uses `label:` syntax if there's a variable 914 /// in scope with matching name and type, otherwise uses `label: todo`. 915 fn format_label( 916 label: &EcoString, 917 kind: &SelectedCallKind, 918 fun_type: &Option<Arc<Type>>, 919 field_map: &FieldMap, 920 variables_in_scope: &HashMap<EcoString, Arc<Type>>, 921 ) -> String { 922 if matches!(kind, SelectedCallKind::Pattern) { 923 return format!("{label}:"); 924 } 925 926 if let Some(var_type) = variables_in_scope.get(label) { 927 let expected_type = fun_type.as_ref().and_then(|ft| { 928 let (arg_types, _) = ft.fn_types()?; 929 let &index = field_map.fields.get(label)?; 930 arg_types.get(index as usize).cloned() 931 }); 932 933 if let Some(expected) = expected_type 934 && expected.same_as(var_type) 935 { 936 return format!("{label}:"); 937 } 938 } 939 940 format!("{label}: todo") 941 } 942 943 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> { 944 CallArg { 945 label: argument.label.clone(), 946 location: argument.location, 947 value: (), 948 implicit: argument.implicit, 949 } 950 } 951} 952 953impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> { 954 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 955 // We store the current function as the containing function while we traverse 956 // it, allowing handling nested functions correctly. 957 let previous_function = self.current_function; 958 self.current_function = Some(fun); 959 ast::visit::visit_typed_function(self, fun); 960 self.current_function = previous_function; 961 } 962 963 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 964 // If we're adding labels to a use call the correct location of the 965 // function we need to add labels to is `use_right_hand_side_location`. 966 // So we store it for when we're typing the use call. 967 let previous = self.use_right_hand_side_location; 968 self.use_right_hand_side_location = Some(use_.right_hand_side_location); 969 ast::visit::visit_typed_use(self, use_); 970 self.use_right_hand_side_location = previous; 971 } 972 973 fn visit_typed_constant_record( 974 &mut self, 975 location: &'ast SrcSpan, 976 module: &'ast Option<(EcoString, SrcSpan)>, 977 name: &'ast EcoString, 978 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 979 type_: &'ast Arc<Type>, 980 field_map: &'ast Inferred<FieldMap>, 981 record_constructor: &'ast Option<Box<ValueConstructor>>, 982 ) { 983 let record_range = self.edits.src_span_to_lsp_range(*location); 984 if !within(self.params.range, record_range) { 985 return; 986 } 987 988 if let Some(arguments) = arguments 989 && let Inferred::Known(field_map) = field_map 990 { 991 self.selected_call = Some(SelectedCall { 992 location: *location, 993 field_map, 994 arguments: arguments.iter().map(Self::empty_argument).collect(), 995 kind: SelectedCallKind::Value, 996 fun_type: record_constructor 997 .as_ref() 998 .map(|constructor| constructor.type_.clone()), 999 enclosing_function: None, 1000 }); 1001 } 1002 1003 // We only want to take into account the innermost function call 1004 // containing the current selection so we can't stop at the first call 1005 // we find (the outermost one) and have to keep traversing it in case 1006 // we're inside a nested call. 1007 ast::visit::visit_typed_constant_record( 1008 self, 1009 location, 1010 module, 1011 name, 1012 arguments, 1013 type_, 1014 field_map, 1015 record_constructor, 1016 ); 1017 } 1018 1019 fn visit_typed_expr_call( 1020 &mut self, 1021 location: &'ast SrcSpan, 1022 type_: &'ast Arc<Type>, 1023 fun: &'ast TypedExpr, 1024 arguments: &'ast [TypedCallArg], 1025 open_parenthesis: &'ast Option<u32>, 1026 ) { 1027 let call_range = self.edits.src_span_to_lsp_range(*location); 1028 if !within(self.params.range, call_range) { 1029 return; 1030 } 1031 1032 if let Some(field_map) = fun.field_map() { 1033 let location = self.use_right_hand_side_location.unwrap_or(*location); 1034 self.selected_call = Some(SelectedCall { 1035 location, 1036 field_map, 1037 arguments: arguments.iter().map(Self::empty_argument).collect(), 1038 kind: SelectedCallKind::Value, 1039 fun_type: Some(fun.type_()), 1040 enclosing_function: self.current_function, 1041 }); 1042 } 1043 1044 // We only want to take into account the innermost function call 1045 // containing the current selection so we can't stop at the first call 1046 // we find (the outermost one) and have to keep traversing it in case 1047 // we're inside a nested call. 1048 let previous = self.use_right_hand_side_location; 1049 self.use_right_hand_side_location = None; 1050 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis); 1051 self.use_right_hand_side_location = previous; 1052 } 1053 1054 fn visit_typed_pattern_constructor( 1055 &mut self, 1056 location: &'ast SrcSpan, 1057 name_location: &'ast SrcSpan, 1058 name: &'ast EcoString, 1059 arguments: &'ast Vec<CallArg<TypedPattern>>, 1060 module: &'ast Option<(EcoString, SrcSpan)>, 1061 constructor: &'ast Inferred<type_::PatternConstructor>, 1062 spread: &'ast Option<SrcSpan>, 1063 type_: &'ast Arc<Type>, 1064 ) { 1065 let call_range = self.edits.src_span_to_lsp_range(*location); 1066 if !within(self.params.range, call_range) { 1067 return; 1068 } 1069 1070 if let Some(field_map) = constructor.field_map() { 1071 self.selected_call = Some(SelectedCall { 1072 location: *location, 1073 field_map, 1074 arguments: arguments.iter().map(Self::empty_argument).collect(), 1075 kind: SelectedCallKind::Pattern, 1076 fun_type: None, 1077 enclosing_function: self.current_function, 1078 }); 1079 } 1080 1081 ast::visit::visit_typed_pattern_constructor( 1082 self, 1083 location, 1084 name_location, 1085 name, 1086 arguments, 1087 module, 1088 constructor, 1089 spread, 1090 type_, 1091 ); 1092 } 1093} 1094 1095/// Collects variables that are in scope at a given cursor position. 1096struct ScopeVariableCollector { 1097 cursor: u32, 1098 pub variables: HashMap<EcoString, Arc<Type>>, 1099} 1100 1101impl ScopeVariableCollector { 1102 fn new(cursor: u32, definitions: &TypedDefinitions) -> Self { 1103 let TypedDefinitions { constants, .. } = definitions; 1104 1105 // When creating a collector, we add module constants to the variables 1106 // that are in scope. 1107 // These will always be available inside the body of any function we 1108 // might want to analyse (unless they are shadowed and replaced in the 1109 // map, that is) 1110 let variables = constants 1111 .iter() 1112 .map(|constant| (constant.name.clone(), constant.type_.clone())) 1113 .collect(); 1114 1115 Self { cursor, variables } 1116 } 1117 1118 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> { 1119 for arg in &fun.arguments { 1120 if let Some(name) = arg.get_variable_name() { 1121 _ = self.variables.insert(name.clone(), arg.type_.clone()); 1122 } 1123 } 1124 1125 for statement in &fun.body { 1126 self.visit_typed_statement(statement); 1127 } 1128 1129 self.variables 1130 } 1131} 1132 1133impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector { 1134 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 1135 // We need to consider variables defined only before the cursor 1136 if statement.location().start >= self.cursor { 1137 return; 1138 } 1139 1140 ast::visit::visit_typed_statement(self, statement); 1141 } 1142 1143 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1144 // if cursor is inside the assignment, we only visit the value expression, 1145 // because the variable being defined isn't in scope yet. 1146 if assignment.location.contains(self.cursor) { 1147 self.visit_typed_expr(&assignment.value); 1148 } else { 1149 self.visit_typed_pattern(&assignment.pattern); 1150 } 1151 } 1152 1153 fn visit_typed_expr_fn( 1154 &mut self, 1155 _location: &'ast SrcSpan, 1156 _type_: &'ast Arc<Type>, 1157 _kind: &'ast FunctionLiteralKind, 1158 _arguments: &'ast [TypedArg], 1159 _body: &'ast Vec1<TypedStatement>, 1160 _return_annotation: &'ast Option<ast::TypeAst>, 1161 ) { 1162 // We don't descend into nested functions, as their variables aren't in 1163 // our scope 1164 } 1165 1166 fn visit_typed_expr_block( 1167 &mut self, 1168 location: &'ast SrcSpan, 1169 statements: &'ast [TypedStatement], 1170 ) { 1171 if self.cursor >= location.end { 1172 return; 1173 } 1174 ast::visit::visit_typed_expr_block(self, location, statements); 1175 } 1176 1177 fn visit_typed_pattern_variable( 1178 &mut self, 1179 _location: &'ast SrcSpan, 1180 name: &'ast EcoString, 1181 type_: &'ast Arc<Type>, 1182 _origin: &'ast VariableOrigin, 1183 ) { 1184 if !name.starts_with('_') { 1185 _ = self.variables.insert(name.clone(), type_.clone()); 1186 } 1187 } 1188 1189 fn visit_typed_pattern_assign( 1190 &mut self, 1191 _location: &'ast SrcSpan, 1192 name: &'ast EcoString, 1193 pattern: &'ast Pattern<Arc<Type>>, 1194 ) { 1195 self.visit_typed_pattern(pattern); 1196 if !name.starts_with('_') { 1197 _ = self.variables.insert(name.clone(), pattern.type_()); 1198 } 1199 } 1200} 1201 1202struct MissingImport { 1203 location: SrcSpan, 1204 suggestions: Vec<ImportSuggestion>, 1205} 1206 1207struct ImportSuggestion { 1208 // The name to replace with, if the user made a typo 1209 name: EcoString, 1210 // The optional module to import, if suggesting an importable module 1211 import: Option<EcoString>, 1212} 1213 1214pub fn code_action_import_module( 1215 module: &Module, 1216 line_numbers: &LineNumbers, 1217 params: &CodeActionParams, 1218 error: &Option<Error>, 1219 actions: &mut Vec<CodeAction>, 1220) { 1221 let uri = &params.text_document.uri; 1222 let Some(errors) = type_errors_for_module(error, module) else { 1223 return; 1224 }; 1225 1226 let missing_imports = errors 1227 .into_iter() 1228 .filter_map(|error| { 1229 if let type_::Error::UnknownModule { 1230 location, 1231 suggestions, 1232 .. 1233 } = error 1234 { 1235 suggest_imports(*location, suggestions) 1236 } else { 1237 None 1238 } 1239 }) 1240 .collect_vec(); 1241 1242 if missing_imports.is_empty() { 1243 return; 1244 } 1245 1246 let first_import_pos = position_of_first_definition_if_import(module, line_numbers); 1247 let first_is_import = first_import_pos.is_some(); 1248 let import_location = first_import_pos.unwrap_or_default(); 1249 1250 let after_import_newlines = 1251 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code); 1252 1253 for missing_import in missing_imports { 1254 let range = src_span_to_lsp_range(missing_import.location, line_numbers); 1255 if !overlaps(params.range, range) { 1256 continue; 1257 } 1258 1259 for suggestion in missing_import.suggestions { 1260 let mut edits = vec![TextEdit { 1261 range, 1262 new_text: suggestion.name.to_string(), 1263 }]; 1264 if let Some(import) = &suggestion.import { 1265 edits.push(get_import_edit( 1266 import_location, 1267 import, 1268 &after_import_newlines, 1269 )); 1270 } 1271 1272 let title = match &suggestion.import { 1273 Some(import) => &format!("Import `{import}`"), 1274 _ => &format!("Did you mean `{}`", suggestion.name), 1275 }; 1276 1277 CodeActionBuilder::new(title) 1278 .kind(CodeActionKind::QuickFix) 1279 .changes(uri.clone(), edits) 1280 .preferred(true) 1281 .push_to(actions); 1282 } 1283 } 1284} 1285 1286pub fn code_action_generate_type( 1287 module: &Module, 1288 line_numbers: &LineNumbers, 1289 params: &CodeActionParams, 1290 error: &Option<Error>, 1291 actions: &mut Vec<CodeAction>, 1292) { 1293 let uri = &params.text_document.uri; 1294 let Some(errors) = type_errors_for_module(error, module) else { 1295 return; 1296 }; 1297 1298 for error in errors { 1299 let type_::Error::UnknownType { 1300 location, 1301 name, 1302 parameter_names, 1303 .. 1304 } = error 1305 else { 1306 continue; 1307 }; 1308 1309 let range = src_span_to_lsp_range(*location, line_numbers); 1310 if !within(params.range, range) { 1311 continue; 1312 } 1313 1314 // Insert the new type stub before the top-level definition that 1315 // contains the error, so it appears close to where it is used. 1316 let (insert_at, is_public) = 1317 definition_start_and_publicity_containing(&module.ast.definitions, location.start) 1318 .unwrap_or((module.code.len() as u32, false)); 1319 let insert_range = src_span_to_lsp_range( 1320 SrcSpan { 1321 start: insert_at, 1322 end: insert_at, 1323 }, 1324 line_numbers, 1325 ); 1326 1327 let pub_prefix = if is_public { "pub " } else { "" }; 1328 let new_text = if parameter_names.is_empty() { 1329 format!("{pub_prefix}type {name}\n\n") 1330 } else { 1331 let parameters = parameter_names.join(", "); 1332 format!("{pub_prefix}type {name}({parameters})\n\n") 1333 }; 1334 1335 let edit = TextEdit { 1336 range: insert_range, 1337 new_text, 1338 }; 1339 1340 CodeActionBuilder::new("Generate type") 1341 .kind(CodeActionKind::QuickFix) 1342 .changes(uri.clone(), vec![edit]) 1343 .preferred(true) 1344 .push_to(actions); 1345 } 1346} 1347 1348/// Returns the source offset and publicity of the top-level definition that 1349/// contains `position`, so the caller can insert code before it. 1350fn definition_start_and_publicity_containing( 1351 definitions: &TypedDefinitions, 1352 position: u32, 1353) -> Option<(u32, bool)> { 1354 let functions = definitions 1355 .functions 1356 .iter() 1357 .map(|function| (function.full_location(), function.publicity.is_public())); 1358 let custom_types = definitions.custom_types.iter().map(|custom_type| { 1359 ( 1360 custom_type.full_location(), 1361 custom_type.publicity.is_public(), 1362 ) 1363 }); 1364 let type_aliases = definitions 1365 .type_aliases 1366 .iter() 1367 .map(|type_alias| (type_alias.location, type_alias.publicity.is_public())); 1368 let constants = definitions 1369 .constants 1370 .iter() 1371 .map(|constant| (constant.location, constant.publicity.is_public())); 1372 1373 functions 1374 .chain(custom_types) 1375 .chain(type_aliases) 1376 .chain(constants) 1377 .filter(|(span, _)| span.start <= position && position <= span.end) 1378 .map(|(span, is_public)| (span.start, is_public)) 1379 .next() 1380} 1381 1382fn suggest_imports( 1383 location: SrcSpan, 1384 importable_modules: &[ModuleSuggestion], 1385) -> Option<MissingImport> { 1386 let suggestions = importable_modules 1387 .iter() 1388 .map(|suggestion| { 1389 let imported_name = suggestion.last_name_component(); 1390 match suggestion { 1391 ModuleSuggestion::Importable(name) => ImportSuggestion { 1392 name: imported_name.into(), 1393 import: Some(name.clone()), 1394 }, 1395 ModuleSuggestion::Imported(_) => ImportSuggestion { 1396 name: imported_name.into(), 1397 import: None, 1398 }, 1399 } 1400 }) 1401 .collect_vec(); 1402 1403 if suggestions.is_empty() { 1404 None 1405 } else { 1406 Some(MissingImport { 1407 location, 1408 suggestions, 1409 }) 1410 } 1411} 1412 1413pub fn code_action_add_missing_patterns( 1414 module: &Module, 1415 line_numbers: &LineNumbers, 1416 params: &CodeActionParams, 1417 error: &Option<Error>, 1418 actions: &mut Vec<CodeAction>, 1419) { 1420 let uri = &params.text_document.uri; 1421 let Some(errors) = type_errors_for_module(error, module) else { 1422 return; 1423 }; 1424 let missing_patterns = errors 1425 .iter() 1426 .filter_map(|error| { 1427 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error { 1428 Some((*location, missing)) 1429 } else { 1430 None 1431 } 1432 }) 1433 .collect_vec(); 1434 1435 if missing_patterns.is_empty() { 1436 return; 1437 } 1438 1439 for (location, missing) in missing_patterns { 1440 let mut edits = TextEdits::new(line_numbers); 1441 let range = edits.src_span_to_lsp_range(location); 1442 if !within(params.range, range) { 1443 continue; 1444 } 1445 1446 let Some(Located::Expression { 1447 expression: TypedExpr::Case { 1448 clauses, subjects, .. 1449 }, 1450 .. 1451 }) = module.find_node(location.start) 1452 else { 1453 continue; 1454 }; 1455 1456 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line); 1457 1458 let indent = " ".repeat(indent_size); 1459 1460 // Insert the missing patterns just after the final clause, or just before 1461 // the closing brace if there are no clauses 1462 1463 let insert_at = clauses 1464 .last() 1465 .map(|clause| clause.location.end) 1466 .unwrap_or(location.end - 1); 1467 1468 for pattern in missing { 1469 let new_text = format!("\n{indent} {pattern} -> todo"); 1470 edits.insert(insert_at, new_text); 1471 } 1472 1473 // Add a newline + indent after the last pattern if there are no clauses 1474 // 1475 // This improves the generated code for this case: 1476 // ```gleam 1477 // case True {} 1478 // ``` 1479 // This produces: 1480 // ```gleam 1481 // case True { 1482 // True -> todo 1483 // False -> todo 1484 // } 1485 // ``` 1486 // Instead of: 1487 // ```gleam 1488 // case True { 1489 // True -> todo 1490 // False -> todo} 1491 // ``` 1492 // 1493 if clauses.is_empty() { 1494 let last_subject_location = subjects 1495 .last() 1496 .expect("Case expressions have at least one subject") 1497 .location() 1498 .end; 1499 1500 // Find the opening brace of the case expression 1501 let chars = module.code[last_subject_location as usize..].chars(); 1502 let mut start_brace_location = last_subject_location; 1503 for char in chars { 1504 start_brace_location += 1; 1505 if char == '{' { 1506 break; 1507 } 1508 } 1509 1510 // We remove any blank spaces/lines between the end of the last 1511 // comment inside the case expression and the insertion point. 1512 // If there's no comments we remove all empty space in between the 1513 // two braces 1514 let deletion_start = module 1515 .extra 1516 .last_comment_between(start_brace_location, insert_at) 1517 .map(|src_span| src_span.end) 1518 .unwrap_or(start_brace_location); 1519 1520 edits.delete(SrcSpan::new(deletion_start, insert_at)); 1521 edits.insert(insert_at, format!("\n{indent}")); 1522 } 1523 1524 CodeActionBuilder::new("Add missing patterns") 1525 .kind(CodeActionKind::QuickFix) 1526 .changes(uri.clone(), edits.edits) 1527 .preferred(true) 1528 .push_to(actions); 1529 } 1530} 1531 1532/// Builder for code action to add annotations to an assignment or function 1533/// 1534pub struct AddAnnotations<'a> { 1535 module: &'a Module, 1536 params: &'a CodeActionParams, 1537 edits: TextEdits<'a>, 1538 printer: Printer<'a>, 1539} 1540 1541impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> { 1542 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1543 self.visit_typed_expr(&assignment.value); 1544 1545 // We only offer this code action between `let` and `=`, because 1546 // otherwise it could lead to confusing behaviour if inside a block 1547 // which is part of a let binding. 1548 let pattern_location = assignment.pattern.location(); 1549 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 1550 let code_action_range = self.edits.src_span_to_lsp_range(location); 1551 1552 // Only offer the code action if the cursor is over the statement 1553 if !overlaps(code_action_range, self.params.range) { 1554 return; 1555 } 1556 1557 // We don't need to add an annotation if there already is one 1558 if assignment.annotation.is_some() { 1559 return; 1560 } 1561 1562 // Various expressions such as pipelines and `use` expressions generate assignments 1563 // internally. However, these cannot be annotated and so we don't offer a code action here. 1564 if matches!(assignment.kind, AssignmentKind::Generated) { 1565 return; 1566 } 1567 1568 self.edits.insert( 1569 pattern_location.end, 1570 format!(": {}", self.printer.print_type(&assignment.type_())), 1571 ); 1572 } 1573 1574 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1575 // Since type variable names are local to definitions, any type variables 1576 // in other parts of the module shouldn't affect what we print for the 1577 // annotations of this constant. 1578 self.printer.clear_type_variables(); 1579 1580 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1581 1582 // Only offer the code action if the cursor is over the statement 1583 if !overlaps(code_action_range, self.params.range) { 1584 return; 1585 } 1586 1587 // We don't need to add an annotation if there already is one 1588 if constant.annotation.is_some() { 1589 return; 1590 } 1591 1592 self.edits.insert( 1593 constant.name_location.end, 1594 format!(": {}", self.printer.print_type(&constant.type_)), 1595 ); 1596 } 1597 1598 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 1599 // Since type variable names are local to definitions, any type variables 1600 // in other parts of the module shouldn't affect what we print for the 1601 // annotations of this functions. The only variables which cannot clash 1602 // are ones defined in the signature of this function, which we register 1603 // when we visit the parameters of this function inside `collect_type_variables`. 1604 self.printer.clear_type_variables(); 1605 collect_type_variables(&mut self.printer, fun); 1606 1607 ast::visit::visit_typed_function(self, fun); 1608 1609 let code_action_range = self.edits.src_span_to_lsp_range( 1610 fun.body_start 1611 .map(|body_start| SrcSpan { 1612 start: fun.location.start, 1613 end: body_start, 1614 }) 1615 .unwrap_or(fun.location), 1616 ); 1617 1618 // Only offer the code action if the cursor is over the statement 1619 if !overlaps(code_action_range, self.params.range) { 1620 return; 1621 } 1622 1623 // Annotate each argument separately 1624 for argument in fun.arguments.iter() { 1625 // Don't annotate the argument if it's already annotated 1626 if argument.annotation.is_some() { 1627 continue; 1628 } 1629 1630 self.edits.insert( 1631 argument.location.end, 1632 format!(": {}", self.printer.print_type(&argument.type_)), 1633 ); 1634 } 1635 1636 // Annotate the return type if it isn't already annotated 1637 if fun.return_annotation.is_none() { 1638 self.edits.insert( 1639 fun.location.end, 1640 format!(" -> {}", self.printer.print_type(&fun.return_type)), 1641 ); 1642 } 1643 } 1644 1645 fn visit_typed_expr_fn( 1646 &mut self, 1647 location: &'ast SrcSpan, 1648 type_: &'ast Arc<Type>, 1649 kind: &'ast FunctionLiteralKind, 1650 arguments: &'ast [TypedArg], 1651 body: &'ast Vec1<TypedStatement>, 1652 return_annotation: &'ast Option<ast::TypeAst>, 1653 ) { 1654 ast::visit::visit_typed_expr_fn( 1655 self, 1656 location, 1657 type_, 1658 kind, 1659 arguments, 1660 body, 1661 return_annotation, 1662 ); 1663 1664 // If the function doesn't have a head, we can't annotate it 1665 let location = match kind { 1666 // Function captures don't need any type annotations 1667 FunctionLiteralKind::Capture { .. } => return, 1668 FunctionLiteralKind::Anonymous { head, .. } => head, 1669 FunctionLiteralKind::Use { location } => location, 1670 }; 1671 1672 let code_action_range = self.edits.src_span_to_lsp_range(*location); 1673 1674 // Only offer the code action if the cursor is over the expression 1675 if !overlaps(code_action_range, self.params.range) { 1676 return; 1677 } 1678 1679 // Annotate each argument separately 1680 for argument in arguments.iter() { 1681 // Don't annotate the argument if it's already annotated 1682 if argument.annotation.is_some() { 1683 continue; 1684 } 1685 1686 self.edits.insert( 1687 argument.location.end, 1688 format!(": {}", self.printer.print_type(&argument.type_)), 1689 ); 1690 } 1691 1692 // Annotate the return type if it isn't already annotated, and this is 1693 // an anonymous function. 1694 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) { 1695 let return_type = &type_.return_type().expect("Type must be a function"); 1696 let pretty_type = self.printer.print_type(return_type); 1697 self.edits 1698 .insert(location.end, format!(" -> {pretty_type}")); 1699 } 1700 } 1701} 1702 1703impl<'a> AddAnnotations<'a> { 1704 pub fn new( 1705 module: &'a Module, 1706 line_numbers: &'a LineNumbers, 1707 params: &'a CodeActionParams, 1708 ) -> Self { 1709 Self { 1710 module, 1711 params, 1712 edits: TextEdits::new(line_numbers), 1713 // We need to use the same printer for all the edits because otherwise 1714 // we could get duplicate type variable names. 1715 printer: Printer::new_without_type_variables(&module.ast.names), 1716 } 1717 } 1718 1719 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) { 1720 self.visit_typed_module(&self.module.ast); 1721 1722 let uri = &self.params.text_document.uri; 1723 1724 let title = match self.edits.edits.len() { 1725 // We don't offer a code action if there is no action to perform 1726 0 => return, 1727 1 => "Add type annotation", 1728 _ => "Add type annotations", 1729 }; 1730 1731 CodeActionBuilder::new(title) 1732 .kind(CodeActionKind::Refactor) 1733 .changes(uri.clone(), self.edits.edits) 1734 .preferred(false) 1735 .push_to(actions); 1736 } 1737} 1738 1739/// Code action to add type annotations to all top level definitions 1740/// 1741pub struct AnnotateTopLevelDefinitions<'a> { 1742 module: &'a Module, 1743 params: &'a CodeActionParams, 1744 edits: TextEdits<'a>, 1745 is_hovering_definition_requiring_annotations: bool, 1746} 1747 1748impl<'a> AnnotateTopLevelDefinitions<'a> { 1749 pub fn new( 1750 module: &'a Module, 1751 line_numbers: &'a LineNumbers, 1752 params: &'a CodeActionParams, 1753 ) -> Self { 1754 Self { 1755 module, 1756 params, 1757 edits: TextEdits::new(line_numbers), 1758 is_hovering_definition_requiring_annotations: false, 1759 } 1760 } 1761 1762 pub fn code_actions(mut self) -> Vec<CodeAction> { 1763 self.visit_typed_module(&self.module.ast); 1764 1765 // We only want to trigger the action if we're over one of the definition 1766 // which is lacking some annotations in the module 1767 if !self.is_hovering_definition_requiring_annotations { 1768 return vec![]; 1769 } 1770 1771 let mut action = Vec::with_capacity(1); 1772 CodeActionBuilder::new("Annotate all top level definitions") 1773 .kind(CodeActionKind::RefactorRewrite) 1774 .changes(self.params.text_document.uri.clone(), self.edits.edits) 1775 .preferred(false) 1776 .push_to(&mut action); 1777 action 1778 } 1779} 1780 1781impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> { 1782 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1783 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1784 1785 // We don't need to add an annotation if there already is one 1786 if constant.annotation.is_some() { 1787 return; 1788 } 1789 1790 // We're hovering definition which needs some annotations 1791 if overlaps(code_action_range, self.params.range) { 1792 self.is_hovering_definition_requiring_annotations = true; 1793 } 1794 1795 self.edits.insert( 1796 constant.name_location.end, 1797 format!( 1798 ": {}", 1799 // Create new printer to ignore type variables from other definitions 1800 Printer::new_without_type_variables(&self.module.ast.names) 1801 .print_type(&constant.type_) 1802 ), 1803 ); 1804 } 1805 1806 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 1807 // Don't annotate already annotated arguments 1808 let arguments_to_annotate = fun 1809 .arguments 1810 .iter() 1811 .filter(|argument| argument.annotation.is_none()) 1812 .collect::<Vec<_>>(); 1813 let needs_return_annotation = fun.return_annotation.is_none(); 1814 1815 if arguments_to_annotate.is_empty() && !needs_return_annotation { 1816 return; 1817 } 1818 1819 let code_action_range = self.edits.src_span_to_lsp_range(fun.location); 1820 if overlaps(code_action_range, self.params.range) { 1821 self.is_hovering_definition_requiring_annotations = true; 1822 } 1823 1824 // Create new printer to ignore type variables from other definitions 1825 let mut printer = Printer::new_without_type_variables(&self.module.ast.names); 1826 collect_type_variables(&mut printer, fun); 1827 1828 // Annotate each argument separately 1829 for argument in arguments_to_annotate { 1830 self.edits.insert( 1831 argument.location.end, 1832 format!(": {}", printer.print_type(&argument.type_)), 1833 ); 1834 } 1835 1836 // Annotate the return type if it isn't already annotated 1837 if needs_return_annotation { 1838 self.edits.insert( 1839 fun.location.end, 1840 format!(" -> {}", printer.print_type(&fun.return_type)), 1841 ); 1842 } 1843 } 1844} 1845 1846struct TypeVariableCollector<'a, 'b> { 1847 printer: &'a mut Printer<'b>, 1848} 1849 1850/// Collect type variables defined within a function and register them for a 1851/// `Printer` 1852fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) { 1853 TypeVariableCollector { printer }.visit_typed_function(function); 1854} 1855 1856impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> { 1857 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) { 1858 // Register this type variable so that we don't duplicate names when 1859 // adding annotations. 1860 self.printer.register_type_variable(name.clone()); 1861 } 1862} 1863 1864pub struct QualifiedConstructor<'a> { 1865 import: &'a Import<EcoString>, 1866 used_name: EcoString, 1867 constructor: EcoString, 1868 layer: ast::Layer, 1869} 1870 1871impl QualifiedConstructor<'_> { 1872 fn constructor_import(&self) -> String { 1873 if self.layer.is_value() { 1874 self.constructor.to_string() 1875 } else { 1876 format!("type {}", self.constructor) 1877 } 1878 } 1879} 1880 1881pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> { 1882 module: &'a Module, 1883 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 1884 params: &'a CodeActionParams, 1885 line_numbers: &'a LineNumbers, 1886 qualified_constructor: Option<QualifiedConstructor<'a>>, 1887} 1888 1889impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> { 1890 fn new( 1891 module: &'a Module, 1892 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 1893 params: &'a CodeActionParams, 1894 line_numbers: &'a LineNumbers, 1895 ) -> Self { 1896 Self { 1897 module, 1898 compiler, 1899 params, 1900 line_numbers, 1901 qualified_constructor: None, 1902 } 1903 } 1904 1905 fn get_module_import( 1906 &self, 1907 module_name: &EcoString, 1908 constructor: &EcoString, 1909 layer: ast::Layer, 1910 ) -> Option<&'a Import<EcoString>> { 1911 let mut matching_import = None; 1912 1913 for import in &self.module.ast.definitions.imports { 1914 if import.used_name().as_deref() == Some(module_name) 1915 && let Some(module) = self.compiler.get_module_interface(&import.module) 1916 { 1917 // If the import is the one we're referring to, we see if the 1918 // referred module exports the type/value we are trying to 1919 // unqualify: we don't want to offer the action indiscriminately if 1920 // it would generate invalid code! 1921 let module_exports_constructor = match layer { 1922 ast::Layer::Value => module.get_importable_value(constructor).is_some(), 1923 ast::Layer::Type => module.get_importable_type(constructor).is_some(), 1924 }; 1925 if module_exports_constructor { 1926 matching_import = Some(import); 1927 } 1928 } else { 1929 // If the import refers to another module we still want to check 1930 // if in its unqualified import list there is a name that's equal 1931 // to the one we're trying to unqualify. In this case we can't 1932 // offer the action as it would generate invalid code. 1933 // 1934 // For example: 1935 // ```gleam 1936 // import wibble.{Some} 1937 // import option 1938 // 1939 // pub fn something() { 1940 // option.Some(1) 1941 // ^^^^ We can't unqualify this because `Some` is already 1942 // imported unqualified from the `wibble` module 1943 // } 1944 // ``` 1945 // 1946 let imported = match layer { 1947 ast::Layer::Value => &import.unqualified_values, 1948 ast::Layer::Type => &import.unqualified_types, 1949 }; 1950 let constructor_already_imported_by_other_module = imported 1951 .iter() 1952 .any(|value| value.used_name() == constructor); 1953 1954 if constructor_already_imported_by_other_module { 1955 return None; 1956 } 1957 } 1958 } 1959 1960 matching_import 1961 } 1962} 1963 1964impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> { 1965 fn visit_type_ast_constructor( 1966 &mut self, 1967 location: &'ast SrcSpan, 1968 name: &'ast TypeAstConstructorName, 1969 arguments: &'ast [ast::TypeAst], 1970 arguments_types: Option<Vec<Arc<Type>>>, 1971 ) { 1972 let range = src_span_to_lsp_range(*location, self.line_numbers); 1973 if within(self.params.range, range) 1974 && let Some(module_alias) = name.module_name() 1975 && let Some(name) = name.name() 1976 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type) 1977 { 1978 self.qualified_constructor = Some(QualifiedConstructor { 1979 import, 1980 used_name: module_alias.clone(), 1981 constructor: name.clone(), 1982 layer: ast::Layer::Type, 1983 }); 1984 } 1985 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 1986 } 1987 1988 fn visit_typed_expr_module_select( 1989 &mut self, 1990 location: &'ast SrcSpan, 1991 field_start: &'ast u32, 1992 type_: &'ast Arc<Type>, 1993 label: &'ast EcoString, 1994 module_name: &'ast EcoString, 1995 module_alias: &'ast EcoString, 1996 constructor: &'ast ModuleValueConstructor, 1997 ) { 1998 // When hovering over a Record Value Constructor, we want to expand the source span to 1999 // include the module name: 2000 // option.Some 2001 // ↑ 2002 // This allows us to offer a code action when hovering over the module name. 2003 let range = src_span_to_lsp_range(*location, self.line_numbers); 2004 if within(self.params.range, range) 2005 && let ModuleValueConstructor::Record { 2006 name: constructor_name, 2007 .. 2008 } = constructor 2009 && let Some(import) = 2010 self.get_module_import(module_alias, constructor_name, ast::Layer::Value) 2011 { 2012 self.qualified_constructor = Some(QualifiedConstructor { 2013 import, 2014 used_name: module_alias.clone(), 2015 constructor: constructor_name.clone(), 2016 layer: ast::Layer::Value, 2017 }); 2018 } 2019 ast::visit::visit_typed_expr_module_select( 2020 self, 2021 location, 2022 field_start, 2023 type_, 2024 label, 2025 module_name, 2026 module_alias, 2027 constructor, 2028 ); 2029 } 2030 2031 fn visit_typed_pattern_constructor( 2032 &mut self, 2033 location: &'ast SrcSpan, 2034 name_location: &'ast SrcSpan, 2035 name: &'ast EcoString, 2036 arguments: &'ast Vec<CallArg<TypedPattern>>, 2037 module: &'ast Option<(EcoString, SrcSpan)>, 2038 constructor: &'ast Inferred<type_::PatternConstructor>, 2039 spread: &'ast Option<SrcSpan>, 2040 type_: &'ast Arc<Type>, 2041 ) { 2042 let range = src_span_to_lsp_range(*location, self.line_numbers); 2043 if within(self.params.range, range) 2044 && let Some((module_alias, _)) = module 2045 && let Inferred::Known(_) = constructor 2046 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 2047 { 2048 self.qualified_constructor = Some(QualifiedConstructor { 2049 import, 2050 used_name: module_alias.clone(), 2051 constructor: name.clone(), 2052 layer: ast::Layer::Value, 2053 }); 2054 } 2055 ast::visit::visit_typed_pattern_constructor( 2056 self, 2057 location, 2058 name_location, 2059 name, 2060 arguments, 2061 module, 2062 constructor, 2063 spread, 2064 type_, 2065 ); 2066 } 2067 2068 fn visit_typed_constant_record( 2069 &mut self, 2070 location: &'ast SrcSpan, 2071 module: &'ast Option<(EcoString, SrcSpan)>, 2072 name: &'ast EcoString, 2073 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2074 type_: &'ast Arc<Type>, 2075 field_map: &'ast Inferred<FieldMap>, 2076 record_constructor: &'ast Option<Box<ValueConstructor>>, 2077 ) { 2078 let range = src_span_to_lsp_range(*location, self.line_numbers); 2079 if within(self.params.range, range) 2080 && let Some((module_alias, _)) = module 2081 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 2082 { 2083 self.qualified_constructor = Some(QualifiedConstructor { 2084 import, 2085 used_name: module_alias.clone(), 2086 constructor: name.clone(), 2087 layer: ast::Layer::Value, 2088 }); 2089 } 2090 ast::visit::visit_typed_constant_record( 2091 self, 2092 location, 2093 module, 2094 name, 2095 arguments, 2096 type_, 2097 field_map, 2098 record_constructor, 2099 ); 2100 } 2101 2102 fn visit_typed_constant_var( 2103 &mut self, 2104 location: &'ast SrcSpan, 2105 module: &'ast Option<(EcoString, SrcSpan)>, 2106 name: &'ast EcoString, 2107 constructor: &'ast Option<Box<ValueConstructor>>, 2108 type_: &'ast Arc<Type>, 2109 ) { 2110 let range = src_span_to_lsp_range(*location, self.line_numbers); 2111 if within(self.params.range, range) 2112 && let Some((module_alias, _)) = module 2113 && let Some(constructor) = constructor 2114 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant 2115 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 2116 { 2117 self.qualified_constructor = Some(QualifiedConstructor { 2118 import, 2119 used_name: module_alias.clone(), 2120 constructor: name.clone(), 2121 layer: ast::Layer::Value, 2122 }); 2123 } 2124 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2125 } 2126} 2127 2128pub struct QualifiedToUnqualifiedImportSecondPass<'a> { 2129 module: &'a Module, 2130 params: &'a CodeActionParams, 2131 edits: TextEdits<'a>, 2132 qualified_constructor: QualifiedConstructor<'a>, 2133} 2134 2135impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> { 2136 pub fn new( 2137 module: &'a Module, 2138 params: &'a CodeActionParams, 2139 line_numbers: &'a LineNumbers, 2140 qualified_constructor: QualifiedConstructor<'a>, 2141 ) -> Self { 2142 Self { 2143 module, 2144 params, 2145 edits: TextEdits::new(line_numbers), 2146 qualified_constructor, 2147 } 2148 } 2149 2150 pub fn code_actions(mut self) -> Vec<CodeAction> { 2151 self.visit_typed_module(&self.module.ast); 2152 if self.edits.edits.is_empty() { 2153 return vec![]; 2154 } 2155 self.edit_import(); 2156 let mut action = Vec::with_capacity(1); 2157 CodeActionBuilder::new(&format!( 2158 "Unqualify {}.{}", 2159 self.qualified_constructor.used_name, self.qualified_constructor.constructor 2160 )) 2161 .kind(CodeActionKind::Refactor) 2162 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2163 .preferred(false) 2164 .push_to(&mut action); 2165 action 2166 } 2167 2168 fn remove_module_qualifier(&mut self, location: SrcSpan) { 2169 self.edits.delete(SrcSpan { 2170 start: location.start, 2171 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus . 2172 }); 2173 } 2174 2175 fn edit_import(&mut self) { 2176 let QualifiedConstructor { 2177 constructor, 2178 layer, 2179 import, 2180 .. 2181 } = &self.qualified_constructor; 2182 let is_imported = if layer.is_value() { 2183 import 2184 .unqualified_values 2185 .iter() 2186 .any(|value| value.used_name() == constructor) 2187 } else { 2188 import 2189 .unqualified_types 2190 .iter() 2191 .any(|type_| type_.used_name() == constructor) 2192 }; 2193 if is_imported { 2194 return; 2195 } 2196 let (insert_pos, new_text) = edits::insert_unqualified_import( 2197 import, 2198 &self.module.code, 2199 self.qualified_constructor.constructor_import(), 2200 ); 2201 let span = SrcSpan::new(insert_pos, insert_pos); 2202 self.edits.replace(span, new_text); 2203 } 2204} 2205 2206impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> { 2207 fn visit_type_ast_constructor( 2208 &mut self, 2209 location: &'ast SrcSpan, 2210 name: &'ast TypeAstConstructorName, 2211 arguments: &'ast [ast::TypeAst], 2212 arguments_types: Option<Vec<Arc<Type>>>, 2213 ) { 2214 if let Some(module_name) = name.module_name() 2215 && let Some(name) = name.name() 2216 { 2217 let QualifiedConstructor { 2218 used_name, 2219 constructor, 2220 layer, 2221 .. 2222 } = &self.qualified_constructor; 2223 2224 if !layer.is_value() && used_name == module_name && name == constructor { 2225 self.remove_module_qualifier(*location); 2226 } 2227 } 2228 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2229 } 2230 2231 fn visit_typed_expr_module_select( 2232 &mut self, 2233 location: &'ast SrcSpan, 2234 field_start: &'ast u32, 2235 type_: &'ast Arc<Type>, 2236 label: &'ast EcoString, 2237 module_name: &'ast EcoString, 2238 module_alias: &'ast EcoString, 2239 constructor: &'ast ModuleValueConstructor, 2240 ) { 2241 if let ModuleValueConstructor::Record { name, .. } = constructor { 2242 let QualifiedConstructor { 2243 used_name, 2244 constructor, 2245 layer, 2246 .. 2247 } = &self.qualified_constructor; 2248 2249 if layer.is_value() && used_name == module_alias && name == constructor { 2250 self.remove_module_qualifier(*location); 2251 } 2252 } 2253 ast::visit::visit_typed_expr_module_select( 2254 self, 2255 location, 2256 field_start, 2257 type_, 2258 label, 2259 module_name, 2260 module_alias, 2261 constructor, 2262 ); 2263 } 2264 2265 fn visit_typed_pattern_constructor( 2266 &mut self, 2267 location: &'ast SrcSpan, 2268 name_location: &'ast SrcSpan, 2269 name: &'ast EcoString, 2270 arguments: &'ast Vec<CallArg<TypedPattern>>, 2271 module: &'ast Option<(EcoString, SrcSpan)>, 2272 constructor: &'ast Inferred<type_::PatternConstructor>, 2273 spread: &'ast Option<SrcSpan>, 2274 type_: &'ast Arc<Type>, 2275 ) { 2276 if let Some((module_alias, _)) = module 2277 && let Inferred::Known(_) = constructor 2278 { 2279 let QualifiedConstructor { 2280 used_name, 2281 constructor, 2282 layer, 2283 .. 2284 } = &self.qualified_constructor; 2285 2286 if layer.is_value() && used_name == module_alias && name == constructor { 2287 self.remove_module_qualifier(*location); 2288 } 2289 } 2290 ast::visit::visit_typed_pattern_constructor( 2291 self, 2292 location, 2293 name_location, 2294 name, 2295 arguments, 2296 module, 2297 constructor, 2298 spread, 2299 type_, 2300 ); 2301 } 2302 2303 fn visit_typed_constant_record( 2304 &mut self, 2305 location: &'ast SrcSpan, 2306 module: &'ast Option<(EcoString, SrcSpan)>, 2307 name: &'ast EcoString, 2308 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2309 type_: &'ast Arc<Type>, 2310 field_map: &'ast Inferred<FieldMap>, 2311 record_constructor: &'ast Option<Box<ValueConstructor>>, 2312 ) { 2313 if let Some((module_alias, _)) = module { 2314 let QualifiedConstructor { 2315 used_name, 2316 constructor, 2317 layer, 2318 .. 2319 } = &self.qualified_constructor; 2320 2321 if layer.is_value() && used_name == module_alias && name == constructor { 2322 self.remove_module_qualifier(*location); 2323 } 2324 } 2325 ast::visit::visit_typed_constant_record( 2326 self, 2327 location, 2328 module, 2329 name, 2330 arguments, 2331 type_, 2332 field_map, 2333 record_constructor, 2334 ); 2335 } 2336 2337 fn visit_typed_constant_var( 2338 &mut self, 2339 location: &'ast SrcSpan, 2340 module: &'ast Option<(EcoString, SrcSpan)>, 2341 name: &'ast EcoString, 2342 constructor: &'ast Option<Box<ValueConstructor>>, 2343 type_: &'ast Arc<Type>, 2344 ) { 2345 if let Some((module_alias, _)) = module { 2346 let QualifiedConstructor { 2347 used_name, 2348 constructor: wanted_constructor, 2349 layer, 2350 .. 2351 } = &self.qualified_constructor; 2352 2353 if layer.is_value() && used_name == module_alias && name == wanted_constructor { 2354 self.remove_module_qualifier(*location); 2355 } 2356 } 2357 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2358 } 2359} 2360 2361pub fn code_action_convert_qualified_constructor_to_unqualified<IO>( 2362 module: &Module, 2363 compiler: &LspProjectCompiler<FileSystemProxy<IO>>, 2364 line_numbers: &LineNumbers, 2365 params: &CodeActionParams, 2366 actions: &mut Vec<CodeAction>, 2367) { 2368 let mut first_pass = 2369 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers); 2370 first_pass.visit_typed_module(&module.ast); 2371 let Some(qualified_constructor) = first_pass.qualified_constructor else { 2372 return; 2373 }; 2374 let second_pass = QualifiedToUnqualifiedImportSecondPass::new( 2375 module, 2376 params, 2377 line_numbers, 2378 qualified_constructor, 2379 ); 2380 let new_actions = second_pass.code_actions(); 2381 actions.extend(new_actions); 2382} 2383 2384struct UnqualifiedConstructor<'a> { 2385 module_name: EcoString, 2386 constructor: &'a ast::UnqualifiedImport, 2387 layer: ast::Layer, 2388} 2389 2390struct UnqualifiedToQualifiedImportFirstPass<'a> { 2391 module: &'a Module, 2392 params: &'a CodeActionParams, 2393 line_numbers: &'a LineNumbers, 2394 unqualified_constructor: Option<UnqualifiedConstructor<'a>>, 2395} 2396 2397impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> { 2398 fn new( 2399 module: &'a Module, 2400 params: &'a CodeActionParams, 2401 line_numbers: &'a LineNumbers, 2402 ) -> Self { 2403 Self { 2404 module, 2405 params, 2406 line_numbers, 2407 unqualified_constructor: None, 2408 } 2409 } 2410 2411 fn get_module_import_from_value_constructor( 2412 &mut self, 2413 module_name: &EcoString, 2414 constructor_name: &EcoString, 2415 ) { 2416 self.unqualified_constructor = self 2417 .module 2418 .ast 2419 .definitions 2420 .imports 2421 .iter() 2422 .filter(|import| import.module == *module_name) 2423 .find_map(|import| { 2424 import 2425 .unqualified_values 2426 .iter() 2427 .find(|value| value.used_name() == constructor_name) 2428 .and_then(|value| { 2429 Some(UnqualifiedConstructor { 2430 constructor: value, 2431 module_name: import.used_name()?, 2432 layer: ast::Layer::Value, 2433 }) 2434 }) 2435 }); 2436 } 2437 2438 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) { 2439 self.unqualified_constructor = 2440 self.module 2441 .ast 2442 .definitions 2443 .imports 2444 .iter() 2445 .find_map(|import| { 2446 if let Some(ty) = import 2447 .unqualified_types 2448 .iter() 2449 .find(|ty| ty.used_name() == constructor_name) 2450 { 2451 return Some(UnqualifiedConstructor { 2452 constructor: ty, 2453 module_name: import.used_name()?, 2454 layer: ast::Layer::Type, 2455 }); 2456 } 2457 None 2458 }); 2459 } 2460} 2461 2462impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> { 2463 fn visit_type_ast_constructor( 2464 &mut self, 2465 location: &'ast SrcSpan, 2466 name: &'ast TypeAstConstructorName, 2467 arguments: &'ast [ast::TypeAst], 2468 arguments_types: Option<Vec<Arc<Type>>>, 2469 ) { 2470 if !name.is_qualified() 2471 && let Some(name) = name.name() 2472 && within( 2473 self.params.range, 2474 src_span_to_lsp_range(*location, self.line_numbers), 2475 ) 2476 { 2477 self.get_module_import_from_type_constructor(name); 2478 } 2479 2480 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2481 } 2482 2483 fn visit_typed_expr_var( 2484 &mut self, 2485 location: &'ast SrcSpan, 2486 constructor: &'ast ValueConstructor, 2487 name: &'ast EcoString, 2488 ) { 2489 let range = src_span_to_lsp_range(*location, self.line_numbers); 2490 if within(self.params.range, range) 2491 && let Some(module_name) = match &constructor.variant { 2492 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2493 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2494 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2495 2496 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2497 } 2498 { 2499 self.get_module_import_from_value_constructor(module_name, name); 2500 } 2501 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2502 } 2503 2504 fn visit_typed_pattern_constructor( 2505 &mut self, 2506 location: &'ast SrcSpan, 2507 name_location: &'ast SrcSpan, 2508 name: &'ast EcoString, 2509 arguments: &'ast Vec<CallArg<TypedPattern>>, 2510 module: &'ast Option<(EcoString, SrcSpan)>, 2511 constructor: &'ast Inferred<type_::PatternConstructor>, 2512 spread: &'ast Option<SrcSpan>, 2513 type_: &'ast Arc<Type>, 2514 ) { 2515 if module.is_none() 2516 && within( 2517 self.params.range, 2518 src_span_to_lsp_range(*location, self.line_numbers), 2519 ) 2520 && let Inferred::Known(constructor) = constructor 2521 { 2522 self.get_module_import_from_value_constructor(&constructor.module, name); 2523 } 2524 2525 ast::visit::visit_typed_pattern_constructor( 2526 self, 2527 location, 2528 name_location, 2529 name, 2530 arguments, 2531 module, 2532 constructor, 2533 spread, 2534 type_, 2535 ); 2536 } 2537 2538 fn visit_typed_constant_record( 2539 &mut self, 2540 location: &'ast SrcSpan, 2541 module: &'ast Option<(EcoString, SrcSpan)>, 2542 name: &'ast EcoString, 2543 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2544 type_: &'ast Arc<Type>, 2545 field_map: &'ast Inferred<FieldMap>, 2546 record_constructor: &'ast Option<Box<ValueConstructor>>, 2547 ) { 2548 if module.is_none() 2549 && within( 2550 self.params.range, 2551 src_span_to_lsp_range(*location, self.line_numbers), 2552 ) 2553 && let Some(record_constructor) = record_constructor 2554 && let Some(module_name) = match &record_constructor.variant { 2555 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2556 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2557 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2558 2559 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2560 } 2561 { 2562 self.get_module_import_from_value_constructor(module_name, name); 2563 } 2564 ast::visit::visit_typed_constant_record( 2565 self, 2566 location, 2567 module, 2568 name, 2569 arguments, 2570 type_, 2571 field_map, 2572 record_constructor, 2573 ); 2574 } 2575 2576 fn visit_typed_constant_var( 2577 &mut self, 2578 location: &'ast SrcSpan, 2579 module: &'ast Option<(EcoString, SrcSpan)>, 2580 name: &'ast EcoString, 2581 constructor: &'ast Option<Box<ValueConstructor>>, 2582 type_: &'ast Arc<Type>, 2583 ) { 2584 if module.is_none() 2585 && within( 2586 self.params.range, 2587 src_span_to_lsp_range(*location, self.line_numbers), 2588 ) 2589 && let Some(constructor) = constructor 2590 && let Some(module_name) = match &constructor.variant { 2591 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2592 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2593 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2594 2595 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2596 } 2597 { 2598 self.get_module_import_from_value_constructor(module_name, name); 2599 } 2600 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2601 } 2602} 2603 2604struct UnqualifiedToQualifiedImportSecondPass<'a> { 2605 module: &'a Module, 2606 params: &'a CodeActionParams, 2607 edits: TextEdits<'a>, 2608 unqualified_constructor: UnqualifiedConstructor<'a>, 2609} 2610 2611impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> { 2612 pub fn new( 2613 module: &'a Module, 2614 params: &'a CodeActionParams, 2615 line_numbers: &'a LineNumbers, 2616 unqualified_constructor: UnqualifiedConstructor<'a>, 2617 ) -> Self { 2618 Self { 2619 module, 2620 params, 2621 edits: TextEdits::new(line_numbers), 2622 unqualified_constructor, 2623 } 2624 } 2625 2626 fn add_module_qualifier(&mut self, location: SrcSpan) { 2627 let src_span = SrcSpan::new( 2628 location.start, 2629 location.start + self.unqualified_constructor.constructor.used_name().len() as u32, 2630 ); 2631 2632 self.edits.replace( 2633 src_span, 2634 format!( 2635 "{}.{}", 2636 self.unqualified_constructor.module_name, 2637 self.unqualified_constructor.constructor.name 2638 ), 2639 ); 2640 } 2641 2642 pub fn code_actions(mut self) -> Vec<CodeAction> { 2643 self.visit_typed_module(&self.module.ast); 2644 if self.edits.edits.is_empty() { 2645 return vec![]; 2646 } 2647 self.edit_import(); 2648 let mut action = Vec::with_capacity(1); 2649 let UnqualifiedConstructor { 2650 module_name, 2651 constructor, 2652 .. 2653 } = self.unqualified_constructor; 2654 CodeActionBuilder::new(&format!( 2655 "Qualify {} as {}.{}", 2656 constructor.used_name(), 2657 module_name, 2658 constructor.name, 2659 )) 2660 .kind(CodeActionKind::Refactor) 2661 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2662 .preferred(false) 2663 .push_to(&mut action); 2664 action 2665 } 2666 2667 fn edit_import(&mut self) { 2668 let UnqualifiedConstructor { 2669 constructor: 2670 ast::UnqualifiedImport { 2671 location: constructor_import_span, 2672 .. 2673 }, 2674 .. 2675 } = self.unqualified_constructor; 2676 2677 let mut last_char_pos = constructor_import_span.end as usize; 2678 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2679 last_char_pos += 1; 2680 } 2681 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") { 2682 last_char_pos += 1; 2683 } 2684 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2685 last_char_pos += 1; 2686 } 2687 2688 self.edits.delete(SrcSpan::new( 2689 constructor_import_span.start, 2690 last_char_pos as u32, 2691 )); 2692 } 2693} 2694 2695impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> { 2696 fn visit_type_ast_constructor( 2697 &mut self, 2698 location: &'ast SrcSpan, 2699 name: &'ast TypeAstConstructorName, 2700 arguments: &'ast [ast::TypeAst], 2701 arguments_types: Option<Vec<Arc<Type>>>, 2702 ) { 2703 if !name.is_qualified() 2704 && let Some(name) = name.name() 2705 { 2706 let UnqualifiedConstructor { 2707 constructor, layer, .. 2708 } = &self.unqualified_constructor; 2709 if !layer.is_value() && constructor.used_name() == name { 2710 self.add_module_qualifier(*location); 2711 } 2712 } 2713 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2714 } 2715 2716 fn visit_typed_expr_var( 2717 &mut self, 2718 location: &'ast SrcSpan, 2719 constructor: &'ast ValueConstructor, 2720 name: &'ast EcoString, 2721 ) { 2722 let UnqualifiedConstructor { 2723 constructor: wanted_constructor, 2724 layer, 2725 .. 2726 } = &self.unqualified_constructor; 2727 2728 if layer.is_value() 2729 && wanted_constructor.used_name() == name 2730 && !constructor.is_local_variable() 2731 { 2732 self.add_module_qualifier(*location); 2733 } 2734 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2735 } 2736 2737 fn visit_typed_pattern_constructor( 2738 &mut self, 2739 location: &'ast SrcSpan, 2740 name_location: &'ast SrcSpan, 2741 name: &'ast EcoString, 2742 arguments: &'ast Vec<CallArg<TypedPattern>>, 2743 module: &'ast Option<(EcoString, SrcSpan)>, 2744 constructor: &'ast Inferred<type_::PatternConstructor>, 2745 spread: &'ast Option<SrcSpan>, 2746 type_: &'ast Arc<Type>, 2747 ) { 2748 if module.is_none() { 2749 let UnqualifiedConstructor { 2750 constructor: wanted_constructor, 2751 layer, 2752 .. 2753 } = &self.unqualified_constructor; 2754 if layer.is_value() && wanted_constructor.used_name() == name { 2755 self.add_module_qualifier(*location); 2756 } 2757 } 2758 ast::visit::visit_typed_pattern_constructor( 2759 self, 2760 location, 2761 name_location, 2762 name, 2763 arguments, 2764 module, 2765 constructor, 2766 spread, 2767 type_, 2768 ); 2769 } 2770 2771 fn visit_typed_constant_record( 2772 &mut self, 2773 location: &'ast SrcSpan, 2774 module: &'ast Option<(EcoString, SrcSpan)>, 2775 name: &'ast EcoString, 2776 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2777 type_: &'ast Arc<Type>, 2778 field_map: &'ast Inferred<FieldMap>, 2779 record_constructor: &'ast Option<Box<ValueConstructor>>, 2780 ) { 2781 if module.is_none() { 2782 let UnqualifiedConstructor { 2783 constructor: wanted_constructor, 2784 layer, 2785 .. 2786 } = &self.unqualified_constructor; 2787 if layer.is_value() && wanted_constructor.used_name() == name { 2788 self.add_module_qualifier(*location); 2789 } 2790 } 2791 ast::visit::visit_typed_constant_record( 2792 self, 2793 location, 2794 module, 2795 name, 2796 arguments, 2797 type_, 2798 field_map, 2799 record_constructor, 2800 ); 2801 } 2802 2803 fn visit_typed_constant_var( 2804 &mut self, 2805 location: &'ast SrcSpan, 2806 module: &'ast Option<(EcoString, SrcSpan)>, 2807 name: &'ast EcoString, 2808 constructor: &'ast Option<Box<ValueConstructor>>, 2809 type_: &'ast Arc<Type>, 2810 ) { 2811 if module.is_none() { 2812 let UnqualifiedConstructor { 2813 constructor: wanted_constructor, 2814 layer, 2815 .. 2816 } = &self.unqualified_constructor; 2817 if layer.is_value() && wanted_constructor.used_name() == name { 2818 self.add_module_qualifier(*location); 2819 } 2820 } 2821 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2822 } 2823} 2824 2825pub fn code_action_convert_unqualified_constructor_to_qualified( 2826 module: &Module, 2827 line_numbers: &LineNumbers, 2828 params: &CodeActionParams, 2829 actions: &mut Vec<CodeAction>, 2830) { 2831 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers); 2832 first_pass.visit_typed_module(&module.ast); 2833 let Some(unqualified_constructor) = first_pass.unqualified_constructor else { 2834 return; 2835 }; 2836 let second_pass = UnqualifiedToQualifiedImportSecondPass::new( 2837 module, 2838 params, 2839 line_numbers, 2840 unqualified_constructor, 2841 ); 2842 let new_actions = second_pass.code_actions(); 2843 actions.extend(new_actions); 2844} 2845 2846/// Builder for code action to apply the convert from use action, turning a use 2847/// expression into a regular function call. 2848/// 2849pub struct ConvertFromUse<'a> { 2850 module: &'a Module, 2851 params: &'a CodeActionParams, 2852 edits: TextEdits<'a>, 2853 selected_use: Option<&'a TypedUse>, 2854} 2855 2856impl<'a> ConvertFromUse<'a> { 2857 pub fn new( 2858 module: &'a Module, 2859 line_numbers: &'a LineNumbers, 2860 params: &'a CodeActionParams, 2861 ) -> Self { 2862 Self { 2863 module, 2864 params, 2865 edits: TextEdits::new(line_numbers), 2866 selected_use: None, 2867 } 2868 } 2869 2870 pub fn code_actions(mut self) -> Vec<CodeAction> { 2871 self.visit_typed_module(&self.module.ast); 2872 2873 let Some(use_) = self.selected_use else { 2874 return vec![]; 2875 }; 2876 2877 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else { 2878 return vec![]; 2879 }; 2880 2881 // If the use callback we're desugaring is using labels, that means we 2882 // have to add the last argument's label when writing the callback; 2883 // otherwise, it would result in invalid code. 2884 // 2885 // use acc, item <- list.fold(over: list, from: 1) 2886 // todo 2887 // 2888 // Needs to be rewritten as: 2889 // 2890 // list.fold(over: list, from: 1, with: fn(acc, item) { ... }) 2891 // ^^^^^ We cannot forget to add this label back! 2892 // 2893 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) { 2894 fun.field_map() 2895 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned()) 2896 .map(|label| eco_format!("{label}: ")) 2897 .unwrap_or(EcoString::from("")) 2898 } else { 2899 EcoString::from("") 2900 }; 2901 2902 // The use callback is not necessarily the last argument. If you have 2903 // the following function: `wibble(a a, b b) { todo }` 2904 // And use it like this: `use <- wibble(b: 1)`, the first argument `a` 2905 // is going to be the use callback, not the last one! 2906 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback()); 2907 let Some(CallArg { 2908 implicit: Some(ImplicitCallArgOrigin::Use), 2909 value: TypedExpr::Fn { body, type_, .. }, 2910 .. 2911 }) = use_callback 2912 else { 2913 return vec![]; 2914 }; 2915 2916 // If there's arguments on the left hand side of the function we extract 2917 // those so we can paste them back as the anonymous function arguments. 2918 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) { 2919 let assignments_range = 2920 use_.assignments_location.start as usize..use_.assignments_location.end as usize; 2921 self.module 2922 .code 2923 .get(assignments_range) 2924 .expect("use assignments") 2925 } else { 2926 "" 2927 }; 2928 2929 // We first delete everything on the left hand side of use and the use 2930 // arrow. 2931 self.edits.delete(SrcSpan { 2932 start: use_.location.start, 2933 end: use_.right_hand_side_location.start, 2934 }); 2935 2936 let use_line_end = use_.right_hand_side_location.end; 2937 let use_rhs_function_has_some_explicit_arguments = arguments 2938 .iter() 2939 .filter(|argument| !argument.is_use_implicit_callback()) 2940 .peekable() 2941 .peek() 2942 .is_some(); 2943 2944 let use_rhs_function_ends_with_closed_parentheses = self 2945 .module 2946 .code 2947 .get(use_line_end as usize - 1..use_line_end as usize) 2948 == Some(")"); 2949 2950 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit()); 2951 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end); 2952 2953 // This is the piece of code between the end of the last argument and 2954 // the end of the use_expression: 2955 // 2956 // use <- wibble(a, b, ) 2957 // ^^^^^ This piece right here, from `,` included 2958 // up to `)` excluded. 2959 // 2960 let text_after_last_argument = self 2961 .module 2962 .code 2963 .get(last_arg_end as usize..use_line_end as usize - 1); 2964 let use_rhs_has_comma_after_last_argument = 2965 text_after_last_argument.is_some_and(|code| code.contains(',')); 2966 let needs_space_before_callback = 2967 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' ')); 2968 2969 if use_rhs_function_ends_with_closed_parentheses { 2970 // If the function on the right hand side of use ends with a closed 2971 // parentheses then we have to remove it and add it later at the end 2972 // of the anonymous function we're inserting. 2973 // 2974 // use <- wibble() 2975 // ^ To add the fn() we need to first remove this 2976 // 2977 // So here we write over the last closed parentheses to remove it. 2978 let callback_start = format!("{callback_label}fn({assignments}) {{"); 2979 self.edits.replace( 2980 SrcSpan { 2981 start: use_line_end - 1, 2982 end: use_line_end, 2983 }, 2984 // If the function on the rhs of use has other orguments besides 2985 // the implicit fn expression then we need to put a comma after 2986 // the last argument. 2987 if use_rhs_function_has_some_explicit_arguments 2988 && !use_rhs_has_comma_after_last_argument 2989 { 2990 format!(", {callback_start}") 2991 } else if needs_space_before_callback { 2992 format!(" {callback_start}") 2993 } else { 2994 callback_start 2995 }, 2996 ); 2997 } else { 2998 // On the other hand, if the function on the right hand side doesn't 2999 // end with a closed parenthese then we have to manually add it. 3000 // 3001 // use <- wibble 3002 // ^ No parentheses 3003 // 3004 self.edits 3005 .insert(use_line_end, format!("(fn({assignments}) {{")); 3006 } 3007 3008 // Then we have to increase indentation for all the lines of the use 3009 // body. 3010 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location()); 3011 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location()); 3012 3013 for line in first_fn_expression_range.start.line..=use_body_range.end.line { 3014 self.edits.edits.push(TextEdit { 3015 range: Range { 3016 start: Position { line, character: 0 }, 3017 end: Position { line, character: 0 }, 3018 }, 3019 new_text: " ".to_string(), 3020 }); 3021 } 3022 3023 let final_line_indentation = " ".repeat(use_body_range.start.character as usize); 3024 self.edits.insert( 3025 use_.call.location().end, 3026 format!("\n{final_line_indentation}}})"), 3027 ); 3028 3029 let mut action = Vec::with_capacity(1); 3030 CodeActionBuilder::new("Convert from `use`") 3031 .kind(CodeActionKind::RefactorRewrite) 3032 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3033 .preferred(false) 3034 .push_to(&mut action); 3035 action 3036 } 3037} 3038 3039impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> { 3040 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3041 let use_range = self.edits.src_span_to_lsp_range(use_.location); 3042 3043 // If the use expression is using patterns that are not just variable 3044 // assignments then we can't automatically rewrite it as it would result 3045 // in a syntax error as we can't pattern match in an anonymous function 3046 // head. 3047 // At the same time we can't safely add bindings inside the anonymous 3048 // function body by picking placeholder names as we'd risk shadowing 3049 // variables coming from the outer scope. 3050 // So we just skip those use expressions we can't safely rewrite! 3051 if within(self.params.range, use_range) 3052 && use_ 3053 .assignments 3054 .iter() 3055 .all(|assignment| assignment.pattern.is_variable()) 3056 { 3057 self.selected_use = Some(use_); 3058 } 3059 3060 // We still want to visit the use expression so that we always end up 3061 // picking the innermost, most relevant use under the cursor. 3062 self.visit_typed_expr(&use_.call); 3063 } 3064} 3065 3066/// Builder for code action to apply the convert to use action. 3067/// 3068pub struct ConvertToUse<'a> { 3069 module: &'a Module, 3070 params: &'a CodeActionParams, 3071 edits: TextEdits<'a>, 3072 selected_call: Option<CallLocations>, 3073} 3074 3075/// All the locations we'll need to transform a function call into a use 3076/// expression. 3077/// 3078struct CallLocations { 3079 call_span: SrcSpan, 3080 called_function_span: SrcSpan, 3081 callback_arguments_span: Option<SrcSpan>, 3082 arg_before_callback_span: Option<SrcSpan>, 3083 callback_body_span: SrcSpan, 3084} 3085 3086impl<'a> ConvertToUse<'a> { 3087 pub fn new( 3088 module: &'a Module, 3089 line_numbers: &'a LineNumbers, 3090 params: &'a CodeActionParams, 3091 ) -> Self { 3092 Self { 3093 module, 3094 params, 3095 edits: TextEdits::new(line_numbers), 3096 selected_call: None, 3097 } 3098 } 3099 3100 pub fn code_actions(mut self) -> Vec<CodeAction> { 3101 self.visit_typed_module(&self.module.ast); 3102 3103 let Some(CallLocations { 3104 call_span, 3105 called_function_span, 3106 callback_arguments_span, 3107 arg_before_callback_span, 3108 callback_body_span, 3109 }) = self.selected_call 3110 else { 3111 return vec![]; 3112 }; 3113 3114 // This is the nesting level of the `use` keyword we've inserted, we 3115 // want to move the entire body of the anonymous function to this level. 3116 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character; 3117 let indentation = " ".repeat(use_nesting_level as usize); 3118 3119 // First we move the callback arguments to the left hand side of the 3120 // call and add the `use` keyword. 3121 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span { 3122 let arguments_start = arguments_location.start as usize; 3123 let arguments_end = arguments_location.end as usize; 3124 let arguments_text = self 3125 .module 3126 .code 3127 .get(arguments_start..arguments_end) 3128 .expect("fn args"); 3129 format!("use {arguments_text} <- ") 3130 } else { 3131 "use <- ".into() 3132 }; 3133 3134 self.edits.insert(call_span.start, left_hand_side_text); 3135 3136 match arg_before_callback_span { 3137 // If the function call has no other arguments besides the callback then 3138 // we just have to remove the `fn(...) {` part. 3139 // 3140 // wibble(fn(...) { ... }) 3141 // ^^^^^^^^^^ This goes from the end of the called function 3142 // To the start of the first thing in the anonymous 3143 // function's body. 3144 // 3145 None => self.edits.replace( 3146 SrcSpan::new(called_function_span.end, callback_body_span.start), 3147 format!("\n{indentation}"), 3148 ), 3149 // If it has other arguments we'll have to remove those and add a closed 3150 // parentheses too: 3151 // 3152 // wibble(1, 2, fn(...) { ... }) 3153 // ^^^^^^^^^^^ We have to replace this with a `)`, it 3154 // goes from the end of the second-to-last 3155 // argument to the start of the first thing 3156 // in the anonymous function's body. 3157 // 3158 Some(arg_before_callback) => self.edits.replace( 3159 SrcSpan::new(arg_before_callback.end, callback_body_span.start), 3160 format!(")\n{indentation}"), 3161 ), 3162 } 3163 3164 // Then we have to remove two spaces of indentation from each line of 3165 // the callback function's body. 3166 let body_range = self.edits.src_span_to_lsp_range(callback_body_span); 3167 for line in body_range.start.line + 1..=body_range.end.line { 3168 self.edits.delete_range(Range::new( 3169 Position { line, character: 0 }, 3170 Position { line, character: 2 }, 3171 )); 3172 } 3173 3174 // Then we have to remove the anonymous fn closing `}` and the call's 3175 // closing `)`. 3176 self.edits 3177 .delete(SrcSpan::new(callback_body_span.end, call_span.end)); 3178 3179 let mut action = Vec::with_capacity(1); 3180 CodeActionBuilder::new("Convert to `use`") 3181 .kind(CodeActionKind::RefactorRewrite) 3182 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3183 .preferred(false) 3184 .push_to(&mut action); 3185 action 3186 } 3187} 3188 3189impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> { 3190 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 3191 // The cursor has to be inside the last statement of the function to 3192 // offer the code action. 3193 if let Some(last) = &fun.body.last() 3194 && within( 3195 self.params.range, 3196 self.edits.src_span_to_lsp_range(last.location()), 3197 ) 3198 && let Some(call_data) = turn_statement_into_use(last) 3199 { 3200 self.selected_call = Some(call_data); 3201 } 3202 3203 ast::visit::visit_typed_function(self, fun); 3204 } 3205 3206 fn visit_typed_expr_fn( 3207 &mut self, 3208 location: &'ast SrcSpan, 3209 type_: &'ast Arc<Type>, 3210 kind: &'ast FunctionLiteralKind, 3211 arguments: &'ast [TypedArg], 3212 body: &'ast Vec1<TypedStatement>, 3213 return_annotation: &'ast Option<ast::TypeAst>, 3214 ) { 3215 // The cursor has to be inside the last statement of the body to 3216 // offer the code action. 3217 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location()); 3218 if within(self.params.range, last_statement_range) 3219 && let Some(call_data) = turn_statement_into_use(body.last()) 3220 { 3221 self.selected_call = Some(call_data); 3222 } 3223 3224 ast::visit::visit_typed_expr_fn( 3225 self, 3226 location, 3227 type_, 3228 kind, 3229 arguments, 3230 body, 3231 return_annotation, 3232 ); 3233 } 3234 3235 fn visit_typed_expr_block( 3236 &mut self, 3237 location: &'ast SrcSpan, 3238 statements: &'ast [TypedStatement], 3239 ) { 3240 let Some(last_statement) = statements.last() else { 3241 return; 3242 }; 3243 3244 // The cursor has to be inside the last statement of the block to offer 3245 // the code action. 3246 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location()); 3247 if within(self.params.range, statement_range) { 3248 // Only the last statement of a block can be turned into a use! 3249 if let Some(selected_call) = turn_statement_into_use(last_statement) { 3250 self.selected_call = Some(selected_call); 3251 } 3252 } 3253 3254 ast::visit::visit_typed_expr_block(self, location, statements); 3255 } 3256} 3257 3258fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> { 3259 match statement { 3260 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None, 3261 ast::Statement::Expression(expression) => turn_expression_into_use(expression), 3262 } 3263} 3264 3265fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> { 3266 let TypedExpr::Call { 3267 arguments, 3268 location: call_span, 3269 fun: called_function, 3270 .. 3271 } = expr 3272 else { 3273 return None; 3274 }; 3275 3276 // The function arguments in the ast are reordered using function's field map. 3277 // This means that in the `args` array they might not appear in the same order 3278 // in which they are written by the user. Since the rest of the code relies 3279 // on their order in the written code we first have to sort them by their 3280 // source position. 3281 let arguments = arguments 3282 .iter() 3283 .sorted_by_key(|argument| argument.location.start) 3284 .collect_vec(); 3285 3286 let CallArg { 3287 value: last_arg, 3288 implicit: None, 3289 .. 3290 } = arguments.last()? 3291 else { 3292 return None; 3293 }; 3294 3295 let TypedExpr::Fn { 3296 arguments: callback_arguments, 3297 body, 3298 .. 3299 } = last_arg 3300 else { 3301 return None; 3302 }; 3303 3304 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) { 3305 (Some(first), Some(last)) => Some(first.location.merge(&last.location)), 3306 _ => None, 3307 }; 3308 3309 let arg_before_callback_span = if arguments.len() >= 2 { 3310 arguments 3311 .get(arguments.len() - 2) 3312 .map(|call_arg| call_arg.location) 3313 } else { 3314 None 3315 }; 3316 3317 let callback_body_span = body.first().location().merge(&body.last().last_location()); 3318 3319 Some(CallLocations { 3320 call_span: *call_span, 3321 called_function_span: called_function.location(), 3322 callback_arguments_span, 3323 arg_before_callback_span, 3324 callback_body_span, 3325 }) 3326} 3327 3328/// Builder for code action to extract expression into a variable. 3329/// The action will wrap the expression in a block if needed in the appropriate scope. 3330/// 3331/// For using the code action on the following selection: 3332/// 3333/// ```gleam 3334/// fn void() { 3335/// case result { 3336/// Ok(value) -> 2 * value + 1 3337/// // ^^^^^^^^^ 3338/// Error(_) -> panic 3339/// } 3340/// } 3341/// ``` 3342/// 3343/// Will result: 3344/// 3345/// ```gleam 3346/// fn void() { 3347/// case result { 3348/// Ok(value) -> { 3349/// let int = 2 * value 3350/// int + 1 3351/// } 3352/// Error(_) -> panic 3353/// } 3354/// } 3355/// ``` 3356pub struct ExtractVariable<'a> { 3357 module: &'a Module, 3358 params: &'a CodeActionParams, 3359 edits: TextEdits<'a>, 3360 position: Option<ExtractVariablePosition>, 3361 selected_expression: Option<ExtractedToVariable>, 3362 statement_before_selected_expression: Option<SrcSpan>, 3363 latest_statement: Option<SrcSpan>, 3364 to_be_wrapped: bool, 3365 name_generator: NameGenerator, 3366} 3367 3368pub enum ExtractedToVariable { 3369 Expression { location: SrcSpan, name: EcoString }, 3370 StartOfPipeline { location: SrcSpan, name: EcoString }, 3371} 3372 3373/// The Position of the selected code 3374#[derive(PartialEq, Eq, Copy, Clone, Debug)] 3375enum ExtractVariablePosition { 3376 InsideCaptureBody, 3377 /// Full statements (i.e. assignments, `use`s, and simple expressions). 3378 TopLevelStatement, 3379 /// The call on the right hand side of a pipe `|>`. 3380 PipelineCall, 3381 /// The right hand side of the `->` in a case expression. 3382 InsideCaseClause, 3383 /// A call argument. This can also be a `use` callback. 3384 CallArg, 3385} 3386 3387impl<'a> ExtractVariable<'a> { 3388 pub fn new( 3389 module: &'a Module, 3390 line_numbers: &'a LineNumbers, 3391 params: &'a CodeActionParams, 3392 ) -> Self { 3393 Self { 3394 module, 3395 params, 3396 edits: TextEdits::new(line_numbers), 3397 position: None, 3398 selected_expression: None, 3399 statement_before_selected_expression: None, 3400 latest_statement: None, 3401 to_be_wrapped: false, 3402 name_generator: NameGenerator::new(), 3403 } 3404 } 3405 3406 pub fn code_actions(mut self) -> Vec<CodeAction> { 3407 self.visit_typed_module(&self.module.ast); 3408 3409 let (Some(extracted_value), Some(insert_location)) = ( 3410 self.selected_expression, 3411 self.statement_before_selected_expression, 3412 ) else { 3413 return vec![]; 3414 }; 3415 3416 let variable_name = match &extracted_value { 3417 ExtractedToVariable::Expression { name, .. } 3418 | ExtractedToVariable::StartOfPipeline { name, .. } => name, 3419 }; 3420 let expression_span = match &extracted_value { 3421 ExtractedToVariable::Expression { location, .. } 3422 | ExtractedToVariable::StartOfPipeline { location, .. } => location, 3423 }; 3424 3425 let content = self 3426 .module 3427 .code 3428 .get(expression_span.start as usize..expression_span.end as usize) 3429 .expect("selected expression"); 3430 3431 let range = self.edits.src_span_to_lsp_range(insert_location); 3432 3433 let indent_size = 3434 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 3435 3436 let mut indent = " ".repeat(indent_size); 3437 3438 // We insert the variable declaration 3439 // Wrap in a block if needed 3440 let mut insertion = match extracted_value { 3441 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"), 3442 ExtractedToVariable::StartOfPipeline { .. } => { 3443 format!("let {variable_name} =\n{indent} {content}\n") 3444 } 3445 }; 3446 3447 if self.to_be_wrapped { 3448 let line_end = self 3449 .edits 3450 .line_numbers 3451 .line_starts 3452 .get((range.end.line + 1) as usize) 3453 .expect("Line number should be valid"); 3454 3455 self.edits.insert(*line_end, format!("{indent}}}\n")); 3456 indent += " "; 3457 insertion = format!("{{\n{indent}{insertion}"); 3458 } 3459 3460 self.edits 3461 .insert(insert_location.start, format!("{insertion}\n{indent}")); 3462 3463 self.edits 3464 .replace(*expression_span, String::from(variable_name)); 3465 3466 let mut action = Vec::with_capacity(1); 3467 CodeActionBuilder::new("Extract variable") 3468 .kind(CodeActionKind::RefactorExtract) 3469 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3470 .preferred(false) 3471 .push_to(&mut action); 3472 action 3473 } 3474 3475 fn inside_new_scope<F>(&mut self, fun: F) 3476 where 3477 F: Fn(&mut Self), 3478 { 3479 let names = self.name_generator.clone(); 3480 fun(self); 3481 self.name_generator = names; 3482 } 3483 3484 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString { 3485 let name = self.name_generator.generate_name_from_type(&type_); 3486 // When the generator generates a name, it rightfully inserts it in the 3487 // current scope so that it cannot be used again. 3488 // However, in our case it's not what we want: the name we're generating 3489 // is a candidate for what we might use for a single variable, at the 3490 // end of the whole process we're gonna pick just a single name. 3491 // If we were to insert this name into scope, that means that all the 3492 // other candidates would have a suffix `int_2`, `int_3`, ... 3493 // When we finally pick one it would be strange if the picked name had 3494 // a suffix but no `int`, `int_1`, ... were in scope! 3495 let _ = self.name_generator.used_names.remove(&name); 3496 name 3497 } 3498 3499 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F) 3500 where 3501 F: Fn(&mut Self), 3502 { 3503 self.at_optional_position(Some(position), fun); 3504 } 3505 3506 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F) 3507 where 3508 F: Fn(&mut Self), 3509 { 3510 let previous_statement = self.latest_statement; 3511 let previous_position = self.position; 3512 self.position = position; 3513 fun(self); 3514 self.position = previous_position; 3515 self.latest_statement = previous_statement; 3516 } 3517} 3518 3519impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> { 3520 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 3521 let range = self.edits.src_span_to_lsp_range(statement.location()); 3522 if !within(self.params.range, range) { 3523 self.latest_statement = Some(statement.location()); 3524 ast::visit::visit_typed_statement(self, statement); 3525 return; 3526 } 3527 3528 match self.position { 3529 // A capture body is comprised of just a single expression statement 3530 // that is inserted by the compiler, we don't really want to put 3531 // anything before that; so in this case we avoid tracking it. 3532 Some(ExtractVariablePosition::InsideCaptureBody) => {} 3533 Some(ExtractVariablePosition::PipelineCall) => { 3534 // Insert above the pipeline start 3535 self.latest_statement = Some(statement.location()); 3536 } 3537 _ => { 3538 // Insert below the previous statement 3539 self.latest_statement = Some(statement.location()); 3540 self.statement_before_selected_expression = self.latest_statement; 3541 } 3542 } 3543 3544 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3545 ast::visit::visit_typed_statement(this, statement); 3546 }); 3547 } 3548 3549 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 3550 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 3551 start: fun.location.start, 3552 end: fun.end_position, 3553 }); 3554 3555 if !within(self.params.range, fun_range) { 3556 return; 3557 } 3558 3559 // We reset the name generator to purge the variable names from other 3560 // scopes. 3561 // We then reserve the names already used by top level definitions. 3562 self.name_generator = NameGenerator::new(); 3563 self.name_generator 3564 .reserve_module_value_names(&self.module.ast.definitions); 3565 3566 ast::visit::visit_typed_function(self, fun); 3567 } 3568 3569 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 3570 if let Pattern::Variable { name, .. } = &assignment.pattern { 3571 self.name_generator.add_used_name(name.clone()); 3572 } 3573 ast::visit::visit_typed_assignment(self, assignment); 3574 } 3575 3576 fn visit_typed_expr_pipeline( 3577 &mut self, 3578 location: &'ast SrcSpan, 3579 first_value: &'ast TypedPipelineAssignment, 3580 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 3581 finally: &'ast TypedExpr, 3582 finally_kind: &'ast PipelineAssignmentKind, 3583 ) { 3584 let expr_range = self.edits.src_span_to_lsp_range(*location); 3585 if !within(self.params.range, expr_range) { 3586 ast::visit::visit_typed_expr_pipeline( 3587 self, 3588 location, 3589 first_value, 3590 assignments, 3591 finally, 3592 finally_kind, 3593 ); 3594 return; 3595 } 3596 3597 // Visiting a pipeline requires a bit of care, we don't want to extract 3598 // intermediate steps as variables (those are function calls)! 3599 // So we start by checking if the selected section contains multiple 3600 // steps including the first one: in that case we can extract all those 3601 // steps as a single variable. 3602 let selection = self.edits.lsp_range_to_src_span(self.params.range); 3603 let is_inside_first_step = first_value.location.contains(selection.start); 3604 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| { 3605 if assignment.location.contains(selection.end) { 3606 Some(assignment) 3607 } else { 3608 None 3609 } 3610 }); 3611 3612 if let Some(last) = last_included_step 3613 && is_inside_first_step 3614 { 3615 let location = first_value.location.merge(&last.value.location()); 3616 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline { 3617 location, 3618 name: self.generate_candidate_name(last.type_()), 3619 }); 3620 return; 3621 } 3622 3623 // Otherwise we visit all the steps individually to see if there's 3624 // something _inside_ a step that might be extracted. 3625 let all_assignments = 3626 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment)); 3627 for assignment in all_assignments { 3628 // With the position as "PipelineCall" we know we can't extract the 3629 // pipeline step itself! 3630 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 3631 this.visit_typed_pipeline_assignment(assignment); 3632 }); 3633 } 3634 3635 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 3636 this.visit_typed_expr(finally); 3637 }); 3638 } 3639 3640 fn visit_typed_expr_call( 3641 &mut self, 3642 location: &'ast SrcSpan, 3643 type_: &'ast Arc<Type>, 3644 fun: &'ast TypedExpr, 3645 arguments: &'ast [TypedCallArg], 3646 open_parenthesis: &'ast Option<u32>, 3647 ) { 3648 // Function calls need some extra care. If we're inspecting a record 3649 // call like this one: `Wibble(wobble, woo)` and the cursor is over the 3650 // constructor itself we never want to allow extracting it, or it would 3651 // result in the following code: 3652 // 3653 // ```gleam 3654 // Wibble(wobble, woo) 3655 // // ^^ Cursor here 3656 // 3657 // let wibble = Wibble 3658 // wibble(wobble, woo) 3659 // // That's a bit silly! 3660 // ``` 3661 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 3662 if within(self.params.range, fun_range) && fun.is_record_constructor_function() { 3663 return; 3664 } 3665 3666 // Otherwise we just keep visiting like usual, no special handling is 3667 // required. 3668 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis); 3669 } 3670 3671 fn visit_typed_expr_record_update( 3672 &mut self, 3673 location: &'ast SrcSpan, 3674 spread_start: &'ast u32, 3675 type_: &'ast Arc<Type>, 3676 updated_record: &'ast TypedExpr, 3677 updated_record_assigned_name: &'ast Option<EcoString>, 3678 constructor: &'ast TypedExpr, 3679 arguments: &'ast [TypedCallArg], 3680 ) { 3681 // Record updates need some extra care. If we're inspecting a record 3682 // update like this one: `Wibble(..wobble, woo)` and the cursor is over 3683 // the constructor itself we never want to allow extracting it, or it 3684 // would result in the following code: 3685 // 3686 // ```gleam 3687 // Wibble(..wobble, woo) 3688 // // ^^ Cursor here 3689 // 3690 // let wibble = Wibble 3691 // wibble(..wobble, woo) 3692 // // That's a bit silly! 3693 // ``` 3694 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location()); 3695 if within(self.params.range, constructor_range) 3696 && constructor.is_record_constructor_function() 3697 { 3698 return; 3699 } 3700 3701 // Otherwise we just keep visiting like usual, no special handling is 3702 // required. 3703 ast::visit::visit_typed_expr_record_update( 3704 self, 3705 location, 3706 spread_start, 3707 type_, 3708 updated_record, 3709 updated_record_assigned_name, 3710 constructor, 3711 arguments, 3712 ); 3713 } 3714 3715 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 3716 let expr_location = expr.location(); 3717 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 3718 if !within(self.params.range, expr_range) { 3719 ast::visit::visit_typed_expr(self, expr); 3720 return; 3721 } 3722 3723 // If the expression is a top level statement we don't want to extract 3724 // it into a variable. It would mean we would turn this: 3725 // 3726 // ```gleam 3727 // pub fn main() { 3728 // let wibble = 1 3729 // // ^ cursor here 3730 // } 3731 // 3732 // // into: 3733 // 3734 // pub fn main() { 3735 // let int = 1 3736 // let wibble = int 3737 // } 3738 // ``` 3739 // 3740 // Not all that useful! 3741 // 3742 match self.position { 3743 Some( 3744 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall, 3745 ) => { 3746 self.at_optional_position(None, |this| { 3747 ast::visit::visit_typed_expr(this, expr); 3748 }); 3749 return; 3750 } 3751 Some( 3752 ExtractVariablePosition::InsideCaptureBody 3753 | ExtractVariablePosition::InsideCaseClause 3754 | ExtractVariablePosition::CallArg, 3755 ) 3756 | None => {} 3757 } 3758 3759 match expr { 3760 TypedExpr::Fn { 3761 kind: FunctionLiteralKind::Anonymous { .. }, 3762 .. 3763 } => { 3764 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3765 ast::visit::visit_typed_expr(this, expr); 3766 }); 3767 return; 3768 } 3769 3770 TypedExpr::Int { location, .. } 3771 | TypedExpr::Float { location, .. } 3772 | TypedExpr::String { location, .. } 3773 | TypedExpr::Pipeline { location, .. } 3774 | TypedExpr::Fn { location, .. } 3775 | TypedExpr::Todo { location, .. } 3776 | TypedExpr::List { location, .. } 3777 | TypedExpr::Call { location, .. } 3778 | TypedExpr::BinOp { location, .. } 3779 | TypedExpr::Case { location, .. } 3780 | TypedExpr::RecordAccess { location, .. } 3781 | TypedExpr::Tuple { location, .. } 3782 | TypedExpr::TupleIndex { location, .. } 3783 | TypedExpr::BitArray { location, .. } 3784 | TypedExpr::RecordUpdate { location, .. } 3785 | TypedExpr::NegateBool { location, .. } 3786 | TypedExpr::NegateInt { location, .. } 3787 // It generally makes no sense to extract variables, the only 3788 // exception is for records with no fields (like `True` and 3789 // `False`): in the AST those are `Var`s with a `Record` 3790 // constructor. Extracting them is allowed! 3791 | TypedExpr::Var { 3792 constructor: 3793 ValueConstructor { 3794 variant: type_::ValueConstructorVariant::Record { .. }, 3795 .. 3796 }, 3797 location, 3798 .. 3799 } => { 3800 if let Some(ExtractVariablePosition::CallArg) = self.position { 3801 // Don't update latest statement, we don't want to insert the extracted 3802 // variable inside the parenthesis where the call argument is located. 3803 } else { 3804 self.statement_before_selected_expression = self.latest_statement; 3805 } 3806 self.selected_expression = Some(ExtractedToVariable::Expression { 3807 location: *location, 3808 name: self.generate_candidate_name(expr.type_()), 3809 }); 3810 } 3811 3812 // Expressions that don't make sense to extract 3813 TypedExpr::Panic { .. } 3814 | TypedExpr::Echo { .. } 3815 | TypedExpr::Block { .. } 3816 | TypedExpr::ModuleSelect { .. } 3817 | TypedExpr::Invalid { .. } 3818 | TypedExpr::PositionalAccess { .. } 3819 | TypedExpr::Var { .. } => (), 3820 } 3821 3822 ast::visit::visit_typed_expr(self, expr); 3823 } 3824 3825 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3826 let range = self.edits.src_span_to_lsp_range(use_.call.location()); 3827 if !within(self.params.range, range) { 3828 ast::visit::visit_typed_use(self, use_); 3829 return; 3830 } 3831 3832 // Insert code under the `use` 3833 self.statement_before_selected_expression = Some(use_.call.location()); 3834 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3835 ast::visit::visit_typed_use(this, use_); 3836 }); 3837 } 3838 3839 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 3840 let range = self.edits.src_span_to_lsp_range(clause.location()); 3841 if !within(self.params.range, range) { 3842 self.inside_new_scope(|this| { 3843 ast::visit::visit_typed_clause(this, clause); 3844 }); 3845 return; 3846 } 3847 3848 // Insert code after the `->` 3849 self.latest_statement = Some(clause.then.location()); 3850 self.to_be_wrapped = true; 3851 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| { 3852 this.inside_new_scope(|this| { 3853 ast::visit::visit_typed_clause(this, clause); 3854 }); 3855 }); 3856 } 3857 3858 fn visit_typed_expr_block( 3859 &mut self, 3860 location: &'ast SrcSpan, 3861 statements: &'ast [TypedStatement], 3862 ) { 3863 let range = self.edits.src_span_to_lsp_range(*location); 3864 if !within(self.params.range, range) { 3865 self.inside_new_scope(|this| { 3866 ast::visit::visit_typed_expr_block(this, location, statements); 3867 }); 3868 return; 3869 } 3870 3871 // Don't extract block as variable 3872 let mut position = self.position; 3873 if let Some(ExtractVariablePosition::InsideCaseClause) = position { 3874 position = None; 3875 self.to_be_wrapped = false; 3876 } 3877 3878 self.at_optional_position(position, |this| { 3879 this.inside_new_scope(|this| { 3880 ast::visit::visit_typed_expr_block(this, location, statements); 3881 }); 3882 }); 3883 } 3884 3885 fn visit_typed_expr_fn( 3886 &mut self, 3887 location: &'ast SrcSpan, 3888 type_: &'ast Arc<Type>, 3889 kind: &'ast FunctionLiteralKind, 3890 arguments: &'ast [TypedArg], 3891 body: &'ast Vec1<TypedStatement>, 3892 return_annotation: &'ast Option<ast::TypeAst>, 3893 ) { 3894 let range = self.edits.src_span_to_lsp_range(*location); 3895 if !within(self.params.range, range) { 3896 self.inside_new_scope(|this| { 3897 ast::visit::visit_typed_expr_fn( 3898 this, 3899 location, 3900 type_, 3901 kind, 3902 arguments, 3903 body, 3904 return_annotation, 3905 ); 3906 }); 3907 return; 3908 } 3909 3910 let position = match kind { 3911 // If a fn is a capture `int.wibble(1, _)` its body will consist of 3912 // just a single expression statement. When visiting we must record 3913 // we're inside a capture body. 3914 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody), 3915 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement), 3916 FunctionLiteralKind::Anonymous { .. } => self.position, 3917 }; 3918 3919 self.at_optional_position(position, |this| { 3920 this.inside_new_scope(|this| { 3921 ast::visit::visit_typed_expr_fn( 3922 this, 3923 location, 3924 type_, 3925 kind, 3926 arguments, 3927 body, 3928 return_annotation, 3929 ); 3930 }); 3931 }); 3932 } 3933 3934 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 3935 let range = self.edits.src_span_to_lsp_range(arg.location); 3936 if !within(self.params.range, range) { 3937 ast::visit::visit_typed_call_arg(self, arg); 3938 return; 3939 } 3940 3941 // An implicit record update arg in inserted by the compiler, we don't 3942 // want folks to interact with this since it doesn't translate to 3943 // anything in the source code despite having a default position. 3944 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit { 3945 return; 3946 } 3947 3948 let position = if arg.is_use_implicit_callback() { 3949 Some(ExtractVariablePosition::TopLevelStatement) 3950 } else { 3951 Some(ExtractVariablePosition::CallArg) 3952 }; 3953 3954 self.at_optional_position(position, |this| { 3955 ast::visit::visit_typed_call_arg(this, arg); 3956 }); 3957 } 3958 3959 // We don't want to offer the action if the cursor is over some invalid 3960 // piece of code. 3961 fn visit_typed_expr_invalid( 3962 &mut self, 3963 location: &'ast SrcSpan, 3964 _type_: &'ast Arc<Type>, 3965 _extra_information: &'ast Option<InvalidExpression>, 3966 ) { 3967 let invalid_range = self.edits.src_span_to_lsp_range(*location); 3968 if within(self.params.range, invalid_range) { 3969 self.selected_expression = None; 3970 } 3971 } 3972} 3973 3974/// Builder for code action to convert a literal use into a const. 3975/// 3976/// For using the code action on each of the following lines: 3977/// 3978/// ```gleam 3979/// fn void() { 3980/// let var = [1, 2, 3] 3981/// let res = function("Statement", var) 3982/// } 3983/// ``` 3984/// 3985/// Both value literals will become: 3986/// 3987/// ```gleam 3988/// const var = [1, 2, 3] 3989/// const string = "Statement" 3990/// 3991/// fn void() { 3992/// let res = function(string, var) 3993/// } 3994/// ``` 3995pub struct ExtractConstant<'a> { 3996 module: &'a Module, 3997 params: &'a CodeActionParams, 3998 edits: TextEdits<'a>, 3999 /// The whole selected expression 4000 selected_expression: Option<SrcSpan>, 4001 /// The location of the start of the function containing the expression. 4002 /// It includes function's documentation as well 4003 container_function_start: Option<u32>, 4004 /// The variant of the extractable expression being extracted (if any) 4005 variant_of_extractable: Option<ExtractableToConstant>, 4006 /// The name of the newly created constant 4007 name_to_use: Option<EcoString>, 4008 /// The right hand side expression of the newly created constant 4009 value_to_use: Option<EcoString>, 4010} 4011 4012/// Used when an expression can be extracted to a constant 4013enum ExtractableToConstant { 4014 /// Used for collections and operator uses. This means that elements 4015 /// inside, are also extractable as constants. 4016 ComposedValue, 4017 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings 4018 /// and type variants (not records). 4019 SingleValue, 4020 /// Used for whole variable assignments. If the right hand side of the 4021 /// expression can be extracted, the whole expression extracted and use the 4022 /// local variable as a constant. 4023 Assignment, 4024} 4025 4026fn can_be_constant( 4027 module: &Module, 4028 expr: &TypedExpr, 4029 module_constants: Option<&HashSet<&EcoString>>, 4030) -> bool { 4031 // We pass the `module_constants` on recursion to not compute them each time 4032 let module_constants = match module_constants { 4033 Some(module_constants) => module_constants, 4034 None => &module 4035 .ast 4036 .definitions 4037 .constants 4038 .iter() 4039 .map(|constant| &constant.name) 4040 .collect(), 4041 }; 4042 4043 match expr { 4044 // Attempt to extract whole list as long as it's comprised of only literals 4045 TypedExpr::List { elements, tail, .. } => { 4046 elements 4047 .iter() 4048 .all(|element| can_be_constant(module, element, Some(module_constants))) 4049 && tail.is_none() 4050 } 4051 4052 // Attempt to extract whole bit array as long as it's made up of literals 4053 TypedExpr::BitArray { segments, .. } => { 4054 segments 4055 .iter() 4056 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants))) 4057 && segments.iter().all(|segment| { 4058 segment.options.iter().all(|option| match option { 4059 ast::BitArrayOption::Size { value, .. } => { 4060 can_be_constant(module, value, Some(module_constants)) 4061 } 4062 4063 ast::BitArrayOption::Bytes { .. } 4064 | ast::BitArrayOption::Int { .. } 4065 | ast::BitArrayOption::Float { .. } 4066 | ast::BitArrayOption::Bits { .. } 4067 | ast::BitArrayOption::Utf8 { .. } 4068 | ast::BitArrayOption::Utf16 { .. } 4069 | ast::BitArrayOption::Utf32 { .. } 4070 | ast::BitArrayOption::Utf8Codepoint { .. } 4071 | ast::BitArrayOption::Utf16Codepoint { .. } 4072 | ast::BitArrayOption::Utf32Codepoint { .. } 4073 | ast::BitArrayOption::Signed { .. } 4074 | ast::BitArrayOption::Unsigned { .. } 4075 | ast::BitArrayOption::Big { .. } 4076 | ast::BitArrayOption::Little { .. } 4077 | ast::BitArrayOption::Native { .. } 4078 | ast::BitArrayOption::Unit { .. } => true, 4079 }) 4080 }) 4081 } 4082 4083 // Attempt to extract whole tuple as long as it's comprised of only literals 4084 TypedExpr::Tuple { elements, .. } => elements 4085 .iter() 4086 .all(|element| can_be_constant(module, element, Some(module_constants))), 4087 4088 // Extract literals directly 4089 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true, 4090 4091 // Extract non-record types directly 4092 TypedExpr::Var { 4093 constructor, name, .. 4094 } => { 4095 matches!( 4096 constructor.variant, 4097 type_::ValueConstructorVariant::Record { arity: 0, .. } 4098 ) || module_constants.contains(name) 4099 } 4100 4101 // Extract record types as long as arguments can be constant 4102 TypedExpr::Call { arguments, fun, .. } => { 4103 fun.is_record_literal() 4104 && arguments 4105 .iter() 4106 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants))) 4107 } 4108 4109 // Extract concat binary operation if both sides can be constants 4110 TypedExpr::BinOp { 4111 operator, 4112 left, 4113 right, 4114 .. 4115 } => { 4116 matches!(operator, ast::BinOp::Concatenate) 4117 && can_be_constant(module, left, Some(module_constants)) 4118 && can_be_constant(module, right, Some(module_constants)) 4119 } 4120 4121 TypedExpr::Block { .. } 4122 | TypedExpr::Pipeline { .. } 4123 | TypedExpr::Fn { .. } 4124 | TypedExpr::Case { .. } 4125 | TypedExpr::RecordAccess { .. } 4126 | TypedExpr::PositionalAccess { .. } 4127 | TypedExpr::ModuleSelect { .. } 4128 | TypedExpr::TupleIndex { .. } 4129 | TypedExpr::Todo { .. } 4130 | TypedExpr::Panic { .. } 4131 | TypedExpr::Echo { .. } 4132 | TypedExpr::RecordUpdate { .. } 4133 | TypedExpr::NegateBool { .. } 4134 | TypedExpr::NegateInt { .. } 4135 | TypedExpr::Invalid { .. } => false, 4136 } 4137} 4138 4139/// Takes the list of already existing constants and functions and creates a 4140/// name that doesn't conflict with them 4141/// 4142fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString { 4143 let mut name_generator = NameGenerator::new(); 4144 name_generator.reserve_module_value_names(&module.ast.definitions); 4145 name_generator.generate_name_from_type(&expr.type_()) 4146} 4147 4148/// Converts the source start position of a documentation comment's contents into 4149/// the position of the leading slash in its marker ('///'). 4150fn get_doc_marker_position(content_pos: u32) -> u32 { 4151 content_pos.saturating_sub(3) 4152} 4153 4154impl<'a> ExtractConstant<'a> { 4155 pub fn new( 4156 module: &'a Module, 4157 line_numbers: &'a LineNumbers, 4158 params: &'a CodeActionParams, 4159 ) -> Self { 4160 Self { 4161 module, 4162 params, 4163 edits: TextEdits::new(line_numbers), 4164 selected_expression: None, 4165 container_function_start: None, 4166 variant_of_extractable: None, 4167 name_to_use: None, 4168 value_to_use: None, 4169 } 4170 } 4171 4172 pub fn code_actions(mut self) -> Vec<CodeAction> { 4173 self.visit_typed_module(&self.module.ast); 4174 4175 let ( 4176 Some(expr_span), 4177 Some(function_start), 4178 Some(type_of_extractable), 4179 Some(new_const_name), 4180 Some(const_value), 4181 ) = ( 4182 self.selected_expression, 4183 self.container_function_start, 4184 self.variant_of_extractable, 4185 self.name_to_use, 4186 self.value_to_use, 4187 ) 4188 else { 4189 return vec![]; 4190 }; 4191 4192 // We insert the constant declaration 4193 self.edits.insert( 4194 function_start, 4195 format!("const {new_const_name} = {const_value}\n\n"), 4196 ); 4197 4198 // We remove or replace the selected expression 4199 match type_of_extractable { 4200 // The whole expression is deleted for assignments 4201 ExtractableToConstant::Assignment => { 4202 let range = self 4203 .edits 4204 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 4205 4206 let indent_size = 4207 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 4208 4209 let expr_span_with_new_line = SrcSpan { 4210 // We remove leading indentation + 1 to remove the newline with it 4211 start: expr_span.start - (indent_size as u32 + 1), 4212 end: expr_span.end, 4213 }; 4214 self.edits.delete(expr_span_with_new_line); 4215 } 4216 4217 // Only right hand side is replaced for collection or values 4218 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => { 4219 self.edits.replace(expr_span, String::from(new_const_name)); 4220 } 4221 } 4222 4223 let mut action = Vec::with_capacity(1); 4224 CodeActionBuilder::new("Extract constant") 4225 .kind(CodeActionKind::RefactorExtract) 4226 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4227 .preferred(false) 4228 .push_to(&mut action); 4229 action 4230 } 4231} 4232 4233impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> { 4234 /// To get the position of the function containing the value or assignment 4235 /// to extract 4236 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 4237 let fun_location = fun.location; 4238 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 4239 start: fun_location.start, 4240 end: fun.end_position, 4241 }); 4242 4243 if !within(self.params.range, fun_range) { 4244 return; 4245 } 4246 4247 // Here we need to get position of the function, starting from the leading slash in the 4248 // documentation comment's marker ('///'), not from comment's content (of which 4249 // we have the position), so we must convert the content start position 4250 // to the leading slash's position. 4251 self.container_function_start = Some( 4252 fun.documentation 4253 .as_ref() 4254 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 4255 .unwrap_or(fun_location.start), 4256 ); 4257 4258 ast::visit::visit_typed_function(self, fun); 4259 } 4260 4261 /// To extract the whole assignment 4262 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 4263 let expr_location = assignment.location; 4264 4265 // We only offer this code action for extracting the whole assignment 4266 // between `let` and `=`. 4267 let pattern_location = assignment.pattern.location(); 4268 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 4269 let code_action_range = self.edits.src_span_to_lsp_range(location); 4270 4271 if !within(self.params.range, code_action_range) { 4272 ast::visit::visit_typed_assignment(self, assignment); 4273 return; 4274 } 4275 4276 // Has to be variable because patterns can't be constants. 4277 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None) 4278 { 4279 self.variant_of_extractable = Some(ExtractableToConstant::Assignment); 4280 self.selected_expression = Some(expr_location); 4281 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern { 4282 Some(name.clone()) 4283 } else { 4284 None 4285 }; 4286 self.value_to_use = Some(EcoString::from( 4287 self.module 4288 .code 4289 .get( 4290 (assignment.value.location().start as usize) 4291 ..(assignment.location.end as usize), 4292 ) 4293 .expect("selected expression"), 4294 )); 4295 } 4296 } 4297 4298 /// To extract only the literal 4299 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 4300 let expr_location = expr.location(); 4301 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 4302 4303 if !within(self.params.range, expr_range) { 4304 ast::visit::visit_typed_expr(self, expr); 4305 return; 4306 } 4307 4308 // Keep going down recursively if: 4309 // - It's no extractable has been found yet (`None`). 4310 // - It's a collection, which may or may not contain a value that can 4311 // be extracted. 4312 // - It's a binary operator, which may or may not operate on 4313 // extractable values. 4314 if matches!( 4315 self.variant_of_extractable, 4316 None | Some(ExtractableToConstant::ComposedValue) 4317 ) && can_be_constant(self.module, expr, None) 4318 { 4319 self.variant_of_extractable = match expr { 4320 TypedExpr::Var { .. } 4321 | TypedExpr::Int { .. } 4322 | TypedExpr::Float { .. } 4323 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue), 4324 4325 TypedExpr::List { .. } 4326 | TypedExpr::Tuple { .. } 4327 | TypedExpr::BitArray { .. } 4328 | TypedExpr::BinOp { .. } 4329 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue), 4330 4331 TypedExpr::Block { .. } 4332 | TypedExpr::Pipeline { .. } 4333 | TypedExpr::Fn { .. } 4334 | TypedExpr::Case { .. } 4335 | TypedExpr::RecordAccess { .. } 4336 | TypedExpr::PositionalAccess { .. } 4337 | TypedExpr::ModuleSelect { .. } 4338 | TypedExpr::TupleIndex { .. } 4339 | TypedExpr::Todo { .. } 4340 | TypedExpr::Panic { .. } 4341 | TypedExpr::Echo { .. } 4342 | TypedExpr::RecordUpdate { .. } 4343 | TypedExpr::NegateBool { .. } 4344 | TypedExpr::NegateInt { .. } 4345 | TypedExpr::Invalid { .. } => None, 4346 }; 4347 4348 self.selected_expression = Some(expr_location); 4349 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr)); 4350 self.value_to_use = Some(EcoString::from( 4351 self.module 4352 .code 4353 .get((expr_location.start as usize)..(expr_location.end as usize)) 4354 .expect("selected expression"), 4355 )); 4356 } 4357 4358 ast::visit::visit_typed_expr(self, expr); 4359 } 4360} 4361 4362/// Builder for code action to apply the "expand function capture" action. 4363/// 4364pub struct ExpandFunctionCapture<'a> { 4365 module: &'a Module, 4366 params: &'a CodeActionParams, 4367 edits: TextEdits<'a>, 4368 function_capture_data: Option<FunctionCaptureData>, 4369} 4370 4371pub struct FunctionCaptureData { 4372 function_span: SrcSpan, 4373 hole_span: SrcSpan, 4374 hole_type: Arc<Type>, 4375 reserved_names: VariablesNames, 4376} 4377 4378impl<'a> ExpandFunctionCapture<'a> { 4379 pub fn new( 4380 module: &'a Module, 4381 line_numbers: &'a LineNumbers, 4382 params: &'a CodeActionParams, 4383 ) -> Self { 4384 Self { 4385 module, 4386 params, 4387 edits: TextEdits::new(line_numbers), 4388 function_capture_data: None, 4389 } 4390 } 4391 4392 pub fn code_actions(mut self) -> Vec<CodeAction> { 4393 self.visit_typed_module(&self.module.ast); 4394 4395 let Some(FunctionCaptureData { 4396 function_span, 4397 hole_span, 4398 hole_type, 4399 reserved_names, 4400 }) = self.function_capture_data 4401 else { 4402 return vec![]; 4403 }; 4404 4405 let mut name_generator = NameGenerator::new(); 4406 name_generator.reserve_variable_names(reserved_names); 4407 let name = name_generator.generate_name_from_type(&hole_type); 4408 4409 self.edits.replace(hole_span, name.clone().into()); 4410 self.edits.insert(function_span.end, " }".into()); 4411 self.edits 4412 .insert(function_span.start, format!("fn({name}) {{ ")); 4413 4414 let mut action = Vec::with_capacity(1); 4415 CodeActionBuilder::new("Expand function capture") 4416 .kind(CodeActionKind::RefactorRewrite) 4417 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4418 .preferred(false) 4419 .push_to(&mut action); 4420 action 4421 } 4422} 4423 4424impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> { 4425 fn visit_typed_expr_fn( 4426 &mut self, 4427 location: &'ast SrcSpan, 4428 type_: &'ast Arc<Type>, 4429 kind: &'ast FunctionLiteralKind, 4430 arguments: &'ast [TypedArg], 4431 body: &'ast Vec1<TypedStatement>, 4432 return_annotation: &'ast Option<ast::TypeAst>, 4433 ) { 4434 let fn_range = self.edits.src_span_to_lsp_range(*location); 4435 if within(self.params.range, fn_range) 4436 && kind.is_capture() 4437 && let [argument] = arguments 4438 { 4439 self.function_capture_data = Some(FunctionCaptureData { 4440 function_span: *location, 4441 hole_span: argument.location, 4442 hole_type: argument.type_.clone(), 4443 reserved_names: VariablesNames::from_statements(body), 4444 }); 4445 } 4446 4447 ast::visit::visit_typed_expr_fn( 4448 self, 4449 location, 4450 type_, 4451 kind, 4452 arguments, 4453 body, 4454 return_annotation, 4455 ); 4456 } 4457} 4458 4459/// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names]. 4460struct VariablesNames { 4461 names: HashSet<EcoString>, 4462} 4463 4464impl VariablesNames { 4465 /// Creates a `VariableNames` by collecting all variables used in a list of statements. 4466 fn from_statements(statements: &[TypedStatement]) -> Self { 4467 let mut variables = Self { 4468 names: HashSet::new(), 4469 }; 4470 4471 for statement in statements { 4472 variables.visit_typed_statement(statement); 4473 } 4474 variables 4475 } 4476 4477 /// Creates a `VariableNames` by collecting all variables used within an expression. 4478 fn from_expression(expression: &TypedExpr) -> Self { 4479 let mut variables = Self { 4480 names: HashSet::new(), 4481 }; 4482 4483 variables.visit_typed_expr(expression); 4484 variables 4485 } 4486} 4487 4488impl<'ast> ast::visit::Visit<'ast> for VariablesNames { 4489 fn visit_typed_expr_var( 4490 &mut self, 4491 _location: &'ast SrcSpan, 4492 _constructor: &'ast ValueConstructor, 4493 name: &'ast EcoString, 4494 ) { 4495 let _ = self.names.insert(name.clone()); 4496 } 4497} 4498 4499/// Builder for code action to apply the "generate dynamic decoder action. 4500/// 4501pub struct GenerateDynamicDecoder<'a, IO> { 4502 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4503 module: &'a Module, 4504 params: &'a CodeActionParams, 4505 edits: TextEdits<'a>, 4506 printer: Printer<'a>, 4507 actions: &'a mut Vec<CodeAction>, 4508} 4509 4510const DECODE_MODULE: &str = "gleam/dynamic/decode"; 4511 4512impl<'a, IO> GenerateDynamicDecoder<'a, IO> { 4513 pub fn new( 4514 module: &'a Module, 4515 line_numbers: &'a LineNumbers, 4516 params: &'a CodeActionParams, 4517 actions: &'a mut Vec<CodeAction>, 4518 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4519 ) -> Self { 4520 // Since we are generating a new function, type variables from other 4521 // functions and constants are irrelevant to the types we print. 4522 let printer = Printer::new_without_type_variables(&module.ast.names); 4523 Self { 4524 module, 4525 params, 4526 edits: TextEdits::new(line_numbers), 4527 printer, 4528 actions, 4529 compiler, 4530 } 4531 } 4532 4533 pub fn code_actions(&mut self) { 4534 self.visit_typed_module(&self.module.ast); 4535 } 4536 4537 fn custom_type_decoder_body( 4538 &mut self, 4539 custom_type: &CustomType<Arc<Type>>, 4540 ) -> Option<EcoString> { 4541 // We cannot generate a decoder for an external type with no constructors! 4542 let constructors_size = custom_type.constructors.len(); 4543 let (first, rest) = custom_type.constructors.split_first()?; 4544 let mode = EncodingMode::for_custom_type(custom_type); 4545 4546 // We generate a decoder for a type with a single constructor: it does not 4547 // require pattern matching on a tag as there's no variants to tell apart. 4548 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag { 4549 return self.constructor_decoder(mode, custom_type, first, 0); 4550 } 4551 4552 // Otherwise we need to generate a decoder that has to tell apart different 4553 // variants, depending on the mode we might have to decode a type field or 4554 // plain strings! 4555 let module = self.printer.print_module(DECODE_MODULE); 4556 let discriminant = if mode == EncodingMode::PlainString { 4557 eco_format!("use variant <- {module}.then({module}.string)") 4558 } else { 4559 eco_format!("use variant <- {module}.field(\"type\", {module}.string)") 4560 }; 4561 4562 let mut clauses = Vec::with_capacity(constructors_size); 4563 for constructor in iter::once(first).chain(rest) { 4564 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?; 4565 let name = to_snake_case(&constructor.name); 4566 clauses.push(eco_format!(r#" "{name}" -> {body}"#)); 4567 } 4568 4569 let failure_clause = self.failure_clause(custom_type); 4570 4571 let cases = clauses.join("\n"); 4572 Some(eco_format!( 4573 r#"{{ 4574 {discriminant} 4575 case variant {{ 4576{cases} 4577{failure_clause} 4578 }} 4579}}"#, 4580 )) 4581 } 4582 4583 fn constructor_decoder( 4584 &mut self, 4585 mode: EncodingMode, 4586 custom_type: &ast::TypedCustomType, 4587 constructor: &TypedRecordConstructor, 4588 nesting: usize, 4589 ) -> Option<EcoString> { 4590 let decode_module = self.printer.print_module(DECODE_MODULE); 4591 let constructor_name = &constructor.name; 4592 4593 // If the constructor was encoded as a plain string with no additional 4594 // fields it means there's nothing else to decode and we can just 4595 // succeed. 4596 if mode == EncodingMode::PlainString { 4597 return Some(eco_format!("{decode_module}.success({constructor_name})")); 4598 } 4599 4600 // Otherwise we have to decode all the constructor fields to build it. 4601 let mut fields = Vec::with_capacity(constructor.arguments.len()); 4602 for argument in constructor.arguments.iter() { 4603 let (_, name) = argument.label.as_ref()?; 4604 let field = RecordField { 4605 label: RecordLabel::Labeled(name), 4606 type_: &argument.type_, 4607 }; 4608 fields.push(field); 4609 } 4610 4611 let mut decoder_printer = DecoderPrinter::new( 4612 &mut self.printer, 4613 custom_type.name.clone(), 4614 self.module.name.clone(), 4615 self.compiler, 4616 ); 4617 4618 let decoders = fields 4619 .iter() 4620 .map(|field| decoder_printer.decode_field(field, nesting + 2)) 4621 .join("\n"); 4622 4623 let indent = " ".repeat(nesting); 4624 4625 Some(if decoders.is_empty() { 4626 eco_format!("{decode_module}.success({constructor_name})") 4627 } else { 4628 let field_names = fields 4629 .iter() 4630 .map(|field| format!("{}:", field.label.variable_name())) 4631 .join(", "); 4632 4633 eco_format!( 4634 "{{ 4635{decoders} 4636{indent} {decode_module}.success({constructor_name}({field_names})) 4637{indent}}}", 4638 ) 4639 }) 4640 } 4641 4642 /// Generates the failure/catch-all clause in a decoder function for a 4643 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()` 4644 /// clause executed when the `type` field does not match any of the known variants. 4645 /// 4646 /// # Arguments 4647 /// * `custom_type` - The root type we are printing a decoder for 4648 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString { 4649 let decode_module = self.printer.print_module(DECODE_MODULE); 4650 let type_name = &custom_type.name; 4651 4652 let mut decoder_printer = DecoderPrinter::new( 4653 &mut self.printer, 4654 type_name.clone(), 4655 self.module.name.clone(), 4656 self.compiler, 4657 ); 4658 4659 // The construction of the zero value might necessitate importing 4660 // modules that aren't currently in scope. We keep track of them here. 4661 let mut modules_to_import = HashSet::new(); 4662 // We also need to keep track of the path we are tracing through the types 4663 // to make sure we don't get stuck in an infinite loop building a recursive 4664 // type. 4665 let mut path = vec![]; 4666 4667 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type( 4668 &self.module.name, 4669 type_name, 4670 &mut path, 4671 &mut modules_to_import, 4672 ) { 4673 for module_name in modules_to_import { 4674 maybe_import(&mut self.edits, self.module, &module_name); 4675 } 4676 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,) 4677 } else { 4678 eco_format!( 4679 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"# 4680 ) 4681 } 4682 } 4683} 4684 4685impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> { 4686 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 4687 let range = self 4688 .edits 4689 .src_span_to_lsp_range(custom_type.full_location()); 4690 if !within(self.params.range, range) { 4691 return; 4692 } 4693 4694 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name)); 4695 let Some(function_body) = self.custom_type_decoder_body(custom_type) else { 4696 return; 4697 }; 4698 4699 let parameters = match custom_type.parameters.len() { 4700 0 => EcoString::new(), 4701 _ => eco_format!( 4702 "({})", 4703 custom_type 4704 .parameters 4705 .iter() 4706 .map(|(_, name)| name) 4707 .join(", ") 4708 ), 4709 }; 4710 4711 let decoder_type = self.printer.print_type(&Type::Named { 4712 publicity: Publicity::Public, 4713 package: STDLIB_PACKAGE_NAME.into(), 4714 module: DECODE_MODULE.into(), 4715 name: "Decoder".into(), 4716 arguments: vec![], 4717 inferred_variant: None, 4718 }); 4719 4720 let function = format!( 4721 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}", 4722 type_name = custom_type.name, 4723 ); 4724 4725 self.edits.insert(custom_type.end_position, function); 4726 maybe_import(&mut self.edits, self.module, DECODE_MODULE); 4727 4728 CodeActionBuilder::new("Generate dynamic decoder") 4729 .kind(CodeActionKind::Refactor) 4730 .preferred(false) 4731 .changes( 4732 self.params.text_document.uri.clone(), 4733 std::mem::take(&mut self.edits.edits), 4734 ) 4735 .push_to(self.actions); 4736 } 4737} 4738 4739/// If `module_name` is not already imported inside `module`, adds an edit to 4740/// add that import. 4741/// This function also makes sure not to import a module in itself. 4742/// 4743fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) { 4744 if module.ast.names.is_imported(module_name) || module.name == module_name { 4745 return; 4746 } 4747 4748 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers); 4749 let first_is_import = first_import_pos.is_some(); 4750 let import_location = first_import_pos.unwrap_or_default(); 4751 let after_import_newlines = add_newlines_after_import( 4752 import_location, 4753 first_is_import, 4754 edits.line_numbers, 4755 &module.code, 4756 ); 4757 4758 edits.edits.push(get_import_edit( 4759 import_location, 4760 module_name, 4761 &after_import_newlines, 4762 )); 4763} 4764 4765struct DecoderPrinter<'a, 'b, IO> { 4766 printer: &'a mut Printer<'b>, 4767 /// The name of the root type we are printing a decoder for 4768 type_name: EcoString, 4769 /// The module name of the root type we are printing a decoder for 4770 type_module: EcoString, 4771 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4772} 4773 4774const DYNAMIC_MODULE: &str = "gleam/dynamic"; 4775const DICT_MODULE: &str = "gleam/dict"; 4776const OPTION_MODULE: &str = "gleam/option"; 4777 4778struct RecordField<'a> { 4779 label: RecordLabel<'a>, 4780 type_: &'a Type, 4781} 4782 4783enum RecordLabel<'a> { 4784 Labeled(&'a str), 4785 Unlabeled(usize), 4786} 4787 4788impl RecordLabel<'_> { 4789 fn field_key(&self) -> EcoString { 4790 match self { 4791 RecordLabel::Labeled(label) => eco_format!("\"{label}\""), 4792 RecordLabel::Unlabeled(index) => { 4793 eco_format!("{index}") 4794 } 4795 } 4796 } 4797 4798 fn variable_name(&self) -> EcoString { 4799 match self { 4800 RecordLabel::Labeled(label) => (*label).into(), 4801 &RecordLabel::Unlabeled(mut index) => { 4802 let mut characters = Vec::new(); 4803 let alphabet_length = 26; 4804 let alphabet_offset = b'a'; 4805 loop { 4806 let alphabet_index = (index % alphabet_length) as u8; 4807 characters.push((alphabet_offset + alphabet_index) as char); 4808 index /= alphabet_length; 4809 4810 if index == 0 { 4811 break; 4812 } 4813 index -= 1; 4814 } 4815 characters.into_iter().rev().collect() 4816 } 4817 } 4818 } 4819} 4820 4821impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> { 4822 fn new( 4823 printer: &'a mut Printer<'b>, 4824 type_name: EcoString, 4825 type_module: EcoString, 4826 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4827 ) -> Self { 4828 Self { 4829 type_name, 4830 type_module, 4831 printer, 4832 compiler, 4833 } 4834 } 4835 4836 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString { 4837 let module_name = self.printer.print_module(DECODE_MODULE); 4838 if type_.is_bit_array() { 4839 eco_format!("{module_name}.bit_array") 4840 } else if type_.is_bool() { 4841 eco_format!("{module_name}.bool") 4842 } else if type_.is_float() { 4843 eco_format!("{module_name}.float") 4844 } else if type_.is_int() { 4845 eco_format!("{module_name}.int") 4846 } else if type_.is_string() { 4847 eco_format!("{module_name}.string") 4848 } else if type_.is_nil() { 4849 eco_format!("{module_name}.success(Nil)") 4850 } else { 4851 match type_.tuple_types() { 4852 Some(types) => { 4853 let fields = types 4854 .iter() 4855 .enumerate() 4856 .map(|(index, type_)| RecordField { 4857 type_, 4858 label: RecordLabel::Unlabeled(index), 4859 }) 4860 .collect_vec(); 4861 let decoders = fields 4862 .iter() 4863 .map(|field| self.decode_field(field, indent + 2)) 4864 .join("\n"); 4865 let mut field_names = fields.iter().map(|field| field.label.variable_name()); 4866 4867 eco_format!( 4868 "{{ 4869{decoders} 4870 4871{indent} {module_name}.success(#({fields})) 4872{indent}}}", 4873 fields = field_names.join(", "), 4874 indent = " ".repeat(indent) 4875 ) 4876 } 4877 _ => { 4878 let type_information = type_.named_type_information(); 4879 let type_information = 4880 type_information.as_ref().map(|(module, name, arguments)| { 4881 (module.as_str(), name.as_str(), arguments.as_slice()) 4882 }); 4883 4884 match type_information { 4885 Some(("gleam/dynamic", "Dynamic", _)) => { 4886 eco_format!("{module_name}.dynamic") 4887 } 4888 Some(("gleam", "List", [element])) => { 4889 eco_format!("{module_name}.list({})", self.decoder_for(element, indent)) 4890 } 4891 Some(("gleam/option", "Option", [some])) => { 4892 eco_format!( 4893 "{module_name}.optional({})", 4894 self.decoder_for(some, indent) 4895 ) 4896 } 4897 Some(("gleam/dict", "Dict", [key, value])) => { 4898 eco_format!( 4899 "{module_name}.dict({}, {})", 4900 self.decoder_for(key, indent), 4901 self.decoder_for(value, indent) 4902 ) 4903 } 4904 Some((module, name, _)) 4905 if module == self.type_module && name == self.type_name => 4906 { 4907 eco_format!("{}_decoder()", to_snake_case(name)) 4908 } 4909 _ => eco_format!( 4910 r#"todo as "Decoder for {}""#, 4911 self.printer.print_type(type_) 4912 ), 4913 } 4914 } 4915 } 4916 } 4917 } 4918 4919 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4920 let decoder = self.decoder_for(field.type_, indent); 4921 4922 eco_format!( 4923 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#, 4924 indent = " ".repeat(indent), 4925 variable = field.label.variable_name(), 4926 field = field.label.field_key(), 4927 module = self.printer.print_module(DECODE_MODULE) 4928 ) 4929 } 4930 4931 /// Performs best-effort generation of zero values for a given type. 4932 /// Will succeed for all prelude types and most stdlib types. 4933 /// For specifics on how custom user-defined types are handled, see 4934 /// `zero_value_for_custom_type`. 4935 fn zero_value_for_type( 4936 &mut self, 4937 type_: &Type, 4938 // Keeps track of types visited already to ensure we don't end up in an infinite 4939 // loop due to recursive types 4940 path: &mut Vec<(EcoString, EcoString)>, 4941 modules_to_import: &mut HashSet<EcoString>, 4942 ) -> Option<EcoString> { 4943 if type_.is_bit_array() { 4944 return Some("<<>>".into()); 4945 } 4946 if type_.is_bool() { 4947 return Some("False".into()); 4948 } 4949 if type_.is_float() { 4950 return Some("0.0".into()); 4951 } 4952 if type_.is_int() { 4953 return Some("0".into()); 4954 } 4955 if type_.is_string() { 4956 return Some("\"\"".into()); 4957 } 4958 if type_.is_list() { 4959 return Some("[]".into()); 4960 } 4961 if type_.is_nil() { 4962 return Some("Nil".into()); 4963 } 4964 4965 if let Some(types) = type_.tuple_types() { 4966 // We try generating zero values for the tuple members and exit early if any of them fail. 4967 let field_zeroes = types 4968 .iter() 4969 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import)) 4970 .collect_vec(); 4971 4972 if field_zeroes.len() < types.len() { 4973 return None; 4974 } else { 4975 return Some(eco_format!("#({})", field_zeroes.iter().join(", "))); 4976 } 4977 } 4978 4979 let (module, name, _) = type_.named_type_information()?; 4980 match (module.as_str(), name.as_str()) { 4981 (OPTION_MODULE, "Option") => { 4982 let _ = modules_to_import.insert(OPTION_MODULE.into()); 4983 Some(eco_format!( 4984 "{}.None", 4985 self.printer.print_module(OPTION_MODULE) 4986 )) 4987 } 4988 4989 (DYNAMIC_MODULE, "Dynamic") => { 4990 let _ = modules_to_import.insert(DYNAMIC_MODULE.into()); 4991 Some(eco_format!( 4992 "{}.nil()", 4993 self.printer.print_module(DYNAMIC_MODULE) 4994 )) 4995 } 4996 4997 (DICT_MODULE, "Dict") => { 4998 let _ = modules_to_import.insert(DICT_MODULE.into()); 4999 Some(eco_format!( 5000 "{}.new()", 5001 self.printer.print_module(DICT_MODULE) 5002 )) 5003 } 5004 5005 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import), 5006 } 5007 } 5008 5009 /// Best-effort zero value generation for user-defined types in the 5010 /// current package. 5011 fn zero_value_for_custom_type( 5012 &mut self, 5013 custom_type_module: &EcoString, 5014 custom_type_name: &EcoString, 5015 path: &mut Vec<(EcoString, EcoString)>, 5016 modules_to_import: &mut HashSet<EcoString>, 5017 ) -> Option<EcoString> { 5018 // First we check that we have not already visited this type before to 5019 // avoid cycles. 5020 let already_seen_type = path.iter().any(|(path_module, path_type_name)| { 5021 path_module == custom_type_module && path_type_name == custom_type_name 5022 }); 5023 5024 if already_seen_type { 5025 return None; 5026 } 5027 5028 let type_is_inside_current_module = &self.type_module == custom_type_module; 5029 5030 let current_module_interface = self 5031 .compiler 5032 .modules 5033 .get(&self.type_module) 5034 .map(|module| &module.ast.type_info)?; 5035 5036 let type_module_interface = if !type_is_inside_current_module { 5037 self.compiler.get_module_interface(custom_type_module)? 5038 } else { 5039 current_module_interface 5040 }; 5041 5042 // We only try and generate zero values for user-defined types in the current 5043 // package. If you are expanding the scope of this functionality to 5044 // remove this limitation, make sure to check for internal modules. 5045 if current_module_interface.package != type_module_interface.package { 5046 return None; 5047 } 5048 5049 let constructors = type_module_interface 5050 .types_value_constructors 5051 .get(custom_type_name)?; 5052 5053 // Opaque types cannot be constructed outside the module they were defined in, 5054 // so we will be unable to produce a zero value. 5055 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque { 5056 return None; 5057 } 5058 5059 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor 5060 // to construct our zero value to reduce visual noise, but this might not always 5061 // be possible. So we check all constructors in increasing order of size to 5062 // find the first one that succeeds, and then short circuit. 5063 constructors 5064 .variants 5065 .iter() 5066 .sorted_by_key(|v| v.parameters.len()) 5067 .find_map(|zero_constructor| { 5068 self.zero_value_for_custom_type_constructor( 5069 custom_type_module, 5070 custom_type_name, 5071 zero_constructor, 5072 type_is_inside_current_module, 5073 path, 5074 modules_to_import, 5075 ) 5076 }) 5077 } 5078 5079 /// Attempts to construct a zero value for one specific constructor 5080 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs 5081 /// _important checks on type visibility that this method does NOT_. 5082 /// This is a helper method extracted for readability. 5083 fn zero_value_for_custom_type_constructor( 5084 &mut self, 5085 custom_type_module: &EcoString, 5086 custom_type_name: &EcoString, 5087 zero_constructor: &type_::TypeValueConstructor, 5088 type_is_inside_current_module: bool, 5089 path: &mut Vec<(EcoString, EcoString)>, 5090 modules_to_import: &mut HashSet<EcoString>, 5091 ) -> Option<EcoString> { 5092 path.push((custom_type_module.clone(), custom_type_name.clone())); 5093 5094 // Try to generate zero values for all fields in the constructor 5095 let zero_params = zero_constructor 5096 .parameters 5097 .iter() 5098 .map_while(|parameter| { 5099 let zero = self.zero_value_for_type(&parameter.type_, path, modules_to_import)?; 5100 5101 if let Some(label) = &parameter.label { 5102 Some(eco_format!("{label}: {zero}")) 5103 } else { 5104 Some(zero) 5105 } 5106 }) 5107 .collect_vec(); 5108 5109 // We need to make sure to clean up the path once we've finished 5110 // "visiting" this type. 5111 let _ = path.pop(); 5112 5113 // Only proceed if we were able to construct every field successfully 5114 if zero_params.len() < zero_constructor.parameters.len() { 5115 return None; 5116 } 5117 5118 let zero_constructor = if !type_is_inside_current_module { 5119 // Type constructors from other modules need to be qualified appropriately, 5120 // and they might need to be brought into scope. 5121 let _ = modules_to_import.insert(custom_type_module.clone()); 5122 eco_format!( 5123 "{}.{}", 5124 self.printer.print_module(custom_type_module), 5125 zero_constructor.name 5126 ) 5127 } else { 5128 eco_format!("{}", zero_constructor.name) 5129 }; 5130 5131 if zero_params.is_empty() { 5132 Some(eco_format!("{zero_constructor}")) 5133 } else { 5134 let zero_args = zero_params.iter().join(", "); 5135 Some(eco_format!("{zero_constructor}({zero_args})")) 5136 } 5137 } 5138} 5139 5140/// Builder for code action to apply the "Generate to-JSON function" action. 5141/// 5142pub struct GenerateJsonEncoder<'a> { 5143 module: &'a Module, 5144 params: &'a CodeActionParams, 5145 edits: TextEdits<'a>, 5146 printer: Printer<'a>, 5147 actions: &'a mut Vec<CodeAction>, 5148 config: &'a PackageConfig, 5149} 5150 5151const JSON_MODULE: &str = "gleam/json"; 5152const JSON_PACKAGE_NAME: &str = "gleam_json"; 5153 5154#[derive(Eq, PartialEq, Copy, Clone)] 5155enum EncodingMode { 5156 PlainString, 5157 ObjectWithTypeTag, 5158 ObjectWithNoTypeTag, 5159} 5160 5161impl EncodingMode { 5162 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self { 5163 match type_.constructors.as_slice() { 5164 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString, 5165 [_constructor] => EncodingMode::ObjectWithNoTypeTag, 5166 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => { 5167 EncodingMode::PlainString 5168 } 5169 _constructors => EncodingMode::ObjectWithTypeTag, 5170 } 5171 } 5172} 5173 5174impl<'a> GenerateJsonEncoder<'a> { 5175 pub fn new( 5176 module: &'a Module, 5177 line_numbers: &'a LineNumbers, 5178 params: &'a CodeActionParams, 5179 actions: &'a mut Vec<CodeAction>, 5180 config: &'a PackageConfig, 5181 ) -> Self { 5182 // Since we are generating a new function, type variables from other 5183 // functions and constants are irrelevant to the types we print. 5184 let printer = Printer::new_without_type_variables(&module.ast.names); 5185 Self { 5186 module, 5187 params, 5188 edits: TextEdits::new(line_numbers), 5189 printer, 5190 actions, 5191 config, 5192 } 5193 } 5194 5195 pub fn code_actions(&mut self) { 5196 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME) 5197 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME) 5198 { 5199 self.visit_typed_module(&self.module.ast); 5200 } 5201 } 5202 5203 fn custom_type_encoder_body( 5204 &mut self, 5205 record_name: EcoString, 5206 custom_type: &CustomType<Arc<Type>>, 5207 ) -> Option<EcoString> { 5208 // We cannot generate a decoder for an external type with no constructors! 5209 let constructors_size = custom_type.constructors.len(); 5210 let (first, rest) = custom_type.constructors.split_first()?; 5211 let mode = EncodingMode::for_custom_type(custom_type); 5212 5213 // We generate an encoder for a type with a single constructor: it does not 5214 // require pattern matching on the argument as we can access all its fields 5215 // with the usual record access syntax. 5216 if rest.is_empty() { 5217 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?; 5218 let unpacking = if first.arguments.is_empty() { 5219 "" 5220 } else { 5221 &eco_format!( 5222 "let {name}({fields}:) = {record_name}\n ", 5223 name = first.name, 5224 fields = first 5225 .arguments 5226 .iter() 5227 .filter_map(|argument| { 5228 argument.label.as_ref().map(|(_location, label)| label) 5229 }) 5230 .join(":, ") 5231 ) 5232 }; 5233 return Some(eco_format!("{unpacking}{encoder}")); 5234 } 5235 5236 // Otherwise we generate an encoder for a type with multiple constructors: 5237 // it will need to pattern match on the various constructors and encode each 5238 // one separately. 5239 let mut clauses = Vec::with_capacity(constructors_size); 5240 for constructor in iter::once(first).chain(rest) { 5241 let RecordConstructor { name, .. } = constructor; 5242 let encoder = 5243 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?; 5244 if constructor.arguments.is_empty() { 5245 clauses.push(eco_format!(" {name} -> {encoder}")); 5246 } else { 5247 let unpacking = constructor 5248 .arguments 5249 .iter() 5250 .filter_map(|argument| { 5251 argument.label.as_ref().map(|(_location, label)| { 5252 if is_nil_like(&argument.type_) { 5253 eco_format!("{}: _", label) 5254 } else { 5255 eco_format!("{}:", label) 5256 } 5257 }) 5258 }) 5259 .join(", "); 5260 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}")); 5261 } 5262 } 5263 5264 let clauses = clauses.join("\n"); 5265 Some(eco_format!( 5266 "case {record_name} {{ 5267{clauses} 5268 }}", 5269 )) 5270 } 5271 5272 fn constructor_encoder( 5273 &mut self, 5274 mode: EncodingMode, 5275 constructor: &TypedRecordConstructor, 5276 type_name: EcoString, 5277 nesting: usize, 5278 ) -> Option<EcoString> { 5279 let json_module = self.printer.print_module(JSON_MODULE); 5280 let tag = to_snake_case(&constructor.name); 5281 let indent = " ".repeat(nesting); 5282 5283 // If the variant is encoded as a simple json string we just call the 5284 // `json.string` with the variant tag as an argument. 5285 if mode == EncodingMode::PlainString { 5286 return Some(eco_format!("{json_module}.string(\"{tag}\")")); 5287 } 5288 5289 // Otherwise we turn it into an object with a `type` tag field. 5290 let mut encoder_printer = 5291 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone()); 5292 5293 // These are the fields of the json object to encode. 5294 let mut fields = Vec::with_capacity(constructor.arguments.len()); 5295 if mode == EncodingMode::ObjectWithTypeTag { 5296 // Any needed type tag is always going to be the first field in the object 5297 fields.push(eco_format!( 5298 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))" 5299 )); 5300 } 5301 5302 for argument in constructor.arguments.iter() { 5303 let (_, label) = argument.label.as_ref()?; 5304 let field = RecordField { 5305 label: RecordLabel::Labeled(label), 5306 type_: &argument.type_, 5307 }; 5308 let encoder = encoder_printer.encode_field(&field, nesting + 2); 5309 fields.push(encoder); 5310 } 5311 5312 let fields = fields.join(",\n"); 5313 Some(eco_format!( 5314 "{json_module}.object([ 5315{fields}, 5316{indent}])" 5317 )) 5318 } 5319} 5320 5321/// When generating an encoder, we need to know when we can ignore the fields 5322/// destructured from a constructor. 5323/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil` 5324/// types, then we will never need to use the field in our encoder function. 5325fn is_nil_like(type_: &Type) -> bool { 5326 if type_.is_nil() { 5327 return true; 5328 } 5329 5330 match type_.tuple_types() { 5331 Some(types) => types.iter().all(|type_| is_nil_like(type_)), 5332 _ => false, 5333 } 5334} 5335 5336impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> { 5337 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 5338 let range = self 5339 .edits 5340 .src_span_to_lsp_range(custom_type.full_location()); 5341 if !within(self.params.range, range) { 5342 return; 5343 } 5344 5345 let record_name = to_snake_case(&custom_type.name); 5346 let name = eco_format!("{record_name}_to_json"); 5347 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else { 5348 return; 5349 }; 5350 5351 let json_type = self.printer.print_type(&Type::Named { 5352 publicity: Publicity::Public, 5353 package: JSON_PACKAGE_NAME.into(), 5354 module: JSON_MODULE.into(), 5355 name: "Json".into(), 5356 arguments: vec![], 5357 inferred_variant: None, 5358 }); 5359 5360 let type_ = if custom_type.parameters.is_empty() { 5361 custom_type.name.clone() 5362 } else { 5363 let parameters = custom_type 5364 .parameters 5365 .iter() 5366 .map(|(_, name)| name) 5367 .join(", "); 5368 eco_format!("{}({})", custom_type.name, parameters) 5369 }; 5370 5371 let function = format!( 5372 " 5373 5374fn {name}({record_name}: {type_}) -> {json_type} {{ 5375 {encoder} 5376}}", 5377 ); 5378 5379 self.edits.insert(custom_type.end_position, function); 5380 maybe_import(&mut self.edits, self.module, JSON_MODULE); 5381 5382 CodeActionBuilder::new("Generate to-JSON function") 5383 .kind(CodeActionKind::Refactor) 5384 .preferred(false) 5385 .changes( 5386 self.params.text_document.uri.clone(), 5387 std::mem::take(&mut self.edits.edits), 5388 ) 5389 .push_to(self.actions); 5390 } 5391} 5392 5393struct JsonEncoderPrinter<'a, 'b> { 5394 printer: &'a mut Printer<'b>, 5395 /// The name of the root type we are printing an encoder for 5396 type_name: EcoString, 5397 /// The module name of the root type we are printing an encoder for 5398 type_module: EcoString, 5399} 5400 5401impl<'a, 'b> JsonEncoderPrinter<'a, 'b> { 5402 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self { 5403 Self { 5404 type_name, 5405 type_module, 5406 printer, 5407 } 5408 } 5409 5410 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString { 5411 let module_name = self.printer.print_module(JSON_MODULE); 5412 let is_capture = encoded_value == "_"; 5413 let maybe_capture = |mut function: EcoString| { 5414 if is_capture { 5415 function 5416 } else { 5417 function.push('('); 5418 function.push_str(encoded_value); 5419 function.push(')'); 5420 function 5421 } 5422 }; 5423 5424 if type_.is_bool() { 5425 maybe_capture(eco_format!("{module_name}.bool")) 5426 } else if type_.is_float() { 5427 maybe_capture(eco_format!("{module_name}.float")) 5428 } else if type_.is_int() { 5429 maybe_capture(eco_format!("{module_name}.int")) 5430 } else if type_.is_string() { 5431 maybe_capture(eco_format!("{module_name}.string")) 5432 } else if type_.is_nil() { 5433 if is_capture { 5434 eco_format!("fn(_) {{ {module_name}.null() }}") 5435 } else { 5436 eco_format!("{module_name}.null()") 5437 } 5438 } else { 5439 match type_.tuple_types() { 5440 Some(types) => { 5441 let (tuple, new_indent) = if is_capture { 5442 ("value", indent + 4) 5443 } else { 5444 (encoded_value, indent + 2) 5445 }; 5446 5447 // We need to iterate over all of the tuple's fields 5448 // to obtain an encoder for each one, so we reuse the 5449 // iteration to check whether this tuple can be ignored 5450 // in the encoder without calling `is_nil_like`. 5451 let mut encoders = Vec::new(); 5452 let all_values_are_nil = types.iter().enumerate().fold( 5453 true, 5454 |all_values_are_nil, (index, type_)| { 5455 encoders.push(self.encoder_for( 5456 &format!("{tuple}.{index}"), 5457 type_, 5458 new_indent, 5459 )); 5460 all_values_are_nil && is_nil_like(type_) 5461 }, 5462 ); 5463 5464 if is_capture { 5465 eco_format!( 5466 "fn({value}) {{ 5467{indent} {module_name}.preprocessed_array([ 5468{indent} {encoders}, 5469{indent} ]) 5470{indent}}}", 5471 value = if all_values_are_nil { "_" } else { "value" }, 5472 indent = " ".repeat(indent), 5473 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 5474 ) 5475 } else { 5476 eco_format!( 5477 "{module_name}.preprocessed_array([ 5478{indent} {encoders}, 5479{indent}])", 5480 indent = " ".repeat(indent), 5481 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 5482 ) 5483 } 5484 } 5485 _ => { 5486 let type_information = type_.named_type_information(); 5487 let type_information: Option<(&str, &str, &[Arc<Type>])> = 5488 type_information.as_ref().map(|(module, name, arguments)| { 5489 (module.as_str(), name.as_str(), arguments.as_slice()) 5490 }); 5491 5492 match type_information { 5493 Some(("gleam", "List", [element])) => { 5494 eco_format!( 5495 "{module_name}.array({encoded_value}, {map_function})", 5496 map_function = self.encoder_for("_", element, indent) 5497 ) 5498 } 5499 Some(("gleam/option", "Option", [some])) => { 5500 eco_format!( 5501 "case {encoded_value} {{ 5502{indent} {none} -> {module_name}.null() 5503{indent} {some}({value}) -> {encoder} 5504{indent}}}", 5505 indent = " ".repeat(indent), 5506 none = self 5507 .printer 5508 .print_constructor(&"gleam/option".into(), &"None".into()), 5509 some = self 5510 .printer 5511 .print_constructor(&"gleam/option".into(), &"Some".into()), 5512 value = if is_nil_like(some) { "_" } else { "value" }, 5513 encoder = self.encoder_for("value", some, indent + 2) 5514 ) 5515 } 5516 Some(("gleam/dict", "Dict", [key, value])) => { 5517 let stringify_function = match key 5518 .named_type_information() 5519 .as_ref() 5520 .map(|(module, name, arguments)| { 5521 (module.as_str(), name.as_str(), arguments.as_slice()) 5522 }) { 5523 Some(("gleam", "String", [])) => "fn(string) { string }", 5524 _ => &format!( 5525 r#"todo as "Function to stringify {}""#, 5526 self.printer.print_type(key) 5527 ), 5528 }; 5529 eco_format!( 5530 "{module_name}.dict({encoded_value}, {stringify_function}, {})", 5531 self.encoder_for("_", value, indent) 5532 ) 5533 } 5534 Some((module, name, _)) 5535 if module == self.type_module && name == self.type_name => 5536 { 5537 maybe_capture(eco_format!("{}_to_json", to_snake_case(name))) 5538 } 5539 _ => eco_format!( 5540 r#"todo as "Encoder for {}""#, 5541 self.printer.print_type(type_) 5542 ), 5543 } 5544 } 5545 } 5546 } 5547 } 5548 5549 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 5550 let field_name = field.label.variable_name(); 5551 let encoder = self.encoder_for(&field_name, field.type_, indent); 5552 5553 eco_format!( 5554 r#"{indent}#("{field_name}", {encoder})"#, 5555 indent = " ".repeat(indent), 5556 ) 5557 } 5558} 5559 5560/// Builder for code action to pattern match on things like (anonymous) function 5561/// arguments or variables. 5562/// For example: 5563/// 5564/// ```gleam 5565/// pub fn wibble(arg: #(key, value)) { 5566/// // ^ [pattern match on argument] 5567/// } 5568/// 5569/// // Generates 5570/// 5571/// pub fn wibble(arg: #(key, value)) { 5572/// let #(value_0, value_1) = arg 5573/// } 5574/// ``` 5575/// 5576/// Another example with variables: 5577/// 5578/// ```gleam 5579/// pub fn main() { 5580/// let pair = #(1, 3) 5581/// // ^ [pattern match on value] 5582/// } 5583/// 5584/// // Generates 5585/// 5586/// pub fn main() { 5587/// let pair = #(1, 3) 5588/// let #(value_0, value_1) = pair 5589/// } 5590/// ``` 5591/// 5592pub struct PatternMatchOnValue<'a, A> { 5593 module: &'a Module, 5594 params: &'a CodeActionParams, 5595 compiler: &'a LspProjectCompiler<A>, 5596 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>, 5597 selected_value: Option<PatternMatchedValue<'a>>, 5598 edits: TextEdits<'a>, 5599} 5600 5601/// A value we might want to pattern match on. 5602/// Each variant will also contain all the info needed to know how to properly 5603/// print and format the corresponding pattern matching code; that's why you'll 5604/// see `Range`s and `SrcSpan` besides the type of the thing being matched. 5605/// 5606#[derive(Clone)] 5607pub enum PatternMatchedValue<'a> { 5608 /// A statement we can match on. For example, function calls, record 5609 /// and tuple accesses: 5610 /// 5611 /// ```gleam 5612 /// pub fn wibble() { 5613 /// wobble(1, 2) 5614 /// //^^^^ This 5615 /// } 5616 /// ``` 5617 /// 5618 Statement { 5619 /// The span covering the entire statement: 5620 /// ```gleam 5621 /// wobble(1, 2) 5622 /// //^^^^^^^^^^^^ This 5623 /// ``` 5624 location: SrcSpan, 5625 /// The statement's type defining what we will be pattern matching 5626 /// on. 5627 type_: Arc<Type>, 5628 }, 5629 FunctionArgument { 5630 /// The argument being pattern matched on. 5631 /// 5632 arg: &'a TypedArg, 5633 /// The first statement inside the function body. Used to correctly 5634 /// position the inserted pattern matching. 5635 /// 5636 first_statement: &'a TypedStatement, 5637 /// The range of the entire function holding the argument. 5638 /// 5639 function_range: Range, 5640 }, 5641 LetVariable { 5642 variable_name: &'a EcoString, 5643 variable_type: Arc<Type>, 5644 /// The location of the entire let assignment the variable is part of, 5645 /// so that we can add the pattern matching _after_ it. 5646 /// 5647 assignment_location: SrcSpan, 5648 }, 5649 /// A variable that is bound in a case branch's pattern. For example: 5650 /// ```gleam 5651 /// case wibble { 5652 /// wobble -> 1 5653 /// // ^^^^^ This! 5654 /// } 5655 /// ``` 5656 /// 5657 ClausePatternVariable { 5658 variable_type: Arc<Type>, 5659 variable_location: PatternLocation, 5660 clause_location: SrcSpan, 5661 /// All the names in the clause that are already taken by pattern variables. 5662 /// We need this to avoid generating invalid code were two pattern variables 5663 /// have the same name. 5664 /// 5665 /// For example: 5666 /// 5667 /// ```gleam 5668 /// case wibble { 5669 /// [first, ..rest] -> todo 5670 /// ^^^^^ When expanding `first` we can't add any variable pattern 5671 /// called `rest` as it would clash with the `rest` tail that is 5672 /// already there. 5673 /// } 5674 /// ``` 5675 /// 5676 bound_variables: Vec<BoundVariable>, 5677 }, 5678 UseVariable { 5679 variable_name: &'a EcoString, 5680 variable_type: Arc<Type>, 5681 /// The location of the entire use expression the variable is part of, 5682 /// so that we can add the pattern matching _after_ it. 5683 /// 5684 use_location: SrcSpan, 5685 }, 5686} 5687 5688#[derive(Clone)] 5689pub enum PatternLocation { 5690 /// Any pattern that doesn't need any special handling. 5691 /// 5692 Regular { location: SrcSpan }, 5693 /// List tails need some care to not generate invalid syntax when pattern 5694 /// matched on in case expressions. 5695 /// 5696 ListTail { 5697 /// This location covers the entire list tail pattern, including the `..` 5698 location: SrcSpan, 5699 }, 5700 /// When the pattern being matched is a discard. For example: 5701 /// ```gleam 5702 /// case wibble { 5703 /// Ok(_) -> todo 5704 /// // ^ Hovering this! 5705 /// } 5706 /// ``` 5707 Discard { location: SrcSpan }, 5708} 5709 5710impl PatternLocation { 5711 fn regular(location: SrcSpan) -> Self { 5712 Self::Regular { location } 5713 } 5714 5715 fn is_discard(&self) -> bool { 5716 match self { 5717 PatternLocation::Regular { .. } | PatternLocation::ListTail { .. } => false, 5718 PatternLocation::Discard { .. } => true, 5719 } 5720 } 5721} 5722 5723impl<'a, IO> PatternMatchOnValue<'a, IO> { 5724 pub fn new( 5725 module: &'a Module, 5726 line_numbers: &'a LineNumbers, 5727 params: &'a CodeActionParams, 5728 compiler: &'a LspProjectCompiler<IO>, 5729 ) -> Self { 5730 Self { 5731 module, 5732 params, 5733 compiler, 5734 selected_value: None, 5735 pattern_variable_under_cursor: None, 5736 edits: TextEdits::new(line_numbers), 5737 } 5738 } 5739 5740 pub fn code_actions(mut self) -> Vec<CodeAction> { 5741 self.visit_typed_module(&self.module.ast); 5742 5743 let action_title = match self.selected_value.clone() { 5744 Some(PatternMatchedValue::FunctionArgument { 5745 arg, 5746 first_statement: function_body, 5747 function_range, 5748 }) => { 5749 self.match_on_function_argument(arg, function_body, function_range); 5750 "Pattern match on argument" 5751 } 5752 Some( 5753 PatternMatchedValue::LetVariable { 5754 variable_name, 5755 variable_type, 5756 assignment_location: location, 5757 } 5758 | PatternMatchedValue::UseVariable { 5759 variable_name, 5760 variable_type, 5761 use_location: location, 5762 }, 5763 ) => { 5764 self.match_on_let_variable(variable_name, variable_type, location); 5765 "Pattern match on variable" 5766 } 5767 5768 Some(PatternMatchedValue::Statement { location, type_ }) => { 5769 self.match_on_statement(location, type_); 5770 "Pattern match on value" 5771 } 5772 5773 Some(PatternMatchedValue::ClausePatternVariable { 5774 variable_type, 5775 variable_location, 5776 clause_location, 5777 bound_variables, 5778 }) => { 5779 let title = if variable_location.is_discard() { 5780 "Pattern match on value" 5781 } else { 5782 "Pattern match on variable" 5783 }; 5784 5785 self.match_on_clause_variable( 5786 variable_type, 5787 variable_location, 5788 clause_location, 5789 &bound_variables, 5790 ); 5791 5792 title 5793 } 5794 5795 None => return vec![], 5796 }; 5797 5798 if self.edits.edits.is_empty() { 5799 return vec![]; 5800 } 5801 5802 let mut action = Vec::with_capacity(1); 5803 CodeActionBuilder::new(action_title) 5804 .kind(CodeActionKind::RefactorRewrite) 5805 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5806 .preferred(false) 5807 .push_to(&mut action); 5808 action 5809 } 5810 5811 fn match_on_function_argument( 5812 &mut self, 5813 arg: &TypedArg, 5814 first_statement: &TypedStatement, 5815 function_range: Range, 5816 ) { 5817 let Some(arg_name) = arg.get_variable_name() else { 5818 return; 5819 }; 5820 5821 let Some(patterns) = 5822 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new()) 5823 else { 5824 return; 5825 }; 5826 5827 let first_statement_location = first_statement.location(); 5828 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location); 5829 5830 // If we're trying to insert the pattern matching on the same 5831 // line as the one where the function is defined we will want to 5832 // put it on a new line instead. So in that case the nesting will 5833 // be the default 2 spaces. 5834 let needs_newline = function_range.start.line == first_statement_range.start.line; 5835 let nesting = if needs_newline { 5836 String::from(" ") 5837 } else { 5838 " ".repeat(first_statement_range.start.character as usize) 5839 }; 5840 5841 let pattern_matching = if patterns.len() == 1 { 5842 let pattern = patterns.first(); 5843 format!("let {pattern} = {arg_name}") 5844 } else { 5845 let patterns = patterns 5846 .iter() 5847 .map(|p| format!(" {nesting}{p} -> todo")) 5848 .join("\n"); 5849 format!("case {arg_name} {{\n{patterns}\n{nesting}}}") 5850 }; 5851 5852 let pattern_matching = if needs_newline { 5853 format!("\n{nesting}{pattern_matching}") 5854 } else { 5855 pattern_matching 5856 }; 5857 5858 let has_empty_body = match first_statement { 5859 ast::Statement::Expression(TypedExpr::Todo { 5860 kind: TodoKind::EmptyFunction { .. }, 5861 .. 5862 }) => true, 5863 ast::Statement::Expression(_) 5864 | ast::Statement::Assignment(_) 5865 | ast::Statement::Use(_) 5866 | ast::Statement::Assert(_) => false, 5867 }; 5868 5869 // If the pattern matching is added to a function with an empty 5870 // body then we do not add any nesting after it, or we would be 5871 // increasing the nesting of the closing `}`! 5872 let pattern_matching = if has_empty_body { 5873 format!("{pattern_matching}\n") 5874 } else { 5875 format!("{pattern_matching}\n{nesting}") 5876 }; 5877 5878 self.edits 5879 .insert(first_statement_location.start, pattern_matching); 5880 } 5881 5882 fn match_on_let_variable( 5883 &mut self, 5884 variable_name: &EcoString, 5885 variable_type: Arc<Type>, 5886 assignment_location: SrcSpan, 5887 ) { 5888 let Some(patterns) = 5889 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new()) 5890 else { 5891 return; 5892 }; 5893 5894 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); 5895 let nesting = " ".repeat(assignment_range.start.character as usize); 5896 let pattern_matching = if patterns.len() == 1 { 5897 let pattern = patterns.first(); 5898 format!("let {pattern} = {variable_name}") 5899 } else { 5900 let patterns = patterns 5901 .iter() 5902 .map(|p| format!(" {nesting}{p} -> todo")) 5903 .join("\n"); 5904 format!("case {variable_name} {{\n{patterns}\n{nesting}}}") 5905 }; 5906 5907 self.edits.insert( 5908 assignment_location.end, 5909 format!("\n{nesting}{pattern_matching}"), 5910 ); 5911 } 5912 5913 fn match_on_statement(&mut self, statement_location: SrcSpan, type_: Arc<Type>) { 5914 let Some(patterns) = 5915 self.type_to_destructure_patterns(type_.as_ref(), &mut NameGenerator::new()) 5916 else { 5917 return; 5918 }; 5919 5920 if patterns.len() == 1 { 5921 let pattern = patterns.first(); 5922 self.edits 5923 .insert(statement_location.start, format!("let {pattern} = ")); 5924 } else { 5925 let statement_range = self.edits.src_span_to_lsp_range(statement_location); 5926 let nesting = " ".repeat(statement_range.start.character as usize); 5927 let patterns = patterns 5928 .iter() 5929 .map(|p| format!(" {nesting}{p} -> todo")) 5930 .join("\n"); 5931 self.edits.insert(statement_location.start, "case ".into()); 5932 self.edits.insert( 5933 statement_location.end, 5934 format!(" {{\n{patterns}\n{nesting}}}"), 5935 ); 5936 } 5937 } 5938 5939 fn match_on_clause_variable( 5940 &mut self, 5941 variable_type: Arc<Type>, 5942 variable_location: PatternLocation, 5943 clause_location: SrcSpan, 5944 bound_variables: &[BoundVariable], 5945 ) { 5946 let mut names = NameGenerator::new(); 5947 names.reserve_bound_variables(bound_variables); 5948 5949 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) { 5950 // Here we're dealing with a special case: if someone wants to expand the tail 5951 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`. 5952 // That would result in invalid syntax. So we have to generate list patterns 5953 // that have no square brackets. 5954 let first = names.rename_to_avoid_shadowing("first".into()); 5955 let rest = names.rename_to_avoid_shadowing("rest".into()); 5956 vec1!["".into(), eco_format!("{first}, ..{rest}")] 5957 } else if let Some(patterns) = 5958 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names) 5959 { 5960 patterns 5961 } else { 5962 return; 5963 }; 5964 5965 let clause_range = self.edits.src_span_to_lsp_range(clause_location); 5966 let nesting = " ".repeat(clause_range.start.character as usize); 5967 5968 let variable_location = match variable_location { 5969 PatternLocation::Regular { location } 5970 | PatternLocation::ListTail { location } 5971 | PatternLocation::Discard { location } => location, 5972 }; 5973 5974 let variable_start = (variable_location.start - clause_location.start) as usize; 5975 let variable_end = variable_start + variable_location.len(); 5976 5977 let clause_code = code_at(self.module, clause_location); 5978 let patterns = patterns 5979 .iter() 5980 .map(|pattern| { 5981 let mut clause_code = clause_code.to_string(); 5982 // If we're replacing a variable that's using the shorthand 5983 // syntax we want to add a space to separate it from the 5984 // preceding `:`. 5985 let pattern = if variable_start == variable_end { 5986 &eco_format!(" {pattern}") 5987 } else { 5988 pattern 5989 }; 5990 5991 clause_code.replace_range(variable_start..variable_end, pattern); 5992 clause_code 5993 }) 5994 .join(&format!("\n{nesting}")); 5995 5996 self.edits.replace(clause_location, patterns); 5997 } 5998 5999 /// Will produce a pattern that can be used on the left hand side of a let 6000 /// assignment to destructure a value of the given type. For example given 6001 /// this type: 6002 /// 6003 /// ```gleam 6004 /// pub type Wibble { 6005 /// Wobble(Int, label: String) 6006 /// } 6007 /// ``` 6008 /// 6009 /// The produced pattern will look like this: `Wobble(value_0, label:)`. 6010 /// The pattern will use the correct qualified/unqualified name for the 6011 /// constructor if it comes from another package. 6012 /// 6013 /// The function will only produce a list of patterns that can be used from 6014 /// the current module. So if the type comes from another module it must be 6015 /// public! Otherwise this function will return an empty vec. 6016 /// 6017 fn type_to_destructure_patterns( 6018 &mut self, 6019 type_: &Type, 6020 names: &mut NameGenerator, 6021 ) -> Option<Vec1<EcoString>> { 6022 match type_ { 6023 Type::Fn { .. } => None, 6024 6025 // Pattern matchin on `Nil` is not all that useful. 6026 Type::Named { .. } if type_.is_nil() => None, 6027 6028 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names), 6029 6030 // We special case lists, they don't have "regular" constructors 6031 // like other types. Instead we always add the two clauses covering 6032 // the empty and non empty list. 6033 Type::Named { .. } if type_.is_list() => { 6034 let first = names.rename_to_avoid_shadowing("first".into()); 6035 let rest = names.rename_to_avoid_shadowing("rest".into()); 6036 Some(vec1![ 6037 EcoString::from("[]"), 6038 eco_format!("[{first}, ..{rest}]") 6039 ]) 6040 } 6041 6042 Type::Named { 6043 module: type_module, 6044 name: type_name, 6045 .. 6046 } => { 6047 let mut patterns = vec![]; 6048 let constructors = 6049 get_type_constructors(self.compiler, &self.module.name, type_module, type_name); 6050 for constructor in constructors { 6051 let names_before = names.clone(); 6052 if let Some(pattern) = 6053 self.record_constructor_to_destructure_pattern(constructor, names) 6054 { 6055 patterns.push(pattern); 6056 } 6057 *names = names_before; 6058 } 6059 6060 Vec1::try_from_vec(patterns).ok() 6061 } 6062 6063 // We don't want to suggest this action for empty tuple as it 6064 // doesn't make a lot of sense to match on those. 6065 Type::Tuple { elements } if elements.is_empty() => None, 6066 Type::Tuple { elements } => { 6067 let elements = elements 6068 .iter() 6069 .map(|element| names.generate_name_from_type(element)) 6070 .join(", "); 6071 Some(vec1![eco_format!("#({elements})")]) 6072 } 6073 } 6074 } 6075 6076 fn type_var_to_destructure_patterns( 6077 &mut self, 6078 type_var: &TypeVar, 6079 names: &mut NameGenerator, 6080 ) -> Option<Vec1<EcoString>> { 6081 match type_var { 6082 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, 6083 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names), 6084 } 6085 } 6086 6087 /// Given the value constructor of a record, returns a string with the 6088 /// pattern used to match on that specific variant. 6089 /// 6090 /// Note how: 6091 /// - If the constructor is internal to another module or comes from another 6092 /// module, then this returns `None` since one cannot pattern match on it. 6093 /// - If the provided `ValueConstructor` is not a record constructor this 6094 /// will return `None`. 6095 /// 6096 fn record_constructor_to_destructure_pattern( 6097 &self, 6098 constructor: &ValueConstructor, 6099 names: &mut NameGenerator, 6100 ) -> Option<EcoString> { 6101 let type_::ValueConstructorVariant::Record { 6102 name: constructor_name, 6103 arity: constructor_arity, 6104 module: constructor_module, 6105 field_map, 6106 .. 6107 } = &constructor.variant 6108 else { 6109 // The constructor should always be a record, in case it's not 6110 // there's not much we can do and just fail. 6111 return None; 6112 }; 6113 6114 // Since the constructor is a record constructor we know that its type 6115 // is either `Named` or a `Fn` type, in either case we have to get the 6116 // arguments types out of it. 6117 let Some(arguments_types) = constructor 6118 .type_ 6119 .fn_types() 6120 .map(|(arguments_types, _return)| arguments_types) 6121 .or_else(|| constructor.type_.constructor_types()) 6122 else { 6123 // This should never happen but just in case we don't want to unwrap 6124 // and panic. 6125 return None; 6126 }; 6127 6128 let index_to_label = match field_map { 6129 None => HashMap::new(), 6130 Some(field_map) => { 6131 names.reserve_all_labels(field_map); 6132 6133 field_map 6134 .fields 6135 .iter() 6136 .map(|(label, index)| (index, label)) 6137 .collect::<HashMap<_, _>>() 6138 } 6139 }; 6140 6141 let mut pattern = 6142 pretty_constructor_name(self.module, constructor_module, constructor_name)?; 6143 6144 if *constructor_arity == 0 { 6145 return Some(pattern); 6146 } 6147 6148 pattern.push('('); 6149 let arguments = (0..*constructor_arity as u32) 6150 .map(|i| match index_to_label.get(&i) { 6151 Some(label) => eco_format!("{label}:"), 6152 None => match arguments_types.get(i as usize) { 6153 None => names.rename_to_avoid_shadowing(EcoString::from("value")), 6154 Some(type_) => names.generate_name_from_type(type_), 6155 }, 6156 }) 6157 .join(", "); 6158 6159 pattern.push_str(&arguments); 6160 pattern.push(')'); 6161 Some(pattern) 6162 } 6163} 6164 6165fn code_at(module: &Module, span: SrcSpan) -> &str { 6166 module 6167 .code 6168 .get(span.start as usize..span.end as usize) 6169 .expect("code location must be valid") 6170} 6171 6172impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> { 6173 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 6174 // If we're not inside the function there's no point in exploring its 6175 // ast further. 6176 let function_span = SrcSpan { 6177 start: fun.location.start, 6178 end: fun.end_position, 6179 }; 6180 let function_range = self.edits.src_span_to_lsp_range(function_span); 6181 if !within(self.params.range, function_range) { 6182 return; 6183 } 6184 6185 for arg in &fun.arguments { 6186 // If the cursor is placed on one of the arguments, then we can try 6187 // and generate code for that one. 6188 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 6189 if within(self.params.range, arg_range) 6190 && let Some(first_statement) = fun.body.first() 6191 { 6192 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 6193 arg, 6194 first_statement, 6195 function_range, 6196 }); 6197 return; 6198 } 6199 } 6200 6201 // If the cursor is not on any of the function arguments then we keep 6202 // exploring the function body as we might want to destructure the 6203 // argument of an expression function! 6204 ast::visit::visit_typed_function(self, fun); 6205 } 6206 6207 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 6208 let statement_range = self.edits.src_span_to_lsp_range(statement.location()); 6209 if !within(self.params.range, statement_range) { 6210 return; 6211 } 6212 6213 ast::visit::visit_typed_statement(self, statement); 6214 match statement { 6215 // If we haven't found any more specific selected expression, then 6216 // we are going to select the statement to pattern match on (if it 6217 // can be meaningfully pattern matched on). 6218 ast::Statement::Expression( 6219 TypedExpr::Call { type_, .. } 6220 | TypedExpr::ModuleSelect { type_, .. } 6221 | TypedExpr::RecordAccess { type_, .. } 6222 | TypedExpr::TupleIndex { type_, .. }, 6223 ) if self.selected_value.is_none() => { 6224 self.selected_value = Some(PatternMatchedValue::Statement { 6225 location: statement.location(), 6226 type_: type_.clone(), 6227 }); 6228 } 6229 6230 ast::Statement::Expression(_) 6231 | ast::Statement::Assignment(_) 6232 | ast::Statement::Use(_) 6233 | ast::Statement::Assert(_) => (), 6234 } 6235 } 6236 6237 fn visit_typed_expr_fn( 6238 &mut self, 6239 location: &'ast SrcSpan, 6240 type_: &'ast Arc<Type>, 6241 kind: &'ast FunctionLiteralKind, 6242 arguments: &'ast [TypedArg], 6243 body: &'ast Vec1<TypedStatement>, 6244 return_annotation: &'ast Option<ast::TypeAst>, 6245 ) { 6246 // If we're not inside the function there's no point in exploring its 6247 // ast further. 6248 let function_range = self.edits.src_span_to_lsp_range(*location); 6249 if !within(self.params.range, function_range) { 6250 return; 6251 } 6252 6253 for argument in arguments { 6254 // If the cursor is placed on one of the arguments, then we can try 6255 // and generate code for that one. 6256 let arg_range = self.edits.src_span_to_lsp_range(argument.location); 6257 if within(self.params.range, arg_range) { 6258 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 6259 arg: argument, 6260 first_statement: body.first(), 6261 function_range, 6262 }); 6263 return; 6264 } 6265 } 6266 6267 // If the cursor is not on any of the function arguments then we keep 6268 // exploring the function body as we might want to destructure the 6269 // argument of an expression function! 6270 ast::visit::visit_typed_expr_fn( 6271 self, 6272 location, 6273 type_, 6274 kind, 6275 arguments, 6276 body, 6277 return_annotation, 6278 ); 6279 } 6280 6281 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 6282 // If we're not inside the assignment there's no point in exploring its 6283 // ast further. 6284 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 6285 if !within(self.params.range, assignment_range) { 6286 return; 6287 } 6288 6289 ast::visit::visit_typed_assignment(self, assignment); 6290 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor 6291 // We must make sure that no other value was selected while visiting 6292 // this. If it were `Some` that means that we have found _another_ 6293 // variable to match on inside the assignmemt itself. For example: 6294 // ```gleam 6295 // let a = { 6296 // let b = todo 6297 // // ^ We're matching on this, not the outer one! 6298 // } 6299 // ``` 6300 && self.selected_value.is_none() 6301 { 6302 self.selected_value = Some(PatternMatchedValue::LetVariable { 6303 variable_name: name, 6304 variable_type: type_.clone(), 6305 assignment_location: assignment.location, 6306 }); 6307 } 6308 } 6309 6310 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 6311 // If we're not inside the clause there's no point in exploring its 6312 // ast further. 6313 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 6314 if !within(self.params.range, clause_range) { 6315 return; 6316 } 6317 6318 for pattern in clause.pattern.iter() { 6319 self.visit_typed_pattern(pattern); 6320 } 6321 for patterns in clause.alternative_patterns.iter() { 6322 for pattern in patterns { 6323 self.visit_typed_pattern(pattern); 6324 } 6325 } 6326 6327 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() { 6328 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable { 6329 variable_type: type_, 6330 variable_location, 6331 clause_location: clause.location(), 6332 bound_variables: clause.bound_variables().collect_vec(), 6333 }); 6334 } else { 6335 self.visit_typed_expr(&clause.then); 6336 } 6337 } 6338 6339 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 6340 if let Some(assignments) = use_.callback_arguments() { 6341 for variable in assignments { 6342 let ast::Arg { 6343 names: ArgNames::Named { name, .. }, 6344 location: variable_location, 6345 type_, 6346 .. 6347 } = variable 6348 else { 6349 continue; 6350 }; 6351 6352 // If we use a pattern in a use assignment, that will end up 6353 // being called `_use` something. We don't want to offer the 6354 // action when hovering a pattern so we ignore those. 6355 if name.starts_with("_use") { 6356 continue; 6357 } 6358 6359 let variable_range = self.edits.src_span_to_lsp_range(*variable_location); 6360 if within(self.params.range, variable_range) { 6361 self.selected_value = Some(PatternMatchedValue::UseVariable { 6362 variable_name: name, 6363 variable_type: type_.clone(), 6364 use_location: use_.location, 6365 }); 6366 // If we've found the variable to pattern match on, there's no 6367 // point in keeping traversing the AST. 6368 return; 6369 } 6370 } 6371 } 6372 6373 ast::visit::visit_typed_use(self, use_); 6374 } 6375 6376 fn visit_typed_pattern_discard( 6377 &mut self, 6378 location: &'ast SrcSpan, 6379 name: &'ast EcoString, 6380 type_: &'ast Arc<Type>, 6381 ) { 6382 if within( 6383 self.params.range, 6384 self.edits.src_span_to_lsp_range(*location), 6385 ) { 6386 let location = PatternLocation::Discard { 6387 location: *location, 6388 }; 6389 self.pattern_variable_under_cursor = Some((name, location, type_.clone())); 6390 } 6391 } 6392 6393 fn visit_typed_pattern_variable( 6394 &mut self, 6395 location: &'ast SrcSpan, 6396 name: &'ast EcoString, 6397 type_: &'ast Arc<Type>, 6398 _origin: &'ast VariableOrigin, 6399 ) { 6400 if within( 6401 self.params.range, 6402 self.edits.src_span_to_lsp_range(*location), 6403 ) { 6404 let location = PatternLocation::regular(*location); 6405 self.pattern_variable_under_cursor = Some((name, location, type_.clone())); 6406 } 6407 } 6408 6409 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 6410 if let Some(name) = arg.label_shorthand_name() 6411 && within( 6412 self.params.range, 6413 self.edits.src_span_to_lsp_range(arg.location), 6414 ) 6415 { 6416 let location = PatternLocation::regular(SrcSpan { 6417 start: arg.location.end, 6418 end: arg.location.end, 6419 }); 6420 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_())); 6421 return; 6422 } 6423 6424 ast::visit::visit_typed_pattern_call_arg(self, arg); 6425 } 6426 6427 fn visit_typed_pattern_string_prefix( 6428 &mut self, 6429 _location: &'ast SrcSpan, 6430 _left_location: &'ast SrcSpan, 6431 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 6432 right_location: &'ast SrcSpan, 6433 _left_side_string: &'ast EcoString, 6434 right_side_assignment: &'ast AssignName, 6435 ) { 6436 if let Some((name, location)) = left_side_assignment 6437 && within( 6438 self.params.range, 6439 self.edits.src_span_to_lsp_range(*location), 6440 ) 6441 { 6442 let location = PatternLocation::regular(*location); 6443 self.pattern_variable_under_cursor = Some((name, location, type_::string())); 6444 } else if let AssignName::Variable(name) = right_side_assignment 6445 && within( 6446 self.params.range, 6447 self.edits.src_span_to_lsp_range(*right_location), 6448 ) 6449 { 6450 let location = PatternLocation::regular(*right_location); 6451 self.pattern_variable_under_cursor = Some((name, location, type_::string())); 6452 } 6453 } 6454 6455 fn visit_typed_pattern_list( 6456 &mut self, 6457 location: &'ast SrcSpan, 6458 elements: &'ast Vec<TypedPattern>, 6459 tail: &'ast Option<Box<TypedTailPattern>>, 6460 type_: &'ast Arc<Type>, 6461 ) { 6462 let (name, tail_location, tail_type) = if let Some(tail) = tail 6463 && let Pattern::Variable { name, type_, .. } = &tail.pattern 6464 { 6465 (name, tail.location, type_) 6466 } else { 6467 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); 6468 return; 6469 }; 6470 6471 let tail_range = self.edits.src_span_to_lsp_range(tail_location); 6472 if !within(self.params.range, tail_range) { 6473 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); 6474 return; 6475 } 6476 6477 let location = PatternLocation::ListTail { 6478 location: tail_location, 6479 }; 6480 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone())); 6481 } 6482} 6483 6484/// Given a type and its module, returns a list of its *importable* 6485/// constructors. 6486/// 6487/// Since this focuses just on importable constructors, if either the module or 6488/// the type are internal the returned array will be empty! 6489/// 6490fn get_type_constructors<'a, 'b, IO>( 6491 compiler: &'a LspProjectCompiler<IO>, 6492 current_module: &'b EcoString, 6493 type_module: &'b EcoString, 6494 type_name: &'b EcoString, 6495) -> Vec<&'a ValueConstructor> { 6496 let type_is_inside_current_module = current_module == type_module; 6497 let module_interface = if !type_is_inside_current_module { 6498 // If the type is outside of the module we're in, we can only pattern 6499 // match on it if the module can be imported. 6500 // The `get_module_interface` already takes care of making this check. 6501 compiler.get_module_interface(type_module) 6502 } else { 6503 // However, if the type is defined in the module we're in, we can always 6504 // pattern match on it. So we get the current module's interface. 6505 compiler 6506 .modules 6507 .get(current_module) 6508 .map(|module| &module.ast.type_info) 6509 }; 6510 6511 let Some(module_interface) = module_interface else { 6512 return vec![]; 6513 }; 6514 6515 // If the type is in an internal module that is not the current one, we 6516 // cannot use its constructors! 6517 if !type_is_inside_current_module && module_interface.is_internal { 6518 return vec![]; 6519 } 6520 6521 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else { 6522 return vec![]; 6523 }; 6524 6525 constructors 6526 .variants 6527 .iter() 6528 .filter_map(|variant| { 6529 let constructor = module_interface.values.get(&variant.name)?; 6530 if type_is_inside_current_module || constructor.publicity.is_public() { 6531 Some(constructor) 6532 } else { 6533 None 6534 } 6535 }) 6536 .collect_vec() 6537} 6538 6539/// Returns a pretty printed record constructor name, the way it would be used 6540/// inside the given `module` (with the correct name and qualification). 6541/// 6542/// If the constructor cannot be used inside the module because it's not 6543/// imported, then this function will return `None`. 6544/// 6545fn pretty_constructor_name( 6546 module: &Module, 6547 constructor_module: &EcoString, 6548 constructor_name: &EcoString, 6549) -> Option<EcoString> { 6550 match module 6551 .ast 6552 .names 6553 .named_constructor(constructor_module, constructor_name) 6554 { 6555 type_::printer::NameContextInformation::Unimported(_, _) => None, 6556 type_::printer::NameContextInformation::Unqualified(constructor_name) => { 6557 Some(eco_format!("{constructor_name}")) 6558 } 6559 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => { 6560 Some(eco_format!("{module_name}.{constructor_name}")) 6561 } 6562 } 6563} 6564 6565/// Builder for the "generate function" code action. 6566/// Whenever someone hovers an invalid expression that is inferred to have a 6567/// function type the language server can generate a function definition for it. 6568/// For example: 6569/// 6570/// ```gleam 6571/// pub fn main() { 6572/// wibble(1, 2, "hello") 6573/// // ^ [generate function] 6574/// } 6575/// ``` 6576/// 6577/// Will generate the following definition: 6578/// 6579/// ```gleam 6580/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a { 6581/// todo 6582/// } 6583/// ``` 6584/// 6585pub struct GenerateFunction<'a> { 6586 module: &'a Module, 6587 modules: &'a std::collections::HashMap<EcoString, Module>, 6588 params: &'a CodeActionParams, 6589 edits: TextEdits<'a>, 6590 last_visited_definition_end: Option<u32>, 6591 function_to_generate: Option<FunctionToGenerate<'a>>, 6592} 6593 6594struct FunctionToGenerate<'a> { 6595 module: Option<&'a str>, 6596 name: &'a str, 6597 arguments_types: Vec<Arc<Type>>, 6598 6599 /// The arguments actually supplied as input to the function, if any. 6600 /// A function to generate might as well be just a name passed as an argument 6601 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually 6602 /// have any actual arguments! 6603 given_arguments: Option<&'a [TypedCallArg]>, 6604 return_type: Arc<Type>, 6605 previous_definition_end: Option<u32>, 6606} 6607 6608impl<'a> GenerateFunction<'a> { 6609 pub fn new( 6610 module: &'a Module, 6611 modules: &'a std::collections::HashMap<EcoString, Module>, 6612 line_numbers: &'a LineNumbers, 6613 params: &'a CodeActionParams, 6614 ) -> Self { 6615 Self { 6616 module, 6617 modules, 6618 params, 6619 edits: TextEdits::new(line_numbers), 6620 last_visited_definition_end: None, 6621 function_to_generate: None, 6622 } 6623 } 6624 6625 pub fn code_actions(mut self) -> Vec<CodeAction> { 6626 self.visit_typed_module(&self.module.ast); 6627 6628 let Some( 6629 function_to_generate @ FunctionToGenerate { 6630 module, 6631 previous_definition_end: Some(insert_at), 6632 .. 6633 }, 6634 ) = self.function_to_generate.take() 6635 else { 6636 return vec![]; 6637 }; 6638 6639 if let Some(module) = module { 6640 if let Some(module) = self.modules.get(module) { 6641 let insert_at = module.code.len() as u32; 6642 self.code_action_for_module( 6643 module, 6644 Publicity::Public, 6645 function_to_generate, 6646 insert_at, 6647 ) 6648 } else { 6649 Vec::new() 6650 } 6651 } else { 6652 let module = self.module; 6653 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at) 6654 } 6655 } 6656 6657 fn code_action_for_module( 6658 mut self, 6659 module: &'a Module, 6660 publicity: Publicity, 6661 function_to_generate: FunctionToGenerate<'a>, 6662 insert_at: u32, 6663 ) -> Vec<CodeAction> { 6664 let FunctionToGenerate { 6665 name, 6666 arguments_types, 6667 given_arguments, 6668 return_type, 6669 .. 6670 } = function_to_generate; 6671 6672 // This might be triggered on variants as well, in that case we don't 6673 // want to offer this action. The "generate variant" action will be 6674 // offered instead. 6675 if !is_valid_lowercase_name(name) { 6676 return vec![]; 6677 } 6678 6679 // Labels do not share the same namespace as argument so we use two 6680 // separate generators to avoid renaming a label in case it shares a 6681 // name with an argument. 6682 let mut label_names = NameGenerator::new(); 6683 let mut argument_names = NameGenerator::new(); 6684 6685 // Since we are generating a new function, type variables from other 6686 // functions and constants are irrelevant to the types we print. 6687 let mut printer = Printer::new_without_type_variables(&module.ast.names); 6688 let arguments = arguments_types 6689 .iter() 6690 .enumerate() 6691 .map(|(index, argument_type)| { 6692 let call_argument = given_arguments.and_then(|arguments| arguments.get(index)); 6693 let (label, name) = 6694 argument_names.generate_label_and_name(call_argument, argument_type); 6695 let pretty_type = printer.print_type(argument_type); 6696 if let Some(label) = label { 6697 let label = label_names.rename_to_avoid_shadowing(label); 6698 format!("{label} {name}: {pretty_type}") 6699 } else { 6700 format!("{name}: {pretty_type}") 6701 } 6702 }) 6703 .join(", "); 6704 6705 let return_type = printer.print_type(&return_type); 6706 6707 let publicity = if publicity.is_public() { "pub " } else { "" }; 6708 6709 // Make sure we use the line number information of the module we are 6710 // editing, which might not be the module where the code action is 6711 // triggered. 6712 self.edits.line_numbers = &module.ast.type_info.line_numbers; 6713 self.edits.insert( 6714 insert_at, 6715 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"), 6716 ); 6717 6718 let Some(uri) = url_from_path(module.input_path.as_str()) else { 6719 return Vec::new(); 6720 }; 6721 let mut action = Vec::with_capacity(1); 6722 CodeActionBuilder::new("Generate function") 6723 .kind(CodeActionKind::QuickFix) 6724 .changes(uri, self.edits.edits) 6725 .preferred(true) 6726 .push_to(&mut action); 6727 action 6728 } 6729 6730 fn try_save_function_to_generate( 6731 &mut self, 6732 name: &'a EcoString, 6733 function_type: &Arc<Type>, 6734 given_arguments: Option<&'a [TypedCallArg]>, 6735 ) { 6736 match function_type.fn_types() { 6737 None => {} 6738 Some((arguments_types, return_type)) => { 6739 self.function_to_generate = Some(FunctionToGenerate { 6740 name, 6741 arguments_types, 6742 given_arguments, 6743 return_type, 6744 previous_definition_end: self.last_visited_definition_end, 6745 module: None, 6746 }); 6747 } 6748 } 6749 } 6750 6751 fn try_save_function_from_other_module( 6752 &mut self, 6753 module: &'a str, 6754 name: &'a str, 6755 function_type: &Arc<Type>, 6756 given_arguments: Option<&'a [TypedCallArg]>, 6757 ) { 6758 if let Some((arguments_types, return_type)) = function_type.fn_types() 6759 && is_valid_lowercase_name(name) 6760 { 6761 self.function_to_generate = Some(FunctionToGenerate { 6762 name, 6763 arguments_types, 6764 given_arguments, 6765 return_type, 6766 previous_definition_end: self.last_visited_definition_end, 6767 module: Some(module), 6768 }); 6769 } 6770 } 6771} 6772 6773impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> { 6774 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 6775 self.last_visited_definition_end = Some(fun.end_position); 6776 ast::visit::visit_typed_function(self, fun); 6777 } 6778 6779 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 6780 self.last_visited_definition_end = Some(constant.value.location().end); 6781 ast::visit::visit_typed_module_constant(self, constant); 6782 } 6783 6784 fn visit_typed_expr_invalid( 6785 &mut self, 6786 location: &'ast SrcSpan, 6787 type_: &'ast Arc<Type>, 6788 extra_information: &'ast Option<InvalidExpression>, 6789 ) { 6790 let invalid_range = self.edits.src_span_to_lsp_range(*location); 6791 if within(self.params.range, invalid_range) { 6792 match extra_information { 6793 Some(InvalidExpression::ModuleSelect { module_name, label }) => { 6794 self.try_save_function_from_other_module(module_name, label, type_, None); 6795 } 6796 Some(InvalidExpression::UnknownVariable { name }) => { 6797 self.try_save_function_to_generate(name, type_, None); 6798 } 6799 None => {} 6800 } 6801 } 6802 6803 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); 6804 } 6805 6806 fn visit_typed_constant_invalid( 6807 &mut self, 6808 location: &'ast SrcSpan, 6809 type_: &'ast Arc<Type>, 6810 extra_information: &'ast Option<InvalidExpression>, 6811 ) { 6812 let constant_range = self.edits.src_span_to_lsp_range(*location); 6813 if let Some(extra_information) = extra_information 6814 && within(self.params.range, constant_range) 6815 { 6816 match extra_information { 6817 InvalidExpression::ModuleSelect { module_name, label } => { 6818 self.try_save_function_from_other_module(module_name, label, type_, None); 6819 } 6820 InvalidExpression::UnknownVariable { name } => { 6821 self.try_save_function_to_generate(name, type_, None); 6822 } 6823 } 6824 } 6825 } 6826 6827 fn visit_typed_expr_call( 6828 &mut self, 6829 location: &'ast SrcSpan, 6830 type_: &'ast Arc<Type>, 6831 fun: &'ast TypedExpr, 6832 arguments: &'ast [TypedCallArg], 6833 argument_parentheses: &'ast Option<u32>, 6834 ) { 6835 // If the function being called is invalid we need to generate a 6836 // function that has the proper labels. 6837 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 6838 6839 if within(self.params.range, fun_range) { 6840 if !labels_are_correct(arguments) { 6841 return; 6842 } 6843 6844 match fun { 6845 TypedExpr::Invalid { 6846 type_, 6847 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }), 6848 location: _, 6849 } => { 6850 return self.try_save_function_from_other_module( 6851 module_name, 6852 label, 6853 type_, 6854 Some(arguments), 6855 ); 6856 } 6857 TypedExpr::Invalid { 6858 type_, 6859 extra_information: Some(InvalidExpression::UnknownVariable { name }), 6860 location: _, 6861 } => { 6862 return self.try_save_function_to_generate(name, type_, Some(arguments)); 6863 } 6864 TypedExpr::Int { .. } 6865 | TypedExpr::Float { .. } 6866 | TypedExpr::String { .. } 6867 | TypedExpr::Block { .. } 6868 | TypedExpr::Pipeline { .. } 6869 | TypedExpr::Var { .. } 6870 | TypedExpr::Fn { .. } 6871 | TypedExpr::List { .. } 6872 | TypedExpr::Call { .. } 6873 | TypedExpr::BinOp { .. } 6874 | TypedExpr::Case { .. } 6875 | TypedExpr::RecordAccess { .. } 6876 | TypedExpr::PositionalAccess { .. } 6877 | TypedExpr::ModuleSelect { .. } 6878 | TypedExpr::Tuple { .. } 6879 | TypedExpr::TupleIndex { .. } 6880 | TypedExpr::Todo { .. } 6881 | TypedExpr::Panic { .. } 6882 | TypedExpr::Echo { .. } 6883 | TypedExpr::BitArray { .. } 6884 | TypedExpr::RecordUpdate { .. } 6885 | TypedExpr::NegateBool { .. } 6886 | TypedExpr::NegateInt { .. } 6887 | TypedExpr::Invalid { .. } => {} 6888 } 6889 } 6890 ast::visit::visit_typed_expr_call( 6891 self, 6892 location, 6893 type_, 6894 fun, 6895 arguments, 6896 argument_parentheses, 6897 ); 6898 } 6899} 6900 6901/// Builder for the "generate variant" code action. This will generate a variant 6902/// for a type if it can tell the type it should come from. It will work with 6903/// non-existing variants both used as expressions 6904/// 6905/// ```gleam 6906/// let a = IDoNotExist(1) 6907/// // ^^^^^^^^^^^ It would generate this variant here 6908/// ``` 6909/// 6910/// And as patterns: 6911/// 6912/// ```gleam 6913/// let assert IDoNotExist(1) = todo 6914/// ^^^^^^^^^^^ It would generate this variant here 6915/// ``` 6916/// 6917pub struct GenerateVariant<'a, IO> { 6918 module: &'a Module, 6919 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 6920 params: &'a CodeActionParams, 6921 line_numbers: &'a LineNumbers, 6922 variant_to_generate: Option<VariantToGenerate<'a>>, 6923 printer: Printer<'a>, 6924} 6925 6926struct VariantToGenerate<'a> { 6927 name: &'a str, 6928 6929 arguments_types: Vec<Arc<Type>>, 6930 6931 /// The start of the variant where the code action was triggered. 6932 /// For example: 6933 /// 6934 /// ```gleam 6935 /// Wobble 6936 /// ^ Trigger here to generate `Wobble` 6937 /// ^ The start is here! 6938 /// ``` 6939 variant_start: u32, 6940 6941 /// If the variant where we triggered the code action is already qualified. 6942 /// For example: 6943 /// 6944 /// ```gleam 6945 /// wibble.Wobble // -> true 6946 /// Wobble // -> false 6947 /// ``` 6948 /// 6949 is_qualified: bool, 6950 6951 /// Where the custom type to add this variant to ends. 6952 /// 6953 end_position: u32, 6954 6955 /// The already existing constructors of the custom type this new variant is 6956 /// going to be added to. 6957 /// 6958 constructors: &'a [RecordConstructor<Arc<Type>>], 6959 6960 /// Wether the type we're adding the variant to is written with braces or 6961 /// not. We need this information to add braces when missing. 6962 /// 6963 type_braces: TypeBraces, 6964 6965 /// The module this variant will be added to. 6966 /// 6967 module_name: EcoString, 6968 6969 /// The type to add this variant to. 6970 type_: Arc<Type>, 6971 6972 /// The arguments actually supplied as input to the variant, if any. 6973 /// A variant to generate might as well be just a name passed as an argument 6974 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually 6975 /// have any actual arguments! 6976 /// 6977 given_arguments: Option<Arguments<'a>>, 6978} 6979 6980#[derive(Debug, Clone, Copy)] 6981enum TypeBraces { 6982 /// If the type is written like this: `pub type Wibble` 6983 HasBraces, 6984 /// If the type is written like this: `pub type Wibble {}` 6985 NoBraces, 6986} 6987 6988/// The arguments to an invalid call or pattern we can use to generate a variant. 6989/// 6990enum Arguments<'a> { 6991 /// These are the arguments provided to the invalid variant constructor 6992 /// when it's used as a function: `let a = Wibble(1, 2)`. 6993 /// 6994 Expressions(&'a [TypedCallArg]), 6995 /// These are the arguments provided to the invalid variant constructor when 6996 /// it's used in a pattern: `let assert Wibble(1, 2) = a` 6997 /// 6998 Patterns(&'a [CallArg<TypedPattern>]), 6999} 7000 7001/// An invalid variant might be used both as a pattern in a case expression or 7002/// as a regular value in an expression. We want to generate the variant in both 7003/// cases, so we use this enum to tell apart the two cases and be able to reuse 7004/// most of the code for both as they are very similar. 7005/// 7006enum Argument<'a> { 7007 Expression(&'a TypedCallArg), 7008 Pattern(&'a CallArg<TypedPattern>), 7009} 7010 7011impl<'a> Arguments<'a> { 7012 fn get(&self, index: usize) -> Option<Argument<'a>> { 7013 match self { 7014 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern), 7015 Arguments::Expressions(call_arguments) => { 7016 call_arguments.get(index).map(Argument::Expression) 7017 } 7018 } 7019 } 7020 7021 fn types(&self) -> Vec<Arc<Type>> { 7022 match self { 7023 Arguments::Expressions(call_arguments) => call_arguments 7024 .iter() 7025 .map(|argument| argument.value.type_()) 7026 .collect_vec(), 7027 7028 Arguments::Patterns(call_arguments) => call_arguments 7029 .iter() 7030 .map(|argument| argument.value.type_()) 7031 .collect_vec(), 7032 } 7033 } 7034} 7035 7036impl Argument<'_> { 7037 fn label(&self) -> Option<EcoString> { 7038 match self { 7039 Argument::Expression(call_arg) => call_arg.label.clone(), 7040 Argument::Pattern(call_arg) => call_arg.label.clone(), 7041 } 7042 } 7043} 7044 7045enum GenerateVariantEdits<'a> { 7046 GenerateInCurrentModule { 7047 current_module_edits: TextEdits<'a>, 7048 }, 7049 GenerateInDifferentModule { 7050 current_module_edits: TextEdits<'a>, 7051 variant_module_edits: TextEdits<'a>, 7052 }, 7053} 7054 7055impl<'a, IO> GenerateVariant<'a, IO> { 7056 pub fn new( 7057 module: &'a Module, 7058 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 7059 line_numbers: &'a LineNumbers, 7060 params: &'a CodeActionParams, 7061 ) -> Self { 7062 Self { 7063 module, 7064 params, 7065 compiler, 7066 line_numbers, 7067 variant_to_generate: None, 7068 printer: Printer::new(&module.ast.names), 7069 } 7070 } 7071 7072 pub fn code_actions(mut self) -> Vec<CodeAction> { 7073 self.visit_typed_module(&self.module.ast); 7074 7075 let Some(VariantToGenerate { 7076 name, 7077 type_, 7078 constructors, 7079 arguments_types, 7080 given_arguments, 7081 module_name, 7082 end_position, 7083 type_braces, 7084 variant_start, 7085 is_qualified, 7086 }) = &self.variant_to_generate 7087 else { 7088 return vec![]; 7089 }; 7090 7091 // Now we need to figure out if we're going to have to edit just the current 7092 // module (because the variant will be added to a type that is defined there), 7093 // or if we'll have to edit both the current module (to import the newly 7094 // generated variant) and a different module (where the variant definition 7095 // is going to end up). 7096 let current_module_line_numbers = LineNumbers::new(&self.module.code); 7097 let current_module_edits = TextEdits::new(&current_module_line_numbers); 7098 let Some(variant_module) = self.compiler.modules.get(module_name) else { 7099 return vec![]; 7100 }; 7101 let variant_module_line_numbers = LineNumbers::new(&variant_module.code); 7102 let variant_module_edits = TextEdits::new(&variant_module_line_numbers); 7103 7104 let mut edits = if *module_name == self.module.name { 7105 GenerateVariantEdits::GenerateInCurrentModule { 7106 current_module_edits, 7107 } 7108 } else { 7109 GenerateVariantEdits::GenerateInDifferentModule { 7110 current_module_edits, 7111 variant_module_edits, 7112 } 7113 }; 7114 7115 self.edits_to_create_variant( 7116 &mut edits, 7117 name, 7118 arguments_types, 7119 given_arguments, 7120 *end_position, 7121 *type_braces, 7122 ); 7123 // If the variant is qualified already we don't have to do anything, 7124 // otherwise we need to import it in the current module. 7125 if !is_qualified { 7126 self.edits_to_import_variant( 7127 &mut edits, 7128 module_name, 7129 name, 7130 *variant_start, 7131 self.module, 7132 constructors, 7133 ); 7134 } 7135 7136 let mut builder = CodeActionBuilder::new(&format!( 7137 "Generate `{}` variant", 7138 self.printer.print_type(type_) 7139 )) 7140 .kind(CodeActionKind::QuickFix) 7141 .preferred(true); 7142 7143 match edits { 7144 GenerateVariantEdits::GenerateInCurrentModule { 7145 current_module_edits, 7146 } => { 7147 builder = builder.changes( 7148 self.params.text_document.uri.clone(), 7149 current_module_edits.edits, 7150 ); 7151 } 7152 GenerateVariantEdits::GenerateInDifferentModule { 7153 current_module_edits, 7154 variant_module_edits, 7155 } => { 7156 let Some(variant_module_path) = url_from_path(variant_module.input_path.as_str()) 7157 else { 7158 return vec![]; 7159 }; 7160 7161 if !current_module_edits.edits.is_empty() { 7162 builder = builder.changes( 7163 self.params.text_document.uri.clone(), 7164 current_module_edits.edits, 7165 ); 7166 } 7167 7168 builder = builder.changes(variant_module_path, variant_module_edits.edits); 7169 } 7170 } 7171 7172 let mut action = Vec::with_capacity(1); 7173 builder.push_to(&mut action); 7174 action 7175 } 7176 7177 /// Returns the edits needed to add this new variant to the given module. 7178 /// It also returns the uri of the module the edits should be applied to. 7179 /// 7180 fn edits_to_create_variant( 7181 &self, 7182 edits: &mut GenerateVariantEdits<'_>, 7183 variant_name: &str, 7184 arguments_types: &[Arc<Type>], 7185 given_arguments: &Option<Arguments<'_>>, 7186 end_position: u32, 7187 type_braces: TypeBraces, 7188 ) { 7189 let mut label_names = NameGenerator::new(); 7190 let mut printer = Printer::new(&self.module.ast.names); 7191 let arguments = arguments_types 7192 .iter() 7193 .enumerate() 7194 .map(|(index, argument_type)| { 7195 let label = given_arguments 7196 .as_ref() 7197 .and_then(|arguments| arguments.get(index)?.label()) 7198 .map(|label| label_names.rename_to_avoid_shadowing(label)); 7199 7200 let pretty_type = printer.print_type(argument_type); 7201 if let Some(arg_label) = label { 7202 format!("{arg_label}: {pretty_type}") 7203 } else { 7204 format!("{pretty_type}") 7205 } 7206 }) 7207 .join(", "); 7208 7209 let variant = if arguments.is_empty() { 7210 variant_name.to_string() 7211 } else { 7212 format!("{variant_name}({arguments})") 7213 }; 7214 7215 let (new_text, insert_at) = match type_braces { 7216 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1), 7217 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position), 7218 }; 7219 7220 match edits { 7221 GenerateVariantEdits::GenerateInCurrentModule { 7222 current_module_edits, 7223 } => current_module_edits.insert(insert_at, new_text), 7224 GenerateVariantEdits::GenerateInDifferentModule { 7225 variant_module_edits, 7226 .. 7227 } => variant_module_edits.insert(insert_at, new_text), 7228 } 7229 } 7230 7231 fn try_save_variant_to_generate( 7232 &mut self, 7233 is_qualified: bool, 7234 function_name_location: SrcSpan, 7235 function_type: &Arc<Type>, 7236 given_arguments: Option<Arguments<'a>>, 7237 ) { 7238 let variant_to_generate = self.variant_to_generate( 7239 is_qualified, 7240 function_name_location, 7241 function_type, 7242 given_arguments, 7243 ); 7244 if variant_to_generate.is_some() { 7245 self.variant_to_generate = variant_to_generate; 7246 } 7247 } 7248 7249 fn variant_to_generate( 7250 &mut self, 7251 is_qualified: bool, 7252 function_name_location: SrcSpan, 7253 type_: &Arc<Type>, 7254 given_arguments: Option<Arguments<'a>>, 7255 ) -> Option<VariantToGenerate<'a>> { 7256 let name = code_at(self.module, function_name_location); 7257 if !is_valid_uppercase_name(name) { 7258 return None; 7259 } 7260 7261 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) { 7262 (Some(result), _) => result, 7263 (None, Some(arguments)) => (arguments.types(), type_.clone()), 7264 (None, None) => (vec![], type_.clone()), 7265 }; 7266 7267 let (module_name, type_name, _) = custom_type.named_type_information()?; 7268 let module = self.compiler.modules.get(&module_name)?; 7269 let (end_position, type_braces, constructors) = 7270 (module.ast.definitions.custom_types.iter()) 7271 .filter(|custom_type| custom_type.name == type_name) 7272 .find_map(|custom_type| { 7273 // If there's already a variant with this name then we definitely 7274 // don't want to generate a new variant with the same name! 7275 let variant_with_this_name_already_exists = custom_type 7276 .constructors 7277 .iter() 7278 .map(|constructor| &constructor.name) 7279 .any(|existing_constructor_name| existing_constructor_name == name); 7280 if variant_with_this_name_already_exists { 7281 return None; 7282 } 7283 let type_braces = if custom_type.end_position == custom_type.location.end { 7284 TypeBraces::NoBraces 7285 } else { 7286 TypeBraces::HasBraces 7287 }; 7288 Some(( 7289 custom_type.end_position, 7290 type_braces, 7291 &custom_type.constructors, 7292 )) 7293 })?; 7294 7295 Some(VariantToGenerate { 7296 name, 7297 is_qualified, 7298 constructors, 7299 arguments_types, 7300 given_arguments, 7301 module_name, 7302 type_: custom_type, 7303 end_position, 7304 type_braces, 7305 variant_start: function_name_location.start, 7306 }) 7307 } 7308 7309 /// If the variant is generated in a module different from the current one, 7310 /// this will add the edits needed to correctly import the variant so that 7311 /// it's readily available. 7312 /// It will also respect the developer's choice of how variants for the type 7313 /// are imported: 7314 /// 7315 /// ```diff 7316 /// - import wibble.{ Wibble } 7317 /// + import wibble.{ Wibble, Wobble } 7318 /// // If generating `Wobble`, and other variants of that type are 7319 /// // unqualified already the new variant is imported unqualified as well. 7320 /// ``` 7321 /// 7322 /// ```diff 7323 /// import wibble 7324 /// 7325 /// pub fn main() { 7326 /// - let assert Wobble = todo 7327 /// + let assert wibble.Wobble = todo 7328 /// } 7329 /// // If no variant is used in an unqualified manner, than the variant 7330 /// // that triggered the generation is also qualified! 7331 /// ``` 7332 fn edits_to_import_variant( 7333 &self, 7334 edits: &mut GenerateVariantEdits<'_>, 7335 variant_module_name: &str, 7336 variant_name: &str, 7337 variant_start: u32, 7338 module: &'a Module, 7339 constructors: &[RecordConstructor<Arc<Type>>], 7340 ) { 7341 let GenerateVariantEdits::GenerateInDifferentModule { 7342 current_module_edits, 7343 .. 7344 } = edits 7345 else { 7346 // If the variant is added to the current module, then no further 7347 // edits are needed. The variant is already available in the current 7348 // module! 7349 return; 7350 }; 7351 7352 let constructors_names: HashSet<_> = constructors 7353 .iter() 7354 .map(|constructor| &constructor.name) 7355 .collect(); 7356 7357 // We start by getting the import for the module where the variant 7358 // is going to be added... 7359 let Some(variant_module_import) = module 7360 .ast 7361 .definitions 7362 .imports 7363 .iter() 7364 .find(|import_| import_.module == variant_module_name) 7365 else { 7366 return; 7367 }; 7368 // ...and then check if any of the variants of the type where the variant 7369 // is going to be added have already been imported in an unqualified way. 7370 let constructors_for_this_type_are_unqualified = variant_module_import 7371 .unqualified_values 7372 .iter() 7373 .any(|value| constructors_names.contains(&value.name)); 7374 7375 if constructors_for_this_type_are_unqualified { 7376 // We need to add an unqualified import! 7377 let (insert_positions, new_text) = edits::insert_unqualified_import( 7378 variant_module_import, 7379 &self.module.code, 7380 variant_name.into(), 7381 ); 7382 current_module_edits.insert(insert_positions, new_text); 7383 } else { 7384 // We need to qualify the variant that triggered the code action! 7385 current_module_edits.insert(variant_start, format!("{variant_module_name}.")); 7386 } 7387 } 7388} 7389 7390impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> { 7391 fn visit_typed_expr_invalid( 7392 &mut self, 7393 location: &'ast SrcSpan, 7394 type_: &'ast Arc<Type>, 7395 extra_information: &'ast Option<InvalidExpression>, 7396 ) { 7397 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 7398 if within(self.params.range, invalid_range) { 7399 self.try_save_variant_to_generate(false, *location, type_, None); 7400 } 7401 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); 7402 } 7403 7404 fn visit_typed_expr_call( 7405 &mut self, 7406 location: &'ast SrcSpan, 7407 type_: &'ast Arc<Type>, 7408 fun: &'ast TypedExpr, 7409 arguments: &'ast [TypedCallArg], 7410 open_parenthesis: &'ast Option<u32>, 7411 ) { 7412 // If the function being called is invalid we need to generate a 7413 // function that has the proper labels. 7414 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers); 7415 if within(self.params.range, fun_range) && fun.is_invalid() { 7416 if labels_are_correct(arguments) { 7417 self.try_save_variant_to_generate( 7418 fun.is_module_select(), 7419 fun.location(), 7420 &fun.type_(), 7421 Some(Arguments::Expressions(arguments)), 7422 ); 7423 } 7424 } else { 7425 ast::visit::visit_typed_expr_call( 7426 self, 7427 location, 7428 type_, 7429 fun, 7430 arguments, 7431 open_parenthesis, 7432 ); 7433 } 7434 } 7435 7436 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 7437 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 7438 if within(self.params.range, invalid_range) { 7439 self.try_save_variant_to_generate(false, *location, type_, None); 7440 } 7441 ast::visit::visit_typed_pattern_invalid(self, location, type_); 7442 } 7443 7444 fn visit_typed_pattern_constructor( 7445 &mut self, 7446 location: &'ast SrcSpan, 7447 name_location: &'ast SrcSpan, 7448 name: &'ast EcoString, 7449 arguments: &'ast Vec<CallArg<TypedPattern>>, 7450 module: &'ast Option<(EcoString, SrcSpan)>, 7451 constructor: &'ast Inferred<type_::PatternConstructor>, 7452 spread: &'ast Option<SrcSpan>, 7453 type_: &'ast Arc<Type>, 7454 ) { 7455 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers); 7456 if within(self.params.range, pattern_range) { 7457 if labels_are_correct(arguments) { 7458 self.try_save_variant_to_generate( 7459 module.is_some(), 7460 *name_location, 7461 type_, 7462 Some(Arguments::Patterns(arguments)), 7463 ); 7464 } 7465 } else { 7466 ast::visit::visit_typed_pattern_constructor( 7467 self, 7468 location, 7469 name_location, 7470 name, 7471 arguments, 7472 module, 7473 constructor, 7474 spread, 7475 type_, 7476 ); 7477 } 7478 } 7479} 7480 7481#[must_use] 7482/// Checks the labels in the given arguments are correct: that is there's no 7483/// duplicate labels and all labelled arguments come after the unlabelled ones. 7484fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool { 7485 let mut labelled_arg_found = false; 7486 let mut used_labels = HashSet::new(); 7487 7488 for argument in arguments { 7489 match &argument.label { 7490 // Labels are invalid if there's duplicate ones or if an unlabelled 7491 // argument comes after a labelled one. 7492 Some(label) if used_labels.contains(label) => return false, 7493 None if labelled_arg_found => return false, 7494 // Otherwise we just add the label to the used ones. 7495 Some(label) => { 7496 labelled_arg_found = true; 7497 let _ = used_labels.insert(label); 7498 } 7499 None => {} 7500 } 7501 } 7502 7503 true 7504} 7505 7506#[derive(Clone)] 7507struct NameGenerator { 7508 used_names: im::HashSet<EcoString>, 7509} 7510 7511impl NameGenerator { 7512 pub fn new() -> Self { 7513 NameGenerator { 7514 used_names: im::HashSet::new(), 7515 } 7516 } 7517 7518 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString { 7519 let mut i = 1; 7520 let mut candidate_name = base.clone(); 7521 7522 loop { 7523 if self.used_names.contains(&candidate_name) { 7524 i += 1; 7525 candidate_name = eco_format!("{base}_{i}"); 7526 } else { 7527 let _ = self.used_names.insert(candidate_name.clone()); 7528 return candidate_name; 7529 } 7530 } 7531 } 7532 7533 /// Given an argument type and the actual call argument (if any), comes up 7534 /// with a label and a name to use for that argument when generating a 7535 /// function. 7536 /// 7537 pub fn generate_label_and_name( 7538 &mut self, 7539 call_argument: Option<&CallArg<TypedExpr>>, 7540 argument_type: &Arc<Type>, 7541 ) -> (Option<EcoString>, EcoString) { 7542 let label = call_argument.and_then(|argument| argument.label.clone()); 7543 let argument_name = call_argument 7544 // We always favour a name derived from the expression (for example if 7545 // the argument is a variable) 7546 .and_then(|argument| self.generate_name_from_expression(&argument.value)) 7547 // If we don't have such a name and there's a label we use that name. 7548 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?))) 7549 // If all else fails we fallback to using a name derived from the 7550 // argument's type. 7551 .unwrap_or_else(|| self.generate_name_from_type(argument_type)); 7552 7553 (label, argument_name) 7554 } 7555 7556 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString { 7557 let type_to_base_name = |type_: &Arc<Type>| { 7558 type_ 7559 .named_type_name() 7560 .map(|(_type_module, type_name)| to_snake_case(&type_name)) 7561 .filter(|name| is_valid_lowercase_name(name)) 7562 .unwrap_or(EcoString::from("value")) 7563 }; 7564 7565 let base_name = match type_.list_type() { 7566 None => type_to_base_name(type_), 7567 // If we're coming up with a name for a list we want to use the 7568 // plural form for the name of the inner type. For example: 7569 // `List(Pokemon)` should generate `pokemons`. 7570 Some(inner_type) => { 7571 let base_name = type_to_base_name(&inner_type); 7572 // If the inner type name already ends in "s" we leave it as it 7573 // is, or it would look funny. 7574 if base_name.ends_with('s') { 7575 base_name 7576 } else { 7577 eco_format!("{base_name}s") 7578 } 7579 } 7580 }; 7581 7582 self.rename_to_avoid_shadowing(base_name) 7583 } 7584 7585 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> { 7586 match expression { 7587 // If the argument is a record, we can't use it as an argument name. 7588 // Similarly, we don't want to base the variable name off a 7589 // compiler-generated variable like `_pipe`. 7590 TypedExpr::Var { 7591 name, constructor, .. 7592 } if !constructor.variant.is_record() 7593 && !constructor.variant.is_generated_variable() => 7594 { 7595 Some(self.rename_to_avoid_shadowing(name.clone())) 7596 } 7597 7598 // If the argument is a record access, we generate a name from the 7599 // label used. 7600 // For example if we have `wibble.id` we would end up picking `id`. 7601 TypedExpr::RecordAccess { label, .. } => { 7602 Some(self.rename_to_avoid_shadowing(label.clone())) 7603 } 7604 7605 TypedExpr::Int { .. } 7606 | TypedExpr::Float { .. } 7607 | TypedExpr::String { .. } 7608 | TypedExpr::Block { .. } 7609 | TypedExpr::Pipeline { .. } 7610 | TypedExpr::Var { .. } 7611 | TypedExpr::Fn { .. } 7612 | TypedExpr::List { .. } 7613 | TypedExpr::Call { .. } 7614 | TypedExpr::BinOp { .. } 7615 | TypedExpr::Case { .. } 7616 | TypedExpr::PositionalAccess { .. } 7617 | TypedExpr::ModuleSelect { .. } 7618 | TypedExpr::Tuple { .. } 7619 | TypedExpr::TupleIndex { .. } 7620 | TypedExpr::Todo { .. } 7621 | TypedExpr::Panic { .. } 7622 | TypedExpr::Echo { .. } 7623 | TypedExpr::BitArray { .. } 7624 | TypedExpr::RecordUpdate { .. } 7625 | TypedExpr::NegateBool { .. } 7626 | TypedExpr::NegateInt { .. } 7627 | TypedExpr::Invalid { .. } => None, 7628 } 7629 } 7630 7631 /// Given some typed definitions this reserves all the value names defined 7632 /// by all the top level definitions. That is: all function names, constant 7633 /// names, and imported modules names. 7634 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) { 7635 for constant in &definitions.constants { 7636 self.add_used_name(constant.name.clone()); 7637 } 7638 7639 for function in &definitions.functions { 7640 if let Some((_, name)) = &function.name { 7641 self.add_used_name(name.clone()); 7642 } 7643 } 7644 7645 for import in &definitions.imports { 7646 let module_name = match &import.used_name() { 7647 Some(used_name) => used_name.clone(), 7648 None => import.module.clone(), 7649 }; 7650 self.add_used_name(module_name); 7651 } 7652 } 7653 7654 pub fn add_used_name(&mut self, name: EcoString) { 7655 let _ = self.used_names.insert(name); 7656 } 7657 7658 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) { 7659 field_map 7660 .fields 7661 .iter() 7662 .for_each(|(label, _)| self.add_used_name(label.clone())); 7663 } 7664 7665 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) { 7666 variable_names 7667 .names 7668 .iter() 7669 .for_each(|name| self.add_used_name(name.clone())); 7670 } 7671 7672 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) { 7673 for variable in bound_variables { 7674 self.add_used_name(variable.name()); 7675 } 7676 } 7677} 7678 7679#[must_use] 7680fn is_valid_lowercase_name(name: &str) -> bool { 7681 if !name.starts_with(|char: char| char.is_ascii_lowercase()) { 7682 return false; 7683 } 7684 7685 for char in name.chars() { 7686 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_'; 7687 if !is_valid_char { 7688 return false; 7689 } 7690 } 7691 7692 string_to_keyword(name).is_none() 7693} 7694 7695#[must_use] 7696fn is_valid_uppercase_name(name: &str) -> bool { 7697 if !name.starts_with(|char: char| char.is_ascii_uppercase()) { 7698 return false; 7699 } 7700 7701 for char in name.chars() { 7702 if !char.is_ascii_alphanumeric() { 7703 return false; 7704 } 7705 } 7706 7707 true 7708} 7709 7710/// Code action to rewrite a single-step pipeline into a regular function call. 7711/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`. 7712/// 7713pub struct ConvertToFunctionCall<'a> { 7714 module: &'a Module, 7715 params: &'a CodeActionParams, 7716 edits: TextEdits<'a>, 7717 locations: Option<ConvertToFunctionCallLocations>, 7718} 7719 7720/// All the different locations the "Convert to function call" code action needs 7721/// to properly rewrite a pipeline into a function call. 7722/// 7723struct ConvertToFunctionCallLocations { 7724 /// This is the location of the value being piped into a call. 7725 /// 7726 /// ```gleam 7727 /// [1, 2, 3] |> list.length 7728 /// // ^^^^^^^^^ This one here 7729 /// ``` 7730 /// 7731 first_value: SrcSpan, 7732 7733 /// This is the location of the call the value is being piped into. 7734 /// 7735 /// ```gleam 7736 /// [1, 2, 3] |> list.length 7737 /// // ^^^^^^^^^^^ This one here 7738 /// ``` 7739 /// 7740 call: SrcSpan, 7741 7742 /// This is the kind of desugaring that is taking place when piping 7743 /// `first_value` into `call`. 7744 /// 7745 call_kind: PipelineAssignmentKind, 7746} 7747 7748impl<'a> ConvertToFunctionCall<'a> { 7749 pub fn new( 7750 module: &'a Module, 7751 line_numbers: &'a LineNumbers, 7752 params: &'a CodeActionParams, 7753 ) -> Self { 7754 Self { 7755 module, 7756 params, 7757 edits: TextEdits::new(line_numbers), 7758 locations: None, 7759 } 7760 } 7761 7762 pub fn code_actions(mut self) -> Vec<CodeAction> { 7763 self.visit_typed_module(&self.module.ast); 7764 7765 // If we couldn't find a pipeline to rewrite we don't return any action. 7766 let Some(ConvertToFunctionCallLocations { 7767 first_value, 7768 call, 7769 call_kind, 7770 }) = self.locations 7771 else { 7772 return vec![]; 7773 }; 7774 7775 // We first delete the first value of the pipeline as it's going to be 7776 // inlined as a function call argument. 7777 self.edits.delete(SrcSpan { 7778 start: first_value.start, 7779 end: call.start, 7780 }); 7781 7782 // Then we have to insert the piped value in the appropriate position. 7783 // This will change based on how the pipeline is being desugared, we 7784 // know this thanks to the `call_kind` 7785 let first_value_text = self 7786 .module 7787 .code 7788 .get(first_value.start as usize..first_value.end as usize) 7789 .expect("invalid code span") 7790 .to_string(); 7791 7792 match call_kind { 7793 // When piping into a `_` we replace the hole with the piped value: 7794 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`. 7795 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text), 7796 7797 // When piping is desguared as a function call we need to add the 7798 // missing parentheses: 7799 // `[1, 2] |> length` becomes `length([1, 2])` 7800 PipelineAssignmentKind::FunctionCall => { 7801 self.edits.insert(call.end, format!("({first_value_text})")); 7802 } 7803 7804 // When the piped value is inserted as the first argument there's two 7805 // possible scenarios: 7806 // - there's a second argument as well: in that case we insert it 7807 // before the second arg and add a comma 7808 // - there's no other argument: `[1, 2] |> length()` becomes 7809 // `length([1, 2])`, we insert the value between the empty 7810 // parentheses 7811 PipelineAssignmentKind::FirstArgument { 7812 second_argument: Some(SrcSpan { start, .. }), 7813 } => self.edits.insert(start, format!("{first_value_text}, ")), 7814 PipelineAssignmentKind::FirstArgument { 7815 second_argument: None, 7816 } => self.edits.insert(call.end - 1, first_value_text), 7817 7818 // When the value is piped into an echo, to rewrite the pipeline we 7819 // have to insert the value after the `echo` with no parentheses: 7820 // `a |> echo` is rewritten as `echo a`. 7821 PipelineAssignmentKind::Echo => { 7822 self.edits.insert(call.end, format!(" {first_value_text}")); 7823 } 7824 } 7825 7826 let mut action = Vec::with_capacity(1); 7827 CodeActionBuilder::new("Convert to function call") 7828 .kind(CodeActionKind::RefactorRewrite) 7829 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7830 .preferred(false) 7831 .push_to(&mut action); 7832 action 7833 } 7834} 7835 7836impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> { 7837 fn visit_typed_expr_pipeline( 7838 &mut self, 7839 location: &'ast SrcSpan, 7840 first_value: &'ast TypedPipelineAssignment, 7841 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 7842 finally: &'ast TypedExpr, 7843 finally_kind: &'ast PipelineAssignmentKind, 7844 ) { 7845 let pipeline_range = self.edits.src_span_to_lsp_range(*location); 7846 if within(self.params.range, pipeline_range) { 7847 // Add final assignment to the list 7848 let all_assignments = assignments 7849 .iter() 7850 .map(|(call, kind)| (call.location, *kind)) 7851 .chain(iter::once((finally.location(), *finally_kind))); 7852 let mut call_information = None; 7853 // Span, containing content that will be extracted as call argument 7854 let mut accumulated_span = first_value.location; 7855 7856 for (current_location, current_kind) in all_assignments { 7857 if within( 7858 self.params.range, 7859 self.edits.src_span_to_lsp_range(current_location), 7860 ) { 7861 call_information = Some((accumulated_span, current_location, current_kind)); 7862 break; 7863 } 7864 accumulated_span = accumulated_span.merge(&current_location); 7865 } 7866 7867 let (final_prev, final_call, final_kind) = match call_information { 7868 Some(triple) => triple, 7869 7870 // Here two situations are possible: 7871 // - If the cursor is not on any of the assignments (for example, it is on first value), then we need 7872 // to use that first value as accumulated location, and the first call location and kind 7873 // - If there are no assignments, then we are dealing with a single 7874 // step pipeline and the call is `finally` 7875 None => { 7876 let (call, call_kind) = &assignments 7877 .first() 7878 .map(|(call, call_kind)| (call.location, *call_kind)) 7879 .unwrap_or_else(|| (finally.location(), *finally_kind)); 7880 (first_value.location, *call, *call_kind) 7881 } 7882 }; 7883 7884 self.locations = Some(ConvertToFunctionCallLocations { 7885 first_value: final_prev, 7886 call: final_call, 7887 call_kind: final_kind, 7888 }); 7889 7890 ast::visit::visit_typed_expr_pipeline( 7891 self, 7892 location, 7893 first_value, 7894 assignments, 7895 finally, 7896 finally_kind, 7897 ); 7898 } 7899 } 7900} 7901 7902/// Builder for code action to inline a variable. 7903/// 7904pub struct InlineVariable<'a> { 7905 module: &'a Module, 7906 params: &'a CodeActionParams, 7907 edits: TextEdits<'a>, 7908 actions: Vec<CodeAction>, 7909} 7910 7911impl<'a> InlineVariable<'a> { 7912 pub fn new( 7913 module: &'a Module, 7914 line_numbers: &'a LineNumbers, 7915 params: &'a CodeActionParams, 7916 ) -> Self { 7917 Self { 7918 module, 7919 params, 7920 edits: TextEdits::new(line_numbers), 7921 actions: Vec::new(), 7922 } 7923 } 7924 7925 pub fn code_actions(mut self) -> Vec<CodeAction> { 7926 self.visit_typed_module(&self.module.ast); 7927 7928 self.actions 7929 } 7930 7931 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) { 7932 let references = 7933 FindVariableReferences::new(location, name).find_in_module(&self.module.ast); 7934 let reference = if references.len() == 1 { 7935 references 7936 .into_iter() 7937 .next() 7938 .expect("References has length 1") 7939 } else { 7940 return; 7941 }; 7942 7943 let Some(ast::Statement::Assignment(assignment)) = 7944 self.module.ast.find_statement(location.start) 7945 else { 7946 return; 7947 }; 7948 7949 // If the assignment does not simple bind a variable, for example: 7950 // ```gleam 7951 // let #(first, second, third) 7952 // io.println(first) 7953 // // ^ Inline here 7954 // ``` 7955 // We can't inline it. 7956 if !matches!(assignment.pattern, Pattern::Variable { .. }) { 7957 return; 7958 } 7959 7960 // If the assignment was generated by the compiler, it doesn't have a 7961 // syntactical representation, so we can't inline it. 7962 if matches!(assignment.kind, AssignmentKind::Generated) { 7963 return; 7964 } 7965 7966 let value_location = assignment.value.location(); 7967 let value = self 7968 .module 7969 .code 7970 .get(value_location.start as usize..value_location.end as usize) 7971 .expect("Span is valid"); 7972 7973 match reference.kind { 7974 VariableReferenceKind::Variable => { 7975 self.edits.replace(reference.location, value.into()); 7976 } 7977 VariableReferenceKind::LabelShorthand => { 7978 self.edits 7979 .insert(reference.location.end, format!(" {value}")); 7980 } 7981 } 7982 7983 let mut location = assignment.location; 7984 location.end = next_nonwhitespace(&self.module.code, location.end); 7985 7986 self.edits.delete(location); 7987 7988 CodeActionBuilder::new("Inline variable") 7989 .kind(CodeActionKind::RefactorInline) 7990 .changes( 7991 self.params.text_document.uri.clone(), 7992 std::mem::take(&mut self.edits.edits), 7993 ) 7994 .preferred(false) 7995 .push_to(&mut self.actions); 7996 } 7997} 7998 7999impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> { 8000 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 8001 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else { 8002 ast::visit::visit_typed_assignment(self, assignment); 8003 return; 8004 }; 8005 8006 // We special case assignment variables because we want to trigger the 8007 // code action also if we're over the let keyword: 8008 // 8009 // ```gleam 8010 // let wibble = 11 8011 // // ^^^^^^^^^^ Here! 8012 // ``` 8013 // 8014 let assignment_range = self 8015 .edits 8016 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end)); 8017 if !within(self.params.range, assignment_range) { 8018 ast::visit::visit_typed_assignment(self, assignment); 8019 return; 8020 } 8021 8022 self.maybe_inline(*location, name.clone()); 8023 } 8024 8025 fn visit_typed_expr_var( 8026 &mut self, 8027 location: &'ast SrcSpan, 8028 constructor: &'ast ValueConstructor, 8029 name: &'ast EcoString, 8030 ) { 8031 let range = self.edits.src_span_to_lsp_range(*location); 8032 8033 if !within(self.params.range, range) { 8034 return; 8035 } 8036 8037 let type_::ValueConstructorVariant::LocalVariable { location, origin } = 8038 &constructor.variant 8039 else { 8040 return; 8041 }; 8042 8043 // We can only inline variables assigned by `let` statements, as it 8044 //doesn't make sense to do so with any other kind of variable. 8045 match origin.declaration { 8046 VariableDeclaration::LetPattern => {} 8047 VariableDeclaration::UsePattern 8048 | VariableDeclaration::ClausePattern 8049 | VariableDeclaration::FunctionParameter { .. } 8050 | VariableDeclaration::Generated => return, 8051 } 8052 8053 self.maybe_inline(*location, name.clone()); 8054 } 8055 8056 fn visit_typed_pattern_variable( 8057 &mut self, 8058 location: &'ast SrcSpan, 8059 name: &'ast EcoString, 8060 _type: &'ast Arc<Type>, 8061 origin: &'ast VariableOrigin, 8062 ) { 8063 // We can only inline variables assigned by `let` statements, as it 8064 //doesn't make sense to do so with any other kind of variable. 8065 match origin.declaration { 8066 VariableDeclaration::LetPattern => {} 8067 VariableDeclaration::UsePattern 8068 | VariableDeclaration::ClausePattern 8069 | VariableDeclaration::FunctionParameter { .. } 8070 | VariableDeclaration::Generated => return, 8071 } 8072 8073 let range = self.edits.src_span_to_lsp_range(*location); 8074 8075 if !within(self.params.range, range) { 8076 return; 8077 } 8078 8079 self.maybe_inline(*location, name.clone()); 8080 } 8081} 8082 8083/// Builder for the "convert to pipe" code action. 8084/// 8085/// ```gleam 8086/// pub fn main() { 8087/// wibble(wobble, woo) 8088/// // ^ [convert to pipe] 8089/// } 8090/// ``` 8091/// 8092/// Will turn the code into the following pipeline: 8093/// 8094/// ```gleam 8095/// pub fn main() { 8096/// wobble |> wibble(woo) 8097/// } 8098/// ``` 8099/// 8100pub struct ConvertToPipe<'a> { 8101 module: &'a Module, 8102 params: &'a CodeActionParams, 8103 edits: TextEdits<'a>, 8104 argument_to_pipe: Option<ConvertToPipeArg<'a>>, 8105 visited_item: VisitedItem, 8106} 8107 8108pub enum VisitedItem { 8109 RegularExpression, 8110 UseRightHandSide, 8111 PipelineFinalStep, 8112} 8113 8114/// Holds all the data needed by the "convert to pipe" code action to properly 8115/// rewrite a call into a pipe. Here's what each span means: 8116/// 8117/// ```gleam 8118/// wibble(wobb|le, woo) 8119/// // ^^^^^^^^^^^^^^^^^^^^ call 8120/// // ^^^^^^ called 8121/// // ^^^^^^^ arg 8122/// // ^^^ next arg 8123/// ``` 8124/// 8125/// In this example `position` is 0, since the cursor is over the first 8126/// argument. 8127/// 8128pub struct ConvertToPipeArg<'a> { 8129 /// The span of the called function. 8130 called: SrcSpan, 8131 /// The span of the entire function call. 8132 call: SrcSpan, 8133 /// The position (0-based) of the argument. 8134 position: usize, 8135 /// The argument we have to pipe. 8136 arg: &'a TypedCallArg, 8137 /// The span of the argument following the one we have to pipe, if there's 8138 /// any. 8139 next_arg: Option<SrcSpan>, 8140} 8141 8142impl<'a> ConvertToPipe<'a> { 8143 pub fn new( 8144 module: &'a Module, 8145 line_numbers: &'a LineNumbers, 8146 params: &'a CodeActionParams, 8147 ) -> Self { 8148 Self { 8149 module, 8150 params, 8151 edits: TextEdits::new(line_numbers), 8152 visited_item: VisitedItem::RegularExpression, 8153 argument_to_pipe: None, 8154 } 8155 } 8156 8157 pub fn code_actions(mut self) -> Vec<CodeAction> { 8158 self.visit_typed_module(&self.module.ast); 8159 8160 let Some(ConvertToPipeArg { 8161 called, 8162 call, 8163 position, 8164 arg, 8165 next_arg, 8166 }) = self.argument_to_pipe 8167 else { 8168 return vec![]; 8169 }; 8170 8171 let arg_location = if arg.uses_label_shorthand() { 8172 SrcSpan { 8173 start: arg.location.start, 8174 end: arg.location.end - 1, 8175 } 8176 } else if arg.label.is_some() { 8177 arg.value.location() 8178 } else { 8179 arg.location 8180 }; 8181 8182 let arg_text = code_at(self.module, arg_location); 8183 // If the expression being piped is a binary operation with 8184 // precedence lower than pipes then we have to wrap it in curly 8185 // braces to not mess with the order of operations. 8186 let arg_text = if let TypedExpr::BinOp { operator, .. } = arg.value 8187 && operator.precedence() < PIPE_PRECEDENCE 8188 { 8189 &format!("{{ {arg_text} }}") 8190 } else { 8191 arg_text 8192 }; 8193 8194 match next_arg { 8195 // When extracting an argument we never want to remove any explicit 8196 // label that was written down, so in case it is labelled (be it a 8197 // shorthand or not) we'll always replace the value with a `_` 8198 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()), 8199 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()), 8200 8201 // Now we can deal with unlabelled arguments: 8202 // If we're removing the first argument and there's other arguments 8203 // after it, we need to delete the comma that was separating the 8204 // two. 8205 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan { 8206 start: arg.location.start, 8207 end: next_arg.start, 8208 }), 8209 // Otherwise, if we're deleting the first argument and there's 8210 // no other arguments following it, we remove the call's 8211 // parentheses. 8212 None if position == 0 => self.edits.delete(SrcSpan { 8213 start: called.end, 8214 end: call.end, 8215 }), 8216 // In all other cases we're piping something that is not the first 8217 // argument so we just replace it with an `_`. 8218 _ => self.edits.replace(arg.location, "_".into()), 8219 } 8220 8221 // Finally we can add the argument that was removed as the first step 8222 // of the newly defined pipeline. 8223 self.edits.insert(call.start, format!("{arg_text} |> ")); 8224 8225 let mut action = Vec::with_capacity(1); 8226 CodeActionBuilder::new("Convert to pipe") 8227 .kind(CodeActionKind::RefactorRewrite) 8228 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8229 .preferred(false) 8230 .push_to(&mut action); 8231 action 8232 } 8233} 8234 8235impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> { 8236 fn visit_typed_expr_call( 8237 &mut self, 8238 location: &'ast SrcSpan, 8239 _type_: &'ast Arc<Type>, 8240 fun: &'ast TypedExpr, 8241 arguments: &'ast [TypedCallArg], 8242 _open_parenthesis: &'ast Option<u32>, 8243 ) { 8244 if arguments.iter().any(|arg| arg.is_capture_hole()) { 8245 return; 8246 } 8247 8248 // If we're visiting the typed function produced by typing a use, we 8249 // skip the thing itself and only visit its arguments and called 8250 // function, that is the body of the use. 8251 match self.visited_item { 8252 VisitedItem::RegularExpression => (), 8253 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => { 8254 self.visited_item = VisitedItem::RegularExpression; 8255 ast::visit::visit_typed_expr(self, fun); 8256 arguments 8257 .iter() 8258 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg)); 8259 return; 8260 } 8261 } 8262 8263 // We only visit a call if the cursor is somewhere within its location, 8264 // otherwise we skip it entirely. 8265 let call_range = self.edits.src_span_to_lsp_range(*location); 8266 if !within(self.params.range, call_range) { 8267 return; 8268 } 8269 8270 // If the cursor is over any of the arguments then we'll use that as 8271 // the one to extract. 8272 // Otherwise the cursor must be over the called function, in that case 8273 // we extract the first argument (if there's one): 8274 // 8275 // ```gleam 8276 // wibble(wobble, woo) 8277 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here 8278 // // ^^^ pipe the second argument if I'm here 8279 // ``` 8280 let argument_to_pipe = arguments 8281 .iter() 8282 .enumerate() 8283 .find_map(|(position, arg)| { 8284 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 8285 if within(self.params.range, arg_range) { 8286 Some((position, arg)) 8287 } else { 8288 None 8289 } 8290 }) 8291 .or_else(|| arguments.first().map(|argument| (0, argument))); 8292 8293 // If we're not hovering over any of the arguments _or_ there's no 8294 // argument to extract at all we just return, there's nothing we can do 8295 // on this call or any of its arguments (since we've determined the 8296 // cursor is not over any of those). 8297 let Some((position, arg)) = argument_to_pipe else { 8298 return; 8299 }; 8300 8301 self.argument_to_pipe = Some(ConvertToPipeArg { 8302 called: fun.location(), 8303 call: *location, 8304 position, 8305 arg, 8306 next_arg: arguments 8307 .get(position + 1) 8308 .map(|argument| argument.location), 8309 }); 8310 8311 // We still want to visit the arguments so that if we're hovering a 8312 // nested pipeline, that's going to be the one we transform: 8313 // 8314 // ```gleam 8315 // wibble(Wobble( 8316 // field: call(other(last(1))) 8317 // // ^^^^ We want to convert this one if we hover over it, 8318 // // not the outer `wibble(Wobble(...))` call 8319 // )) 8320 // ``` 8321 // 8322 for argument in arguments { 8323 ast::visit::visit_typed_call_arg(self, argument); 8324 } 8325 } 8326 8327 fn visit_typed_expr_pipeline( 8328 &mut self, 8329 _location: &'ast SrcSpan, 8330 first_value: &'ast TypedPipelineAssignment, 8331 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 8332 finally: &'ast TypedExpr, 8333 _finally_kind: &'ast PipelineAssignmentKind, 8334 ) { 8335 // We can only apply the action on the first step of a pipeline, so we 8336 // visit just that one and skip all the others. 8337 ast::visit::visit_typed_pipeline_assignment(self, first_value); 8338 self.visited_item = VisitedItem::PipelineFinalStep; 8339 ast::visit::visit_typed_expr(self, finally); 8340 } 8341 8342 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 8343 self.visited_item = VisitedItem::UseRightHandSide; 8344 ast::visit::visit_typed_use(self, use_); 8345 } 8346} 8347 8348/// Code action to interpolate a string. If the cursor is inside the string 8349/// (not selecting anything) the language server will offer to split it: 8350/// 8351/// ```gleam 8352/// "wibble | wobble" 8353/// // ^ [Split string] 8354/// // Will produce the following 8355/// "wibble " <> todo <> " wobble" 8356/// ``` 8357/// 8358/// If the cursor is selecting an entire valid gleam name, then the language 8359/// server will offer to interpolate it as a variable: 8360/// 8361/// ```gleam 8362/// "wibble wobble woo" 8363/// // ^^^^^^ [Interpolate variable] 8364/// // Will produce the following 8365/// "wibble " <> wobble <> " woo" 8366/// ``` 8367/// 8368/// > Note: the cursor won't end up right after the inserted variable/todo. 8369/// > that's a bit annoying, but in a future LSP version we will be able to 8370/// > isnert tab stops to allow one to jump to the newly added variable/todo. 8371/// 8372pub struct InterpolateString<'a> { 8373 module: &'a Module, 8374 params: &'a CodeActionParams, 8375 edits: TextEdits<'a>, 8376 string_interpolation: Option<(SrcSpan, StringInterpolation)>, 8377 string_literal_position: StringLiteralPosition, 8378} 8379 8380#[derive(Debug, Clone, Copy, Eq, PartialEq)] 8381pub enum StringLiteralPosition { 8382 FirstPipelineStep, 8383 Other, 8384} 8385 8386#[derive(Clone, Copy)] 8387enum StringInterpolation { 8388 InterpolateValue { value_location: SrcSpan }, 8389 SplitString { split_at: u32 }, 8390} 8391 8392impl<'a> InterpolateString<'a> { 8393 pub fn new( 8394 module: &'a Module, 8395 line_numbers: &'a LineNumbers, 8396 params: &'a CodeActionParams, 8397 ) -> Self { 8398 Self { 8399 module, 8400 params, 8401 edits: TextEdits::new(line_numbers), 8402 string_interpolation: None, 8403 string_literal_position: StringLiteralPosition::Other, 8404 } 8405 } 8406 8407 pub fn code_actions(mut self) -> Vec<CodeAction> { 8408 self.visit_typed_module(&self.module.ast); 8409 8410 let Some((string_location, interpolation)) = self.string_interpolation else { 8411 return vec![]; 8412 }; 8413 8414 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 8415 self.edits.insert(string_location.start, "{ ".into()); 8416 } 8417 8418 match interpolation { 8419 StringInterpolation::InterpolateValue { value_location } => { 8420 let name = self 8421 .module 8422 .code 8423 .get(value_location.start as usize..value_location.end as usize) 8424 .expect("invalid value range"); 8425 8426 // We trust that the programmer has correctly selected the part 8427 // of the string they want to interpolate and simply "cut it out" 8428 // for them. In future, we could try and parse their selection to 8429 // see if it is a valid expression in Gleam. 8430 if value_location.start == string_location.start + 1 { 8431 self.edits 8432 .insert(string_location.start, format!("{name} <> ")); 8433 } else if value_location.end == string_location.end - 1 { 8434 self.edits 8435 .insert(string_location.end, format!(" <> {name}")); 8436 } else { 8437 self.edits 8438 .insert(value_location.start, format!("\" <> {name} <> \"")); 8439 } 8440 self.edits.delete(value_location); 8441 } 8442 8443 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => { 8444 self.edits.insert(split_at, "\" <> todo <> \"".into()); 8445 } 8446 8447 StringInterpolation::SplitString { .. } => return vec![], 8448 } 8449 8450 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 8451 self.edits.insert(string_location.end, " }".into()); 8452 } 8453 8454 let mut action = Vec::with_capacity(1); 8455 CodeActionBuilder::new("Interpolate string") 8456 .kind(CodeActionKind::RefactorRewrite) 8457 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8458 .preferred(false) 8459 .push_to(&mut action); 8460 action 8461 } 8462 8463 fn can_split_string_at(&self, at: u32) -> bool { 8464 self.string_interpolation 8465 .is_some_and(|(string_location, _)| { 8466 !(at <= string_location.start + 1 || at >= string_location.end - 1) 8467 }) 8468 } 8469 8470 fn visit_literal_string( 8471 &mut self, 8472 string_location: SrcSpan, 8473 string_position: StringLiteralPosition, 8474 ) { 8475 // We can only interpolate/split a string if the cursor is somewhere 8476 // within its location, otherwise we skip it. 8477 let string_range = self.edits.src_span_to_lsp_range(string_location); 8478 if !within(self.params.range, string_range) { 8479 return; 8480 } 8481 8482 let selection @ SrcSpan { start, end } = 8483 self.edits.lsp_range_to_src_span(self.params.range); 8484 8485 // We can't interpolate/split if the double quotes delimiting the 8486 // string have been selected. 8487 if start == string_location.start || end == string_location.end { 8488 return; 8489 } 8490 8491 let name = self 8492 .module 8493 .code 8494 .get(start as usize..end as usize) 8495 .expect("invalid value range"); 8496 8497 // TUI editors like Helix and Kakoune that use the selection-action edit 8498 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always 8499 // have something selected. For programmers using these editors, the 8500 // smallest selection possible is a 1-character selection. The best we can do 8501 // to provide parity with other editors is to consider a single-character SPACE 8502 // selection as an empty selection, as they most likely want to split the 8503 // string instead of interpolating a variable. 8504 let interpolation = if start == end || (end - start == 1 && name == " ") { 8505 StringInterpolation::SplitString { split_at: start } 8506 } else { 8507 StringInterpolation::InterpolateValue { 8508 value_location: selection, 8509 } 8510 }; 8511 self.string_interpolation = Some((string_location, interpolation)); 8512 self.string_literal_position = string_position; 8513 } 8514} 8515 8516impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> { 8517 fn visit_typed_expr_string( 8518 &mut self, 8519 location: &'ast SrcSpan, 8520 _type_: &'ast Arc<Type>, 8521 _value: &'ast EcoString, 8522 ) { 8523 self.visit_literal_string(*location, StringLiteralPosition::Other); 8524 } 8525 8526 fn visit_typed_expr_pipeline( 8527 &mut self, 8528 _location: &'ast SrcSpan, 8529 first_value: &'ast TypedPipelineAssignment, 8530 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 8531 finally: &'ast TypedExpr, 8532 _finally_kind: &'ast PipelineAssignmentKind, 8533 ) { 8534 if first_value.value.is_literal_string() { 8535 self.visit_literal_string( 8536 first_value.location, 8537 StringLiteralPosition::FirstPipelineStep, 8538 ); 8539 } else { 8540 ast::visit::visit_typed_pipeline_assignment(self, first_value); 8541 } 8542 8543 assignments 8544 .iter() 8545 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a)); 8546 self.visit_typed_expr(finally); 8547 } 8548} 8549 8550/// Code action to replace a `..` in a pattern with all the missing fields that 8551/// have not been explicitly provided; labelled ones are introduced with the 8552/// shorthand syntax. 8553/// 8554/// ```gleam 8555/// pub type Pokemon { 8556/// Pokemon(Int, name: String, moves: List(String)) 8557/// } 8558/// 8559/// pub fn main() { 8560/// let Pokemon(..) = todo 8561/// // ^^ Cursor over the spread 8562/// } 8563/// ``` 8564/// Would become 8565/// ```gleam 8566/// pub fn main() { 8567/// let Pokemon(int, name:, moves:) = todo 8568/// } 8569/// 8570pub struct FillUnusedFields<'a> { 8571 module: &'a Module, 8572 params: &'a CodeActionParams, 8573 edits: TextEdits<'a>, 8574 data: Option<FillUnusedFieldsData>, 8575} 8576 8577pub struct FillUnusedFieldsData { 8578 /// All the missing positional and labelled fields. 8579 positional: Vec<Arc<Type>>, 8580 labelled: Vec<(EcoString, Arc<Type>)>, 8581 /// We need this in order to tell where the missing positional arguments 8582 /// should be inserted. 8583 first_labelled_argument_start: Option<u32>, 8584 /// The end of the final argument before the spread, if there's any. 8585 /// We'll use this to delete everything that comes after the final argument, 8586 /// after adding all the ignored fields. 8587 last_argument_end: Option<u32>, 8588 spread_location: SrcSpan, 8589} 8590 8591impl<'a> FillUnusedFields<'a> { 8592 pub fn new( 8593 module: &'a Module, 8594 line_numbers: &'a LineNumbers, 8595 params: &'a CodeActionParams, 8596 ) -> Self { 8597 Self { 8598 module, 8599 params, 8600 edits: TextEdits::new(line_numbers), 8601 data: None, 8602 } 8603 } 8604 8605 pub fn code_actions(mut self) -> Vec<CodeAction> { 8606 self.visit_typed_module(&self.module.ast); 8607 8608 let Some(FillUnusedFieldsData { 8609 positional, 8610 labelled, 8611 first_labelled_argument_start, 8612 last_argument_end, 8613 spread_location, 8614 }) = self.data 8615 else { 8616 return vec![]; 8617 }; 8618 8619 // Do not suggest this code action if there's no ignored fields at all. 8620 if positional.is_empty() && labelled.is_empty() { 8621 return vec![]; 8622 } 8623 8624 // We add all the missing positional arguments before the first 8625 // labelled one (and so after all the already existing positional ones). 8626 if !positional.is_empty() { 8627 // We want to make sure that all positional args will have a name 8628 // that's different from any label. So we add those as already used 8629 // names. 8630 let mut names = NameGenerator::new(); 8631 for (label, _) in labelled.iter() { 8632 names.add_used_name(label.clone()); 8633 } 8634 8635 let positional_arguments = positional 8636 .iter() 8637 .map(|type_| names.generate_name_from_type(type_)) 8638 .join(", "); 8639 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); 8640 8641 // The positional arguments are going to be followed by some other 8642 // arguments if there's some already existing labelled args 8643 // (`last_argument_end.is_some`), of if we're adding those labelled args 8644 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the 8645 // final positional argument we're adding to separate it from the ones that 8646 // are going to come after. 8647 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); 8648 let positional_arguments = if has_arguments_after { 8649 format!("{positional_arguments}, ") 8650 } else { 8651 positional_arguments 8652 }; 8653 8654 self.edits.insert(insert_at, positional_arguments); 8655 } 8656 8657 if !labelled.is_empty() { 8658 // If there's labelled arguments to add, we replace the existing spread 8659 // with the arguments to be added. This way commas and all should already 8660 // be correct. 8661 let labelled_arguments = labelled 8662 .iter() 8663 .map(|(label, _)| format!("{label}:")) 8664 .join(", "); 8665 self.edits.replace(spread_location, labelled_arguments); 8666 } else if let Some(delete_start) = last_argument_end { 8667 // However, if there's no labelled arguments to insert we still need 8668 // to delete the entire spread: we start deleting from the end of the 8669 // final argument, if there's one. 8670 // This way we also get rid of any comma separating the last argument 8671 // and the spread to be removed. 8672 self.edits 8673 .delete(SrcSpan::new(delete_start, spread_location.end)); 8674 } else { 8675 // Otherwise we just delete the spread. 8676 self.edits.delete(spread_location); 8677 } 8678 8679 let mut action = Vec::with_capacity(1); 8680 CodeActionBuilder::new("Fill unused fields") 8681 .kind(CodeActionKind::RefactorRewrite) 8682 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8683 .preferred(false) 8684 .push_to(&mut action); 8685 action 8686 } 8687} 8688 8689impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> { 8690 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { 8691 // We can only interpolate/split a string if the cursor is somewhere 8692 // within its location, otherwise we skip it. 8693 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); 8694 if !within(self.params.range, pattern_range) { 8695 return; 8696 } 8697 8698 if let TypedPattern::Constructor { 8699 arguments, 8700 spread: Some(spread_location), 8701 .. 8702 } = pattern 8703 && let Some(PatternUnusedArguments { 8704 positional, 8705 labelled, 8706 }) = pattern.unused_arguments() 8707 { 8708 // If there's any unused argument that's being ignored we want to 8709 // suggest the code action. 8710 let first_labelled_argument_start = arguments 8711 .iter() 8712 .find(|arg| !arg.is_implicit() && arg.label.is_some()) 8713 .map(|arg| arg.location.start); 8714 8715 let last_argument_end = arguments 8716 .iter() 8717 .rfind(|arg| !arg.is_implicit()) 8718 .map(|arg| arg.location.end); 8719 8720 self.data = Some(FillUnusedFieldsData { 8721 positional, 8722 labelled, 8723 first_labelled_argument_start, 8724 last_argument_end, 8725 spread_location: *spread_location, 8726 }); 8727 } 8728 8729 ast::visit::visit_typed_pattern(self, pattern); 8730 } 8731} 8732 8733/// Code action to remove an echo. 8734/// 8735pub struct RemoveEchos<'a> { 8736 module: &'a Module, 8737 params: &'a CodeActionParams, 8738 edits: TextEdits<'a>, 8739 is_hovering_echo: bool, 8740 echo_spans_to_delete: Vec<SrcSpan>, 8741 // We need to keep a reference to the two latest pipeline assignments we 8742 // run into to properly delete an echo that's inside a pipeline. 8743 latest_pipe_step: Option<SrcSpan>, 8744 second_to_latest_pipe_step: Option<SrcSpan>, 8745} 8746 8747impl<'a> RemoveEchos<'a> { 8748 pub fn new( 8749 module: &'a Module, 8750 line_numbers: &'a LineNumbers, 8751 params: &'a CodeActionParams, 8752 ) -> Self { 8753 Self { 8754 module, 8755 params, 8756 edits: TextEdits::new(line_numbers), 8757 is_hovering_echo: false, 8758 echo_spans_to_delete: vec![], 8759 latest_pipe_step: None, 8760 second_to_latest_pipe_step: None, 8761 } 8762 } 8763 8764 pub fn code_actions(mut self) -> Vec<CodeAction> { 8765 self.visit_typed_module(&self.module.ast); 8766 8767 // We only want to trigger the action if we're over one of the echos in 8768 // the module 8769 if !self.is_hovering_echo { 8770 return vec![]; 8771 } 8772 8773 for span in self.echo_spans_to_delete { 8774 self.edits.delete(span); 8775 } 8776 8777 let mut action = Vec::with_capacity(1); 8778 CodeActionBuilder::new("Remove all `echo`s from this module") 8779 .kind(CodeActionKind::RefactorRewrite) 8780 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8781 .preferred(false) 8782 .push_to(&mut action); 8783 action 8784 } 8785 8786 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) { 8787 for i in 0..statements.len() { 8788 let statement = statements 8789 .get(i) 8790 .expect("Statement must exist in iteration"); 8791 let next_statement = statements.get(i + 1); 8792 let is_last = i == statements.len() - 1; 8793 8794 match statement { 8795 // We remove any echo that is used as a standalone statement used 8796 // to print a literal value. 8797 // 8798 // ```gleam 8799 // pub fn main() { 8800 // echo "I'm here" 8801 // do_something() 8802 // echo "Safe!" 8803 // do_something_else() 8804 // } 8805 // ``` 8806 // 8807 // Here we want to remove not just the echo but also the literal 8808 // strings they're printing. 8809 // 8810 // It's safe to do this only if echo is not the last expression 8811 // in a function's block (otherwise we might change the function's 8812 // return type by removing the entire line) and the value being 8813 // printed is a literal expression. 8814 // 8815 ast::Statement::Expression(TypedExpr::Echo { 8816 location, 8817 expression, 8818 .. 8819 }) if !is_last 8820 && expression.as_ref().is_some_and(|expression| { 8821 expression.is_literal() || expression.is_var() 8822 }) => 8823 { 8824 let echo_range = self.edits.src_span_to_lsp_range(*location); 8825 if within(self.params.range, echo_range) { 8826 self.is_hovering_echo = true; 8827 } 8828 8829 let end = next_statement 8830 .map(|next| { 8831 let echo_end = location.end; 8832 let next_start = next.location().start; 8833 // We want to remove everything until the start of the 8834 // following statement. However, we have to be careful not to 8835 // delete any comments. So if there's any comment between the 8836 // echo to remove and the next statement, we just delete until 8837 // the comment's start. 8838 self.module 8839 .extra 8840 .first_comment_between(echo_end, next_start) 8841 // For comments we record the start of their content, not of the `//` 8842 // so we're subtracting 2 here to not delete the `//` as well 8843 .map(|comment| comment.start - 2) 8844 .unwrap_or(next_start) 8845 }) 8846 .unwrap_or(location.end); 8847 8848 self.echo_spans_to_delete.push(SrcSpan { 8849 start: location.start, 8850 end, 8851 }); 8852 } 8853 8854 // Otherwise we visit the statement as usual. 8855 ast::Statement::Expression(_) 8856 | ast::Statement::Assignment(_) 8857 | ast::Statement::Use(_) 8858 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement), 8859 } 8860 } 8861 } 8862} 8863 8864impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> { 8865 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 8866 self.visit_function_statements(&fun.body); 8867 } 8868 8869 fn visit_typed_expr_fn( 8870 &mut self, 8871 _location: &'ast SrcSpan, 8872 _type_: &'ast Arc<Type>, 8873 _kind: &'ast FunctionLiteralKind, 8874 _arguments: &'ast [TypedArg], 8875 body: &'ast Vec1<TypedStatement>, 8876 _return_annotation: &'ast Option<ast::TypeAst>, 8877 ) { 8878 self.visit_function_statements(body); 8879 } 8880 8881 fn visit_typed_expr_echo( 8882 &mut self, 8883 location: &'ast SrcSpan, 8884 type_: &'ast Arc<Type>, 8885 expression: &'ast Option<Box<TypedExpr>>, 8886 message: &'ast Option<Box<TypedExpr>>, 8887 ) { 8888 // We also want to trigger the action if we're hovering over the expression 8889 // being printed. So we create a unique span starting from the start of echo 8890 // end ending at the end of the expression. 8891 // 8892 // ``` 8893 // echo 1 + 2 8894 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're 8895 // inside it, not just the keyword 8896 // ``` 8897 // 8898 let echo_range = self.edits.src_span_to_lsp_range(*location); 8899 if within(self.params.range, echo_range) { 8900 self.is_hovering_echo = true; 8901 } 8902 8903 // We also want to remove the echo message! 8904 if message.is_some() { 8905 let start = expression 8906 .as_ref() 8907 .map(|expression| expression.location().end) 8908 .unwrap_or(location.start + 4); 8909 8910 self.echo_spans_to_delete 8911 .push(SrcSpan::new(start, location.end)); 8912 } 8913 8914 if let Some(expression) = expression { 8915 // If there's an expression we delete everything we find until its 8916 // start (excluded). 8917 let span_to_delete = SrcSpan::new(location.start, expression.location().start); 8918 self.echo_spans_to_delete.push(span_to_delete); 8919 } else { 8920 // Othwerise we know we're inside a pipeline, we take the closest step 8921 // that is not echo itself and delete everything from its end until the 8922 // end of the echo keyword: 8923 // 8924 // ```txt 8925 // wibble |> echo |> wobble 8926 // ^^^^^^^^ This span right here 8927 // ``` 8928 let step_preceding_echo = self 8929 .latest_pipe_step 8930 .filter(|l| l != location) 8931 .or(self.second_to_latest_pipe_step); 8932 if let Some(step_preceding_echo) = step_preceding_echo { 8933 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4); 8934 self.echo_spans_to_delete.push(span_to_delete); 8935 } 8936 } 8937 8938 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message); 8939 } 8940 8941 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 8942 if self.latest_pipe_step.is_some() { 8943 self.second_to_latest_pipe_step = self.latest_pipe_step; 8944 } 8945 self.latest_pipe_step = Some(assignment.location); 8946 ast::visit::visit_typed_pipeline_assignment(self, assignment); 8947 } 8948} 8949 8950/// Code action to wrap assignment and case clause values in a block. 8951/// 8952/// ```gleam 8953/// pub type PokemonType { 8954/// Fire 8955/// Water 8956/// } 8957/// 8958/// pub fn main() { 8959/// let pokemon_type: PokemonType = todo 8960/// case pokemon_type { 8961/// Water -> soak() 8962/// ^^^^^^ Cursor over the spread 8963/// Fire -> burn() 8964/// } 8965/// } 8966/// ``` 8967/// Becomes 8968/// ```gleam 8969/// pub type PokemonType { 8970/// Fire 8971/// Water 8972/// } 8973/// 8974/// pub fn main() { 8975/// let pokemon_type: PokemonType = todo 8976/// case pokemon_type { 8977/// Water -> { 8978/// soak() 8979/// } 8980/// Fire -> burn() 8981/// } 8982/// } 8983/// ``` 8984/// 8985pub struct WrapInBlock<'a> { 8986 module: &'a Module, 8987 params: &'a CodeActionParams, 8988 edits: TextEdits<'a>, 8989 selected_expression: Option<SrcSpan>, 8990} 8991 8992impl<'a> WrapInBlock<'a> { 8993 pub fn new( 8994 module: &'a Module, 8995 line_numbers: &'a LineNumbers, 8996 params: &'a CodeActionParams, 8997 ) -> Self { 8998 Self { 8999 module, 9000 params, 9001 edits: TextEdits::new(line_numbers), 9002 selected_expression: None, 9003 } 9004 } 9005 9006 pub fn code_actions(mut self) -> Vec<CodeAction> { 9007 self.visit_typed_module(&self.module.ast); 9008 9009 let Some(expr_span) = self.selected_expression else { 9010 return vec![]; 9011 }; 9012 9013 let Some(expr_string) = self 9014 .module 9015 .code 9016 .get(expr_span.start as usize..(expr_span.end as usize + 1)) 9017 else { 9018 return vec![]; 9019 }; 9020 9021 let range = self 9022 .edits 9023 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 9024 9025 let indent_size = 9026 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 9027 9028 let expr_indent_size = indent_size + 2; 9029 9030 let indent = " ".repeat(indent_size); 9031 let inner_indent = " ".repeat(expr_indent_size); 9032 9033 self.edits.replace( 9034 expr_span, 9035 format!("{{\n{inner_indent}{expr_string}{indent}}}"), 9036 ); 9037 9038 let mut action = Vec::with_capacity(1); 9039 CodeActionBuilder::new("Wrap in block") 9040 .kind(CodeActionKind::RefactorExtract) 9041 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9042 .preferred(false) 9043 .push_to(&mut action); 9044 action 9045 } 9046} 9047 9048impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> { 9049 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 9050 ast::visit::visit_typed_expr(self, &assignment.value); 9051 if !within( 9052 self.params.range, 9053 self.edits 9054 .src_span_to_lsp_range(assignment.value.location()), 9055 ) { 9056 return; 9057 } 9058 match &assignment.value { 9059 // To avoid wrapping the same expression in multiple, nested blocks. 9060 TypedExpr::Block { .. } => {} 9061 TypedExpr::RecordAccess { .. } 9062 | TypedExpr::PositionalAccess { .. } 9063 | TypedExpr::Int { .. } 9064 | TypedExpr::Float { .. } 9065 | TypedExpr::String { .. } 9066 | TypedExpr::Pipeline { .. } 9067 | TypedExpr::Var { .. } 9068 | TypedExpr::Fn { .. } 9069 | TypedExpr::List { .. } 9070 | TypedExpr::Call { .. } 9071 | TypedExpr::BinOp { .. } 9072 | TypedExpr::Case { .. } 9073 | TypedExpr::ModuleSelect { .. } 9074 | TypedExpr::Tuple { .. } 9075 | TypedExpr::TupleIndex { .. } 9076 | TypedExpr::Todo { .. } 9077 | TypedExpr::Panic { .. } 9078 | TypedExpr::Echo { .. } 9079 | TypedExpr::BitArray { .. } 9080 | TypedExpr::RecordUpdate { .. } 9081 | TypedExpr::NegateBool { .. } 9082 | TypedExpr::NegateInt { .. } 9083 | TypedExpr::Invalid { .. } => { 9084 self.selected_expression = Some(assignment.value.location()); 9085 } 9086 } 9087 ast::visit::visit_typed_assignment(self, assignment); 9088 } 9089 9090 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 9091 ast::visit::visit_typed_clause(self, clause); 9092 9093 if !within( 9094 self.params.range, 9095 self.edits.src_span_to_lsp_range(clause.then.location()), 9096 ) { 9097 return; 9098 } 9099 9100 // To avoid wrapping the same expression in multiple, nested blocks. 9101 if !matches!(clause.then, TypedExpr::Block { .. }) { 9102 self.selected_expression = Some(clause.then.location()); 9103 } 9104 9105 ast::visit::visit_typed_clause(self, clause); 9106 } 9107} 9108 9109/// Code action to fix wrong binary operators when the compiler can easily tell 9110/// what the correct alternative is. 9111/// 9112/// ```gleam 9113/// 1 +. 2 // becomes 1 + 2 9114/// 1.0 + 2.3 // becomes 1.0 +. 2.3 9115/// ``` 9116/// 9117pub struct FixBinaryOperation<'a> { 9118 module: &'a Module, 9119 params: &'a CodeActionParams, 9120 edits: TextEdits<'a>, 9121 fix: Option<(SrcSpan, ast::BinOp)>, 9122} 9123 9124impl<'a> FixBinaryOperation<'a> { 9125 pub fn new( 9126 module: &'a Module, 9127 line_numbers: &'a LineNumbers, 9128 params: &'a CodeActionParams, 9129 ) -> Self { 9130 Self { 9131 module, 9132 params, 9133 edits: TextEdits::new(line_numbers), 9134 fix: None, 9135 } 9136 } 9137 9138 pub fn code_actions(mut self) -> Vec<CodeAction> { 9139 self.visit_typed_module(&self.module.ast); 9140 9141 let Some((location, replacement)) = self.fix else { 9142 return vec![]; 9143 }; 9144 9145 self.edits.replace(location, replacement.name().into()); 9146 9147 let mut action = Vec::with_capacity(1); 9148 CodeActionBuilder::new(&format!("Use `{}`", replacement.name())) 9149 .kind(CodeActionKind::RefactorRewrite) 9150 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9151 .preferred(true) 9152 .push_to(&mut action); 9153 action 9154 } 9155 9156 fn try_fix( 9157 &mut self, 9158 left: Arc<Type>, 9159 right: Arc<Type>, 9160 operator: ast::BinOp, 9161 operator_start: u32, 9162 ) { 9163 let operator_location = SrcSpan::new(operator_start, operator_start + operator.size()); 9164 if operator.is_int_operator() && left.is_float() && right.is_float() { 9165 self.fix = operator 9166 .float_equivalent() 9167 .map(|fix| (operator_location, fix)); 9168 } else if operator.is_float_operator() && left.is_int() && right.is_int() { 9169 self.fix = operator 9170 .int_equivalent() 9171 .map(|fix| (operator_location, fix)); 9172 } else if operator == ast::BinOp::AddInt && left.is_string() && right.is_string() { 9173 self.fix = Some((operator_location, ast::BinOp::Concatenate)); 9174 } 9175 } 9176} 9177 9178impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> { 9179 fn visit_typed_expr_case( 9180 &mut self, 9181 location: &'ast SrcSpan, 9182 type_: &'ast Arc<Type>, 9183 subjects: &'ast [TypedExpr], 9184 clauses: &'ast [ast::TypedClause], 9185 compiled_case: &'ast CompiledCase, 9186 ) { 9187 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 9188 } 9189 fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) { 9190 ast::visit::visit_typed_clause_guard(self, guard); 9191 } 9192 9193 fn visit_typed_clause_guard_bin_op( 9194 &mut self, 9195 left: &'ast TypedClauseGuard, 9196 right: &'ast TypedClauseGuard, 9197 operator: &'ast ast::BinOp, 9198 operator_start: &'ast u32, 9199 location: &'ast SrcSpan, 9200 ) { 9201 let binop_range = self.edits.src_span_to_lsp_range(*location); 9202 if !within(self.params.range, binop_range) { 9203 return; 9204 } 9205 9206 self.try_fix(left.type_(), right.type_(), *operator, *operator_start); 9207 9208 ast::visit::visit_typed_clause_guard_bin_op( 9209 self, 9210 left, 9211 right, 9212 operator, 9213 operator_start, 9214 location, 9215 ); 9216 } 9217 9218 fn visit_typed_expr_bin_op( 9219 &mut self, 9220 location: &'ast SrcSpan, 9221 type_: &'ast Arc<Type>, 9222 operator: &'ast ast::BinOp, 9223 operator_start: &'ast u32, 9224 left: &'ast TypedExpr, 9225 right: &'ast TypedExpr, 9226 ) { 9227 let binop_range = self.edits.src_span_to_lsp_range(*location); 9228 if !within(self.params.range, binop_range) { 9229 return; 9230 } 9231 9232 self.try_fix(left.type_(), right.type_(), *operator, *operator_start); 9233 9234 ast::visit::visit_typed_expr_bin_op( 9235 self, 9236 location, 9237 type_, 9238 operator, 9239 operator_start, 9240 left, 9241 right, 9242 ); 9243 } 9244} 9245 9246/// Code action builder to automatically fix segments that have a value that's 9247/// guaranteed to overflow. 9248/// 9249pub struct FixTruncatedBitArraySegment<'a> { 9250 module: &'a Module, 9251 params: &'a CodeActionParams, 9252 edits: TextEdits<'a>, 9253 truncation: Option<BitArraySegmentTruncation>, 9254} 9255 9256impl<'a> FixTruncatedBitArraySegment<'a> { 9257 pub fn new( 9258 module: &'a Module, 9259 line_numbers: &'a LineNumbers, 9260 params: &'a CodeActionParams, 9261 ) -> Self { 9262 Self { 9263 module, 9264 params, 9265 edits: TextEdits::new(line_numbers), 9266 truncation: None, 9267 } 9268 } 9269 9270 pub fn code_actions(mut self) -> Vec<CodeAction> { 9271 self.visit_typed_module(&self.module.ast); 9272 9273 let Some(truncation) = self.truncation else { 9274 return vec![]; 9275 }; 9276 9277 let replacement = truncation.truncated_into.to_string(); 9278 self.edits 9279 .replace(truncation.value_location, replacement.clone()); 9280 9281 let mut action = Vec::with_capacity(1); 9282 CodeActionBuilder::new(&format!("Replace with `{replacement}`")) 9283 .kind(CodeActionKind::RefactorRewrite) 9284 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9285 .preferred(true) 9286 .push_to(&mut action); 9287 action 9288 } 9289} 9290 9291impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> { 9292 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) { 9293 let segment_range = self.edits.src_span_to_lsp_range(segment.location); 9294 if !within(self.params.range, segment_range) { 9295 return; 9296 } 9297 9298 if let Some(truncation) = segment.check_for_truncated_value() { 9299 self.truncation = Some(truncation); 9300 } 9301 9302 ast::visit::visit_typed_expr_bit_array_segment(self, segment); 9303 } 9304} 9305 9306/// Code action builder to remove unused imports and values. 9307/// 9308pub struct RemoveUnusedImports<'a> { 9309 module: &'a Module, 9310 params: &'a CodeActionParams, 9311 edits: TextEdits<'a>, 9312} 9313 9314#[derive(Debug)] 9315enum UnusedImport { 9316 ValueOrType(SrcSpan), 9317 Module(SrcSpan), 9318 ModuleAlias(SrcSpan), 9319} 9320 9321impl UnusedImport { 9322 fn location(&self) -> SrcSpan { 9323 match self { 9324 UnusedImport::ValueOrType(location) 9325 | UnusedImport::Module(location) 9326 | UnusedImport::ModuleAlias(location) => *location, 9327 } 9328 } 9329} 9330 9331impl<'a> RemoveUnusedImports<'a> { 9332 pub fn new( 9333 module: &'a Module, 9334 line_numbers: &'a LineNumbers, 9335 params: &'a CodeActionParams, 9336 ) -> Self { 9337 Self { 9338 module, 9339 params, 9340 edits: TextEdits::new(line_numbers), 9341 } 9342 } 9343 9344 /// Given an import location, returns a list of the spans of all the 9345 /// unqualified values it's importing. Sorted by SrcSpan location. 9346 /// 9347 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> { 9348 self.module 9349 .ast 9350 .definitions 9351 .imports 9352 .iter() 9353 .find(|import| import.location.contains(import_location.start)) 9354 .map(|import| { 9355 let types = import.unqualified_types.iter().map(|type_| type_.location); 9356 let values = import.unqualified_values.iter().map(|value| value.location); 9357 types 9358 .chain(values) 9359 .sorted_by_key(|location| location.start) 9360 .collect_vec() 9361 }) 9362 .unwrap_or_default() 9363 } 9364 9365 pub fn code_actions(mut self) -> Vec<CodeAction> { 9366 // If there's no import in the module then there can't be any unused 9367 // import to remove. 9368 if self.module.ast.definitions.imports.is_empty() { 9369 return vec![]; 9370 } 9371 9372 let unused_imports = self 9373 .module 9374 .ast 9375 .type_info 9376 .warnings 9377 .iter() 9378 .filter_map(|warning| match warning { 9379 type_::Warning::UnusedImportedValue { location, .. } => { 9380 Some(UnusedImport::ValueOrType(*location)) 9381 } 9382 type_::Warning::UnusedType { 9383 location, 9384 imported: true, 9385 .. 9386 } => Some(UnusedImport::ValueOrType(*location)), 9387 type_::Warning::UnusedImportedModule { location, .. } => { 9388 Some(UnusedImport::Module(*location)) 9389 } 9390 type_::Warning::UnusedImportedModuleAlias { location, .. } => { 9391 Some(UnusedImport::ModuleAlias(*location)) 9392 } 9393 type_::Warning::Todo { .. } 9394 | type_::Warning::ImplicitlyDiscardedResult { .. } 9395 | type_::Warning::UnusedLiteral { .. } 9396 | type_::Warning::UnusedValue { .. } 9397 | type_::Warning::NoFieldsRecordUpdate { .. } 9398 | type_::Warning::AllFieldsRecordUpdate { .. } 9399 | type_::Warning::UnusedType { .. } 9400 | type_::Warning::UnusedConstructor { .. } 9401 | type_::Warning::UnusedPrivateModuleConstant { .. } 9402 | type_::Warning::UnusedPrivateFunction { .. } 9403 | type_::Warning::UnusedVariable { .. } 9404 | type_::Warning::UnnecessaryDoubleIntNegation { .. } 9405 | type_::Warning::UnnecessaryDoubleBoolNegation { .. } 9406 | type_::Warning::InefficientEmptyListCheck { .. } 9407 | type_::Warning::TransitiveDependencyImported { .. } 9408 | type_::Warning::DeprecatedItem { .. } 9409 | type_::Warning::UnreachableCasePattern { .. } 9410 | type_::Warning::UnusedDiscardPattern { .. } 9411 | type_::Warning::CaseMatchOnLiteralCollection { .. } 9412 | type_::Warning::CaseMatchOnLiteralValue { .. } 9413 | type_::Warning::OpaqueExternalType { .. } 9414 | type_::Warning::RedundantAssertAssignment { .. } 9415 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. } 9416 | type_::Warning::TodoOrPanicUsedAsFunction { .. } 9417 | type_::Warning::UnreachableCodeAfterPanic { .. } 9418 | type_::Warning::RedundantPipeFunctionCapture { .. } 9419 | type_::Warning::FeatureRequiresHigherGleamVersion { .. } 9420 | type_::Warning::JavaScriptIntUnsafe { .. } 9421 | type_::Warning::AssertLiteralBool { .. } 9422 | type_::Warning::BitArraySegmentTruncatedValue { .. } 9423 | type_::Warning::ModuleImportedTwice { .. } 9424 | type_::Warning::TopLevelDefinitionShadowsImport { .. } 9425 | type_::Warning::RedundantComparison { .. } 9426 | type_::Warning::UnusedRecursiveArgument { .. } 9427 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. } 9428 | type_::Warning::PipeIntoCallWhichReturnsFunction { .. } => None, 9429 }) 9430 .sorted_by_key(|import| import.location()) 9431 .collect_vec(); 9432 9433 // If the cursor is not over any of the unused imports then we don't offer 9434 // the code action. 9435 let hovering_unused_import = unused_imports.iter().any(|import| { 9436 let unused_range = self.edits.src_span_to_lsp_range(import.location()); 9437 overlaps(self.params.range, unused_range) 9438 }); 9439 if !hovering_unused_import { 9440 return vec![]; 9441 } 9442 9443 // Otherwise we start removing all unused imports: 9444 for import in &unused_imports { 9445 match import { 9446 // When an entire module is unused we can delete its entire location 9447 // in the source code. 9448 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { 9449 if self.edits.line_numbers.spans_entire_line(location) { 9450 // If the unused module spans over the entire line then 9451 // we also take care of removing the following newline 9452 // characther! 9453 self.edits.delete(SrcSpan { 9454 start: location.start, 9455 end: location.end + 1, 9456 }); 9457 } else { 9458 self.edits.delete(*location); 9459 } 9460 } 9461 9462 // When removing unused imported values we have to be a bit more 9463 // careful: an unused value might be followed or preceded by a 9464 // comma that we also need to remove! 9465 UnusedImport::ValueOrType(location) => { 9466 let imported = self.imported_values(*location); 9467 let unused_index = imported.binary_search(location); 9468 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1); 9469 let next_value = unused_index 9470 .ok() 9471 .and_then(|value_index| imported.get(value_index + 1)); 9472 let previous_value = unused_index.ok().and_then(|value_index| { 9473 value_index 9474 .checked_sub(1) 9475 .and_then(|previous_index| imported.get(previous_index)) 9476 }); 9477 let previous_is_unused = previous_value.is_some_and(|previous| { 9478 unused_imports 9479 .as_slice() 9480 .binary_search_by_key(previous, |import| import.location()) 9481 .is_ok() 9482 }); 9483 9484 match (previous_value, next_value) { 9485 // If there's a value following the unused import we need 9486 // to remove all characters until its start! 9487 // 9488 // ```gleam 9489 // import wibble.{unused, used} 9490 // // ^^^^^^^^^^^ We need to remove all of this! 9491 // ``` 9492 // 9493 (_, Some(next_value)) => self.edits.delete(SrcSpan { 9494 start: location.start, 9495 end: next_value.start, 9496 }), 9497 9498 // If this unused import is the last of the unuqualified 9499 // list and is preceded by another used value then we 9500 // need to do some additional cleanup and remove all 9501 // characters starting from its end. 9502 // (If the previous one is unused as well it will take 9503 // care of removing all the extra space) 9504 // 9505 // ```gleam 9506 // import wibble.{used, unused} 9507 // // ^^^^^^^^^^^^ We need to remove all of this! 9508 // ``` 9509 // 9510 (Some(previous_value), _) if is_last && !previous_is_unused => { 9511 self.edits.delete(SrcSpan { 9512 start: previous_value.end, 9513 end: location.end, 9514 }); 9515 } 9516 9517 // In all other cases it means that this is the only 9518 // item in the import list. We can just remove it. 9519 // 9520 // ```gleam 9521 // import wibble.{unused} 9522 // // ^^^^^^ We remove this import, the formatter will already 9523 // // take care of removing the empty curly braces 9524 // ``` 9525 // 9526 (_, _) => self.edits.delete(*location), 9527 } 9528 } 9529 } 9530 } 9531 9532 let mut action = Vec::with_capacity(1); 9533 CodeActionBuilder::new("Remove unused imports") 9534 .kind(CodeActionKind::RefactorRewrite) 9535 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9536 .preferred(true) 9537 .push_to(&mut action); 9538 action 9539 } 9540} 9541 9542/// Code action to remove a block wrapping a single expression. 9543/// 9544pub struct RemoveBlock<'a> { 9545 module: &'a Module, 9546 params: &'a CodeActionParams, 9547 edits: TextEdits<'a>, 9548 block_span: Option<SrcSpan>, 9549 position: RemoveBlockPosition, 9550} 9551 9552#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] 9553enum RemoveBlockPosition { 9554 InsideBinOp, 9555 OutsideBinOp, 9556} 9557 9558impl<'a> RemoveBlock<'a> { 9559 pub fn new( 9560 module: &'a Module, 9561 line_numbers: &'a LineNumbers, 9562 params: &'a CodeActionParams, 9563 ) -> Self { 9564 Self { 9565 module, 9566 params, 9567 edits: TextEdits::new(line_numbers), 9568 block_span: None, 9569 position: RemoveBlockPosition::OutsideBinOp, 9570 } 9571 } 9572 9573 pub fn code_actions(mut self) -> Vec<CodeAction> { 9574 self.visit_typed_module(&self.module.ast); 9575 9576 let Some(SrcSpan { start, end }) = self.block_span else { 9577 return vec![]; 9578 }; 9579 9580 self.edits.delete(SrcSpan::new(start, start + 1)); 9581 self.edits.delete(SrcSpan::new(end - 1, end)); 9582 9583 let mut action = Vec::with_capacity(1); 9584 CodeActionBuilder::new("Remove block") 9585 .kind(CodeActionKind::RefactorRewrite) 9586 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9587 .preferred(true) 9588 .push_to(&mut action); 9589 action 9590 } 9591} 9592 9593impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> { 9594 fn visit_typed_expr_bin_op( 9595 &mut self, 9596 _location: &'ast SrcSpan, 9597 _type_: &'ast Arc<Type>, 9598 _operator: &'ast ast::BinOp, 9599 _operator_start: &'ast u32, 9600 left: &'ast TypedExpr, 9601 right: &'ast TypedExpr, 9602 ) { 9603 let old_position = self.position; 9604 self.position = RemoveBlockPosition::InsideBinOp; 9605 ast::visit::visit_typed_expr(self, left); 9606 self.position = RemoveBlockPosition::InsideBinOp; 9607 ast::visit::visit_typed_expr(self, right); 9608 self.position = old_position; 9609 } 9610 9611 fn visit_typed_expr_block( 9612 &mut self, 9613 location: &'ast SrcSpan, 9614 statements: &'ast [TypedStatement], 9615 ) { 9616 let block_range = self.edits.src_span_to_lsp_range(*location); 9617 if !within(self.params.range, block_range) { 9618 return; 9619 } 9620 9621 match statements { 9622 [] | [_, _, ..] => (), 9623 [value] => match value { 9624 ast::Statement::Use(_) 9625 | ast::Statement::Assert(_) 9626 | ast::Statement::Assignment(_) => { 9627 ast::visit::visit_typed_expr_block(self, location, statements); 9628 } 9629 9630 ast::Statement::Expression(expr) => match expr { 9631 TypedExpr::Int { .. } 9632 | TypedExpr::Float { .. } 9633 | TypedExpr::String { .. } 9634 | TypedExpr::Block { .. } 9635 | TypedExpr::Var { .. } 9636 | TypedExpr::Fn { .. } 9637 | TypedExpr::List { .. } 9638 | TypedExpr::Call { .. } 9639 | TypedExpr::Case { .. } 9640 | TypedExpr::RecordAccess { .. } 9641 | TypedExpr::PositionalAccess { .. } 9642 | TypedExpr::ModuleSelect { .. } 9643 | TypedExpr::Tuple { .. } 9644 | TypedExpr::TupleIndex { .. } 9645 | TypedExpr::Todo { .. } 9646 | TypedExpr::Panic { .. } 9647 | TypedExpr::Echo { .. } 9648 | TypedExpr::BitArray { .. } 9649 | TypedExpr::RecordUpdate { .. } 9650 | TypedExpr::NegateBool { .. } 9651 | TypedExpr::NegateInt { .. } 9652 | TypedExpr::Invalid { .. } => { 9653 self.block_span = Some(*location); 9654 } 9655 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => { 9656 if self.position == RemoveBlockPosition::OutsideBinOp { 9657 self.block_span = Some(*location); 9658 } 9659 } 9660 }, 9661 }, 9662 } 9663 9664 ast::visit::visit_typed_expr_block(self, location, statements); 9665 } 9666} 9667 9668/// Code action to remove `opaque` from a private type. 9669/// 9670pub struct RemovePrivateOpaque<'a> { 9671 module: &'a Module, 9672 params: &'a CodeActionParams, 9673 edits: TextEdits<'a>, 9674 opaque_span: Option<SrcSpan>, 9675} 9676 9677impl<'a> RemovePrivateOpaque<'a> { 9678 pub fn new( 9679 module: &'a Module, 9680 line_numbers: &'a LineNumbers, 9681 params: &'a CodeActionParams, 9682 ) -> Self { 9683 Self { 9684 module, 9685 params, 9686 edits: TextEdits::new(line_numbers), 9687 opaque_span: None, 9688 } 9689 } 9690 9691 pub fn code_actions(mut self) -> Vec<CodeAction> { 9692 self.visit_typed_module(&self.module.ast); 9693 9694 let Some(opaque_span) = self.opaque_span else { 9695 return vec![]; 9696 }; 9697 9698 self.edits.delete(opaque_span); 9699 9700 let mut action = Vec::with_capacity(1); 9701 CodeActionBuilder::new("Remove opaque from private type") 9702 .kind(CodeActionKind::QuickFix) 9703 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9704 .preferred(true) 9705 .push_to(&mut action); 9706 action 9707 } 9708} 9709 9710impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> { 9711 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 9712 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location); 9713 if !within(self.params.range, custom_type_range) { 9714 return; 9715 } 9716 9717 if custom_type.opaque && custom_type.publicity.is_private() { 9718 self.opaque_span = Some(SrcSpan { 9719 start: custom_type.location.start, 9720 end: custom_type.location.start + 7, 9721 }); 9722 } 9723 } 9724} 9725 9726/// Code action to rewrite a case expression as part of an outer case expression 9727/// branch. For example: 9728/// 9729/// ```gleam 9730/// case wibble { 9731/// Ok(a) -> case a { 9732/// 1 -> todo 9733/// _ -> todo 9734/// } 9735/// Error(_) -> todo 9736/// } 9737/// ``` 9738/// 9739/// Would become: 9740/// 9741/// ```gleam 9742/// case wibble { 9743/// Ok(1) -> todo 9744/// Ok(_) -> todo 9745/// Error(_) -> todo 9746/// } 9747/// ``` 9748/// 9749pub struct CollapseNestedCase<'a> { 9750 module: &'a Module, 9751 params: &'a CodeActionParams, 9752 edits: TextEdits<'a>, 9753 collapsed: Option<Collapsed<'a>>, 9754} 9755 9756/// This holds all the needed data about the pattern to collapse. 9757/// We'll use this piece of code as an example: 9758/// ```gleam 9759/// case something { 9760/// User(username: _, NotAdmin) -> "Stranger!!" 9761/// User(username:, Admin) if wibble -> 9762/// case username { // <- We're collapsing this nested case 9763/// "Joe" -> "Hello, Joe!" 9764/// _ -> "I don't know you, " <> username 9765/// } 9766/// } 9767/// ``` 9768/// 9769struct Collapsed<'a> { 9770 /// This is the span covering the entire clause being collapsed: 9771 /// 9772 /// ```gleam 9773 /// case something { 9774 /// User(username: _, NotAdmin) -> "Stranger!!" 9775 /// User(username:, Admin) if wibble -> 9776 /// ┬ It goes all the way from here... 9777 /// ╭─╯ 9778 /// │ case username { 9779 /// │ "Joe" -> "Hello, Joe!" 9780 /// │ _ -> "I don't know you, " <> username 9781 /// │ } 9782 /// │ ┬ ...to here! 9783 /// ╰───╯ 9784 /// } 9785 /// ``` 9786 /// 9787 outer_clause_span: SrcSpan, 9788 9789 /// The (optional) guard of the outer branch. In this exmaple it's this one: 9790 /// 9791 /// ```gleam 9792 /// case something { 9793 /// User(username: _, NotAdmin) -> "Stranger!!" 9794 /// User(username:, Admin) if wibble -> 9795 /// ┬──────── 9796 /// ╰─ `outer_guard` 9797 /// case username { 9798 /// "Joe" -> "Hello, Joe!" 9799 /// _ -> "I don't know you, " <> username 9800 /// } 9801 /// } 9802 /// ``` 9803 /// 9804 outer_guard: &'a Option<TypedClauseGuard>, 9805 9806 /// The pattern variable being matched on: 9807 /// 9808 /// ```gleam 9809 /// case something { 9810 /// User(username: _, NotAdmin) -> "Stranger!!" 9811 /// User(username:, Admin) if wibble -> 9812 /// ┬─────── 9813 /// ╰─ `matched_variable` 9814 /// case username { 9815 /// "Joe" -> "Hello, Joe!" 9816 /// _ -> "I don't know you, " <> username 9817 /// } 9818 /// } 9819 /// ``` 9820 /// 9821 matched_variable: BoundVariable, 9822 9823 /// The span covering the entire pattern that is bringing the matched 9824 /// variable in scope: 9825 /// 9826 /// ```gleam 9827 /// case something { 9828 /// User(username: _, NotAdmin) -> "Stranger!!" 9829 /// User(username:, Admin) if wibble -> 9830 /// ┬───────────────────── 9831 /// ╰─ `matched_pattern_span` 9832 /// case username { 9833 /// "Joe" -> "Hello, Joe!" 9834 /// _ -> "I don't know you, " <> username 9835 /// } 9836 /// } 9837 /// ``` 9838 /// 9839 matched_pattern_span: SrcSpan, 9840 9841 /// The clauses matching on the `username` variable. In this case they are: 9842 /// ```gleam 9843 /// "Joe" -> "Hello, Joe!" 9844 /// _ -> "I don't know you, " <> username 9845 /// ``` 9846 /// 9847 inner_clauses: &'a Vec<ast::TypedClause>, 9848} 9849 9850impl<'a> CollapseNestedCase<'a> { 9851 pub fn new( 9852 module: &'a Module, 9853 line_numbers: &'a LineNumbers, 9854 params: &'a CodeActionParams, 9855 ) -> Self { 9856 Self { 9857 module, 9858 params, 9859 edits: TextEdits::new(line_numbers), 9860 collapsed: None, 9861 } 9862 } 9863 9864 pub fn code_actions(mut self) -> Vec<CodeAction> { 9865 self.visit_typed_module(&self.module.ast); 9866 9867 let Some(Collapsed { 9868 outer_clause_span, 9869 outer_guard, 9870 ref matched_variable, 9871 matched_pattern_span, 9872 inner_clauses, 9873 }) = self.collapsed 9874 else { 9875 return vec![]; 9876 }; 9877 9878 // Now comes the tricky part: we need to replace the current pattern 9879 // that is bringing the variable into scope with many new patterns, one 9880 // for each of the inner clauses. 9881 // 9882 // Each time we will have to replace the matched variable with the 9883 // pattern used in the inner clause. Let's look at an example: 9884 // 9885 // ```gleam 9886 // Ok(a) -> case a { 9887 // 1 -> wibble 9888 // 2 | 3 -> wobble 9889 // _ -> woo 9890 // } 9891 // ``` 9892 // 9893 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then 9894 // with `2` and `3`, and finally with `_`. Obtaining something like 9895 // this: 9896 // 9897 // ```gleam 9898 // Ok(1) -> wibble 9899 // Ok(2) | Ok(3) -> wobble 9900 // Ok(_) -> woo 9901 // ``` 9902 // 9903 // Notice one key detail: since alternative patterns can't be nested we 9904 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`! 9905 9906 let pattern_text: String = code_at(self.module, matched_pattern_span).into(); 9907 let matched_variable_span = matched_variable.location; 9908 9909 let pattern_with_variable = |mut new_content: String| { 9910 let mut new_pattern = pattern_text.clone(); 9911 9912 match matched_variable { 9913 BoundVariable { 9914 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. }, 9915 .. 9916 } => { 9917 let trimmed_contents = new_content.trim(); 9918 9919 let pattern_is_literal_list = 9920 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]"); 9921 let pattern_is_discard = trimmed_contents == "_"; 9922 9923 let span_to_replace = match ( 9924 &matched_variable.name, 9925 // We verify whether the pattern is compatible with the list prefix `..`. 9926 // For example, `..var` is valid syntax, but `..[]` and `.._` are not. 9927 pattern_is_literal_list || pattern_is_discard, 9928 ) { 9929 // We normally replace the selected variable with the pattern. 9930 (BoundVariableName::Regular { .. }, _) => matched_variable_span, 9931 9932 // If the selected pattern is not a list, we also replace it normally. 9933 (BoundVariableName::ListTail { .. }, false) => matched_variable_span, 9934 // If the pattern is a list to also remove the list tail prefix. 9935 (BoundVariableName::ListTail { tail_location, .. }, true) => { 9936 // When it's a list literal, we remove the surrounding brackets. 9937 let len = trimmed_contents.len(); 9938 if let Some(slice) = new_content.trim().get(1..(len - 1)) { 9939 new_content = slice.to_string(); 9940 } 9941 9942 *tail_location 9943 } 9944 9945 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(), 9946 }; 9947 9948 let start_of_pattern = 9949 (span_to_replace.start - matched_pattern_span.start) as usize; 9950 let pattern_length = span_to_replace.len(); 9951 9952 let end_of_pattern = start_of_pattern + pattern_length; 9953 let replaced_range = start_of_pattern..end_of_pattern; 9954 9955 new_pattern.replace_range(replaced_range, &new_content); 9956 } 9957 9958 BoundVariable { 9959 name: BoundVariableName::ShorthandLabel { .. }, 9960 .. 9961 } => { 9962 // But if it's introduced using the shorthand syntax we can't 9963 // just replace it's location with the new pattern: we would be 9964 // removing the label!! 9965 // So we instead insert the pattern right after the label. 9966 new_pattern.insert_str( 9967 (matched_variable_span.end - matched_pattern_span.start) as usize, 9968 &format!(" {new_content}"), 9969 ); 9970 } 9971 } 9972 9973 new_pattern 9974 }; 9975 9976 let mut new_clauses = vec![]; 9977 for clause in inner_clauses { 9978 // Here we take care of unrolling any alterantive patterns: for each 9979 // of the alternatives we build a new pattern and then join 9980 // everything together with ` | `. 9981 9982 let references_to_matched_variable = 9983 FindVariableReferences::new(matched_variable_span, matched_variable.name()) 9984 .find(&clause.then); 9985 9986 let new_patterns = iter::once(&clause.pattern) 9987 .chain(&clause.alternative_patterns) 9988 .map(|patterns| { 9989 // If we've reached this point we've already made in the 9990 // traversal that the inner clause is matching on a single 9991 // subject. So this should be safe to expect! 9992 let pattern_location = 9993 patterns.first().expect("must have a pattern").location(); 9994 9995 let mut pattern_code = code_at(self.module, pattern_location).to_string(); 9996 if !references_to_matched_variable.is_empty() { 9997 pattern_code = format!("{pattern_code} as {}", matched_variable.name()); 9998 } 9999 pattern_with_variable(pattern_code) 10000 }) 10001 .join(" | "); 10002 10003 let clause_code = code_at(self.module, clause.then.location()); 10004 let guard_code = match (outer_guard, &clause.guard) { 10005 (Some(outer), Some(inner)) => { 10006 let mut outer_code = code_at(self.module, outer.location()).to_string(); 10007 let mut inner_code = code_at(self.module, inner.location()).to_string(); 10008 if ast::BinOp::And.precedence() > outer.precedence() { 10009 outer_code = format!("{{ {outer_code} }}"); 10010 } 10011 if ast::BinOp::And.precedence() > inner.precedence() { 10012 inner_code = format!("{{ {inner_code} }}"); 10013 } 10014 format!(" if {outer_code} && {inner_code}") 10015 } 10016 (None, Some(guard)) | (Some(guard), None) => { 10017 format!(" if {}", code_at(self.module, guard.location())) 10018 } 10019 (None, None) => "".into(), 10020 }; 10021 10022 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}")); 10023 } 10024 10025 let pattern_nesting = self 10026 .edits 10027 .src_span_to_lsp_range(outer_clause_span) 10028 .start 10029 .character; 10030 let indentation = " ".repeat(pattern_nesting as usize); 10031 10032 self.edits.replace( 10033 outer_clause_span, 10034 new_clauses.join(&format!("\n{indentation}")), 10035 ); 10036 10037 let mut action = Vec::with_capacity(1); 10038 CodeActionBuilder::new("Collapse nested case") 10039 .kind(CodeActionKind::RefactorRewrite) 10040 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10041 .preferred(false) 10042 .push_to(&mut action); 10043 action 10044 } 10045 10046 /// If the clause can be flattened because it's matching on a single variable 10047 /// defined in it, this function will return the info needed by the language 10048 /// server to flatten that case. 10049 /// 10050 /// We can only flatten a case expression in a very specific case: 10051 /// - This pattern may be introducing multiple variables, 10052 /// - The expression following this branch must be a case, and 10053 /// - It must be matching on one of those variables 10054 /// 10055 /// For example: 10056 /// 10057 /// ```gleam 10058 /// Wibble(a, b, 1) -> case a { ... } 10059 /// Wibble(a, b, 1) -> case b { ... } 10060 /// ``` 10061 /// 10062 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> { 10063 let ast::TypedClause { 10064 pattern, 10065 alternative_patterns, 10066 then, 10067 location, 10068 guard, 10069 } = clause; 10070 10071 if !alternative_patterns.is_empty() { 10072 return None; 10073 } 10074 10075 // The `then` clause must be a single case expression matching on a 10076 // single variable. 10077 let Some(TypedExpr::Case { 10078 subjects, clauses, .. 10079 }) = single_expression(then) 10080 else { 10081 return None; 10082 }; 10083 10084 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else { 10085 return None; 10086 }; 10087 10088 // That variable must be one the variables we brought into scope in this 10089 // branch. 10090 let variable = pattern 10091 .iter() 10092 .flat_map(|pattern| pattern.bound_variables()) 10093 .find(|variable| variable.name() == *name)?; 10094 10095 // There's one last condition to trigger the code action: we must 10096 // actually be with the cursor over the pattern or the nested case 10097 // expression! 10098 // 10099 // ```gleam 10100 // case wibble { 10101 // Ok(a) -> case a { 10102 // //^^^^^^^^^^^^^^^ Anywhere over here! 10103 // } 10104 // } 10105 // ``` 10106 // 10107 let first_pattern = pattern.first().expect("at least one pattern"); 10108 let last_pattern = pattern.last().expect("at least one pattern"); 10109 let pattern_location = first_pattern.location().merge(&last_pattern.location()); 10110 10111 let last_inner_subject = subjects.last().expect("at least one subject"); 10112 let trigger_location = pattern_location.merge(&last_inner_subject.location()); 10113 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location); 10114 10115 if within(self.params.range, trigger_range) { 10116 Some(Collapsed { 10117 outer_clause_span: *location, 10118 outer_guard: guard, 10119 matched_variable: variable, 10120 matched_pattern_span: pattern_location, 10121 inner_clauses: clauses, 10122 }) 10123 } else { 10124 None 10125 } 10126 } 10127} 10128 10129impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> { 10130 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 10131 if let Some(collapsed) = self.flatten_clause(clause) { 10132 self.collapsed = Some(collapsed); 10133 10134 // We're done, there's no need to keep exploring as we know the 10135 // cursor is over this pattern and it can't be over any other one! 10136 return; 10137 } 10138 10139 ast::visit::visit_typed_clause(self, clause); 10140 } 10141} 10142 10143/// If the expression is a single expression, or a block containing a single 10144/// expression, this function will return it. 10145/// But if the expression is a block with multiple statements, an assignment 10146/// of a use, this will return None. 10147/// 10148fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> { 10149 match expression { 10150 // If a block has a single statement, we can flatten it into a 10151 // single expression if that one statement is an expression. 10152 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() { 10153 ast::Statement::Expression(expression) => single_expression(expression), 10154 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => { 10155 None 10156 } 10157 }, 10158 10159 // If a block has multiple statements then it can't be flattened 10160 // into a single expression. 10161 TypedExpr::Block { .. } => None, 10162 10163 TypedExpr::Int { .. } 10164 | TypedExpr::Float { .. } 10165 | TypedExpr::String { .. } 10166 | TypedExpr::Pipeline { .. } 10167 | TypedExpr::Var { .. } 10168 | TypedExpr::Fn { .. } 10169 | TypedExpr::List { .. } 10170 | TypedExpr::Call { .. } 10171 | TypedExpr::BinOp { .. } 10172 | TypedExpr::Case { .. } 10173 | TypedExpr::RecordAccess { .. } 10174 | TypedExpr::PositionalAccess { .. } 10175 | TypedExpr::ModuleSelect { .. } 10176 | TypedExpr::Tuple { .. } 10177 | TypedExpr::TupleIndex { .. } 10178 | TypedExpr::Todo { .. } 10179 | TypedExpr::Panic { .. } 10180 | TypedExpr::Echo { .. } 10181 | TypedExpr::BitArray { .. } 10182 | TypedExpr::RecordUpdate { .. } 10183 | TypedExpr::NegateBool { .. } 10184 | TypedExpr::NegateInt { .. } 10185 | TypedExpr::Invalid { .. } => Some(expression), 10186 } 10187} 10188 10189/// Code action to remove unreachable clauses from a case expression. 10190/// 10191pub struct RemoveUnreachableCaseClauses<'a> { 10192 module: &'a Module, 10193 params: &'a CodeActionParams, 10194 edits: TextEdits<'a>, 10195 /// The source location of the patterns of all the unreachable clauses in 10196 /// the current module. 10197 /// 10198 unreachable_clauses: HashSet<SrcSpan>, 10199 clauses_to_delete: Vec<SrcSpan>, 10200} 10201 10202impl<'a> RemoveUnreachableCaseClauses<'a> { 10203 pub fn new( 10204 module: &'a Module, 10205 line_numbers: &'a LineNumbers, 10206 params: &'a CodeActionParams, 10207 ) -> Self { 10208 let unreachable_clauses = module 10209 .ast 10210 .type_info 10211 .warnings 10212 .iter() 10213 .filter_map(|warning| { 10214 if let type_::Warning::UnreachableCasePattern { location, .. } = warning { 10215 Some(*location) 10216 } else { 10217 None 10218 } 10219 }) 10220 .collect(); 10221 10222 Self { 10223 unreachable_clauses, 10224 module, 10225 params, 10226 edits: TextEdits::new(line_numbers), 10227 clauses_to_delete: vec![], 10228 } 10229 } 10230 10231 pub fn code_actions(mut self) -> Vec<CodeAction> { 10232 self.visit_typed_module(&self.module.ast); 10233 if self.clauses_to_delete.is_empty() { 10234 return vec![]; 10235 } 10236 10237 for branch in self.clauses_to_delete { 10238 self.edits.delete(branch); 10239 } 10240 10241 let mut action = Vec::with_capacity(1); 10242 CodeActionBuilder::new("Remove unreachable clauses") 10243 .kind(CodeActionKind::QuickFix) 10244 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10245 .preferred(true) 10246 .push_to(&mut action); 10247 action 10248 } 10249} 10250 10251impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> { 10252 fn visit_typed_expr_case( 10253 &mut self, 10254 location: &'ast SrcSpan, 10255 type_: &'ast Arc<Type>, 10256 subjects: &'ast [TypedExpr], 10257 clauses: &'ast [ast::TypedClause], 10258 compiled_case: &'ast CompiledCase, 10259 ) { 10260 // We're showing the code action only if we're within one of the 10261 // unreachable patterns. And the code action is going to remove all the 10262 // unreachable patterns for this case. 10263 let is_hovering_clause = clauses.iter().any(|clause| { 10264 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location()); 10265 within(self.params.range, pattern_range) 10266 }); 10267 10268 // If we're not hovering any of the clauses then we want to 10269 // keep visiting the case expression as the unreachable branch might be 10270 // in one of the nested cases. 10271 if !is_hovering_clause { 10272 ast::visit::visit_typed_expr_case( 10273 self, 10274 location, 10275 type_, 10276 subjects, 10277 clauses, 10278 compiled_case, 10279 ); 10280 return; 10281 } 10282 10283 for clause in clauses { 10284 let mut all_patterns_are_unreachable = true; 10285 let mut unreachable_patterns = vec![]; 10286 let mut previous_pattern_end = None; 10287 let mut all_previous_patterns_were_deleted = true; 10288 10289 for pattern in clause.patterns() { 10290 let pattern_location = multi_pattern_location(pattern); 10291 if self.unreachable_clauses.contains(&pattern_location) { 10292 // If an alternative is unreachable we want to delete 10293 // everything from the end of the previous alternative to 10294 // the start of this one. 10295 // 10296 // ```gleam 10297 // Error(_) | Error(_) | Ok(_) 10298 // // ^^^^^^^^^^^ We want to delete all of this 10299 // ``` 10300 unreachable_patterns.push( 10301 previous_pattern_end.map_or(pattern_location, |previous_end| { 10302 SrcSpan::new(previous_end, pattern_location.end) 10303 }), 10304 ); 10305 } else { 10306 // If all the previous alternatives have been deleted and 10307 // this one is reachable there's some final cleanup we need 10308 // to take care of: 10309 // 10310 // ```gleam 10311 // Ok(_) | Ok(_) | Error(_) -> todo 10312 // //^^^^^^^^^^^ All of this has been deleted, but there's 10313 // // that last vertical bar that needs to be 10314 // // taken care of! 10315 // ``` 10316 // 10317 10318 if all_previous_patterns_were_deleted && let Some(end) = previous_pattern_end { 10319 self.clauses_to_delete 10320 .push(SrcSpan::new(end, pattern_location.start)); 10321 } 10322 10323 all_previous_patterns_were_deleted = false; 10324 all_patterns_are_unreachable = false; 10325 } 10326 10327 previous_pattern_end = pattern.last().map(|pattern| pattern.location().end); 10328 } 10329 10330 if all_patterns_are_unreachable { 10331 // If all the patterns of the clause are unreachable then we 10332 // want to delete the entire branch: 10333 // 10334 // ```gleam 10335 // case a, b { 10336 // _, _ -> todo 10337 // Ok(_), Ok(_) | Error(_), Error(_) -> todo 10338 // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 10339 // // we want the entire branch to be deleted! 10340 // } 10341 // ``` 10342 self.clauses_to_delete.push(clause.location()); 10343 } else { 10344 // If only some of the variants are unreachable but not all 10345 // we want to delete just those. 10346 // case a, b { 10347 // 1, 2 | 1, 2 -> todo 10348 // // ^^^^ just this one should be deleted 10349 // } 10350 self.clauses_to_delete.extend(&unreachable_patterns); 10351 } 10352 } 10353 } 10354} 10355 10356/// Given a pattern with possibly many subjects, this returns the location 10357/// spanning the whole thing. 10358fn multi_pattern_location(alternative: &[Pattern<Arc<Type>>]) -> SrcSpan { 10359 let start = alternative 10360 .first() 10361 .map(|pattern| pattern.location().start) 10362 .unwrap_or_default(); 10363 let end = alternative 10364 .last() 10365 .map(|pattern| pattern.location().end) 10366 .unwrap_or_default(); 10367 10368 SrcSpan::new(start, end) 10369} 10370 10371/// Code action to remove a record update when all of its fields have been 10372/// provided already: 10373/// 10374/// ```gleam 10375/// pub type Wibble { Wibble(one: Int, two: Int) } 10376/// 10377/// wibble(..wibble, one:, two:) 10378/// // ^^^^^^^^ This is not needed and raises a warning! 10379/// ``` 10380/// 10381pub struct RemoveRedundantRecordUpdate<'a> { 10382 module: &'a Module, 10383 params: &'a CodeActionParams, 10384 edits: TextEdits<'a>, 10385} 10386 10387impl<'a> RemoveRedundantRecordUpdate<'a> { 10388 pub fn new( 10389 module: &'a Module, 10390 line_numbers: &'a LineNumbers, 10391 params: &'a CodeActionParams, 10392 ) -> Self { 10393 Self { 10394 module, 10395 params, 10396 edits: TextEdits::new(line_numbers), 10397 } 10398 } 10399 10400 pub fn code_actions(mut self) -> Vec<CodeAction> { 10401 let spread_to_remove = self 10402 .module 10403 .ast 10404 .type_info 10405 .warnings 10406 .iter() 10407 .find_map(|warning| { 10408 if let type_::Warning::AllFieldsRecordUpdate { 10409 location, 10410 record_location, 10411 } = warning 10412 && within( 10413 self.params.range, 10414 self.edits.src_span_to_lsp_range(*location), 10415 ) 10416 { 10417 Some(*record_location) 10418 } else { 10419 None 10420 } 10421 }); 10422 10423 let Some(spread_to_remove) = spread_to_remove else { 10424 return vec![]; 10425 }; 10426 self.edits.delete(spread_to_remove); 10427 10428 let mut action = Vec::with_capacity(1); 10429 CodeActionBuilder::new("Remove redundant record update") 10430 .kind(CodeActionKind::QuickFix) 10431 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10432 .preferred(true) 10433 .push_to(&mut action); 10434 action 10435 } 10436} 10437 10438/// Code action to add labels to a constructor/call where all the labels where 10439/// omitted. 10440/// 10441pub struct AddOmittedLabels<'a> { 10442 module: &'a Module, 10443 params: &'a CodeActionParams, 10444 edits: TextEdits<'a>, 10445 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>, 10446} 10447 10448struct CallArgumentWithOmittedLabel { 10449 location: SrcSpan, 10450 10451 /// If the argument has a label this will be the label we can use for it. 10452 /// 10453 omitted_label: Option<EcoString>, 10454 10455 /// If the argument is a variable that has the same name as the omitted label 10456 /// and could use the shorthand syntax. 10457 /// 10458 can_use_shorthand_syntax: bool, 10459} 10460 10461impl<'a> AddOmittedLabels<'a> { 10462 pub fn new( 10463 module: &'a Module, 10464 line_numbers: &'a LineNumbers, 10465 params: &'a CodeActionParams, 10466 ) -> Self { 10467 Self { 10468 module, 10469 params, 10470 edits: TextEdits::new(line_numbers), 10471 arguments_and_omitted_labels: None, 10472 } 10473 } 10474 10475 pub fn code_actions(mut self) -> Vec<CodeAction> { 10476 self.visit_typed_module(&self.module.ast); 10477 10478 let Some(call_arguments) = self.arguments_and_omitted_labels else { 10479 return vec![]; 10480 }; 10481 10482 for call_argument in call_arguments { 10483 let Some(label) = call_argument.omitted_label else { 10484 continue; 10485 }; 10486 if call_argument.can_use_shorthand_syntax { 10487 self.edits.insert(call_argument.location.end, ":".into()); 10488 } else { 10489 self.edits 10490 .insert(call_argument.location.start, format!("{label}: ")); 10491 } 10492 } 10493 10494 let mut action = Vec::with_capacity(1); 10495 CodeActionBuilder::new("Add omitted labels") 10496 .kind(CodeActionKind::RefactorRewrite) 10497 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10498 .preferred(false) 10499 .push_to(&mut action); 10500 action 10501 } 10502} 10503 10504impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> { 10505 fn visit_typed_expr_call( 10506 &mut self, 10507 location: &'ast SrcSpan, 10508 type_: &'ast Arc<Type>, 10509 fun: &'ast TypedExpr, 10510 arguments: &'ast [TypedCallArg], 10511 open_parenthesis: &'ast Option<u32>, 10512 ) { 10513 let called_function_range = self.edits.src_span_to_lsp_range(fun.location()); 10514 if !within(self.params.range, called_function_range) { 10515 ast::visit::visit_typed_expr_call( 10516 self, 10517 location, 10518 type_, 10519 fun, 10520 arguments, 10521 open_parenthesis, 10522 ); 10523 return; 10524 } 10525 10526 let Some(field_map) = fun.field_map() else { 10527 ast::visit::visit_typed_expr_call( 10528 self, 10529 location, 10530 type_, 10531 fun, 10532 arguments, 10533 open_parenthesis, 10534 ); 10535 return; 10536 }; 10537 let argument_index_to_label = field_map.indices_to_labels(); 10538 10539 let mut omitted_labels = Vec::with_capacity(arguments.len()); 10540 for (index, argument) in arguments.iter().enumerate() { 10541 // If the argument already has a label we don't want to add a label 10542 // for it, so we skip it. 10543 if let Some(label) = &argument.label { 10544 // Though, before skipping, we want to make sure that the label 10545 // is actually right for the function call. If it's not then we 10546 // give up on adding labels because there wouldn't be no way of 10547 // knowing which label to add. 10548 if !field_map.fields.contains_key(label) { 10549 return; 10550 } else { 10551 continue; 10552 } 10553 } 10554 // No labels for pipes, uses, etc! 10555 if argument.is_implicit() { 10556 continue; 10557 } 10558 10559 let label = argument_index_to_label 10560 .get(&(index as u32)) 10561 .cloned() 10562 .cloned(); 10563 10564 let can_use_shorthand_syntax = match (&label, &argument.value) { 10565 (Some(label), TypedExpr::Var { name, .. }) => name == label, 10566 (Some(_) | None, _) => false, 10567 }; 10568 10569 omitted_labels.push(CallArgumentWithOmittedLabel { 10570 location: argument.location, 10571 omitted_label: label, 10572 can_use_shorthand_syntax, 10573 }); 10574 } 10575 self.arguments_and_omitted_labels = Some(omitted_labels); 10576 } 10577} 10578 10579/// Code action to extract selected code into a separate function. 10580/// If a user selected a portion of code in a function, we offer a code action 10581/// to extract it into a new one. This can either be a single expression, such 10582/// as in the following example: 10583/// 10584/// ```gleam 10585/// pub fn main() { 10586/// let value = { 10587/// // ^ User selects from here 10588/// ... 10589/// } 10590/// //^ Until here 10591/// } 10592/// ``` 10593/// 10594/// Here, we would extract the selected block expression. It could also be a 10595/// series of statements. For example: 10596/// 10597/// ```gleam 10598/// pub fn main() { 10599/// let a = 1 10600/// //^ User selects from here 10601/// let b = 2 10602/// let c = a + b 10603/// // ^ Until here 10604/// 10605/// do_more_things(c) 10606/// } 10607/// ``` 10608/// 10609/// Here, we want to extract the statements inside the user's selection. 10610/// 10611pub struct ExtractFunction<'a> { 10612 module: &'a Module, 10613 params: &'a CodeActionParams, 10614 edits: TextEdits<'a>, 10615 function: Option<ExtractedFunction<'a>>, 10616 function_end_position: Option<u32>, 10617 /// Since the `visit_typed_statement` visitor function doesn't tell us when 10618 /// a statement is the last in a block or function, we need to track that 10619 /// manually. 10620 last_statement_location: Option<SrcSpan>, 10621 /// When visiting a pipeline step, this will hold the type of the value 10622 /// returned by the previous step (if any!) 10623 previous_pipeline_assignment_type: Option<Arc<Type>>, 10624} 10625 10626/// Information about a section of code we are extracting as a function. 10627#[derive(Debug)] 10628struct ExtractedFunction<'a> { 10629 /// A list of parameters which need to be passed to the extracted function. 10630 /// These are any variables used in the extracted code, which are defined 10631 /// outside of the extracted code. 10632 parameters: Vec<(EcoString, Arc<Type>)>, 10633 /// A list of values which need to be returned from the extracted function. 10634 /// These are the variables defined in the extracted code which are used 10635 /// outside of the extracted section. 10636 returned_variables: Vec<(EcoString, Arc<Type>)>, 10637 /// The piece of code to be extracted. This is either a single expression or 10638 /// a list of statements, as explained in the documentation of `ExtractFunction` 10639 value: ExtractedValue<'a>, 10640} 10641 10642impl<'a> ExtractedFunction<'a> { 10643 fn new(value: ExtractedValue<'a>) -> Self { 10644 Self { 10645 value, 10646 parameters: Vec::new(), 10647 returned_variables: Vec::new(), 10648 } 10649 } 10650 10651 fn location(&self) -> SrcSpan { 10652 match &self.value { 10653 ExtractedValue::Expression(expression) => expression.location(), 10654 ExtractedValue::Statements { location, .. } 10655 | ExtractedValue::Use { location, .. } 10656 | ExtractedValue::PipelineSteps { location, .. } => *location, 10657 } 10658 } 10659 10660 /// If the extracted function is a series of pipeline steps, this adds to it 10661 /// the given pipeline step, otherwise leaving it unchanged. 10662 /// If the extracted function was indeed a pipeline, this will return `true`, 10663 /// otherwise it returns `false`. 10664 /// 10665 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) { 10666 if let ExtractedFunction { 10667 value: 10668 ExtractedValue::PipelineSteps { 10669 location, 10670 before_first: _, 10671 return_type, 10672 }, 10673 .. 10674 } = self 10675 { 10676 // If we're extracting this pipeline and the final step is included 10677 // in the selection we want to add it to the extracted steps 10678 *return_type = step_type; 10679 *location = location.merge(&step_location); 10680 } 10681 } 10682} 10683 10684#[derive(Debug)] 10685enum ExtractedValue<'a> { 10686 Expression(&'a TypedExpr), 10687 Statements { 10688 location: SrcSpan, 10689 position: StatementPosition, 10690 /// The type of the final statement. 10691 type_: Arc<Type>, 10692 }, 10693 /// We're extracting a single use statement. We need this special case to 10694 /// properly handle the statements inside of them. 10695 Use { 10696 /// This is the location of the entire use block, including the 10697 /// statements in it. 10698 location: SrcSpan, 10699 /// This is the location of the expression on the right hand side of the use 10700 /// arrow. 10701 /// 10702 /// ```gleam 10703 /// use a <- result.try(result) 10704 /// ^^^^^^^^^^^^^^^^^^ 10705 /// ``` 10706 /// 10707 use_line_location: SrcSpan, 10708 type_: Arc<Type>, 10709 }, 10710 PipelineSteps { 10711 location: SrcSpan, 10712 /// The type of the value produced by the pipeline steps that will be 10713 /// piped into the extracted function. Could be none if the steps we're 10714 /// extracting include the first step, in that case there would be 10715 /// nothing that is fed into it. 10716 before_first: Option<Arc<Type>>, 10717 /// The type returned by the extracted steps. 10718 return_type: Arc<Type>, 10719 }, 10720} 10721 10722impl ExtractedValue<'_> { 10723 fn location(&self) -> SrcSpan { 10724 match self { 10725 ExtractedValue::Expression(typed_expr) => typed_expr.location(), 10726 ExtractedValue::Statements { location, .. } 10727 | ExtractedValue::PipelineSteps { location, .. } 10728 | ExtractedValue::Use { location, .. } => *location, 10729 } 10730 } 10731} 10732 10733#[derive(Debug)] 10734enum StatementPosition { 10735 Tail, 10736 NotTail, 10737} 10738 10739impl<'a> ExtractFunction<'a> { 10740 pub fn new( 10741 module: &'a Module, 10742 line_numbers: &'a LineNumbers, 10743 params: &'a CodeActionParams, 10744 ) -> Self { 10745 Self { 10746 module, 10747 params, 10748 edits: TextEdits::new(line_numbers), 10749 function: None, 10750 function_end_position: None, 10751 last_statement_location: None, 10752 previous_pipeline_assignment_type: None, 10753 } 10754 } 10755 10756 pub fn code_actions(mut self) -> Vec<CodeAction> { 10757 // If no code is selected, then there is no function to extract and we 10758 // can return no code actions. 10759 if self.params.range.start == self.params.range.end { 10760 return Vec::new(); 10761 } 10762 10763 self.visit_typed_module(&self.module.ast); 10764 10765 let Some(end) = self.function_end_position else { 10766 return Vec::new(); 10767 }; 10768 10769 // If nothing was found in the selected range, there is no code action. 10770 let Some(extracted) = self.function.take() else { 10771 return Vec::new(); 10772 }; 10773 10774 match extracted.value { 10775 // If we extract a block, it isn't very helpful to have the body of 10776 // the extracted function just be a single block expression, so 10777 // instead we extract the statements inside the block. For example, 10778 // the following code: 10779 // 10780 // ```gleam 10781 // pub fn main() { 10782 // let x = { 10783 // // ^ Select from here 10784 // let a = 1 10785 // let b = 2 10786 // a + b 10787 // } 10788 // //^ Until here 10789 // x 10790 // } 10791 // ``` 10792 // 10793 // Would produce the following extracted function: 10794 // 10795 // ```gleam 10796 // fn function() { 10797 // let a = 1 10798 // let b = 2 10799 // a + b 10800 // } 10801 // ``` 10802 // 10803 // Rather than: 10804 // 10805 // ```gleam 10806 // fn function() { 10807 // { 10808 // let a = 1 10809 // let b = 2 10810 // a + b 10811 // } 10812 // } 10813 // ``` 10814 // 10815 ExtractedValue::Expression(TypedExpr::Block { 10816 statements, 10817 location: full_location, 10818 }) => { 10819 let location = statements 10820 .first() 10821 .location() 10822 .merge(&statements.last().location()); 10823 10824 self.extract_unbound_statements( 10825 *full_location, 10826 location, 10827 extracted.parameters, 10828 statements.last().type_(), 10829 end, 10830 ); 10831 } 10832 ExtractedValue::Expression(TypedExpr::Fn { 10833 type_, 10834 location: full_location, 10835 kind: FunctionLiteralKind::Anonymous { .. }, 10836 arguments, 10837 body, 10838 .. 10839 }) => { 10840 let location = body.first().location().merge(&body.last().location()); 10841 let return_type = type_.return_type().expect("Fn should have a return type"); 10842 10843 if extracted.parameters.is_empty() { 10844 self.extract_anonymous_function( 10845 *full_location, 10846 location, 10847 arguments, 10848 return_type, 10849 end, 10850 ); 10851 } else if arguments.len() == 1 { 10852 self.extract_anonymous_function_with_capture_hole( 10853 *full_location, 10854 location, 10855 arguments.first().expect("There is exactly one argument"), 10856 extracted.parameters, 10857 return_type, 10858 end, 10859 ); 10860 } else { 10861 self.extract_anonymous_function_body( 10862 location, 10863 arguments, 10864 extracted.parameters, 10865 return_type, 10866 end, 10867 ); 10868 } 10869 } 10870 ExtractedValue::Expression(expression) => { 10871 let expression_type = if let TypedExpr::Fn { 10872 type_, 10873 kind: FunctionLiteralKind::Use { .. }, 10874 .. 10875 } = expression 10876 { 10877 type_.fn_types().expect("use callback to be a function").1 10878 } else { 10879 expression.type_() 10880 }; 10881 self.extract_unbound_statements( 10882 expression.location(), 10883 expression.location(), 10884 extracted.parameters, 10885 expression_type, 10886 end, 10887 ); 10888 } 10889 ExtractedValue::Statements { 10890 location, 10891 position: StatementPosition::NotTail, 10892 type_, 10893 } => { 10894 if extracted.returned_variables.is_empty() { 10895 self.extract_unbound_statements( 10896 location, 10897 location, 10898 extracted.parameters, 10899 type_, 10900 end, 10901 ); 10902 } else { 10903 self.extract_bound_statements( 10904 location, 10905 extracted.parameters, 10906 extracted.returned_variables, 10907 end, 10908 ); 10909 } 10910 } 10911 10912 ExtractedValue::Use { 10913 location, 10914 use_line_location: _, 10915 type_, 10916 } 10917 | ExtractedValue::Statements { 10918 location, 10919 position: StatementPosition::Tail, 10920 type_, 10921 } => self.extract_unbound_statements( 10922 location, 10923 location, 10924 extracted.parameters, 10925 type_, 10926 end, 10927 ), 10928 ExtractedValue::PipelineSteps { 10929 location, 10930 before_first, 10931 return_type, 10932 } => self.extract_pipeline_steps( 10933 location, 10934 end, 10935 extracted.parameters, 10936 before_first, 10937 return_type, 10938 ), 10939 } 10940 10941 let mut action = Vec::with_capacity(1); 10942 CodeActionBuilder::new("Extract function") 10943 .kind(CodeActionKind::RefactorExtract) 10944 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10945 .preferred(false) 10946 .push_to(&mut action); 10947 action 10948 } 10949 10950 /// Choose a suitable name for an extracted function to make sure it doesn't 10951 /// clash with existing functions defined in the module and cause an error. 10952 fn function_name(&self) -> EcoString { 10953 if !self.module.ast.type_info.values.contains_key("function") { 10954 return "function".into(); 10955 } 10956 10957 let mut number = 2; 10958 loop { 10959 let name = eco_format!("function_{number}"); 10960 if !self.module.ast.type_info.values.contains_key(&name) { 10961 return name; 10962 } 10963 number += 1; 10964 } 10965 } 10966 10967 /// For anonymous functions that do not capture any variables from an outer scope. 10968 /// Moves the function so it is defined at the module top-level instead, 10969 /// replacing the original literal with a reference to the new function. 10970 fn extract_anonymous_function( 10971 &mut self, 10972 location: SrcSpan, 10973 code_location: SrcSpan, 10974 arguments: &[TypedArg], 10975 return_type: Arc<Type>, 10976 function_end: u32, 10977 ) { 10978 // --- BEFORE 10979 // ```gleam 10980 // pub fn main() { 10981 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) }) 10982 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 10983 // } 10984 // ``` 10985 // 10986 // --- AFTER 10987 // ```gleam 10988 // pub fn main() { 10989 // list.each([1, 2, 3], function) 10990 // } 10991 // 10992 // fn function(x: Int) -> Nil { 10993 // io.println(int.to_string(x)) 10994 // } 10995 // ``` 10996 10997 let name = self.function_name(); 10998 self.edits.replace(location, name.to_string()); 10999 11000 let mut printer = Printer::new(&self.module.ast.names); 11001 11002 let return_type = printer.print_type(&return_type); 11003 let function_body = code_at(self.module, code_location); 11004 let mut name_generator = NameGenerator::new(); 11005 let arguments = arguments 11006 .iter() 11007 .map(|arg| { 11008 if let Some(name) = arg.get_variable_name() { 11009 eco_format!("{name}: {}", printer.print_type(&arg.type_)) 11010 } else { 11011 let name = name_generator.generate_name_from_type(&arg.type_); 11012 eco_format!("_{name}: {}", printer.print_type(&arg.type_)) 11013 } 11014 }) 11015 .join(", "); 11016 11017 let function = format!( 11018 "\n\nfn {name}({arguments}) -> {return_type} {{ 11019 {function_body} 11020}}" 11021 ); 11022 self.edits.insert(function_end, function); 11023 } 11024 11025 /// For anonymous functions that capture variables from an external scope 11026 /// but only expect a single argument. 11027 /// Uses function caputre syntax to provide a more concise refactoring than 11028 /// `extract_anonymous_function_body`. 11029 fn extract_anonymous_function_with_capture_hole( 11030 &mut self, 11031 location: SrcSpan, 11032 code_location: SrcSpan, 11033 argument: &TypedArg, 11034 extra_parameters: Vec<(EcoString, Arc<Type>)>, 11035 return_type: Arc<Type>, 11036 function_end: u32, 11037 ) { 11038 let name = self.function_name(); 11039 11040 // --- BEFORE 11041 // ```gleam 11042 // pub fn main() { 11043 // let needle = 42 11044 // let haystack = [25, 81, 74, 42, 33] 11045 // list.filter(haystack, fn(x) { x == needle }) 11046 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 11047 // } 11048 // ``` 11049 // 11050 // --- AFTER 11051 // ```gleam 11052 // pub fn main() { 11053 // let needle = 42 11054 // let haystack = [25, 81, 74, 42, 33] 11055 // list.filter(haystack, function(_, needle)) 11056 // } 11057 // 11058 // fn function(x: Int, needle: Int) -> Bool { 11059 // x == needle 11060 // } 11061 // ``` 11062 11063 let call = format!( 11064 "{name}(_, {})", 11065 extra_parameters.iter().map(|(name, _)| name).join(", ") 11066 ); 11067 self.edits.replace(location, call); 11068 11069 let mut printer = Printer::new(&self.module.ast.names); 11070 11071 // build up the code for the newly generated function 11072 let return_type = printer.print_type(&return_type); 11073 let function_body = code_at(self.module, code_location); 11074 let argument = if let Some(name) = argument.get_variable_name() { 11075 eco_format!("{name}: {}", printer.print_type(&argument.type_)) 11076 } else { 11077 let name = NameGenerator::new().generate_name_from_type(&argument.type_); 11078 eco_format!("_{name}: {}", printer.print_type(&argument.type_)) 11079 }; 11080 let extra_parameters = extra_parameters 11081 .iter() 11082 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11083 .join(", "); 11084 11085 let function = format!( 11086 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{ 11087 {function_body} 11088}}" 11089 ); 11090 self.edits.insert(function_end, function); 11091 } 11092 11093 /// For non-unary anonymous functions that capture variables from an external scope. 11094 /// Replaces just the _function body_ with a call to the newly generated function. 11095 fn extract_anonymous_function_body( 11096 &mut self, 11097 location: SrcSpan, 11098 arguments: &[TypedArg], 11099 extra_parameters: Vec<(EcoString, Arc<Type>)>, 11100 return_type: Arc<Type>, 11101 function_end: u32, 11102 ) { 11103 let name = self.function_name(); 11104 // --- BEFORE 11105 // ```gleam 11106 // pub fn main() { 11107 // let factor = 2 11108 // list.fold([], 0, fn(acc, value) { acc + value * factor }) 11109 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 11110 // } 11111 // ``` 11112 // 11113 // --- AFTER 11114 // ```gleam 11115 // pub fn main() { 11116 // let factor = 2 11117 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) }) 11118 // } 11119 // 11120 // fn function(acc: Int, value: Int, factor: Int) -> Int { 11121 // acc + value * factor 11122 // } 11123 // ``` 11124 11125 // if the programmer has ignored an argument, the generated function 11126 // cannot take it as an parameter 11127 let arguments = arguments 11128 .iter() 11129 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_))) 11130 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_))) 11131 .collect::<Vec<(&EcoString, &Arc<Type>)>>(); 11132 11133 let call = format!( 11134 "{name}({})", 11135 arguments.iter().map(|(name, _)| name).join(", ") 11136 ); 11137 self.edits.replace(location, call); 11138 11139 let mut printer = Printer::new(&self.module.ast.names); 11140 11141 let return_type = printer.print_type(&return_type); 11142 let function_body = code_at(self.module, location); 11143 let arguments = arguments 11144 .iter() 11145 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11146 .join(", "); 11147 11148 let function = format!( 11149 "\n\nfn {name}({arguments}) -> {return_type} {{ 11150 {function_body} 11151}}" 11152 ); 11153 self.edits.insert(function_end, function); 11154 } 11155 11156 /// Extracts a function whose return value does not get bound to a variable 11157 /// in the calling function. 11158 /// 11159 /// There are two cases: 11160 /// 1. The function is in tail position, and its return value is used as the 11161 /// return value of the calling function. 11162 /// 2. The function's return value is unused in the calling function, and so 11163 /// does not get bound to a variable. 11164 /// 11165 /// # Parameters 11166 /// 11167 /// In most cases, `location` and `code_location` are the same. They differ 11168 /// only when the code being extracted is a single block expression. In 11169 /// that case, the code being replaced in the calling function (`location`) 11170 /// includes the braces around the block, while the code being put into the 11171 /// body of the extracted function (`code_location`) does not. 11172 fn extract_unbound_statements( 11173 &mut self, 11174 location: SrcSpan, 11175 code_location: SrcSpan, 11176 parameters: Vec<(EcoString, Arc<Type>)>, 11177 return_type: Arc<Type>, 11178 function_end: u32, 11179 ) { 11180 let expression_code = code_at(self.module, code_location); 11181 11182 let name = self.function_name(); 11183 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 11184 let call = format!("{name}({arguments})"); 11185 11186 // Since we are only extracting a single expression, we can just replace 11187 // it with the call and preserve all other semantics; only one value can 11188 // be returned from the expression, unlike when extracting multiple 11189 // statements. 11190 self.edits.replace(location, call); 11191 11192 let mut printer = Printer::new(&self.module.ast.names); 11193 11194 let parameters = parameters 11195 .iter() 11196 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11197 .join(", "); 11198 let return_type = printer.print_type(&return_type); 11199 11200 let function = format!( 11201 "\n\nfn {name}({parameters}) -> {return_type} {{ 11202 {expression_code} 11203}}" 11204 ); 11205 11206 self.edits.insert(function_end, function); 11207 } 11208 11209 /// Extracts a function that does get bound to one or more variables in the 11210 /// calling function. 11211 /// 11212 /// For example, in the code 11213 /// 11214 /// ```gleam 11215 /// pub fn main() { 11216 /// let a = 10 11217 /// //^ Select from here 11218 /// let b = 20 11219 /// let c = a + b 11220 /// // ^ Until here 11221 /// 11222 /// echo a 11223 /// echo b 11224 /// echo c 11225 /// } 11226 /// ``` 11227 /// 11228 /// multiple values defined in the extracted block are used in the calling 11229 /// function. So, the extracted function must return multiple values that 11230 /// are bound to variables: `let #(a, b, c) = function()`. 11231 /// 11232 /// There is a special case when there is only one value being bound, as it 11233 /// is unnecessary to create a single-element tuple. For example: 11234 /// 11235 /// ```gleam 11236 /// pub fn main() { 11237 /// let a = 10 11238 /// //^ Select from here 11239 /// let b = 20 11240 /// let c = a + b 11241 /// // ^ Until here 11242 /// 11243 /// echo c 11244 /// } 11245 /// ``` 11246 /// 11247 /// Here, the return value of the extracted function will be bound to a 11248 /// single variable in the calling function: `let c = function()`. 11249 fn extract_bound_statements( 11250 &mut self, 11251 location: SrcSpan, 11252 parameters: Vec<(EcoString, Arc<Type>)>, 11253 returned_variables: Vec<(EcoString, Arc<Type>)>, 11254 function_end: u32, 11255 ) { 11256 let code = code_at(self.module, location); 11257 11258 let (return_type, return_value) = match returned_variables.as_slice() { 11259 [(returned_name, type_)] => (type_.clone(), returned_name.clone()), 11260 _ => { 11261 let returned_names = returned_variables 11262 .iter() 11263 .map(|(name, _type)| name) 11264 .join(", "); 11265 let return_value = eco_format!("#({returned_names})"); 11266 11267 let returned_types = returned_variables 11268 .into_iter() 11269 .map(|(_name, type_)| type_) 11270 .collect(); 11271 let type_ = type_::tuple(returned_types); 11272 11273 (type_, return_value) 11274 } 11275 }; 11276 11277 let name = self.function_name(); 11278 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 11279 let call = format!("let {return_value} = {name}({arguments})"); 11280 11281 self.edits.replace(location, call); 11282 11283 let mut printer = Printer::new(&self.module.ast.names); 11284 11285 let parameters = parameters 11286 .iter() 11287 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11288 .join(", "); 11289 let return_type = printer.print_type(&return_type); 11290 11291 let function = format!( 11292 "\n\nfn {name}({parameters}) -> {return_type} {{ 11293 {code} 11294 {return_value} 11295}}" 11296 ); 11297 11298 self.edits.insert(function_end, function); 11299 } 11300 11301 fn extract_pipeline_steps( 11302 &mut self, 11303 location: SrcSpan, 11304 function_end: u32, 11305 parameters: Vec<(EcoString, Arc<Type>)>, 11306 before_first: Option<Arc<Type>>, 11307 return_type: Arc<Type>, 11308 ) { 11309 let name = self.function_name(); 11310 let code = code_at(self.module, location); 11311 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", "); 11312 let replacement = match before_first { 11313 Some(_) if parameters.is_empty() => format!("{name}"), 11314 Some(_) | None => format!("{name}({arguments})"), 11315 }; 11316 self.edits.replace(location, replacement); 11317 11318 // When extracting something out of the middle of a pipeline the 11319 // function we produce will produce a single value as output but could 11320 // take multiple values as input: 11321 // 11322 // ```gleam 11323 // wibble 11324 // |> wobble(a) // extracting this 11325 // |> woo(b) // 11326 // |> something 11327 // ``` 11328 // 11329 // It will take the type returned by `wibble`, `a`, and `b` as input, 11330 // and produce the value returned by `woo` as output: 11331 // 11332 // ```gleam 11333 // wibble 11334 // |> function(a, b) 11335 // |> something 11336 // ``` 11337 // 11338 // If the steps extracted are at the beginning of the pipeline, then it 11339 // won't take that additional argument! 11340 // 11341 // ```gleam 11342 // wibble // extracting these 11343 // |> wobble(a) // 11344 // |> woo(b) // 11345 // |> something 11346 // ``` 11347 // 11348 // Becomes: 11349 // 11350 // ```gleam 11351 // function(a, b) 11352 // |> something 11353 // ``` 11354 11355 let mut type_printer = Printer::new(&self.module.ast.names); 11356 let return_type = type_printer.print_type(&return_type); 11357 let first_argument = before_first.map(|type_of_first_argument| { 11358 let mut generator = NameGenerator::new(); 11359 for (name, _) in parameters.iter() { 11360 generator.add_used_name(name.clone()); 11361 } 11362 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument); 11363 (first_argument_name, type_of_first_argument.clone()) 11364 }); 11365 let parameters = first_argument 11366 .clone() 11367 .into_iter() 11368 .chain(parameters) 11369 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_))) 11370 .join(", "); 11371 11372 let code = if let Some((first_argument_name, _)) = first_argument { 11373 format!("{first_argument_name}\n |> {code}") 11374 } else { 11375 code.trim_start_matches("|>").to_string() 11376 }; 11377 let function = format!( 11378 "\n\nfn {name}({parameters}) -> {return_type} {{ 11379 {code} 11380}}" 11381 ); 11382 11383 self.edits.insert(function_end, function); 11384 } 11385 11386 /// When a variable is referenced, we need to decide if we need to do anything 11387 /// to ensure that the reference is still valid after extracting a function. 11388 /// If the variable is defined outside the extracted function, but used inside 11389 /// it, then we need to add it as a parameter of the function. Similarly, if 11390 /// a variable is defined inside the extracted code, but used outside of it, 11391 /// we need to ensure that value is returned from the function so that it is 11392 /// accessible. 11393 fn register_referenced_variable( 11394 &mut self, 11395 name: &EcoString, 11396 type_: &Arc<Type>, 11397 location: SrcSpan, 11398 definition_location: SrcSpan, 11399 ) { 11400 let Some(extracted) = &mut self.function else { 11401 return; 11402 }; 11403 11404 let extracted_location = extracted.location(); 11405 11406 // If a variable defined outside the extracted code is referenced inside 11407 // it, we need to add it to the list of parameters. 11408 let variables = if extracted_location.contains_span(location) 11409 && !extracted_location.contains_span(definition_location) 11410 { 11411 &mut extracted.parameters 11412 // If a variable defined inside the extracted code is referenced outside 11413 // it, then we need to ensure that it is returned from the function. 11414 } else if extracted_location.contains_span(definition_location) 11415 && !extracted_location.contains_span(location) 11416 { 11417 &mut extracted.returned_variables 11418 } else { 11419 return; 11420 }; 11421 11422 // If the variable has already been tracked, no need to register it again. 11423 // We use a `Vec` here rather than a `HashMap` because we want to ensure 11424 // the order of arguments is consistent; in this case it will be determined 11425 // by the order the variables are used. This isn't always desired, but it's 11426 // better than random order, and makes it easier to write tests too. 11427 // The cost of iterating the list here is minimal; it is unlikely that 11428 // a given function will ever have more than 10 or so parameters. 11429 if variables.iter().any(|(variable, _)| variable == name) { 11430 return; 11431 } 11432 11433 variables.push((name.clone(), type_.clone())); 11434 } 11435 11436 fn can_extract_expression(&self, expression: &TypedExpr) -> bool { 11437 let expression_range = self.edits.src_span_to_lsp_range(expression.location()); 11438 let selected_range = self.params.range; 11439 11440 // If the selected range doesn't touch the expression at all, then there 11441 // is no reason to extract it. 11442 if !overlaps(expression_range, selected_range) { 11443 return false; 11444 } 11445 11446 match expression { 11447 TypedExpr::Pipeline { 11448 first_value, 11449 finally, 11450 .. 11451 } => { 11452 // We can extract a pipeline as a whole only if the selection 11453 // spans all of its steps! 11454 let first_step = self.edits.src_span_to_lsp_range(first_value.location); 11455 let last_step = self.edits.src_span_to_lsp_range(finally.location()); 11456 position_within(selected_range.start, first_step) 11457 && position_within(selected_range.end, last_step) 11458 } 11459 11460 TypedExpr::Int { .. } 11461 | TypedExpr::Float { .. } 11462 | TypedExpr::String { .. } 11463 | TypedExpr::Block { .. } 11464 | TypedExpr::Var { .. } 11465 | TypedExpr::Fn { .. } 11466 | TypedExpr::List { .. } 11467 | TypedExpr::Call { .. } 11468 | TypedExpr::BinOp { .. } 11469 | TypedExpr::Case { .. } 11470 | TypedExpr::RecordAccess { .. } 11471 | TypedExpr::PositionalAccess { .. } 11472 | TypedExpr::ModuleSelect { .. } 11473 | TypedExpr::Tuple { .. } 11474 | TypedExpr::TupleIndex { .. } 11475 | TypedExpr::Todo { .. } 11476 | TypedExpr::Panic { .. } 11477 | TypedExpr::Echo { .. } 11478 | TypedExpr::BitArray { .. } 11479 | TypedExpr::RecordUpdate { .. } 11480 | TypedExpr::NegateBool { .. } 11481 | TypedExpr::NegateInt { .. } 11482 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range), 11483 } 11484 } 11485 11486 fn can_extract_statement(&self, statement: &TypedStatement) -> bool { 11487 let statement_range = self.edits.src_span_to_lsp_range(statement.location()); 11488 let selected_range = self.params.range; 11489 11490 // If the selected range doesn't touch the statement at all, then there 11491 // is no reason to extract it. 11492 if !overlaps(statement_range, selected_range) { 11493 return false; 11494 } 11495 11496 match statement { 11497 ast::Statement::Expression(expression) => self.can_extract_expression(expression), 11498 11499 // Determine whether the selected range falls completely within the 11500 // expression. For example: 11501 // ```gleam 11502 // pub fn main() { 11503 // let something = { 11504 // let a = 1 11505 // let b = 2 11506 // let c = a + b 11507 // //^ The user has selected from here 11508 // let d = a * b 11509 // c / d 11510 // // ^ Until here 11511 // } 11512 // } 11513 // ``` 11514 // 11515 // Here, the selected range does overlap with the `let something` 11516 // statement; but we don't want to extract that whole statement! The 11517 // user only wanted to extract the statements inside the block. So if 11518 // the selected range falls completely within the expression, we ignore 11519 // it and traverse the tree further until we find exactly what the user 11520 // selected. 11521 // 11522 // If the selected range is completely within the expression, we don't 11523 // want to extract it. 11524 ast::Statement::Use(_) | ast::Statement::Assert(_) => { 11525 !completely_within(selected_range, statement_range) 11526 } 11527 11528 // We can only extract a whole let statement if the assignment 11529 // part itself is selected. If the only part being selected is the 11530 // expression then the right call is not extracting the whole 11531 // statement but just the expression. 11532 ast::Statement::Assignment(assignment) => { 11533 let value_range = self 11534 .edits 11535 .src_span_to_lsp_range(assignment.value.location()); 11536 11537 !within(selected_range, value_range) 11538 && !completely_within(selected_range, statement_range) 11539 } 11540 } 11541 } 11542} 11543 11544impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> { 11545 fn visit_typed_function(&mut self, function: &'ast TypedFunction) { 11546 let range = self.edits.src_span_to_lsp_range(function.full_location()); 11547 11548 if within(self.params.range, range) { 11549 self.function_end_position = Some(function.end_position); 11550 self.last_statement_location = function.body.last().map(|last| last.location()); 11551 11552 ast::visit::visit_typed_function(self, function); 11553 } 11554 } 11555 11556 fn visit_typed_expr_block( 11557 &mut self, 11558 location: &'ast SrcSpan, 11559 statements: &'ast [TypedStatement], 11560 ) { 11561 let last_statement_location = self.last_statement_location; 11562 self.last_statement_location = statements.last().map(|last| last.location()); 11563 11564 ast::visit::visit_typed_expr_block(self, location, statements); 11565 11566 self.last_statement_location = last_statement_location; 11567 } 11568 11569 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 11570 let last_statement_location = self.last_statement_location; 11571 // The body must be visited first, before the desugared function 11572 if let TypedExpr::Call { arguments, .. } = &*use_.call 11573 && let Some(CallArg { 11574 value: TypedExpr::Fn { body, .. }, 11575 .. 11576 }) = arguments.last() 11577 { 11578 self.last_statement_location = Some(body.last().location()); 11579 for statement in body { 11580 self.visit_typed_statement(statement); 11581 } 11582 } 11583 ast::visit::visit_typed_use(self, use_); 11584 self.last_statement_location = last_statement_location; 11585 } 11586 11587 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 11588 // If we have already determined what code we want to extract, we don't 11589 // want to extract this instead. This expression would be inside the 11590 // piece of code we already are going to extract, leading to us 11591 // extracting just a single literal in any selection, which is of course 11592 // not desired. 11593 if self.function.is_none() { 11594 // If this expression is fully selected, we mark it as being extracted. 11595 if self.can_extract_expression(expression) { 11596 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression( 11597 expression, 11598 ))); 11599 } 11600 } 11601 11602 // If the expression is a function, then the last statement location 11603 // needs to be updated accordingly 11604 let last_statement_location = self.last_statement_location; 11605 if let TypedExpr::Fn { body, .. } = expression { 11606 self.last_statement_location = Some(body.last().location()); 11607 } 11608 11609 ast::visit::visit_typed_expr(self, expression); 11610 11611 self.last_statement_location = last_statement_location; 11612 } 11613 11614 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 11615 let statement_location = statement.location(); 11616 11617 if self.can_extract_statement(statement) { 11618 let is_in_tail_position = 11619 self.last_statement_location 11620 .is_some_and(|last_statement_location| { 11621 last_statement_location == statement_location 11622 }); 11623 11624 // A use is always eating up the entire block, if we're extracting it, 11625 // it will be in tail position there and the extracted function should 11626 // return its returned value. 11627 let position = if statement.is_use() || is_in_tail_position { 11628 StatementPosition::Tail 11629 } else { 11630 StatementPosition::NotTail 11631 }; 11632 11633 match &mut self.function { 11634 None => { 11635 self.function = match statement { 11636 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None, 11637 TypedStatement::Assert(_) 11638 | TypedStatement::Assignment(_) 11639 | TypedStatement::Expression(_) => { 11640 Some(ExtractedFunction::new(ExtractedValue::Statements { 11641 location: statement_location, 11642 position, 11643 type_: statement.type_(), 11644 })) 11645 } 11646 TypedStatement::Use(use_) => { 11647 Some(ExtractedFunction::new(ExtractedValue::Use { 11648 location: use_.call.location(), 11649 use_line_location: use_.location, 11650 type_: statement.type_(), 11651 })) 11652 } 11653 }; 11654 } 11655 11656 // If we're extracting something that is within a use expression 11657 // we need to check whether to extract the whole use or just 11658 // statements inside of it 11659 Some(ExtractedFunction { 11660 value: 11661 ExtractedValue::Use { 11662 location, 11663 use_line_location, 11664 .. 11665 }, 11666 parameters, 11667 returned_variables, 11668 }) if location.contains_span(statement_location) => { 11669 let use_line_range = self.edits.src_span_to_lsp_range(*use_line_location); 11670 11671 // If the current statement is not a part of the use line, 11672 // and the current selection does not overlap the use line, 11673 // then the outer use is not the correct target. 11674 if !use_line_location.contains_span(statement_location) 11675 && !overlaps(use_line_range, self.params.range) 11676 { 11677 self.function = Some(ExtractedFunction { 11678 value: ExtractedValue::Statements { 11679 location: statement_location, 11680 position, 11681 type_: statement.type_(), 11682 }, 11683 parameters: parameters.to_vec(), 11684 returned_variables: returned_variables.to_vec(), 11685 }); 11686 } 11687 } 11688 // Otherwise it means we're extracting multiple statements 11689 // _including_ some use expression, we fallback to extracting 11690 // multiple statements 11691 Some(ExtractedFunction { 11692 value: value @ ExtractedValue::Use { .. }, 11693 .. 11694 }) => { 11695 *value = ExtractedValue::Statements { 11696 location: value.location().merge(&statement_location), 11697 position, 11698 type_: statement.type_(), 11699 }; 11700 } 11701 11702 // If we have already chosen an expression to extract, that means 11703 // that this statement is within the already extracted expression, 11704 // so we don't want to extract this instead. 11705 Some(ExtractedFunction { 11706 value: ExtractedValue::Expression(_), 11707 .. 11708 }) => {} 11709 // If we are selecting multiple statements, this statement should 11710 // be included within that list, so we merge the spans to ensure 11711 // it is included. 11712 Some(ExtractedFunction { 11713 value: 11714 ExtractedValue::Statements { 11715 location, 11716 position: extracted_position, 11717 type_: extracted_type, 11718 }, 11719 .. 11720 }) => { 11721 *location = location.merge(&statement_location); 11722 *extracted_position = position; 11723 *extracted_type = statement.type_(); 11724 } 11725 Some(ExtractedFunction { 11726 value: value @ ExtractedValue::PipelineSteps { .. }, 11727 .. 11728 }) => { 11729 // If we were extracting a pipeline, but end up selecting 11730 // some statement that is not part of it, then we go back to 11731 // selecting a batch of statements. 11732 *value = ExtractedValue::Statements { 11733 location: value.location(), 11734 position, 11735 type_: statement.type_(), 11736 } 11737 } 11738 } 11739 } 11740 ast::visit::visit_typed_statement(self, statement); 11741 } 11742 11743 fn visit_typed_expr_pipeline( 11744 &mut self, 11745 _location: &'ast SrcSpan, 11746 first_value: &'ast TypedPipelineAssignment, 11747 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 11748 finally: &'ast TypedExpr, 11749 _finally_kind: &'ast PipelineAssignmentKind, 11750 ) { 11751 self.previous_pipeline_assignment_type = None; 11752 self.visit_typed_pipeline_assignment(first_value); 11753 11754 self.previous_pipeline_assignment_type = Some(first_value.type_()); 11755 for (assignment, _kind) in assignments { 11756 self.visit_typed_pipeline_assignment(assignment); 11757 self.previous_pipeline_assignment_type = Some(assignment.type_()); 11758 } 11759 11760 // If we're selecting a pipeline and the selection ends on its final step 11761 // we want to include that as well into the extracted bit. 11762 let final_step_range = self.edits.src_span_to_lsp_range(finally.location()); 11763 if let Some(extracted_function) = &mut self.function 11764 && position_within(self.params.range.end, final_step_range) 11765 { 11766 extracted_function.try_add_pipeline_step(finally.type_(), finally.location()); 11767 } 11768 11769 self.visit_typed_expr(finally); 11770 self.previous_pipeline_assignment_type = None; 11771 } 11772 11773 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 11774 // In order to be extracted, a pipeline step must be overlapping with 11775 // the cursor selection! 11776 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 11777 if !overlaps(self.params.range, assignment_range) { 11778 return; 11779 } 11780 11781 match &mut self.function { 11782 None => { 11783 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps { 11784 location: assignment.location, 11785 before_first: self.previous_pipeline_assignment_type.clone(), 11786 return_type: assignment.type_(), 11787 })); 11788 } 11789 Some(extracted_function) => { 11790 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location); 11791 } 11792 } 11793 ast::visit::visit_typed_pipeline_assignment(self, assignment); 11794 } 11795 11796 fn visit_typed_expr_var( 11797 &mut self, 11798 location: &'ast SrcSpan, 11799 constructor: &'ast ValueConstructor, 11800 name: &'ast EcoString, 11801 ) { 11802 if let type_::ValueConstructorVariant::LocalVariable { 11803 location: definition_location, 11804 .. 11805 } = &constructor.variant 11806 { 11807 self.register_referenced_variable( 11808 name, 11809 &constructor.type_, 11810 *location, 11811 *definition_location, 11812 ); 11813 } 11814 } 11815 11816 fn visit_typed_clause_guard_var( 11817 &mut self, 11818 location: &'ast SrcSpan, 11819 name: &'ast EcoString, 11820 type_: &'ast Arc<Type>, 11821 definition_location: &'ast SrcSpan, 11822 _origin: &'ast VariableOrigin, 11823 ) { 11824 self.register_referenced_variable(name, type_, *location, *definition_location); 11825 } 11826 11827 fn visit_typed_expr_case( 11828 &mut self, 11829 location: &'ast SrcSpan, 11830 type_: &'ast Arc<Type>, 11831 subjects: &'ast [TypedExpr], 11832 clauses: &'ast [ast::TypedClause], 11833 compiled_case: &'ast CompiledCase, 11834 ) { 11835 let was_extracting_already = self.function.is_some(); 11836 11837 // We first visit as usual... 11838 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 11839 11840 // But then we need to check we're in a situation where it actually makes 11841 // sense to extract: if the cursor is entirely within the case (so it's 11842 // not part of a bigger extracted chunk) and it spans over one of the 11843 // branches' pattern or guard, then we don't want to allow extracting 11844 // anything. Popping up the action would be confusing. 11845 // For example: 11846 // 11847 // ```gleam 11848 // case wibble { 11849 // Ok(_) -> todo 11850 // //^^^^^^^^^^^^^ If I'm selecting this whole branch it makes no sense 11851 // to propose extracting it as a function 11852 // _ if wibble -> todo 11853 // // ^^^^^^^^^^^ If I'm selecting this it makes no sense to 11854 // // propose extracting it as a function 11855 // } 11856 // ``` 11857 11858 // We were already extracting something, we don't want to render that 11859 // choice null, this is just a part of a bigger piece being extracted. 11860 if was_extracting_already { 11861 return; 11862 } 11863 11864 for clause in clauses { 11865 let left_hand_side_location = SrcSpan { 11866 start: clause.pattern_location().start, 11867 end: clause.then.location().start - 1, 11868 }; 11869 let left_hand_side_range = self.edits.src_span_to_lsp_range(left_hand_side_location); 11870 // If there's any overlapping with one of the patterns, then we 11871 // don't want to extract anything. 11872 if overlaps(self.params.range, left_hand_side_range) { 11873 self.function = None; 11874 break; 11875 } 11876 } 11877 } 11878 11879 fn visit_typed_bit_array_size_variable( 11880 &mut self, 11881 location: &'ast SrcSpan, 11882 name: &'ast EcoString, 11883 constructor: &'ast Option<Box<ValueConstructor>>, 11884 type_: &'ast Arc<Type>, 11885 ) { 11886 let variant = match constructor { 11887 Some(constructor) => &constructor.variant, 11888 None => return, 11889 }; 11890 if let type_::ValueConstructorVariant::LocalVariable { 11891 location: definition_location, 11892 .. 11893 } = variant 11894 { 11895 self.register_referenced_variable(name, type_, *location, *definition_location); 11896 } 11897 } 11898} 11899 11900/// Code action to merge two identical branches together. 11901/// 11902pub struct MergeCaseBranches<'a> { 11903 module: &'a Module, 11904 params: &'a CodeActionParams, 11905 edits: TextEdits<'a>, 11906 /// These are the positions of the patterns of all the consecutive branches 11907 /// we've determined can be merged, for example if we're mergin the first 11908 /// two branches here: 11909 /// 11910 /// ```gleam 11911 /// case wibble { 11912 /// 1 -> todo 11913 /// // ^ this location here 11914 /// 20 -> todo 11915 /// // ^^ and this location here 11916 /// _ -> todo 11917 /// } 11918 /// ``` 11919 /// 11920 /// We need those to delete all the space between each consecutive pattern, 11921 /// replacing it with the `|` for alternatives 11922 /// 11923 patterns_to_merge: Option<MergeableBranches>, 11924} 11925 11926struct MergeableBranches { 11927 /// The span of the body to keep when merging multiple branches. For 11928 /// example: 11929 /// 11930 /// ```gleam 11931 /// case n { 11932 /// // Imagine we're merging the first three branches together... 11933 /// 1 -> todo 11934 /// 2 -> n * 2 11935 /// // ^^^^^ This would be the location of the one body to keep 11936 /// 3 -> todo 11937 /// _ -> todo 11938 /// } 11939 /// ``` 11940 /// 11941 body_to_keep: SrcSpan, 11942 11943 /// The location body of the last of the branches that are going to be 11944 /// merged; that is where we're going to place the code of the body to keep 11945 /// once the action is done. For example: 11946 /// 11947 /// ```gleam 11948 /// case n { 11949 /// // Imagine we're merging the first three branches together... 11950 /// 1 -> todo 11951 /// 2 -> n * 2 11952 /// 3 -> todo 11953 /// // ^^^^ This would be the location of the final body 11954 /// _ -> todo 11955 /// } 11956 /// ``` 11957 /// 11958 final_body: SrcSpan, 11959 11960 /// The span of the patterns whose branches are going to be merged. For 11961 /// example: 11962 /// 11963 /// ```gleam 11964 /// case n { 11965 /// // Imagine we're merging the first three branches together... 11966 /// 1 -> todo 11967 /// // ^ 11968 /// 2 -> n * 2 11969 /// // ^ 11970 /// 3 -> todo 11971 /// // ^ These would be the locations of the patterns 11972 /// _ -> todo 11973 /// } 11974 /// ``` 11975 /// 11976 patterns_to_merge: Vec<SrcSpan>, 11977} 11978 11979impl<'a> MergeCaseBranches<'a> { 11980 pub fn new( 11981 module: &'a Module, 11982 line_numbers: &'a LineNumbers, 11983 params: &'a CodeActionParams, 11984 ) -> Self { 11985 Self { 11986 module, 11987 params, 11988 edits: TextEdits::new(line_numbers), 11989 patterns_to_merge: None, 11990 } 11991 } 11992 11993 pub fn code_actions(mut self) -> Vec<CodeAction> { 11994 self.visit_typed_module(&self.module.ast); 11995 11996 let Some(mergeable_branches) = self.patterns_to_merge else { 11997 return vec![]; 11998 }; 11999 12000 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() { 12001 self.edits 12002 .replace(SrcSpan::new(one.end, next.start), " | ".into()); 12003 } 12004 12005 self.edits.replace( 12006 mergeable_branches.final_body, 12007 code_at(self.module, mergeable_branches.body_to_keep).into(), 12008 ); 12009 12010 let mut action = Vec::with_capacity(1); 12011 CodeActionBuilder::new("Merge case branches") 12012 .kind(CodeActionKind::RefactorRewrite) 12013 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12014 .preferred(false) 12015 .push_to(&mut action); 12016 action 12017 } 12018 12019 fn select_mergeable_branches( 12020 &self, 12021 clauses: &'a [ast::TypedClause], 12022 ) -> Option<MergeableBranches> { 12023 let mut clauses = clauses 12024 .iter() 12025 // We want to skip all the branches at the beginning of the case 12026 // expression that the cursor is not hovering over. For example: 12027 // 12028 // ```gleam 12029 // case wibble { 12030 // a -> 1 <- we want to skip this one here that is not selected 12031 // b -> 2 12032 // ^^^^ this is the selection 12033 // _ -> 3 12034 // ^^ 12035 // } 12036 // ``` 12037 .skip_while(|clause| { 12038 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 12039 !overlaps(self.params.range, clause_range) 12040 }) 12041 // Then we only want to take the clauses that we're hovering over 12042 // with our selection (even partially!) 12043 // In the provious example they would be `b -> 2` and `_ -> 3`. 12044 .take_while(|clause| { 12045 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 12046 overlaps(self.params.range, clause_range) 12047 }); 12048 12049 let first_hovered_clause = clauses.next()?; 12050 12051 // This is the clause we're comparing all the others with. We need to 12052 // make sure that all the clauses we're going to join can be merged with 12053 // this one. 12054 let mut reference_clause = first_hovered_clause; 12055 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()]; 12056 let mut final_body = first_hovered_clause.then.location(); 12057 12058 for clause in clauses { 12059 // As soon as we find a clause that can't be merged with the current 12060 // reference we know we're done looking for consecutive clauses to 12061 // merge. 12062 if !clauses_can_be_merged(reference_clause, clause) { 12063 break; 12064 } 12065 12066 clause_patterns_to_merge.push(clause.pattern_location()); 12067 final_body = clause.then.location(); 12068 12069 // If the current reference is a `todo` expression, we want to use 12070 // the newly found mergeable clause as the next reference. The 12071 // reference clause is the one whose body will be kept around, so if 12072 // we can we avoid keeping `todo`s 12073 if reference_clause.then.is_todo_with_no_message() { 12074 reference_clause = clause; 12075 } 12076 } 12077 12078 // We only offer the code action if we have found two or more clauses 12079 // to merge. 12080 if clause_patterns_to_merge.len() >= 2 { 12081 Some(MergeableBranches { 12082 final_body, 12083 body_to_keep: reference_clause.then.location(), 12084 patterns_to_merge: clause_patterns_to_merge, 12085 }) 12086 } else { 12087 None 12088 } 12089 } 12090} 12091 12092fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool { 12093 // Two clauses cannot be merged if any of those has an if guard 12094 if one.guard.is_some() || other.guard.is_some() { 12095 return false; 12096 } 12097 12098 // Two clauses can only be merged if they define the same variables, 12099 // otherwise joining them would result in invalid code. 12100 let variables_one = one 12101 .bound_variables() 12102 .map(|variable| (variable.name(), variable.type_)) 12103 .collect::<HashMap<_, _>>(); 12104 12105 let variables_other = other 12106 .bound_variables() 12107 .map(|variable| (variable.name(), variable.type_)) 12108 .collect::<HashMap<_, _>>(); 12109 12110 for (name, type_) in variables_one.iter() { 12111 if let Some(type_other) = variables_other.get(name) 12112 && type_other.same_as(type_) 12113 { 12114 continue; 12115 } 12116 12117 // There's a variable that is not defined in the second branch but 12118 // is defined in the first one, or it's defined in the second branch 12119 // but it has an incompatible type. 12120 return false; 12121 } 12122 12123 for (name, _) in variables_other.iter() { 12124 if !variables_one.contains_key(name) { 12125 // There's some variables defined in the second branch that are not 12126 // defined in the first one, so they can't be merged! 12127 return false; 12128 } 12129 } 12130 12131 // Anything can be merged with a simple todo, or the two bodies must be 12132 // syntactically equal. 12133 one.then.is_todo_with_no_message() 12134 || other.then.is_todo_with_no_message() 12135 || one.then.syntactically_eq(&other.then) 12136} 12137 12138impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> { 12139 fn visit_typed_expr_case( 12140 &mut self, 12141 location: &'ast SrcSpan, 12142 type_: &'ast Arc<Type>, 12143 subjects: &'ast [TypedExpr], 12144 clauses: &'ast [ast::TypedClause], 12145 compiled_case: &'ast CompiledCase, 12146 ) { 12147 // We only trigger the code action if we are within a case expression, 12148 // otherwise there's no point in exploring the expression any further. 12149 let case_range = self.edits.src_span_to_lsp_range(*location); 12150 if !within(self.params.range, case_range) { 12151 return; 12152 } 12153 12154 if let result @ Some(_) = self.select_mergeable_branches(clauses) { 12155 self.patterns_to_merge = result; 12156 } 12157 12158 // We still need to visit the case expression in case we want to apply 12159 // the code action to some case expression that is nested in one of its 12160 // branches! 12161 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 12162 } 12163} 12164 12165/// Code action to add a missing type parameter to custom types. 12166/// If a custom type is missing a type parameter, as it is the case 12167/// in the following example, this action will offer to add the 12168/// type parameter to the type definition. 12169/// 12170/// Before: 12171/// ```gleam 12172/// type Wibble { 12173/// Wibble(field: t) 12174/// } 12175/// ``` 12176/// 12177/// After: 12178/// ```gleam 12179/// type Wibble(t) { 12180/// Wibble(field: t) 12181/// } 12182/// ``` 12183/// 12184pub struct AddMissingTypeParameter<'a> { 12185 module: &'a Module, 12186 params: &'a CodeActionParams, 12187 edits: TextEdits<'a>, 12188 /// The source location where the parameters should be defined. 12189 /// This might be a zero-length span if there are no parameters yet, 12190 /// or it might cover the already existing type parameter definitions. 12191 parameters_location: Option<SrcSpan>, 12192 /// If the type definition already had existing parameters before. 12193 has_existing_parameters: bool, 12194 /// The set of all type parameter names in the different variants of the type 12195 /// that are not already part of the type parameter definition on the type. 12196 missing_parameters: HashSet<EcoString>, 12197} 12198 12199impl<'a> AddMissingTypeParameter<'a> { 12200 pub fn new( 12201 module: &'a Module, 12202 line_numbers: &'a LineNumbers, 12203 params: &'a CodeActionParams, 12204 ) -> Self { 12205 Self { 12206 module, 12207 params, 12208 edits: TextEdits::new(line_numbers), 12209 parameters_location: None, 12210 has_existing_parameters: false, 12211 missing_parameters: HashSet::new(), 12212 } 12213 } 12214 12215 pub fn code_actions(mut self) -> Vec<CodeAction> { 12216 self.visit_typed_module(&self.module.ast); 12217 12218 let Some(type_parameters_location) = self.parameters_location else { 12219 return vec![]; 12220 }; 12221 12222 if self.missing_parameters.is_empty() { 12223 return vec![]; 12224 } 12225 12226 let mut new_parameters = self.missing_parameters.iter().sorted().join(", "); 12227 if self.has_existing_parameters { 12228 let has_trailing_comma = self 12229 .module 12230 .extra 12231 .trailing_commas 12232 .iter() 12233 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma)); 12234 12235 if !has_trailing_comma { 12236 new_parameters.insert_str(0, ", "); 12237 } 12238 12239 self.edits 12240 .insert(type_parameters_location.end - 1, new_parameters); 12241 } else { 12242 self.edits 12243 .insert(type_parameters_location.end, format!("({new_parameters})")); 12244 } 12245 12246 let mut action = Vec::with_capacity(1); 12247 CodeActionBuilder::new("Add missing type parameter") 12248 .kind(CodeActionKind::QuickFix) 12249 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12250 .preferred(true) 12251 .push_to(&mut action); 12252 action 12253 } 12254} 12255 12256impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> { 12257 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 12258 let custom_type_range = self 12259 .edits 12260 .src_span_to_lsp_range(custom_type.full_location()); 12261 12262 // Only continue, if the action was selected anywhere within the custom type definition. 12263 if !within(self.params.range, custom_type_range) { 12264 return; 12265 } 12266 12267 self.parameters_location = Some(SrcSpan::new( 12268 custom_type.name_location.end, 12269 custom_type.location.end, 12270 )); 12271 12272 self.has_existing_parameters = !custom_type.typed_parameters.is_empty(); 12273 12274 let existing_names: HashSet<_> = custom_type 12275 .parameters 12276 .iter() 12277 .map(|(_, name)| name) 12278 .collect(); 12279 12280 // Collect the remaining type parameters from the variant constructors. 12281 for record in &custom_type.constructors { 12282 for argument in &record.arguments { 12283 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast 12284 && !existing_names.contains(name) 12285 { 12286 let _ = self.missing_parameters.insert(name.clone()); 12287 } 12288 } 12289 } 12290 } 12291} 12292 12293/// Code action to replace a `_` with its actual type in an annotation. 12294/// 12295/// Before: 12296/// ```gleam 12297/// fn wibble() -> Ok(_) { Ok(1) } 12298/// // ^ Trigger it here 12299/// ``` 12300/// 12301/// After: 12302/// ```gleam 12303/// fn wibble() -> Ok(Int) { Ok(1) } 12304/// ``` 12305/// 12306pub struct ReplaceUnderscoreWithType<'a> { 12307 module: &'a Module, 12308 params: &'a CodeActionParams, 12309 edits: TextEdits<'a>, 12310 hovered_hole: Option<HoveredHole>, 12311} 12312 12313struct HoveredHole { 12314 type_: Arc<Type>, 12315 location: SrcSpan, 12316} 12317 12318impl<'a> ReplaceUnderscoreWithType<'a> { 12319 pub fn new( 12320 module: &'a Module, 12321 line_numbers: &'a LineNumbers, 12322 params: &'a CodeActionParams, 12323 ) -> Self { 12324 Self { 12325 module, 12326 params, 12327 edits: TextEdits::new(line_numbers), 12328 hovered_hole: None, 12329 } 12330 } 12331 12332 pub fn code_actions(mut self) -> Vec<CodeAction> { 12333 self.visit_typed_module(&self.module.ast); 12334 12335 let mut action = Vec::with_capacity(1); 12336 12337 let Some(HoveredHole { type_, location }) = self.hovered_hole else { 12338 return vec![]; 12339 }; 12340 let mut printer = Printer::new(&self.module.ast.names); 12341 self.edits 12342 .replace(location, format!("{}", printer.print_type(&type_))); 12343 12344 CodeActionBuilder::new("Replace `_` with type") 12345 .kind(CodeActionKind::QuickFix) 12346 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12347 .preferred(true) 12348 .push_to(&mut action); 12349 action 12350 } 12351} 12352 12353impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> { 12354 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) { 12355 // We never traverse a type annotation we're not hovering 12356 let node_location = self.edits.src_span_to_lsp_range(node.location()); 12357 if !within(self.params.range, node_location) { 12358 return; 12359 } 12360 ast::visit::visit_type_ast(self, node, inferred_type); 12361 } 12362 12363 fn visit_type_ast_hole( 12364 &mut self, 12365 location: &'ast SrcSpan, 12366 _name: &'ast EcoString, 12367 type_: Option<Arc<Type>>, 12368 ) { 12369 if let Some(type_) = type_ { 12370 self.hovered_hole = Some(HoveredHole { 12371 type_, 12372 location: *location, 12373 }); 12374 } 12375 } 12376} 12377 12378/// Code action to turn a function used as a reference into a one-statement anonymous function. 12379/// 12380/// For example, if the code action was used on `op` here: 12381/// 12382/// ```gleam 12383/// list.map([1, 2, 3], op) 12384/// ``` 12385/// 12386/// it would become: 12387/// 12388/// ```gleam 12389/// list.map([1, 2, 3], fn(int) { 12390/// op(int) 12391/// }) 12392/// ``` 12393pub struct WrapInAnonymousFunction<'a> { 12394 module: &'a Module, 12395 params: &'a CodeActionParams, 12396 functions: Vec<FunctionToWrap>, 12397 edits: TextEdits<'a>, 12398} 12399 12400/// Helper struct, a target for [WrapInAnonymousFunction]. 12401struct FunctionToWrap { 12402 location: SrcSpan, 12403 arguments: Vec<Arc<Type>>, 12404 variables_names: VariablesNames, 12405} 12406 12407impl<'a> WrapInAnonymousFunction<'a> { 12408 pub fn new( 12409 module: &'a Module, 12410 line_numbers: &'a LineNumbers, 12411 params: &'a CodeActionParams, 12412 ) -> Self { 12413 Self { 12414 module, 12415 params, 12416 edits: TextEdits::new(line_numbers), 12417 functions: vec![], 12418 } 12419 } 12420 12421 pub fn code_actions(mut self) -> Vec<CodeAction> { 12422 self.visit_typed_module(&self.module.ast); 12423 12424 let mut actions = Vec::with_capacity(self.functions.len()); 12425 for target in self.functions { 12426 let mut name_generator = NameGenerator::new(); 12427 name_generator.reserve_variable_names(target.variables_names); 12428 let arguments = target 12429 .arguments 12430 .iter() 12431 .map(|type_| name_generator.generate_name_from_type(type_)) 12432 .join(", "); 12433 12434 self.edits 12435 .insert(target.location.start, format!("fn({arguments}) {{ ")); 12436 self.edits 12437 .insert(target.location.end, format!("({arguments}) }}")); 12438 12439 CodeActionBuilder::new("Wrap in anonymous function") 12440 .kind(CodeActionKind::RefactorRewrite) 12441 .changes( 12442 self.params.text_document.uri.clone(), 12443 self.edits.edits.drain(..).collect(), 12444 ) 12445 .push_to(&mut actions); 12446 } 12447 actions 12448 } 12449} 12450 12451impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> { 12452 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 12453 let expression_range = self.edits.src_span_to_lsp_range(expression.location()); 12454 if !within(self.params.range, expression_range) { 12455 return; 12456 } 12457 12458 // Exclude if the expression is already a function literal 12459 let is_excluded = matches!(expression, TypedExpr::Fn { .. }); 12460 12461 if let Type::Fn { arguments, .. } = &*expression.type_() 12462 && !is_excluded 12463 { 12464 self.functions.push(FunctionToWrap { 12465 location: expression.location(), 12466 arguments: arguments.clone(), 12467 variables_names: VariablesNames::from_expression(expression), 12468 }); 12469 } 12470 12471 ast::visit::visit_typed_expr(self, expression); 12472 } 12473 12474 /// We don't want to apply to functions that are being explicitly called 12475 /// already, so we need to intercept visits to function calls and bounce 12476 /// them out again so they don't end up in our impl for visit_typed_expr. 12477 /// Otherwise this is the same as []. 12478 fn visit_typed_expr_call( 12479 &mut self, 12480 _location: &'ast SrcSpan, 12481 _type: &'ast Arc<Type>, 12482 fun: &'ast TypedExpr, 12483 arguments: &'ast [TypedCallArg], 12484 _argument_parentheses: &'ast Option<u32>, 12485 ) { 12486 // We only need to do this interception for explicit calls, so if any 12487 // of our arguments are explicit we re-enter the visitor as usual. 12488 if arguments.iter().any(|argument| argument.is_implicit()) { 12489 self.visit_typed_expr(fun); 12490 } else { 12491 // We still want to visit other nodes nested in the function being 12492 // called so we bounce the call back out. 12493 ast::visit::visit_typed_expr(self, fun); 12494 } 12495 12496 for argument in arguments { 12497 self.visit_typed_call_arg(argument); 12498 } 12499 } 12500 12501 /// We don't want to apply this to record constructors used in a record 12502 /// update. 12503 fn visit_typed_expr_record_update( 12504 &mut self, 12505 location: &'ast SrcSpan, 12506 spread_start: &'ast u32, 12507 type_: &'ast Arc<Type>, 12508 updated_record: &'ast TypedExpr, 12509 updated_record_assigned_name: &'ast Option<EcoString>, 12510 constructor: &'ast TypedExpr, 12511 arguments: &'ast [TypedCallArg], 12512 ) { 12513 // If we're hovering the record constructor of an update and that is a 12514 // simple record constructor, we don't want to offer the wrap in 12515 // anonymous function code action: 12516 // 12517 // ```gleam 12518 // Wibble(..wibble, a: 1) 12519 // // ^^^ Wrap in anonymous function makes no sense on this! 12520 // ``` 12521 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location()); 12522 if within(self.params.range, constructor_range) 12523 && constructor.is_record_constructor_function() 12524 { 12525 return; 12526 } 12527 12528 ast::visit::visit_typed_expr_record_update( 12529 self, 12530 location, 12531 spread_start, 12532 type_, 12533 updated_record, 12534 updated_record_assigned_name, 12535 constructor, 12536 arguments, 12537 ); 12538 } 12539} 12540 12541/// Code action to unwrap trivial one-statement anonymous functions into just a 12542/// reference to the function called 12543/// 12544/// For example, if the code action was used on the anonymous function here: 12545/// 12546/// ```gleam 12547/// list.map([1, 2, 3], fn(int) { 12548/// op(int) 12549/// }) 12550/// ``` 12551/// 12552/// it would become: 12553/// 12554/// ```gleam 12555/// list.map([1, 2, 3], op) 12556/// ``` 12557pub struct UnwrapAnonymousFunction<'a> { 12558 module: &'a Module, 12559 line_numbers: &'a LineNumbers, 12560 params: &'a CodeActionParams, 12561 functions: Vec<FunctionToUnwrap>, 12562} 12563 12564/// Helper struct, a target for [UnwrapAnonymousFunction] 12565struct FunctionToUnwrap { 12566 /// Location of the anonymous function to apply the action to. 12567 outer_function: SrcSpan, 12568 /// Location of the opening brace of the anonymous function. 12569 outer_function_body_start: u32, 12570 /// Location of the function being called inside the anonymous function. 12571 /// This will be all that's left after the action, plus any comments. 12572 inner_function: SrcSpan, 12573 // Location of the opening parenthesis of the inner function's argument list. 12574 inner_function_arguments_start: u32, 12575} 12576 12577impl<'a> UnwrapAnonymousFunction<'a> { 12578 pub fn new( 12579 module: &'a Module, 12580 line_numbers: &'a LineNumbers, 12581 params: &'a CodeActionParams, 12582 ) -> Self { 12583 Self { 12584 module, 12585 line_numbers, 12586 params, 12587 functions: vec![], 12588 } 12589 } 12590 12591 pub fn code_actions(mut self) -> Vec<CodeAction> { 12592 self.visit_typed_module(&self.module.ast); 12593 12594 let mut actions = Vec::with_capacity(self.functions.len()); 12595 for function in &self.functions { 12596 let mut edits = TextEdits::new(self.line_numbers); 12597 12598 // We need to delete the anonymous function's head and the opening 12599 // brace but preserve comments between it and the inner function call. 12600 // We set our endpoint at the start of the function body, and move 12601 // it on through any whitespace. 12602 let head_deletion_end = 12603 next_nonwhitespace(&self.module.code, function.outer_function_body_start + 1); 12604 edits.delete(SrcSpan { 12605 start: function.outer_function.start, 12606 end: head_deletion_end, 12607 }); 12608 12609 // Delete the inner function call's arguments. 12610 edits.delete(SrcSpan { 12611 start: function.inner_function_arguments_start, 12612 end: function.inner_function.end, 12613 }); 12614 12615 // To delete the tail we remove the function end (the '}') and any 12616 // whitespace before it. 12617 let tail_deletion_start = 12618 previous_nonwhitespace(&self.module.code, function.outer_function.end - 1); 12619 edits.delete(SrcSpan { 12620 start: tail_deletion_start, 12621 end: function.outer_function.end, 12622 }); 12623 12624 CodeActionBuilder::new("Remove anonymous function wrapper") 12625 .kind(CodeActionKind::RefactorRewrite) 12626 .changes(self.params.text_document.uri.clone(), edits.edits) 12627 .push_to(&mut actions); 12628 } 12629 actions 12630 } 12631 12632 /// If an anonymous function can be unwrapped, save it to our list 12633 /// 12634 /// We need to ensure our subjects: 12635 /// - are anonymous function literals (not captures) 12636 /// - only contain a single statement 12637 /// - that statement is a function call 12638 /// - that call's arguments exactly match the arguments of the enclosing 12639 /// function 12640 fn register_function( 12641 &mut self, 12642 location: &'a SrcSpan, 12643 kind: &'a FunctionLiteralKind, 12644 arguments: &'a [TypedArg], 12645 body: &'a Vec1<TypedStatement>, 12646 ) { 12647 let function_range = src_span_to_lsp_range(*location, self.line_numbers); 12648 if !within(self.params.range, function_range) { 12649 return; 12650 } 12651 12652 let outer_body = match kind { 12653 FunctionLiteralKind::Anonymous { head, .. } => SrcSpan::new( 12654 next_nonwhitespace(&self.module.code, head.end), 12655 location.end, 12656 ), 12657 _ => return, 12658 }; 12659 12660 // We can only apply to anonymous functions containing a single function call 12661 let [ 12662 TypedStatement::Expression(TypedExpr::Call { 12663 location: call_location, 12664 arguments: call_arguments, 12665 open_parenthesis: Some(arguments_start), 12666 .. 12667 }), 12668 ] = body.as_slice() 12669 else { 12670 return; 12671 }; 12672 12673 // We need the existing argument list for the fn to be a 1:1 match for 12674 // the args we pass to the called function, so we need to collect the 12675 // names used in both lists and check they're equal. 12676 12677 let outer_argument_names = arguments.iter().map(|a| match &a.names { 12678 ArgNames::Named { name, .. } => Some(name), 12679 // We can bail out early if any arguments are discarded, since 12680 // they couldn't match those actually used. 12681 ArgNames::Discard { .. } => None, 12682 // Anonymous functions can't have labelled arguments. 12683 ArgNames::NamedLabelled { .. } => unreachable!(), 12684 ArgNames::LabelledDiscard { .. } => unreachable!(), 12685 }); 12686 12687 let inner_argument_names = call_arguments.iter().map(|a| match &a.value { 12688 TypedExpr::Var { name, .. } => Some(name), 12689 // We can bail out early if any of these aren't variables, since 12690 // they couldn't match the inputs. 12691 _ => None, 12692 }); 12693 12694 if !inner_argument_names.eq(outer_argument_names) { 12695 return; 12696 } 12697 12698 self.functions.push(FunctionToUnwrap { 12699 outer_function: *location, 12700 outer_function_body_start: outer_body.start, 12701 inner_function: *call_location, 12702 inner_function_arguments_start: *arguments_start, 12703 }); 12704 } 12705} 12706 12707impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> { 12708 fn visit_typed_expr_fn( 12709 &mut self, 12710 location: &'ast SrcSpan, 12711 type_: &'ast Arc<Type>, 12712 kind: &'ast FunctionLiteralKind, 12713 arguments: &'ast [TypedArg], 12714 body: &'ast Vec1<TypedStatement>, 12715 return_annotation: &'ast Option<ast::TypeAst>, 12716 ) { 12717 let function_range = src_span_to_lsp_range(*location, self.line_numbers); 12718 if !within(self.params.range, function_range) { 12719 return; 12720 } 12721 12722 self.register_function(location, kind, arguments, body); 12723 12724 ast::visit::visit_typed_expr_fn( 12725 self, 12726 location, 12727 type_, 12728 kind, 12729 arguments, 12730 body, 12731 return_annotation, 12732 ); 12733 } 12734} 12735 12736/// Code action to create unknown modules when an import is added for a 12737/// module that doesn't exist. 12738/// 12739/// For example, if `import wobble/woo` is added to `src/wiggle.gleam`, 12740/// then a code action to create `src/wobble/woo.gleam` will be presented 12741/// when triggered over `import wobble/woo`. 12742pub struct CreateUnknownModule<'a, IO> { 12743 module: &'a Module, 12744 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 12745 lines: &'a LineNumbers, 12746 params: &'a CodeActionParams, 12747 paths: &'a ProjectPaths, 12748 error: &'a Option<Error>, 12749} 12750 12751impl<'a, IO> CreateUnknownModule<'a, IO> { 12752 pub fn new( 12753 module: &'a Module, 12754 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 12755 lines: &'a LineNumbers, 12756 params: &'a CodeActionParams, 12757 paths: &'a ProjectPaths, 12758 error: &'a Option<Error>, 12759 ) -> Self { 12760 Self { 12761 module, 12762 compiler, 12763 lines, 12764 params, 12765 paths, 12766 error, 12767 } 12768 } 12769 12770 pub fn code_actions(self) -> Vec<CodeAction> { 12771 // This code action can be derived from `UnknownModule` type errors. 12772 // If those errors don't exist for the current module, then we will not 12773 // suggest this action. 12774 let Some(Error::Type { failed_modules, .. }) = self.error else { 12775 return vec![]; 12776 }; 12777 let Some(failed_module) = failed_modules.get(&self.module.name) else { 12778 return vec![]; 12779 }; 12780 12781 let mut unknown_module_name = None; 12782 // We then need to find the `UnknownModule` error that is under the 12783 // cursor (if any, otherwise there's no action to suggest)! 12784 for error in &failed_module.errors { 12785 let TypeError::UnknownModule { location, name, .. } = error else { 12786 continue; 12787 }; 12788 let error_range = src_span_to_lsp_range(*location, self.lines); 12789 if !within(self.params.range, error_range) { 12790 continue; 12791 } 12792 // We've found the unknown module error!! 12793 unknown_module_name = Some(name); 12794 } 12795 12796 let Some(unknown_module_name) = unknown_module_name else { 12797 return vec![]; 12798 }; 12799 12800 // Now we need to check the module actually doesn't exist among the 12801 // importable ones! Imagine we've written `timestamp.to_string` and 12802 // `timestamp` is unknown: if there's any importable module in the form 12803 // `.../timestamp` then the most likely scenario is that the programmer 12804 // wanted to import that and not create a new `src/timestamp.gleam` 12805 // file! 12806 // So we check if any of the importable modules ends with the unknown 12807 // name and if that's the case we don't suggest this action. 12808 let importable_modules = self.compiler.project_compiler.get_importable_modules(); 12809 let unknown_module_can_be_imported = importable_modules 12810 .keys() 12811 .find(|module_name| module_name.split('/').next_back() == Some(unknown_module_name)) 12812 .is_some(); 12813 if unknown_module_can_be_imported { 12814 return vec![]; 12815 } 12816 12817 // We've made sure the module is not among the importable ones, so now 12818 // we figure out if the generated module needs to go under `src`, 12819 // `test`, or `dev` and we're good to actually generate it! 12820 let origin_directory = match self.module.origin { 12821 Origin::Src => self.paths.src_directory(), 12822 Origin::Test => self.paths.test_directory(), 12823 Origin::Dev => self.paths.dev_directory(), 12824 }; 12825 12826 let uri = url_from_path(&format!("{origin_directory}/{}.gleam", unknown_module_name)) 12827 .expect("origin directory is absolute"); 12828 12829 let mut actions = vec![]; 12830 CodeActionBuilder::new(&format!( 12831 "Create {}/{}.gleam", 12832 self.module.origin.folder_name(), 12833 unknown_module_name 12834 )) 12835 .kind(CodeActionKind::QuickFix) 12836 .document_changes(vec![DocumentChange::CreateFile(CreateFile { 12837 uri, 12838 options: Some(CreateFileOptions { 12839 overwrite: Some(false), 12840 ignore_if_exists: Some(true), 12841 }), 12842 annotation_id: None, 12843 })]) 12844 .push_to(&mut actions); 12845 12846 actions 12847 } 12848} 12849 12850/// Code action to discard unused variable. 12851pub struct DiscardUnusedVariable<'a> { 12852 module: &'a Module, 12853 params: &'a CodeActionParams, 12854 edits: TextEdits<'a>, 12855} 12856 12857#[derive(Debug)] 12858struct UnusedVariable<'a> { 12859 location: &'a SrcSpan, 12860 origin: &'a VariableOrigin, 12861} 12862 12863impl<'a> DiscardUnusedVariable<'a> { 12864 pub fn new( 12865 module: &'a Module, 12866 line_numbers: &'a LineNumbers, 12867 params: &'a CodeActionParams, 12868 ) -> Self { 12869 Self { 12870 module, 12871 params, 12872 edits: TextEdits::new(line_numbers), 12873 } 12874 } 12875 12876 pub fn code_actions(mut self) -> Vec<CodeAction> { 12877 let unused_variable = self 12878 .module 12879 .ast 12880 .type_info 12881 .warnings 12882 .iter() 12883 .find_map(|warning| { 12884 if let type_::Warning::UnusedVariable { location, origin } = warning 12885 && within( 12886 self.params.range, 12887 self.edits.src_span_to_lsp_range(*location), 12888 ) 12889 { 12890 Some(UnusedVariable { location, origin }) 12891 } else { 12892 None 12893 } 12894 }); 12895 12896 let Some(unused_variable) = unused_variable else { 12897 return vec![]; 12898 }; 12899 12900 match unused_variable.origin.syntax { 12901 type_::error::VariableSyntax::Variable(_) => { 12902 self.edits 12903 .insert(unused_variable.location.start, ("_").to_string()); 12904 } 12905 type_::error::VariableSyntax::LabelShorthand(_) => { 12906 self.edits 12907 .insert(unused_variable.location.end, (" _").to_string()); 12908 } 12909 type_::error::VariableSyntax::AssignmentPattern(location) => { 12910 self.edits.delete(SrcSpan { 12911 start: location.start, 12912 end: location.end, 12913 }); 12914 } 12915 type_::error::VariableSyntax::Generated => (), 12916 } 12917 12918 let mut action = Vec::with_capacity(1); 12919 CodeActionBuilder::new("Discard unused variable") 12920 .kind(CodeActionKind::QuickFix) 12921 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12922 .preferred(true) 12923 .push_to(&mut action); 12924 action 12925 } 12926} 12927 12928pub fn code_action_fix_deprecated_pipe( 12929 module: &Module, 12930 line_numbers: &LineNumbers, 12931 params: &CodeActionParams, 12932 actions: &mut Vec<CodeAction>, 12933) { 12934 let uri = &params.text_document.uri; 12935 let mut deprecated_pipes: Vec<&SrcSpan> = module 12936 .ast 12937 .type_info 12938 .warnings 12939 .iter() 12940 .filter_map(|warning| { 12941 if let type_::Warning::PipeIntoCallWhichReturnsFunction { location } = warning { 12942 Some(location) 12943 } else { 12944 None 12945 } 12946 }) 12947 .collect(); 12948 12949 if deprecated_pipes.is_empty() { 12950 return; 12951 } 12952 12953 // Sort spans by start position, with longer spans coming first 12954 deprecated_pipes.sort_by_key(|span| (span.start, -(span.len() as i64))); 12955 12956 for location in deprecated_pipes { 12957 let range = src_span_to_lsp_range(*location, line_numbers); 12958 12959 // Check if the cursor is within this span 12960 if !within(params.range, range) { 12961 continue; 12962 } 12963 12964 let edit = TextEdit { 12965 range: Range::new(range.end, range.end), 12966 new_text: "()".into(), 12967 }; 12968 12969 CodeActionBuilder::new("Add extra parentheses") 12970 .kind(CodeActionKind::QuickFix) 12971 .changes(uri.clone(), vec![edit]) 12972 .preferred(true) 12973 .push_to(actions); 12974 } 12975} 12976 12977/// Code action to switch between doc and regular comments. 12978pub struct ConvertBetweenDocAndRegularComment<'a> { 12979 module: &'a Module, 12980 lines: &'a LineNumbers, 12981 params: &'a CodeActionParams, 12982} 12983 12984impl<'a> ConvertBetweenDocAndRegularComment<'a> { 12985 pub fn new(module: &'a Module, lines: &'a LineNumbers, params: &'a CodeActionParams) -> Self { 12986 Self { 12987 module, 12988 lines, 12989 params, 12990 } 12991 } 12992 12993 pub fn code_actions(self) -> Vec<CodeAction> { 12994 let line = self.params.range.start.line; 12995 let Some(num_slashes) = self.count_leading_slashes(line) else { 12996 return vec![]; 12997 }; 12998 12999 if !(2..=4).contains(&num_slashes) { 13000 return vec![]; 13001 } 13002 13003 let start_line = self 13004 .find_comment_edge((0..line).rev(), num_slashes) 13005 .unwrap_or(line); 13006 let end_line = self 13007 .find_comment_edge(line + 1.., num_slashes) 13008 .unwrap_or(line); 13009 13010 if self.params.range.end.line > end_line { 13011 return vec![]; 13012 } 13013 13014 let comment = match num_slashes { 13015 2 if self.can_be_module_comment(start_line, end_line) => "////", 13016 2 if self.can_have_doc_comment(end_line) => "///", 13017 3 | 4 => "//", 13018 _ => return vec![], 13019 }; 13020 13021 let mut edits = TextEdits::new(self.lines); 13022 for line in start_line..=end_line { 13023 let Some(line_start) = self.line_start(line) else { 13024 return vec![]; 13025 }; 13026 let start = next_nonwhitespace(&self.module.code, line_start); 13027 edits.replace(SrcSpan::new(start, start + num_slashes), comment.to_owned()); 13028 } 13029 13030 let action_name = if comment == "//" { 13031 "Convert to regular comment" 13032 } else { 13033 "Convert to documentation comment" 13034 }; 13035 13036 let mut action = Vec::with_capacity(1); 13037 CodeActionBuilder::new(action_name) 13038 .kind(CodeActionKind::RefactorRewrite) 13039 .changes(self.params.text_document.uri.clone(), edits.edits) 13040 .push_to(&mut action); 13041 action 13042 } 13043 13044 fn count_leading_slashes(&self, line: u32) -> Option<u32> { 13045 let mut count = 0; 13046 for c in self.module.code[self.line_start(line)? as usize..].chars() { 13047 if c == '/' { 13048 count += 1; 13049 } else if !c.is_whitespace() || c == '\n' || count > 0 { 13050 break; 13051 } 13052 } 13053 Some(count) 13054 } 13055 13056 fn line_start(&self, line: u32) -> Option<u32> { 13057 self.lines.line_starts.get(line as usize).copied() 13058 } 13059 13060 /// Find the last line in the range that is part of the same comment. 13061 fn find_comment_edge(&self, lines: impl Iterator<Item = u32>, num_slashes: u32) -> Option<u32> { 13062 lines 13063 .take_while(|&line| { 13064 self.count_leading_slashes(line) 13065 .is_some_and(|n| n == num_slashes) 13066 }) 13067 .last() 13068 } 13069 13070 fn can_be_module_comment(&self, start_line: u32, end_line: u32) -> bool { 13071 let Some(line_start) = self.line_start(start_line) else { 13072 return false; 13073 }; 13074 previous_nonwhitespace(&self.module.code, line_start) == 0 13075 && self 13076 .line_start(end_line + 1) 13077 .is_none_or(|position| self.module.extra.empty_lines.contains(&position)) 13078 } 13079 13080 /// Check if the comment is before a node that can have a doc comment, e.g. a function. 13081 fn can_have_doc_comment(&self, end_line: u32) -> bool { 13082 let Some(position) = self.line_start(end_line + 1) else { 13083 return false; 13084 }; 13085 13086 let next_node = next_nonwhitespace(&self.module.code, position); 13087 let definitions = &self.module.ast.definitions; 13088 definitions 13089 .functions 13090 .iter() 13091 .any(|function| function.location.contains(next_node)) 13092 || definitions 13093 .constants 13094 .iter() 13095 .any(|constant| constant.location.contains(next_node)) 13096 || definitions 13097 .type_aliases 13098 .iter() 13099 .any(|type_alias| type_alias.location.contains(next_node)) 13100 || definitions.custom_types.iter().any(|custom_type| { 13101 custom_type.location.contains(next_node) 13102 || custom_type.constructors.iter().any(|constructor| { 13103 constructor.location.contains(next_node) 13104 || constructor.arguments.iter().any(|argument| { 13105 argument 13106 .label 13107 .as_ref() 13108 .is_some_and(|label| label.0.contains(next_node)) 13109 }) 13110 }) 13111 }) 13112 } 13113} 13114 13115/// Code action to convert integers to a different base from the current one. 13116/// 13117pub struct ConvertIntToDifferentBase<'a> { 13118 module: &'a Module, 13119 params: &'a CodeActionParams, 13120 edits: TextEdits<'a>, 13121 /// This is gonna hold the base the hovered integer is written in, and its 13122 /// decimal value. And its position in the source code. 13123 int: Option<(SrcSpan, Base, BigInt)>, 13124} 13125 13126enum Base { 13127 Binary, 13128 Octal, 13129 Decimal, 13130 Hexadecimal, 13131} 13132 13133impl<'a> ConvertIntToDifferentBase<'a> { 13134 pub fn new( 13135 module: &'a Module, 13136 line_numbers: &'a LineNumbers, 13137 params: &'a CodeActionParams, 13138 ) -> Self { 13139 Self { 13140 module, 13141 params, 13142 edits: TextEdits::new(line_numbers), 13143 int: None, 13144 } 13145 } 13146 13147 pub fn code_actions(mut self) -> Vec<CodeAction> { 13148 self.visit_typed_module(&self.module.ast); 13149 13150 let Some((location, base, int)) = self.int else { 13151 return vec![]; 13152 }; 13153 13154 let missing_bases = match base { 13155 Base::Binary => [Base::Decimal, Base::Octal, Base::Hexadecimal], 13156 Base::Octal => [Base::Decimal, Base::Binary, Base::Hexadecimal], 13157 Base::Decimal => [Base::Binary, Base::Octal, Base::Hexadecimal], 13158 Base::Hexadecimal => [Base::Decimal, Base::Binary, Base::Octal], 13159 }; 13160 13161 let is_negative = int < BigInt::ZERO; 13162 let int = if is_negative { int.neg() } else { int }; 13163 13164 let mut action = Vec::with_capacity(3); 13165 for base in missing_bases { 13166 let minus = if is_negative { "-" } else { "" }; 13167 let converted_number = match base { 13168 Base::Binary => format!("{minus}0b{:b}", int), 13169 Base::Octal => format!("{minus}0o{:o}", int), 13170 Base::Hexadecimal => format!("{minus}0x{:x}", int), 13171 Base::Decimal => { 13172 format!("{minus}{}", format_int_with_thousands_separator(&int)) 13173 } 13174 }; 13175 let title = format!("Convert to `{converted_number}`"); 13176 self.edits.replace(location, converted_number); 13177 13178 CodeActionBuilder::new(&title) 13179 .kind(CodeActionKind::RefactorRewrite) 13180 .changes( 13181 self.params.text_document.uri.clone(), 13182 self.edits.edits.drain(..).collect(), 13183 ) 13184 .preferred(false) 13185 .push_to(&mut action); 13186 } 13187 action 13188 } 13189} 13190 13191fn format_int_with_thousands_separator(int: &BigInt) -> String { 13192 if int <= &BigInt::from(9999) { 13193 return int.to_string(); 13194 } 13195 13196 // We get chunks of three digits. If we start from 1234567 13197 // we will have `765`, `432`, `1` 13198 (int.to_string().chars().rev().chunks(3).into_iter()) 13199 // Each chunk is turned into a string and those are joined with a 13200 // separator. 13201 .map(|chunk| chunk.collect::<EcoString>()) 13202 .join("_") 13203 // And finally reverse everything to bring it back to normal, the number 13204 // is spelled in reverse right now! 13205 .chars() 13206 .rev() 13207 .collect() 13208} 13209 13210impl<'ast> ast::visit::Visit<'ast> for ConvertIntToDifferentBase<'ast> { 13211 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 13212 // We skip all the functions the cursor is not inside of. 13213 // This is gonna make it faster to find the int we're hovering, if any. 13214 let fun_range = self.edits.src_span_to_lsp_range(fun.full_location()); 13215 if within(self.params.range, fun_range) { 13216 ast::visit::visit_typed_function(self, fun); 13217 } 13218 } 13219 13220 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 13221 // We skip all the expression's the cursor is not inside of. 13222 // This is gonna make it faster to find the int we're hovering, if any. 13223 let expression_range = self.edits.src_span_to_lsp_range(expr.location()); 13224 if within(self.params.range, expression_range) { 13225 ast::visit::visit_typed_expr(self, expr); 13226 } 13227 } 13228 13229 fn visit_typed_expr_int( 13230 &mut self, 13231 location: &'ast SrcSpan, 13232 _type_: &'ast Arc<Type>, 13233 string_value: &'ast EcoString, 13234 int_value: &'ast BigInt, 13235 ) { 13236 let int_range = self.edits.src_span_to_lsp_range(*location); 13237 if !within(self.params.range, int_range) { 13238 return; 13239 } 13240 13241 let string_value = string_value.trim_start_matches('-'); 13242 let base = if string_value.starts_with("0b") { 13243 Base::Binary 13244 } else if string_value.starts_with("0o") { 13245 Base::Octal 13246 } else if string_value.starts_with("0x") { 13247 Base::Hexadecimal 13248 } else { 13249 Base::Decimal 13250 }; 13251 13252 self.int = Some((*location, base, int_value.clone())) 13253 } 13254}