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
452 kB 12964 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use std::{collections::HashSet, iter, 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 vec1::{Vec1, vec1}; 40 41use crate::engine::{completely_within, position_within}; 42 43use super::{ 44 TextEdits, 45 compiler::LspProjectCompiler, 46 edits, 47 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import}, 48 engine::{overlaps, within}, 49 files::FileSystemProxy, 50 reference::{FindVariableReferences, VariableReferenceKind}, 51 src_span_to_lsp_range, url_from_path, 52}; 53 54#[derive(Debug)] 55pub struct CodeActionBuilder { 56 action: CodeAction, 57} 58 59impl CodeActionBuilder { 60 pub fn new(title: &str) -> Self { 61 Self { 62 action: CodeAction { 63 title: title.to_string(), 64 kind: None, 65 diagnostics: None, 66 edit: None, 67 command: None, 68 is_preferred: None, 69 disabled: None, 70 data: None, 71 tags: None, 72 }, 73 } 74 } 75 76 pub fn kind(mut self, kind: CodeActionKind) -> Self { 77 self.action.kind = Some(kind); 78 self 79 } 80 81 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self { 82 let mut edit = self.action.edit.take().unwrap_or_default(); 83 let mut changes = edit.changes.take().unwrap_or_default(); 84 _ = changes.insert(uri, edits); 85 86 edit.changes = Some(changes); 87 self.action.edit = Some(edit); 88 self 89 } 90 91 pub fn document_changes(mut self, changes: Vec<DocumentChange>) -> Self { 92 let mut edit = self.action.edit.take().unwrap_or_default(); 93 94 edit.document_changes = Some(changes); 95 96 self.action.edit = Some(edit); 97 self 98 } 99 100 pub fn preferred(mut self, is_preferred: bool) -> Self { 101 self.action.is_preferred = Some(is_preferred); 102 self 103 } 104 105 pub fn push_to(self, actions: &mut Vec<CodeAction>) { 106 actions.push(self.action); 107 } 108} 109 110/// A small helper function to get the indentation at a given position. 111fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize { 112 let mut indent_size = 0; 113 let line_start = *line_numbers 114 .line_starts 115 .get(line as usize) 116 .expect("Line number should be valid"); 117 118 let mut chars = code[line_start as usize..].chars(); 119 while chars.next() == Some(' ') { 120 indent_size += 1; 121 } 122 123 indent_size 124} 125 126// Given a string and a position in it, if the position points to whitespace, 127// this function returns the next position which doesn't. 128fn next_nonwhitespace(string: &EcoString, position: u32) -> u32 { 129 let mut n = position; 130 let mut chars = string[position as usize..].chars(); 131 while chars.next().is_some_and(char::is_whitespace) { 132 n += 1; 133 } 134 n 135} 136 137// Given a string and a position in it, if the position points after whitespace, 138// this function returns the previous position which doesn't. 139fn previous_nonwhitespace(string: &EcoString, position: u32) -> u32 { 140 let mut n = position; 141 let mut chars = string[..position as usize].chars(); 142 while chars.next_back().is_some_and(char::is_whitespace) { 143 n -= 1; 144 } 145 n 146} 147 148/// Code action to remove literal tuples in case subjects, essentially making 149/// the elements of the tuples into the case's subjects. 150/// 151/// The code action is only available for the i'th subject if: 152/// - it is a non-empty tuple, and 153/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses. 154/// 155/// # Basic example: 156/// 157/// The following case expression: 158/// 159/// ```gleam 160/// case #(1, 2) { 161/// #(a, b) -> 0 162/// } 163/// ``` 164/// 165/// Becomes: 166/// 167/// ```gleam 168/// case 1, 2 { 169/// a, b -> 0 170/// } 171/// ``` 172/// 173/// # Another example: 174/// 175/// The following case expression does not produce any code action 176/// 177/// ```gleam 178/// case #(1, 2) { 179/// a -> 0 // <- the pattern is not a tuple 180/// } 181/// ``` 182pub struct RedundantTupleInCaseSubject<'a> { 183 edits: TextEdits<'a>, 184 code: &'a EcoString, 185 extra: &'a ModuleExtra, 186 params: &'a CodeActionParams, 187 module: &'a ast::TypedModule, 188 hovered: bool, 189} 190 191impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> { 192 fn visit_typed_expr_case( 193 &mut self, 194 location: &'ast SrcSpan, 195 type_: &'ast Arc<Type>, 196 subjects: &'ast [TypedExpr], 197 clauses: &'ast [ast::TypedClause], 198 compiled_case: &'ast CompiledCase, 199 ) { 200 for (subject_idx, subject) in subjects.iter().enumerate() { 201 let TypedExpr::Tuple { 202 location, elements, .. 203 } = subject 204 else { 205 continue; 206 }; 207 208 // Ignore empty tuple 209 if elements.is_empty() { 210 continue; 211 } 212 213 // We cannot rewrite clauses whose i-th pattern is not a discard or 214 // tuples. 215 let all_ith_patterns_are_tuples_or_discards = clauses 216 .iter() 217 .map(|clause| clause.pattern.get(subject_idx)) 218 .all(|pattern| { 219 matches!( 220 pattern, 221 Some(Pattern::Tuple { .. } | Pattern::Discard { .. }) 222 ) 223 }); 224 225 if !all_ith_patterns_are_tuples_or_discards { 226 continue; 227 } 228 229 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location())); 230 231 for clause in clauses { 232 match clause.pattern.get(subject_idx) { 233 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens( 234 *location, 235 elements.last().map(|element| element.location()), 236 ), 237 Some(Pattern::Discard { location, .. }) => { 238 self.discard_tuple_items(*location, elements.len()) 239 } 240 _ => panic!("safe: we've just checked all patterns must be discards/tuples"), 241 } 242 } 243 let range = self.edits.src_span_to_lsp_range(*location); 244 self.hovered = self.hovered || overlaps(self.params.range, range); 245 } 246 247 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case) 248 } 249} 250 251impl<'a> RedundantTupleInCaseSubject<'a> { 252 pub fn new( 253 module: &'a Module, 254 line_numbers: &'a LineNumbers, 255 params: &'a CodeActionParams, 256 ) -> Self { 257 Self { 258 edits: TextEdits::new(line_numbers), 259 code: &module.code, 260 extra: &module.extra, 261 params, 262 module: &module.ast, 263 hovered: false, 264 } 265 } 266 267 pub fn code_actions(mut self) -> Vec<CodeAction> { 268 self.visit_typed_module(self.module); 269 if !self.hovered { 270 return vec![]; 271 } 272 273 self.edits.edits.sort_by_key(|edit| edit.range.start); 274 275 let mut actions = vec![]; 276 CodeActionBuilder::new("Remove redundant tuples") 277 .kind(CodeActionKind::RefactorRewrite) 278 .changes(self.params.text_document.uri.clone(), self.edits.edits) 279 .preferred(true) 280 .push_to(&mut actions); 281 282 actions 283 } 284 285 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) { 286 let tuple_code = self 287 .code 288 .get(location.start as usize..location.end as usize) 289 .expect("valid span"); 290 291 // Delete `#` 292 self.edits 293 .delete(SrcSpan::new(location.start, location.start + 1)); 294 295 // Delete `(` 296 let (lparen_offset, _) = tuple_code 297 .match_indices('(') 298 // Ignore in comments 299 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32)) 300 .expect("`(` not found in tuple"); 301 302 self.edits.delete(SrcSpan::new( 303 location.start + lparen_offset as u32, 304 location.start + lparen_offset as u32 + 1, 305 )); 306 307 // Delete trailing `,` (if applicable) 308 if let Some(last_elem_location) = last_elem_location { 309 // Get the code after the last element until the tuple's `)` 310 let code_after_last_elem = self 311 .code 312 .get(last_elem_location.end as usize..location.end as usize) 313 .expect("valid span"); 314 315 if let Some((trailing_comma_offset, _)) = code_after_last_elem 316 .rmatch_indices(',') 317 // Ignore in comments 318 .find(|(i, _)| { 319 !self 320 .extra 321 .is_within_comment(last_elem_location.end + *i as u32) 322 }) 323 { 324 self.edits.delete(SrcSpan::new( 325 last_elem_location.end + trailing_comma_offset as u32, 326 last_elem_location.end + trailing_comma_offset as u32 + 1, 327 )); 328 } 329 } 330 331 // Delete ) 332 self.edits 333 .delete(SrcSpan::new(location.end - 1, location.end)); 334 } 335 336 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) { 337 // Replace the old discard with multiple discard, one for each of the 338 // tuple items. 339 self.edits.replace( 340 discard_location, 341 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(), 342 ) 343 } 344} 345 346/// Builder for code action to convert `let assert` into a case expression. 347/// 348pub struct LetAssertToCase<'a> { 349 module: &'a Module, 350 params: &'a CodeActionParams, 351 actions: Vec<CodeAction>, 352 edits: TextEdits<'a>, 353} 354 355impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> { 356 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 357 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 358 let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan { 359 start: assignment.location.start, 360 end: assignment.value.location().start, 361 }); 362 self.visit_typed_expr(&assignment.value); 363 364 // Only offer the code action if the cursor is over the statement and 365 // to prevent weird behaviour when `let assert` statements are nested, 366 // we only check for the code action between the `let` and `=`. 367 if !(within(self.params.range, assignment_range) 368 && overlaps(self.params.range, assignment_start_range)) 369 { 370 return; 371 } 372 373 // This pattern only applies to `let assert` 374 let AssignmentKind::Assert { message, .. } = &assignment.kind else { 375 return; 376 }; 377 378 // Get the source code for the tested expression 379 let location = assignment.value.location(); 380 let expr = self 381 .module 382 .code 383 .get(location.start as usize..location.end as usize) 384 .expect("Location must be valid"); 385 386 // Get the source code for the pattern 387 let pattern_location = assignment.pattern.location(); 388 let pattern = self 389 .module 390 .code 391 .get(pattern_location.start as usize..pattern_location.end as usize) 392 .expect("Location must be valid"); 393 394 let message = message.as_ref().map(|message| { 395 let location = message.location(); 396 self.module 397 .code 398 .get(location.start as usize..location.end as usize) 399 .expect("Location must be valid") 400 }); 401 402 let range = self.edits.src_span_to_lsp_range(assignment.location); 403 404 // Figure out which variables are assigned in the pattern 405 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern); 406 407 let assigned = match variables.len() { 408 0 => "_", 409 1 => variables.first().expect("Variables is length one"), 410 _ => &format!("#({})", variables.join(", ")), 411 }; 412 413 let mut new_text = format!("let {assigned} = "); 414 let panic_message = if let Some(message) = message { 415 &format!("panic as {message}") 416 } else { 417 "panic" 418 }; 419 let clauses = vec![ 420 // The existing pattern 421 CaseClause { 422 pattern, 423 // `_` is not a valid expression, so if we are not 424 // binding any variables in the pattern, we simply return Nil. 425 expression: if assigned == "_" { "Nil" } else { assigned }, 426 }, 427 CaseClause { 428 pattern: "_", 429 expression: panic_message, 430 }, 431 ]; 432 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text); 433 434 let uri = &self.params.text_document.uri; 435 436 CodeActionBuilder::new("Convert to case") 437 .kind(CodeActionKind::RefactorRewrite) 438 .changes(uri.clone(), vec![TextEdit { range, new_text }]) 439 .preferred(false) 440 .push_to(&mut self.actions); 441 } 442} 443 444impl<'a> LetAssertToCase<'a> { 445 pub fn new( 446 module: &'a Module, 447 line_numbers: &'a LineNumbers, 448 params: &'a CodeActionParams, 449 ) -> Self { 450 Self { 451 module, 452 params, 453 actions: Vec::new(), 454 edits: TextEdits::new(line_numbers), 455 } 456 } 457 458 pub fn code_actions(mut self) -> Vec<CodeAction> { 459 self.visit_typed_module(&self.module.ast); 460 self.actions 461 } 462} 463 464struct PatternVariableFinder { 465 pattern_variables: Vec<EcoString>, 466} 467 468impl PatternVariableFinder { 469 fn new() -> Self { 470 Self { 471 pattern_variables: Vec::new(), 472 } 473 } 474 475 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> { 476 let mut finder = Self::new(); 477 finder.visit_typed_pattern(pattern); 478 finder.pattern_variables 479 } 480} 481 482impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder { 483 fn visit_typed_pattern_variable( 484 &mut self, 485 _location: &'ast SrcSpan, 486 name: &'ast EcoString, 487 _type: &'ast Arc<Type>, 488 _origin: &'ast VariableOrigin, 489 ) { 490 self.pattern_variables.push(name.clone()); 491 } 492 493 fn visit_typed_pattern_assign( 494 &mut self, 495 location: &'ast SrcSpan, 496 name: &'ast EcoString, 497 pattern: &'ast TypedPattern, 498 ) { 499 self.pattern_variables.push(name.clone()); 500 ast::visit::visit_typed_pattern_assign(self, location, name, pattern); 501 } 502 503 fn visit_typed_pattern_string_prefix( 504 &mut self, 505 _location: &'ast SrcSpan, 506 _left_location: &'ast SrcSpan, 507 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 508 _right_location: &'ast SrcSpan, 509 _left_side_string: &'ast EcoString, 510 right_side_assignment: &'ast AssignName, 511 ) { 512 if let Some((name, _)) = left_side_assignment { 513 self.pattern_variables.push(name.clone()); 514 } 515 if let AssignName::Variable(name) = right_side_assignment { 516 self.pattern_variables.push(name.clone()); 517 } 518 } 519} 520 521pub fn code_action_inexhaustive_let_to_case( 522 module: &Module, 523 line_numbers: &LineNumbers, 524 params: &CodeActionParams, 525 error: &Option<Error>, 526 actions: &mut Vec<CodeAction>, 527) { 528 let Some(errors) = type_errors_for_module(error, module) else { 529 return; 530 }; 531 532 let inexhaustive_assignments = errors 533 .iter() 534 .filter_map(|error| { 535 if let type_::Error::InexhaustiveLetAssignment { location, missing } = error { 536 Some((*location, missing)) 537 } else { 538 None 539 } 540 }) 541 .collect_vec(); 542 543 if inexhaustive_assignments.is_empty() { 544 return; 545 } 546 547 for (location, missing) in inexhaustive_assignments { 548 let mut text_edits = TextEdits::new(line_numbers); 549 550 let range = text_edits.src_span_to_lsp_range(location); 551 if !within(params.range, range) { 552 continue; 553 } 554 555 let Some(Located::Statement(TypedStatement::Assignment(assignment))) = 556 module.find_node(location.start) 557 else { 558 continue; 559 }; 560 561 let TypedAssignment { 562 value, 563 pattern, 564 kind: AssignmentKind::Let, 565 location, 566 compiled_case: _, 567 annotation: _, 568 } = assignment.as_ref() 569 else { 570 continue; 571 }; 572 573 // Get the source code for the tested expression 574 let value_location = value.location(); 575 let expr = module 576 .code 577 .get(value_location.start as usize..value_location.end as usize) 578 .expect("Location must be valid"); 579 580 // Get the source code for the pattern 581 let pattern_location = pattern.location(); 582 let pattern_code = module 583 .code 584 .get(pattern_location.start as usize..pattern_location.end as usize) 585 .expect("Location must be valid"); 586 587 let range = text_edits.src_span_to_lsp_range(*location); 588 589 // Figure out which variables are assigned in the pattern 590 let variables = PatternVariableFinder::find_variables_in_pattern(pattern); 591 592 let assigned = match variables.len() { 593 0 => "_", 594 1 => variables.first().expect("Variables is length one"), 595 _ => &format!("#({})", variables.join(", ")), 596 }; 597 598 let mut new_text = format!("let {assigned} = "); 599 print_case_expression( 600 range.start.character as usize, 601 expr, 602 iter::once(CaseClause { 603 pattern: pattern_code, 604 expression: if assigned == "_" { "Nil" } else { assigned }, 605 }) 606 .chain(missing.iter().map(|pattern| CaseClause { 607 pattern, 608 expression: "todo", 609 })) 610 .collect(), 611 &mut new_text, 612 ); 613 614 let uri = &params.text_document.uri; 615 616 text_edits.replace(*location, new_text); 617 618 CodeActionBuilder::new("Convert to case") 619 .kind(CodeActionKind::QuickFix) 620 .changes(uri.clone(), text_edits.edits) 621 .preferred(true) 622 .push_to(actions); 623 } 624} 625 626pub(crate) fn type_errors_for_module<'a>( 627 error: &'a Option<Error>, 628 module: &Module, 629) -> Option<&'a Vec1<type_::Error>> { 630 let Some(Error::Type { 631 skipped_modules: _, 632 failed_modules, 633 }) = error 634 else { 635 return None; 636 }; 637 Some(&failed_modules.get(&module.name)?.errors) 638} 639 640struct CaseClause<'a> { 641 pattern: &'a str, 642 expression: &'a str, 643} 644 645fn print_case_expression( 646 indent_size: usize, 647 subject: &str, 648 clauses: Vec<CaseClause<'_>>, 649 buffer: &mut String, 650) { 651 let indent = " ".repeat(indent_size); 652 653 // Print the beginning of the expression 654 buffer.push_str("case "); 655 buffer.push_str(subject); 656 buffer.push_str(" {"); 657 658 for clause in clauses.iter() { 659 // Print the newline and indentation for this clause 660 buffer.push('\n'); 661 buffer.push_str(&indent); 662 // Indent this clause one level deeper than the case expression 663 buffer.push_str(" "); 664 665 // Print the clause 666 buffer.push_str(clause.pattern); 667 buffer.push_str(" -> "); 668 buffer.push_str(clause.expression); 669 } 670 671 // If there are no clauses to print, the closing brace should be 672 // on the same line as the opening one, with no space between. 673 if !clauses.is_empty() { 674 buffer.push('\n'); 675 buffer.push_str(&indent); 676 } 677 buffer.push('}'); 678} 679 680/// Builder for code action to apply the label shorthand syntax on arguments 681/// where the label has the same name as the variable. 682/// 683pub struct UseLabelShorthandSyntax<'a> { 684 module: &'a Module, 685 params: &'a CodeActionParams, 686 edits: TextEdits<'a>, 687} 688 689impl<'a> UseLabelShorthandSyntax<'a> { 690 pub fn new( 691 module: &'a Module, 692 line_numbers: &'a LineNumbers, 693 params: &'a CodeActionParams, 694 ) -> Self { 695 Self { 696 module, 697 params, 698 edits: TextEdits::new(line_numbers), 699 } 700 } 701 702 pub fn code_actions(mut self) -> Vec<CodeAction> { 703 self.visit_typed_module(&self.module.ast); 704 if self.edits.edits.is_empty() { 705 return vec![]; 706 } 707 let mut action = Vec::with_capacity(1); 708 CodeActionBuilder::new("Use label shorthand syntax") 709 .kind(CodeActionKind::Refactor) 710 .changes(self.params.text_document.uri.clone(), self.edits.edits) 711 .preferred(false) 712 .push_to(&mut action); 713 action 714 } 715} 716 717impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> { 718 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 719 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 720 let is_selected = overlaps(arg_range, self.params.range); 721 722 match arg { 723 CallArg { 724 label: Some(label), 725 value: TypedExpr::Var { name, location, .. }, 726 .. 727 } if is_selected && !arg.uses_label_shorthand() && label == name => { 728 self.edits.delete(*location) 729 } 730 _ => (), 731 } 732 733 ast::visit::visit_typed_call_arg(self, arg) 734 } 735 736 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 737 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 738 let is_selected = overlaps(arg_range, self.params.range); 739 740 match arg { 741 CallArg { 742 label: Some(label), 743 value: TypedPattern::Variable { name, location, .. }, 744 .. 745 } if is_selected && !arg.uses_label_shorthand() && label == name => { 746 self.edits.delete(*location) 747 } 748 _ => (), 749 } 750 751 ast::visit::visit_typed_pattern_call_arg(self, arg) 752 } 753} 754 755/// Builder for code action to apply the fill in the missing labelled arguments 756/// of the selected function call. 757/// 758pub struct FillInMissingLabelledArgs<'a> { 759 module: &'a Module, 760 params: &'a CodeActionParams, 761 edits: TextEdits<'a>, 762 use_right_hand_side_location: Option<SrcSpan>, 763 selected_call: Option<SelectedCall<'a>>, 764 current_function: Option<&'a TypedFunction>, 765} 766 767struct SelectedCall<'a> { 768 location: SrcSpan, 769 field_map: &'a FieldMap, 770 arguments: Vec<CallArg<()>>, 771 kind: SelectedCallKind, 772 fun_type: Option<Arc<Type>>, 773 enclosing_function: Option<&'a TypedFunction>, 774} 775 776enum SelectedCallKind { 777 Value, 778 Pattern, 779} 780 781impl<'a> FillInMissingLabelledArgs<'a> { 782 pub fn new( 783 module: &'a Module, 784 line_numbers: &'a LineNumbers, 785 params: &'a CodeActionParams, 786 ) -> Self { 787 Self { 788 module, 789 params, 790 edits: TextEdits::new(line_numbers), 791 use_right_hand_side_location: None, 792 selected_call: None, 793 current_function: None, 794 } 795 } 796 797 pub fn code_actions(mut self) -> Vec<CodeAction> { 798 self.visit_typed_module(&self.module.ast); 799 let Some(SelectedCall { 800 location: call_location, 801 field_map, 802 arguments, 803 kind, 804 fun_type, 805 enclosing_function, 806 }) = self.selected_call 807 else { 808 return vec![]; 809 }; 810 811 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback()); 812 let missing_labels = field_map.missing_labels(&arguments); 813 814 // If we're applying the code action to a use call, then we know 815 // that the last missing argument is going to be implicitly inserted 816 // by the compiler, so in that case we don't want to also add that 817 // last label to the completions. 818 let missing_labels = missing_labels.iter().peekable(); 819 let mut missing_labels = if is_use_call { 820 missing_labels.dropping_back(1) 821 } else { 822 missing_labels 823 }; 824 825 // If we couldn't find any missing label to insert we just return. 826 if missing_labels.peek().is_none() { 827 return vec![]; 828 } 829 830 // A pattern could have been written with no parentheses at all! 831 // So we need to check for the last character to see if parentheses 832 // are there or not before filling the arguments in 833 let has_parentheses = ")" 834 == code_at( 835 self.module, 836 SrcSpan::new(call_location.end - 1, call_location.end), 837 ); 838 let label_insertion_start = if has_parentheses { 839 // If it ends with a parentheses we'll need to start inserting 840 // right before the closing one... 841 call_location.end - 1 842 } else { 843 // ...otherwise we just append the result 844 call_location.end 845 }; 846 847 // Now we need to figure out if there's a comma at the end of the 848 // arguments list: 849 // 850 // call(one, |) 851 // ^ Cursor here, with a comma behind 852 // 853 // call(one|) 854 // ^ Cursor here, no comma behind, we'll have to add one! 855 // 856 let has_comma_after_last_argument = 857 if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) { 858 self.module 859 .code 860 .get(last_arg.location.end as usize..=label_insertion_start as usize) 861 .is_some_and(|text| text.contains(',')) 862 } else { 863 false 864 }; 865 866 let scope_variables = 867 ScopeVariableCollector::new(call_location.start, &self.module.ast.definitions); 868 let variables_in_scope = match enclosing_function { 869 Some(function) => scope_variables.collect_from_function(function), 870 None => scope_variables.variables, 871 }; 872 873 let labels_list = missing_labels 874 .map(|label| { 875 Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope) 876 }) 877 .join(", "); 878 879 let has_no_explicit_arguments = arguments 880 .iter() 881 .filter(|arg| !arg.is_implicit()) 882 .peekable() 883 .peek() 884 .is_none(); 885 886 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument { 887 labels_list 888 } else { 889 format!(", {labels_list}") 890 }; 891 892 let edit = if has_parentheses { 893 labels_list 894 } else { 895 // If the variant whose arguments we're filling in was written 896 // with no parentheses we need to add those as well to make it a 897 // valid constructor. 898 format!("({labels_list})") 899 }; 900 901 self.edits.insert(label_insertion_start, edit); 902 903 let mut action = Vec::with_capacity(1); 904 CodeActionBuilder::new("Fill labels") 905 .kind(CodeActionKind::QuickFix) 906 .changes(self.params.text_document.uri.clone(), self.edits.edits) 907 .preferred(true) 908 .push_to(&mut action); 909 action 910 } 911 912 /// Formats a label for insertion. Uses `label:` syntax if there's a variable 913 /// in scope with matching name and type, otherwise uses `label: todo`. 914 fn format_label( 915 label: &EcoString, 916 kind: &SelectedCallKind, 917 fun_type: &Option<Arc<Type>>, 918 field_map: &FieldMap, 919 variables_in_scope: &HashMap<EcoString, Arc<Type>>, 920 ) -> String { 921 if matches!(kind, SelectedCallKind::Pattern) { 922 return format!("{label}:"); 923 } 924 925 if let Some(var_type) = variables_in_scope.get(label) { 926 let expected_type = fun_type.as_ref().and_then(|ft| { 927 let (arg_types, _) = ft.fn_types()?; 928 let &index = field_map.fields.get(label)?; 929 arg_types.get(index as usize).cloned() 930 }); 931 932 if let Some(expected) = expected_type 933 && expected.same_as(var_type) 934 { 935 return format!("{label}:"); 936 } 937 } 938 939 format!("{label}: todo") 940 } 941 942 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> { 943 CallArg { 944 label: argument.label.clone(), 945 location: argument.location, 946 value: (), 947 implicit: argument.implicit, 948 } 949 } 950} 951 952impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> { 953 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 954 // We store the current function as the containing function while we traverse 955 // it, allowing handling nested functions correctly. 956 let previous_function = self.current_function; 957 self.current_function = Some(fun); 958 ast::visit::visit_typed_function(self, fun); 959 self.current_function = previous_function; 960 } 961 962 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 963 // If we're adding labels to a use call the correct location of the 964 // function we need to add labels to is `use_right_hand_side_location`. 965 // So we store it for when we're typing the use call. 966 let previous = self.use_right_hand_side_location; 967 self.use_right_hand_side_location = Some(use_.right_hand_side_location); 968 ast::visit::visit_typed_use(self, use_); 969 self.use_right_hand_side_location = previous; 970 } 971 972 fn visit_typed_constant_record( 973 &mut self, 974 location: &'ast SrcSpan, 975 module: &'ast Option<(EcoString, SrcSpan)>, 976 name: &'ast EcoString, 977 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 978 type_: &'ast Arc<Type>, 979 field_map: &'ast Inferred<FieldMap>, 980 record_constructor: &'ast Option<Box<ValueConstructor>>, 981 ) { 982 let record_range = self.edits.src_span_to_lsp_range(*location); 983 if !within(self.params.range, record_range) { 984 return; 985 } 986 987 if let Some(arguments) = arguments 988 && let Inferred::Known(field_map) = field_map 989 { 990 self.selected_call = Some(SelectedCall { 991 location: *location, 992 field_map, 993 arguments: arguments.iter().map(Self::empty_argument).collect(), 994 kind: SelectedCallKind::Value, 995 fun_type: record_constructor 996 .as_ref() 997 .map(|constructor| constructor.type_.clone()), 998 enclosing_function: None, 999 }) 1000 } 1001 1002 // We only want to take into account the innermost function call 1003 // containing the current selection so we can't stop at the first call 1004 // we find (the outermost one) and have to keep traversing it in case 1005 // we're inside a nested call. 1006 ast::visit::visit_typed_constant_record( 1007 self, 1008 location, 1009 module, 1010 name, 1011 arguments, 1012 type_, 1013 field_map, 1014 record_constructor, 1015 ); 1016 } 1017 1018 fn visit_typed_expr_call( 1019 &mut self, 1020 location: &'ast SrcSpan, 1021 type_: &'ast Arc<Type>, 1022 fun: &'ast TypedExpr, 1023 arguments: &'ast [TypedCallArg], 1024 open_parenthesis: &'ast Option<u32>, 1025 ) { 1026 let call_range = self.edits.src_span_to_lsp_range(*location); 1027 if !within(self.params.range, call_range) { 1028 return; 1029 } 1030 1031 if let Some(field_map) = fun.field_map() { 1032 let location = self.use_right_hand_side_location.unwrap_or(*location); 1033 self.selected_call = Some(SelectedCall { 1034 location, 1035 field_map, 1036 arguments: arguments.iter().map(Self::empty_argument).collect(), 1037 kind: SelectedCallKind::Value, 1038 fun_type: Some(fun.type_()), 1039 enclosing_function: self.current_function, 1040 }) 1041 } 1042 1043 // We only want to take into account the innermost function call 1044 // containing the current selection so we can't stop at the first call 1045 // we find (the outermost one) and have to keep traversing it in case 1046 // we're inside a nested call. 1047 let previous = self.use_right_hand_side_location; 1048 self.use_right_hand_side_location = None; 1049 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis); 1050 self.use_right_hand_side_location = previous; 1051 } 1052 1053 fn visit_typed_pattern_constructor( 1054 &mut self, 1055 location: &'ast SrcSpan, 1056 name_location: &'ast SrcSpan, 1057 name: &'ast EcoString, 1058 arguments: &'ast Vec<CallArg<TypedPattern>>, 1059 module: &'ast Option<(EcoString, SrcSpan)>, 1060 constructor: &'ast Inferred<type_::PatternConstructor>, 1061 spread: &'ast Option<SrcSpan>, 1062 type_: &'ast Arc<Type>, 1063 ) { 1064 let call_range = self.edits.src_span_to_lsp_range(*location); 1065 if !within(self.params.range, call_range) { 1066 return; 1067 } 1068 1069 if let Some(field_map) = constructor.field_map() { 1070 self.selected_call = Some(SelectedCall { 1071 location: *location, 1072 field_map, 1073 arguments: arguments.iter().map(Self::empty_argument).collect(), 1074 kind: SelectedCallKind::Pattern, 1075 fun_type: None, 1076 enclosing_function: self.current_function, 1077 }) 1078 } 1079 1080 ast::visit::visit_typed_pattern_constructor( 1081 self, 1082 location, 1083 name_location, 1084 name, 1085 arguments, 1086 module, 1087 constructor, 1088 spread, 1089 type_, 1090 ); 1091 } 1092} 1093 1094/// Collects variables that are in scope at a given cursor position. 1095struct ScopeVariableCollector { 1096 cursor: u32, 1097 pub variables: HashMap<EcoString, Arc<Type>>, 1098} 1099 1100impl ScopeVariableCollector { 1101 fn new(cursor: u32, definitions: &TypedDefinitions) -> Self { 1102 let TypedDefinitions { constants, .. } = definitions; 1103 1104 // When creating a collector, we add module constants to the variables 1105 // that are in scope. 1106 // These will always be available inside the body of any function we 1107 // might want to analyse (unless they are shadowed and replaced in the 1108 // map, that is) 1109 let variables = constants 1110 .iter() 1111 .map(|constant| (constant.name.clone(), constant.type_.clone())) 1112 .collect(); 1113 1114 Self { cursor, variables } 1115 } 1116 1117 fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap<EcoString, Arc<Type>> { 1118 for arg in &fun.arguments { 1119 if let Some(name) = arg.get_variable_name() { 1120 _ = self.variables.insert(name.clone(), arg.type_.clone()); 1121 } 1122 } 1123 1124 for statement in &fun.body { 1125 self.visit_typed_statement(statement); 1126 } 1127 1128 self.variables 1129 } 1130} 1131 1132impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector { 1133 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 1134 // We need to consider variables defined only before the cursor 1135 if statement.location().start >= self.cursor { 1136 return; 1137 } 1138 1139 ast::visit::visit_typed_statement(self, statement); 1140 } 1141 1142 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1143 // if cursor is inside the assignment, we only visit the value expression, 1144 // because the variable being defined isn't in scope yet. 1145 if assignment.location.contains(self.cursor) { 1146 self.visit_typed_expr(&assignment.value); 1147 } else { 1148 self.visit_typed_pattern(&assignment.pattern); 1149 } 1150 } 1151 1152 fn visit_typed_expr_fn( 1153 &mut self, 1154 _location: &'ast SrcSpan, 1155 _type_: &'ast Arc<Type>, 1156 _kind: &'ast FunctionLiteralKind, 1157 _arguments: &'ast [TypedArg], 1158 _body: &'ast Vec1<TypedStatement>, 1159 _return_annotation: &'ast Option<ast::TypeAst>, 1160 ) { 1161 // We don't descend into nested functions, as their variables aren't in 1162 // our scope 1163 } 1164 1165 fn visit_typed_expr_block( 1166 &mut self, 1167 location: &'ast SrcSpan, 1168 statements: &'ast [TypedStatement], 1169 ) { 1170 if self.cursor >= location.end { 1171 return; 1172 } 1173 ast::visit::visit_typed_expr_block(self, location, statements); 1174 } 1175 1176 fn visit_typed_pattern_variable( 1177 &mut self, 1178 _location: &'ast SrcSpan, 1179 name: &'ast EcoString, 1180 type_: &'ast Arc<Type>, 1181 _origin: &'ast VariableOrigin, 1182 ) { 1183 if !name.starts_with('_') { 1184 _ = self.variables.insert(name.clone(), type_.clone()); 1185 } 1186 } 1187 1188 fn visit_typed_pattern_assign( 1189 &mut self, 1190 _location: &'ast SrcSpan, 1191 name: &'ast EcoString, 1192 pattern: &'ast Pattern<Arc<Type>>, 1193 ) { 1194 self.visit_typed_pattern(pattern); 1195 if !name.starts_with('_') { 1196 _ = self.variables.insert(name.clone(), pattern.type_()); 1197 } 1198 } 1199} 1200 1201struct MissingImport { 1202 location: SrcSpan, 1203 suggestions: Vec<ImportSuggestion>, 1204} 1205 1206struct ImportSuggestion { 1207 // The name to replace with, if the user made a typo 1208 name: EcoString, 1209 // The optional module to import, if suggesting an importable module 1210 import: Option<EcoString>, 1211} 1212 1213pub fn code_action_import_module( 1214 module: &Module, 1215 line_numbers: &LineNumbers, 1216 params: &CodeActionParams, 1217 error: &Option<Error>, 1218 actions: &mut Vec<CodeAction>, 1219) { 1220 let uri = &params.text_document.uri; 1221 let Some(errors) = type_errors_for_module(error, module) else { 1222 return; 1223 }; 1224 1225 let missing_imports = errors 1226 .into_iter() 1227 .filter_map(|error| { 1228 if let type_::Error::UnknownModule { 1229 location, 1230 suggestions, 1231 .. 1232 } = error 1233 { 1234 suggest_imports(*location, suggestions) 1235 } else { 1236 None 1237 } 1238 }) 1239 .collect_vec(); 1240 1241 if missing_imports.is_empty() { 1242 return; 1243 } 1244 1245 let first_import_pos = position_of_first_definition_if_import(module, line_numbers); 1246 let first_is_import = first_import_pos.is_some(); 1247 let import_location = first_import_pos.unwrap_or_default(); 1248 1249 let after_import_newlines = 1250 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code); 1251 1252 for missing_import in missing_imports { 1253 let range = src_span_to_lsp_range(missing_import.location, line_numbers); 1254 if !overlaps(params.range, range) { 1255 continue; 1256 } 1257 1258 for suggestion in missing_import.suggestions { 1259 let mut edits = vec![TextEdit { 1260 range, 1261 new_text: suggestion.name.to_string(), 1262 }]; 1263 if let Some(import) = &suggestion.import { 1264 edits.push(get_import_edit( 1265 import_location, 1266 import, 1267 &after_import_newlines, 1268 )) 1269 }; 1270 1271 let title = match &suggestion.import { 1272 Some(import) => &format!("Import `{import}`"), 1273 _ => &format!("Did you mean `{}`", suggestion.name), 1274 }; 1275 1276 CodeActionBuilder::new(title) 1277 .kind(CodeActionKind::QuickFix) 1278 .changes(uri.clone(), edits) 1279 .preferred(true) 1280 .push_to(actions); 1281 } 1282 } 1283} 1284 1285pub fn code_action_generate_type( 1286 module: &Module, 1287 line_numbers: &LineNumbers, 1288 params: &CodeActionParams, 1289 error: &Option<Error>, 1290 actions: &mut Vec<CodeAction>, 1291) { 1292 let uri = &params.text_document.uri; 1293 let Some(errors) = type_errors_for_module(error, module) else { 1294 return; 1295 }; 1296 1297 for error in errors { 1298 let type_::Error::UnknownType { 1299 location, 1300 name, 1301 parameter_names, 1302 .. 1303 } = error 1304 else { 1305 continue; 1306 }; 1307 1308 let range = src_span_to_lsp_range(*location, line_numbers); 1309 if !within(params.range, range) { 1310 continue; 1311 } 1312 1313 // Insert the new type stub before the top-level definition that 1314 // contains the error, so it appears close to where it is used. 1315 let (insert_at, is_public) = 1316 definition_start_and_publicity_containing(&module.ast.definitions, location.start) 1317 .unwrap_or((module.code.len() as u32, false)); 1318 let insert_range = src_span_to_lsp_range( 1319 SrcSpan { 1320 start: insert_at, 1321 end: insert_at, 1322 }, 1323 line_numbers, 1324 ); 1325 1326 let pub_prefix = if is_public { "pub " } else { "" }; 1327 let new_text = if parameter_names.is_empty() { 1328 format!("{pub_prefix}type {name}\n\n") 1329 } else { 1330 let parameters = parameter_names.join(", "); 1331 format!("{pub_prefix}type {name}({parameters})\n\n") 1332 }; 1333 1334 let edit = TextEdit { 1335 range: insert_range, 1336 new_text, 1337 }; 1338 1339 CodeActionBuilder::new("Generate type") 1340 .kind(CodeActionKind::QuickFix) 1341 .changes(uri.clone(), vec![edit]) 1342 .preferred(true) 1343 .push_to(actions); 1344 } 1345} 1346 1347/// Returns the source offset and publicity of the top-level definition that 1348/// contains `position`, so the caller can insert code before it. 1349fn definition_start_and_publicity_containing( 1350 definitions: &TypedDefinitions, 1351 position: u32, 1352) -> Option<(u32, bool)> { 1353 let functions = definitions 1354 .functions 1355 .iter() 1356 .map(|function| (function.full_location(), function.publicity.is_public())); 1357 let custom_types = definitions.custom_types.iter().map(|custom_type| { 1358 ( 1359 custom_type.full_location(), 1360 custom_type.publicity.is_public(), 1361 ) 1362 }); 1363 let type_aliases = definitions 1364 .type_aliases 1365 .iter() 1366 .map(|type_alias| (type_alias.location, type_alias.publicity.is_public())); 1367 let constants = definitions 1368 .constants 1369 .iter() 1370 .map(|constant| (constant.location, constant.publicity.is_public())); 1371 1372 functions 1373 .chain(custom_types) 1374 .chain(type_aliases) 1375 .chain(constants) 1376 .filter(|(span, _)| span.start <= position && position <= span.end) 1377 .map(|(span, is_public)| (span.start, is_public)) 1378 .next() 1379} 1380 1381fn suggest_imports( 1382 location: SrcSpan, 1383 importable_modules: &[ModuleSuggestion], 1384) -> Option<MissingImport> { 1385 let suggestions = importable_modules 1386 .iter() 1387 .map(|suggestion| { 1388 let imported_name = suggestion.last_name_component(); 1389 match suggestion { 1390 ModuleSuggestion::Importable(name) => ImportSuggestion { 1391 name: imported_name.into(), 1392 import: Some(name.clone()), 1393 }, 1394 ModuleSuggestion::Imported(_) => ImportSuggestion { 1395 name: imported_name.into(), 1396 import: None, 1397 }, 1398 } 1399 }) 1400 .collect_vec(); 1401 1402 if suggestions.is_empty() { 1403 None 1404 } else { 1405 Some(MissingImport { 1406 location, 1407 suggestions, 1408 }) 1409 } 1410} 1411 1412pub fn code_action_add_missing_patterns( 1413 module: &Module, 1414 line_numbers: &LineNumbers, 1415 params: &CodeActionParams, 1416 error: &Option<Error>, 1417 actions: &mut Vec<CodeAction>, 1418) { 1419 let uri = &params.text_document.uri; 1420 let Some(errors) = type_errors_for_module(error, module) else { 1421 return; 1422 }; 1423 let missing_patterns = errors 1424 .iter() 1425 .filter_map(|error| { 1426 if let type_::Error::InexhaustiveCaseExpression { location, missing } = error { 1427 Some((*location, missing)) 1428 } else { 1429 None 1430 } 1431 }) 1432 .collect_vec(); 1433 1434 if missing_patterns.is_empty() { 1435 return; 1436 } 1437 1438 for (location, missing) in missing_patterns { 1439 let mut edits = TextEdits::new(line_numbers); 1440 let range = edits.src_span_to_lsp_range(location); 1441 if !within(params.range, range) { 1442 continue; 1443 } 1444 1445 let Some(Located::Expression { 1446 expression: TypedExpr::Case { 1447 clauses, subjects, .. 1448 }, 1449 .. 1450 }) = module.find_node(location.start) 1451 else { 1452 continue; 1453 }; 1454 1455 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line); 1456 1457 let indent = " ".repeat(indent_size); 1458 1459 // Insert the missing patterns just after the final clause, or just before 1460 // the closing brace if there are no clauses 1461 1462 let insert_at = clauses 1463 .last() 1464 .map(|clause| clause.location.end) 1465 .unwrap_or(location.end - 1); 1466 1467 for pattern in missing { 1468 let new_text = format!("\n{indent} {pattern} -> todo"); 1469 edits.insert(insert_at, new_text); 1470 } 1471 1472 // Add a newline + indent after the last pattern if there are no clauses 1473 // 1474 // This improves the generated code for this case: 1475 // ```gleam 1476 // case True {} 1477 // ``` 1478 // This produces: 1479 // ```gleam 1480 // case True { 1481 // True -> todo 1482 // False -> todo 1483 // } 1484 // ``` 1485 // Instead of: 1486 // ```gleam 1487 // case True { 1488 // True -> todo 1489 // False -> todo} 1490 // ``` 1491 // 1492 if clauses.is_empty() { 1493 let last_subject_location = subjects 1494 .last() 1495 .expect("Case expressions have at least one subject") 1496 .location() 1497 .end; 1498 1499 // Find the opening brace of the case expression 1500 let chars = module.code[last_subject_location as usize..].chars(); 1501 let mut start_brace_location = last_subject_location; 1502 for char in chars { 1503 start_brace_location += 1; 1504 if char == '{' { 1505 break; 1506 } 1507 } 1508 1509 // We remove any blank spaces/lines between the end of the last 1510 // comment inside the case expression and the insertion point. 1511 // If there's no comments we remove all empty space in between the 1512 // two braces 1513 let deletion_start = module 1514 .extra 1515 .last_comment_between(start_brace_location, insert_at) 1516 .map(|src_span| src_span.end) 1517 .unwrap_or(start_brace_location); 1518 1519 edits.delete(SrcSpan::new(deletion_start, insert_at)); 1520 edits.insert(insert_at, format!("\n{indent}")); 1521 } 1522 1523 CodeActionBuilder::new("Add missing patterns") 1524 .kind(CodeActionKind::QuickFix) 1525 .changes(uri.clone(), edits.edits) 1526 .preferred(true) 1527 .push_to(actions); 1528 } 1529} 1530 1531/// Builder for code action to add annotations to an assignment or function 1532/// 1533pub struct AddAnnotations<'a> { 1534 module: &'a Module, 1535 params: &'a CodeActionParams, 1536 edits: TextEdits<'a>, 1537 printer: Printer<'a>, 1538} 1539 1540impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> { 1541 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1542 self.visit_typed_expr(&assignment.value); 1543 1544 // We only offer this code action between `let` and `=`, because 1545 // otherwise it could lead to confusing behaviour if inside a block 1546 // which is part of a let binding. 1547 let pattern_location = assignment.pattern.location(); 1548 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 1549 let code_action_range = self.edits.src_span_to_lsp_range(location); 1550 1551 // Only offer the code action if the cursor is over the statement 1552 if !overlaps(code_action_range, self.params.range) { 1553 return; 1554 } 1555 1556 // We don't need to add an annotation if there already is one 1557 if assignment.annotation.is_some() { 1558 return; 1559 } 1560 1561 // Various expressions such as pipelines and `use` expressions generate assignments 1562 // internally. However, these cannot be annotated and so we don't offer a code action here. 1563 if matches!(assignment.kind, AssignmentKind::Generated) { 1564 return; 1565 } 1566 1567 self.edits.insert( 1568 pattern_location.end, 1569 format!(": {}", self.printer.print_type(&assignment.type_())), 1570 ); 1571 } 1572 1573 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1574 // Since type variable names are local to definitions, any type variables 1575 // in other parts of the module shouldn't affect what we print for the 1576 // annotations of this constant. 1577 self.printer.clear_type_variables(); 1578 1579 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1580 1581 // Only offer the code action if the cursor is over the statement 1582 if !overlaps(code_action_range, self.params.range) { 1583 return; 1584 } 1585 1586 // We don't need to add an annotation if there already is one 1587 if constant.annotation.is_some() { 1588 return; 1589 } 1590 1591 self.edits.insert( 1592 constant.name_location.end, 1593 format!(": {}", self.printer.print_type(&constant.type_)), 1594 ); 1595 } 1596 1597 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 1598 // Since type variable names are local to definitions, any type variables 1599 // in other parts of the module shouldn't affect what we print for the 1600 // annotations of this functions. The only variables which cannot clash 1601 // are ones defined in the signature of this function, which we register 1602 // when we visit the parameters of this function inside `collect_type_variables`. 1603 self.printer.clear_type_variables(); 1604 collect_type_variables(&mut self.printer, fun); 1605 1606 ast::visit::visit_typed_function(self, fun); 1607 1608 let code_action_range = self.edits.src_span_to_lsp_range( 1609 fun.body_start 1610 .map(|body_start| SrcSpan { 1611 start: fun.location.start, 1612 end: body_start, 1613 }) 1614 .unwrap_or(fun.location), 1615 ); 1616 1617 // Only offer the code action if the cursor is over the statement 1618 if !overlaps(code_action_range, self.params.range) { 1619 return; 1620 } 1621 1622 // Annotate each argument separately 1623 for argument in fun.arguments.iter() { 1624 // Don't annotate the argument if it's already annotated 1625 if argument.annotation.is_some() { 1626 continue; 1627 } 1628 1629 self.edits.insert( 1630 argument.location.end, 1631 format!(": {}", self.printer.print_type(&argument.type_)), 1632 ); 1633 } 1634 1635 // Annotate the return type if it isn't already annotated 1636 if fun.return_annotation.is_none() { 1637 self.edits.insert( 1638 fun.location.end, 1639 format!(" -> {}", self.printer.print_type(&fun.return_type)), 1640 ); 1641 } 1642 } 1643 1644 fn visit_typed_expr_fn( 1645 &mut self, 1646 location: &'ast SrcSpan, 1647 type_: &'ast Arc<Type>, 1648 kind: &'ast FunctionLiteralKind, 1649 arguments: &'ast [TypedArg], 1650 body: &'ast Vec1<TypedStatement>, 1651 return_annotation: &'ast Option<ast::TypeAst>, 1652 ) { 1653 ast::visit::visit_typed_expr_fn( 1654 self, 1655 location, 1656 type_, 1657 kind, 1658 arguments, 1659 body, 1660 return_annotation, 1661 ); 1662 1663 // If the function doesn't have a head, we can't annotate it 1664 let location = match kind { 1665 // Function captures don't need any type annotations 1666 FunctionLiteralKind::Capture { .. } => return, 1667 FunctionLiteralKind::Anonymous { head, .. } => head, 1668 FunctionLiteralKind::Use { location } => location, 1669 }; 1670 1671 let code_action_range = self.edits.src_span_to_lsp_range(*location); 1672 1673 // Only offer the code action if the cursor is over the expression 1674 if !overlaps(code_action_range, self.params.range) { 1675 return; 1676 } 1677 1678 // Annotate each argument separately 1679 for argument in arguments.iter() { 1680 // Don't annotate the argument if it's already annotated 1681 if argument.annotation.is_some() { 1682 continue; 1683 } 1684 1685 self.edits.insert( 1686 argument.location.end, 1687 format!(": {}", self.printer.print_type(&argument.type_)), 1688 ); 1689 } 1690 1691 // Annotate the return type if it isn't already annotated, and this is 1692 // an anonymous function. 1693 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) { 1694 let return_type = &type_.return_type().expect("Type must be a function"); 1695 let pretty_type = self.printer.print_type(return_type); 1696 self.edits 1697 .insert(location.end, format!(" -> {pretty_type}")); 1698 } 1699 } 1700} 1701 1702impl<'a> AddAnnotations<'a> { 1703 pub fn new( 1704 module: &'a Module, 1705 line_numbers: &'a LineNumbers, 1706 params: &'a CodeActionParams, 1707 ) -> Self { 1708 Self { 1709 module, 1710 params, 1711 edits: TextEdits::new(line_numbers), 1712 // We need to use the same printer for all the edits because otherwise 1713 // we could get duplicate type variable names. 1714 printer: Printer::new_without_type_variables(&module.ast.names), 1715 } 1716 } 1717 1718 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) { 1719 self.visit_typed_module(&self.module.ast); 1720 1721 let uri = &self.params.text_document.uri; 1722 1723 let title = match self.edits.edits.len() { 1724 // We don't offer a code action if there is no action to perform 1725 0 => return, 1726 1 => "Add type annotation", 1727 _ => "Add type annotations", 1728 }; 1729 1730 CodeActionBuilder::new(title) 1731 .kind(CodeActionKind::Refactor) 1732 .changes(uri.clone(), self.edits.edits) 1733 .preferred(false) 1734 .push_to(actions); 1735 } 1736} 1737 1738/// Code action to add type annotations to all top level definitions 1739/// 1740pub struct AnnotateTopLevelDefinitions<'a> { 1741 module: &'a Module, 1742 params: &'a CodeActionParams, 1743 edits: TextEdits<'a>, 1744 is_hovering_definition_requiring_annotations: bool, 1745} 1746 1747impl<'a> AnnotateTopLevelDefinitions<'a> { 1748 pub fn new( 1749 module: &'a Module, 1750 line_numbers: &'a LineNumbers, 1751 params: &'a CodeActionParams, 1752 ) -> Self { 1753 Self { 1754 module, 1755 params, 1756 edits: TextEdits::new(line_numbers), 1757 is_hovering_definition_requiring_annotations: false, 1758 } 1759 } 1760 1761 pub fn code_actions(mut self) -> Vec<CodeAction> { 1762 self.visit_typed_module(&self.module.ast); 1763 1764 // We only want to trigger the action if we're over one of the definition 1765 // which is lacking some annotations in the module 1766 if !self.is_hovering_definition_requiring_annotations { 1767 return vec![]; 1768 }; 1769 1770 let mut action = Vec::with_capacity(1); 1771 CodeActionBuilder::new("Annotate all top level definitions") 1772 .kind(CodeActionKind::RefactorRewrite) 1773 .changes(self.params.text_document.uri.clone(), self.edits.edits) 1774 .preferred(false) 1775 .push_to(&mut action); 1776 action 1777 } 1778} 1779 1780impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> { 1781 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1782 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1783 1784 // We don't need to add an annotation if there already is one 1785 if constant.annotation.is_some() { 1786 return; 1787 } 1788 1789 // We're hovering definition which needs some annotations 1790 if overlaps(code_action_range, self.params.range) { 1791 self.is_hovering_definition_requiring_annotations = true; 1792 } 1793 1794 self.edits.insert( 1795 constant.name_location.end, 1796 format!( 1797 ": {}", 1798 // Create new printer to ignore type variables from other definitions 1799 Printer::new_without_type_variables(&self.module.ast.names) 1800 .print_type(&constant.type_) 1801 ), 1802 ); 1803 } 1804 1805 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 1806 // Don't annotate already annotated arguments 1807 let arguments_to_annotate = fun 1808 .arguments 1809 .iter() 1810 .filter(|argument| argument.annotation.is_none()) 1811 .collect::<Vec<_>>(); 1812 let needs_return_annotation = fun.return_annotation.is_none(); 1813 1814 if arguments_to_annotate.is_empty() && !needs_return_annotation { 1815 return; 1816 } 1817 1818 let code_action_range = self.edits.src_span_to_lsp_range(fun.location); 1819 if overlaps(code_action_range, self.params.range) { 1820 self.is_hovering_definition_requiring_annotations = true; 1821 } 1822 1823 // Create new printer to ignore type variables from other definitions 1824 let mut printer = Printer::new_without_type_variables(&self.module.ast.names); 1825 collect_type_variables(&mut printer, fun); 1826 1827 // Annotate each argument separately 1828 for argument in arguments_to_annotate { 1829 self.edits.insert( 1830 argument.location.end, 1831 format!(": {}", printer.print_type(&argument.type_)), 1832 ); 1833 } 1834 1835 // Annotate the return type if it isn't already annotated 1836 if needs_return_annotation { 1837 self.edits.insert( 1838 fun.location.end, 1839 format!(" -> {}", printer.print_type(&fun.return_type)), 1840 ); 1841 } 1842 } 1843} 1844 1845struct TypeVariableCollector<'a, 'b> { 1846 printer: &'a mut Printer<'b>, 1847} 1848 1849/// Collect type variables defined within a function and register them for a 1850/// `Printer` 1851fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) { 1852 TypeVariableCollector { printer }.visit_typed_function(function); 1853} 1854 1855impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> { 1856 fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) { 1857 // Register this type variable so that we don't duplicate names when 1858 // adding annotations. 1859 self.printer.register_type_variable(name.clone()); 1860 } 1861} 1862 1863pub struct QualifiedConstructor<'a> { 1864 import: &'a Import<EcoString>, 1865 used_name: EcoString, 1866 constructor: EcoString, 1867 layer: ast::Layer, 1868} 1869 1870impl QualifiedConstructor<'_> { 1871 fn constructor_import(&self) -> String { 1872 if self.layer.is_value() { 1873 self.constructor.to_string() 1874 } else { 1875 format!("type {}", self.constructor) 1876 } 1877 } 1878} 1879 1880pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> { 1881 module: &'a Module, 1882 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 1883 params: &'a CodeActionParams, 1884 line_numbers: &'a LineNumbers, 1885 qualified_constructor: Option<QualifiedConstructor<'a>>, 1886} 1887 1888impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> { 1889 fn new( 1890 module: &'a Module, 1891 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 1892 params: &'a CodeActionParams, 1893 line_numbers: &'a LineNumbers, 1894 ) -> Self { 1895 Self { 1896 module, 1897 compiler, 1898 params, 1899 line_numbers, 1900 qualified_constructor: None, 1901 } 1902 } 1903 1904 fn get_module_import( 1905 &self, 1906 module_name: &EcoString, 1907 constructor: &EcoString, 1908 layer: ast::Layer, 1909 ) -> Option<&'a Import<EcoString>> { 1910 let mut matching_import = None; 1911 1912 for import in &self.module.ast.definitions.imports { 1913 if import.used_name().as_deref() == Some(module_name) 1914 && let Some(module) = self.compiler.get_module_interface(&import.module) 1915 { 1916 // If the import is the one we're referring to, we see if the 1917 // referred module exports the type/value we are trying to 1918 // unqualify: we don't want to offer the action indiscriminately if 1919 // it would generate invalid code! 1920 let module_exports_constructor = match layer { 1921 ast::Layer::Value => module.get_importable_value(constructor).is_some(), 1922 ast::Layer::Type => module.get_importable_type(constructor).is_some(), 1923 }; 1924 if module_exports_constructor { 1925 matching_import = Some(import); 1926 } 1927 } else { 1928 // If the import refers to another module we still want to check 1929 // if in its unqualified import list there is a name that's equal 1930 // to the one we're trying to unqualify. In this case we can't 1931 // offer the action as it would generate invalid code. 1932 // 1933 // For example: 1934 // ```gleam 1935 // import wibble.{Some} 1936 // import option 1937 // 1938 // pub fn something() { 1939 // option.Some(1) 1940 // ^^^^ We can't unqualify this because `Some` is already 1941 // imported unqualified from the `wibble` module 1942 // } 1943 // ``` 1944 // 1945 let imported = match layer { 1946 ast::Layer::Value => &import.unqualified_values, 1947 ast::Layer::Type => &import.unqualified_types, 1948 }; 1949 let constructor_already_imported_by_other_module = imported 1950 .iter() 1951 .any(|value| value.used_name() == constructor); 1952 1953 if constructor_already_imported_by_other_module { 1954 return None; 1955 } 1956 } 1957 } 1958 1959 matching_import 1960 } 1961} 1962 1963impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> { 1964 fn visit_type_ast_constructor( 1965 &mut self, 1966 location: &'ast SrcSpan, 1967 name: &'ast TypeAstConstructorName, 1968 arguments: &'ast [ast::TypeAst], 1969 arguments_types: Option<Vec<Arc<Type>>>, 1970 ) { 1971 let range = src_span_to_lsp_range(*location, self.line_numbers); 1972 if within(self.params.range, range) 1973 && let Some(module_alias) = name.module_name() 1974 && let Some(name) = name.name() 1975 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type) 1976 { 1977 self.qualified_constructor = Some(QualifiedConstructor { 1978 import, 1979 used_name: module_alias.clone(), 1980 constructor: name.clone(), 1981 layer: ast::Layer::Type, 1982 }); 1983 } 1984 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 1985 } 1986 1987 fn visit_typed_expr_module_select( 1988 &mut self, 1989 location: &'ast SrcSpan, 1990 field_start: &'ast u32, 1991 type_: &'ast Arc<Type>, 1992 label: &'ast EcoString, 1993 module_name: &'ast EcoString, 1994 module_alias: &'ast EcoString, 1995 constructor: &'ast ModuleValueConstructor, 1996 ) { 1997 // When hovering over a Record Value Constructor, we want to expand the source span to 1998 // include the module name: 1999 // option.Some 2000 // ↑ 2001 // This allows us to offer a code action when hovering over the module name. 2002 let range = src_span_to_lsp_range(*location, self.line_numbers); 2003 if within(self.params.range, range) 2004 && let ModuleValueConstructor::Record { 2005 name: constructor_name, 2006 .. 2007 } = constructor 2008 && let Some(import) = 2009 self.get_module_import(module_alias, constructor_name, ast::Layer::Value) 2010 { 2011 self.qualified_constructor = Some(QualifiedConstructor { 2012 import, 2013 used_name: module_alias.clone(), 2014 constructor: constructor_name.clone(), 2015 layer: ast::Layer::Value, 2016 }); 2017 } 2018 ast::visit::visit_typed_expr_module_select( 2019 self, 2020 location, 2021 field_start, 2022 type_, 2023 label, 2024 module_name, 2025 module_alias, 2026 constructor, 2027 ) 2028 } 2029 2030 fn visit_typed_pattern_constructor( 2031 &mut self, 2032 location: &'ast SrcSpan, 2033 name_location: &'ast SrcSpan, 2034 name: &'ast EcoString, 2035 arguments: &'ast Vec<CallArg<TypedPattern>>, 2036 module: &'ast Option<(EcoString, SrcSpan)>, 2037 constructor: &'ast Inferred<type_::PatternConstructor>, 2038 spread: &'ast Option<SrcSpan>, 2039 type_: &'ast Arc<Type>, 2040 ) { 2041 let range = src_span_to_lsp_range(*location, self.line_numbers); 2042 if within(self.params.range, range) 2043 && let Some((module_alias, _)) = module 2044 && let Inferred::Known(_) = constructor 2045 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 2046 { 2047 self.qualified_constructor = Some(QualifiedConstructor { 2048 import, 2049 used_name: module_alias.clone(), 2050 constructor: name.clone(), 2051 layer: ast::Layer::Value, 2052 }); 2053 } 2054 ast::visit::visit_typed_pattern_constructor( 2055 self, 2056 location, 2057 name_location, 2058 name, 2059 arguments, 2060 module, 2061 constructor, 2062 spread, 2063 type_, 2064 ); 2065 } 2066 2067 fn visit_typed_constant_record( 2068 &mut self, 2069 location: &'ast SrcSpan, 2070 module: &'ast Option<(EcoString, SrcSpan)>, 2071 name: &'ast EcoString, 2072 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2073 type_: &'ast Arc<Type>, 2074 field_map: &'ast Inferred<FieldMap>, 2075 record_constructor: &'ast Option<Box<ValueConstructor>>, 2076 ) { 2077 let range = src_span_to_lsp_range(*location, self.line_numbers); 2078 if within(self.params.range, range) 2079 && let Some((module_alias, _)) = module 2080 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 2081 { 2082 self.qualified_constructor = Some(QualifiedConstructor { 2083 import, 2084 used_name: module_alias.clone(), 2085 constructor: name.clone(), 2086 layer: ast::Layer::Value, 2087 }); 2088 } 2089 ast::visit::visit_typed_constant_record( 2090 self, 2091 location, 2092 module, 2093 name, 2094 arguments, 2095 type_, 2096 field_map, 2097 record_constructor, 2098 ); 2099 } 2100 2101 fn visit_typed_constant_var( 2102 &mut self, 2103 location: &'ast SrcSpan, 2104 module: &'ast Option<(EcoString, SrcSpan)>, 2105 name: &'ast EcoString, 2106 constructor: &'ast Option<Box<ValueConstructor>>, 2107 type_: &'ast Arc<Type>, 2108 ) { 2109 let range = src_span_to_lsp_range(*location, self.line_numbers); 2110 if within(self.params.range, range) 2111 && let Some((module_alias, _)) = module 2112 && let Some(constructor) = constructor 2113 && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant 2114 && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) 2115 { 2116 self.qualified_constructor = Some(QualifiedConstructor { 2117 import, 2118 used_name: module_alias.clone(), 2119 constructor: name.clone(), 2120 layer: ast::Layer::Value, 2121 }); 2122 } 2123 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2124 } 2125} 2126 2127pub struct QualifiedToUnqualifiedImportSecondPass<'a> { 2128 module: &'a Module, 2129 params: &'a CodeActionParams, 2130 edits: TextEdits<'a>, 2131 qualified_constructor: QualifiedConstructor<'a>, 2132} 2133 2134impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> { 2135 pub fn new( 2136 module: &'a Module, 2137 params: &'a CodeActionParams, 2138 line_numbers: &'a LineNumbers, 2139 qualified_constructor: QualifiedConstructor<'a>, 2140 ) -> Self { 2141 Self { 2142 module, 2143 params, 2144 edits: TextEdits::new(line_numbers), 2145 qualified_constructor, 2146 } 2147 } 2148 2149 pub fn code_actions(mut self) -> Vec<CodeAction> { 2150 self.visit_typed_module(&self.module.ast); 2151 if self.edits.edits.is_empty() { 2152 return vec![]; 2153 } 2154 self.edit_import(); 2155 let mut action = Vec::with_capacity(1); 2156 CodeActionBuilder::new(&format!( 2157 "Unqualify {}.{}", 2158 self.qualified_constructor.used_name, self.qualified_constructor.constructor 2159 )) 2160 .kind(CodeActionKind::Refactor) 2161 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2162 .preferred(false) 2163 .push_to(&mut action); 2164 action 2165 } 2166 2167 fn remove_module_qualifier(&mut self, location: SrcSpan) { 2168 self.edits.delete(SrcSpan { 2169 start: location.start, 2170 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus . 2171 }) 2172 } 2173 2174 fn edit_import(&mut self) { 2175 let QualifiedConstructor { 2176 constructor, 2177 layer, 2178 import, 2179 .. 2180 } = &self.qualified_constructor; 2181 let is_imported = if layer.is_value() { 2182 import 2183 .unqualified_values 2184 .iter() 2185 .any(|value| value.used_name() == constructor) 2186 } else { 2187 import 2188 .unqualified_types 2189 .iter() 2190 .any(|type_| type_.used_name() == constructor) 2191 }; 2192 if is_imported { 2193 return; 2194 } 2195 let (insert_pos, new_text) = edits::insert_unqualified_import( 2196 import, 2197 &self.module.code, 2198 self.qualified_constructor.constructor_import(), 2199 ); 2200 let span = SrcSpan::new(insert_pos, insert_pos); 2201 self.edits.replace(span, new_text); 2202 } 2203} 2204 2205impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> { 2206 fn visit_type_ast_constructor( 2207 &mut self, 2208 location: &'ast SrcSpan, 2209 name: &'ast TypeAstConstructorName, 2210 arguments: &'ast [ast::TypeAst], 2211 arguments_types: Option<Vec<Arc<Type>>>, 2212 ) { 2213 if let Some(module_name) = name.module_name() 2214 && let Some(name) = name.name() 2215 { 2216 let QualifiedConstructor { 2217 used_name, 2218 constructor, 2219 layer, 2220 .. 2221 } = &self.qualified_constructor; 2222 2223 if !layer.is_value() && used_name == module_name && name == constructor { 2224 self.remove_module_qualifier(*location); 2225 } 2226 } 2227 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2228 } 2229 2230 fn visit_typed_expr_module_select( 2231 &mut self, 2232 location: &'ast SrcSpan, 2233 field_start: &'ast u32, 2234 type_: &'ast Arc<Type>, 2235 label: &'ast EcoString, 2236 module_name: &'ast EcoString, 2237 module_alias: &'ast EcoString, 2238 constructor: &'ast ModuleValueConstructor, 2239 ) { 2240 if let ModuleValueConstructor::Record { name, .. } = constructor { 2241 let QualifiedConstructor { 2242 used_name, 2243 constructor, 2244 layer, 2245 .. 2246 } = &self.qualified_constructor; 2247 2248 if layer.is_value() && used_name == module_alias && name == constructor { 2249 self.remove_module_qualifier(*location); 2250 } 2251 } 2252 ast::visit::visit_typed_expr_module_select( 2253 self, 2254 location, 2255 field_start, 2256 type_, 2257 label, 2258 module_name, 2259 module_alias, 2260 constructor, 2261 ) 2262 } 2263 2264 fn visit_typed_pattern_constructor( 2265 &mut self, 2266 location: &'ast SrcSpan, 2267 name_location: &'ast SrcSpan, 2268 name: &'ast EcoString, 2269 arguments: &'ast Vec<CallArg<TypedPattern>>, 2270 module: &'ast Option<(EcoString, SrcSpan)>, 2271 constructor: &'ast Inferred<type_::PatternConstructor>, 2272 spread: &'ast Option<SrcSpan>, 2273 type_: &'ast Arc<Type>, 2274 ) { 2275 if let Some((module_alias, _)) = module 2276 && let Inferred::Known(_) = constructor 2277 { 2278 let QualifiedConstructor { 2279 used_name, 2280 constructor, 2281 layer, 2282 .. 2283 } = &self.qualified_constructor; 2284 2285 if layer.is_value() && used_name == module_alias && name == constructor { 2286 self.remove_module_qualifier(*location); 2287 } 2288 } 2289 ast::visit::visit_typed_pattern_constructor( 2290 self, 2291 location, 2292 name_location, 2293 name, 2294 arguments, 2295 module, 2296 constructor, 2297 spread, 2298 type_, 2299 ); 2300 } 2301 2302 fn visit_typed_constant_record( 2303 &mut self, 2304 location: &'ast SrcSpan, 2305 module: &'ast Option<(EcoString, SrcSpan)>, 2306 name: &'ast EcoString, 2307 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2308 type_: &'ast Arc<Type>, 2309 field_map: &'ast Inferred<FieldMap>, 2310 record_constructor: &'ast Option<Box<ValueConstructor>>, 2311 ) { 2312 if let Some((module_alias, _)) = module { 2313 let QualifiedConstructor { 2314 used_name, 2315 constructor, 2316 layer, 2317 .. 2318 } = &self.qualified_constructor; 2319 2320 if layer.is_value() && used_name == module_alias && name == constructor { 2321 self.remove_module_qualifier(*location); 2322 } 2323 } 2324 ast::visit::visit_typed_constant_record( 2325 self, 2326 location, 2327 module, 2328 name, 2329 arguments, 2330 type_, 2331 field_map, 2332 record_constructor, 2333 ); 2334 } 2335 2336 fn visit_typed_constant_var( 2337 &mut self, 2338 location: &'ast SrcSpan, 2339 module: &'ast Option<(EcoString, SrcSpan)>, 2340 name: &'ast EcoString, 2341 constructor: &'ast Option<Box<ValueConstructor>>, 2342 type_: &'ast Arc<Type>, 2343 ) { 2344 if let Some((module_alias, _)) = module { 2345 let QualifiedConstructor { 2346 used_name, 2347 constructor: wanted_constructor, 2348 layer, 2349 .. 2350 } = &self.qualified_constructor; 2351 2352 if layer.is_value() && used_name == module_alias && name == wanted_constructor { 2353 self.remove_module_qualifier(*location); 2354 } 2355 } 2356 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2357 } 2358} 2359 2360pub fn code_action_convert_qualified_constructor_to_unqualified<IO>( 2361 module: &Module, 2362 compiler: &LspProjectCompiler<FileSystemProxy<IO>>, 2363 line_numbers: &LineNumbers, 2364 params: &CodeActionParams, 2365 actions: &mut Vec<CodeAction>, 2366) { 2367 let mut first_pass = 2368 QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers); 2369 first_pass.visit_typed_module(&module.ast); 2370 let Some(qualified_constructor) = first_pass.qualified_constructor else { 2371 return; 2372 }; 2373 let second_pass = QualifiedToUnqualifiedImportSecondPass::new( 2374 module, 2375 params, 2376 line_numbers, 2377 qualified_constructor, 2378 ); 2379 let new_actions = second_pass.code_actions(); 2380 actions.extend(new_actions); 2381} 2382 2383struct UnqualifiedConstructor<'a> { 2384 module_name: EcoString, 2385 constructor: &'a ast::UnqualifiedImport, 2386 layer: ast::Layer, 2387} 2388 2389struct UnqualifiedToQualifiedImportFirstPass<'a> { 2390 module: &'a Module, 2391 params: &'a CodeActionParams, 2392 line_numbers: &'a LineNumbers, 2393 unqualified_constructor: Option<UnqualifiedConstructor<'a>>, 2394} 2395 2396impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> { 2397 fn new( 2398 module: &'a Module, 2399 params: &'a CodeActionParams, 2400 line_numbers: &'a LineNumbers, 2401 ) -> Self { 2402 Self { 2403 module, 2404 params, 2405 line_numbers, 2406 unqualified_constructor: None, 2407 } 2408 } 2409 2410 fn get_module_import_from_value_constructor( 2411 &mut self, 2412 module_name: &EcoString, 2413 constructor_name: &EcoString, 2414 ) { 2415 self.unqualified_constructor = self 2416 .module 2417 .ast 2418 .definitions 2419 .imports 2420 .iter() 2421 .filter(|import| import.module == *module_name) 2422 .find_map(|import| { 2423 import 2424 .unqualified_values 2425 .iter() 2426 .find(|value| value.used_name() == constructor_name) 2427 .and_then(|value| { 2428 Some(UnqualifiedConstructor { 2429 constructor: value, 2430 module_name: import.used_name()?, 2431 layer: ast::Layer::Value, 2432 }) 2433 }) 2434 }) 2435 } 2436 2437 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) { 2438 self.unqualified_constructor = 2439 self.module 2440 .ast 2441 .definitions 2442 .imports 2443 .iter() 2444 .find_map(|import| { 2445 if let Some(ty) = import 2446 .unqualified_types 2447 .iter() 2448 .find(|ty| ty.used_name() == constructor_name) 2449 { 2450 return Some(UnqualifiedConstructor { 2451 constructor: ty, 2452 module_name: import.used_name()?, 2453 layer: ast::Layer::Type, 2454 }); 2455 } 2456 None 2457 }) 2458 } 2459} 2460 2461impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> { 2462 fn visit_type_ast_constructor( 2463 &mut self, 2464 location: &'ast SrcSpan, 2465 name: &'ast TypeAstConstructorName, 2466 arguments: &'ast [ast::TypeAst], 2467 arguments_types: Option<Vec<Arc<Type>>>, 2468 ) { 2469 if !name.is_qualified() 2470 && let Some(name) = name.name() 2471 && within( 2472 self.params.range, 2473 src_span_to_lsp_range(*location, self.line_numbers), 2474 ) 2475 { 2476 self.get_module_import_from_type_constructor(name); 2477 } 2478 2479 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2480 } 2481 2482 fn visit_typed_expr_var( 2483 &mut self, 2484 location: &'ast SrcSpan, 2485 constructor: &'ast ValueConstructor, 2486 name: &'ast EcoString, 2487 ) { 2488 let range = src_span_to_lsp_range(*location, self.line_numbers); 2489 if within(self.params.range, range) 2490 && let Some(module_name) = match &constructor.variant { 2491 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2492 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2493 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2494 2495 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2496 } 2497 { 2498 self.get_module_import_from_value_constructor(module_name, name); 2499 } 2500 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2501 } 2502 2503 fn visit_typed_pattern_constructor( 2504 &mut self, 2505 location: &'ast SrcSpan, 2506 name_location: &'ast SrcSpan, 2507 name: &'ast EcoString, 2508 arguments: &'ast Vec<CallArg<TypedPattern>>, 2509 module: &'ast Option<(EcoString, SrcSpan)>, 2510 constructor: &'ast Inferred<type_::PatternConstructor>, 2511 spread: &'ast Option<SrcSpan>, 2512 type_: &'ast Arc<Type>, 2513 ) { 2514 if module.is_none() 2515 && within( 2516 self.params.range, 2517 src_span_to_lsp_range(*location, self.line_numbers), 2518 ) 2519 && let Inferred::Known(constructor) = constructor 2520 { 2521 self.get_module_import_from_value_constructor(&constructor.module, name); 2522 } 2523 2524 ast::visit::visit_typed_pattern_constructor( 2525 self, 2526 location, 2527 name_location, 2528 name, 2529 arguments, 2530 module, 2531 constructor, 2532 spread, 2533 type_, 2534 ); 2535 } 2536 2537 fn visit_typed_constant_record( 2538 &mut self, 2539 location: &'ast SrcSpan, 2540 module: &'ast Option<(EcoString, SrcSpan)>, 2541 name: &'ast EcoString, 2542 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2543 type_: &'ast Arc<Type>, 2544 field_map: &'ast Inferred<FieldMap>, 2545 record_constructor: &'ast Option<Box<ValueConstructor>>, 2546 ) { 2547 if module.is_none() 2548 && within( 2549 self.params.range, 2550 src_span_to_lsp_range(*location, self.line_numbers), 2551 ) 2552 && let Some(record_constructor) = record_constructor 2553 && let Some(module_name) = match &record_constructor.variant { 2554 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2555 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2556 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2557 2558 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2559 } 2560 { 2561 self.get_module_import_from_value_constructor(module_name, name); 2562 } 2563 ast::visit::visit_typed_constant_record( 2564 self, 2565 location, 2566 module, 2567 name, 2568 arguments, 2569 type_, 2570 field_map, 2571 record_constructor, 2572 ); 2573 } 2574 2575 fn visit_typed_constant_var( 2576 &mut self, 2577 location: &'ast SrcSpan, 2578 module: &'ast Option<(EcoString, SrcSpan)>, 2579 name: &'ast EcoString, 2580 constructor: &'ast Option<Box<ValueConstructor>>, 2581 type_: &'ast Arc<Type>, 2582 ) { 2583 if module.is_none() 2584 && within( 2585 self.params.range, 2586 src_span_to_lsp_range(*location, self.line_numbers), 2587 ) 2588 && let Some(constructor) = constructor 2589 && let Some(module_name) = match &constructor.variant { 2590 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2591 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2592 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2593 2594 type_::ValueConstructorVariant::LocalVariable { .. } => None, 2595 } 2596 { 2597 self.get_module_import_from_value_constructor(module_name, name); 2598 } 2599 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2600 } 2601} 2602 2603struct UnqualifiedToQualifiedImportSecondPass<'a> { 2604 module: &'a Module, 2605 params: &'a CodeActionParams, 2606 edits: TextEdits<'a>, 2607 unqualified_constructor: UnqualifiedConstructor<'a>, 2608} 2609 2610impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> { 2611 pub fn new( 2612 module: &'a Module, 2613 params: &'a CodeActionParams, 2614 line_numbers: &'a LineNumbers, 2615 unqualified_constructor: UnqualifiedConstructor<'a>, 2616 ) -> Self { 2617 Self { 2618 module, 2619 params, 2620 edits: TextEdits::new(line_numbers), 2621 unqualified_constructor, 2622 } 2623 } 2624 2625 fn add_module_qualifier(&mut self, location: SrcSpan) { 2626 let src_span = SrcSpan::new( 2627 location.start, 2628 location.start + self.unqualified_constructor.constructor.used_name().len() as u32, 2629 ); 2630 2631 self.edits.replace( 2632 src_span, 2633 format!( 2634 "{}.{}", 2635 self.unqualified_constructor.module_name, 2636 self.unqualified_constructor.constructor.name 2637 ), 2638 ); 2639 } 2640 2641 pub fn code_actions(mut self) -> Vec<CodeAction> { 2642 self.visit_typed_module(&self.module.ast); 2643 if self.edits.edits.is_empty() { 2644 return vec![]; 2645 } 2646 self.edit_import(); 2647 let mut action = Vec::with_capacity(1); 2648 let UnqualifiedConstructor { 2649 module_name, 2650 constructor, 2651 .. 2652 } = self.unqualified_constructor; 2653 CodeActionBuilder::new(&format!( 2654 "Qualify {} as {}.{}", 2655 constructor.used_name(), 2656 module_name, 2657 constructor.name, 2658 )) 2659 .kind(CodeActionKind::Refactor) 2660 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2661 .preferred(false) 2662 .push_to(&mut action); 2663 action 2664 } 2665 2666 fn edit_import(&mut self) { 2667 let UnqualifiedConstructor { 2668 constructor: 2669 ast::UnqualifiedImport { 2670 location: constructor_import_span, 2671 .. 2672 }, 2673 .. 2674 } = self.unqualified_constructor; 2675 2676 let mut last_char_pos = constructor_import_span.end as usize; 2677 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2678 last_char_pos += 1; 2679 } 2680 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") { 2681 last_char_pos += 1; 2682 } 2683 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2684 last_char_pos += 1; 2685 } 2686 2687 self.edits.delete(SrcSpan::new( 2688 constructor_import_span.start, 2689 last_char_pos as u32, 2690 )); 2691 } 2692} 2693 2694impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> { 2695 fn visit_type_ast_constructor( 2696 &mut self, 2697 location: &'ast SrcSpan, 2698 name: &'ast TypeAstConstructorName, 2699 arguments: &'ast [ast::TypeAst], 2700 arguments_types: Option<Vec<Arc<Type>>>, 2701 ) { 2702 if !name.is_qualified() 2703 && let Some(name) = name.name() 2704 { 2705 let UnqualifiedConstructor { 2706 constructor, layer, .. 2707 } = &self.unqualified_constructor; 2708 if !layer.is_value() && constructor.used_name() == name { 2709 self.add_module_qualifier(*location); 2710 } 2711 } 2712 ast::visit::visit_type_ast_constructor(self, location, name, arguments, arguments_types); 2713 } 2714 2715 fn visit_typed_expr_var( 2716 &mut self, 2717 location: &'ast SrcSpan, 2718 constructor: &'ast ValueConstructor, 2719 name: &'ast EcoString, 2720 ) { 2721 let UnqualifiedConstructor { 2722 constructor: wanted_constructor, 2723 layer, 2724 .. 2725 } = &self.unqualified_constructor; 2726 2727 if layer.is_value() 2728 && wanted_constructor.used_name() == name 2729 && !constructor.is_local_variable() 2730 { 2731 self.add_module_qualifier(*location); 2732 } 2733 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2734 } 2735 2736 fn visit_typed_pattern_constructor( 2737 &mut self, 2738 location: &'ast SrcSpan, 2739 name_location: &'ast SrcSpan, 2740 name: &'ast EcoString, 2741 arguments: &'ast Vec<CallArg<TypedPattern>>, 2742 module: &'ast Option<(EcoString, SrcSpan)>, 2743 constructor: &'ast Inferred<type_::PatternConstructor>, 2744 spread: &'ast Option<SrcSpan>, 2745 type_: &'ast Arc<Type>, 2746 ) { 2747 if module.is_none() { 2748 let UnqualifiedConstructor { 2749 constructor: wanted_constructor, 2750 layer, 2751 .. 2752 } = &self.unqualified_constructor; 2753 if layer.is_value() && wanted_constructor.used_name() == name { 2754 self.add_module_qualifier(*location); 2755 } 2756 } 2757 ast::visit::visit_typed_pattern_constructor( 2758 self, 2759 location, 2760 name_location, 2761 name, 2762 arguments, 2763 module, 2764 constructor, 2765 spread, 2766 type_, 2767 ); 2768 } 2769 2770 fn visit_typed_constant_record( 2771 &mut self, 2772 location: &'ast SrcSpan, 2773 module: &'ast Option<(EcoString, SrcSpan)>, 2774 name: &'ast EcoString, 2775 arguments: &'ast Option<Vec<CallArg<ast::TypedConstant>>>, 2776 type_: &'ast Arc<Type>, 2777 field_map: &'ast Inferred<FieldMap>, 2778 record_constructor: &'ast Option<Box<ValueConstructor>>, 2779 ) { 2780 if module.is_none() { 2781 let UnqualifiedConstructor { 2782 constructor: wanted_constructor, 2783 layer, 2784 .. 2785 } = &self.unqualified_constructor; 2786 if layer.is_value() && wanted_constructor.used_name() == name { 2787 self.add_module_qualifier(*location); 2788 } 2789 } 2790 ast::visit::visit_typed_constant_record( 2791 self, 2792 location, 2793 module, 2794 name, 2795 arguments, 2796 type_, 2797 field_map, 2798 record_constructor, 2799 ); 2800 } 2801 2802 fn visit_typed_constant_var( 2803 &mut self, 2804 location: &'ast SrcSpan, 2805 module: &'ast Option<(EcoString, SrcSpan)>, 2806 name: &'ast EcoString, 2807 constructor: &'ast Option<Box<ValueConstructor>>, 2808 type_: &'ast Arc<Type>, 2809 ) { 2810 if module.is_none() { 2811 let UnqualifiedConstructor { 2812 constructor: wanted_constructor, 2813 layer, 2814 .. 2815 } = &self.unqualified_constructor; 2816 if layer.is_value() && wanted_constructor.used_name() == name { 2817 self.add_module_qualifier(*location); 2818 } 2819 } 2820 ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); 2821 } 2822} 2823 2824pub fn code_action_convert_unqualified_constructor_to_qualified( 2825 module: &Module, 2826 line_numbers: &LineNumbers, 2827 params: &CodeActionParams, 2828 actions: &mut Vec<CodeAction>, 2829) { 2830 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers); 2831 first_pass.visit_typed_module(&module.ast); 2832 let Some(unqualified_constructor) = first_pass.unqualified_constructor else { 2833 return; 2834 }; 2835 let second_pass = UnqualifiedToQualifiedImportSecondPass::new( 2836 module, 2837 params, 2838 line_numbers, 2839 unqualified_constructor, 2840 ); 2841 let new_actions = second_pass.code_actions(); 2842 actions.extend(new_actions); 2843} 2844 2845/// Builder for code action to apply the convert from use action, turning a use 2846/// expression into a regular function call. 2847/// 2848pub struct ConvertFromUse<'a> { 2849 module: &'a Module, 2850 params: &'a CodeActionParams, 2851 edits: TextEdits<'a>, 2852 selected_use: Option<&'a TypedUse>, 2853} 2854 2855impl<'a> ConvertFromUse<'a> { 2856 pub fn new( 2857 module: &'a Module, 2858 line_numbers: &'a LineNumbers, 2859 params: &'a CodeActionParams, 2860 ) -> Self { 2861 Self { 2862 module, 2863 params, 2864 edits: TextEdits::new(line_numbers), 2865 selected_use: None, 2866 } 2867 } 2868 2869 pub fn code_actions(mut self) -> Vec<CodeAction> { 2870 self.visit_typed_module(&self.module.ast); 2871 2872 let Some(use_) = self.selected_use else { 2873 return vec![]; 2874 }; 2875 2876 let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else { 2877 return vec![]; 2878 }; 2879 2880 // If the use callback we're desugaring is using labels, that means we 2881 // have to add the last argument's label when writing the callback; 2882 // otherwise, it would result in invalid code. 2883 // 2884 // use acc, item <- list.fold(over: list, from: 1) 2885 // todo 2886 // 2887 // Needs to be rewritten as: 2888 // 2889 // list.fold(over: list, from: 1, with: fn(acc, item) { ... }) 2890 // ^^^^^ We cannot forget to add this label back! 2891 // 2892 let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) { 2893 fun.field_map() 2894 .and_then(|field_map| field_map.missing_labels(arguments).last().cloned()) 2895 .map(|label| eco_format!("{label}: ")) 2896 .unwrap_or(EcoString::from("")) 2897 } else { 2898 EcoString::from("") 2899 }; 2900 2901 // The use callback is not necessarily the last argument. If you have 2902 // the following function: `wibble(a a, b b) { todo }` 2903 // And use it like this: `use <- wibble(b: 1)`, the first argument `a` 2904 // is going to be the use callback, not the last one! 2905 let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback()); 2906 let Some(CallArg { 2907 implicit: Some(ImplicitCallArgOrigin::Use), 2908 value: TypedExpr::Fn { body, type_, .. }, 2909 .. 2910 }) = use_callback 2911 else { 2912 return vec![]; 2913 }; 2914 2915 // If there's arguments on the left hand side of the function we extract 2916 // those so we can paste them back as the anonymous function arguments. 2917 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) { 2918 let assignments_range = 2919 use_.assignments_location.start as usize..use_.assignments_location.end as usize; 2920 self.module 2921 .code 2922 .get(assignments_range) 2923 .expect("use assignments") 2924 } else { 2925 "" 2926 }; 2927 2928 // We first delete everything on the left hand side of use and the use 2929 // arrow. 2930 self.edits.delete(SrcSpan { 2931 start: use_.location.start, 2932 end: use_.right_hand_side_location.start, 2933 }); 2934 2935 let use_line_end = use_.right_hand_side_location.end; 2936 let use_rhs_function_has_some_explicit_arguments = arguments 2937 .iter() 2938 .filter(|argument| !argument.is_use_implicit_callback()) 2939 .peekable() 2940 .peek() 2941 .is_some(); 2942 2943 let use_rhs_function_ends_with_closed_parentheses = self 2944 .module 2945 .code 2946 .get(use_line_end as usize - 1..use_line_end as usize) 2947 == Some(")"); 2948 2949 let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit()); 2950 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end); 2951 2952 // This is the piece of code between the end of the last argument and 2953 // the end of the use_expression: 2954 // 2955 // use <- wibble(a, b, ) 2956 // ^^^^^ This piece right here, from `,` included 2957 // up to `)` excluded. 2958 // 2959 let text_after_last_argument = self 2960 .module 2961 .code 2962 .get(last_arg_end as usize..use_line_end as usize - 1); 2963 let use_rhs_has_comma_after_last_argument = 2964 text_after_last_argument.is_some_and(|code| code.contains(',')); 2965 let needs_space_before_callback = 2966 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' ')); 2967 2968 if use_rhs_function_ends_with_closed_parentheses { 2969 // If the function on the right hand side of use ends with a closed 2970 // parentheses then we have to remove it and add it later at the end 2971 // of the anonymous function we're inserting. 2972 // 2973 // use <- wibble() 2974 // ^ To add the fn() we need to first remove this 2975 // 2976 // So here we write over the last closed parentheses to remove it. 2977 let callback_start = format!("{callback_label}fn({assignments}) {{"); 2978 self.edits.replace( 2979 SrcSpan { 2980 start: use_line_end - 1, 2981 end: use_line_end, 2982 }, 2983 // If the function on the rhs of use has other orguments besides 2984 // the implicit fn expression then we need to put a comma after 2985 // the last argument. 2986 if use_rhs_function_has_some_explicit_arguments 2987 && !use_rhs_has_comma_after_last_argument 2988 { 2989 format!(", {callback_start}") 2990 } else if needs_space_before_callback { 2991 format!(" {callback_start}") 2992 } else { 2993 callback_start 2994 }, 2995 ) 2996 } else { 2997 // On the other hand, if the function on the right hand side doesn't 2998 // end with a closed parenthese then we have to manually add it. 2999 // 3000 // use <- wibble 3001 // ^ No parentheses 3002 // 3003 self.edits 3004 .insert(use_line_end, format!("(fn({assignments}) {{")) 3005 }; 3006 3007 // Then we have to increase indentation for all the lines of the use 3008 // body. 3009 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location()); 3010 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location()); 3011 3012 for line in first_fn_expression_range.start.line..=use_body_range.end.line { 3013 self.edits.edits.push(TextEdit { 3014 range: Range { 3015 start: Position { line, character: 0 }, 3016 end: Position { line, character: 0 }, 3017 }, 3018 new_text: " ".to_string(), 3019 }) 3020 } 3021 3022 let final_line_indentation = " ".repeat(use_body_range.start.character as usize); 3023 self.edits.insert( 3024 use_.call.location().end, 3025 format!("\n{final_line_indentation}}})"), 3026 ); 3027 3028 let mut action = Vec::with_capacity(1); 3029 CodeActionBuilder::new("Convert from `use`") 3030 .kind(CodeActionKind::RefactorRewrite) 3031 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3032 .preferred(false) 3033 .push_to(&mut action); 3034 action 3035 } 3036} 3037 3038impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> { 3039 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3040 let use_range = self.edits.src_span_to_lsp_range(use_.location); 3041 3042 // If the use expression is using patterns that are not just variable 3043 // assignments then we can't automatically rewrite it as it would result 3044 // in a syntax error as we can't pattern match in an anonymous function 3045 // head. 3046 // At the same time we can't safely add bindings inside the anonymous 3047 // function body by picking placeholder names as we'd risk shadowing 3048 // variables coming from the outer scope. 3049 // So we just skip those use expressions we can't safely rewrite! 3050 if within(self.params.range, use_range) 3051 && use_ 3052 .assignments 3053 .iter() 3054 .all(|assignment| assignment.pattern.is_variable()) 3055 { 3056 self.selected_use = Some(use_); 3057 } 3058 3059 // We still want to visit the use expression so that we always end up 3060 // picking the innermost, most relevant use under the cursor. 3061 self.visit_typed_expr(&use_.call); 3062 } 3063} 3064 3065/// Builder for code action to apply the convert to use action. 3066/// 3067pub struct ConvertToUse<'a> { 3068 module: &'a Module, 3069 params: &'a CodeActionParams, 3070 edits: TextEdits<'a>, 3071 selected_call: Option<CallLocations>, 3072} 3073 3074/// All the locations we'll need to transform a function call into a use 3075/// expression. 3076/// 3077struct CallLocations { 3078 call_span: SrcSpan, 3079 called_function_span: SrcSpan, 3080 callback_arguments_span: Option<SrcSpan>, 3081 arg_before_callback_span: Option<SrcSpan>, 3082 callback_body_span: SrcSpan, 3083} 3084 3085impl<'a> ConvertToUse<'a> { 3086 pub fn new( 3087 module: &'a Module, 3088 line_numbers: &'a LineNumbers, 3089 params: &'a CodeActionParams, 3090 ) -> Self { 3091 Self { 3092 module, 3093 params, 3094 edits: TextEdits::new(line_numbers), 3095 selected_call: None, 3096 } 3097 } 3098 3099 pub fn code_actions(mut self) -> Vec<CodeAction> { 3100 self.visit_typed_module(&self.module.ast); 3101 3102 let Some(CallLocations { 3103 call_span, 3104 called_function_span, 3105 callback_arguments_span, 3106 arg_before_callback_span, 3107 callback_body_span, 3108 }) = self.selected_call 3109 else { 3110 return vec![]; 3111 }; 3112 3113 // This is the nesting level of the `use` keyword we've inserted, we 3114 // want to move the entire body of the anonymous function to this level. 3115 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character; 3116 let indentation = " ".repeat(use_nesting_level as usize); 3117 3118 // First we move the callback arguments to the left hand side of the 3119 // call and add the `use` keyword. 3120 let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span { 3121 let arguments_start = arguments_location.start as usize; 3122 let arguments_end = arguments_location.end as usize; 3123 let arguments_text = self 3124 .module 3125 .code 3126 .get(arguments_start..arguments_end) 3127 .expect("fn args"); 3128 format!("use {arguments_text} <- ") 3129 } else { 3130 "use <- ".into() 3131 }; 3132 3133 self.edits.insert(call_span.start, left_hand_side_text); 3134 3135 match arg_before_callback_span { 3136 // If the function call has no other arguments besides the callback then 3137 // we just have to remove the `fn(...) {` part. 3138 // 3139 // wibble(fn(...) { ... }) 3140 // ^^^^^^^^^^ This goes from the end of the called function 3141 // To the start of the first thing in the anonymous 3142 // function's body. 3143 // 3144 None => self.edits.replace( 3145 SrcSpan::new(called_function_span.end, callback_body_span.start), 3146 format!("\n{indentation}"), 3147 ), 3148 // If it has other arguments we'll have to remove those and add a closed 3149 // parentheses too: 3150 // 3151 // wibble(1, 2, fn(...) { ... }) 3152 // ^^^^^^^^^^^ We have to replace this with a `)`, it 3153 // goes from the end of the second-to-last 3154 // argument to the start of the first thing 3155 // in the anonymous function's body. 3156 // 3157 Some(arg_before_callback) => self.edits.replace( 3158 SrcSpan::new(arg_before_callback.end, callback_body_span.start), 3159 format!(")\n{indentation}"), 3160 ), 3161 }; 3162 3163 // Then we have to remove two spaces of indentation from each line of 3164 // the callback function's body. 3165 let body_range = self.edits.src_span_to_lsp_range(callback_body_span); 3166 for line in body_range.start.line + 1..=body_range.end.line { 3167 self.edits.delete_range(Range::new( 3168 Position { line, character: 0 }, 3169 Position { line, character: 2 }, 3170 )) 3171 } 3172 3173 // Then we have to remove the anonymous fn closing `}` and the call's 3174 // closing `)`. 3175 self.edits 3176 .delete(SrcSpan::new(callback_body_span.end, call_span.end)); 3177 3178 let mut action = Vec::with_capacity(1); 3179 CodeActionBuilder::new("Convert to `use`") 3180 .kind(CodeActionKind::RefactorRewrite) 3181 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3182 .preferred(false) 3183 .push_to(&mut action); 3184 action 3185 } 3186} 3187 3188impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> { 3189 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 3190 // The cursor has to be inside the last statement of the function to 3191 // offer the code action. 3192 if let Some(last) = &fun.body.last() 3193 && within( 3194 self.params.range, 3195 self.edits.src_span_to_lsp_range(last.location()), 3196 ) 3197 && let Some(call_data) = turn_statement_into_use(last) 3198 { 3199 self.selected_call = Some(call_data); 3200 } 3201 3202 ast::visit::visit_typed_function(self, fun) 3203 } 3204 3205 fn visit_typed_expr_fn( 3206 &mut self, 3207 location: &'ast SrcSpan, 3208 type_: &'ast Arc<Type>, 3209 kind: &'ast FunctionLiteralKind, 3210 arguments: &'ast [TypedArg], 3211 body: &'ast Vec1<TypedStatement>, 3212 return_annotation: &'ast Option<ast::TypeAst>, 3213 ) { 3214 // The cursor has to be inside the last statement of the body to 3215 // offer the code action. 3216 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location()); 3217 if within(self.params.range, last_statement_range) 3218 && let Some(call_data) = turn_statement_into_use(body.last()) 3219 { 3220 self.selected_call = Some(call_data); 3221 } 3222 3223 ast::visit::visit_typed_expr_fn( 3224 self, 3225 location, 3226 type_, 3227 kind, 3228 arguments, 3229 body, 3230 return_annotation, 3231 ); 3232 } 3233 3234 fn visit_typed_expr_block( 3235 &mut self, 3236 location: &'ast SrcSpan, 3237 statements: &'ast [TypedStatement], 3238 ) { 3239 let Some(last_statement) = statements.last() else { 3240 return; 3241 }; 3242 3243 // The cursor has to be inside the last statement of the block to offer 3244 // the code action. 3245 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location()); 3246 if within(self.params.range, statement_range) { 3247 // Only the last statement of a block can be turned into a use! 3248 if let Some(selected_call) = turn_statement_into_use(last_statement) { 3249 self.selected_call = Some(selected_call) 3250 } 3251 } 3252 3253 ast::visit::visit_typed_expr_block(self, location, statements); 3254 } 3255} 3256 3257fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> { 3258 match statement { 3259 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None, 3260 ast::Statement::Expression(expression) => turn_expression_into_use(expression), 3261 } 3262} 3263 3264fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> { 3265 let TypedExpr::Call { 3266 arguments, 3267 location: call_span, 3268 fun: called_function, 3269 .. 3270 } = expr 3271 else { 3272 return None; 3273 }; 3274 3275 // The function arguments in the ast are reordered using function's field map. 3276 // This means that in the `args` array they might not appear in the same order 3277 // in which they are written by the user. Since the rest of the code relies 3278 // on their order in the written code we first have to sort them by their 3279 // source position. 3280 let arguments = arguments 3281 .iter() 3282 .sorted_by_key(|argument| argument.location.start) 3283 .collect_vec(); 3284 3285 let CallArg { 3286 value: last_arg, 3287 implicit: None, 3288 .. 3289 } = arguments.last()? 3290 else { 3291 return None; 3292 }; 3293 3294 let TypedExpr::Fn { 3295 arguments: callback_arguments, 3296 body, 3297 .. 3298 } = last_arg 3299 else { 3300 return None; 3301 }; 3302 3303 let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) { 3304 (Some(first), Some(last)) => Some(first.location.merge(&last.location)), 3305 _ => None, 3306 }; 3307 3308 let arg_before_callback_span = if arguments.len() >= 2 { 3309 arguments 3310 .get(arguments.len() - 2) 3311 .map(|call_arg| call_arg.location) 3312 } else { 3313 None 3314 }; 3315 3316 let callback_body_span = body.first().location().merge(&body.last().last_location()); 3317 3318 Some(CallLocations { 3319 call_span: *call_span, 3320 called_function_span: called_function.location(), 3321 callback_arguments_span, 3322 arg_before_callback_span, 3323 callback_body_span, 3324 }) 3325} 3326 3327/// Builder for code action to extract expression into a variable. 3328/// The action will wrap the expression in a block if needed in the appropriate scope. 3329/// 3330/// For using the code action on the following selection: 3331/// 3332/// ```gleam 3333/// fn void() { 3334/// case result { 3335/// Ok(value) -> 2 * value + 1 3336/// // ^^^^^^^^^ 3337/// Error(_) -> panic 3338/// } 3339/// } 3340/// ``` 3341/// 3342/// Will result: 3343/// 3344/// ```gleam 3345/// fn void() { 3346/// case result { 3347/// Ok(value) -> { 3348/// let int = 2 * value 3349/// int + 1 3350/// } 3351/// Error(_) -> panic 3352/// } 3353/// } 3354/// ``` 3355pub struct ExtractVariable<'a> { 3356 module: &'a Module, 3357 params: &'a CodeActionParams, 3358 edits: TextEdits<'a>, 3359 position: Option<ExtractVariablePosition>, 3360 selected_expression: Option<ExtractedToVariable>, 3361 statement_before_selected_expression: Option<SrcSpan>, 3362 latest_statement: Option<SrcSpan>, 3363 to_be_wrapped: bool, 3364 name_generator: NameGenerator, 3365} 3366 3367pub enum ExtractedToVariable { 3368 Expression { location: SrcSpan, name: EcoString }, 3369 StartOfPipeline { location: SrcSpan, name: EcoString }, 3370} 3371 3372/// The Position of the selected code 3373#[derive(PartialEq, Eq, Copy, Clone, Debug)] 3374enum ExtractVariablePosition { 3375 InsideCaptureBody, 3376 /// Full statements (i.e. assignments, `use`s, and simple expressions). 3377 TopLevelStatement, 3378 /// The call on the right hand side of a pipe `|>`. 3379 PipelineCall, 3380 /// The right hand side of the `->` in a case expression. 3381 InsideCaseClause, 3382 /// A call argument. This can also be a `use` callback. 3383 CallArg, 3384} 3385 3386impl<'a> ExtractVariable<'a> { 3387 pub fn new( 3388 module: &'a Module, 3389 line_numbers: &'a LineNumbers, 3390 params: &'a CodeActionParams, 3391 ) -> Self { 3392 Self { 3393 module, 3394 params, 3395 edits: TextEdits::new(line_numbers), 3396 position: None, 3397 selected_expression: None, 3398 statement_before_selected_expression: None, 3399 latest_statement: None, 3400 to_be_wrapped: false, 3401 name_generator: NameGenerator::new(), 3402 } 3403 } 3404 3405 pub fn code_actions(mut self) -> Vec<CodeAction> { 3406 self.visit_typed_module(&self.module.ast); 3407 3408 let (Some(extracted_value), Some(insert_location)) = ( 3409 self.selected_expression, 3410 self.statement_before_selected_expression, 3411 ) else { 3412 return vec![]; 3413 }; 3414 3415 let variable_name = match &extracted_value { 3416 ExtractedToVariable::Expression { name, .. } 3417 | ExtractedToVariable::StartOfPipeline { name, .. } => name, 3418 }; 3419 let expression_span = match &extracted_value { 3420 ExtractedToVariable::Expression { location, .. } 3421 | ExtractedToVariable::StartOfPipeline { location, .. } => location, 3422 }; 3423 3424 let content = self 3425 .module 3426 .code 3427 .get(expression_span.start as usize..expression_span.end as usize) 3428 .expect("selected expression"); 3429 3430 let range = self.edits.src_span_to_lsp_range(insert_location); 3431 3432 let indent_size = 3433 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 3434 3435 let mut indent = " ".repeat(indent_size); 3436 3437 // We insert the variable declaration 3438 // Wrap in a block if needed 3439 let mut insertion = match extracted_value { 3440 ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"), 3441 ExtractedToVariable::StartOfPipeline { .. } => { 3442 format!("let {variable_name} =\n{indent} {content}\n") 3443 } 3444 }; 3445 3446 if self.to_be_wrapped { 3447 let line_end = self 3448 .edits 3449 .line_numbers 3450 .line_starts 3451 .get((range.end.line + 1) as usize) 3452 .expect("Line number should be valid"); 3453 3454 self.edits.insert(*line_end, format!("{indent}}}\n")); 3455 indent += " "; 3456 insertion = format!("{{\n{indent}{insertion}"); 3457 }; 3458 3459 self.edits 3460 .insert(insert_location.start, format!("{insertion}\n{indent}")); 3461 3462 self.edits 3463 .replace(*expression_span, String::from(variable_name)); 3464 3465 let mut action = Vec::with_capacity(1); 3466 CodeActionBuilder::new("Extract variable") 3467 .kind(CodeActionKind::RefactorExtract) 3468 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3469 .preferred(false) 3470 .push_to(&mut action); 3471 action 3472 } 3473 3474 fn inside_new_scope<F>(&mut self, fun: F) 3475 where 3476 F: Fn(&mut Self), 3477 { 3478 let names = self.name_generator.clone(); 3479 fun(self); 3480 self.name_generator = names; 3481 } 3482 3483 fn generate_candidate_name(&mut self, type_: Arc<Type>) -> EcoString { 3484 let name = self.name_generator.generate_name_from_type(&type_); 3485 // When the generator generates a name, it rightfully inserts it in the 3486 // current scope so that it cannot be used again. 3487 // However, in our case it's not what we want: the name we're generating 3488 // is a candidate for what we might use for a single variable, at the 3489 // end of the whole process we're gonna pick just a single name. 3490 // If we were to insert this name into scope, that means that all the 3491 // other candidates would have a suffix `int_2`, `int_3`, ... 3492 // When we finally pick one it would be strange if the picked name had 3493 // a suffix but no `int`, `int_1`, ... were in scope! 3494 let _ = self.name_generator.used_names.remove(&name); 3495 name 3496 } 3497 3498 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F) 3499 where 3500 F: Fn(&mut Self), 3501 { 3502 self.at_optional_position(Some(position), fun); 3503 } 3504 3505 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F) 3506 where 3507 F: Fn(&mut Self), 3508 { 3509 let previous_statement = self.latest_statement; 3510 let previous_position = self.position; 3511 self.position = position; 3512 fun(self); 3513 self.position = previous_position; 3514 self.latest_statement = previous_statement; 3515 } 3516} 3517 3518impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> { 3519 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 3520 let range = self.edits.src_span_to_lsp_range(statement.location()); 3521 if !within(self.params.range, range) { 3522 self.latest_statement = Some(statement.location()); 3523 ast::visit::visit_typed_statement(self, statement); 3524 return; 3525 } 3526 3527 match self.position { 3528 // A capture body is comprised of just a single expression statement 3529 // that is inserted by the compiler, we don't really want to put 3530 // anything before that; so in this case we avoid tracking it. 3531 Some(ExtractVariablePosition::InsideCaptureBody) => {} 3532 Some(ExtractVariablePosition::PipelineCall) => { 3533 // Insert above the pipeline start 3534 self.latest_statement = Some(statement.location()); 3535 } 3536 _ => { 3537 // Insert below the previous statement 3538 self.latest_statement = Some(statement.location()); 3539 self.statement_before_selected_expression = self.latest_statement; 3540 } 3541 } 3542 3543 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3544 ast::visit::visit_typed_statement(this, statement); 3545 }); 3546 } 3547 3548 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 3549 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 3550 start: fun.location.start, 3551 end: fun.end_position, 3552 }); 3553 3554 if !within(self.params.range, fun_range) { 3555 return; 3556 } 3557 3558 // We reset the name generator to purge the variable names from other 3559 // scopes. 3560 // We then reserve the names already used by top level definitions. 3561 self.name_generator = NameGenerator::new(); 3562 self.name_generator 3563 .reserve_module_value_names(&self.module.ast.definitions); 3564 3565 ast::visit::visit_typed_function(self, fun); 3566 } 3567 3568 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 3569 if let Pattern::Variable { name, .. } = &assignment.pattern { 3570 self.name_generator.add_used_name(name.clone()) 3571 }; 3572 ast::visit::visit_typed_assignment(self, assignment); 3573 } 3574 3575 fn visit_typed_expr_pipeline( 3576 &mut self, 3577 location: &'ast SrcSpan, 3578 first_value: &'ast TypedPipelineAssignment, 3579 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 3580 finally: &'ast TypedExpr, 3581 finally_kind: &'ast PipelineAssignmentKind, 3582 ) { 3583 let expr_range = self.edits.src_span_to_lsp_range(*location); 3584 if !within(self.params.range, expr_range) { 3585 ast::visit::visit_typed_expr_pipeline( 3586 self, 3587 location, 3588 first_value, 3589 assignments, 3590 finally, 3591 finally_kind, 3592 ); 3593 return; 3594 }; 3595 3596 // Visiting a pipeline requires a bit of care, we don't want to extract 3597 // intermediate steps as variables (those are function calls)! 3598 // So we start by checking if the selected section contains multiple 3599 // steps including the first one: in that case we can extract all those 3600 // steps as a single variable. 3601 let selection = self.edits.lsp_range_to_src_span(self.params.range); 3602 let is_inside_first_step = first_value.location.contains(selection.start); 3603 let last_included_step = assignments.iter().find_map(|(assignment, _kind)| { 3604 if assignment.location.contains(selection.end) { 3605 Some(assignment) 3606 } else { 3607 None 3608 } 3609 }); 3610 3611 if let Some(last) = last_included_step 3612 && is_inside_first_step 3613 { 3614 let location = first_value.location.merge(&last.value.location()); 3615 self.selected_expression = Some(ExtractedToVariable::StartOfPipeline { 3616 location, 3617 name: self.generate_candidate_name(last.type_()), 3618 }); 3619 return; 3620 } 3621 3622 // Otherwise we visit all the steps individually to see if there's 3623 // something _inside_ a step that might be extracted. 3624 let all_assignments = 3625 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment)); 3626 for assignment in all_assignments { 3627 // With the position as "PipelineCall" we know we can't extract the 3628 // pipeline step itself! 3629 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 3630 this.visit_typed_pipeline_assignment(assignment); 3631 }); 3632 } 3633 3634 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 3635 this.visit_typed_expr(finally) 3636 }); 3637 } 3638 3639 fn visit_typed_expr_call( 3640 &mut self, 3641 location: &'ast SrcSpan, 3642 type_: &'ast Arc<Type>, 3643 fun: &'ast TypedExpr, 3644 arguments: &'ast [TypedCallArg], 3645 open_parenthesis: &'ast Option<u32>, 3646 ) { 3647 // Function calls need some extra care. If we're inspecting a record 3648 // call like this one: `Wibble(wobble, woo)` and the cursor is over the 3649 // constructor itself we never want to allow extracting it, or it would 3650 // result in the following code: 3651 // 3652 // ```gleam 3653 // Wibble(wobble, woo) 3654 // // ^^ Cursor here 3655 // 3656 // let wibble = Wibble 3657 // wibble(wobble, woo) 3658 // // That's a bit silly! 3659 // ``` 3660 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 3661 if within(self.params.range, fun_range) && fun.is_record_constructor_function() { 3662 return; 3663 } 3664 3665 // Otherwise we just keep visiting like usual, no special handling is 3666 // required. 3667 ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis); 3668 } 3669 3670 fn visit_typed_expr_record_update( 3671 &mut self, 3672 location: &'ast SrcSpan, 3673 spread_start: &'ast u32, 3674 type_: &'ast Arc<Type>, 3675 updated_record: &'ast TypedExpr, 3676 updated_record_assigned_name: &'ast Option<EcoString>, 3677 constructor: &'ast TypedExpr, 3678 arguments: &'ast [TypedCallArg], 3679 ) { 3680 // Record updates need some extra care. If we're inspecting a record 3681 // update like this one: `Wibble(..wobble, woo)` and the cursor is over 3682 // the constructor itself we never want to allow extracting it, or it 3683 // would result in the following code: 3684 // 3685 // ```gleam 3686 // Wibble(..wobble, woo) 3687 // // ^^ Cursor here 3688 // 3689 // let wibble = Wibble 3690 // wibble(..wobble, woo) 3691 // // That's a bit silly! 3692 // ``` 3693 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location()); 3694 if within(self.params.range, constructor_range) 3695 && constructor.is_record_constructor_function() 3696 { 3697 return; 3698 } 3699 3700 // Otherwise we just keep visiting like usual, no special handling is 3701 // required. 3702 ast::visit::visit_typed_expr_record_update( 3703 self, 3704 location, 3705 spread_start, 3706 type_, 3707 updated_record, 3708 updated_record_assigned_name, 3709 constructor, 3710 arguments, 3711 ); 3712 } 3713 3714 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 3715 let expr_location = expr.location(); 3716 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 3717 if !within(self.params.range, expr_range) { 3718 ast::visit::visit_typed_expr(self, expr); 3719 return; 3720 } 3721 3722 // If the expression is a top level statement we don't want to extract 3723 // it into a variable. It would mean we would turn this: 3724 // 3725 // ```gleam 3726 // pub fn main() { 3727 // let wibble = 1 3728 // // ^ cursor here 3729 // } 3730 // 3731 // // into: 3732 // 3733 // pub fn main() { 3734 // let int = 1 3735 // let wibble = int 3736 // } 3737 // ``` 3738 // 3739 // Not all that useful! 3740 // 3741 match self.position { 3742 Some( 3743 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall, 3744 ) => { 3745 self.at_optional_position(None, |this| { 3746 ast::visit::visit_typed_expr(this, expr); 3747 }); 3748 return; 3749 } 3750 Some( 3751 ExtractVariablePosition::InsideCaptureBody 3752 | ExtractVariablePosition::InsideCaseClause 3753 | ExtractVariablePosition::CallArg, 3754 ) 3755 | None => {} 3756 } 3757 3758 match expr { 3759 TypedExpr::Fn { 3760 kind: FunctionLiteralKind::Anonymous { .. }, 3761 .. 3762 } => { 3763 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3764 ast::visit::visit_typed_expr(this, expr); 3765 }); 3766 return; 3767 } 3768 3769 TypedExpr::Int { location, .. } 3770 | TypedExpr::Float { location, .. } 3771 | TypedExpr::String { location, .. } 3772 | TypedExpr::Pipeline { location, .. } 3773 | TypedExpr::Fn { location, .. } 3774 | TypedExpr::Todo { location, .. } 3775 | TypedExpr::List { location, .. } 3776 | TypedExpr::Call { location, .. } 3777 | TypedExpr::BinOp { location, .. } 3778 | TypedExpr::Case { location, .. } 3779 | TypedExpr::RecordAccess { location, .. } 3780 | TypedExpr::Tuple { location, .. } 3781 | TypedExpr::TupleIndex { location, .. } 3782 | TypedExpr::BitArray { location, .. } 3783 | TypedExpr::RecordUpdate { location, .. } 3784 | TypedExpr::NegateBool { location, .. } 3785 | TypedExpr::NegateInt { location, .. } 3786 // It generally makes no sense to extract variables, the only 3787 // exception is for records with no fields (like `True` and 3788 // `False`): in the AST those are `Var`s with a `Record` 3789 // constructor. Extracting them is allowed! 3790 | TypedExpr::Var { 3791 constructor: 3792 ValueConstructor { 3793 variant: type_::ValueConstructorVariant::Record { .. }, 3794 .. 3795 }, 3796 location, 3797 .. 3798 } => { 3799 if let Some(ExtractVariablePosition::CallArg) = self.position { 3800 // Don't update latest statement, we don't want to insert the extracted 3801 // variable inside the parenthesis where the call argument is located. 3802 } else { 3803 self.statement_before_selected_expression = self.latest_statement; 3804 }; 3805 self.selected_expression = Some(ExtractedToVariable::Expression { 3806 location: *location, 3807 name: self.generate_candidate_name(expr.type_()), 3808 }); 3809 } 3810 3811 // Expressions that don't make sense to extract 3812 TypedExpr::Panic { .. } 3813 | TypedExpr::Echo { .. } 3814 | TypedExpr::Block { .. } 3815 | TypedExpr::ModuleSelect { .. } 3816 | TypedExpr::Invalid { .. } 3817 | TypedExpr::PositionalAccess { .. } 3818 | TypedExpr::Var { .. } => (), 3819 } 3820 3821 ast::visit::visit_typed_expr(self, expr); 3822 } 3823 3824 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3825 let range = self.edits.src_span_to_lsp_range(use_.call.location()); 3826 if !within(self.params.range, range) { 3827 ast::visit::visit_typed_use(self, use_); 3828 return; 3829 } 3830 3831 // Insert code under the `use` 3832 self.statement_before_selected_expression = Some(use_.call.location()); 3833 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3834 ast::visit::visit_typed_use(this, use_); 3835 }); 3836 } 3837 3838 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 3839 let range = self.edits.src_span_to_lsp_range(clause.location()); 3840 if !within(self.params.range, range) { 3841 self.inside_new_scope(|this| { 3842 ast::visit::visit_typed_clause(this, clause); 3843 }); 3844 return; 3845 } 3846 3847 // Insert code after the `->` 3848 self.latest_statement = Some(clause.then.location()); 3849 self.to_be_wrapped = true; 3850 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| { 3851 this.inside_new_scope(|this| { 3852 ast::visit::visit_typed_clause(this, clause); 3853 }); 3854 }); 3855 } 3856 3857 fn visit_typed_expr_block( 3858 &mut self, 3859 location: &'ast SrcSpan, 3860 statements: &'ast [TypedStatement], 3861 ) { 3862 let range = self.edits.src_span_to_lsp_range(*location); 3863 if !within(self.params.range, range) { 3864 self.inside_new_scope(|this| { 3865 ast::visit::visit_typed_expr_block(this, location, statements); 3866 }); 3867 return; 3868 } 3869 3870 // Don't extract block as variable 3871 let mut position = self.position; 3872 if let Some(ExtractVariablePosition::InsideCaseClause) = position { 3873 position = None; 3874 self.to_be_wrapped = false; 3875 } 3876 3877 self.at_optional_position(position, |this| { 3878 this.inside_new_scope(|this| { 3879 ast::visit::visit_typed_expr_block(this, location, statements); 3880 }); 3881 }); 3882 } 3883 3884 fn visit_typed_expr_fn( 3885 &mut self, 3886 location: &'ast SrcSpan, 3887 type_: &'ast Arc<Type>, 3888 kind: &'ast FunctionLiteralKind, 3889 arguments: &'ast [TypedArg], 3890 body: &'ast Vec1<TypedStatement>, 3891 return_annotation: &'ast Option<ast::TypeAst>, 3892 ) { 3893 let range = self.edits.src_span_to_lsp_range(*location); 3894 if !within(self.params.range, range) { 3895 self.inside_new_scope(|this| { 3896 ast::visit::visit_typed_expr_fn( 3897 this, 3898 location, 3899 type_, 3900 kind, 3901 arguments, 3902 body, 3903 return_annotation, 3904 ); 3905 }); 3906 return; 3907 } 3908 3909 let position = match kind { 3910 // If a fn is a capture `int.wibble(1, _)` its body will consist of 3911 // just a single expression statement. When visiting we must record 3912 // we're inside a capture body. 3913 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody), 3914 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement), 3915 FunctionLiteralKind::Anonymous { .. } => self.position, 3916 }; 3917 3918 self.at_optional_position(position, |this| { 3919 this.inside_new_scope(|this| { 3920 ast::visit::visit_typed_expr_fn( 3921 this, 3922 location, 3923 type_, 3924 kind, 3925 arguments, 3926 body, 3927 return_annotation, 3928 ); 3929 }); 3930 }); 3931 } 3932 3933 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 3934 let range = self.edits.src_span_to_lsp_range(arg.location); 3935 if !within(self.params.range, range) { 3936 ast::visit::visit_typed_call_arg(self, arg); 3937 return; 3938 } 3939 3940 // An implicit record update arg in inserted by the compiler, we don't 3941 // want folks to interact with this since it doesn't translate to 3942 // anything in the source code despite having a default position. 3943 if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit { 3944 return; 3945 } 3946 3947 let position = if arg.is_use_implicit_callback() { 3948 Some(ExtractVariablePosition::TopLevelStatement) 3949 } else { 3950 Some(ExtractVariablePosition::CallArg) 3951 }; 3952 3953 self.at_optional_position(position, |this| { 3954 ast::visit::visit_typed_call_arg(this, arg); 3955 }); 3956 } 3957 3958 // We don't want to offer the action if the cursor is over some invalid 3959 // piece of code. 3960 fn visit_typed_expr_invalid( 3961 &mut self, 3962 location: &'ast SrcSpan, 3963 _type_: &'ast Arc<Type>, 3964 _extra_information: &'ast Option<InvalidExpression>, 3965 ) { 3966 let invalid_range = self.edits.src_span_to_lsp_range(*location); 3967 if within(self.params.range, invalid_range) { 3968 self.selected_expression = None; 3969 } 3970 } 3971} 3972 3973/// Builder for code action to convert a literal use into a const. 3974/// 3975/// For using the code action on each of the following lines: 3976/// 3977/// ```gleam 3978/// fn void() { 3979/// let var = [1, 2, 3] 3980/// let res = function("Statement", var) 3981/// } 3982/// ``` 3983/// 3984/// Both value literals will become: 3985/// 3986/// ```gleam 3987/// const var = [1, 2, 3] 3988/// const string = "Statement" 3989/// 3990/// fn void() { 3991/// let res = function(string, var) 3992/// } 3993/// ``` 3994pub struct ExtractConstant<'a> { 3995 module: &'a Module, 3996 params: &'a CodeActionParams, 3997 edits: TextEdits<'a>, 3998 /// The whole selected expression 3999 selected_expression: Option<SrcSpan>, 4000 /// The location of the start of the function containing the expression. 4001 /// It includes function's documentation as well 4002 container_function_start: Option<u32>, 4003 /// The variant of the extractable expression being extracted (if any) 4004 variant_of_extractable: Option<ExtractableToConstant>, 4005 /// The name of the newly created constant 4006 name_to_use: Option<EcoString>, 4007 /// The right hand side expression of the newly created constant 4008 value_to_use: Option<EcoString>, 4009} 4010 4011/// Used when an expression can be extracted to a constant 4012enum ExtractableToConstant { 4013 /// Used for collections and operator uses. This means that elements 4014 /// inside, are also extractable as constants. 4015 ComposedValue, 4016 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings 4017 /// and type variants (not records). 4018 SingleValue, 4019 /// Used for whole variable assignments. If the right hand side of the 4020 /// expression can be extracted, the whole expression extracted and use the 4021 /// local variable as a constant. 4022 Assignment, 4023} 4024 4025fn can_be_constant( 4026 module: &Module, 4027 expr: &TypedExpr, 4028 module_constants: Option<&HashSet<&EcoString>>, 4029) -> bool { 4030 // We pass the `module_constants` on recursion to not compute them each time 4031 let module_constants = match module_constants { 4032 Some(module_constants) => module_constants, 4033 None => &module 4034 .ast 4035 .definitions 4036 .constants 4037 .iter() 4038 .map(|constant| &constant.name) 4039 .collect(), 4040 }; 4041 4042 match expr { 4043 // Attempt to extract whole list as long as it's comprised of only literals 4044 TypedExpr::List { elements, tail, .. } => { 4045 elements 4046 .iter() 4047 .all(|element| can_be_constant(module, element, Some(module_constants))) 4048 && tail.is_none() 4049 } 4050 4051 // Attempt to extract whole bit array as long as it's made up of literals 4052 TypedExpr::BitArray { segments, .. } => { 4053 segments 4054 .iter() 4055 .all(|segment| can_be_constant(module, &segment.value, Some(module_constants))) 4056 && segments.iter().all(|segment| { 4057 segment.options.iter().all(|option| match option { 4058 ast::BitArrayOption::Size { value, .. } => { 4059 can_be_constant(module, value, Some(module_constants)) 4060 } 4061 4062 ast::BitArrayOption::Bytes { .. } 4063 | ast::BitArrayOption::Int { .. } 4064 | ast::BitArrayOption::Float { .. } 4065 | ast::BitArrayOption::Bits { .. } 4066 | ast::BitArrayOption::Utf8 { .. } 4067 | ast::BitArrayOption::Utf16 { .. } 4068 | ast::BitArrayOption::Utf32 { .. } 4069 | ast::BitArrayOption::Utf8Codepoint { .. } 4070 | ast::BitArrayOption::Utf16Codepoint { .. } 4071 | ast::BitArrayOption::Utf32Codepoint { .. } 4072 | ast::BitArrayOption::Signed { .. } 4073 | ast::BitArrayOption::Unsigned { .. } 4074 | ast::BitArrayOption::Big { .. } 4075 | ast::BitArrayOption::Little { .. } 4076 | ast::BitArrayOption::Native { .. } 4077 | ast::BitArrayOption::Unit { .. } => true, 4078 }) 4079 }) 4080 } 4081 4082 // Attempt to extract whole tuple as long as it's comprised of only literals 4083 TypedExpr::Tuple { elements, .. } => elements 4084 .iter() 4085 .all(|element| can_be_constant(module, element, Some(module_constants))), 4086 4087 // Extract literals directly 4088 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true, 4089 4090 // Extract non-record types directly 4091 TypedExpr::Var { 4092 constructor, name, .. 4093 } => { 4094 matches!( 4095 constructor.variant, 4096 type_::ValueConstructorVariant::Record { arity: 0, .. } 4097 ) || module_constants.contains(name) 4098 } 4099 4100 // Extract record types as long as arguments can be constant 4101 TypedExpr::Call { arguments, fun, .. } => { 4102 fun.is_record_literal() 4103 && arguments 4104 .iter() 4105 .all(|arg| can_be_constant(module, &arg.value, Some(module_constants))) 4106 } 4107 4108 // Extract concat binary operation if both sides can be constants 4109 TypedExpr::BinOp { 4110 operator, 4111 left, 4112 right, 4113 .. 4114 } => { 4115 matches!(operator, ast::BinOp::Concatenate) 4116 && can_be_constant(module, left, Some(module_constants)) 4117 && can_be_constant(module, right, Some(module_constants)) 4118 } 4119 4120 TypedExpr::Block { .. } 4121 | TypedExpr::Pipeline { .. } 4122 | TypedExpr::Fn { .. } 4123 | TypedExpr::Case { .. } 4124 | TypedExpr::RecordAccess { .. } 4125 | TypedExpr::PositionalAccess { .. } 4126 | TypedExpr::ModuleSelect { .. } 4127 | TypedExpr::TupleIndex { .. } 4128 | TypedExpr::Todo { .. } 4129 | TypedExpr::Panic { .. } 4130 | TypedExpr::Echo { .. } 4131 | TypedExpr::RecordUpdate { .. } 4132 | TypedExpr::NegateBool { .. } 4133 | TypedExpr::NegateInt { .. } 4134 | TypedExpr::Invalid { .. } => false, 4135 } 4136} 4137 4138/// Takes the list of already existing constants and functions and creates a 4139/// name that doesn't conflict with them 4140/// 4141fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString { 4142 let mut name_generator = NameGenerator::new(); 4143 name_generator.reserve_module_value_names(&module.ast.definitions); 4144 name_generator.generate_name_from_type(&expr.type_()) 4145} 4146 4147/// Converts the source start position of a documentation comment's contents into 4148/// the position of the leading slash in its marker ('///'). 4149fn get_doc_marker_position(content_pos: u32) -> u32 { 4150 content_pos.saturating_sub(3) 4151} 4152 4153impl<'a> ExtractConstant<'a> { 4154 pub fn new( 4155 module: &'a Module, 4156 line_numbers: &'a LineNumbers, 4157 params: &'a CodeActionParams, 4158 ) -> Self { 4159 Self { 4160 module, 4161 params, 4162 edits: TextEdits::new(line_numbers), 4163 selected_expression: None, 4164 container_function_start: None, 4165 variant_of_extractable: None, 4166 name_to_use: None, 4167 value_to_use: None, 4168 } 4169 } 4170 4171 pub fn code_actions(mut self) -> Vec<CodeAction> { 4172 self.visit_typed_module(&self.module.ast); 4173 4174 let ( 4175 Some(expr_span), 4176 Some(function_start), 4177 Some(type_of_extractable), 4178 Some(new_const_name), 4179 Some(const_value), 4180 ) = ( 4181 self.selected_expression, 4182 self.container_function_start, 4183 self.variant_of_extractable, 4184 self.name_to_use, 4185 self.value_to_use, 4186 ) 4187 else { 4188 return vec![]; 4189 }; 4190 4191 // We insert the constant declaration 4192 self.edits.insert( 4193 function_start, 4194 format!("const {new_const_name} = {const_value}\n\n"), 4195 ); 4196 4197 // We remove or replace the selected expression 4198 match type_of_extractable { 4199 // The whole expression is deleted for assignments 4200 ExtractableToConstant::Assignment => { 4201 let range = self 4202 .edits 4203 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 4204 4205 let indent_size = 4206 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 4207 4208 let expr_span_with_new_line = SrcSpan { 4209 // We remove leading indentation + 1 to remove the newline with it 4210 start: expr_span.start - (indent_size as u32 + 1), 4211 end: expr_span.end, 4212 }; 4213 self.edits.delete(expr_span_with_new_line); 4214 } 4215 4216 // Only right hand side is replaced for collection or values 4217 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => { 4218 self.edits.replace(expr_span, String::from(new_const_name)); 4219 } 4220 } 4221 4222 let mut action = Vec::with_capacity(1); 4223 CodeActionBuilder::new("Extract constant") 4224 .kind(CodeActionKind::RefactorExtract) 4225 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4226 .preferred(false) 4227 .push_to(&mut action); 4228 action 4229 } 4230} 4231 4232impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> { 4233 /// To get the position of the function containing the value or assignment 4234 /// to extract 4235 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 4236 let fun_location = fun.location; 4237 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 4238 start: fun_location.start, 4239 end: fun.end_position, 4240 }); 4241 4242 if !within(self.params.range, fun_range) { 4243 return; 4244 } 4245 4246 // Here we need to get position of the function, starting from the leading slash in the 4247 // documentation comment's marker ('///'), not from comment's content (of which 4248 // we have the position), so we must convert the content start position 4249 // to the leading slash's position. 4250 self.container_function_start = Some( 4251 fun.documentation 4252 .as_ref() 4253 .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) 4254 .unwrap_or(fun_location.start), 4255 ); 4256 4257 ast::visit::visit_typed_function(self, fun); 4258 } 4259 4260 /// To extract the whole assignment 4261 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 4262 let expr_location = assignment.location; 4263 4264 // We only offer this code action for extracting the whole assignment 4265 // between `let` and `=`. 4266 let pattern_location = assignment.pattern.location(); 4267 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 4268 let code_action_range = self.edits.src_span_to_lsp_range(location); 4269 4270 if !within(self.params.range, code_action_range) { 4271 ast::visit::visit_typed_assignment(self, assignment); 4272 return; 4273 } 4274 4275 // Has to be variable because patterns can't be constants. 4276 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None) 4277 { 4278 self.variant_of_extractable = Some(ExtractableToConstant::Assignment); 4279 self.selected_expression = Some(expr_location); 4280 self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern { 4281 Some(name.clone()) 4282 } else { 4283 None 4284 }; 4285 self.value_to_use = Some(EcoString::from( 4286 self.module 4287 .code 4288 .get( 4289 (assignment.value.location().start as usize) 4290 ..(assignment.location.end as usize), 4291 ) 4292 .expect("selected expression"), 4293 )); 4294 } 4295 } 4296 4297 /// To extract only the literal 4298 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 4299 let expr_location = expr.location(); 4300 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 4301 4302 if !within(self.params.range, expr_range) { 4303 ast::visit::visit_typed_expr(self, expr); 4304 return; 4305 } 4306 4307 // Keep going down recursively if: 4308 // - It's no extractable has been found yet (`None`). 4309 // - It's a collection, which may or may not contain a value that can 4310 // be extracted. 4311 // - It's a binary operator, which may or may not operate on 4312 // extractable values. 4313 if matches!( 4314 self.variant_of_extractable, 4315 None | Some(ExtractableToConstant::ComposedValue) 4316 ) && can_be_constant(self.module, expr, None) 4317 { 4318 self.variant_of_extractable = match expr { 4319 TypedExpr::Var { .. } 4320 | TypedExpr::Int { .. } 4321 | TypedExpr::Float { .. } 4322 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue), 4323 4324 TypedExpr::List { .. } 4325 | TypedExpr::Tuple { .. } 4326 | TypedExpr::BitArray { .. } 4327 | TypedExpr::BinOp { .. } 4328 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue), 4329 4330 TypedExpr::Block { .. } 4331 | TypedExpr::Pipeline { .. } 4332 | TypedExpr::Fn { .. } 4333 | TypedExpr::Case { .. } 4334 | TypedExpr::RecordAccess { .. } 4335 | TypedExpr::PositionalAccess { .. } 4336 | TypedExpr::ModuleSelect { .. } 4337 | TypedExpr::TupleIndex { .. } 4338 | TypedExpr::Todo { .. } 4339 | TypedExpr::Panic { .. } 4340 | TypedExpr::Echo { .. } 4341 | TypedExpr::RecordUpdate { .. } 4342 | TypedExpr::NegateBool { .. } 4343 | TypedExpr::NegateInt { .. } 4344 | TypedExpr::Invalid { .. } => None, 4345 }; 4346 4347 self.selected_expression = Some(expr_location); 4348 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr)); 4349 self.value_to_use = Some(EcoString::from( 4350 self.module 4351 .code 4352 .get((expr_location.start as usize)..(expr_location.end as usize)) 4353 .expect("selected expression"), 4354 )); 4355 } 4356 4357 ast::visit::visit_typed_expr(self, expr); 4358 } 4359} 4360 4361/// Builder for code action to apply the "expand function capture" action. 4362/// 4363pub struct ExpandFunctionCapture<'a> { 4364 module: &'a Module, 4365 params: &'a CodeActionParams, 4366 edits: TextEdits<'a>, 4367 function_capture_data: Option<FunctionCaptureData>, 4368} 4369 4370pub struct FunctionCaptureData { 4371 function_span: SrcSpan, 4372 hole_span: SrcSpan, 4373 hole_type: Arc<Type>, 4374 reserved_names: VariablesNames, 4375} 4376 4377impl<'a> ExpandFunctionCapture<'a> { 4378 pub fn new( 4379 module: &'a Module, 4380 line_numbers: &'a LineNumbers, 4381 params: &'a CodeActionParams, 4382 ) -> Self { 4383 Self { 4384 module, 4385 params, 4386 edits: TextEdits::new(line_numbers), 4387 function_capture_data: None, 4388 } 4389 } 4390 4391 pub fn code_actions(mut self) -> Vec<CodeAction> { 4392 self.visit_typed_module(&self.module.ast); 4393 4394 let Some(FunctionCaptureData { 4395 function_span, 4396 hole_span, 4397 hole_type, 4398 reserved_names, 4399 }) = self.function_capture_data 4400 else { 4401 return vec![]; 4402 }; 4403 4404 let mut name_generator = NameGenerator::new(); 4405 name_generator.reserve_variable_names(reserved_names); 4406 let name = name_generator.generate_name_from_type(&hole_type); 4407 4408 self.edits.replace(hole_span, name.clone().into()); 4409 self.edits.insert(function_span.end, " }".into()); 4410 self.edits 4411 .insert(function_span.start, format!("fn({name}) {{ ")); 4412 4413 let mut action = Vec::with_capacity(1); 4414 CodeActionBuilder::new("Expand function capture") 4415 .kind(CodeActionKind::RefactorRewrite) 4416 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4417 .preferred(false) 4418 .push_to(&mut action); 4419 action 4420 } 4421} 4422 4423impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> { 4424 fn visit_typed_expr_fn( 4425 &mut self, 4426 location: &'ast SrcSpan, 4427 type_: &'ast Arc<Type>, 4428 kind: &'ast FunctionLiteralKind, 4429 arguments: &'ast [TypedArg], 4430 body: &'ast Vec1<TypedStatement>, 4431 return_annotation: &'ast Option<ast::TypeAst>, 4432 ) { 4433 let fn_range = self.edits.src_span_to_lsp_range(*location); 4434 if within(self.params.range, fn_range) 4435 && kind.is_capture() 4436 && let [argument] = arguments 4437 { 4438 self.function_capture_data = Some(FunctionCaptureData { 4439 function_span: *location, 4440 hole_span: argument.location, 4441 hole_type: argument.type_.clone(), 4442 reserved_names: VariablesNames::from_statements(body), 4443 }); 4444 } 4445 4446 ast::visit::visit_typed_expr_fn( 4447 self, 4448 location, 4449 type_, 4450 kind, 4451 arguments, 4452 body, 4453 return_annotation, 4454 ) 4455 } 4456} 4457 4458/// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names]. 4459struct VariablesNames { 4460 names: HashSet<EcoString>, 4461} 4462 4463impl VariablesNames { 4464 /// Creates a `VariableNames` by collecting all variables used in a list of statements. 4465 fn from_statements(statements: &[TypedStatement]) -> Self { 4466 let mut variables = Self { 4467 names: HashSet::new(), 4468 }; 4469 4470 for statement in statements { 4471 variables.visit_typed_statement(statement); 4472 } 4473 variables 4474 } 4475 4476 /// Creates a `VariableNames` by collecting all variables used within an expression. 4477 fn from_expression(expression: &TypedExpr) -> Self { 4478 let mut variables = Self { 4479 names: HashSet::new(), 4480 }; 4481 4482 variables.visit_typed_expr(expression); 4483 variables 4484 } 4485} 4486 4487impl<'ast> ast::visit::Visit<'ast> for VariablesNames { 4488 fn visit_typed_expr_var( 4489 &mut self, 4490 _location: &'ast SrcSpan, 4491 _constructor: &'ast ValueConstructor, 4492 name: &'ast EcoString, 4493 ) { 4494 let _ = self.names.insert(name.clone()); 4495 } 4496} 4497 4498/// Builder for code action to apply the "generate dynamic decoder action. 4499/// 4500pub struct GenerateDynamicDecoder<'a, IO> { 4501 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4502 module: &'a Module, 4503 params: &'a CodeActionParams, 4504 edits: TextEdits<'a>, 4505 printer: Printer<'a>, 4506 actions: &'a mut Vec<CodeAction>, 4507} 4508 4509const DECODE_MODULE: &str = "gleam/dynamic/decode"; 4510 4511impl<'a, IO> GenerateDynamicDecoder<'a, IO> { 4512 pub fn new( 4513 module: &'a Module, 4514 line_numbers: &'a LineNumbers, 4515 params: &'a CodeActionParams, 4516 actions: &'a mut Vec<CodeAction>, 4517 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4518 ) -> Self { 4519 // Since we are generating a new function, type variables from other 4520 // functions and constants are irrelevant to the types we print. 4521 let printer = Printer::new_without_type_variables(&module.ast.names); 4522 Self { 4523 module, 4524 params, 4525 edits: TextEdits::new(line_numbers), 4526 printer, 4527 actions, 4528 compiler, 4529 } 4530 } 4531 4532 pub fn code_actions(&mut self) { 4533 self.visit_typed_module(&self.module.ast); 4534 } 4535 4536 fn custom_type_decoder_body( 4537 &mut self, 4538 custom_type: &CustomType<Arc<Type>>, 4539 ) -> Option<EcoString> { 4540 // We cannot generate a decoder for an external type with no constructors! 4541 let constructors_size = custom_type.constructors.len(); 4542 let (first, rest) = custom_type.constructors.split_first()?; 4543 let mode = EncodingMode::for_custom_type(custom_type); 4544 4545 // We generate a decoder for a type with a single constructor: it does not 4546 // require pattern matching on a tag as there's no variants to tell apart. 4547 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag { 4548 return self.constructor_decoder(mode, custom_type, first, 0); 4549 } 4550 4551 // Otherwise we need to generate a decoder that has to tell apart different 4552 // variants, depending on the mode we might have to decode a type field or 4553 // plain strings! 4554 let module = self.printer.print_module(DECODE_MODULE); 4555 let discriminant = if mode == EncodingMode::PlainString { 4556 eco_format!("use variant <- {module}.then({module}.string)") 4557 } else { 4558 eco_format!("use variant <- {module}.field(\"type\", {module}.string)") 4559 }; 4560 4561 let mut clauses = Vec::with_capacity(constructors_size); 4562 for constructor in iter::once(first).chain(rest) { 4563 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?; 4564 let name = to_snake_case(&constructor.name); 4565 clauses.push(eco_format!(r#" "{name}" -> {body}"#)); 4566 } 4567 4568 let failure_clause = self.failure_clause(custom_type); 4569 4570 let cases = clauses.join("\n"); 4571 Some(eco_format!( 4572 r#"{{ 4573 {discriminant} 4574 case variant {{ 4575{cases} 4576{failure_clause} 4577 }} 4578}}"#, 4579 )) 4580 } 4581 4582 fn constructor_decoder( 4583 &mut self, 4584 mode: EncodingMode, 4585 custom_type: &ast::TypedCustomType, 4586 constructor: &TypedRecordConstructor, 4587 nesting: usize, 4588 ) -> Option<EcoString> { 4589 let decode_module = self.printer.print_module(DECODE_MODULE); 4590 let constructor_name = &constructor.name; 4591 4592 // If the constructor was encoded as a plain string with no additional 4593 // fields it means there's nothing else to decode and we can just 4594 // succeed. 4595 if mode == EncodingMode::PlainString { 4596 return Some(eco_format!("{decode_module}.success({constructor_name})")); 4597 } 4598 4599 // Otherwise we have to decode all the constructor fields to build it. 4600 let mut fields = Vec::with_capacity(constructor.arguments.len()); 4601 for argument in constructor.arguments.iter() { 4602 let (_, name) = argument.label.as_ref()?; 4603 let field = RecordField { 4604 label: RecordLabel::Labeled(name), 4605 type_: &argument.type_, 4606 }; 4607 fields.push(field); 4608 } 4609 4610 let mut decoder_printer = DecoderPrinter::new( 4611 &mut self.printer, 4612 custom_type.name.clone(), 4613 self.module.name.clone(), 4614 self.compiler, 4615 ); 4616 4617 let decoders = fields 4618 .iter() 4619 .map(|field| decoder_printer.decode_field(field, nesting + 2)) 4620 .join("\n"); 4621 4622 let indent = " ".repeat(nesting); 4623 4624 Some(if decoders.is_empty() { 4625 eco_format!("{decode_module}.success({constructor_name})") 4626 } else { 4627 let field_names = fields 4628 .iter() 4629 .map(|field| format!("{}:", field.label.variable_name())) 4630 .join(", "); 4631 4632 eco_format!( 4633 "{{ 4634{decoders} 4635{indent} {decode_module}.success({constructor_name}({field_names})) 4636{indent}}}", 4637 ) 4638 }) 4639 } 4640 4641 /// Generates the failure/catch-all clause in a decoder function for a 4642 /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()` 4643 /// clause executed when the `type` field does not match any of the known variants. 4644 /// 4645 /// # Arguments 4646 /// * `custom_type` - The root type we are printing a decoder for 4647 fn failure_clause(&mut self, custom_type: &CustomType<Arc<Type>>) -> EcoString { 4648 let decode_module = self.printer.print_module(DECODE_MODULE); 4649 let type_name = &custom_type.name; 4650 4651 let mut decoder_printer = DecoderPrinter::new( 4652 &mut self.printer, 4653 type_name.clone(), 4654 self.module.name.clone(), 4655 self.compiler, 4656 ); 4657 4658 // The construction of the zero value might necessitate importing 4659 // modules that aren't currently in scope. We keep track of them here. 4660 let mut modules_to_import = HashSet::new(); 4661 // We also need to keep track of the path we are tracing through the types 4662 // to make sure we don't get stuck in an infinite loop building a recursive 4663 // type. 4664 let mut path = vec![]; 4665 4666 if let Some(zero_value) = decoder_printer.zero_value_for_custom_type( 4667 &self.module.name, 4668 type_name, 4669 &mut path, 4670 &mut modules_to_import, 4671 ) { 4672 for module_name in modules_to_import { 4673 maybe_import(&mut self.edits, self.module, &module_name); 4674 } 4675 eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,) 4676 } else { 4677 eco_format!( 4678 r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"# 4679 ) 4680 } 4681 } 4682} 4683 4684impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> { 4685 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 4686 let range = self 4687 .edits 4688 .src_span_to_lsp_range(custom_type.full_location()); 4689 if !within(self.params.range, range) { 4690 return; 4691 } 4692 4693 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name)); 4694 let Some(function_body) = self.custom_type_decoder_body(custom_type) else { 4695 return; 4696 }; 4697 4698 let parameters = match custom_type.parameters.len() { 4699 0 => EcoString::new(), 4700 _ => eco_format!( 4701 "({})", 4702 custom_type 4703 .parameters 4704 .iter() 4705 .map(|(_, name)| name) 4706 .join(", ") 4707 ), 4708 }; 4709 4710 let decoder_type = self.printer.print_type(&Type::Named { 4711 publicity: Publicity::Public, 4712 package: STDLIB_PACKAGE_NAME.into(), 4713 module: DECODE_MODULE.into(), 4714 name: "Decoder".into(), 4715 arguments: vec![], 4716 inferred_variant: None, 4717 }); 4718 4719 let function = format!( 4720 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}", 4721 type_name = custom_type.name, 4722 ); 4723 4724 self.edits.insert(custom_type.end_position, function); 4725 maybe_import(&mut self.edits, self.module, DECODE_MODULE); 4726 4727 CodeActionBuilder::new("Generate dynamic decoder") 4728 .kind(CodeActionKind::Refactor) 4729 .preferred(false) 4730 .changes( 4731 self.params.text_document.uri.clone(), 4732 std::mem::take(&mut self.edits.edits), 4733 ) 4734 .push_to(self.actions); 4735 } 4736} 4737 4738/// If `module_name` is not already imported inside `module`, adds an edit to 4739/// add that import. 4740/// This function also makes sure not to import a module in itself. 4741/// 4742fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) { 4743 if module.ast.names.is_imported(module_name) || module.name == module_name { 4744 return; 4745 } 4746 4747 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers); 4748 let first_is_import = first_import_pos.is_some(); 4749 let import_location = first_import_pos.unwrap_or_default(); 4750 let after_import_newlines = add_newlines_after_import( 4751 import_location, 4752 first_is_import, 4753 edits.line_numbers, 4754 &module.code, 4755 ); 4756 4757 edits.edits.push(get_import_edit( 4758 import_location, 4759 module_name, 4760 &after_import_newlines, 4761 )); 4762} 4763 4764struct DecoderPrinter<'a, 'b, IO> { 4765 printer: &'a mut Printer<'b>, 4766 /// The name of the root type we are printing a decoder for 4767 type_name: EcoString, 4768 /// The module name of the root type we are printing a decoder for 4769 type_module: EcoString, 4770 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4771} 4772 4773const DYNAMIC_MODULE: &str = "gleam/dynamic"; 4774const DICT_MODULE: &str = "gleam/dict"; 4775const OPTION_MODULE: &str = "gleam/option"; 4776 4777struct RecordField<'a> { 4778 label: RecordLabel<'a>, 4779 type_: &'a Type, 4780} 4781 4782enum RecordLabel<'a> { 4783 Labeled(&'a str), 4784 Unlabeled(usize), 4785} 4786 4787impl RecordLabel<'_> { 4788 fn field_key(&self) -> EcoString { 4789 match self { 4790 RecordLabel::Labeled(label) => eco_format!("\"{label}\""), 4791 RecordLabel::Unlabeled(index) => { 4792 eco_format!("{index}") 4793 } 4794 } 4795 } 4796 4797 fn variable_name(&self) -> EcoString { 4798 match self { 4799 RecordLabel::Labeled(label) => (*label).into(), 4800 &RecordLabel::Unlabeled(mut index) => { 4801 let mut characters = Vec::new(); 4802 let alphabet_length = 26; 4803 let alphabet_offset = b'a'; 4804 loop { 4805 let alphabet_index = (index % alphabet_length) as u8; 4806 characters.push((alphabet_offset + alphabet_index) as char); 4807 index /= alphabet_length; 4808 4809 if index == 0 { 4810 break; 4811 } 4812 index -= 1; 4813 } 4814 characters.into_iter().rev().collect() 4815 } 4816 } 4817 } 4818} 4819 4820impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> { 4821 fn new( 4822 printer: &'a mut Printer<'b>, 4823 type_name: EcoString, 4824 type_module: EcoString, 4825 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 4826 ) -> Self { 4827 Self { 4828 type_name, 4829 type_module, 4830 printer, 4831 compiler, 4832 } 4833 } 4834 4835 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString { 4836 let module_name = self.printer.print_module(DECODE_MODULE); 4837 if type_.is_bit_array() { 4838 eco_format!("{module_name}.bit_array") 4839 } else if type_.is_bool() { 4840 eco_format!("{module_name}.bool") 4841 } else if type_.is_float() { 4842 eco_format!("{module_name}.float") 4843 } else if type_.is_int() { 4844 eco_format!("{module_name}.int") 4845 } else if type_.is_string() { 4846 eco_format!("{module_name}.string") 4847 } else if type_.is_nil() { 4848 eco_format!("{module_name}.success(Nil)") 4849 } else { 4850 match type_.tuple_types() { 4851 Some(types) => { 4852 let fields = types 4853 .iter() 4854 .enumerate() 4855 .map(|(index, type_)| RecordField { 4856 type_, 4857 label: RecordLabel::Unlabeled(index), 4858 }) 4859 .collect_vec(); 4860 let decoders = fields 4861 .iter() 4862 .map(|field| self.decode_field(field, indent + 2)) 4863 .join("\n"); 4864 let mut field_names = fields.iter().map(|field| field.label.variable_name()); 4865 4866 eco_format!( 4867 "{{ 4868{decoders} 4869 4870{indent} {module_name}.success(#({fields})) 4871{indent}}}", 4872 fields = field_names.join(", "), 4873 indent = " ".repeat(indent) 4874 ) 4875 } 4876 _ => { 4877 let type_information = type_.named_type_information(); 4878 let type_information = 4879 type_information.as_ref().map(|(module, name, arguments)| { 4880 (module.as_str(), name.as_str(), arguments.as_slice()) 4881 }); 4882 4883 match type_information { 4884 Some(("gleam/dynamic", "Dynamic", _)) => { 4885 eco_format!("{module_name}.dynamic") 4886 } 4887 Some(("gleam", "List", [element])) => { 4888 eco_format!("{module_name}.list({})", self.decoder_for(element, indent)) 4889 } 4890 Some(("gleam/option", "Option", [some])) => { 4891 eco_format!( 4892 "{module_name}.optional({})", 4893 self.decoder_for(some, indent) 4894 ) 4895 } 4896 Some(("gleam/dict", "Dict", [key, value])) => { 4897 eco_format!( 4898 "{module_name}.dict({}, {})", 4899 self.decoder_for(key, indent), 4900 self.decoder_for(value, indent) 4901 ) 4902 } 4903 Some((module, name, _)) 4904 if module == self.type_module && name == self.type_name => 4905 { 4906 eco_format!("{}_decoder()", to_snake_case(name)) 4907 } 4908 _ => eco_format!( 4909 r#"todo as "Decoder for {}""#, 4910 self.printer.print_type(type_) 4911 ), 4912 } 4913 } 4914 } 4915 } 4916 } 4917 4918 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4919 let decoder = self.decoder_for(field.type_, indent); 4920 4921 eco_format!( 4922 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#, 4923 indent = " ".repeat(indent), 4924 variable = field.label.variable_name(), 4925 field = field.label.field_key(), 4926 module = self.printer.print_module(DECODE_MODULE) 4927 ) 4928 } 4929 4930 /// Performs best-effort generation of zero values for a given type. 4931 /// Will succeed for all prelude types and most stdlib types. 4932 /// For specifics on how custom user-defined types are handled, see 4933 /// `zero_value_for_custom_type`. 4934 fn zero_value_for_type( 4935 &mut self, 4936 type_: &Type, 4937 // Keeps track of types visited already to ensure we don't end up in an infinite 4938 // loop due to recursive types 4939 path: &mut Vec<(EcoString, EcoString)>, 4940 modules_to_import: &mut HashSet<EcoString>, 4941 ) -> Option<EcoString> { 4942 if type_.is_bit_array() { 4943 return Some("<<>>".into()); 4944 } 4945 if type_.is_bool() { 4946 return Some("False".into()); 4947 } 4948 if type_.is_float() { 4949 return Some("0.0".into()); 4950 } 4951 if type_.is_int() { 4952 return Some("0".into()); 4953 } 4954 if type_.is_string() { 4955 return Some("\"\"".into()); 4956 } 4957 if type_.is_list() { 4958 return Some("[]".into()); 4959 } 4960 if type_.is_nil() { 4961 return Some("Nil".into()); 4962 } 4963 4964 if let Some(types) = type_.tuple_types() { 4965 // We try generating zero values for the tuple members and exit early if any of them fail. 4966 let field_zeroes = types 4967 .iter() 4968 .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import)) 4969 .collect_vec(); 4970 4971 if field_zeroes.len() < types.len() { 4972 return None; 4973 } else { 4974 return Some(eco_format!("#({})", field_zeroes.iter().join(", "))); 4975 } 4976 }; 4977 4978 let (module, name, _) = type_.named_type_information()?; 4979 match (module.as_str(), name.as_str()) { 4980 (OPTION_MODULE, "Option") => { 4981 let _ = modules_to_import.insert(OPTION_MODULE.into()); 4982 Some(eco_format!( 4983 "{}.None", 4984 self.printer.print_module(OPTION_MODULE) 4985 )) 4986 } 4987 4988 (DYNAMIC_MODULE, "Dynamic") => { 4989 let _ = modules_to_import.insert(DYNAMIC_MODULE.into()); 4990 Some(eco_format!( 4991 "{}.nil()", 4992 self.printer.print_module(DYNAMIC_MODULE) 4993 )) 4994 } 4995 4996 (DICT_MODULE, "Dict") => { 4997 let _ = modules_to_import.insert(DICT_MODULE.into()); 4998 Some(eco_format!( 4999 "{}.new()", 5000 self.printer.print_module(DICT_MODULE) 5001 )) 5002 } 5003 5004 _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import), 5005 } 5006 } 5007 5008 /// Best-effort zero value generation for user-defined types in the 5009 /// current package. 5010 fn zero_value_for_custom_type( 5011 &mut self, 5012 custom_type_module: &EcoString, 5013 custom_type_name: &EcoString, 5014 path: &mut Vec<(EcoString, EcoString)>, 5015 modules_to_import: &mut HashSet<EcoString>, 5016 ) -> Option<EcoString> { 5017 // First we check that we have not already visited this type before to 5018 // avoid cycles. 5019 let already_seen_type = path.iter().any(|(path_module, path_type_name)| { 5020 path_module == custom_type_module && path_type_name == custom_type_name 5021 }); 5022 5023 if already_seen_type { 5024 return None; 5025 }; 5026 5027 let type_is_inside_current_module = &self.type_module == custom_type_module; 5028 5029 let current_module_interface = self 5030 .compiler 5031 .modules 5032 .get(&self.type_module) 5033 .map(|module| &module.ast.type_info)?; 5034 5035 let type_module_interface = if !type_is_inside_current_module { 5036 self.compiler.get_module_interface(custom_type_module)? 5037 } else { 5038 current_module_interface 5039 }; 5040 5041 // We only try and generate zero values for user-defined types in the current 5042 // package. If you are expanding the scope of this functionality to 5043 // remove this limitation, make sure to check for internal modules. 5044 if current_module_interface.package != type_module_interface.package { 5045 return None; 5046 } 5047 5048 let constructors = type_module_interface 5049 .types_value_constructors 5050 .get(custom_type_name)?; 5051 5052 // Opaque types cannot be constructed outside the module they were defined in, 5053 // so we will be unable to produce a zero value. 5054 if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque { 5055 return None; 5056 } 5057 5058 // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor 5059 // to construct our zero value to reduce visual noise, but this might not always 5060 // be possible. So we check all constructors in increasing order of size to 5061 // find the first one that succeeds, and then short circuit. 5062 constructors 5063 .variants 5064 .iter() 5065 .sorted_by_key(|v| v.parameters.len()) 5066 .find_map(|zero_constructor| { 5067 self.zero_value_for_custom_type_constructor( 5068 custom_type_module, 5069 custom_type_name, 5070 zero_constructor, 5071 type_is_inside_current_module, 5072 path, 5073 modules_to_import, 5074 ) 5075 }) 5076 } 5077 5078 /// Attempts to construct a zero value for one specific constructor 5079 /// of a custom type. Use `zero_value_for_custom_type` instead as it performs 5080 /// _important checks on type visibility that this method does NOT_. 5081 /// This is a helper method extracted for readability. 5082 fn zero_value_for_custom_type_constructor( 5083 &mut self, 5084 custom_type_module: &EcoString, 5085 custom_type_name: &EcoString, 5086 zero_constructor: &type_::TypeValueConstructor, 5087 type_is_inside_current_module: bool, 5088 path: &mut Vec<(EcoString, EcoString)>, 5089 modules_to_import: &mut HashSet<EcoString>, 5090 ) -> Option<EcoString> { 5091 path.push((custom_type_module.clone(), custom_type_name.clone())); 5092 5093 // Try to generate zero values for all fields in the constructor 5094 let zero_params = zero_constructor 5095 .parameters 5096 .iter() 5097 .map_while(|parameter| { 5098 let zero = self.zero_value_for_type(&parameter.type_, path, modules_to_import)?; 5099 5100 if let Some(label) = &parameter.label { 5101 Some(eco_format!("{label}: {zero}")) 5102 } else { 5103 Some(zero) 5104 } 5105 }) 5106 .collect_vec(); 5107 5108 // We need to make sure to clean up the path once we've finished 5109 // "visiting" this type. 5110 let _ = path.pop(); 5111 5112 // Only proceed if we were able to construct every field successfully 5113 if zero_params.len() < zero_constructor.parameters.len() { 5114 return None; 5115 }; 5116 5117 let zero_constructor = if !type_is_inside_current_module { 5118 // Type constructors from other modules need to be qualified appropriately, 5119 // and they might need to be brought into scope. 5120 let _ = modules_to_import.insert(custom_type_module.clone()); 5121 eco_format!( 5122 "{}.{}", 5123 self.printer.print_module(custom_type_module), 5124 zero_constructor.name 5125 ) 5126 } else { 5127 eco_format!("{}", zero_constructor.name) 5128 }; 5129 5130 if zero_params.is_empty() { 5131 Some(eco_format!("{zero_constructor}")) 5132 } else { 5133 let zero_args = zero_params.iter().join(", "); 5134 Some(eco_format!("{zero_constructor}({zero_args})")) 5135 } 5136 } 5137} 5138 5139/// Builder for code action to apply the "Generate to-JSON function" action. 5140/// 5141pub struct GenerateJsonEncoder<'a> { 5142 module: &'a Module, 5143 params: &'a CodeActionParams, 5144 edits: TextEdits<'a>, 5145 printer: Printer<'a>, 5146 actions: &'a mut Vec<CodeAction>, 5147 config: &'a PackageConfig, 5148} 5149 5150const JSON_MODULE: &str = "gleam/json"; 5151const JSON_PACKAGE_NAME: &str = "gleam_json"; 5152 5153#[derive(Eq, PartialEq, Copy, Clone)] 5154enum EncodingMode { 5155 PlainString, 5156 ObjectWithTypeTag, 5157 ObjectWithNoTypeTag, 5158} 5159 5160impl EncodingMode { 5161 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self { 5162 match type_.constructors.as_slice() { 5163 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString, 5164 [_constructor] => EncodingMode::ObjectWithNoTypeTag, 5165 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => { 5166 EncodingMode::PlainString 5167 } 5168 _constructors => EncodingMode::ObjectWithTypeTag, 5169 } 5170 } 5171} 5172 5173impl<'a> GenerateJsonEncoder<'a> { 5174 pub fn new( 5175 module: &'a Module, 5176 line_numbers: &'a LineNumbers, 5177 params: &'a CodeActionParams, 5178 actions: &'a mut Vec<CodeAction>, 5179 config: &'a PackageConfig, 5180 ) -> Self { 5181 // Since we are generating a new function, type variables from other 5182 // functions and constants are irrelevant to the types we print. 5183 let printer = Printer::new_without_type_variables(&module.ast.names); 5184 Self { 5185 module, 5186 params, 5187 edits: TextEdits::new(line_numbers), 5188 printer, 5189 actions, 5190 config, 5191 } 5192 } 5193 5194 pub fn code_actions(&mut self) { 5195 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME) 5196 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME) 5197 { 5198 self.visit_typed_module(&self.module.ast); 5199 } 5200 } 5201 5202 fn custom_type_encoder_body( 5203 &mut self, 5204 record_name: EcoString, 5205 custom_type: &CustomType<Arc<Type>>, 5206 ) -> Option<EcoString> { 5207 // We cannot generate a decoder for an external type with no constructors! 5208 let constructors_size = custom_type.constructors.len(); 5209 let (first, rest) = custom_type.constructors.split_first()?; 5210 let mode = EncodingMode::for_custom_type(custom_type); 5211 5212 // We generate an encoder for a type with a single constructor: it does not 5213 // require pattern matching on the argument as we can access all its fields 5214 // with the usual record access syntax. 5215 if rest.is_empty() { 5216 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?; 5217 let unpacking = if first.arguments.is_empty() { 5218 "" 5219 } else { 5220 &eco_format!( 5221 "let {name}({fields}:) = {record_name}\n ", 5222 name = first.name, 5223 fields = first 5224 .arguments 5225 .iter() 5226 .filter_map(|argument| { 5227 argument.label.as_ref().map(|(_location, label)| label) 5228 }) 5229 .join(":, ") 5230 ) 5231 }; 5232 return Some(eco_format!("{unpacking}{encoder}")); 5233 } 5234 5235 // Otherwise we generate an encoder for a type with multiple constructors: 5236 // it will need to pattern match on the various constructors and encode each 5237 // one separately. 5238 let mut clauses = Vec::with_capacity(constructors_size); 5239 for constructor in iter::once(first).chain(rest) { 5240 let RecordConstructor { name, .. } = constructor; 5241 let encoder = 5242 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?; 5243 if constructor.arguments.is_empty() { 5244 clauses.push(eco_format!(" {name} -> {encoder}")); 5245 } else { 5246 let unpacking = constructor 5247 .arguments 5248 .iter() 5249 .filter_map(|argument| { 5250 argument.label.as_ref().map(|(_location, label)| { 5251 if is_nil_like(&argument.type_) { 5252 eco_format!("{}: _", label) 5253 } else { 5254 eco_format!("{}:", label) 5255 } 5256 }) 5257 }) 5258 .join(", "); 5259 clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}")); 5260 } 5261 } 5262 5263 let clauses = clauses.join("\n"); 5264 Some(eco_format!( 5265 "case {record_name} {{ 5266{clauses} 5267 }}", 5268 )) 5269 } 5270 5271 fn constructor_encoder( 5272 &mut self, 5273 mode: EncodingMode, 5274 constructor: &TypedRecordConstructor, 5275 type_name: EcoString, 5276 nesting: usize, 5277 ) -> Option<EcoString> { 5278 let json_module = self.printer.print_module(JSON_MODULE); 5279 let tag = to_snake_case(&constructor.name); 5280 let indent = " ".repeat(nesting); 5281 5282 // If the variant is encoded as a simple json string we just call the 5283 // `json.string` with the variant tag as an argument. 5284 if mode == EncodingMode::PlainString { 5285 return Some(eco_format!("{json_module}.string(\"{tag}\")")); 5286 } 5287 5288 // Otherwise we turn it into an object with a `type` tag field. 5289 let mut encoder_printer = 5290 JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone()); 5291 5292 // These are the fields of the json object to encode. 5293 let mut fields = Vec::with_capacity(constructor.arguments.len()); 5294 if mode == EncodingMode::ObjectWithTypeTag { 5295 // Any needed type tag is always going to be the first field in the object 5296 fields.push(eco_format!( 5297 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))" 5298 )); 5299 } 5300 5301 for argument in constructor.arguments.iter() { 5302 let (_, label) = argument.label.as_ref()?; 5303 let field = RecordField { 5304 label: RecordLabel::Labeled(label), 5305 type_: &argument.type_, 5306 }; 5307 let encoder = encoder_printer.encode_field(&field, nesting + 2); 5308 fields.push(encoder); 5309 } 5310 5311 let fields = fields.join(",\n"); 5312 Some(eco_format!( 5313 "{json_module}.object([ 5314{fields}, 5315{indent}])" 5316 )) 5317 } 5318} 5319 5320/// When generating an encoder, we need to know when we can ignore the fields 5321/// destructured from a constructor. 5322/// If a field is of type `Nil` or is a tuple type composed entirely of `Nil` 5323/// types, then we will never need to use the field in our encoder function. 5324fn is_nil_like(type_: &Type) -> bool { 5325 if type_.is_nil() { 5326 return true; 5327 } 5328 5329 match type_.tuple_types() { 5330 Some(types) => types.iter().all(|type_| is_nil_like(type_)), 5331 _ => false, 5332 } 5333} 5334 5335impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> { 5336 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 5337 let range = self 5338 .edits 5339 .src_span_to_lsp_range(custom_type.full_location()); 5340 if !within(self.params.range, range) { 5341 return; 5342 } 5343 5344 let record_name = to_snake_case(&custom_type.name); 5345 let name = eco_format!("{record_name}_to_json"); 5346 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else { 5347 return; 5348 }; 5349 5350 let json_type = self.printer.print_type(&Type::Named { 5351 publicity: Publicity::Public, 5352 package: JSON_PACKAGE_NAME.into(), 5353 module: JSON_MODULE.into(), 5354 name: "Json".into(), 5355 arguments: vec![], 5356 inferred_variant: None, 5357 }); 5358 5359 let type_ = if custom_type.parameters.is_empty() { 5360 custom_type.name.clone() 5361 } else { 5362 let parameters = custom_type 5363 .parameters 5364 .iter() 5365 .map(|(_, name)| name) 5366 .join(", "); 5367 eco_format!("{}({})", custom_type.name, parameters) 5368 }; 5369 5370 let function = format!( 5371 " 5372 5373fn {name}({record_name}: {type_}) -> {json_type} {{ 5374 {encoder} 5375}}", 5376 ); 5377 5378 self.edits.insert(custom_type.end_position, function); 5379 maybe_import(&mut self.edits, self.module, JSON_MODULE); 5380 5381 CodeActionBuilder::new("Generate to-JSON function") 5382 .kind(CodeActionKind::Refactor) 5383 .preferred(false) 5384 .changes( 5385 self.params.text_document.uri.clone(), 5386 std::mem::take(&mut self.edits.edits), 5387 ) 5388 .push_to(self.actions); 5389 } 5390} 5391 5392struct JsonEncoderPrinter<'a, 'b> { 5393 printer: &'a mut Printer<'b>, 5394 /// The name of the root type we are printing an encoder for 5395 type_name: EcoString, 5396 /// The module name of the root type we are printing an encoder for 5397 type_module: EcoString, 5398} 5399 5400impl<'a, 'b> JsonEncoderPrinter<'a, 'b> { 5401 fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self { 5402 Self { 5403 type_name, 5404 type_module, 5405 printer, 5406 } 5407 } 5408 5409 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString { 5410 let module_name = self.printer.print_module(JSON_MODULE); 5411 let is_capture = encoded_value == "_"; 5412 let maybe_capture = |mut function: EcoString| { 5413 if is_capture { 5414 function 5415 } else { 5416 function.push('('); 5417 function.push_str(encoded_value); 5418 function.push(')'); 5419 function 5420 } 5421 }; 5422 5423 if type_.is_bool() { 5424 maybe_capture(eco_format!("{module_name}.bool")) 5425 } else if type_.is_float() { 5426 maybe_capture(eco_format!("{module_name}.float")) 5427 } else if type_.is_int() { 5428 maybe_capture(eco_format!("{module_name}.int")) 5429 } else if type_.is_string() { 5430 maybe_capture(eco_format!("{module_name}.string")) 5431 } else if type_.is_nil() { 5432 if is_capture { 5433 eco_format!("fn(_) {{ {module_name}.null() }}") 5434 } else { 5435 eco_format!("{module_name}.null()") 5436 } 5437 } else { 5438 match type_.tuple_types() { 5439 Some(types) => { 5440 let (tuple, new_indent) = if is_capture { 5441 ("value", indent + 4) 5442 } else { 5443 (encoded_value, indent + 2) 5444 }; 5445 5446 // We need to iterate over all of the tuple's fields 5447 // to obtain an encoder for each one, so we reuse the 5448 // iteration to check whether this tuple can be ignored 5449 // in the encoder without calling `is_nil_like`. 5450 let mut encoders = Vec::new(); 5451 let all_values_are_nil = types.iter().enumerate().fold( 5452 true, 5453 |all_values_are_nil, (index, type_)| { 5454 encoders.push(self.encoder_for( 5455 &format!("{tuple}.{index}"), 5456 type_, 5457 new_indent, 5458 )); 5459 all_values_are_nil && is_nil_like(type_) 5460 }, 5461 ); 5462 5463 if is_capture { 5464 eco_format!( 5465 "fn({value}) {{ 5466{indent} {module_name}.preprocessed_array([ 5467{indent} {encoders}, 5468{indent} ]) 5469{indent}}}", 5470 value = if all_values_are_nil { "_" } else { "value" }, 5471 indent = " ".repeat(indent), 5472 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 5473 ) 5474 } else { 5475 eco_format!( 5476 "{module_name}.preprocessed_array([ 5477{indent} {encoders}, 5478{indent}])", 5479 indent = " ".repeat(indent), 5480 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 5481 ) 5482 } 5483 } 5484 _ => { 5485 let type_information = type_.named_type_information(); 5486 let type_information: Option<(&str, &str, &[Arc<Type>])> = 5487 type_information.as_ref().map(|(module, name, arguments)| { 5488 (module.as_str(), name.as_str(), arguments.as_slice()) 5489 }); 5490 5491 match type_information { 5492 Some(("gleam", "List", [element])) => { 5493 eco_format!( 5494 "{module_name}.array({encoded_value}, {map_function})", 5495 map_function = self.encoder_for("_", element, indent) 5496 ) 5497 } 5498 Some(("gleam/option", "Option", [some])) => { 5499 eco_format!( 5500 "case {encoded_value} {{ 5501{indent} {none} -> {module_name}.null() 5502{indent} {some}({value}) -> {encoder} 5503{indent}}}", 5504 indent = " ".repeat(indent), 5505 none = self 5506 .printer 5507 .print_constructor(&"gleam/option".into(), &"None".into()), 5508 some = self 5509 .printer 5510 .print_constructor(&"gleam/option".into(), &"Some".into()), 5511 value = if is_nil_like(some) { "_" } else { "value" }, 5512 encoder = self.encoder_for("value", some, indent + 2) 5513 ) 5514 } 5515 Some(("gleam/dict", "Dict", [key, value])) => { 5516 let stringify_function = match key 5517 .named_type_information() 5518 .as_ref() 5519 .map(|(module, name, arguments)| { 5520 (module.as_str(), name.as_str(), arguments.as_slice()) 5521 }) { 5522 Some(("gleam", "String", [])) => "fn(string) { string }", 5523 _ => &format!( 5524 r#"todo as "Function to stringify {}""#, 5525 self.printer.print_type(key) 5526 ), 5527 }; 5528 eco_format!( 5529 "{module_name}.dict({encoded_value}, {stringify_function}, {})", 5530 self.encoder_for("_", value, indent) 5531 ) 5532 } 5533 Some((module, name, _)) 5534 if module == self.type_module && name == self.type_name => 5535 { 5536 maybe_capture(eco_format!("{}_to_json", to_snake_case(name))) 5537 } 5538 _ => eco_format!( 5539 r#"todo as "Encoder for {}""#, 5540 self.printer.print_type(type_) 5541 ), 5542 } 5543 } 5544 } 5545 } 5546 } 5547 5548 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 5549 let field_name = field.label.variable_name(); 5550 let encoder = self.encoder_for(&field_name, field.type_, indent); 5551 5552 eco_format!( 5553 r#"{indent}#("{field_name}", {encoder})"#, 5554 indent = " ".repeat(indent), 5555 ) 5556 } 5557} 5558 5559/// Builder for code action to pattern match on things like (anonymous) function 5560/// arguments or variables. 5561/// For example: 5562/// 5563/// ```gleam 5564/// pub fn wibble(arg: #(key, value)) { 5565/// // ^ [pattern match on argument] 5566/// } 5567/// 5568/// // Generates 5569/// 5570/// pub fn wibble(arg: #(key, value)) { 5571/// let #(value_0, value_1) = arg 5572/// } 5573/// ``` 5574/// 5575/// Another example with variables: 5576/// 5577/// ```gleam 5578/// pub fn main() { 5579/// let pair = #(1, 3) 5580/// // ^ [pattern match on value] 5581/// } 5582/// 5583/// // Generates 5584/// 5585/// pub fn main() { 5586/// let pair = #(1, 3) 5587/// let #(value_0, value_1) = pair 5588/// } 5589/// ``` 5590/// 5591pub struct PatternMatchOnValue<'a, A> { 5592 module: &'a Module, 5593 params: &'a CodeActionParams, 5594 compiler: &'a LspProjectCompiler<A>, 5595 pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc<Type>)>, 5596 selected_value: Option<PatternMatchedValue<'a>>, 5597 edits: TextEdits<'a>, 5598} 5599 5600/// A value we might want to pattern match on. 5601/// Each variant will also contain all the info needed to know how to properly 5602/// print and format the corresponding pattern matching code; that's why you'll 5603/// see `Range`s and `SrcSpan` besides the type of the thing being matched. 5604/// 5605#[derive(Clone)] 5606pub enum PatternMatchedValue<'a> { 5607 /// A statement we can match on. For example, function calls, record 5608 /// and tuple accesses: 5609 /// 5610 /// ```gleam 5611 /// pub fn wibble() { 5612 /// wobble(1, 2) 5613 /// //^^^^ This 5614 /// } 5615 /// ``` 5616 /// 5617 Statement { 5618 /// The span covering the entire statement: 5619 /// ```gleam 5620 /// wobble(1, 2) 5621 /// //^^^^^^^^^^^^ This 5622 /// ``` 5623 location: SrcSpan, 5624 /// The statement's type defining what we will be pattern matching 5625 /// on. 5626 type_: Arc<Type>, 5627 }, 5628 FunctionArgument { 5629 /// The argument being pattern matched on. 5630 /// 5631 arg: &'a TypedArg, 5632 /// The first statement inside the function body. Used to correctly 5633 /// position the inserted pattern matching. 5634 /// 5635 first_statement: &'a TypedStatement, 5636 /// The range of the entire function holding the argument. 5637 /// 5638 function_range: Range, 5639 }, 5640 LetVariable { 5641 variable_name: &'a EcoString, 5642 variable_type: Arc<Type>, 5643 /// The location of the entire let assignment the variable is part of, 5644 /// so that we can add the pattern matching _after_ it. 5645 /// 5646 assignment_location: SrcSpan, 5647 }, 5648 /// A variable that is bound in a case branch's pattern. For example: 5649 /// ```gleam 5650 /// case wibble { 5651 /// wobble -> 1 5652 /// // ^^^^^ This! 5653 /// } 5654 /// ``` 5655 /// 5656 ClausePatternVariable { 5657 variable_type: Arc<Type>, 5658 variable_location: PatternLocation, 5659 clause_location: SrcSpan, 5660 /// All the names in the clause that are already taken by pattern variables. 5661 /// We need this to avoid generating invalid code were two pattern variables 5662 /// have the same name. 5663 /// 5664 /// For example: 5665 /// 5666 /// ```gleam 5667 /// case wibble { 5668 /// [first, ..rest] -> todo 5669 /// ^^^^^ When expanding `first` we can't add any variable pattern 5670 /// called `rest` as it would clash with the `rest` tail that is 5671 /// already there. 5672 /// } 5673 /// ``` 5674 /// 5675 bound_variables: Vec<BoundVariable>, 5676 }, 5677 UseVariable { 5678 variable_name: &'a EcoString, 5679 variable_type: Arc<Type>, 5680 /// The location of the entire use expression the variable is part of, 5681 /// so that we can add the pattern matching _after_ it. 5682 /// 5683 use_location: SrcSpan, 5684 }, 5685} 5686 5687#[derive(Clone)] 5688pub enum PatternLocation { 5689 /// Any pattern that doesn't need any special handling. 5690 /// 5691 Regular { location: SrcSpan }, 5692 /// List tails need some care to not generate invalid syntax when pattern 5693 /// matched on in case expressions. 5694 /// 5695 ListTail { 5696 /// This location covers the entire list tail pattern, including the `..` 5697 location: SrcSpan, 5698 }, 5699 /// When the pattern being matched is a discard. For example: 5700 /// ```gleam 5701 /// case wibble { 5702 /// Ok(_) -> todo 5703 /// // ^ Hovering this! 5704 /// } 5705 /// ``` 5706 Discard { location: SrcSpan }, 5707} 5708 5709impl PatternLocation { 5710 fn regular(location: SrcSpan) -> Self { 5711 Self::Regular { location } 5712 } 5713 5714 fn is_discard(&self) -> bool { 5715 match self { 5716 PatternLocation::Regular { .. } | PatternLocation::ListTail { .. } => false, 5717 PatternLocation::Discard { .. } => true, 5718 } 5719 } 5720} 5721 5722impl<'a, IO> PatternMatchOnValue<'a, IO> { 5723 pub fn new( 5724 module: &'a Module, 5725 line_numbers: &'a LineNumbers, 5726 params: &'a CodeActionParams, 5727 compiler: &'a LspProjectCompiler<IO>, 5728 ) -> Self { 5729 Self { 5730 module, 5731 params, 5732 compiler, 5733 selected_value: None, 5734 pattern_variable_under_cursor: None, 5735 edits: TextEdits::new(line_numbers), 5736 } 5737 } 5738 5739 pub fn code_actions(mut self) -> Vec<CodeAction> { 5740 self.visit_typed_module(&self.module.ast); 5741 5742 let action_title = match self.selected_value.clone() { 5743 Some(PatternMatchedValue::FunctionArgument { 5744 arg, 5745 first_statement: function_body, 5746 function_range, 5747 }) => { 5748 self.match_on_function_argument(arg, function_body, function_range); 5749 "Pattern match on argument" 5750 } 5751 Some( 5752 PatternMatchedValue::LetVariable { 5753 variable_name, 5754 variable_type, 5755 assignment_location: location, 5756 } 5757 | PatternMatchedValue::UseVariable { 5758 variable_name, 5759 variable_type, 5760 use_location: location, 5761 }, 5762 ) => { 5763 self.match_on_let_variable(variable_name, variable_type, location); 5764 "Pattern match on variable" 5765 } 5766 5767 Some(PatternMatchedValue::Statement { location, type_ }) => { 5768 self.match_on_statement(location, type_); 5769 "Pattern match on value" 5770 } 5771 5772 Some(PatternMatchedValue::ClausePatternVariable { 5773 variable_type, 5774 variable_location, 5775 clause_location, 5776 bound_variables, 5777 }) => { 5778 let title = if variable_location.is_discard() { 5779 "Pattern match on value" 5780 } else { 5781 "Pattern match on variable" 5782 }; 5783 5784 self.match_on_clause_variable( 5785 variable_type, 5786 variable_location, 5787 clause_location, 5788 &bound_variables, 5789 ); 5790 5791 title 5792 } 5793 5794 None => return vec![], 5795 }; 5796 5797 if self.edits.edits.is_empty() { 5798 return vec![]; 5799 } 5800 5801 let mut action = Vec::with_capacity(1); 5802 CodeActionBuilder::new(action_title) 5803 .kind(CodeActionKind::RefactorRewrite) 5804 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5805 .preferred(false) 5806 .push_to(&mut action); 5807 action 5808 } 5809 5810 fn match_on_function_argument( 5811 &mut self, 5812 arg: &TypedArg, 5813 first_statement: &TypedStatement, 5814 function_range: Range, 5815 ) { 5816 let Some(arg_name) = arg.get_variable_name() else { 5817 return; 5818 }; 5819 5820 let Some(patterns) = 5821 self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new()) 5822 else { 5823 return; 5824 }; 5825 5826 let first_statement_location = first_statement.location(); 5827 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location); 5828 5829 // If we're trying to insert the pattern matching on the same 5830 // line as the one where the function is defined we will want to 5831 // put it on a new line instead. So in that case the nesting will 5832 // be the default 2 spaces. 5833 let needs_newline = function_range.start.line == first_statement_range.start.line; 5834 let nesting = if needs_newline { 5835 String::from(" ") 5836 } else { 5837 " ".repeat(first_statement_range.start.character as usize) 5838 }; 5839 5840 let pattern_matching = if patterns.len() == 1 { 5841 let pattern = patterns.first(); 5842 format!("let {pattern} = {arg_name}") 5843 } else { 5844 let patterns = patterns 5845 .iter() 5846 .map(|p| format!(" {nesting}{p} -> todo")) 5847 .join("\n"); 5848 format!("case {arg_name} {{\n{patterns}\n{nesting}}}") 5849 }; 5850 5851 let pattern_matching = if needs_newline { 5852 format!("\n{nesting}{pattern_matching}") 5853 } else { 5854 pattern_matching 5855 }; 5856 5857 let has_empty_body = match first_statement { 5858 ast::Statement::Expression(TypedExpr::Todo { 5859 kind: TodoKind::EmptyFunction { .. }, 5860 .. 5861 }) => true, 5862 ast::Statement::Expression(_) 5863 | ast::Statement::Assignment(_) 5864 | ast::Statement::Use(_) 5865 | ast::Statement::Assert(_) => false, 5866 }; 5867 5868 // If the pattern matching is added to a function with an empty 5869 // body then we do not add any nesting after it, or we would be 5870 // increasing the nesting of the closing `}`! 5871 let pattern_matching = if has_empty_body { 5872 format!("{pattern_matching}\n") 5873 } else { 5874 format!("{pattern_matching}\n{nesting}") 5875 }; 5876 5877 self.edits 5878 .insert(first_statement_location.start, pattern_matching); 5879 } 5880 5881 fn match_on_let_variable( 5882 &mut self, 5883 variable_name: &EcoString, 5884 variable_type: Arc<Type>, 5885 assignment_location: SrcSpan, 5886 ) { 5887 let Some(patterns) = 5888 self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new()) 5889 else { 5890 return; 5891 }; 5892 5893 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); 5894 let nesting = " ".repeat(assignment_range.start.character as usize); 5895 let pattern_matching = if patterns.len() == 1 { 5896 let pattern = patterns.first(); 5897 format!("let {pattern} = {variable_name}") 5898 } else { 5899 let patterns = patterns 5900 .iter() 5901 .map(|p| format!(" {nesting}{p} -> todo")) 5902 .join("\n"); 5903 format!("case {variable_name} {{\n{patterns}\n{nesting}}}") 5904 }; 5905 5906 self.edits.insert( 5907 assignment_location.end, 5908 format!("\n{nesting}{pattern_matching}"), 5909 ); 5910 } 5911 5912 fn match_on_statement(&mut self, statement_location: SrcSpan, type_: Arc<Type>) { 5913 let Some(patterns) = 5914 self.type_to_destructure_patterns(type_.as_ref(), &mut NameGenerator::new()) 5915 else { 5916 return; 5917 }; 5918 5919 if patterns.len() == 1 { 5920 let pattern = patterns.first(); 5921 self.edits 5922 .insert(statement_location.start, format!("let {pattern} = ")); 5923 } else { 5924 let statement_range = self.edits.src_span_to_lsp_range(statement_location); 5925 let nesting = " ".repeat(statement_range.start.character as usize); 5926 let patterns = patterns 5927 .iter() 5928 .map(|p| format!(" {nesting}{p} -> todo")) 5929 .join("\n"); 5930 self.edits.insert(statement_location.start, "case ".into()); 5931 self.edits.insert( 5932 statement_location.end, 5933 format!(" {{\n{patterns}\n{nesting}}}"), 5934 ) 5935 } 5936 } 5937 5938 fn match_on_clause_variable( 5939 &mut self, 5940 variable_type: Arc<Type>, 5941 variable_location: PatternLocation, 5942 clause_location: SrcSpan, 5943 bound_variables: &[BoundVariable], 5944 ) { 5945 let mut names = NameGenerator::new(); 5946 names.reserve_bound_variables(bound_variables); 5947 5948 let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) { 5949 // Here we're dealing with a special case: if someone wants to expand the tail 5950 // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`. 5951 // That would result in invalid syntax. So we have to generate list patterns 5952 // that have no square brackets. 5953 let first = names.rename_to_avoid_shadowing("first".into()); 5954 let rest = names.rename_to_avoid_shadowing("rest".into()); 5955 vec1!["".into(), eco_format!("{first}, ..{rest}")] 5956 } else if let Some(patterns) = 5957 self.type_to_destructure_patterns(variable_type.as_ref(), &mut names) 5958 { 5959 patterns 5960 } else { 5961 return; 5962 }; 5963 5964 let clause_range = self.edits.src_span_to_lsp_range(clause_location); 5965 let nesting = " ".repeat(clause_range.start.character as usize); 5966 5967 let variable_location = match variable_location { 5968 PatternLocation::Regular { location } 5969 | PatternLocation::ListTail { location } 5970 | PatternLocation::Discard { location } => location, 5971 }; 5972 5973 let variable_start = (variable_location.start - clause_location.start) as usize; 5974 let variable_end = variable_start + variable_location.len(); 5975 5976 let clause_code = code_at(self.module, clause_location); 5977 let patterns = patterns 5978 .iter() 5979 .map(|pattern| { 5980 let mut clause_code = clause_code.to_string(); 5981 // If we're replacing a variable that's using the shorthand 5982 // syntax we want to add a space to separate it from the 5983 // preceding `:`. 5984 let pattern = if variable_start == variable_end { 5985 &eco_format!(" {pattern}") 5986 } else { 5987 pattern 5988 }; 5989 5990 clause_code.replace_range(variable_start..variable_end, pattern); 5991 clause_code 5992 }) 5993 .join(&format!("\n{nesting}")); 5994 5995 self.edits.replace(clause_location, patterns); 5996 } 5997 5998 /// Will produce a pattern that can be used on the left hand side of a let 5999 /// assignment to destructure a value of the given type. For example given 6000 /// this type: 6001 /// 6002 /// ```gleam 6003 /// pub type Wibble { 6004 /// Wobble(Int, label: String) 6005 /// } 6006 /// ``` 6007 /// 6008 /// The produced pattern will look like this: `Wobble(value_0, label:)`. 6009 /// The pattern will use the correct qualified/unqualified name for the 6010 /// constructor if it comes from another package. 6011 /// 6012 /// The function will only produce a list of patterns that can be used from 6013 /// the current module. So if the type comes from another module it must be 6014 /// public! Otherwise this function will return an empty vec. 6015 /// 6016 fn type_to_destructure_patterns( 6017 &mut self, 6018 type_: &Type, 6019 names: &mut NameGenerator, 6020 ) -> Option<Vec1<EcoString>> { 6021 match type_ { 6022 Type::Fn { .. } => None, 6023 6024 // Pattern matchin on `Nil` is not all that useful. 6025 Type::Named { .. } if type_.is_nil() => None, 6026 6027 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names), 6028 6029 // We special case lists, they don't have "regular" constructors 6030 // like other types. Instead we always add the two clauses covering 6031 // the empty and non empty list. 6032 Type::Named { .. } if type_.is_list() => { 6033 let first = names.rename_to_avoid_shadowing("first".into()); 6034 let rest = names.rename_to_avoid_shadowing("rest".into()); 6035 Some(vec1![ 6036 EcoString::from("[]"), 6037 eco_format!("[{first}, ..{rest}]") 6038 ]) 6039 } 6040 6041 Type::Named { 6042 module: type_module, 6043 name: type_name, 6044 .. 6045 } => { 6046 let mut patterns = vec![]; 6047 let constructors = 6048 get_type_constructors(self.compiler, &self.module.name, type_module, type_name); 6049 for constructor in constructors { 6050 let names_before = names.clone(); 6051 if let Some(pattern) = 6052 self.record_constructor_to_destructure_pattern(constructor, names) 6053 { 6054 patterns.push(pattern); 6055 } 6056 *names = names_before; 6057 } 6058 6059 Vec1::try_from_vec(patterns).ok() 6060 } 6061 6062 // We don't want to suggest this action for empty tuple as it 6063 // doesn't make a lot of sense to match on those. 6064 Type::Tuple { elements } if elements.is_empty() => None, 6065 Type::Tuple { elements } => { 6066 let elements = elements 6067 .iter() 6068 .map(|element| names.generate_name_from_type(element)) 6069 .join(", "); 6070 Some(vec1![eco_format!("#({elements})")]) 6071 } 6072 } 6073 } 6074 6075 fn type_var_to_destructure_patterns( 6076 &mut self, 6077 type_var: &TypeVar, 6078 names: &mut NameGenerator, 6079 ) -> Option<Vec1<EcoString>> { 6080 match type_var { 6081 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, 6082 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names), 6083 } 6084 } 6085 6086 /// Given the value constructor of a record, returns a string with the 6087 /// pattern used to match on that specific variant. 6088 /// 6089 /// Note how: 6090 /// - If the constructor is internal to another module or comes from another 6091 /// module, then this returns `None` since one cannot pattern match on it. 6092 /// - If the provided `ValueConstructor` is not a record constructor this 6093 /// will return `None`. 6094 /// 6095 fn record_constructor_to_destructure_pattern( 6096 &self, 6097 constructor: &ValueConstructor, 6098 names: &mut NameGenerator, 6099 ) -> Option<EcoString> { 6100 let type_::ValueConstructorVariant::Record { 6101 name: constructor_name, 6102 arity: constructor_arity, 6103 module: constructor_module, 6104 field_map, 6105 .. 6106 } = &constructor.variant 6107 else { 6108 // The constructor should always be a record, in case it's not 6109 // there's not much we can do and just fail. 6110 return None; 6111 }; 6112 6113 // Since the constructor is a record constructor we know that its type 6114 // is either `Named` or a `Fn` type, in either case we have to get the 6115 // arguments types out of it. 6116 let Some(arguments_types) = constructor 6117 .type_ 6118 .fn_types() 6119 .map(|(arguments_types, _return)| arguments_types) 6120 .or_else(|| constructor.type_.constructor_types()) 6121 else { 6122 // This should never happen but just in case we don't want to unwrap 6123 // and panic. 6124 return None; 6125 }; 6126 6127 let index_to_label = match field_map { 6128 None => HashMap::new(), 6129 Some(field_map) => { 6130 names.reserve_all_labels(field_map); 6131 6132 field_map 6133 .fields 6134 .iter() 6135 .map(|(label, index)| (index, label)) 6136 .collect::<HashMap<_, _>>() 6137 } 6138 }; 6139 6140 let mut pattern = 6141 pretty_constructor_name(self.module, constructor_module, constructor_name)?; 6142 6143 if *constructor_arity == 0 { 6144 return Some(pattern); 6145 } 6146 6147 pattern.push('('); 6148 let arguments = (0..*constructor_arity as u32) 6149 .map(|i| match index_to_label.get(&i) { 6150 Some(label) => eco_format!("{label}:"), 6151 None => match arguments_types.get(i as usize) { 6152 None => names.rename_to_avoid_shadowing(EcoString::from("value")), 6153 Some(type_) => names.generate_name_from_type(type_), 6154 }, 6155 }) 6156 .join(", "); 6157 6158 pattern.push_str(&arguments); 6159 pattern.push(')'); 6160 Some(pattern) 6161 } 6162} 6163 6164fn code_at(module: &Module, span: SrcSpan) -> &str { 6165 module 6166 .code 6167 .get(span.start as usize..span.end as usize) 6168 .expect("code location must be valid") 6169} 6170 6171impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> { 6172 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 6173 // If we're not inside the function there's no point in exploring its 6174 // ast further. 6175 let function_span = SrcSpan { 6176 start: fun.location.start, 6177 end: fun.end_position, 6178 }; 6179 let function_range = self.edits.src_span_to_lsp_range(function_span); 6180 if !within(self.params.range, function_range) { 6181 return; 6182 } 6183 6184 for arg in &fun.arguments { 6185 // If the cursor is placed on one of the arguments, then we can try 6186 // and generate code for that one. 6187 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 6188 if within(self.params.range, arg_range) 6189 && let Some(first_statement) = fun.body.first() 6190 { 6191 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 6192 arg, 6193 first_statement, 6194 function_range, 6195 }); 6196 return; 6197 } 6198 } 6199 6200 // If the cursor is not on any of the function arguments then we keep 6201 // exploring the function body as we might want to destructure the 6202 // argument of an expression function! 6203 ast::visit::visit_typed_function(self, fun); 6204 } 6205 6206 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 6207 let statement_range = self.edits.src_span_to_lsp_range(statement.location()); 6208 if !within(self.params.range, statement_range) { 6209 return; 6210 } 6211 6212 ast::visit::visit_typed_statement(self, statement); 6213 match statement { 6214 // If we haven't found any more specific selected expression, then 6215 // we are going to select the statement to pattern match on (if it 6216 // can be meaningfully pattern matched on). 6217 ast::Statement::Expression( 6218 TypedExpr::Call { type_, .. } 6219 | TypedExpr::ModuleSelect { type_, .. } 6220 | TypedExpr::RecordAccess { type_, .. } 6221 | TypedExpr::TupleIndex { type_, .. }, 6222 ) if self.selected_value.is_none() => { 6223 self.selected_value = Some(PatternMatchedValue::Statement { 6224 location: statement.location(), 6225 type_: type_.clone(), 6226 }) 6227 } 6228 6229 ast::Statement::Expression(_) 6230 | ast::Statement::Assignment(_) 6231 | ast::Statement::Use(_) 6232 | ast::Statement::Assert(_) => (), 6233 } 6234 } 6235 6236 fn visit_typed_expr_fn( 6237 &mut self, 6238 location: &'ast SrcSpan, 6239 type_: &'ast Arc<Type>, 6240 kind: &'ast FunctionLiteralKind, 6241 arguments: &'ast [TypedArg], 6242 body: &'ast Vec1<TypedStatement>, 6243 return_annotation: &'ast Option<ast::TypeAst>, 6244 ) { 6245 // If we're not inside the function there's no point in exploring its 6246 // ast further. 6247 let function_range = self.edits.src_span_to_lsp_range(*location); 6248 if !within(self.params.range, function_range) { 6249 return; 6250 } 6251 6252 for argument in arguments { 6253 // If the cursor is placed on one of the arguments, then we can try 6254 // and generate code for that one. 6255 let arg_range = self.edits.src_span_to_lsp_range(argument.location); 6256 if within(self.params.range, arg_range) { 6257 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 6258 arg: argument, 6259 first_statement: body.first(), 6260 function_range, 6261 }); 6262 return; 6263 } 6264 } 6265 6266 // If the cursor is not on any of the function arguments then we keep 6267 // exploring the function body as we might want to destructure the 6268 // argument of an expression function! 6269 ast::visit::visit_typed_expr_fn( 6270 self, 6271 location, 6272 type_, 6273 kind, 6274 arguments, 6275 body, 6276 return_annotation, 6277 ); 6278 } 6279 6280 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 6281 // If we're not inside the assignment there's no point in exploring its 6282 // ast further. 6283 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 6284 if !within(self.params.range, assignment_range) { 6285 return; 6286 } 6287 6288 ast::visit::visit_typed_assignment(self, assignment); 6289 if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor 6290 // We must make sure that no other value was selected while visiting 6291 // this. If it were `Some` that means that we have found _another_ 6292 // variable to match on inside the assignmemt itself. For example: 6293 // ```gleam 6294 // let a = { 6295 // let b = todo 6296 // // ^ We're matching on this, not the outer one! 6297 // } 6298 // ``` 6299 && self.selected_value.is_none() 6300 { 6301 self.selected_value = Some(PatternMatchedValue::LetVariable { 6302 variable_name: name, 6303 variable_type: type_.clone(), 6304 assignment_location: assignment.location, 6305 }); 6306 } 6307 } 6308 6309 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 6310 // If we're not inside the clause there's no point in exploring its 6311 // ast further. 6312 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 6313 if !within(self.params.range, clause_range) { 6314 return; 6315 } 6316 6317 for pattern in clause.pattern.iter() { 6318 self.visit_typed_pattern(pattern); 6319 } 6320 for patterns in clause.alternative_patterns.iter() { 6321 for pattern in patterns { 6322 self.visit_typed_pattern(pattern); 6323 } 6324 } 6325 6326 if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() { 6327 self.selected_value = Some(PatternMatchedValue::ClausePatternVariable { 6328 variable_type: type_, 6329 variable_location, 6330 clause_location: clause.location(), 6331 bound_variables: clause.bound_variables().collect_vec(), 6332 }); 6333 } else { 6334 self.visit_typed_expr(&clause.then); 6335 } 6336 } 6337 6338 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 6339 if let Some(assignments) = use_.callback_arguments() { 6340 for variable in assignments { 6341 let ast::Arg { 6342 names: ArgNames::Named { name, .. }, 6343 location: variable_location, 6344 type_, 6345 .. 6346 } = variable 6347 else { 6348 continue; 6349 }; 6350 6351 // If we use a pattern in a use assignment, that will end up 6352 // being called `_use` something. We don't want to offer the 6353 // action when hovering a pattern so we ignore those. 6354 if name.starts_with("_use") { 6355 continue; 6356 } 6357 6358 let variable_range = self.edits.src_span_to_lsp_range(*variable_location); 6359 if within(self.params.range, variable_range) { 6360 self.selected_value = Some(PatternMatchedValue::UseVariable { 6361 variable_name: name, 6362 variable_type: type_.clone(), 6363 use_location: use_.location, 6364 }); 6365 // If we've found the variable to pattern match on, there's no 6366 // point in keeping traversing the AST. 6367 return; 6368 } 6369 } 6370 } 6371 6372 ast::visit::visit_typed_use(self, use_); 6373 } 6374 6375 fn visit_typed_pattern_discard( 6376 &mut self, 6377 location: &'ast SrcSpan, 6378 name: &'ast EcoString, 6379 type_: &'ast Arc<Type>, 6380 ) { 6381 if within( 6382 self.params.range, 6383 self.edits.src_span_to_lsp_range(*location), 6384 ) { 6385 let location = PatternLocation::Discard { 6386 location: *location, 6387 }; 6388 self.pattern_variable_under_cursor = Some((name, location, type_.clone())); 6389 } 6390 } 6391 6392 fn visit_typed_pattern_variable( 6393 &mut self, 6394 location: &'ast SrcSpan, 6395 name: &'ast EcoString, 6396 type_: &'ast Arc<Type>, 6397 _origin: &'ast VariableOrigin, 6398 ) { 6399 if within( 6400 self.params.range, 6401 self.edits.src_span_to_lsp_range(*location), 6402 ) { 6403 let location = PatternLocation::regular(*location); 6404 self.pattern_variable_under_cursor = Some((name, location, type_.clone())); 6405 } 6406 } 6407 6408 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 6409 if let Some(name) = arg.label_shorthand_name() 6410 && within( 6411 self.params.range, 6412 self.edits.src_span_to_lsp_range(arg.location), 6413 ) 6414 { 6415 let location = PatternLocation::regular(SrcSpan { 6416 start: arg.location.end, 6417 end: arg.location.end, 6418 }); 6419 self.pattern_variable_under_cursor = Some((name, location, arg.value.type_())); 6420 return; 6421 } 6422 6423 ast::visit::visit_typed_pattern_call_arg(self, arg); 6424 } 6425 6426 fn visit_typed_pattern_string_prefix( 6427 &mut self, 6428 _location: &'ast SrcSpan, 6429 _left_location: &'ast SrcSpan, 6430 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 6431 right_location: &'ast SrcSpan, 6432 _left_side_string: &'ast EcoString, 6433 right_side_assignment: &'ast AssignName, 6434 ) { 6435 if let Some((name, location)) = left_side_assignment 6436 && within( 6437 self.params.range, 6438 self.edits.src_span_to_lsp_range(*location), 6439 ) 6440 { 6441 let location = PatternLocation::regular(*location); 6442 self.pattern_variable_under_cursor = Some((name, location, type_::string())); 6443 } else if let AssignName::Variable(name) = right_side_assignment 6444 && within( 6445 self.params.range, 6446 self.edits.src_span_to_lsp_range(*right_location), 6447 ) 6448 { 6449 let location = PatternLocation::regular(*right_location); 6450 self.pattern_variable_under_cursor = Some((name, location, type_::string())); 6451 } 6452 } 6453 6454 fn visit_typed_pattern_list( 6455 &mut self, 6456 location: &'ast SrcSpan, 6457 elements: &'ast Vec<TypedPattern>, 6458 tail: &'ast Option<Box<TypedTailPattern>>, 6459 type_: &'ast Arc<Type>, 6460 ) { 6461 let (name, tail_location, tail_type) = if let Some(tail) = tail 6462 && let Pattern::Variable { name, type_, .. } = &tail.pattern 6463 { 6464 (name, tail.location, type_) 6465 } else { 6466 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); 6467 return; 6468 }; 6469 6470 let tail_range = self.edits.src_span_to_lsp_range(tail_location); 6471 if !within(self.params.range, tail_range) { 6472 ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); 6473 return; 6474 } 6475 6476 let location = PatternLocation::ListTail { 6477 location: tail_location, 6478 }; 6479 self.pattern_variable_under_cursor = Some((name, location, tail_type.clone())) 6480 } 6481} 6482 6483/// Given a type and its module, returns a list of its *importable* 6484/// constructors. 6485/// 6486/// Since this focuses just on importable constructors, if either the module or 6487/// the type are internal the returned array will be empty! 6488/// 6489fn get_type_constructors<'a, 'b, IO>( 6490 compiler: &'a LspProjectCompiler<IO>, 6491 current_module: &'b EcoString, 6492 type_module: &'b EcoString, 6493 type_name: &'b EcoString, 6494) -> Vec<&'a ValueConstructor> { 6495 let type_is_inside_current_module = current_module == type_module; 6496 let module_interface = if !type_is_inside_current_module { 6497 // If the type is outside of the module we're in, we can only pattern 6498 // match on it if the module can be imported. 6499 // The `get_module_interface` already takes care of making this check. 6500 compiler.get_module_interface(type_module) 6501 } else { 6502 // However, if the type is defined in the module we're in, we can always 6503 // pattern match on it. So we get the current module's interface. 6504 compiler 6505 .modules 6506 .get(current_module) 6507 .map(|module| &module.ast.type_info) 6508 }; 6509 6510 let Some(module_interface) = module_interface else { 6511 return vec![]; 6512 }; 6513 6514 // If the type is in an internal module that is not the current one, we 6515 // cannot use its constructors! 6516 if !type_is_inside_current_module && module_interface.is_internal { 6517 return vec![]; 6518 } 6519 6520 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else { 6521 return vec![]; 6522 }; 6523 6524 constructors 6525 .variants 6526 .iter() 6527 .filter_map(|variant| { 6528 let constructor = module_interface.values.get(&variant.name)?; 6529 if type_is_inside_current_module || constructor.publicity.is_public() { 6530 Some(constructor) 6531 } else { 6532 None 6533 } 6534 }) 6535 .collect_vec() 6536} 6537 6538/// Returns a pretty printed record constructor name, the way it would be used 6539/// inside the given `module` (with the correct name and qualification). 6540/// 6541/// If the constructor cannot be used inside the module because it's not 6542/// imported, then this function will return `None`. 6543/// 6544fn pretty_constructor_name( 6545 module: &Module, 6546 constructor_module: &EcoString, 6547 constructor_name: &EcoString, 6548) -> Option<EcoString> { 6549 match module 6550 .ast 6551 .names 6552 .named_constructor(constructor_module, constructor_name) 6553 { 6554 type_::printer::NameContextInformation::Unimported(_, _) => None, 6555 type_::printer::NameContextInformation::Unqualified(constructor_name) => { 6556 Some(eco_format!("{constructor_name}")) 6557 } 6558 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => { 6559 Some(eco_format!("{module_name}.{constructor_name}")) 6560 } 6561 } 6562} 6563 6564/// Builder for the "generate function" code action. 6565/// Whenever someone hovers an invalid expression that is inferred to have a 6566/// function type the language server can generate a function definition for it. 6567/// For example: 6568/// 6569/// ```gleam 6570/// pub fn main() { 6571/// wibble(1, 2, "hello") 6572/// // ^ [generate function] 6573/// } 6574/// ``` 6575/// 6576/// Will generate the following definition: 6577/// 6578/// ```gleam 6579/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a { 6580/// todo 6581/// } 6582/// ``` 6583/// 6584pub struct GenerateFunction<'a> { 6585 module: &'a Module, 6586 modules: &'a std::collections::HashMap<EcoString, Module>, 6587 params: &'a CodeActionParams, 6588 edits: TextEdits<'a>, 6589 last_visited_definition_end: Option<u32>, 6590 function_to_generate: Option<FunctionToGenerate<'a>>, 6591} 6592 6593struct FunctionToGenerate<'a> { 6594 module: Option<&'a str>, 6595 name: &'a str, 6596 arguments_types: Vec<Arc<Type>>, 6597 6598 /// The arguments actually supplied as input to the function, if any. 6599 /// A function to generate might as well be just a name passed as an argument 6600 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually 6601 /// have any actual arguments! 6602 given_arguments: Option<&'a [TypedCallArg]>, 6603 return_type: Arc<Type>, 6604 previous_definition_end: Option<u32>, 6605} 6606 6607impl<'a> GenerateFunction<'a> { 6608 pub fn new( 6609 module: &'a Module, 6610 modules: &'a std::collections::HashMap<EcoString, Module>, 6611 line_numbers: &'a LineNumbers, 6612 params: &'a CodeActionParams, 6613 ) -> Self { 6614 Self { 6615 module, 6616 modules, 6617 params, 6618 edits: TextEdits::new(line_numbers), 6619 last_visited_definition_end: None, 6620 function_to_generate: None, 6621 } 6622 } 6623 6624 pub fn code_actions(mut self) -> Vec<CodeAction> { 6625 self.visit_typed_module(&self.module.ast); 6626 6627 let Some( 6628 function_to_generate @ FunctionToGenerate { 6629 module, 6630 previous_definition_end: Some(insert_at), 6631 .. 6632 }, 6633 ) = self.function_to_generate.take() 6634 else { 6635 return vec![]; 6636 }; 6637 6638 if let Some(module) = module { 6639 if let Some(module) = self.modules.get(module) { 6640 let insert_at = module.code.len() as u32; 6641 self.code_action_for_module( 6642 module, 6643 Publicity::Public, 6644 function_to_generate, 6645 insert_at, 6646 ) 6647 } else { 6648 Vec::new() 6649 } 6650 } else { 6651 let module = self.module; 6652 self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at) 6653 } 6654 } 6655 6656 fn code_action_for_module( 6657 mut self, 6658 module: &'a Module, 6659 publicity: Publicity, 6660 function_to_generate: FunctionToGenerate<'a>, 6661 insert_at: u32, 6662 ) -> Vec<CodeAction> { 6663 let FunctionToGenerate { 6664 name, 6665 arguments_types, 6666 given_arguments, 6667 return_type, 6668 .. 6669 } = function_to_generate; 6670 6671 // This might be triggered on variants as well, in that case we don't 6672 // want to offer this action. The "generate variant" action will be 6673 // offered instead. 6674 if !is_valid_lowercase_name(name) { 6675 return vec![]; 6676 } 6677 6678 // Labels do not share the same namespace as argument so we use two 6679 // separate generators to avoid renaming a label in case it shares a 6680 // name with an argument. 6681 let mut label_names = NameGenerator::new(); 6682 let mut argument_names = NameGenerator::new(); 6683 6684 // Since we are generating a new function, type variables from other 6685 // functions and constants are irrelevant to the types we print. 6686 let mut printer = Printer::new_without_type_variables(&module.ast.names); 6687 let arguments = arguments_types 6688 .iter() 6689 .enumerate() 6690 .map(|(index, argument_type)| { 6691 let call_argument = given_arguments.and_then(|arguments| arguments.get(index)); 6692 let (label, name) = 6693 argument_names.generate_label_and_name(call_argument, argument_type); 6694 let pretty_type = printer.print_type(argument_type); 6695 if let Some(label) = label { 6696 let label = label_names.rename_to_avoid_shadowing(label); 6697 format!("{label} {name}: {pretty_type}") 6698 } else { 6699 format!("{name}: {pretty_type}") 6700 } 6701 }) 6702 .join(", "); 6703 6704 let return_type = printer.print_type(&return_type); 6705 6706 let publicity = if publicity.is_public() { "pub " } else { "" }; 6707 6708 // Make sure we use the line number information of the module we are 6709 // editing, which might not be the module where the code action is 6710 // triggered. 6711 self.edits.line_numbers = &module.ast.type_info.line_numbers; 6712 self.edits.insert( 6713 insert_at, 6714 format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"), 6715 ); 6716 6717 let Some(uri) = url_from_path(module.input_path.as_str()) else { 6718 return Vec::new(); 6719 }; 6720 let mut action = Vec::with_capacity(1); 6721 CodeActionBuilder::new("Generate function") 6722 .kind(CodeActionKind::QuickFix) 6723 .changes(uri, self.edits.edits) 6724 .preferred(true) 6725 .push_to(&mut action); 6726 action 6727 } 6728 6729 fn try_save_function_to_generate( 6730 &mut self, 6731 name: &'a EcoString, 6732 function_type: &Arc<Type>, 6733 given_arguments: Option<&'a [TypedCallArg]>, 6734 ) { 6735 match function_type.fn_types() { 6736 None => {} 6737 Some((arguments_types, return_type)) => { 6738 self.function_to_generate = Some(FunctionToGenerate { 6739 name, 6740 arguments_types, 6741 given_arguments, 6742 return_type, 6743 previous_definition_end: self.last_visited_definition_end, 6744 module: None, 6745 }) 6746 } 6747 } 6748 } 6749 6750 fn try_save_function_from_other_module( 6751 &mut self, 6752 module: &'a str, 6753 name: &'a str, 6754 function_type: &Arc<Type>, 6755 given_arguments: Option<&'a [TypedCallArg]>, 6756 ) { 6757 if let Some((arguments_types, return_type)) = function_type.fn_types() 6758 && is_valid_lowercase_name(name) 6759 { 6760 self.function_to_generate = Some(FunctionToGenerate { 6761 name, 6762 arguments_types, 6763 given_arguments, 6764 return_type, 6765 previous_definition_end: self.last_visited_definition_end, 6766 module: Some(module), 6767 }) 6768 } 6769 } 6770} 6771 6772impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> { 6773 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 6774 self.last_visited_definition_end = Some(fun.end_position); 6775 ast::visit::visit_typed_function(self, fun); 6776 } 6777 6778 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 6779 self.last_visited_definition_end = Some(constant.value.location().end); 6780 ast::visit::visit_typed_module_constant(self, constant); 6781 } 6782 6783 fn visit_typed_expr_invalid( 6784 &mut self, 6785 location: &'ast SrcSpan, 6786 type_: &'ast Arc<Type>, 6787 extra_information: &'ast Option<InvalidExpression>, 6788 ) { 6789 let invalid_range = self.edits.src_span_to_lsp_range(*location); 6790 if within(self.params.range, invalid_range) { 6791 match extra_information { 6792 Some(InvalidExpression::ModuleSelect { module_name, label }) => { 6793 self.try_save_function_from_other_module(module_name, label, type_, None) 6794 } 6795 Some(InvalidExpression::UnknownVariable { name }) => { 6796 self.try_save_function_to_generate(name, type_, None) 6797 } 6798 None => {} 6799 } 6800 } 6801 6802 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); 6803 } 6804 6805 fn visit_typed_constant_invalid( 6806 &mut self, 6807 location: &'ast SrcSpan, 6808 type_: &'ast Arc<Type>, 6809 extra_information: &'ast Option<InvalidExpression>, 6810 ) { 6811 let constant_range = self.edits.src_span_to_lsp_range(*location); 6812 if let Some(extra_information) = extra_information 6813 && within(self.params.range, constant_range) 6814 { 6815 match extra_information { 6816 InvalidExpression::ModuleSelect { module_name, label } => { 6817 self.try_save_function_from_other_module(module_name, label, type_, None) 6818 } 6819 InvalidExpression::UnknownVariable { name } => { 6820 self.try_save_function_to_generate(name, type_, None) 6821 } 6822 } 6823 } 6824 } 6825 6826 fn visit_typed_expr_call( 6827 &mut self, 6828 location: &'ast SrcSpan, 6829 type_: &'ast Arc<Type>, 6830 fun: &'ast TypedExpr, 6831 arguments: &'ast [TypedCallArg], 6832 argument_parentheses: &'ast Option<u32>, 6833 ) { 6834 // If the function being called is invalid we need to generate a 6835 // function that has the proper labels. 6836 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 6837 6838 if within(self.params.range, fun_range) { 6839 if !labels_are_correct(arguments) { 6840 return; 6841 } 6842 6843 match fun { 6844 TypedExpr::Invalid { 6845 type_, 6846 extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }), 6847 location: _, 6848 } => { 6849 return self.try_save_function_from_other_module( 6850 module_name, 6851 label, 6852 type_, 6853 Some(arguments), 6854 ); 6855 } 6856 TypedExpr::Invalid { 6857 type_, 6858 extra_information: Some(InvalidExpression::UnknownVariable { name }), 6859 location: _, 6860 } => { 6861 return self.try_save_function_to_generate(name, type_, Some(arguments)); 6862 } 6863 TypedExpr::Int { .. } 6864 | TypedExpr::Float { .. } 6865 | TypedExpr::String { .. } 6866 | TypedExpr::Block { .. } 6867 | TypedExpr::Pipeline { .. } 6868 | TypedExpr::Var { .. } 6869 | TypedExpr::Fn { .. } 6870 | TypedExpr::List { .. } 6871 | TypedExpr::Call { .. } 6872 | TypedExpr::BinOp { .. } 6873 | TypedExpr::Case { .. } 6874 | TypedExpr::RecordAccess { .. } 6875 | TypedExpr::PositionalAccess { .. } 6876 | TypedExpr::ModuleSelect { .. } 6877 | TypedExpr::Tuple { .. } 6878 | TypedExpr::TupleIndex { .. } 6879 | TypedExpr::Todo { .. } 6880 | TypedExpr::Panic { .. } 6881 | TypedExpr::Echo { .. } 6882 | TypedExpr::BitArray { .. } 6883 | TypedExpr::RecordUpdate { .. } 6884 | TypedExpr::NegateBool { .. } 6885 | TypedExpr::NegateInt { .. } 6886 | TypedExpr::Invalid { .. } => {} 6887 } 6888 } 6889 ast::visit::visit_typed_expr_call( 6890 self, 6891 location, 6892 type_, 6893 fun, 6894 arguments, 6895 argument_parentheses, 6896 ); 6897 } 6898} 6899 6900/// Builder for the "generate variant" code action. This will generate a variant 6901/// for a type if it can tell the type it should come from. It will work with 6902/// non-existing variants both used as expressions 6903/// 6904/// ```gleam 6905/// let a = IDoNotExist(1) 6906/// // ^^^^^^^^^^^ It would generate this variant here 6907/// ``` 6908/// 6909/// And as patterns: 6910/// 6911/// ```gleam 6912/// let assert IDoNotExist(1) = todo 6913/// ^^^^^^^^^^^ It would generate this variant here 6914/// ``` 6915/// 6916pub struct GenerateVariant<'a, IO> { 6917 module: &'a Module, 6918 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 6919 params: &'a CodeActionParams, 6920 line_numbers: &'a LineNumbers, 6921 variant_to_generate: Option<VariantToGenerate<'a>>, 6922} 6923 6924struct VariantToGenerate<'a> { 6925 name: &'a str, 6926 6927 arguments_types: Vec<Arc<Type>>, 6928 6929 /// The start of the variant where the code action was triggered. 6930 /// For example: 6931 /// 6932 /// ```gleam 6933 /// Wobble 6934 /// ^ Trigger here to generate `Wobble` 6935 /// ^ The start is here! 6936 /// ``` 6937 variant_start: u32, 6938 6939 /// If the variant where we triggered the code action is already qualified. 6940 /// For example: 6941 /// 6942 /// ```gleam 6943 /// wibble.Wobble // -> true 6944 /// Wobble // -> false 6945 /// ``` 6946 /// 6947 is_qualified: bool, 6948 6949 /// Where the custom type to add this variant to ends. 6950 /// 6951 end_position: u32, 6952 6953 /// The already existing constructors of the custom type this new variant is 6954 /// going to be added to. 6955 /// 6956 constructors: &'a [RecordConstructor<Arc<Type>>], 6957 6958 /// Wether the type we're adding the variant to is written with braces or 6959 /// not. We need this information to add braces when missing. 6960 /// 6961 type_braces: TypeBraces, 6962 6963 /// The module this variant will be added to. 6964 /// 6965 module_name: EcoString, 6966 6967 /// The arguments actually supplied as input to the variant, if any. 6968 /// A variant to generate might as well be just a name passed as an argument 6969 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually 6970 /// have any actual arguments! 6971 /// 6972 given_arguments: Option<Arguments<'a>>, 6973} 6974 6975#[derive(Debug, Clone, Copy)] 6976enum TypeBraces { 6977 /// If the type is written like this: `pub type Wibble` 6978 HasBraces, 6979 /// If the type is written like this: `pub type Wibble {}` 6980 NoBraces, 6981} 6982 6983/// The arguments to an invalid call or pattern we can use to generate a variant. 6984/// 6985enum Arguments<'a> { 6986 /// These are the arguments provided to the invalid variant constructor 6987 /// when it's used as a function: `let a = Wibble(1, 2)`. 6988 /// 6989 Expressions(&'a [TypedCallArg]), 6990 /// These are the arguments provided to the invalid variant constructor when 6991 /// it's used in a pattern: `let assert Wibble(1, 2) = a` 6992 /// 6993 Patterns(&'a [CallArg<TypedPattern>]), 6994} 6995 6996/// An invalid variant might be used both as a pattern in a case expression or 6997/// as a regular value in an expression. We want to generate the variant in both 6998/// cases, so we use this enum to tell apart the two cases and be able to reuse 6999/// most of the code for both as they are very similar. 7000/// 7001enum Argument<'a> { 7002 Expression(&'a TypedCallArg), 7003 Pattern(&'a CallArg<TypedPattern>), 7004} 7005 7006impl<'a> Arguments<'a> { 7007 fn get(&self, index: usize) -> Option<Argument<'a>> { 7008 match self { 7009 Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern), 7010 Arguments::Expressions(call_arguments) => { 7011 call_arguments.get(index).map(Argument::Expression) 7012 } 7013 } 7014 } 7015 7016 fn types(&self) -> Vec<Arc<Type>> { 7017 match self { 7018 Arguments::Expressions(call_arguments) => call_arguments 7019 .iter() 7020 .map(|argument| argument.value.type_()) 7021 .collect_vec(), 7022 7023 Arguments::Patterns(call_arguments) => call_arguments 7024 .iter() 7025 .map(|argument| argument.value.type_()) 7026 .collect_vec(), 7027 } 7028 } 7029} 7030 7031impl Argument<'_> { 7032 fn label(&self) -> Option<EcoString> { 7033 match self { 7034 Argument::Expression(call_arg) => call_arg.label.clone(), 7035 Argument::Pattern(call_arg) => call_arg.label.clone(), 7036 } 7037 } 7038} 7039 7040enum GenerateVariantEdits<'a> { 7041 GenerateInCurrentModule { 7042 current_module_edits: TextEdits<'a>, 7043 }, 7044 GenerateInDifferentModule { 7045 current_module_edits: TextEdits<'a>, 7046 variant_module_edits: TextEdits<'a>, 7047 }, 7048} 7049 7050impl<'a, IO> GenerateVariant<'a, IO> { 7051 pub fn new( 7052 module: &'a Module, 7053 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 7054 line_numbers: &'a LineNumbers, 7055 params: &'a CodeActionParams, 7056 ) -> Self { 7057 Self { 7058 module, 7059 params, 7060 compiler, 7061 line_numbers, 7062 variant_to_generate: None, 7063 } 7064 } 7065 7066 pub fn code_actions(mut self) -> Vec<CodeAction> { 7067 self.visit_typed_module(&self.module.ast); 7068 7069 let Some(VariantToGenerate { 7070 name, 7071 constructors, 7072 arguments_types, 7073 given_arguments, 7074 module_name, 7075 end_position, 7076 type_braces, 7077 variant_start, 7078 is_qualified, 7079 }) = &self.variant_to_generate 7080 else { 7081 return vec![]; 7082 }; 7083 7084 // Now we need to figure out if we're going to have to edit just the current 7085 // module (because the variant will be added to a type that is defined there), 7086 // or if we'll have to edit both the current module (to import the newly 7087 // generated variant) and a different module (where the variant definition 7088 // is going to end up). 7089 let current_module_line_numbers = LineNumbers::new(&self.module.code); 7090 let current_module_edits = TextEdits::new(&current_module_line_numbers); 7091 let Some(variant_module) = self.compiler.modules.get(module_name) else { 7092 return vec![]; 7093 }; 7094 let variant_module_line_numbers = LineNumbers::new(&variant_module.code); 7095 let variant_module_edits = TextEdits::new(&variant_module_line_numbers); 7096 7097 let mut edits = if *module_name == self.module.name { 7098 GenerateVariantEdits::GenerateInCurrentModule { 7099 current_module_edits, 7100 } 7101 } else { 7102 GenerateVariantEdits::GenerateInDifferentModule { 7103 current_module_edits, 7104 variant_module_edits, 7105 } 7106 }; 7107 7108 self.edits_to_create_variant( 7109 &mut edits, 7110 name, 7111 arguments_types, 7112 given_arguments, 7113 *end_position, 7114 *type_braces, 7115 ); 7116 // If the variant is qualified already we don't have to do anything, 7117 // otherwise we need to import it in the current module. 7118 if !is_qualified { 7119 self.edits_to_import_variant( 7120 &mut edits, 7121 module_name, 7122 name, 7123 *variant_start, 7124 self.module, 7125 constructors, 7126 ); 7127 } 7128 7129 let mut builder = CodeActionBuilder::new("Generate variant") 7130 .kind(CodeActionKind::QuickFix) 7131 .preferred(true); 7132 7133 match edits { 7134 GenerateVariantEdits::GenerateInCurrentModule { 7135 current_module_edits, 7136 } => { 7137 builder = builder.changes( 7138 self.params.text_document.uri.clone(), 7139 current_module_edits.edits, 7140 ) 7141 } 7142 GenerateVariantEdits::GenerateInDifferentModule { 7143 current_module_edits, 7144 variant_module_edits, 7145 } => { 7146 let Some(variant_module_path) = url_from_path(variant_module.input_path.as_str()) 7147 else { 7148 return vec![]; 7149 }; 7150 7151 if !current_module_edits.edits.is_empty() { 7152 builder = builder.changes( 7153 self.params.text_document.uri.clone(), 7154 current_module_edits.edits, 7155 ); 7156 } 7157 7158 builder = builder.changes(variant_module_path, variant_module_edits.edits) 7159 } 7160 }; 7161 7162 let mut action = Vec::with_capacity(1); 7163 builder.push_to(&mut action); 7164 action 7165 } 7166 7167 /// Returns the edits needed to add this new variant to the given module. 7168 /// It also returns the uri of the module the edits should be applied to. 7169 /// 7170 fn edits_to_create_variant( 7171 &self, 7172 edits: &mut GenerateVariantEdits<'_>, 7173 variant_name: &str, 7174 arguments_types: &[Arc<Type>], 7175 given_arguments: &Option<Arguments<'_>>, 7176 end_position: u32, 7177 type_braces: TypeBraces, 7178 ) { 7179 let mut label_names = NameGenerator::new(); 7180 let mut printer = Printer::new(&self.module.ast.names); 7181 let arguments = arguments_types 7182 .iter() 7183 .enumerate() 7184 .map(|(index, argument_type)| { 7185 let label = given_arguments 7186 .as_ref() 7187 .and_then(|arguments| arguments.get(index)?.label()) 7188 .map(|label| label_names.rename_to_avoid_shadowing(label)); 7189 7190 let pretty_type = printer.print_type(argument_type); 7191 if let Some(arg_label) = label { 7192 format!("{arg_label}: {pretty_type}") 7193 } else { 7194 format!("{pretty_type}") 7195 } 7196 }) 7197 .join(", "); 7198 7199 let variant = if arguments.is_empty() { 7200 variant_name.to_string() 7201 } else { 7202 format!("{variant_name}({arguments})") 7203 }; 7204 7205 let (new_text, insert_at) = match type_braces { 7206 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1), 7207 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position), 7208 }; 7209 7210 match edits { 7211 GenerateVariantEdits::GenerateInCurrentModule { 7212 current_module_edits, 7213 } => current_module_edits.insert(insert_at, new_text), 7214 GenerateVariantEdits::GenerateInDifferentModule { 7215 variant_module_edits, 7216 .. 7217 } => variant_module_edits.insert(insert_at, new_text), 7218 } 7219 } 7220 7221 fn try_save_variant_to_generate( 7222 &mut self, 7223 is_qualified: bool, 7224 function_name_location: SrcSpan, 7225 function_type: &Arc<Type>, 7226 given_arguments: Option<Arguments<'a>>, 7227 ) { 7228 let variant_to_generate = self.variant_to_generate( 7229 is_qualified, 7230 function_name_location, 7231 function_type, 7232 given_arguments, 7233 ); 7234 if variant_to_generate.is_some() { 7235 self.variant_to_generate = variant_to_generate; 7236 } 7237 } 7238 7239 fn variant_to_generate( 7240 &mut self, 7241 is_qualified: bool, 7242 function_name_location: SrcSpan, 7243 type_: &Arc<Type>, 7244 given_arguments: Option<Arguments<'a>>, 7245 ) -> Option<VariantToGenerate<'a>> { 7246 let name = code_at(self.module, function_name_location); 7247 if !is_valid_uppercase_name(name) { 7248 return None; 7249 } 7250 7251 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) { 7252 (Some(result), _) => result, 7253 (None, Some(arguments)) => (arguments.types(), type_.clone()), 7254 (None, None) => (vec![], type_.clone()), 7255 }; 7256 7257 let (module_name, type_name, _) = custom_type.named_type_information()?; 7258 let module = self.compiler.modules.get(&module_name)?; 7259 let (end_position, type_braces, constructors) = 7260 (module.ast.definitions.custom_types.iter()) 7261 .filter(|custom_type| custom_type.name == type_name) 7262 .find_map(|custom_type| { 7263 // If there's already a variant with this name then we definitely 7264 // don't want to generate a new variant with the same name! 7265 let variant_with_this_name_already_exists = custom_type 7266 .constructors 7267 .iter() 7268 .map(|constructor| &constructor.name) 7269 .any(|existing_constructor_name| existing_constructor_name == name); 7270 if variant_with_this_name_already_exists { 7271 return None; 7272 } 7273 let type_braces = if custom_type.end_position == custom_type.location.end { 7274 TypeBraces::NoBraces 7275 } else { 7276 TypeBraces::HasBraces 7277 }; 7278 Some(( 7279 custom_type.end_position, 7280 type_braces, 7281 &custom_type.constructors, 7282 )) 7283 })?; 7284 7285 Some(VariantToGenerate { 7286 name, 7287 is_qualified, 7288 constructors, 7289 arguments_types, 7290 given_arguments, 7291 module_name, 7292 end_position, 7293 type_braces, 7294 variant_start: function_name_location.start, 7295 }) 7296 } 7297 7298 /// If the variant is generated in a module different from the current one, 7299 /// this will add the edits needed to correctly import the variant so that 7300 /// it's readily available. 7301 /// It will also respect the developer's choice of how variants for the type 7302 /// are imported: 7303 /// 7304 /// ```diff 7305 /// - import wibble.{ Wibble } 7306 /// + import wibble.{ Wibble, Wobble } 7307 /// // If generating `Wobble`, and other variants of that type are 7308 /// // unqualified already the new variant is imported unqualified as well. 7309 /// ``` 7310 /// 7311 /// ```diff 7312 /// import wibble 7313 /// 7314 /// pub fn main() { 7315 /// - let assert Wobble = todo 7316 /// + let assert wibble.Wobble = todo 7317 /// } 7318 /// // If no variant is used in an unqualified manner, than the variant 7319 /// // that triggered the generation is also qualified! 7320 /// ``` 7321 fn edits_to_import_variant( 7322 &self, 7323 edits: &mut GenerateVariantEdits<'_>, 7324 variant_module_name: &str, 7325 variant_name: &str, 7326 variant_start: u32, 7327 module: &'a Module, 7328 constructors: &[RecordConstructor<Arc<Type>>], 7329 ) { 7330 let GenerateVariantEdits::GenerateInDifferentModule { 7331 current_module_edits, 7332 .. 7333 } = edits 7334 else { 7335 // If the variant is added to the current module, then no further 7336 // edits are needed. The variant is already available in the current 7337 // module! 7338 return; 7339 }; 7340 7341 let constructors_names: HashSet<_> = constructors 7342 .iter() 7343 .map(|constructor| &constructor.name) 7344 .collect(); 7345 7346 // We start by getting the import for the module where the variant 7347 // is going to be added... 7348 let Some(variant_module_import) = module 7349 .ast 7350 .definitions 7351 .imports 7352 .iter() 7353 .find(|import_| import_.module == variant_module_name) 7354 else { 7355 return; 7356 }; 7357 // ...and then check if any of the variants of the type where the variant 7358 // is going to be added have already been imported in an unqualified way. 7359 let constructors_for_this_type_are_unqualified = variant_module_import 7360 .unqualified_values 7361 .iter() 7362 .any(|value| constructors_names.contains(&value.name)); 7363 7364 if constructors_for_this_type_are_unqualified { 7365 // We need to add an unqualified import! 7366 let (insert_positions, new_text) = edits::insert_unqualified_import( 7367 variant_module_import, 7368 &self.module.code, 7369 variant_name.into(), 7370 ); 7371 current_module_edits.insert(insert_positions, new_text); 7372 } else { 7373 // We need to qualify the variant that triggered the code action! 7374 current_module_edits.insert(variant_start, format!("{variant_module_name}.")) 7375 } 7376 } 7377} 7378 7379impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> { 7380 fn visit_typed_expr_invalid( 7381 &mut self, 7382 location: &'ast SrcSpan, 7383 type_: &'ast Arc<Type>, 7384 extra_information: &'ast Option<InvalidExpression>, 7385 ) { 7386 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 7387 if within(self.params.range, invalid_range) { 7388 self.try_save_variant_to_generate(false, *location, type_, None); 7389 } 7390 ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); 7391 } 7392 7393 fn visit_typed_expr_call( 7394 &mut self, 7395 location: &'ast SrcSpan, 7396 type_: &'ast Arc<Type>, 7397 fun: &'ast TypedExpr, 7398 arguments: &'ast [TypedCallArg], 7399 open_parenthesis: &'ast Option<u32>, 7400 ) { 7401 // If the function being called is invalid we need to generate a 7402 // function that has the proper labels. 7403 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers); 7404 if within(self.params.range, fun_range) && fun.is_invalid() { 7405 if labels_are_correct(arguments) { 7406 self.try_save_variant_to_generate( 7407 fun.is_module_select(), 7408 fun.location(), 7409 &fun.type_(), 7410 Some(Arguments::Expressions(arguments)), 7411 ); 7412 } 7413 } else { 7414 ast::visit::visit_typed_expr_call( 7415 self, 7416 location, 7417 type_, 7418 fun, 7419 arguments, 7420 open_parenthesis, 7421 ); 7422 } 7423 } 7424 7425 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 7426 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 7427 if within(self.params.range, invalid_range) { 7428 self.try_save_variant_to_generate(false, *location, type_, None); 7429 } 7430 ast::visit::visit_typed_pattern_invalid(self, location, type_); 7431 } 7432 7433 fn visit_typed_pattern_constructor( 7434 &mut self, 7435 location: &'ast SrcSpan, 7436 name_location: &'ast SrcSpan, 7437 name: &'ast EcoString, 7438 arguments: &'ast Vec<CallArg<TypedPattern>>, 7439 module: &'ast Option<(EcoString, SrcSpan)>, 7440 constructor: &'ast Inferred<type_::PatternConstructor>, 7441 spread: &'ast Option<SrcSpan>, 7442 type_: &'ast Arc<Type>, 7443 ) { 7444 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers); 7445 if within(self.params.range, pattern_range) { 7446 if labels_are_correct(arguments) { 7447 self.try_save_variant_to_generate( 7448 module.is_some(), 7449 *name_location, 7450 type_, 7451 Some(Arguments::Patterns(arguments)), 7452 ); 7453 } 7454 } else { 7455 ast::visit::visit_typed_pattern_constructor( 7456 self, 7457 location, 7458 name_location, 7459 name, 7460 arguments, 7461 module, 7462 constructor, 7463 spread, 7464 type_, 7465 ); 7466 } 7467 } 7468} 7469 7470#[must_use] 7471/// Checks the labels in the given arguments are correct: that is there's no 7472/// duplicate labels and all labelled arguments come after the unlabelled ones. 7473fn labels_are_correct<A>(arguments: &[CallArg<A>]) -> bool { 7474 let mut labelled_arg_found = false; 7475 let mut used_labels = HashSet::new(); 7476 7477 for argument in arguments { 7478 match &argument.label { 7479 // Labels are invalid if there's duplicate ones or if an unlabelled 7480 // argument comes after a labelled one. 7481 Some(label) if used_labels.contains(label) => return false, 7482 None if labelled_arg_found => return false, 7483 // Otherwise we just add the label to the used ones. 7484 Some(label) => { 7485 labelled_arg_found = true; 7486 let _ = used_labels.insert(label); 7487 } 7488 None => {} 7489 } 7490 } 7491 7492 true 7493} 7494 7495#[derive(Clone)] 7496struct NameGenerator { 7497 used_names: im::HashSet<EcoString>, 7498} 7499 7500impl NameGenerator { 7501 pub fn new() -> Self { 7502 NameGenerator { 7503 used_names: im::HashSet::new(), 7504 } 7505 } 7506 7507 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString { 7508 let mut i = 1; 7509 let mut candidate_name = base.clone(); 7510 7511 loop { 7512 if self.used_names.contains(&candidate_name) { 7513 i += 1; 7514 candidate_name = eco_format!("{base}_{i}"); 7515 } else { 7516 let _ = self.used_names.insert(candidate_name.clone()); 7517 return candidate_name; 7518 } 7519 } 7520 } 7521 7522 /// Given an argument type and the actual call argument (if any), comes up 7523 /// with a label and a name to use for that argument when generating a 7524 /// function. 7525 /// 7526 pub fn generate_label_and_name( 7527 &mut self, 7528 call_argument: Option<&CallArg<TypedExpr>>, 7529 argument_type: &Arc<Type>, 7530 ) -> (Option<EcoString>, EcoString) { 7531 let label = call_argument.and_then(|argument| argument.label.clone()); 7532 let argument_name = call_argument 7533 // We always favour a name derived from the expression (for example if 7534 // the argument is a variable) 7535 .and_then(|argument| self.generate_name_from_expression(&argument.value)) 7536 // If we don't have such a name and there's a label we use that name. 7537 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?))) 7538 // If all else fails we fallback to using a name derived from the 7539 // argument's type. 7540 .unwrap_or_else(|| self.generate_name_from_type(argument_type)); 7541 7542 (label, argument_name) 7543 } 7544 7545 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString { 7546 let type_to_base_name = |type_: &Arc<Type>| { 7547 type_ 7548 .named_type_name() 7549 .map(|(_type_module, type_name)| to_snake_case(&type_name)) 7550 .filter(|name| is_valid_lowercase_name(name)) 7551 .unwrap_or(EcoString::from("value")) 7552 }; 7553 7554 let base_name = match type_.list_type() { 7555 None => type_to_base_name(type_), 7556 // If we're coming up with a name for a list we want to use the 7557 // plural form for the name of the inner type. For example: 7558 // `List(Pokemon)` should generate `pokemons`. 7559 Some(inner_type) => { 7560 let base_name = type_to_base_name(&inner_type); 7561 // If the inner type name already ends in "s" we leave it as it 7562 // is, or it would look funny. 7563 if base_name.ends_with('s') { 7564 base_name 7565 } else { 7566 eco_format!("{base_name}s") 7567 } 7568 } 7569 }; 7570 7571 self.rename_to_avoid_shadowing(base_name) 7572 } 7573 7574 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> { 7575 match expression { 7576 // If the argument is a record, we can't use it as an argument name. 7577 // Similarly, we don't want to base the variable name off a 7578 // compiler-generated variable like `_pipe`. 7579 TypedExpr::Var { 7580 name, constructor, .. 7581 } if !constructor.variant.is_record() 7582 && !constructor.variant.is_generated_variable() => 7583 { 7584 Some(self.rename_to_avoid_shadowing(name.clone())) 7585 } 7586 7587 // If the argument is a record access, we generate a name from the 7588 // label used. 7589 // For example if we have `wibble.id` we would end up picking `id`. 7590 TypedExpr::RecordAccess { label, .. } => { 7591 Some(self.rename_to_avoid_shadowing(label.clone())) 7592 } 7593 7594 TypedExpr::Int { .. } 7595 | TypedExpr::Float { .. } 7596 | TypedExpr::String { .. } 7597 | TypedExpr::Block { .. } 7598 | TypedExpr::Pipeline { .. } 7599 | TypedExpr::Var { .. } 7600 | TypedExpr::Fn { .. } 7601 | TypedExpr::List { .. } 7602 | TypedExpr::Call { .. } 7603 | TypedExpr::BinOp { .. } 7604 | TypedExpr::Case { .. } 7605 | TypedExpr::PositionalAccess { .. } 7606 | TypedExpr::ModuleSelect { .. } 7607 | TypedExpr::Tuple { .. } 7608 | TypedExpr::TupleIndex { .. } 7609 | TypedExpr::Todo { .. } 7610 | TypedExpr::Panic { .. } 7611 | TypedExpr::Echo { .. } 7612 | TypedExpr::BitArray { .. } 7613 | TypedExpr::RecordUpdate { .. } 7614 | TypedExpr::NegateBool { .. } 7615 | TypedExpr::NegateInt { .. } 7616 | TypedExpr::Invalid { .. } => None, 7617 } 7618 } 7619 7620 /// Given some typed definitions this reserves all the value names defined 7621 /// by all the top level definitions. That is: all function names, constant 7622 /// names, and imported modules names. 7623 pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) { 7624 for constant in &definitions.constants { 7625 self.add_used_name(constant.name.clone()); 7626 } 7627 7628 for function in &definitions.functions { 7629 if let Some((_, name)) = &function.name { 7630 self.add_used_name(name.clone()); 7631 } 7632 } 7633 7634 for import in &definitions.imports { 7635 let module_name = match &import.used_name() { 7636 Some(used_name) => used_name.clone(), 7637 None => import.module.clone(), 7638 }; 7639 self.add_used_name(module_name); 7640 } 7641 } 7642 7643 pub fn add_used_name(&mut self, name: EcoString) { 7644 let _ = self.used_names.insert(name); 7645 } 7646 7647 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) { 7648 field_map 7649 .fields 7650 .iter() 7651 .for_each(|(label, _)| self.add_used_name(label.clone())); 7652 } 7653 7654 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) { 7655 variable_names 7656 .names 7657 .iter() 7658 .for_each(|name| self.add_used_name(name.clone())); 7659 } 7660 7661 fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) { 7662 for variable in bound_variables { 7663 self.add_used_name(variable.name()); 7664 } 7665 } 7666} 7667 7668#[must_use] 7669fn is_valid_lowercase_name(name: &str) -> bool { 7670 if !name.starts_with(|char: char| char.is_ascii_lowercase()) { 7671 return false; 7672 } 7673 7674 for char in name.chars() { 7675 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_'; 7676 if !is_valid_char { 7677 return false; 7678 } 7679 } 7680 7681 string_to_keyword(name).is_none() 7682} 7683 7684#[must_use] 7685fn is_valid_uppercase_name(name: &str) -> bool { 7686 if !name.starts_with(|char: char| char.is_ascii_uppercase()) { 7687 return false; 7688 } 7689 7690 for char in name.chars() { 7691 if !char.is_ascii_alphanumeric() { 7692 return false; 7693 } 7694 } 7695 7696 true 7697} 7698 7699/// Code action to rewrite a single-step pipeline into a regular function call. 7700/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`. 7701/// 7702pub struct ConvertToFunctionCall<'a> { 7703 module: &'a Module, 7704 params: &'a CodeActionParams, 7705 edits: TextEdits<'a>, 7706 locations: Option<ConvertToFunctionCallLocations>, 7707} 7708 7709/// All the different locations the "Convert to function call" code action needs 7710/// to properly rewrite a pipeline into a function call. 7711/// 7712struct ConvertToFunctionCallLocations { 7713 /// This is the location of the value being piped into a call. 7714 /// 7715 /// ```gleam 7716 /// [1, 2, 3] |> list.length 7717 /// // ^^^^^^^^^ This one here 7718 /// ``` 7719 /// 7720 first_value: SrcSpan, 7721 7722 /// This is the location of the call the value is being piped into. 7723 /// 7724 /// ```gleam 7725 /// [1, 2, 3] |> list.length 7726 /// // ^^^^^^^^^^^ This one here 7727 /// ``` 7728 /// 7729 call: SrcSpan, 7730 7731 /// This is the kind of desugaring that is taking place when piping 7732 /// `first_value` into `call`. 7733 /// 7734 call_kind: PipelineAssignmentKind, 7735} 7736 7737impl<'a> ConvertToFunctionCall<'a> { 7738 pub fn new( 7739 module: &'a Module, 7740 line_numbers: &'a LineNumbers, 7741 params: &'a CodeActionParams, 7742 ) -> Self { 7743 Self { 7744 module, 7745 params, 7746 edits: TextEdits::new(line_numbers), 7747 locations: None, 7748 } 7749 } 7750 7751 pub fn code_actions(mut self) -> Vec<CodeAction> { 7752 self.visit_typed_module(&self.module.ast); 7753 7754 // If we couldn't find a pipeline to rewrite we don't return any action. 7755 let Some(ConvertToFunctionCallLocations { 7756 first_value, 7757 call, 7758 call_kind, 7759 }) = self.locations 7760 else { 7761 return vec![]; 7762 }; 7763 7764 // We first delete the first value of the pipeline as it's going to be 7765 // inlined as a function call argument. 7766 self.edits.delete(SrcSpan { 7767 start: first_value.start, 7768 end: call.start, 7769 }); 7770 7771 // Then we have to insert the piped value in the appropriate position. 7772 // This will change based on how the pipeline is being desugared, we 7773 // know this thanks to the `call_kind` 7774 let first_value_text = self 7775 .module 7776 .code 7777 .get(first_value.start as usize..first_value.end as usize) 7778 .expect("invalid code span") 7779 .to_string(); 7780 7781 match call_kind { 7782 // When piping into a `_` we replace the hole with the piped value: 7783 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`. 7784 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text), 7785 7786 // When piping is desguared as a function call we need to add the 7787 // missing parentheses: 7788 // `[1, 2] |> length` becomes `length([1, 2])` 7789 PipelineAssignmentKind::FunctionCall => { 7790 self.edits.insert(call.end, format!("({first_value_text})")) 7791 } 7792 7793 // When the piped value is inserted as the first argument there's two 7794 // possible scenarios: 7795 // - there's a second argument as well: in that case we insert it 7796 // before the second arg and add a comma 7797 // - there's no other argument: `[1, 2] |> length()` becomes 7798 // `length([1, 2])`, we insert the value between the empty 7799 // parentheses 7800 PipelineAssignmentKind::FirstArgument { 7801 second_argument: Some(SrcSpan { start, .. }), 7802 } => self.edits.insert(start, format!("{first_value_text}, ")), 7803 PipelineAssignmentKind::FirstArgument { 7804 second_argument: None, 7805 } => self.edits.insert(call.end - 1, first_value_text), 7806 7807 // When the value is piped into an echo, to rewrite the pipeline we 7808 // have to insert the value after the `echo` with no parentheses: 7809 // `a |> echo` is rewritten as `echo a`. 7810 PipelineAssignmentKind::Echo => { 7811 self.edits.insert(call.end, format!(" {first_value_text}")) 7812 } 7813 } 7814 7815 let mut action = Vec::with_capacity(1); 7816 CodeActionBuilder::new("Convert to function call") 7817 .kind(CodeActionKind::RefactorRewrite) 7818 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7819 .preferred(false) 7820 .push_to(&mut action); 7821 action 7822 } 7823} 7824 7825impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> { 7826 fn visit_typed_expr_pipeline( 7827 &mut self, 7828 location: &'ast SrcSpan, 7829 first_value: &'ast TypedPipelineAssignment, 7830 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 7831 finally: &'ast TypedExpr, 7832 finally_kind: &'ast PipelineAssignmentKind, 7833 ) { 7834 let pipeline_range = self.edits.src_span_to_lsp_range(*location); 7835 if within(self.params.range, pipeline_range) { 7836 // Add final assignment to the list 7837 let all_assignments = assignments 7838 .iter() 7839 .map(|(call, kind)| (call.location, *kind)) 7840 .chain(iter::once((finally.location(), *finally_kind))); 7841 let mut call_information = None; 7842 // Span, containing content that will be extracted as call argument 7843 let mut accumulated_span = first_value.location; 7844 7845 for (current_location, current_kind) in all_assignments { 7846 if within( 7847 self.params.range, 7848 self.edits.src_span_to_lsp_range(current_location), 7849 ) { 7850 call_information = Some((accumulated_span, current_location, current_kind)); 7851 break; 7852 } 7853 accumulated_span = accumulated_span.merge(&current_location); 7854 } 7855 7856 let (final_prev, final_call, final_kind) = match call_information { 7857 Some(triple) => triple, 7858 7859 // Here two situations are possible: 7860 // - If the cursor is not on any of the assignments (for example, it is on first value), then we need 7861 // to use that first value as accumulated location, and the first call location and kind 7862 // - If there are no assignments, then we are dealing with a single 7863 // step pipeline and the call is `finally` 7864 None => { 7865 let (call, call_kind) = &assignments 7866 .first() 7867 .map(|(call, call_kind)| (call.location, *call_kind)) 7868 .unwrap_or_else(|| (finally.location(), *finally_kind)); 7869 (first_value.location, *call, *call_kind) 7870 } 7871 }; 7872 7873 self.locations = Some(ConvertToFunctionCallLocations { 7874 first_value: final_prev, 7875 call: final_call, 7876 call_kind: final_kind, 7877 }); 7878 7879 ast::visit::visit_typed_expr_pipeline( 7880 self, 7881 location, 7882 first_value, 7883 assignments, 7884 finally, 7885 finally_kind, 7886 ); 7887 } 7888 } 7889} 7890 7891/// Builder for code action to inline a variable. 7892/// 7893pub struct InlineVariable<'a> { 7894 module: &'a Module, 7895 params: &'a CodeActionParams, 7896 edits: TextEdits<'a>, 7897 actions: Vec<CodeAction>, 7898} 7899 7900impl<'a> InlineVariable<'a> { 7901 pub fn new( 7902 module: &'a Module, 7903 line_numbers: &'a LineNumbers, 7904 params: &'a CodeActionParams, 7905 ) -> Self { 7906 Self { 7907 module, 7908 params, 7909 edits: TextEdits::new(line_numbers), 7910 actions: Vec::new(), 7911 } 7912 } 7913 7914 pub fn code_actions(mut self) -> Vec<CodeAction> { 7915 self.visit_typed_module(&self.module.ast); 7916 7917 self.actions 7918 } 7919 7920 fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) { 7921 let references = 7922 FindVariableReferences::new(location, name).find_in_module(&self.module.ast); 7923 let reference = if references.len() == 1 { 7924 references 7925 .into_iter() 7926 .next() 7927 .expect("References has length 1") 7928 } else { 7929 return; 7930 }; 7931 7932 let Some(ast::Statement::Assignment(assignment)) = 7933 self.module.ast.find_statement(location.start) 7934 else { 7935 return; 7936 }; 7937 7938 // If the assignment does not simple bind a variable, for example: 7939 // ```gleam 7940 // let #(first, second, third) 7941 // io.println(first) 7942 // // ^ Inline here 7943 // ``` 7944 // We can't inline it. 7945 if !matches!(assignment.pattern, Pattern::Variable { .. }) { 7946 return; 7947 } 7948 7949 // If the assignment was generated by the compiler, it doesn't have a 7950 // syntactical representation, so we can't inline it. 7951 if matches!(assignment.kind, AssignmentKind::Generated) { 7952 return; 7953 } 7954 7955 let value_location = assignment.value.location(); 7956 let value = self 7957 .module 7958 .code 7959 .get(value_location.start as usize..value_location.end as usize) 7960 .expect("Span is valid"); 7961 7962 match reference.kind { 7963 VariableReferenceKind::Variable => { 7964 self.edits.replace(reference.location, value.into()); 7965 } 7966 VariableReferenceKind::LabelShorthand => { 7967 self.edits 7968 .insert(reference.location.end, format!(" {value}")); 7969 } 7970 } 7971 7972 let mut location = assignment.location; 7973 location.end = next_nonwhitespace(&self.module.code, location.end); 7974 7975 self.edits.delete(location); 7976 7977 CodeActionBuilder::new("Inline variable") 7978 .kind(CodeActionKind::RefactorInline) 7979 .changes( 7980 self.params.text_document.uri.clone(), 7981 std::mem::take(&mut self.edits.edits), 7982 ) 7983 .preferred(false) 7984 .push_to(&mut self.actions); 7985 } 7986} 7987 7988impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> { 7989 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 7990 let TypedPattern::Variable { location, name, .. } = &assignment.pattern else { 7991 ast::visit::visit_typed_assignment(self, assignment); 7992 return; 7993 }; 7994 7995 // We special case assignment variables because we want to trigger the 7996 // code action also if we're over the let keyword: 7997 // 7998 // ```gleam 7999 // let wibble = 11 8000 // // ^^^^^^^^^^ Here! 8001 // ``` 8002 // 8003 let assignment_range = self 8004 .edits 8005 .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end)); 8006 if !within(self.params.range, assignment_range) { 8007 ast::visit::visit_typed_assignment(self, assignment); 8008 return; 8009 } 8010 8011 self.maybe_inline(*location, name.clone()); 8012 } 8013 8014 fn visit_typed_expr_var( 8015 &mut self, 8016 location: &'ast SrcSpan, 8017 constructor: &'ast ValueConstructor, 8018 name: &'ast EcoString, 8019 ) { 8020 let range = self.edits.src_span_to_lsp_range(*location); 8021 8022 if !within(self.params.range, range) { 8023 return; 8024 } 8025 8026 let type_::ValueConstructorVariant::LocalVariable { location, origin } = 8027 &constructor.variant 8028 else { 8029 return; 8030 }; 8031 8032 // We can only inline variables assigned by `let` statements, as it 8033 //doesn't make sense to do so with any other kind of variable. 8034 match origin.declaration { 8035 VariableDeclaration::LetPattern => {} 8036 VariableDeclaration::UsePattern 8037 | VariableDeclaration::ClausePattern 8038 | VariableDeclaration::FunctionParameter { .. } 8039 | VariableDeclaration::Generated => return, 8040 } 8041 8042 self.maybe_inline(*location, name.clone()); 8043 } 8044 8045 fn visit_typed_pattern_variable( 8046 &mut self, 8047 location: &'ast SrcSpan, 8048 name: &'ast EcoString, 8049 _type: &'ast Arc<Type>, 8050 origin: &'ast VariableOrigin, 8051 ) { 8052 // We can only inline variables assigned by `let` statements, as it 8053 //doesn't make sense to do so with any other kind of variable. 8054 match origin.declaration { 8055 VariableDeclaration::LetPattern => {} 8056 VariableDeclaration::UsePattern 8057 | VariableDeclaration::ClausePattern 8058 | VariableDeclaration::FunctionParameter { .. } 8059 | VariableDeclaration::Generated => return, 8060 } 8061 8062 let range = self.edits.src_span_to_lsp_range(*location); 8063 8064 if !within(self.params.range, range) { 8065 return; 8066 } 8067 8068 self.maybe_inline(*location, name.clone()); 8069 } 8070} 8071 8072/// Builder for the "convert to pipe" code action. 8073/// 8074/// ```gleam 8075/// pub fn main() { 8076/// wibble(wobble, woo) 8077/// // ^ [convert to pipe] 8078/// } 8079/// ``` 8080/// 8081/// Will turn the code into the following pipeline: 8082/// 8083/// ```gleam 8084/// pub fn main() { 8085/// wobble |> wibble(woo) 8086/// } 8087/// ``` 8088/// 8089pub struct ConvertToPipe<'a> { 8090 module: &'a Module, 8091 params: &'a CodeActionParams, 8092 edits: TextEdits<'a>, 8093 argument_to_pipe: Option<ConvertToPipeArg<'a>>, 8094 visited_item: VisitedItem, 8095} 8096 8097pub enum VisitedItem { 8098 RegularExpression, 8099 UseRightHandSide, 8100 PipelineFinalStep, 8101} 8102 8103/// Holds all the data needed by the "convert to pipe" code action to properly 8104/// rewrite a call into a pipe. Here's what each span means: 8105/// 8106/// ```gleam 8107/// wibble(wobb|le, woo) 8108/// // ^^^^^^^^^^^^^^^^^^^^ call 8109/// // ^^^^^^ called 8110/// // ^^^^^^^ arg 8111/// // ^^^ next arg 8112/// ``` 8113/// 8114/// In this example `position` is 0, since the cursor is over the first 8115/// argument. 8116/// 8117pub struct ConvertToPipeArg<'a> { 8118 /// The span of the called function. 8119 called: SrcSpan, 8120 /// The span of the entire function call. 8121 call: SrcSpan, 8122 /// The position (0-based) of the argument. 8123 position: usize, 8124 /// The argument we have to pipe. 8125 arg: &'a TypedCallArg, 8126 /// The span of the argument following the one we have to pipe, if there's 8127 /// any. 8128 next_arg: Option<SrcSpan>, 8129} 8130 8131impl<'a> ConvertToPipe<'a> { 8132 pub fn new( 8133 module: &'a Module, 8134 line_numbers: &'a LineNumbers, 8135 params: &'a CodeActionParams, 8136 ) -> Self { 8137 Self { 8138 module, 8139 params, 8140 edits: TextEdits::new(line_numbers), 8141 visited_item: VisitedItem::RegularExpression, 8142 argument_to_pipe: None, 8143 } 8144 } 8145 8146 pub fn code_actions(mut self) -> Vec<CodeAction> { 8147 self.visit_typed_module(&self.module.ast); 8148 8149 let Some(ConvertToPipeArg { 8150 called, 8151 call, 8152 position, 8153 arg, 8154 next_arg, 8155 }) = self.argument_to_pipe 8156 else { 8157 return vec![]; 8158 }; 8159 8160 let arg_location = if arg.uses_label_shorthand() { 8161 SrcSpan { 8162 start: arg.location.start, 8163 end: arg.location.end - 1, 8164 } 8165 } else if arg.label.is_some() { 8166 arg.value.location() 8167 } else { 8168 arg.location 8169 }; 8170 8171 let arg_text = code_at(self.module, arg_location); 8172 // If the expression being piped is a binary operation with 8173 // precedence lower than pipes then we have to wrap it in curly 8174 // braces to not mess with the order of operations. 8175 let arg_text = if let TypedExpr::BinOp { operator, .. } = arg.value 8176 && operator.precedence() < PIPE_PRECEDENCE 8177 { 8178 &format!("{{ {arg_text} }}") 8179 } else { 8180 arg_text 8181 }; 8182 8183 match next_arg { 8184 // When extracting an argument we never want to remove any explicit 8185 // label that was written down, so in case it is labelled (be it a 8186 // shorthand or not) we'll always replace the value with a `_` 8187 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()), 8188 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()), 8189 8190 // Now we can deal with unlabelled arguments: 8191 // If we're removing the first argument and there's other arguments 8192 // after it, we need to delete the comma that was separating the 8193 // two. 8194 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan { 8195 start: arg.location.start, 8196 end: next_arg.start, 8197 }), 8198 // Otherwise, if we're deleting the first argument and there's 8199 // no other arguments following it, we remove the call's 8200 // parentheses. 8201 None if position == 0 => self.edits.delete(SrcSpan { 8202 start: called.end, 8203 end: call.end, 8204 }), 8205 // In all other cases we're piping something that is not the first 8206 // argument so we just replace it with an `_`. 8207 _ => self.edits.replace(arg.location, "_".into()), 8208 }; 8209 8210 // Finally we can add the argument that was removed as the first step 8211 // of the newly defined pipeline. 8212 self.edits.insert(call.start, format!("{arg_text} |> ")); 8213 8214 let mut action = Vec::with_capacity(1); 8215 CodeActionBuilder::new("Convert to pipe") 8216 .kind(CodeActionKind::RefactorRewrite) 8217 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8218 .preferred(false) 8219 .push_to(&mut action); 8220 action 8221 } 8222} 8223 8224impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> { 8225 fn visit_typed_expr_call( 8226 &mut self, 8227 location: &'ast SrcSpan, 8228 _type_: &'ast Arc<Type>, 8229 fun: &'ast TypedExpr, 8230 arguments: &'ast [TypedCallArg], 8231 _open_parenthesis: &'ast Option<u32>, 8232 ) { 8233 if arguments.iter().any(|arg| arg.is_capture_hole()) { 8234 return; 8235 } 8236 8237 // If we're visiting the typed function produced by typing a use, we 8238 // skip the thing itself and only visit its arguments and called 8239 // function, that is the body of the use. 8240 match self.visited_item { 8241 VisitedItem::RegularExpression => (), 8242 VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => { 8243 self.visited_item = VisitedItem::RegularExpression; 8244 ast::visit::visit_typed_expr(self, fun); 8245 arguments 8246 .iter() 8247 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg)); 8248 return; 8249 } 8250 } 8251 8252 // We only visit a call if the cursor is somewhere within its location, 8253 // otherwise we skip it entirely. 8254 let call_range = self.edits.src_span_to_lsp_range(*location); 8255 if !within(self.params.range, call_range) { 8256 return; 8257 } 8258 8259 // If the cursor is over any of the arguments then we'll use that as 8260 // the one to extract. 8261 // Otherwise the cursor must be over the called function, in that case 8262 // we extract the first argument (if there's one): 8263 // 8264 // ```gleam 8265 // wibble(wobble, woo) 8266 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here 8267 // // ^^^ pipe the second argument if I'm here 8268 // ``` 8269 let argument_to_pipe = arguments 8270 .iter() 8271 .enumerate() 8272 .find_map(|(position, arg)| { 8273 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 8274 if within(self.params.range, arg_range) { 8275 Some((position, arg)) 8276 } else { 8277 None 8278 } 8279 }) 8280 .or_else(|| arguments.first().map(|argument| (0, argument))); 8281 8282 // If we're not hovering over any of the arguments _or_ there's no 8283 // argument to extract at all we just return, there's nothing we can do 8284 // on this call or any of its arguments (since we've determined the 8285 // cursor is not over any of those). 8286 let Some((position, arg)) = argument_to_pipe else { 8287 return; 8288 }; 8289 8290 self.argument_to_pipe = Some(ConvertToPipeArg { 8291 called: fun.location(), 8292 call: *location, 8293 position, 8294 arg, 8295 next_arg: arguments 8296 .get(position + 1) 8297 .map(|argument| argument.location), 8298 }); 8299 8300 // We still want to visit the arguments so that if we're hovering a 8301 // nested pipeline, that's going to be the one we transform: 8302 // 8303 // ```gleam 8304 // wibble(Wobble( 8305 // field: call(other(last(1))) 8306 // // ^^^^ We want to convert this one if we hover over it, 8307 // // not the outer `wibble(Wobble(...))` call 8308 // )) 8309 // ``` 8310 // 8311 for argument in arguments { 8312 ast::visit::visit_typed_call_arg(self, argument); 8313 } 8314 } 8315 8316 fn visit_typed_expr_pipeline( 8317 &mut self, 8318 _location: &'ast SrcSpan, 8319 first_value: &'ast TypedPipelineAssignment, 8320 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 8321 finally: &'ast TypedExpr, 8322 _finally_kind: &'ast PipelineAssignmentKind, 8323 ) { 8324 // We can only apply the action on the first step of a pipeline, so we 8325 // visit just that one and skip all the others. 8326 ast::visit::visit_typed_pipeline_assignment(self, first_value); 8327 self.visited_item = VisitedItem::PipelineFinalStep; 8328 ast::visit::visit_typed_expr(self, finally); 8329 } 8330 8331 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 8332 self.visited_item = VisitedItem::UseRightHandSide; 8333 ast::visit::visit_typed_use(self, use_); 8334 } 8335} 8336 8337/// Code action to interpolate a string. If the cursor is inside the string 8338/// (not selecting anything) the language server will offer to split it: 8339/// 8340/// ```gleam 8341/// "wibble | wobble" 8342/// // ^ [Split string] 8343/// // Will produce the following 8344/// "wibble " <> todo <> " wobble" 8345/// ``` 8346/// 8347/// If the cursor is selecting an entire valid gleam name, then the language 8348/// server will offer to interpolate it as a variable: 8349/// 8350/// ```gleam 8351/// "wibble wobble woo" 8352/// // ^^^^^^ [Interpolate variable] 8353/// // Will produce the following 8354/// "wibble " <> wobble <> " woo" 8355/// ``` 8356/// 8357/// > Note: the cursor won't end up right after the inserted variable/todo. 8358/// > that's a bit annoying, but in a future LSP version we will be able to 8359/// > isnert tab stops to allow one to jump to the newly added variable/todo. 8360/// 8361pub struct InterpolateString<'a> { 8362 module: &'a Module, 8363 params: &'a CodeActionParams, 8364 edits: TextEdits<'a>, 8365 string_interpolation: Option<(SrcSpan, StringInterpolation)>, 8366 string_literal_position: StringLiteralPosition, 8367} 8368 8369#[derive(Debug, Clone, Copy, Eq, PartialEq)] 8370pub enum StringLiteralPosition { 8371 FirstPipelineStep, 8372 Other, 8373} 8374 8375#[derive(Clone, Copy)] 8376enum StringInterpolation { 8377 InterpolateValue { value_location: SrcSpan }, 8378 SplitString { split_at: u32 }, 8379} 8380 8381impl<'a> InterpolateString<'a> { 8382 pub fn new( 8383 module: &'a Module, 8384 line_numbers: &'a LineNumbers, 8385 params: &'a CodeActionParams, 8386 ) -> Self { 8387 Self { 8388 module, 8389 params, 8390 edits: TextEdits::new(line_numbers), 8391 string_interpolation: None, 8392 string_literal_position: StringLiteralPosition::Other, 8393 } 8394 } 8395 8396 pub fn code_actions(mut self) -> Vec<CodeAction> { 8397 self.visit_typed_module(&self.module.ast); 8398 8399 let Some((string_location, interpolation)) = self.string_interpolation else { 8400 return vec![]; 8401 }; 8402 8403 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 8404 self.edits.insert(string_location.start, "{ ".into()); 8405 } 8406 8407 match interpolation { 8408 StringInterpolation::InterpolateValue { value_location } => { 8409 let name = self 8410 .module 8411 .code 8412 .get(value_location.start as usize..value_location.end as usize) 8413 .expect("invalid value range"); 8414 8415 // We trust that the programmer has correctly selected the part 8416 // of the string they want to interpolate and simply "cut it out" 8417 // for them. In future, we could try and parse their selection to 8418 // see if it is a valid expression in Gleam. 8419 if value_location.start == string_location.start + 1 { 8420 self.edits 8421 .insert(string_location.start, format!("{name} <> ")); 8422 } else if value_location.end == string_location.end - 1 { 8423 self.edits 8424 .insert(string_location.end, format!(" <> {name}")); 8425 } else { 8426 self.edits 8427 .insert(value_location.start, format!("\" <> {name} <> \"")); 8428 } 8429 self.edits.delete(value_location); 8430 } 8431 8432 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => { 8433 self.edits.insert(split_at, "\" <> todo <> \"".into()); 8434 } 8435 8436 StringInterpolation::SplitString { .. } => return vec![], 8437 }; 8438 8439 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 8440 self.edits.insert(string_location.end, " }".into()); 8441 } 8442 8443 let mut action = Vec::with_capacity(1); 8444 CodeActionBuilder::new("Interpolate string") 8445 .kind(CodeActionKind::RefactorRewrite) 8446 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8447 .preferred(false) 8448 .push_to(&mut action); 8449 action 8450 } 8451 8452 fn can_split_string_at(&self, at: u32) -> bool { 8453 self.string_interpolation 8454 .is_some_and(|(string_location, _)| { 8455 !(at <= string_location.start + 1 || at >= string_location.end - 1) 8456 }) 8457 } 8458 8459 fn visit_literal_string( 8460 &mut self, 8461 string_location: SrcSpan, 8462 string_position: StringLiteralPosition, 8463 ) { 8464 // We can only interpolate/split a string if the cursor is somewhere 8465 // within its location, otherwise we skip it. 8466 let string_range = self.edits.src_span_to_lsp_range(string_location); 8467 if !within(self.params.range, string_range) { 8468 return; 8469 } 8470 8471 let selection @ SrcSpan { start, end } = 8472 self.edits.lsp_range_to_src_span(self.params.range); 8473 8474 // We can't interpolate/split if the double quotes delimiting the 8475 // string have been selected. 8476 if start == string_location.start || end == string_location.end { 8477 return; 8478 } 8479 8480 let name = self 8481 .module 8482 .code 8483 .get(start as usize..end as usize) 8484 .expect("invalid value range"); 8485 8486 // TUI editors like Helix and Kakoune that use the selection-action edit 8487 // model are always in the equivalent of Vim's VISUAL mode, i.e. they always 8488 // have something selected. For programmers using these editors, the 8489 // smallest selection possible is a 1-character selection. The best we can do 8490 // to provide parity with other editors is to consider a single-character SPACE 8491 // selection as an empty selection, as they most likely want to split the 8492 // string instead of interpolating a variable. 8493 let interpolation = if start == end || (end - start == 1 && name == " ") { 8494 StringInterpolation::SplitString { split_at: start } 8495 } else { 8496 StringInterpolation::InterpolateValue { 8497 value_location: selection, 8498 } 8499 }; 8500 self.string_interpolation = Some((string_location, interpolation)); 8501 self.string_literal_position = string_position; 8502 } 8503} 8504 8505impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> { 8506 fn visit_typed_expr_string( 8507 &mut self, 8508 location: &'ast SrcSpan, 8509 _type_: &'ast Arc<Type>, 8510 _value: &'ast EcoString, 8511 ) { 8512 self.visit_literal_string(*location, StringLiteralPosition::Other); 8513 } 8514 8515 fn visit_typed_expr_pipeline( 8516 &mut self, 8517 _location: &'ast SrcSpan, 8518 first_value: &'ast TypedPipelineAssignment, 8519 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 8520 finally: &'ast TypedExpr, 8521 _finally_kind: &'ast PipelineAssignmentKind, 8522 ) { 8523 if first_value.value.is_literal_string() { 8524 self.visit_literal_string( 8525 first_value.location, 8526 StringLiteralPosition::FirstPipelineStep, 8527 ); 8528 } else { 8529 ast::visit::visit_typed_pipeline_assignment(self, first_value); 8530 } 8531 8532 assignments 8533 .iter() 8534 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a)); 8535 self.visit_typed_expr(finally); 8536 } 8537} 8538 8539/// Code action to replace a `..` in a pattern with all the missing fields that 8540/// have not been explicitly provided; labelled ones are introduced with the 8541/// shorthand syntax. 8542/// 8543/// ```gleam 8544/// pub type Pokemon { 8545/// Pokemon(Int, name: String, moves: List(String)) 8546/// } 8547/// 8548/// pub fn main() { 8549/// let Pokemon(..) = todo 8550/// // ^^ Cursor over the spread 8551/// } 8552/// ``` 8553/// Would become 8554/// ```gleam 8555/// pub fn main() { 8556/// let Pokemon(int, name:, moves:) = todo 8557/// } 8558/// 8559pub struct FillUnusedFields<'a> { 8560 module: &'a Module, 8561 params: &'a CodeActionParams, 8562 edits: TextEdits<'a>, 8563 data: Option<FillUnusedFieldsData>, 8564} 8565 8566pub struct FillUnusedFieldsData { 8567 /// All the missing positional and labelled fields. 8568 positional: Vec<Arc<Type>>, 8569 labelled: Vec<(EcoString, Arc<Type>)>, 8570 /// We need this in order to tell where the missing positional arguments 8571 /// should be inserted. 8572 first_labelled_argument_start: Option<u32>, 8573 /// The end of the final argument before the spread, if there's any. 8574 /// We'll use this to delete everything that comes after the final argument, 8575 /// after adding all the ignored fields. 8576 last_argument_end: Option<u32>, 8577 spread_location: SrcSpan, 8578} 8579 8580impl<'a> FillUnusedFields<'a> { 8581 pub fn new( 8582 module: &'a Module, 8583 line_numbers: &'a LineNumbers, 8584 params: &'a CodeActionParams, 8585 ) -> Self { 8586 Self { 8587 module, 8588 params, 8589 edits: TextEdits::new(line_numbers), 8590 data: None, 8591 } 8592 } 8593 8594 pub fn code_actions(mut self) -> Vec<CodeAction> { 8595 self.visit_typed_module(&self.module.ast); 8596 8597 let Some(FillUnusedFieldsData { 8598 positional, 8599 labelled, 8600 first_labelled_argument_start, 8601 last_argument_end, 8602 spread_location, 8603 }) = self.data 8604 else { 8605 return vec![]; 8606 }; 8607 8608 // Do not suggest this code action if there's no ignored fields at all. 8609 if positional.is_empty() && labelled.is_empty() { 8610 return vec![]; 8611 }; 8612 8613 // We add all the missing positional arguments before the first 8614 // labelled one (and so after all the already existing positional ones). 8615 if !positional.is_empty() { 8616 // We want to make sure that all positional args will have a name 8617 // that's different from any label. So we add those as already used 8618 // names. 8619 let mut names = NameGenerator::new(); 8620 for (label, _) in labelled.iter() { 8621 names.add_used_name(label.clone()); 8622 } 8623 8624 let positional_arguments = positional 8625 .iter() 8626 .map(|type_| names.generate_name_from_type(type_)) 8627 .join(", "); 8628 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); 8629 8630 // The positional arguments are going to be followed by some other 8631 // arguments if there's some already existing labelled args 8632 // (`last_argument_end.is_some`), of if we're adding those labelled args 8633 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the 8634 // final positional argument we're adding to separate it from the ones that 8635 // are going to come after. 8636 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); 8637 let positional_arguments = if has_arguments_after { 8638 format!("{positional_arguments}, ") 8639 } else { 8640 positional_arguments 8641 }; 8642 8643 self.edits.insert(insert_at, positional_arguments); 8644 } 8645 8646 if !labelled.is_empty() { 8647 // If there's labelled arguments to add, we replace the existing spread 8648 // with the arguments to be added. This way commas and all should already 8649 // be correct. 8650 let labelled_arguments = labelled 8651 .iter() 8652 .map(|(label, _)| format!("{label}:")) 8653 .join(", "); 8654 self.edits.replace(spread_location, labelled_arguments); 8655 } else if let Some(delete_start) = last_argument_end { 8656 // However, if there's no labelled arguments to insert we still need 8657 // to delete the entire spread: we start deleting from the end of the 8658 // final argument, if there's one. 8659 // This way we also get rid of any comma separating the last argument 8660 // and the spread to be removed. 8661 self.edits 8662 .delete(SrcSpan::new(delete_start, spread_location.end)) 8663 } else { 8664 // Otherwise we just delete the spread. 8665 self.edits.delete(spread_location) 8666 } 8667 8668 let mut action = Vec::with_capacity(1); 8669 CodeActionBuilder::new("Fill unused fields") 8670 .kind(CodeActionKind::RefactorRewrite) 8671 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8672 .preferred(false) 8673 .push_to(&mut action); 8674 action 8675 } 8676} 8677 8678impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> { 8679 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { 8680 // We can only interpolate/split a string if the cursor is somewhere 8681 // within its location, otherwise we skip it. 8682 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); 8683 if !within(self.params.range, pattern_range) { 8684 return; 8685 } 8686 8687 if let TypedPattern::Constructor { 8688 arguments, 8689 spread: Some(spread_location), 8690 .. 8691 } = pattern 8692 && let Some(PatternUnusedArguments { 8693 positional, 8694 labelled, 8695 }) = pattern.unused_arguments() 8696 { 8697 // If there's any unused argument that's being ignored we want to 8698 // suggest the code action. 8699 let first_labelled_argument_start = arguments 8700 .iter() 8701 .find(|arg| !arg.is_implicit() && arg.label.is_some()) 8702 .map(|arg| arg.location.start); 8703 8704 let last_argument_end = arguments 8705 .iter() 8706 .rfind(|arg| !arg.is_implicit()) 8707 .map(|arg| arg.location.end); 8708 8709 self.data = Some(FillUnusedFieldsData { 8710 positional, 8711 labelled, 8712 first_labelled_argument_start, 8713 last_argument_end, 8714 spread_location: *spread_location, 8715 }); 8716 }; 8717 8718 ast::visit::visit_typed_pattern(self, pattern); 8719 } 8720} 8721 8722/// Code action to remove an echo. 8723/// 8724pub struct RemoveEchos<'a> { 8725 module: &'a Module, 8726 params: &'a CodeActionParams, 8727 edits: TextEdits<'a>, 8728 is_hovering_echo: bool, 8729 echo_spans_to_delete: Vec<SrcSpan>, 8730 // We need to keep a reference to the two latest pipeline assignments we 8731 // run into to properly delete an echo that's inside a pipeline. 8732 latest_pipe_step: Option<SrcSpan>, 8733 second_to_latest_pipe_step: Option<SrcSpan>, 8734} 8735 8736impl<'a> RemoveEchos<'a> { 8737 pub fn new( 8738 module: &'a Module, 8739 line_numbers: &'a LineNumbers, 8740 params: &'a CodeActionParams, 8741 ) -> Self { 8742 Self { 8743 module, 8744 params, 8745 edits: TextEdits::new(line_numbers), 8746 is_hovering_echo: false, 8747 echo_spans_to_delete: vec![], 8748 latest_pipe_step: None, 8749 second_to_latest_pipe_step: None, 8750 } 8751 } 8752 8753 pub fn code_actions(mut self) -> Vec<CodeAction> { 8754 self.visit_typed_module(&self.module.ast); 8755 8756 // We only want to trigger the action if we're over one of the echos in 8757 // the module 8758 if !self.is_hovering_echo { 8759 return vec![]; 8760 }; 8761 8762 for span in self.echo_spans_to_delete { 8763 self.edits.delete(span); 8764 } 8765 8766 let mut action = Vec::with_capacity(1); 8767 CodeActionBuilder::new("Remove all `echo`s from this module") 8768 .kind(CodeActionKind::RefactorRewrite) 8769 .changes(self.params.text_document.uri.clone(), self.edits.edits) 8770 .preferred(false) 8771 .push_to(&mut action); 8772 action 8773 } 8774 8775 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) { 8776 for i in 0..statements.len() { 8777 let statement = statements 8778 .get(i) 8779 .expect("Statement must exist in iteration"); 8780 let next_statement = statements.get(i + 1); 8781 let is_last = i == statements.len() - 1; 8782 8783 match statement { 8784 // We remove any echo that is used as a standalone statement used 8785 // to print a literal value. 8786 // 8787 // ```gleam 8788 // pub fn main() { 8789 // echo "I'm here" 8790 // do_something() 8791 // echo "Safe!" 8792 // do_something_else() 8793 // } 8794 // ``` 8795 // 8796 // Here we want to remove not just the echo but also the literal 8797 // strings they're printing. 8798 // 8799 // It's safe to do this only if echo is not the last expression 8800 // in a function's block (otherwise we might change the function's 8801 // return type by removing the entire line) and the value being 8802 // printed is a literal expression. 8803 // 8804 ast::Statement::Expression(TypedExpr::Echo { 8805 location, 8806 expression, 8807 .. 8808 }) if !is_last 8809 && expression.as_ref().is_some_and(|expression| { 8810 expression.is_literal() || expression.is_var() 8811 }) => 8812 { 8813 let echo_range = self.edits.src_span_to_lsp_range(*location); 8814 if within(self.params.range, echo_range) { 8815 self.is_hovering_echo = true; 8816 } 8817 8818 let end = next_statement 8819 .map(|next| { 8820 let echo_end = location.end; 8821 let next_start = next.location().start; 8822 // We want to remove everything until the start of the 8823 // following statement. However, we have to be careful not to 8824 // delete any comments. So if there's any comment between the 8825 // echo to remove and the next statement, we just delete until 8826 // the comment's start. 8827 self.module 8828 .extra 8829 .first_comment_between(echo_end, next_start) 8830 // For comments we record the start of their content, not of the `//` 8831 // so we're subtracting 2 here to not delete the `//` as well 8832 .map(|comment| comment.start - 2) 8833 .unwrap_or(next_start) 8834 }) 8835 .unwrap_or(location.end); 8836 8837 self.echo_spans_to_delete.push(SrcSpan { 8838 start: location.start, 8839 end, 8840 }); 8841 } 8842 8843 // Otherwise we visit the statement as usual. 8844 ast::Statement::Expression(_) 8845 | ast::Statement::Assignment(_) 8846 | ast::Statement::Use(_) 8847 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement), 8848 } 8849 } 8850 } 8851} 8852 8853impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> { 8854 fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { 8855 self.visit_function_statements(&fun.body); 8856 } 8857 8858 fn visit_typed_expr_fn( 8859 &mut self, 8860 _location: &'ast SrcSpan, 8861 _type_: &'ast Arc<Type>, 8862 _kind: &'ast FunctionLiteralKind, 8863 _arguments: &'ast [TypedArg], 8864 body: &'ast Vec1<TypedStatement>, 8865 _return_annotation: &'ast Option<ast::TypeAst>, 8866 ) { 8867 self.visit_function_statements(body); 8868 } 8869 8870 fn visit_typed_expr_echo( 8871 &mut self, 8872 location: &'ast SrcSpan, 8873 type_: &'ast Arc<Type>, 8874 expression: &'ast Option<Box<TypedExpr>>, 8875 message: &'ast Option<Box<TypedExpr>>, 8876 ) { 8877 // We also want to trigger the action if we're hovering over the expression 8878 // being printed. So we create a unique span starting from the start of echo 8879 // end ending at the end of the expression. 8880 // 8881 // ``` 8882 // echo 1 + 2 8883 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're 8884 // inside it, not just the keyword 8885 // ``` 8886 // 8887 let echo_range = self.edits.src_span_to_lsp_range(*location); 8888 if within(self.params.range, echo_range) { 8889 self.is_hovering_echo = true; 8890 } 8891 8892 // We also want to remove the echo message! 8893 if message.is_some() { 8894 let start = expression 8895 .as_ref() 8896 .map(|expression| expression.location().end) 8897 .unwrap_or(location.start + 4); 8898 8899 self.echo_spans_to_delete 8900 .push(SrcSpan::new(start, location.end)); 8901 } 8902 8903 if let Some(expression) = expression { 8904 // If there's an expression we delete everything we find until its 8905 // start (excluded). 8906 let span_to_delete = SrcSpan::new(location.start, expression.location().start); 8907 self.echo_spans_to_delete.push(span_to_delete); 8908 } else { 8909 // Othwerise we know we're inside a pipeline, we take the closest step 8910 // that is not echo itself and delete everything from its end until the 8911 // end of the echo keyword: 8912 // 8913 // ```txt 8914 // wibble |> echo |> wobble 8915 // ^^^^^^^^ This span right here 8916 // ``` 8917 let step_preceding_echo = self 8918 .latest_pipe_step 8919 .filter(|l| l != location) 8920 .or(self.second_to_latest_pipe_step); 8921 if let Some(step_preceding_echo) = step_preceding_echo { 8922 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4); 8923 self.echo_spans_to_delete.push(span_to_delete); 8924 } 8925 } 8926 8927 ast::visit::visit_typed_expr_echo(self, location, type_, expression, message); 8928 } 8929 8930 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 8931 if self.latest_pipe_step.is_some() { 8932 self.second_to_latest_pipe_step = self.latest_pipe_step; 8933 } 8934 self.latest_pipe_step = Some(assignment.location); 8935 ast::visit::visit_typed_pipeline_assignment(self, assignment); 8936 } 8937} 8938 8939/// Code action to wrap assignment and case clause values in a block. 8940/// 8941/// ```gleam 8942/// pub type PokemonType { 8943/// Fire 8944/// Water 8945/// } 8946/// 8947/// pub fn main() { 8948/// let pokemon_type: PokemonType = todo 8949/// case pokemon_type { 8950/// Water -> soak() 8951/// ^^^^^^ Cursor over the spread 8952/// Fire -> burn() 8953/// } 8954/// } 8955/// ``` 8956/// Becomes 8957/// ```gleam 8958/// pub type PokemonType { 8959/// Fire 8960/// Water 8961/// } 8962/// 8963/// pub fn main() { 8964/// let pokemon_type: PokemonType = todo 8965/// case pokemon_type { 8966/// Water -> { 8967/// soak() 8968/// } 8969/// Fire -> burn() 8970/// } 8971/// } 8972/// ``` 8973/// 8974pub struct WrapInBlock<'a> { 8975 module: &'a Module, 8976 params: &'a CodeActionParams, 8977 edits: TextEdits<'a>, 8978 selected_expression: Option<SrcSpan>, 8979} 8980 8981impl<'a> WrapInBlock<'a> { 8982 pub fn new( 8983 module: &'a Module, 8984 line_numbers: &'a LineNumbers, 8985 params: &'a CodeActionParams, 8986 ) -> Self { 8987 Self { 8988 module, 8989 params, 8990 edits: TextEdits::new(line_numbers), 8991 selected_expression: None, 8992 } 8993 } 8994 8995 pub fn code_actions(mut self) -> Vec<CodeAction> { 8996 self.visit_typed_module(&self.module.ast); 8997 8998 let Some(expr_span) = self.selected_expression else { 8999 return vec![]; 9000 }; 9001 9002 let Some(expr_string) = self 9003 .module 9004 .code 9005 .get(expr_span.start as usize..(expr_span.end as usize + 1)) 9006 else { 9007 return vec![]; 9008 }; 9009 9010 let range = self 9011 .edits 9012 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 9013 9014 let indent_size = 9015 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 9016 9017 let expr_indent_size = indent_size + 2; 9018 9019 let indent = " ".repeat(indent_size); 9020 let inner_indent = " ".repeat(expr_indent_size); 9021 9022 self.edits.replace( 9023 expr_span, 9024 format!("{{\n{inner_indent}{expr_string}{indent}}}"), 9025 ); 9026 9027 let mut action = Vec::with_capacity(1); 9028 CodeActionBuilder::new("Wrap in block") 9029 .kind(CodeActionKind::RefactorExtract) 9030 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9031 .preferred(false) 9032 .push_to(&mut action); 9033 action 9034 } 9035} 9036 9037impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> { 9038 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 9039 ast::visit::visit_typed_expr(self, &assignment.value); 9040 if !within( 9041 self.params.range, 9042 self.edits 9043 .src_span_to_lsp_range(assignment.value.location()), 9044 ) { 9045 return; 9046 } 9047 match &assignment.value { 9048 // To avoid wrapping the same expression in multiple, nested blocks. 9049 TypedExpr::Block { .. } => {} 9050 TypedExpr::RecordAccess { .. } 9051 | TypedExpr::PositionalAccess { .. } 9052 | TypedExpr::Int { .. } 9053 | TypedExpr::Float { .. } 9054 | TypedExpr::String { .. } 9055 | TypedExpr::Pipeline { .. } 9056 | TypedExpr::Var { .. } 9057 | TypedExpr::Fn { .. } 9058 | TypedExpr::List { .. } 9059 | TypedExpr::Call { .. } 9060 | TypedExpr::BinOp { .. } 9061 | TypedExpr::Case { .. } 9062 | TypedExpr::ModuleSelect { .. } 9063 | TypedExpr::Tuple { .. } 9064 | TypedExpr::TupleIndex { .. } 9065 | TypedExpr::Todo { .. } 9066 | TypedExpr::Panic { .. } 9067 | TypedExpr::Echo { .. } 9068 | TypedExpr::BitArray { .. } 9069 | TypedExpr::RecordUpdate { .. } 9070 | TypedExpr::NegateBool { .. } 9071 | TypedExpr::NegateInt { .. } 9072 | TypedExpr::Invalid { .. } => { 9073 self.selected_expression = Some(assignment.value.location()); 9074 } 9075 }; 9076 ast::visit::visit_typed_assignment(self, assignment); 9077 } 9078 9079 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 9080 ast::visit::visit_typed_clause(self, clause); 9081 9082 if !within( 9083 self.params.range, 9084 self.edits.src_span_to_lsp_range(clause.then.location()), 9085 ) { 9086 return; 9087 } 9088 9089 // To avoid wrapping the same expression in multiple, nested blocks. 9090 if !matches!(clause.then, TypedExpr::Block { .. }) { 9091 self.selected_expression = Some(clause.then.location()); 9092 }; 9093 9094 ast::visit::visit_typed_clause(self, clause); 9095 } 9096} 9097 9098/// Code action to fix wrong binary operators when the compiler can easily tell 9099/// what the correct alternative is. 9100/// 9101/// ```gleam 9102/// 1 +. 2 // becomes 1 + 2 9103/// 1.0 + 2.3 // becomes 1.0 +. 2.3 9104/// ``` 9105/// 9106pub struct FixBinaryOperation<'a> { 9107 module: &'a Module, 9108 params: &'a CodeActionParams, 9109 edits: TextEdits<'a>, 9110 fix: Option<(SrcSpan, ast::BinOp)>, 9111} 9112 9113impl<'a> FixBinaryOperation<'a> { 9114 pub fn new( 9115 module: &'a Module, 9116 line_numbers: &'a LineNumbers, 9117 params: &'a CodeActionParams, 9118 ) -> Self { 9119 Self { 9120 module, 9121 params, 9122 edits: TextEdits::new(line_numbers), 9123 fix: None, 9124 } 9125 } 9126 9127 pub fn code_actions(mut self) -> Vec<CodeAction> { 9128 self.visit_typed_module(&self.module.ast); 9129 9130 let Some((location, replacement)) = self.fix else { 9131 return vec![]; 9132 }; 9133 9134 self.edits.replace(location, replacement.name().into()); 9135 9136 let mut action = Vec::with_capacity(1); 9137 CodeActionBuilder::new(&format!("Use `{}`", replacement.name())) 9138 .kind(CodeActionKind::RefactorRewrite) 9139 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9140 .preferred(true) 9141 .push_to(&mut action); 9142 action 9143 } 9144 9145 fn try_fix( 9146 &mut self, 9147 left: Arc<Type>, 9148 right: Arc<Type>, 9149 operator: ast::BinOp, 9150 operator_start: u32, 9151 ) { 9152 let operator_location = SrcSpan::new(operator_start, operator_start + operator.size()); 9153 if operator.is_int_operator() && left.is_float() && right.is_float() { 9154 self.fix = operator 9155 .float_equivalent() 9156 .map(|fix| (operator_location, fix)); 9157 } else if operator.is_float_operator() && left.is_int() && right.is_int() { 9158 self.fix = operator 9159 .int_equivalent() 9160 .map(|fix| (operator_location, fix)) 9161 } else if operator == ast::BinOp::AddInt && left.is_string() && right.is_string() { 9162 self.fix = Some((operator_location, ast::BinOp::Concatenate)) 9163 } 9164 } 9165} 9166 9167impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> { 9168 fn visit_typed_expr_case( 9169 &mut self, 9170 location: &'ast SrcSpan, 9171 type_: &'ast Arc<Type>, 9172 subjects: &'ast [TypedExpr], 9173 clauses: &'ast [ast::TypedClause], 9174 compiled_case: &'ast CompiledCase, 9175 ) { 9176 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 9177 } 9178 fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) { 9179 ast::visit::visit_typed_clause_guard(self, guard); 9180 } 9181 9182 fn visit_typed_clause_guard_bin_op( 9183 &mut self, 9184 left: &'ast TypedClauseGuard, 9185 right: &'ast TypedClauseGuard, 9186 operator: &'ast ast::BinOp, 9187 operator_start: &'ast u32, 9188 location: &'ast SrcSpan, 9189 ) { 9190 let binop_range = self.edits.src_span_to_lsp_range(*location); 9191 if !within(self.params.range, binop_range) { 9192 return; 9193 } 9194 9195 self.try_fix(left.type_(), right.type_(), *operator, *operator_start); 9196 9197 ast::visit::visit_typed_clause_guard_bin_op( 9198 self, 9199 left, 9200 right, 9201 operator, 9202 operator_start, 9203 location, 9204 ); 9205 } 9206 9207 fn visit_typed_expr_bin_op( 9208 &mut self, 9209 location: &'ast SrcSpan, 9210 type_: &'ast Arc<Type>, 9211 operator: &'ast ast::BinOp, 9212 operator_start: &'ast u32, 9213 left: &'ast TypedExpr, 9214 right: &'ast TypedExpr, 9215 ) { 9216 let binop_range = self.edits.src_span_to_lsp_range(*location); 9217 if !within(self.params.range, binop_range) { 9218 return; 9219 } 9220 9221 self.try_fix(left.type_(), right.type_(), *operator, *operator_start); 9222 9223 ast::visit::visit_typed_expr_bin_op( 9224 self, 9225 location, 9226 type_, 9227 operator, 9228 operator_start, 9229 left, 9230 right, 9231 ); 9232 } 9233} 9234 9235/// Code action builder to automatically fix segments that have a value that's 9236/// guaranteed to overflow. 9237/// 9238pub struct FixTruncatedBitArraySegment<'a> { 9239 module: &'a Module, 9240 params: &'a CodeActionParams, 9241 edits: TextEdits<'a>, 9242 truncation: Option<BitArraySegmentTruncation>, 9243} 9244 9245impl<'a> FixTruncatedBitArraySegment<'a> { 9246 pub fn new( 9247 module: &'a Module, 9248 line_numbers: &'a LineNumbers, 9249 params: &'a CodeActionParams, 9250 ) -> Self { 9251 Self { 9252 module, 9253 params, 9254 edits: TextEdits::new(line_numbers), 9255 truncation: None, 9256 } 9257 } 9258 9259 pub fn code_actions(mut self) -> Vec<CodeAction> { 9260 self.visit_typed_module(&self.module.ast); 9261 9262 let Some(truncation) = self.truncation else { 9263 return vec![]; 9264 }; 9265 9266 let replacement = truncation.truncated_into.to_string(); 9267 self.edits 9268 .replace(truncation.value_location, replacement.clone()); 9269 9270 let mut action = Vec::with_capacity(1); 9271 CodeActionBuilder::new(&format!("Replace with `{replacement}`")) 9272 .kind(CodeActionKind::RefactorRewrite) 9273 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9274 .preferred(true) 9275 .push_to(&mut action); 9276 action 9277 } 9278} 9279 9280impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> { 9281 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) { 9282 let segment_range = self.edits.src_span_to_lsp_range(segment.location); 9283 if !within(self.params.range, segment_range) { 9284 return; 9285 } 9286 9287 if let Some(truncation) = segment.check_for_truncated_value() { 9288 self.truncation = Some(truncation); 9289 } 9290 9291 ast::visit::visit_typed_expr_bit_array_segment(self, segment); 9292 } 9293} 9294 9295/// Code action builder to remove unused imports and values. 9296/// 9297pub struct RemoveUnusedImports<'a> { 9298 module: &'a Module, 9299 params: &'a CodeActionParams, 9300 edits: TextEdits<'a>, 9301} 9302 9303#[derive(Debug)] 9304enum UnusedImport { 9305 ValueOrType(SrcSpan), 9306 Module(SrcSpan), 9307 ModuleAlias(SrcSpan), 9308} 9309 9310impl UnusedImport { 9311 fn location(&self) -> SrcSpan { 9312 match self { 9313 UnusedImport::ValueOrType(location) 9314 | UnusedImport::Module(location) 9315 | UnusedImport::ModuleAlias(location) => *location, 9316 } 9317 } 9318} 9319 9320impl<'a> RemoveUnusedImports<'a> { 9321 pub fn new( 9322 module: &'a Module, 9323 line_numbers: &'a LineNumbers, 9324 params: &'a CodeActionParams, 9325 ) -> Self { 9326 Self { 9327 module, 9328 params, 9329 edits: TextEdits::new(line_numbers), 9330 } 9331 } 9332 9333 /// Given an import location, returns a list of the spans of all the 9334 /// unqualified values it's importing. Sorted by SrcSpan location. 9335 /// 9336 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> { 9337 self.module 9338 .ast 9339 .definitions 9340 .imports 9341 .iter() 9342 .find(|import| import.location.contains(import_location.start)) 9343 .map(|import| { 9344 let types = import.unqualified_types.iter().map(|type_| type_.location); 9345 let values = import.unqualified_values.iter().map(|value| value.location); 9346 types 9347 .chain(values) 9348 .sorted_by_key(|location| location.start) 9349 .collect_vec() 9350 }) 9351 .unwrap_or_default() 9352 } 9353 9354 pub fn code_actions(mut self) -> Vec<CodeAction> { 9355 // If there's no import in the module then there can't be any unused 9356 // import to remove. 9357 if self.module.ast.definitions.imports.is_empty() { 9358 return vec![]; 9359 } 9360 9361 let unused_imports = self 9362 .module 9363 .ast 9364 .type_info 9365 .warnings 9366 .iter() 9367 .filter_map(|warning| match warning { 9368 type_::Warning::UnusedImportedValue { location, .. } => { 9369 Some(UnusedImport::ValueOrType(*location)) 9370 } 9371 type_::Warning::UnusedType { 9372 location, 9373 imported: true, 9374 .. 9375 } => Some(UnusedImport::ValueOrType(*location)), 9376 type_::Warning::UnusedImportedModule { location, .. } => { 9377 Some(UnusedImport::Module(*location)) 9378 } 9379 type_::Warning::UnusedImportedModuleAlias { location, .. } => { 9380 Some(UnusedImport::ModuleAlias(*location)) 9381 } 9382 type_::Warning::Todo { .. } 9383 | type_::Warning::ImplicitlyDiscardedResult { .. } 9384 | type_::Warning::UnusedLiteral { .. } 9385 | type_::Warning::UnusedValue { .. } 9386 | type_::Warning::NoFieldsRecordUpdate { .. } 9387 | type_::Warning::AllFieldsRecordUpdate { .. } 9388 | type_::Warning::UnusedType { .. } 9389 | type_::Warning::UnusedConstructor { .. } 9390 | type_::Warning::UnusedPrivateModuleConstant { .. } 9391 | type_::Warning::UnusedPrivateFunction { .. } 9392 | type_::Warning::UnusedVariable { .. } 9393 | type_::Warning::UnnecessaryDoubleIntNegation { .. } 9394 | type_::Warning::UnnecessaryDoubleBoolNegation { .. } 9395 | type_::Warning::InefficientEmptyListCheck { .. } 9396 | type_::Warning::TransitiveDependencyImported { .. } 9397 | type_::Warning::DeprecatedItem { .. } 9398 | type_::Warning::UnreachableCasePattern { .. } 9399 | type_::Warning::UnusedDiscardPattern { .. } 9400 | type_::Warning::CaseMatchOnLiteralCollection { .. } 9401 | type_::Warning::CaseMatchOnLiteralValue { .. } 9402 | type_::Warning::OpaqueExternalType { .. } 9403 | type_::Warning::RedundantAssertAssignment { .. } 9404 | type_::Warning::AssertAssignmentOnImpossiblePattern { .. } 9405 | type_::Warning::TodoOrPanicUsedAsFunction { .. } 9406 | type_::Warning::UnreachableCodeAfterPanic { .. } 9407 | type_::Warning::RedundantPipeFunctionCapture { .. } 9408 | type_::Warning::FeatureRequiresHigherGleamVersion { .. } 9409 | type_::Warning::JavaScriptIntUnsafe { .. } 9410 | type_::Warning::AssertLiteralBool { .. } 9411 | type_::Warning::BitArraySegmentTruncatedValue { .. } 9412 | type_::Warning::ModuleImportedTwice { .. } 9413 | type_::Warning::TopLevelDefinitionShadowsImport { .. } 9414 | type_::Warning::RedundantComparison { .. } 9415 | type_::Warning::UnusedRecursiveArgument { .. } 9416 | type_::Warning::JavaScriptBitArrayUnsafeInt { .. } 9417 | type_::Warning::PipeIntoCallWhichReturnsFunction { .. } => None, 9418 }) 9419 .sorted_by_key(|import| import.location()) 9420 .collect_vec(); 9421 9422 // If the cursor is not over any of the unused imports then we don't offer 9423 // the code action. 9424 let hovering_unused_import = unused_imports.iter().any(|import| { 9425 let unused_range = self.edits.src_span_to_lsp_range(import.location()); 9426 overlaps(self.params.range, unused_range) 9427 }); 9428 if !hovering_unused_import { 9429 return vec![]; 9430 } 9431 9432 // Otherwise we start removing all unused imports: 9433 for import in &unused_imports { 9434 match import { 9435 // When an entire module is unused we can delete its entire location 9436 // in the source code. 9437 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { 9438 if self.edits.line_numbers.spans_entire_line(location) { 9439 // If the unused module spans over the entire line then 9440 // we also take care of removing the following newline 9441 // characther! 9442 self.edits.delete(SrcSpan { 9443 start: location.start, 9444 end: location.end + 1, 9445 }) 9446 } else { 9447 self.edits.delete(*location) 9448 } 9449 } 9450 9451 // When removing unused imported values we have to be a bit more 9452 // careful: an unused value might be followed or preceded by a 9453 // comma that we also need to remove! 9454 UnusedImport::ValueOrType(location) => { 9455 let imported = self.imported_values(*location); 9456 let unused_index = imported.binary_search(location); 9457 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1); 9458 let next_value = unused_index 9459 .ok() 9460 .and_then(|value_index| imported.get(value_index + 1)); 9461 let previous_value = unused_index.ok().and_then(|value_index| { 9462 value_index 9463 .checked_sub(1) 9464 .and_then(|previous_index| imported.get(previous_index)) 9465 }); 9466 let previous_is_unused = previous_value.is_some_and(|previous| { 9467 unused_imports 9468 .as_slice() 9469 .binary_search_by_key(previous, |import| import.location()) 9470 .is_ok() 9471 }); 9472 9473 match (previous_value, next_value) { 9474 // If there's a value following the unused import we need 9475 // to remove all characters until its start! 9476 // 9477 // ```gleam 9478 // import wibble.{unused, used} 9479 // // ^^^^^^^^^^^ We need to remove all of this! 9480 // ``` 9481 // 9482 (_, Some(next_value)) => self.edits.delete(SrcSpan { 9483 start: location.start, 9484 end: next_value.start, 9485 }), 9486 9487 // If this unused import is the last of the unuqualified 9488 // list and is preceded by another used value then we 9489 // need to do some additional cleanup and remove all 9490 // characters starting from its end. 9491 // (If the previous one is unused as well it will take 9492 // care of removing all the extra space) 9493 // 9494 // ```gleam 9495 // import wibble.{used, unused} 9496 // // ^^^^^^^^^^^^ We need to remove all of this! 9497 // ``` 9498 // 9499 (Some(previous_value), _) if is_last && !previous_is_unused => { 9500 self.edits.delete(SrcSpan { 9501 start: previous_value.end, 9502 end: location.end, 9503 }) 9504 } 9505 9506 // In all other cases it means that this is the only 9507 // item in the import list. We can just remove it. 9508 // 9509 // ```gleam 9510 // import wibble.{unused} 9511 // // ^^^^^^ We remove this import, the formatter will already 9512 // // take care of removing the empty curly braces 9513 // ``` 9514 // 9515 (_, _) => self.edits.delete(*location), 9516 } 9517 } 9518 } 9519 } 9520 9521 let mut action = Vec::with_capacity(1); 9522 CodeActionBuilder::new("Remove unused imports") 9523 .kind(CodeActionKind::RefactorRewrite) 9524 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9525 .preferred(true) 9526 .push_to(&mut action); 9527 action 9528 } 9529} 9530 9531/// Code action to remove a block wrapping a single expression. 9532/// 9533pub struct RemoveBlock<'a> { 9534 module: &'a Module, 9535 params: &'a CodeActionParams, 9536 edits: TextEdits<'a>, 9537 block_span: Option<SrcSpan>, 9538 position: RemoveBlockPosition, 9539} 9540 9541#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] 9542enum RemoveBlockPosition { 9543 InsideBinOp, 9544 OutsideBinOp, 9545} 9546 9547impl<'a> RemoveBlock<'a> { 9548 pub fn new( 9549 module: &'a Module, 9550 line_numbers: &'a LineNumbers, 9551 params: &'a CodeActionParams, 9552 ) -> Self { 9553 Self { 9554 module, 9555 params, 9556 edits: TextEdits::new(line_numbers), 9557 block_span: None, 9558 position: RemoveBlockPosition::OutsideBinOp, 9559 } 9560 } 9561 9562 pub fn code_actions(mut self) -> Vec<CodeAction> { 9563 self.visit_typed_module(&self.module.ast); 9564 9565 let Some(SrcSpan { start, end }) = self.block_span else { 9566 return vec![]; 9567 }; 9568 9569 self.edits.delete(SrcSpan::new(start, start + 1)); 9570 self.edits.delete(SrcSpan::new(end - 1, end)); 9571 9572 let mut action = Vec::with_capacity(1); 9573 CodeActionBuilder::new("Remove block") 9574 .kind(CodeActionKind::RefactorRewrite) 9575 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9576 .preferred(true) 9577 .push_to(&mut action); 9578 action 9579 } 9580} 9581 9582impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> { 9583 fn visit_typed_expr_bin_op( 9584 &mut self, 9585 _location: &'ast SrcSpan, 9586 _type_: &'ast Arc<Type>, 9587 _operator: &'ast ast::BinOp, 9588 _operator_start: &'ast u32, 9589 left: &'ast TypedExpr, 9590 right: &'ast TypedExpr, 9591 ) { 9592 let old_position = self.position; 9593 self.position = RemoveBlockPosition::InsideBinOp; 9594 ast::visit::visit_typed_expr(self, left); 9595 self.position = RemoveBlockPosition::InsideBinOp; 9596 ast::visit::visit_typed_expr(self, right); 9597 self.position = old_position; 9598 } 9599 9600 fn visit_typed_expr_block( 9601 &mut self, 9602 location: &'ast SrcSpan, 9603 statements: &'ast [TypedStatement], 9604 ) { 9605 let block_range = self.edits.src_span_to_lsp_range(*location); 9606 if !within(self.params.range, block_range) { 9607 return; 9608 } 9609 9610 match statements { 9611 [] | [_, _, ..] => (), 9612 [value] => match value { 9613 ast::Statement::Use(_) 9614 | ast::Statement::Assert(_) 9615 | ast::Statement::Assignment(_) => { 9616 ast::visit::visit_typed_expr_block(self, location, statements) 9617 } 9618 9619 ast::Statement::Expression(expr) => match expr { 9620 TypedExpr::Int { .. } 9621 | TypedExpr::Float { .. } 9622 | TypedExpr::String { .. } 9623 | TypedExpr::Block { .. } 9624 | TypedExpr::Var { .. } 9625 | TypedExpr::Fn { .. } 9626 | TypedExpr::List { .. } 9627 | TypedExpr::Call { .. } 9628 | TypedExpr::Case { .. } 9629 | TypedExpr::RecordAccess { .. } 9630 | TypedExpr::PositionalAccess { .. } 9631 | TypedExpr::ModuleSelect { .. } 9632 | TypedExpr::Tuple { .. } 9633 | TypedExpr::TupleIndex { .. } 9634 | TypedExpr::Todo { .. } 9635 | TypedExpr::Panic { .. } 9636 | TypedExpr::Echo { .. } 9637 | TypedExpr::BitArray { .. } 9638 | TypedExpr::RecordUpdate { .. } 9639 | TypedExpr::NegateBool { .. } 9640 | TypedExpr::NegateInt { .. } 9641 | TypedExpr::Invalid { .. } => { 9642 self.block_span = Some(*location); 9643 } 9644 TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => { 9645 if self.position == RemoveBlockPosition::OutsideBinOp { 9646 self.block_span = Some(*location); 9647 } 9648 } 9649 }, 9650 }, 9651 } 9652 9653 ast::visit::visit_typed_expr_block(self, location, statements); 9654 } 9655} 9656 9657/// Code action to remove `opaque` from a private type. 9658/// 9659pub struct RemovePrivateOpaque<'a> { 9660 module: &'a Module, 9661 params: &'a CodeActionParams, 9662 edits: TextEdits<'a>, 9663 opaque_span: Option<SrcSpan>, 9664} 9665 9666impl<'a> RemovePrivateOpaque<'a> { 9667 pub fn new( 9668 module: &'a Module, 9669 line_numbers: &'a LineNumbers, 9670 params: &'a CodeActionParams, 9671 ) -> Self { 9672 Self { 9673 module, 9674 params, 9675 edits: TextEdits::new(line_numbers), 9676 opaque_span: None, 9677 } 9678 } 9679 9680 pub fn code_actions(mut self) -> Vec<CodeAction> { 9681 self.visit_typed_module(&self.module.ast); 9682 9683 let Some(opaque_span) = self.opaque_span else { 9684 return vec![]; 9685 }; 9686 9687 self.edits.delete(opaque_span); 9688 9689 let mut action = Vec::with_capacity(1); 9690 CodeActionBuilder::new("Remove opaque from private type") 9691 .kind(CodeActionKind::QuickFix) 9692 .changes(self.params.text_document.uri.clone(), self.edits.edits) 9693 .preferred(true) 9694 .push_to(&mut action); 9695 action 9696 } 9697} 9698 9699impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> { 9700 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 9701 let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location); 9702 if !within(self.params.range, custom_type_range) { 9703 return; 9704 } 9705 9706 if custom_type.opaque && custom_type.publicity.is_private() { 9707 self.opaque_span = Some(SrcSpan { 9708 start: custom_type.location.start, 9709 end: custom_type.location.start + 7, 9710 }) 9711 } 9712 } 9713} 9714 9715/// Code action to rewrite a case expression as part of an outer case expression 9716/// branch. For example: 9717/// 9718/// ```gleam 9719/// case wibble { 9720/// Ok(a) -> case a { 9721/// 1 -> todo 9722/// _ -> todo 9723/// } 9724/// Error(_) -> todo 9725/// } 9726/// ``` 9727/// 9728/// Would become: 9729/// 9730/// ```gleam 9731/// case wibble { 9732/// Ok(1) -> todo 9733/// Ok(_) -> todo 9734/// Error(_) -> todo 9735/// } 9736/// ``` 9737/// 9738pub struct CollapseNestedCase<'a> { 9739 module: &'a Module, 9740 params: &'a CodeActionParams, 9741 edits: TextEdits<'a>, 9742 collapsed: Option<Collapsed<'a>>, 9743} 9744 9745/// This holds all the needed data about the pattern to collapse. 9746/// We'll use this piece of code as an example: 9747/// ```gleam 9748/// case something { 9749/// User(username: _, NotAdmin) -> "Stranger!!" 9750/// User(username:, Admin) if wibble -> 9751/// case username { // <- We're collapsing this nested case 9752/// "Joe" -> "Hello, Joe!" 9753/// _ -> "I don't know you, " <> username 9754/// } 9755/// } 9756/// ``` 9757/// 9758struct Collapsed<'a> { 9759 /// This is the span covering the entire clause being collapsed: 9760 /// 9761 /// ```gleam 9762 /// case something { 9763 /// User(username: _, NotAdmin) -> "Stranger!!" 9764 /// User(username:, Admin) if wibble -> 9765 /// ┬ It goes all the way from here... 9766 /// ╭─╯ 9767 /// │ case username { 9768 /// │ "Joe" -> "Hello, Joe!" 9769 /// │ _ -> "I don't know you, " <> username 9770 /// │ } 9771 /// │ ┬ ...to here! 9772 /// ╰───╯ 9773 /// } 9774 /// ``` 9775 /// 9776 outer_clause_span: SrcSpan, 9777 9778 /// The (optional) guard of the outer branch. In this exmaple it's this one: 9779 /// 9780 /// ```gleam 9781 /// case something { 9782 /// User(username: _, NotAdmin) -> "Stranger!!" 9783 /// User(username:, Admin) if wibble -> 9784 /// ┬──────── 9785 /// ╰─ `outer_guard` 9786 /// case username { 9787 /// "Joe" -> "Hello, Joe!" 9788 /// _ -> "I don't know you, " <> username 9789 /// } 9790 /// } 9791 /// ``` 9792 /// 9793 outer_guard: &'a Option<TypedClauseGuard>, 9794 9795 /// The pattern variable being matched on: 9796 /// 9797 /// ```gleam 9798 /// case something { 9799 /// User(username: _, NotAdmin) -> "Stranger!!" 9800 /// User(username:, Admin) if wibble -> 9801 /// ┬─────── 9802 /// ╰─ `matched_variable` 9803 /// case username { 9804 /// "Joe" -> "Hello, Joe!" 9805 /// _ -> "I don't know you, " <> username 9806 /// } 9807 /// } 9808 /// ``` 9809 /// 9810 matched_variable: BoundVariable, 9811 9812 /// The span covering the entire pattern that is bringing the matched 9813 /// variable in scope: 9814 /// 9815 /// ```gleam 9816 /// case something { 9817 /// User(username: _, NotAdmin) -> "Stranger!!" 9818 /// User(username:, Admin) if wibble -> 9819 /// ┬───────────────────── 9820 /// ╰─ `matched_pattern_span` 9821 /// case username { 9822 /// "Joe" -> "Hello, Joe!" 9823 /// _ -> "I don't know you, " <> username 9824 /// } 9825 /// } 9826 /// ``` 9827 /// 9828 matched_pattern_span: SrcSpan, 9829 9830 /// The clauses matching on the `username` variable. In this case they are: 9831 /// ```gleam 9832 /// "Joe" -> "Hello, Joe!" 9833 /// _ -> "I don't know you, " <> username 9834 /// ``` 9835 /// 9836 inner_clauses: &'a Vec<ast::TypedClause>, 9837} 9838 9839impl<'a> CollapseNestedCase<'a> { 9840 pub fn new( 9841 module: &'a Module, 9842 line_numbers: &'a LineNumbers, 9843 params: &'a CodeActionParams, 9844 ) -> Self { 9845 Self { 9846 module, 9847 params, 9848 edits: TextEdits::new(line_numbers), 9849 collapsed: None, 9850 } 9851 } 9852 9853 pub fn code_actions(mut self) -> Vec<CodeAction> { 9854 self.visit_typed_module(&self.module.ast); 9855 9856 let Some(Collapsed { 9857 outer_clause_span, 9858 outer_guard, 9859 ref matched_variable, 9860 matched_pattern_span, 9861 inner_clauses, 9862 }) = self.collapsed 9863 else { 9864 return vec![]; 9865 }; 9866 9867 // Now comes the tricky part: we need to replace the current pattern 9868 // that is bringing the variable into scope with many new patterns, one 9869 // for each of the inner clauses. 9870 // 9871 // Each time we will have to replace the matched variable with the 9872 // pattern used in the inner clause. Let's look at an example: 9873 // 9874 // ```gleam 9875 // Ok(a) -> case a { 9876 // 1 -> wibble 9877 // 2 | 3 -> wobble 9878 // _ -> woo 9879 // } 9880 // ``` 9881 // 9882 // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then 9883 // with `2` and `3`, and finally with `_`. Obtaining something like 9884 // this: 9885 // 9886 // ```gleam 9887 // Ok(1) -> wibble 9888 // Ok(2) | Ok(3) -> wobble 9889 // Ok(_) -> woo 9890 // ``` 9891 // 9892 // Notice one key detail: since alternative patterns can't be nested we 9893 // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`! 9894 9895 let pattern_text: String = code_at(self.module, matched_pattern_span).into(); 9896 let matched_variable_span = matched_variable.location; 9897 9898 let pattern_with_variable = |mut new_content: String| { 9899 let mut new_pattern = pattern_text.clone(); 9900 9901 match matched_variable { 9902 BoundVariable { 9903 name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. }, 9904 .. 9905 } => { 9906 let trimmed_contents = new_content.trim(); 9907 9908 let pattern_is_literal_list = 9909 trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]"); 9910 let pattern_is_discard = trimmed_contents == "_"; 9911 9912 let span_to_replace = match ( 9913 &matched_variable.name, 9914 // We verify whether the pattern is compatible with the list prefix `..`. 9915 // For example, `..var` is valid syntax, but `..[]` and `.._` are not. 9916 pattern_is_literal_list || pattern_is_discard, 9917 ) { 9918 // We normally replace the selected variable with the pattern. 9919 (BoundVariableName::Regular { .. }, _) => matched_variable_span, 9920 9921 // If the selected pattern is not a list, we also replace it normally. 9922 (BoundVariableName::ListTail { .. }, false) => matched_variable_span, 9923 // If the pattern is a list to also remove the list tail prefix. 9924 (BoundVariableName::ListTail { tail_location, .. }, true) => { 9925 // When it's a list literal, we remove the surrounding brackets. 9926 let len = trimmed_contents.len(); 9927 if let Some(slice) = new_content.trim().get(1..(len - 1)) { 9928 new_content = slice.to_string() 9929 }; 9930 9931 *tail_location 9932 } 9933 9934 (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(), 9935 }; 9936 9937 let start_of_pattern = 9938 (span_to_replace.start - matched_pattern_span.start) as usize; 9939 let pattern_length = span_to_replace.len(); 9940 9941 let end_of_pattern = start_of_pattern + pattern_length; 9942 let replaced_range = start_of_pattern..end_of_pattern; 9943 9944 new_pattern.replace_range(replaced_range, &new_content); 9945 } 9946 9947 BoundVariable { 9948 name: BoundVariableName::ShorthandLabel { .. }, 9949 .. 9950 } => { 9951 // But if it's introduced using the shorthand syntax we can't 9952 // just replace it's location with the new pattern: we would be 9953 // removing the label!! 9954 // So we instead insert the pattern right after the label. 9955 new_pattern.insert_str( 9956 (matched_variable_span.end - matched_pattern_span.start) as usize, 9957 &format!(" {new_content}"), 9958 ); 9959 } 9960 } 9961 9962 new_pattern 9963 }; 9964 9965 let mut new_clauses = vec![]; 9966 for clause in inner_clauses { 9967 // Here we take care of unrolling any alterantive patterns: for each 9968 // of the alternatives we build a new pattern and then join 9969 // everything together with ` | `. 9970 9971 let references_to_matched_variable = 9972 FindVariableReferences::new(matched_variable_span, matched_variable.name()) 9973 .find(&clause.then); 9974 9975 let new_patterns = iter::once(&clause.pattern) 9976 .chain(&clause.alternative_patterns) 9977 .map(|patterns| { 9978 // If we've reached this point we've already made in the 9979 // traversal that the inner clause is matching on a single 9980 // subject. So this should be safe to expect! 9981 let pattern_location = 9982 patterns.first().expect("must have a pattern").location(); 9983 9984 let mut pattern_code = code_at(self.module, pattern_location).to_string(); 9985 if !references_to_matched_variable.is_empty() { 9986 pattern_code = format!("{pattern_code} as {}", matched_variable.name()); 9987 }; 9988 pattern_with_variable(pattern_code) 9989 }) 9990 .join(" | "); 9991 9992 let clause_code = code_at(self.module, clause.then.location()); 9993 let guard_code = match (outer_guard, &clause.guard) { 9994 (Some(outer), Some(inner)) => { 9995 let mut outer_code = code_at(self.module, outer.location()).to_string(); 9996 let mut inner_code = code_at(self.module, inner.location()).to_string(); 9997 if ast::BinOp::And.precedence() > outer.precedence() { 9998 outer_code = format!("{{ {outer_code} }}") 9999 } 10000 if ast::BinOp::And.precedence() > inner.precedence() { 10001 inner_code = format!("{{ {inner_code} }}") 10002 } 10003 format!(" if {outer_code} && {inner_code}") 10004 } 10005 (None, Some(guard)) | (Some(guard), None) => { 10006 format!(" if {}", code_at(self.module, guard.location())) 10007 } 10008 (None, None) => "".into(), 10009 }; 10010 10011 new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}")); 10012 } 10013 10014 let pattern_nesting = self 10015 .edits 10016 .src_span_to_lsp_range(outer_clause_span) 10017 .start 10018 .character; 10019 let indentation = " ".repeat(pattern_nesting as usize); 10020 10021 self.edits.replace( 10022 outer_clause_span, 10023 new_clauses.join(&format!("\n{indentation}")), 10024 ); 10025 10026 let mut action = Vec::with_capacity(1); 10027 CodeActionBuilder::new("Collapse nested case") 10028 .kind(CodeActionKind::RefactorRewrite) 10029 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10030 .preferred(false) 10031 .push_to(&mut action); 10032 action 10033 } 10034 10035 /// If the clause can be flattened because it's matching on a single variable 10036 /// defined in it, this function will return the info needed by the language 10037 /// server to flatten that case. 10038 /// 10039 /// We can only flatten a case expression in a very specific case: 10040 /// - This pattern may be introducing multiple variables, 10041 /// - The expression following this branch must be a case, and 10042 /// - It must be matching on one of those variables 10043 /// 10044 /// For example: 10045 /// 10046 /// ```gleam 10047 /// Wibble(a, b, 1) -> case a { ... } 10048 /// Wibble(a, b, 1) -> case b { ... } 10049 /// ``` 10050 /// 10051 fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option<Collapsed<'a>> { 10052 let ast::TypedClause { 10053 pattern, 10054 alternative_patterns, 10055 then, 10056 location, 10057 guard, 10058 } = clause; 10059 10060 if !alternative_patterns.is_empty() { 10061 return None; 10062 } 10063 10064 // The `then` clause must be a single case expression matching on a 10065 // single variable. 10066 let Some(TypedExpr::Case { 10067 subjects, clauses, .. 10068 }) = single_expression(then) 10069 else { 10070 return None; 10071 }; 10072 10073 let [TypedExpr::Var { name, .. }] = subjects.as_slice() else { 10074 return None; 10075 }; 10076 10077 // That variable must be one the variables we brought into scope in this 10078 // branch. 10079 let variable = pattern 10080 .iter() 10081 .flat_map(|pattern| pattern.bound_variables()) 10082 .find(|variable| variable.name() == *name)?; 10083 10084 // There's one last condition to trigger the code action: we must 10085 // actually be with the cursor over the pattern or the nested case 10086 // expression! 10087 // 10088 // ```gleam 10089 // case wibble { 10090 // Ok(a) -> case a { 10091 // //^^^^^^^^^^^^^^^ Anywhere over here! 10092 // } 10093 // } 10094 // ``` 10095 // 10096 let first_pattern = pattern.first().expect("at least one pattern"); 10097 let last_pattern = pattern.last().expect("at least one pattern"); 10098 let pattern_location = first_pattern.location().merge(&last_pattern.location()); 10099 10100 let last_inner_subject = subjects.last().expect("at least one subject"); 10101 let trigger_location = pattern_location.merge(&last_inner_subject.location()); 10102 let trigger_range = self.edits.src_span_to_lsp_range(trigger_location); 10103 10104 if within(self.params.range, trigger_range) { 10105 Some(Collapsed { 10106 outer_clause_span: *location, 10107 outer_guard: guard, 10108 matched_variable: variable, 10109 matched_pattern_span: pattern_location, 10110 inner_clauses: clauses, 10111 }) 10112 } else { 10113 None 10114 } 10115 } 10116} 10117 10118impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> { 10119 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 10120 if let Some(collapsed) = self.flatten_clause(clause) { 10121 self.collapsed = Some(collapsed); 10122 10123 // We're done, there's no need to keep exploring as we know the 10124 // cursor is over this pattern and it can't be over any other one! 10125 return; 10126 }; 10127 10128 ast::visit::visit_typed_clause(self, clause); 10129 } 10130} 10131 10132/// If the expression is a single expression, or a block containing a single 10133/// expression, this function will return it. 10134/// But if the expression is a block with multiple statements, an assignment 10135/// of a use, this will return None. 10136/// 10137fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> { 10138 match expression { 10139 // If a block has a single statement, we can flatten it into a 10140 // single expression if that one statement is an expression. 10141 TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() { 10142 ast::Statement::Expression(expression) => single_expression(expression), 10143 ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => { 10144 None 10145 } 10146 }, 10147 10148 // If a block has multiple statements then it can't be flattened 10149 // into a single expression. 10150 TypedExpr::Block { .. } => None, 10151 10152 TypedExpr::Int { .. } 10153 | TypedExpr::Float { .. } 10154 | TypedExpr::String { .. } 10155 | TypedExpr::Pipeline { .. } 10156 | TypedExpr::Var { .. } 10157 | TypedExpr::Fn { .. } 10158 | TypedExpr::List { .. } 10159 | TypedExpr::Call { .. } 10160 | TypedExpr::BinOp { .. } 10161 | TypedExpr::Case { .. } 10162 | TypedExpr::RecordAccess { .. } 10163 | TypedExpr::PositionalAccess { .. } 10164 | TypedExpr::ModuleSelect { .. } 10165 | TypedExpr::Tuple { .. } 10166 | TypedExpr::TupleIndex { .. } 10167 | TypedExpr::Todo { .. } 10168 | TypedExpr::Panic { .. } 10169 | TypedExpr::Echo { .. } 10170 | TypedExpr::BitArray { .. } 10171 | TypedExpr::RecordUpdate { .. } 10172 | TypedExpr::NegateBool { .. } 10173 | TypedExpr::NegateInt { .. } 10174 | TypedExpr::Invalid { .. } => Some(expression), 10175 } 10176} 10177 10178/// Code action to remove unreachable clauses from a case expression. 10179/// 10180pub struct RemoveUnreachableCaseClauses<'a> { 10181 module: &'a Module, 10182 params: &'a CodeActionParams, 10183 edits: TextEdits<'a>, 10184 /// The source location of the patterns of all the unreachable clauses in 10185 /// the current module. 10186 /// 10187 unreachable_clauses: HashSet<SrcSpan>, 10188 clauses_to_delete: Vec<SrcSpan>, 10189} 10190 10191impl<'a> RemoveUnreachableCaseClauses<'a> { 10192 pub fn new( 10193 module: &'a Module, 10194 line_numbers: &'a LineNumbers, 10195 params: &'a CodeActionParams, 10196 ) -> Self { 10197 let unreachable_clauses = module 10198 .ast 10199 .type_info 10200 .warnings 10201 .iter() 10202 .filter_map(|warning| { 10203 if let type_::Warning::UnreachableCasePattern { location, .. } = warning { 10204 Some(*location) 10205 } else { 10206 None 10207 } 10208 }) 10209 .collect(); 10210 10211 Self { 10212 unreachable_clauses, 10213 module, 10214 params, 10215 edits: TextEdits::new(line_numbers), 10216 clauses_to_delete: vec![], 10217 } 10218 } 10219 10220 pub fn code_actions(mut self) -> Vec<CodeAction> { 10221 self.visit_typed_module(&self.module.ast); 10222 if self.clauses_to_delete.is_empty() { 10223 return vec![]; 10224 } 10225 10226 for branch in self.clauses_to_delete { 10227 self.edits.delete(branch); 10228 } 10229 10230 let mut action = Vec::with_capacity(1); 10231 CodeActionBuilder::new("Remove unreachable clauses") 10232 .kind(CodeActionKind::QuickFix) 10233 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10234 .preferred(true) 10235 .push_to(&mut action); 10236 action 10237 } 10238} 10239 10240impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> { 10241 fn visit_typed_expr_case( 10242 &mut self, 10243 location: &'ast SrcSpan, 10244 type_: &'ast Arc<Type>, 10245 subjects: &'ast [TypedExpr], 10246 clauses: &'ast [ast::TypedClause], 10247 compiled_case: &'ast CompiledCase, 10248 ) { 10249 // We're showing the code action only if we're within one of the 10250 // unreachable patterns. And the code action is going to remove all the 10251 // unreachable patterns for this case. 10252 let is_hovering_clause = clauses.iter().any(|clause| { 10253 let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location()); 10254 within(self.params.range, pattern_range) 10255 }); 10256 10257 // If we're not hovering any of the clauses then we want to 10258 // keep visiting the case expression as the unreachable branch might be 10259 // in one of the nested cases. 10260 if !is_hovering_clause { 10261 ast::visit::visit_typed_expr_case( 10262 self, 10263 location, 10264 type_, 10265 subjects, 10266 clauses, 10267 compiled_case, 10268 ); 10269 return; 10270 } 10271 10272 for clause in clauses { 10273 let mut all_patterns_are_unreachable = true; 10274 let mut unreachable_patterns = vec![]; 10275 let mut previous_pattern_end = None; 10276 let mut all_previous_patterns_were_deleted = true; 10277 10278 for pattern in clause.patterns() { 10279 let pattern_location = multi_pattern_location(pattern); 10280 if self.unreachable_clauses.contains(&pattern_location) { 10281 // If an alternative is unreachable we want to delete 10282 // everything from the end of the previous alternative to 10283 // the start of this one. 10284 // 10285 // ```gleam 10286 // Error(_) | Error(_) | Ok(_) 10287 // // ^^^^^^^^^^^ We want to delete all of this 10288 // ``` 10289 unreachable_patterns.push( 10290 previous_pattern_end.map_or(pattern_location, |previous_end| { 10291 SrcSpan::new(previous_end, pattern_location.end) 10292 }), 10293 ); 10294 } else { 10295 // If all the previous alternatives have been deleted and 10296 // this one is reachable there's some final cleanup we need 10297 // to take care of: 10298 // 10299 // ```gleam 10300 // Ok(_) | Ok(_) | Error(_) -> todo 10301 // //^^^^^^^^^^^ All of this has been deleted, but there's 10302 // // that last vertical bar that needs to be 10303 // // taken care of! 10304 // ``` 10305 // 10306 10307 if all_previous_patterns_were_deleted && let Some(end) = previous_pattern_end { 10308 self.clauses_to_delete 10309 .push(SrcSpan::new(end, pattern_location.start)); 10310 } 10311 10312 all_previous_patterns_were_deleted = false; 10313 all_patterns_are_unreachable = false; 10314 } 10315 10316 previous_pattern_end = pattern.last().map(|pattern| pattern.location().end); 10317 } 10318 10319 if all_patterns_are_unreachable { 10320 // If all the patterns of the clause are unreachable then we 10321 // want to delete the entire branch: 10322 // 10323 // ```gleam 10324 // case a, b { 10325 // _, _ -> todo 10326 // Ok(_), Ok(_) | Error(_), Error(_) -> todo 10327 // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 10328 // // we want the entire branch to be deleted! 10329 // } 10330 // ``` 10331 self.clauses_to_delete.push(clause.location()) 10332 } else { 10333 // If only some of the variants are unreachable but not all 10334 // we want to delete just those. 10335 // case a, b { 10336 // 1, 2 | 1, 2 -> todo 10337 // // ^^^^ just this one should be deleted 10338 // } 10339 self.clauses_to_delete.extend(&unreachable_patterns); 10340 } 10341 } 10342 } 10343} 10344 10345/// Given a pattern with possibly many subjects, this returns the location 10346/// spanning the whole thing. 10347fn multi_pattern_location(alternative: &[Pattern<Arc<Type>>]) -> SrcSpan { 10348 let start = alternative 10349 .first() 10350 .map(|pattern| pattern.location().start) 10351 .unwrap_or_default(); 10352 let end = alternative 10353 .last() 10354 .map(|pattern| pattern.location().end) 10355 .unwrap_or_default(); 10356 10357 SrcSpan::new(start, end) 10358} 10359 10360/// Code action to remove a record update when all of its fields have been 10361/// provided already: 10362/// 10363/// ```gleam 10364/// pub type Wibble { Wibble(one: Int, two: Int) } 10365/// 10366/// wibble(..wibble, one:, two:) 10367/// // ^^^^^^^^ This is not needed and raises a warning! 10368/// ``` 10369/// 10370pub struct RemoveRedundantRecordUpdate<'a> { 10371 module: &'a Module, 10372 params: &'a CodeActionParams, 10373 edits: TextEdits<'a>, 10374} 10375 10376impl<'a> RemoveRedundantRecordUpdate<'a> { 10377 pub fn new( 10378 module: &'a Module, 10379 line_numbers: &'a LineNumbers, 10380 params: &'a CodeActionParams, 10381 ) -> Self { 10382 Self { 10383 module, 10384 params, 10385 edits: TextEdits::new(line_numbers), 10386 } 10387 } 10388 10389 pub fn code_actions(mut self) -> Vec<CodeAction> { 10390 let spread_to_remove = self 10391 .module 10392 .ast 10393 .type_info 10394 .warnings 10395 .iter() 10396 .find_map(|warning| { 10397 if let type_::Warning::AllFieldsRecordUpdate { 10398 location, 10399 record_location, 10400 } = warning 10401 && within( 10402 self.params.range, 10403 self.edits.src_span_to_lsp_range(*location), 10404 ) 10405 { 10406 Some(*record_location) 10407 } else { 10408 None 10409 } 10410 }); 10411 10412 let Some(spread_to_remove) = spread_to_remove else { 10413 return vec![]; 10414 }; 10415 self.edits.delete(spread_to_remove); 10416 10417 let mut action = Vec::with_capacity(1); 10418 CodeActionBuilder::new("Remove redundant record update") 10419 .kind(CodeActionKind::QuickFix) 10420 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10421 .preferred(true) 10422 .push_to(&mut action); 10423 action 10424 } 10425} 10426 10427/// Code action to add labels to a constructor/call where all the labels where 10428/// omitted. 10429/// 10430pub struct AddOmittedLabels<'a> { 10431 module: &'a Module, 10432 params: &'a CodeActionParams, 10433 edits: TextEdits<'a>, 10434 arguments_and_omitted_labels: Option<Vec<CallArgumentWithOmittedLabel>>, 10435} 10436 10437struct CallArgumentWithOmittedLabel { 10438 location: SrcSpan, 10439 10440 /// If the argument has a label this will be the label we can use for it. 10441 /// 10442 omitted_label: Option<EcoString>, 10443 10444 /// If the argument is a variable that has the same name as the omitted label 10445 /// and could use the shorthand syntax. 10446 /// 10447 can_use_shorthand_syntax: bool, 10448} 10449 10450impl<'a> AddOmittedLabels<'a> { 10451 pub fn new( 10452 module: &'a Module, 10453 line_numbers: &'a LineNumbers, 10454 params: &'a CodeActionParams, 10455 ) -> Self { 10456 Self { 10457 module, 10458 params, 10459 edits: TextEdits::new(line_numbers), 10460 arguments_and_omitted_labels: None, 10461 } 10462 } 10463 10464 pub fn code_actions(mut self) -> Vec<CodeAction> { 10465 self.visit_typed_module(&self.module.ast); 10466 10467 let Some(call_arguments) = self.arguments_and_omitted_labels else { 10468 return vec![]; 10469 }; 10470 10471 for call_argument in call_arguments { 10472 let Some(label) = call_argument.omitted_label else { 10473 continue; 10474 }; 10475 if call_argument.can_use_shorthand_syntax { 10476 self.edits.insert(call_argument.location.end, ":".into()); 10477 } else { 10478 self.edits 10479 .insert(call_argument.location.start, format!("{label}: ")) 10480 } 10481 } 10482 10483 let mut action = Vec::with_capacity(1); 10484 CodeActionBuilder::new("Add omitted labels") 10485 .kind(CodeActionKind::RefactorRewrite) 10486 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10487 .preferred(false) 10488 .push_to(&mut action); 10489 action 10490 } 10491} 10492 10493impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> { 10494 fn visit_typed_expr_call( 10495 &mut self, 10496 location: &'ast SrcSpan, 10497 type_: &'ast Arc<Type>, 10498 fun: &'ast TypedExpr, 10499 arguments: &'ast [TypedCallArg], 10500 open_parenthesis: &'ast Option<u32>, 10501 ) { 10502 let called_function_range = self.edits.src_span_to_lsp_range(fun.location()); 10503 if !within(self.params.range, called_function_range) { 10504 ast::visit::visit_typed_expr_call( 10505 self, 10506 location, 10507 type_, 10508 fun, 10509 arguments, 10510 open_parenthesis, 10511 ); 10512 return; 10513 } 10514 10515 let Some(field_map) = fun.field_map() else { 10516 ast::visit::visit_typed_expr_call( 10517 self, 10518 location, 10519 type_, 10520 fun, 10521 arguments, 10522 open_parenthesis, 10523 ); 10524 return; 10525 }; 10526 let argument_index_to_label = field_map.indices_to_labels(); 10527 10528 let mut omitted_labels = Vec::with_capacity(arguments.len()); 10529 for (index, argument) in arguments.iter().enumerate() { 10530 // If the argument already has a label we don't want to add a label 10531 // for it, so we skip it. 10532 if let Some(label) = &argument.label { 10533 // Though, before skipping, we want to make sure that the label 10534 // is actually right for the function call. If it's not then we 10535 // give up on adding labels because there wouldn't be no way of 10536 // knowing which label to add. 10537 if !field_map.fields.contains_key(label) { 10538 return; 10539 } else { 10540 continue; 10541 } 10542 } 10543 // No labels for pipes, uses, etc! 10544 if argument.is_implicit() { 10545 continue; 10546 } 10547 10548 let label = argument_index_to_label 10549 .get(&(index as u32)) 10550 .cloned() 10551 .cloned(); 10552 10553 let can_use_shorthand_syntax = match (&label, &argument.value) { 10554 (Some(label), TypedExpr::Var { name, .. }) => name == label, 10555 (Some(_) | None, _) => false, 10556 }; 10557 10558 omitted_labels.push(CallArgumentWithOmittedLabel { 10559 location: argument.location, 10560 omitted_label: label, 10561 can_use_shorthand_syntax, 10562 }) 10563 } 10564 self.arguments_and_omitted_labels = Some(omitted_labels); 10565 } 10566} 10567 10568/// Code action to extract selected code into a separate function. 10569/// If a user selected a portion of code in a function, we offer a code action 10570/// to extract it into a new one. This can either be a single expression, such 10571/// as in the following example: 10572/// 10573/// ```gleam 10574/// pub fn main() { 10575/// let value = { 10576/// // ^ User selects from here 10577/// ... 10578/// } 10579/// //^ Until here 10580/// } 10581/// ``` 10582/// 10583/// Here, we would extract the selected block expression. It could also be a 10584/// series of statements. For example: 10585/// 10586/// ```gleam 10587/// pub fn main() { 10588/// let a = 1 10589/// //^ User selects from here 10590/// let b = 2 10591/// let c = a + b 10592/// // ^ Until here 10593/// 10594/// do_more_things(c) 10595/// } 10596/// ``` 10597/// 10598/// Here, we want to extract the statements inside the user's selection. 10599/// 10600pub struct ExtractFunction<'a> { 10601 module: &'a Module, 10602 params: &'a CodeActionParams, 10603 edits: TextEdits<'a>, 10604 function: Option<ExtractedFunction<'a>>, 10605 function_end_position: Option<u32>, 10606 /// Since the `visit_typed_statement` visitor function doesn't tell us when 10607 /// a statement is the last in a block or function, we need to track that 10608 /// manually. 10609 last_statement_location: Option<SrcSpan>, 10610 /// When visiting a pipeline step, this will hold the type of the value 10611 /// returned by the previous step (if any!) 10612 previous_pipeline_assignment_type: Option<Arc<Type>>, 10613} 10614 10615/// Information about a section of code we are extracting as a function. 10616#[derive(Debug)] 10617struct ExtractedFunction<'a> { 10618 /// A list of parameters which need to be passed to the extracted function. 10619 /// These are any variables used in the extracted code, which are defined 10620 /// outside of the extracted code. 10621 parameters: Vec<(EcoString, Arc<Type>)>, 10622 /// A list of values which need to be returned from the extracted function. 10623 /// These are the variables defined in the extracted code which are used 10624 /// outside of the extracted section. 10625 returned_variables: Vec<(EcoString, Arc<Type>)>, 10626 /// The piece of code to be extracted. This is either a single expression or 10627 /// a list of statements, as explained in the documentation of `ExtractFunction` 10628 value: ExtractedValue<'a>, 10629} 10630 10631impl<'a> ExtractedFunction<'a> { 10632 fn new(value: ExtractedValue<'a>) -> Self { 10633 Self { 10634 value, 10635 parameters: Vec::new(), 10636 returned_variables: Vec::new(), 10637 } 10638 } 10639 10640 fn location(&self) -> SrcSpan { 10641 match &self.value { 10642 ExtractedValue::Expression(expression) => expression.location(), 10643 ExtractedValue::Statements { location, .. } 10644 | ExtractedValue::Use { location, .. } 10645 | ExtractedValue::PipelineSteps { location, .. } => *location, 10646 } 10647 } 10648 10649 /// If the extracted function is a series of pipeline steps, this adds to it 10650 /// the given pipeline step, otherwise leaving it unchanged. 10651 /// If the extracted function was indeed a pipeline, this will return `true`, 10652 /// otherwise it returns `false`. 10653 /// 10654 fn try_add_pipeline_step(&mut self, step_type: Arc<Type>, step_location: SrcSpan) { 10655 if let ExtractedFunction { 10656 value: 10657 ExtractedValue::PipelineSteps { 10658 location, 10659 before_first: _, 10660 return_type, 10661 }, 10662 .. 10663 } = self 10664 { 10665 // If we're extracting this pipeline and the final step is included 10666 // in the selection we want to add it to the extracted steps 10667 *return_type = step_type; 10668 *location = location.merge(&step_location); 10669 } 10670 } 10671} 10672 10673#[derive(Debug)] 10674enum ExtractedValue<'a> { 10675 Expression(&'a TypedExpr), 10676 Statements { 10677 location: SrcSpan, 10678 position: StatementPosition, 10679 /// The type of the final statement. 10680 type_: Arc<Type>, 10681 }, 10682 /// We're extracting a single use statement. We need this special case to 10683 /// properly handle the statements inside of them. 10684 Use { 10685 /// This is the location of the entire use block, including the 10686 /// statements in it. 10687 location: SrcSpan, 10688 /// This is the location of the expression on the right hand side of the use 10689 /// arrow. 10690 /// 10691 /// ```gleam 10692 /// use a <- result.try(result) 10693 /// ^^^^^^^^^^^^^^^^^^ 10694 /// ``` 10695 /// 10696 use_line_location: SrcSpan, 10697 type_: Arc<Type>, 10698 }, 10699 PipelineSteps { 10700 location: SrcSpan, 10701 /// The type of the value produced by the pipeline steps that will be 10702 /// piped into the extracted function. Could be none if the steps we're 10703 /// extracting include the first step, in that case there would be 10704 /// nothing that is fed into it. 10705 before_first: Option<Arc<Type>>, 10706 /// The type returned by the extracted steps. 10707 return_type: Arc<Type>, 10708 }, 10709} 10710 10711impl ExtractedValue<'_> { 10712 fn location(&self) -> SrcSpan { 10713 match self { 10714 ExtractedValue::Expression(typed_expr) => typed_expr.location(), 10715 ExtractedValue::Statements { location, .. } 10716 | ExtractedValue::PipelineSteps { location, .. } 10717 | ExtractedValue::Use { location, .. } => *location, 10718 } 10719 } 10720} 10721 10722#[derive(Debug)] 10723enum StatementPosition { 10724 Tail, 10725 NotTail, 10726} 10727 10728impl<'a> ExtractFunction<'a> { 10729 pub fn new( 10730 module: &'a Module, 10731 line_numbers: &'a LineNumbers, 10732 params: &'a CodeActionParams, 10733 ) -> Self { 10734 Self { 10735 module, 10736 params, 10737 edits: TextEdits::new(line_numbers), 10738 function: None, 10739 function_end_position: None, 10740 last_statement_location: None, 10741 previous_pipeline_assignment_type: None, 10742 } 10743 } 10744 10745 pub fn code_actions(mut self) -> Vec<CodeAction> { 10746 // If no code is selected, then there is no function to extract and we 10747 // can return no code actions. 10748 if self.params.range.start == self.params.range.end { 10749 return Vec::new(); 10750 } 10751 10752 self.visit_typed_module(&self.module.ast); 10753 10754 let Some(end) = self.function_end_position else { 10755 return Vec::new(); 10756 }; 10757 10758 // If nothing was found in the selected range, there is no code action. 10759 let Some(extracted) = self.function.take() else { 10760 return Vec::new(); 10761 }; 10762 10763 match extracted.value { 10764 // If we extract a block, it isn't very helpful to have the body of 10765 // the extracted function just be a single block expression, so 10766 // instead we extract the statements inside the block. For example, 10767 // the following code: 10768 // 10769 // ```gleam 10770 // pub fn main() { 10771 // let x = { 10772 // // ^ Select from here 10773 // let a = 1 10774 // let b = 2 10775 // a + b 10776 // } 10777 // //^ Until here 10778 // x 10779 // } 10780 // ``` 10781 // 10782 // Would produce the following extracted function: 10783 // 10784 // ```gleam 10785 // fn function() { 10786 // let a = 1 10787 // let b = 2 10788 // a + b 10789 // } 10790 // ``` 10791 // 10792 // Rather than: 10793 // 10794 // ```gleam 10795 // fn function() { 10796 // { 10797 // let a = 1 10798 // let b = 2 10799 // a + b 10800 // } 10801 // } 10802 // ``` 10803 // 10804 ExtractedValue::Expression(TypedExpr::Block { 10805 statements, 10806 location: full_location, 10807 }) => { 10808 let location = statements 10809 .first() 10810 .location() 10811 .merge(&statements.last().location()); 10812 10813 self.extract_unbound_statements( 10814 *full_location, 10815 location, 10816 extracted.parameters, 10817 statements.last().type_(), 10818 end, 10819 ) 10820 } 10821 ExtractedValue::Expression(TypedExpr::Fn { 10822 type_, 10823 location: full_location, 10824 kind: FunctionLiteralKind::Anonymous { .. }, 10825 arguments, 10826 body, 10827 .. 10828 }) => { 10829 let location = body.first().location().merge(&body.last().location()); 10830 let return_type = type_.return_type().expect("Fn should have a return type"); 10831 10832 if extracted.parameters.is_empty() { 10833 self.extract_anonymous_function( 10834 *full_location, 10835 location, 10836 arguments, 10837 return_type, 10838 end, 10839 ) 10840 } else if arguments.len() == 1 { 10841 self.extract_anonymous_function_with_capture_hole( 10842 *full_location, 10843 location, 10844 arguments.first().expect("There is exactly one argument"), 10845 extracted.parameters, 10846 return_type, 10847 end, 10848 ) 10849 } else { 10850 self.extract_anonymous_function_body( 10851 location, 10852 arguments, 10853 extracted.parameters, 10854 return_type, 10855 end, 10856 ) 10857 } 10858 } 10859 ExtractedValue::Expression(expression) => { 10860 let expression_type = if let TypedExpr::Fn { 10861 type_, 10862 kind: FunctionLiteralKind::Use { .. }, 10863 .. 10864 } = expression 10865 { 10866 type_.fn_types().expect("use callback to be a function").1 10867 } else { 10868 expression.type_() 10869 }; 10870 self.extract_unbound_statements( 10871 expression.location(), 10872 expression.location(), 10873 extracted.parameters, 10874 expression_type, 10875 end, 10876 ) 10877 } 10878 ExtractedValue::Statements { 10879 location, 10880 position: StatementPosition::NotTail, 10881 type_, 10882 } => { 10883 if extracted.returned_variables.is_empty() { 10884 self.extract_unbound_statements( 10885 location, 10886 location, 10887 extracted.parameters, 10888 type_, 10889 end, 10890 ); 10891 } else { 10892 self.extract_bound_statements( 10893 location, 10894 extracted.parameters, 10895 extracted.returned_variables, 10896 end, 10897 ) 10898 }; 10899 } 10900 10901 ExtractedValue::Use { 10902 location, 10903 use_line_location: _, 10904 type_, 10905 } 10906 | ExtractedValue::Statements { 10907 location, 10908 position: StatementPosition::Tail, 10909 type_, 10910 } => self.extract_unbound_statements( 10911 location, 10912 location, 10913 extracted.parameters, 10914 type_, 10915 end, 10916 ), 10917 ExtractedValue::PipelineSteps { 10918 location, 10919 before_first, 10920 return_type, 10921 } => self.extract_pipeline_steps( 10922 location, 10923 end, 10924 extracted.parameters, 10925 before_first, 10926 return_type, 10927 ), 10928 } 10929 10930 let mut action = Vec::with_capacity(1); 10931 CodeActionBuilder::new("Extract function") 10932 .kind(CodeActionKind::RefactorExtract) 10933 .changes(self.params.text_document.uri.clone(), self.edits.edits) 10934 .preferred(false) 10935 .push_to(&mut action); 10936 action 10937 } 10938 10939 /// Choose a suitable name for an extracted function to make sure it doesn't 10940 /// clash with existing functions defined in the module and cause an error. 10941 fn function_name(&self) -> EcoString { 10942 if !self.module.ast.type_info.values.contains_key("function") { 10943 return "function".into(); 10944 } 10945 10946 let mut number = 2; 10947 loop { 10948 let name = eco_format!("function_{number}"); 10949 if !self.module.ast.type_info.values.contains_key(&name) { 10950 return name; 10951 } 10952 number += 1; 10953 } 10954 } 10955 10956 /// For anonymous functions that do not capture any variables from an outer scope. 10957 /// Moves the function so it is defined at the module top-level instead, 10958 /// replacing the original literal with a reference to the new function. 10959 fn extract_anonymous_function( 10960 &mut self, 10961 location: SrcSpan, 10962 code_location: SrcSpan, 10963 arguments: &[TypedArg], 10964 return_type: Arc<Type>, 10965 function_end: u32, 10966 ) { 10967 // --- BEFORE 10968 // ```gleam 10969 // pub fn main() { 10970 // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) }) 10971 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 10972 // } 10973 // ``` 10974 // 10975 // --- AFTER 10976 // ```gleam 10977 // pub fn main() { 10978 // list.each([1, 2, 3], function) 10979 // } 10980 // 10981 // fn function(x: Int) -> Nil { 10982 // io.println(int.to_string(x)) 10983 // } 10984 // ``` 10985 10986 let name = self.function_name(); 10987 self.edits.replace(location, name.to_string()); 10988 10989 let mut printer = Printer::new(&self.module.ast.names); 10990 10991 let return_type = printer.print_type(&return_type); 10992 let function_body = code_at(self.module, code_location); 10993 let mut name_generator = NameGenerator::new(); 10994 let arguments = arguments 10995 .iter() 10996 .map(|arg| { 10997 if let Some(name) = arg.get_variable_name() { 10998 eco_format!("{name}: {}", printer.print_type(&arg.type_)) 10999 } else { 11000 let name = name_generator.generate_name_from_type(&arg.type_); 11001 eco_format!("_{name}: {}", printer.print_type(&arg.type_)) 11002 } 11003 }) 11004 .join(", "); 11005 11006 let function = format!( 11007 "\n\nfn {name}({arguments}) -> {return_type} {{ 11008 {function_body} 11009}}" 11010 ); 11011 self.edits.insert(function_end, function); 11012 } 11013 11014 /// For anonymous functions that capture variables from an external scope 11015 /// but only expect a single argument. 11016 /// Uses function caputre syntax to provide a more concise refactoring than 11017 /// `extract_anonymous_function_body`. 11018 fn extract_anonymous_function_with_capture_hole( 11019 &mut self, 11020 location: SrcSpan, 11021 code_location: SrcSpan, 11022 argument: &TypedArg, 11023 extra_parameters: Vec<(EcoString, Arc<Type>)>, 11024 return_type: Arc<Type>, 11025 function_end: u32, 11026 ) { 11027 let name = self.function_name(); 11028 11029 // --- BEFORE 11030 // ```gleam 11031 // pub fn main() { 11032 // let needle = 42 11033 // let haystack = [25, 81, 74, 42, 33] 11034 // list.filter(haystack, fn(x) { x == needle }) 11035 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 11036 // } 11037 // ``` 11038 // 11039 // --- AFTER 11040 // ```gleam 11041 // pub fn main() { 11042 // let needle = 42 11043 // let haystack = [25, 81, 74, 42, 33] 11044 // list.filter(haystack, function(_, needle)) 11045 // } 11046 // 11047 // fn function(x: Int, needle: Int) -> Bool { 11048 // x == needle 11049 // } 11050 // ``` 11051 11052 let call = format!( 11053 "{name}(_, {})", 11054 extra_parameters.iter().map(|(name, _)| name).join(", ") 11055 ); 11056 self.edits.replace(location, call); 11057 11058 let mut printer = Printer::new(&self.module.ast.names); 11059 11060 // build up the code for the newly generated function 11061 let return_type = printer.print_type(&return_type); 11062 let function_body = code_at(self.module, code_location); 11063 let argument = if let Some(name) = argument.get_variable_name() { 11064 eco_format!("{name}: {}", printer.print_type(&argument.type_)) 11065 } else { 11066 let name = NameGenerator::new().generate_name_from_type(&argument.type_); 11067 eco_format!("_{name}: {}", printer.print_type(&argument.type_)) 11068 }; 11069 let extra_parameters = extra_parameters 11070 .iter() 11071 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11072 .join(", "); 11073 11074 let function = format!( 11075 "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{ 11076 {function_body} 11077}}" 11078 ); 11079 self.edits.insert(function_end, function); 11080 } 11081 11082 /// For non-unary anonymous functions that capture variables from an external scope. 11083 /// Replaces just the _function body_ with a call to the newly generated function. 11084 fn extract_anonymous_function_body( 11085 &mut self, 11086 location: SrcSpan, 11087 arguments: &[TypedArg], 11088 extra_parameters: Vec<(EcoString, Arc<Type>)>, 11089 return_type: Arc<Type>, 11090 function_end: u32, 11091 ) { 11092 let name = self.function_name(); 11093 // --- BEFORE 11094 // ```gleam 11095 // pub fn main() { 11096 // let factor = 2 11097 // list.fold([], 0, fn(acc, value) { acc + value * factor }) 11098 // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ 11099 // } 11100 // ``` 11101 // 11102 // --- AFTER 11103 // ```gleam 11104 // pub fn main() { 11105 // let factor = 2 11106 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) }) 11107 // } 11108 // 11109 // fn function(acc: Int, value: Int, factor: Int) -> Int { 11110 // acc + value * factor 11111 // } 11112 // ``` 11113 11114 // if the programmer has ignored an argument, the generated function 11115 // cannot take it as an parameter 11116 let arguments = arguments 11117 .iter() 11118 .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_))) 11119 .chain(extra_parameters.iter().map(|(name, type_)| (name, type_))) 11120 .collect::<Vec<(&EcoString, &Arc<Type>)>>(); 11121 11122 let call = format!( 11123 "{name}({})", 11124 arguments.iter().map(|(name, _)| name).join(", ") 11125 ); 11126 self.edits.replace(location, call); 11127 11128 let mut printer = Printer::new(&self.module.ast.names); 11129 11130 let return_type = printer.print_type(&return_type); 11131 let function_body = code_at(self.module, location); 11132 let arguments = arguments 11133 .iter() 11134 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11135 .join(", "); 11136 11137 let function = format!( 11138 "\n\nfn {name}({arguments}) -> {return_type} {{ 11139 {function_body} 11140}}" 11141 ); 11142 self.edits.insert(function_end, function); 11143 } 11144 11145 /// Extracts a function whose return value does not get bound to a variable 11146 /// in the calling function. 11147 /// 11148 /// There are two cases: 11149 /// 1. The function is in tail position, and its return value is used as the 11150 /// return value of the calling function. 11151 /// 2. The function's return value is unused in the calling function, and so 11152 /// does not get bound to a variable. 11153 /// 11154 /// # Parameters 11155 /// 11156 /// In most cases, `location` and `code_location` are the same. They differ 11157 /// only when the code being extracted is a single block expression. In 11158 /// that case, the code being replaced in the calling function (`location`) 11159 /// includes the braces around the block, while the code being put into the 11160 /// body of the extracted function (`code_location`) does not. 11161 fn extract_unbound_statements( 11162 &mut self, 11163 location: SrcSpan, 11164 code_location: SrcSpan, 11165 parameters: Vec<(EcoString, Arc<Type>)>, 11166 return_type: Arc<Type>, 11167 function_end: u32, 11168 ) { 11169 let expression_code = code_at(self.module, code_location); 11170 11171 let name = self.function_name(); 11172 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 11173 let call = format!("{name}({arguments})"); 11174 11175 // Since we are only extracting a single expression, we can just replace 11176 // it with the call and preserve all other semantics; only one value can 11177 // be returned from the expression, unlike when extracting multiple 11178 // statements. 11179 self.edits.replace(location, call); 11180 11181 let mut printer = Printer::new(&self.module.ast.names); 11182 11183 let parameters = parameters 11184 .iter() 11185 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11186 .join(", "); 11187 let return_type = printer.print_type(&return_type); 11188 11189 let function = format!( 11190 "\n\nfn {name}({parameters}) -> {return_type} {{ 11191 {expression_code} 11192}}" 11193 ); 11194 11195 self.edits.insert(function_end, function); 11196 } 11197 11198 /// Extracts a function that does get bound to one or more variables in the 11199 /// calling function. 11200 /// 11201 /// For example, in the code 11202 /// 11203 /// ```gleam 11204 /// pub fn main() { 11205 /// let a = 10 11206 /// //^ Select from here 11207 /// let b = 20 11208 /// let c = a + b 11209 /// // ^ Until here 11210 /// 11211 /// echo a 11212 /// echo b 11213 /// echo c 11214 /// } 11215 /// ``` 11216 /// 11217 /// multiple values defined in the extracted block are used in the calling 11218 /// function. So, the extracted function must return multiple values that 11219 /// are bound to variables: `let #(a, b, c) = function()`. 11220 /// 11221 /// There is a special case when there is only one value being bound, as it 11222 /// is unnecessary to create a single-element tuple. For example: 11223 /// 11224 /// ```gleam 11225 /// pub fn main() { 11226 /// let a = 10 11227 /// //^ Select from here 11228 /// let b = 20 11229 /// let c = a + b 11230 /// // ^ Until here 11231 /// 11232 /// echo c 11233 /// } 11234 /// ``` 11235 /// 11236 /// Here, the return value of the extracted function will be bound to a 11237 /// single variable in the calling function: `let c = function()`. 11238 fn extract_bound_statements( 11239 &mut self, 11240 location: SrcSpan, 11241 parameters: Vec<(EcoString, Arc<Type>)>, 11242 returned_variables: Vec<(EcoString, Arc<Type>)>, 11243 function_end: u32, 11244 ) { 11245 let code = code_at(self.module, location); 11246 11247 let (return_type, return_value) = match returned_variables.as_slice() { 11248 [(returned_name, type_)] => (type_.clone(), returned_name.clone()), 11249 _ => { 11250 let returned_names = returned_variables 11251 .iter() 11252 .map(|(name, _type)| name) 11253 .join(", "); 11254 let return_value = eco_format!("#({returned_names})"); 11255 11256 let returned_types = returned_variables 11257 .into_iter() 11258 .map(|(_name, type_)| type_) 11259 .collect(); 11260 let type_ = type_::tuple(returned_types); 11261 11262 (type_, return_value) 11263 } 11264 }; 11265 11266 let name = self.function_name(); 11267 let arguments = parameters.iter().map(|(name, _)| name).join(", "); 11268 let call = format!("let {return_value} = {name}({arguments})"); 11269 11270 self.edits.replace(location, call); 11271 11272 let mut printer = Printer::new(&self.module.ast.names); 11273 11274 let parameters = parameters 11275 .iter() 11276 .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) 11277 .join(", "); 11278 let return_type = printer.print_type(&return_type); 11279 11280 let function = format!( 11281 "\n\nfn {name}({parameters}) -> {return_type} {{ 11282 {code} 11283 {return_value} 11284}}" 11285 ); 11286 11287 self.edits.insert(function_end, function); 11288 } 11289 11290 fn extract_pipeline_steps( 11291 &mut self, 11292 location: SrcSpan, 11293 function_end: u32, 11294 parameters: Vec<(EcoString, Arc<Type>)>, 11295 before_first: Option<Arc<Type>>, 11296 return_type: Arc<Type>, 11297 ) { 11298 let name = self.function_name(); 11299 let code = code_at(self.module, location); 11300 let arguments = parameters.iter().map(|(name, _)| name.clone()).join(", "); 11301 let replacement = match before_first { 11302 Some(_) if parameters.is_empty() => format!("{name}"), 11303 Some(_) | None => format!("{name}({arguments})"), 11304 }; 11305 self.edits.replace(location, replacement); 11306 11307 // When extracting something out of the middle of a pipeline the 11308 // function we produce will produce a single value as output but could 11309 // take multiple values as input: 11310 // 11311 // ```gleam 11312 // wibble 11313 // |> wobble(a) // extracting this 11314 // |> woo(b) // 11315 // |> something 11316 // ``` 11317 // 11318 // It will take the type returned by `wibble`, `a`, and `b` as input, 11319 // and produce the value returned by `woo` as output: 11320 // 11321 // ```gleam 11322 // wibble 11323 // |> function(a, b) 11324 // |> something 11325 // ``` 11326 // 11327 // If the steps extracted are at the beginning of the pipeline, then it 11328 // won't take that additional argument! 11329 // 11330 // ```gleam 11331 // wibble // extracting these 11332 // |> wobble(a) // 11333 // |> woo(b) // 11334 // |> something 11335 // ``` 11336 // 11337 // Becomes: 11338 // 11339 // ```gleam 11340 // function(a, b) 11341 // |> something 11342 // ``` 11343 11344 let mut type_printer = Printer::new(&self.module.ast.names); 11345 let return_type = type_printer.print_type(&return_type); 11346 let first_argument = before_first.map(|type_of_first_argument| { 11347 let mut generator = NameGenerator::new(); 11348 for (name, _) in parameters.iter() { 11349 generator.add_used_name(name.clone()); 11350 } 11351 let first_argument_name = generator.generate_name_from_type(&type_of_first_argument); 11352 (first_argument_name, type_of_first_argument.clone()) 11353 }); 11354 let parameters = first_argument 11355 .clone() 11356 .into_iter() 11357 .chain(parameters) 11358 .map(|(name, type_)| eco_format!("{name}: {}", type_printer.print_type(&type_))) 11359 .join(", "); 11360 11361 let code = if let Some((first_argument_name, _)) = first_argument { 11362 format!("{first_argument_name}\n |> {code}") 11363 } else { 11364 code.trim_start_matches("|>").to_string() 11365 }; 11366 let function = format!( 11367 "\n\nfn {name}({parameters}) -> {return_type} {{ 11368 {code} 11369}}" 11370 ); 11371 11372 self.edits.insert(function_end, function); 11373 } 11374 11375 /// When a variable is referenced, we need to decide if we need to do anything 11376 /// to ensure that the reference is still valid after extracting a function. 11377 /// If the variable is defined outside the extracted function, but used inside 11378 /// it, then we need to add it as a parameter of the function. Similarly, if 11379 /// a variable is defined inside the extracted code, but used outside of it, 11380 /// we need to ensure that value is returned from the function so that it is 11381 /// accessible. 11382 fn register_referenced_variable( 11383 &mut self, 11384 name: &EcoString, 11385 type_: &Arc<Type>, 11386 location: SrcSpan, 11387 definition_location: SrcSpan, 11388 ) { 11389 let Some(extracted) = &mut self.function else { 11390 return; 11391 }; 11392 11393 let extracted_location = extracted.location(); 11394 11395 // If a variable defined outside the extracted code is referenced inside 11396 // it, we need to add it to the list of parameters. 11397 let variables = if extracted_location.contains_span(location) 11398 && !extracted_location.contains_span(definition_location) 11399 { 11400 &mut extracted.parameters 11401 // If a variable defined inside the extracted code is referenced outside 11402 // it, then we need to ensure that it is returned from the function. 11403 } else if extracted_location.contains_span(definition_location) 11404 && !extracted_location.contains_span(location) 11405 { 11406 &mut extracted.returned_variables 11407 } else { 11408 return; 11409 }; 11410 11411 // If the variable has already been tracked, no need to register it again. 11412 // We use a `Vec` here rather than a `HashMap` because we want to ensure 11413 // the order of arguments is consistent; in this case it will be determined 11414 // by the order the variables are used. This isn't always desired, but it's 11415 // better than random order, and makes it easier to write tests too. 11416 // The cost of iterating the list here is minimal; it is unlikely that 11417 // a given function will ever have more than 10 or so parameters. 11418 if variables.iter().any(|(variable, _)| variable == name) { 11419 return; 11420 } 11421 11422 variables.push((name.clone(), type_.clone())); 11423 } 11424 11425 fn can_extract_expression(&self, expression: &TypedExpr) -> bool { 11426 let expression_range = self.edits.src_span_to_lsp_range(expression.location()); 11427 let selected_range = self.params.range; 11428 11429 // If the selected range doesn't touch the expression at all, then there 11430 // is no reason to extract it. 11431 if !overlaps(expression_range, selected_range) { 11432 return false; 11433 } 11434 11435 match expression { 11436 TypedExpr::Pipeline { 11437 first_value, 11438 finally, 11439 .. 11440 } => { 11441 // We can extract a pipeline as a whole only if the selection 11442 // spans all of its steps! 11443 let first_step = self.edits.src_span_to_lsp_range(first_value.location); 11444 let last_step = self.edits.src_span_to_lsp_range(finally.location()); 11445 position_within(selected_range.start, first_step) 11446 && position_within(selected_range.end, last_step) 11447 } 11448 11449 TypedExpr::Int { .. } 11450 | TypedExpr::Float { .. } 11451 | TypedExpr::String { .. } 11452 | TypedExpr::Block { .. } 11453 | TypedExpr::Var { .. } 11454 | TypedExpr::Fn { .. } 11455 | TypedExpr::List { .. } 11456 | TypedExpr::Call { .. } 11457 | TypedExpr::BinOp { .. } 11458 | TypedExpr::Case { .. } 11459 | TypedExpr::RecordAccess { .. } 11460 | TypedExpr::PositionalAccess { .. } 11461 | TypedExpr::ModuleSelect { .. } 11462 | TypedExpr::Tuple { .. } 11463 | TypedExpr::TupleIndex { .. } 11464 | TypedExpr::Todo { .. } 11465 | TypedExpr::Panic { .. } 11466 | TypedExpr::Echo { .. } 11467 | TypedExpr::BitArray { .. } 11468 | TypedExpr::RecordUpdate { .. } 11469 | TypedExpr::NegateBool { .. } 11470 | TypedExpr::NegateInt { .. } 11471 | TypedExpr::Invalid { .. } => !completely_within(selected_range, expression_range), 11472 } 11473 } 11474 11475 fn can_extract_statement(&self, statement: &TypedStatement) -> bool { 11476 let statement_range = self.edits.src_span_to_lsp_range(statement.location()); 11477 let selected_range = self.params.range; 11478 11479 // If the selected range doesn't touch the statement at all, then there 11480 // is no reason to extract it. 11481 if !overlaps(statement_range, selected_range) { 11482 return false; 11483 } 11484 11485 match statement { 11486 ast::Statement::Expression(expression) => self.can_extract_expression(expression), 11487 11488 // Determine whether the selected range falls completely within the 11489 // expression. For example: 11490 // ```gleam 11491 // pub fn main() { 11492 // let something = { 11493 // let a = 1 11494 // let b = 2 11495 // let c = a + b 11496 // //^ The user has selected from here 11497 // let d = a * b 11498 // c / d 11499 // // ^ Until here 11500 // } 11501 // } 11502 // ``` 11503 // 11504 // Here, the selected range does overlap with the `let something` 11505 // statement; but we don't want to extract that whole statement! The 11506 // user only wanted to extract the statements inside the block. So if 11507 // the selected range falls completely within the expression, we ignore 11508 // it and traverse the tree further until we find exactly what the user 11509 // selected. 11510 // 11511 // If the selected range is completely within the expression, we don't 11512 // want to extract it. 11513 ast::Statement::Use(_) | ast::Statement::Assert(_) => { 11514 !completely_within(selected_range, statement_range) 11515 } 11516 11517 // We can only extract a whole let statement if the assignment 11518 // part itself is selected. If the only part being selected is the 11519 // expression then the right call is not extracting the whole 11520 // statement but just the expression. 11521 ast::Statement::Assignment(assignment) => { 11522 let value_range = self 11523 .edits 11524 .src_span_to_lsp_range(assignment.value.location()); 11525 11526 !within(selected_range, value_range) 11527 && !completely_within(selected_range, statement_range) 11528 } 11529 } 11530 } 11531} 11532 11533impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> { 11534 fn visit_typed_function(&mut self, function: &'ast TypedFunction) { 11535 let range = self.edits.src_span_to_lsp_range(function.full_location()); 11536 11537 if within(self.params.range, range) { 11538 self.function_end_position = Some(function.end_position); 11539 self.last_statement_location = function.body.last().map(|last| last.location()); 11540 11541 ast::visit::visit_typed_function(self, function); 11542 } 11543 } 11544 11545 fn visit_typed_expr_block( 11546 &mut self, 11547 location: &'ast SrcSpan, 11548 statements: &'ast [TypedStatement], 11549 ) { 11550 let last_statement_location = self.last_statement_location; 11551 self.last_statement_location = statements.last().map(|last| last.location()); 11552 11553 ast::visit::visit_typed_expr_block(self, location, statements); 11554 11555 self.last_statement_location = last_statement_location; 11556 } 11557 11558 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 11559 let last_statement_location = self.last_statement_location; 11560 // The body must be visited first, before the desugared function 11561 if let TypedExpr::Call { arguments, .. } = &*use_.call 11562 && let Some(CallArg { 11563 value: TypedExpr::Fn { body, .. }, 11564 .. 11565 }) = arguments.last() 11566 { 11567 self.last_statement_location = Some(body.last().location()); 11568 for statement in body { 11569 self.visit_typed_statement(statement); 11570 } 11571 } 11572 ast::visit::visit_typed_use(self, use_); 11573 self.last_statement_location = last_statement_location; 11574 } 11575 11576 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 11577 // If we have already determined what code we want to extract, we don't 11578 // want to extract this instead. This expression would be inside the 11579 // piece of code we already are going to extract, leading to us 11580 // extracting just a single literal in any selection, which is of course 11581 // not desired. 11582 if self.function.is_none() { 11583 // If this expression is fully selected, we mark it as being extracted. 11584 if self.can_extract_expression(expression) { 11585 self.function = Some(ExtractedFunction::new(ExtractedValue::Expression( 11586 expression, 11587 ))); 11588 } 11589 } 11590 11591 // If the expression is a function, then the last statement location 11592 // needs to be updated accordingly 11593 let last_statement_location = self.last_statement_location; 11594 if let TypedExpr::Fn { body, .. } = expression { 11595 self.last_statement_location = Some(body.last().location()); 11596 } 11597 11598 ast::visit::visit_typed_expr(self, expression); 11599 11600 self.last_statement_location = last_statement_location; 11601 } 11602 11603 fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 11604 let statement_location = statement.location(); 11605 11606 if self.can_extract_statement(statement) { 11607 let is_in_tail_position = 11608 self.last_statement_location 11609 .is_some_and(|last_statement_location| { 11610 last_statement_location == statement_location 11611 }); 11612 11613 // A use is always eating up the entire block, if we're extracting it, 11614 // it will be in tail position there and the extracted function should 11615 // return its returned value. 11616 let position = if statement.is_use() || is_in_tail_position { 11617 StatementPosition::Tail 11618 } else { 11619 StatementPosition::NotTail 11620 }; 11621 11622 match &mut self.function { 11623 None => { 11624 self.function = match statement { 11625 TypedStatement::Expression(TypedExpr::Pipeline { .. }) => None, 11626 TypedStatement::Assert(_) 11627 | TypedStatement::Assignment(_) 11628 | TypedStatement::Expression(_) => { 11629 Some(ExtractedFunction::new(ExtractedValue::Statements { 11630 location: statement_location, 11631 position, 11632 type_: statement.type_(), 11633 })) 11634 } 11635 TypedStatement::Use(use_) => { 11636 Some(ExtractedFunction::new(ExtractedValue::Use { 11637 location: use_.call.location(), 11638 use_line_location: use_.location, 11639 type_: statement.type_(), 11640 })) 11641 } 11642 }; 11643 } 11644 11645 // If we're extracting something that is within a use expression 11646 // we need to check whether to extract the whole use or just 11647 // statements inside of it 11648 Some(ExtractedFunction { 11649 value: 11650 ExtractedValue::Use { 11651 location, 11652 use_line_location, 11653 .. 11654 }, 11655 parameters, 11656 returned_variables, 11657 }) if location.contains_span(statement_location) => { 11658 let use_line_range = self.edits.src_span_to_lsp_range(*use_line_location); 11659 11660 // If the current statement is not a part of the use line, 11661 // and the current selection does not overlap the use line, 11662 // then the outer use is not the correct target. 11663 if !use_line_location.contains_span(statement_location) 11664 && !overlaps(use_line_range, self.params.range) 11665 { 11666 self.function = Some(ExtractedFunction { 11667 value: ExtractedValue::Statements { 11668 location: statement_location, 11669 position, 11670 type_: statement.type_(), 11671 }, 11672 parameters: parameters.to_vec(), 11673 returned_variables: returned_variables.to_vec(), 11674 }); 11675 } 11676 } 11677 // Otherwise it means we're extracting multiple statements 11678 // _including_ some use expression, we fallback to extracting 11679 // multiple statements 11680 Some(ExtractedFunction { 11681 value: value @ ExtractedValue::Use { .. }, 11682 .. 11683 }) => { 11684 *value = ExtractedValue::Statements { 11685 location: value.location().merge(&statement_location), 11686 position, 11687 type_: statement.type_(), 11688 }; 11689 } 11690 11691 // If we have already chosen an expression to extract, that means 11692 // that this statement is within the already extracted expression, 11693 // so we don't want to extract this instead. 11694 Some(ExtractedFunction { 11695 value: ExtractedValue::Expression(_), 11696 .. 11697 }) => {} 11698 // If we are selecting multiple statements, this statement should 11699 // be included within that list, so we merge the spans to ensure 11700 // it is included. 11701 Some(ExtractedFunction { 11702 value: 11703 ExtractedValue::Statements { 11704 location, 11705 position: extracted_position, 11706 type_: extracted_type, 11707 }, 11708 .. 11709 }) => { 11710 *location = location.merge(&statement_location); 11711 *extracted_position = position; 11712 *extracted_type = statement.type_(); 11713 } 11714 Some(ExtractedFunction { 11715 value: value @ ExtractedValue::PipelineSteps { .. }, 11716 .. 11717 }) => { 11718 // If we were extracting a pipeline, but end up selecting 11719 // some statement that is not part of it, then we go back to 11720 // selecting a batch of statements. 11721 *value = ExtractedValue::Statements { 11722 location: value.location(), 11723 position, 11724 type_: statement.type_(), 11725 } 11726 } 11727 } 11728 } 11729 ast::visit::visit_typed_statement(self, statement); 11730 } 11731 11732 fn visit_typed_expr_pipeline( 11733 &mut self, 11734 _location: &'ast SrcSpan, 11735 first_value: &'ast TypedPipelineAssignment, 11736 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 11737 finally: &'ast TypedExpr, 11738 _finally_kind: &'ast PipelineAssignmentKind, 11739 ) { 11740 self.previous_pipeline_assignment_type = None; 11741 self.visit_typed_pipeline_assignment(first_value); 11742 11743 self.previous_pipeline_assignment_type = Some(first_value.type_()); 11744 for (assignment, _kind) in assignments { 11745 self.visit_typed_pipeline_assignment(assignment); 11746 self.previous_pipeline_assignment_type = Some(assignment.type_()); 11747 } 11748 11749 // If we're selecting a pipeline and the selection ends on its final step 11750 // we want to include that as well into the extracted bit. 11751 let final_step_range = self.edits.src_span_to_lsp_range(finally.location()); 11752 if let Some(extracted_function) = &mut self.function 11753 && position_within(self.params.range.end, final_step_range) 11754 { 11755 extracted_function.try_add_pipeline_step(finally.type_(), finally.location()); 11756 }; 11757 11758 self.visit_typed_expr(finally); 11759 self.previous_pipeline_assignment_type = None; 11760 } 11761 11762 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 11763 // In order to be extracted, a pipeline step must be overlapping with 11764 // the cursor selection! 11765 let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); 11766 if !overlaps(self.params.range, assignment_range) { 11767 return; 11768 } 11769 11770 match &mut self.function { 11771 None => { 11772 self.function = Some(ExtractedFunction::new(ExtractedValue::PipelineSteps { 11773 location: assignment.location, 11774 before_first: self.previous_pipeline_assignment_type.clone(), 11775 return_type: assignment.type_(), 11776 })); 11777 } 11778 Some(extracted_function) => { 11779 extracted_function.try_add_pipeline_step(assignment.type_(), assignment.location) 11780 } 11781 } 11782 ast::visit::visit_typed_pipeline_assignment(self, assignment); 11783 } 11784 11785 fn visit_typed_expr_var( 11786 &mut self, 11787 location: &'ast SrcSpan, 11788 constructor: &'ast ValueConstructor, 11789 name: &'ast EcoString, 11790 ) { 11791 if let type_::ValueConstructorVariant::LocalVariable { 11792 location: definition_location, 11793 .. 11794 } = &constructor.variant 11795 { 11796 self.register_referenced_variable( 11797 name, 11798 &constructor.type_, 11799 *location, 11800 *definition_location, 11801 ); 11802 } 11803 } 11804 11805 fn visit_typed_clause_guard_var( 11806 &mut self, 11807 location: &'ast SrcSpan, 11808 name: &'ast EcoString, 11809 type_: &'ast Arc<Type>, 11810 definition_location: &'ast SrcSpan, 11811 _origin: &'ast VariableOrigin, 11812 ) { 11813 self.register_referenced_variable(name, type_, *location, *definition_location); 11814 } 11815 11816 fn visit_typed_expr_case( 11817 &mut self, 11818 location: &'ast SrcSpan, 11819 type_: &'ast Arc<Type>, 11820 subjects: &'ast [TypedExpr], 11821 clauses: &'ast [ast::TypedClause], 11822 compiled_case: &'ast CompiledCase, 11823 ) { 11824 let was_extracting_already = self.function.is_some(); 11825 11826 // We first visit as usual... 11827 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 11828 11829 // But then we need to check we're in a situation where it actually makes 11830 // sense to extract: if the cursor is entirely within the case (so it's 11831 // not part of a bigger extracted chunk) and it spans over one of the 11832 // branches' pattern or guard, then we don't want to allow extracting 11833 // anything. Popping up the action would be confusing. 11834 // For example: 11835 // 11836 // ```gleam 11837 // case wibble { 11838 // Ok(_) -> todo 11839 // //^^^^^^^^^^^^^ If I'm selecting this whole branch it makes no sense 11840 // to propose extracting it as a function 11841 // _ if wibble -> todo 11842 // // ^^^^^^^^^^^ If I'm selecting this it makes no sense to 11843 // // propose extracting it as a function 11844 // } 11845 // ``` 11846 11847 // We were already extracting something, we don't want to render that 11848 // choice null, this is just a part of a bigger piece being extracted. 11849 if was_extracting_already { 11850 return; 11851 } 11852 11853 for clause in clauses { 11854 let left_hand_side_location = SrcSpan { 11855 start: clause.pattern_location().start, 11856 end: clause.then.location().start - 1, 11857 }; 11858 let left_hand_side_range = self.edits.src_span_to_lsp_range(left_hand_side_location); 11859 // If there's any overlapping with one of the patterns, then we 11860 // don't want to extract anything. 11861 if overlaps(self.params.range, left_hand_side_range) { 11862 self.function = None; 11863 break; 11864 } 11865 } 11866 } 11867 11868 fn visit_typed_bit_array_size_variable( 11869 &mut self, 11870 location: &'ast SrcSpan, 11871 name: &'ast EcoString, 11872 constructor: &'ast Option<Box<ValueConstructor>>, 11873 type_: &'ast Arc<Type>, 11874 ) { 11875 let variant = match constructor { 11876 Some(constructor) => &constructor.variant, 11877 None => return, 11878 }; 11879 if let type_::ValueConstructorVariant::LocalVariable { 11880 location: definition_location, 11881 .. 11882 } = variant 11883 { 11884 self.register_referenced_variable(name, type_, *location, *definition_location); 11885 } 11886 } 11887} 11888 11889/// Code action to merge two identical branches together. 11890/// 11891pub struct MergeCaseBranches<'a> { 11892 module: &'a Module, 11893 params: &'a CodeActionParams, 11894 edits: TextEdits<'a>, 11895 /// These are the positions of the patterns of all the consecutive branches 11896 /// we've determined can be merged, for example if we're mergin the first 11897 /// two branches here: 11898 /// 11899 /// ```gleam 11900 /// case wibble { 11901 /// 1 -> todo 11902 /// // ^ this location here 11903 /// 20 -> todo 11904 /// // ^^ and this location here 11905 /// _ -> todo 11906 /// } 11907 /// ``` 11908 /// 11909 /// We need those to delete all the space between each consecutive pattern, 11910 /// replacing it with the `|` for alternatives 11911 /// 11912 patterns_to_merge: Option<MergeableBranches>, 11913} 11914 11915struct MergeableBranches { 11916 /// The span of the body to keep when merging multiple branches. For 11917 /// example: 11918 /// 11919 /// ```gleam 11920 /// case n { 11921 /// // Imagine we're merging the first three branches together... 11922 /// 1 -> todo 11923 /// 2 -> n * 2 11924 /// // ^^^^^ This would be the location of the one body to keep 11925 /// 3 -> todo 11926 /// _ -> todo 11927 /// } 11928 /// ``` 11929 /// 11930 body_to_keep: SrcSpan, 11931 11932 /// The location body of the last of the branches that are going to be 11933 /// merged; that is where we're going to place the code of the body to keep 11934 /// once the action is done. For example: 11935 /// 11936 /// ```gleam 11937 /// case n { 11938 /// // Imagine we're merging the first three branches together... 11939 /// 1 -> todo 11940 /// 2 -> n * 2 11941 /// 3 -> todo 11942 /// // ^^^^ This would be the location of the final body 11943 /// _ -> todo 11944 /// } 11945 /// ``` 11946 /// 11947 final_body: SrcSpan, 11948 11949 /// The span of the patterns whose branches are going to be merged. For 11950 /// example: 11951 /// 11952 /// ```gleam 11953 /// case n { 11954 /// // Imagine we're merging the first three branches together... 11955 /// 1 -> todo 11956 /// // ^ 11957 /// 2 -> n * 2 11958 /// // ^ 11959 /// 3 -> todo 11960 /// // ^ These would be the locations of the patterns 11961 /// _ -> todo 11962 /// } 11963 /// ``` 11964 /// 11965 patterns_to_merge: Vec<SrcSpan>, 11966} 11967 11968impl<'a> MergeCaseBranches<'a> { 11969 pub fn new( 11970 module: &'a Module, 11971 line_numbers: &'a LineNumbers, 11972 params: &'a CodeActionParams, 11973 ) -> Self { 11974 Self { 11975 module, 11976 params, 11977 edits: TextEdits::new(line_numbers), 11978 patterns_to_merge: None, 11979 } 11980 } 11981 11982 pub fn code_actions(mut self) -> Vec<CodeAction> { 11983 self.visit_typed_module(&self.module.ast); 11984 11985 let Some(mergeable_branches) = self.patterns_to_merge else { 11986 return vec![]; 11987 }; 11988 11989 for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() { 11990 self.edits 11991 .replace(SrcSpan::new(one.end, next.start), " | ".into()); 11992 } 11993 11994 self.edits.replace( 11995 mergeable_branches.final_body, 11996 code_at(self.module, mergeable_branches.body_to_keep).into(), 11997 ); 11998 11999 let mut action = Vec::with_capacity(1); 12000 CodeActionBuilder::new("Merge case branches") 12001 .kind(CodeActionKind::RefactorRewrite) 12002 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12003 .preferred(false) 12004 .push_to(&mut action); 12005 action 12006 } 12007 12008 fn select_mergeable_branches( 12009 &self, 12010 clauses: &'a [ast::TypedClause], 12011 ) -> Option<MergeableBranches> { 12012 let mut clauses = clauses 12013 .iter() 12014 // We want to skip all the branches at the beginning of the case 12015 // expression that the cursor is not hovering over. For example: 12016 // 12017 // ```gleam 12018 // case wibble { 12019 // a -> 1 <- we want to skip this one here that is not selected 12020 // b -> 2 12021 // ^^^^ this is the selection 12022 // _ -> 3 12023 // ^^ 12024 // } 12025 // ``` 12026 .skip_while(|clause| { 12027 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 12028 !overlaps(self.params.range, clause_range) 12029 }) 12030 // Then we only want to take the clauses that we're hovering over 12031 // with our selection (even partially!) 12032 // In the provious example they would be `b -> 2` and `_ -> 3`. 12033 .take_while(|clause| { 12034 let clause_range = self.edits.src_span_to_lsp_range(clause.location); 12035 overlaps(self.params.range, clause_range) 12036 }); 12037 12038 let first_hovered_clause = clauses.next()?; 12039 12040 // This is the clause we're comparing all the others with. We need to 12041 // make sure that all the clauses we're going to join can be merged with 12042 // this one. 12043 let mut reference_clause = first_hovered_clause; 12044 let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()]; 12045 let mut final_body = first_hovered_clause.then.location(); 12046 12047 for clause in clauses { 12048 // As soon as we find a clause that can't be merged with the current 12049 // reference we know we're done looking for consecutive clauses to 12050 // merge. 12051 if !clauses_can_be_merged(reference_clause, clause) { 12052 break; 12053 } 12054 12055 clause_patterns_to_merge.push(clause.pattern_location()); 12056 final_body = clause.then.location(); 12057 12058 // If the current reference is a `todo` expression, we want to use 12059 // the newly found mergeable clause as the next reference. The 12060 // reference clause is the one whose body will be kept around, so if 12061 // we can we avoid keeping `todo`s 12062 if reference_clause.then.is_todo_with_no_message() { 12063 reference_clause = clause; 12064 } 12065 } 12066 12067 // We only offer the code action if we have found two or more clauses 12068 // to merge. 12069 if clause_patterns_to_merge.len() >= 2 { 12070 Some(MergeableBranches { 12071 final_body, 12072 body_to_keep: reference_clause.then.location(), 12073 patterns_to_merge: clause_patterns_to_merge, 12074 }) 12075 } else { 12076 None 12077 } 12078 } 12079} 12080 12081fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool { 12082 // Two clauses cannot be merged if any of those has an if guard 12083 if one.guard.is_some() || other.guard.is_some() { 12084 return false; 12085 } 12086 12087 // Two clauses can only be merged if they define the same variables, 12088 // otherwise joining them would result in invalid code. 12089 let variables_one = one 12090 .bound_variables() 12091 .map(|variable| (variable.name(), variable.type_)) 12092 .collect::<HashMap<_, _>>(); 12093 12094 let variables_other = other 12095 .bound_variables() 12096 .map(|variable| (variable.name(), variable.type_)) 12097 .collect::<HashMap<_, _>>(); 12098 12099 for (name, type_) in variables_one.iter() { 12100 if let Some(type_other) = variables_other.get(name) 12101 && type_other.same_as(type_) 12102 { 12103 continue; 12104 } 12105 12106 // There's a variable that is not defined in the second branch but 12107 // is defined in the first one, or it's defined in the second branch 12108 // but it has an incompatible type. 12109 return false; 12110 } 12111 12112 for (name, _) in variables_other.iter() { 12113 if !variables_one.contains_key(name) { 12114 // There's some variables defined in the second branch that are not 12115 // defined in the first one, so they can't be merged! 12116 return false; 12117 } 12118 } 12119 12120 // Anything can be merged with a simple todo, or the two bodies must be 12121 // syntactically equal. 12122 one.then.is_todo_with_no_message() 12123 || other.then.is_todo_with_no_message() 12124 || one.then.syntactically_eq(&other.then) 12125} 12126 12127impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> { 12128 fn visit_typed_expr_case( 12129 &mut self, 12130 location: &'ast SrcSpan, 12131 type_: &'ast Arc<Type>, 12132 subjects: &'ast [TypedExpr], 12133 clauses: &'ast [ast::TypedClause], 12134 compiled_case: &'ast CompiledCase, 12135 ) { 12136 // We only trigger the code action if we are within a case expression, 12137 // otherwise there's no point in exploring the expression any further. 12138 let case_range = self.edits.src_span_to_lsp_range(*location); 12139 if !within(self.params.range, case_range) { 12140 return; 12141 } 12142 12143 if let result @ Some(_) = self.select_mergeable_branches(clauses) { 12144 self.patterns_to_merge = result 12145 } 12146 12147 // We still need to visit the case expression in case we want to apply 12148 // the code action to some case expression that is nested in one of its 12149 // branches! 12150 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); 12151 } 12152} 12153 12154/// Code action to add a missing type parameter to custom types. 12155/// If a custom type is missing a type parameter, as it is the case 12156/// in the following example, this action will offer to add the 12157/// type parameter to the type definition. 12158/// 12159/// Before: 12160/// ```gleam 12161/// type Wibble { 12162/// Wibble(field: t) 12163/// } 12164/// ``` 12165/// 12166/// After: 12167/// ```gleam 12168/// type Wibble(t) { 12169/// Wibble(field: t) 12170/// } 12171/// ``` 12172/// 12173pub struct AddMissingTypeParameter<'a> { 12174 module: &'a Module, 12175 params: &'a CodeActionParams, 12176 edits: TextEdits<'a>, 12177 /// The source location where the parameters should be defined. 12178 /// This might be a zero-length span if there are no parameters yet, 12179 /// or it might cover the already existing type parameter definitions. 12180 parameters_location: Option<SrcSpan>, 12181 /// If the type definition already had existing parameters before. 12182 has_existing_parameters: bool, 12183 /// The set of all type parameter names in the different variants of the type 12184 /// that are not already part of the type parameter definition on the type. 12185 missing_parameters: HashSet<EcoString>, 12186} 12187 12188impl<'a> AddMissingTypeParameter<'a> { 12189 pub fn new( 12190 module: &'a Module, 12191 line_numbers: &'a LineNumbers, 12192 params: &'a CodeActionParams, 12193 ) -> Self { 12194 Self { 12195 module, 12196 params, 12197 edits: TextEdits::new(line_numbers), 12198 parameters_location: None, 12199 has_existing_parameters: false, 12200 missing_parameters: HashSet::new(), 12201 } 12202 } 12203 12204 pub fn code_actions(mut self) -> Vec<CodeAction> { 12205 self.visit_typed_module(&self.module.ast); 12206 12207 let Some(type_parameters_location) = self.parameters_location else { 12208 return vec![]; 12209 }; 12210 12211 if self.missing_parameters.is_empty() { 12212 return vec![]; 12213 } 12214 12215 let mut new_parameters = self.missing_parameters.iter().sorted().join(", "); 12216 if self.has_existing_parameters { 12217 let has_trailing_comma = self 12218 .module 12219 .extra 12220 .trailing_commas 12221 .iter() 12222 .any(|&trailing_comma| type_parameters_location.contains(trailing_comma)); 12223 12224 if !has_trailing_comma { 12225 new_parameters.insert_str(0, ", "); 12226 } 12227 12228 self.edits 12229 .insert(type_parameters_location.end - 1, new_parameters); 12230 } else { 12231 self.edits 12232 .insert(type_parameters_location.end, format!("({new_parameters})")); 12233 } 12234 12235 let mut action = Vec::with_capacity(1); 12236 CodeActionBuilder::new("Add missing type parameter") 12237 .kind(CodeActionKind::QuickFix) 12238 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12239 .preferred(true) 12240 .push_to(&mut action); 12241 action 12242 } 12243} 12244 12245impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> { 12246 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 12247 let custom_type_range = self 12248 .edits 12249 .src_span_to_lsp_range(custom_type.full_location()); 12250 12251 // Only continue, if the action was selected anywhere within the custom type definition. 12252 if !within(self.params.range, custom_type_range) { 12253 return; 12254 } 12255 12256 self.parameters_location = Some(SrcSpan::new( 12257 custom_type.name_location.end, 12258 custom_type.location.end, 12259 )); 12260 12261 self.has_existing_parameters = !custom_type.typed_parameters.is_empty(); 12262 12263 let existing_names: HashSet<_> = custom_type 12264 .parameters 12265 .iter() 12266 .map(|(_, name)| name) 12267 .collect(); 12268 12269 // Collect the remaining type parameters from the variant constructors. 12270 for record in &custom_type.constructors { 12271 for argument in &record.arguments { 12272 if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast 12273 && !existing_names.contains(name) 12274 { 12275 let _ = self.missing_parameters.insert(name.clone()); 12276 } 12277 } 12278 } 12279 } 12280} 12281 12282/// Code action to replace a `_` with its actual type in an annotation. 12283/// 12284/// Before: 12285/// ```gleam 12286/// fn wibble() -> Ok(_) { Ok(1) } 12287/// // ^ Trigger it here 12288/// ``` 12289/// 12290/// After: 12291/// ```gleam 12292/// fn wibble() -> Ok(Int) { Ok(1) } 12293/// ``` 12294/// 12295pub struct ReplaceUnderscoreWithType<'a> { 12296 module: &'a Module, 12297 params: &'a CodeActionParams, 12298 edits: TextEdits<'a>, 12299 hovered_hole: Option<HoveredHole>, 12300} 12301 12302struct HoveredHole { 12303 type_: Arc<Type>, 12304 location: SrcSpan, 12305} 12306 12307impl<'a> ReplaceUnderscoreWithType<'a> { 12308 pub fn new( 12309 module: &'a Module, 12310 line_numbers: &'a LineNumbers, 12311 params: &'a CodeActionParams, 12312 ) -> Self { 12313 Self { 12314 module, 12315 params, 12316 edits: TextEdits::new(line_numbers), 12317 hovered_hole: None, 12318 } 12319 } 12320 12321 pub fn code_actions(mut self) -> Vec<CodeAction> { 12322 self.visit_typed_module(&self.module.ast); 12323 12324 let mut action = Vec::with_capacity(1); 12325 12326 let Some(HoveredHole { type_, location }) = self.hovered_hole else { 12327 return vec![]; 12328 }; 12329 let mut printer = Printer::new(&self.module.ast.names); 12330 self.edits 12331 .replace(location, format!("{}", printer.print_type(&type_))); 12332 12333 CodeActionBuilder::new("Replace `_` with type") 12334 .kind(CodeActionKind::QuickFix) 12335 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12336 .preferred(true) 12337 .push_to(&mut action); 12338 action 12339 } 12340} 12341 12342impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> { 12343 fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option<Arc<Type>>) { 12344 // We never traverse a type annotation we're not hovering 12345 let node_location = self.edits.src_span_to_lsp_range(node.location()); 12346 if !within(self.params.range, node_location) { 12347 return; 12348 } 12349 ast::visit::visit_type_ast(self, node, inferred_type); 12350 } 12351 12352 fn visit_type_ast_hole( 12353 &mut self, 12354 location: &'ast SrcSpan, 12355 _name: &'ast EcoString, 12356 type_: Option<Arc<Type>>, 12357 ) { 12358 if let Some(type_) = type_ { 12359 self.hovered_hole = Some(HoveredHole { 12360 type_, 12361 location: *location, 12362 }) 12363 } 12364 } 12365} 12366 12367/// Code action to turn a function used as a reference into a one-statement anonymous function. 12368/// 12369/// For example, if the code action was used on `op` here: 12370/// 12371/// ```gleam 12372/// list.map([1, 2, 3], op) 12373/// ``` 12374/// 12375/// it would become: 12376/// 12377/// ```gleam 12378/// list.map([1, 2, 3], fn(int) { 12379/// op(int) 12380/// }) 12381/// ``` 12382pub struct WrapInAnonymousFunction<'a> { 12383 module: &'a Module, 12384 params: &'a CodeActionParams, 12385 functions: Vec<FunctionToWrap>, 12386 edits: TextEdits<'a>, 12387} 12388 12389/// Helper struct, a target for [WrapInAnonymousFunction]. 12390struct FunctionToWrap { 12391 location: SrcSpan, 12392 arguments: Vec<Arc<Type>>, 12393 variables_names: VariablesNames, 12394} 12395 12396impl<'a> WrapInAnonymousFunction<'a> { 12397 pub fn new( 12398 module: &'a Module, 12399 line_numbers: &'a LineNumbers, 12400 params: &'a CodeActionParams, 12401 ) -> Self { 12402 Self { 12403 module, 12404 params, 12405 edits: TextEdits::new(line_numbers), 12406 functions: vec![], 12407 } 12408 } 12409 12410 pub fn code_actions(mut self) -> Vec<CodeAction> { 12411 self.visit_typed_module(&self.module.ast); 12412 12413 let mut actions = Vec::with_capacity(self.functions.len()); 12414 for target in self.functions { 12415 let mut name_generator = NameGenerator::new(); 12416 name_generator.reserve_variable_names(target.variables_names); 12417 let arguments = target 12418 .arguments 12419 .iter() 12420 .map(|type_| name_generator.generate_name_from_type(type_)) 12421 .join(", "); 12422 12423 self.edits 12424 .insert(target.location.start, format!("fn({arguments}) {{ ")); 12425 self.edits 12426 .insert(target.location.end, format!("({arguments}) }}")); 12427 12428 CodeActionBuilder::new("Wrap in anonymous function") 12429 .kind(CodeActionKind::RefactorRewrite) 12430 .changes( 12431 self.params.text_document.uri.clone(), 12432 self.edits.edits.drain(..).collect(), 12433 ) 12434 .push_to(&mut actions); 12435 } 12436 actions 12437 } 12438} 12439 12440impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> { 12441 fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 12442 let expression_range = self.edits.src_span_to_lsp_range(expression.location()); 12443 if !within(self.params.range, expression_range) { 12444 return; 12445 } 12446 12447 // Exclude if the expression is already a function literal 12448 let is_excluded = matches!(expression, TypedExpr::Fn { .. }); 12449 12450 if let Type::Fn { arguments, .. } = &*expression.type_() 12451 && !is_excluded 12452 { 12453 self.functions.push(FunctionToWrap { 12454 location: expression.location(), 12455 arguments: arguments.clone(), 12456 variables_names: VariablesNames::from_expression(expression), 12457 }); 12458 } 12459 12460 ast::visit::visit_typed_expr(self, expression); 12461 } 12462 12463 /// We don't want to apply to functions that are being explicitly called 12464 /// already, so we need to intercept visits to function calls and bounce 12465 /// them out again so they don't end up in our impl for visit_typed_expr. 12466 /// Otherwise this is the same as []. 12467 fn visit_typed_expr_call( 12468 &mut self, 12469 _location: &'ast SrcSpan, 12470 _type: &'ast Arc<Type>, 12471 fun: &'ast TypedExpr, 12472 arguments: &'ast [TypedCallArg], 12473 _argument_parentheses: &'ast Option<u32>, 12474 ) { 12475 // We only need to do this interception for explicit calls, so if any 12476 // of our arguments are explicit we re-enter the visitor as usual. 12477 if arguments.iter().any(|argument| argument.is_implicit()) { 12478 self.visit_typed_expr(fun); 12479 } else { 12480 // We still want to visit other nodes nested in the function being 12481 // called so we bounce the call back out. 12482 ast::visit::visit_typed_expr(self, fun); 12483 } 12484 12485 for argument in arguments { 12486 self.visit_typed_call_arg(argument); 12487 } 12488 } 12489 12490 /// We don't want to apply this to record constructors used in a record 12491 /// update. 12492 fn visit_typed_expr_record_update( 12493 &mut self, 12494 location: &'ast SrcSpan, 12495 spread_start: &'ast u32, 12496 type_: &'ast Arc<Type>, 12497 updated_record: &'ast TypedExpr, 12498 updated_record_assigned_name: &'ast Option<EcoString>, 12499 constructor: &'ast TypedExpr, 12500 arguments: &'ast [TypedCallArg], 12501 ) { 12502 // If we're hovering the record constructor of an update and that is a 12503 // simple record constructor, we don't want to offer the wrap in 12504 // anonymous function code action: 12505 // 12506 // ```gleam 12507 // Wibble(..wibble, a: 1) 12508 // // ^^^ Wrap in anonymous function makes no sense on this! 12509 // ``` 12510 let constructor_range = self.edits.src_span_to_lsp_range(constructor.location()); 12511 if within(self.params.range, constructor_range) 12512 && constructor.is_record_constructor_function() 12513 { 12514 return; 12515 } 12516 12517 ast::visit::visit_typed_expr_record_update( 12518 self, 12519 location, 12520 spread_start, 12521 type_, 12522 updated_record, 12523 updated_record_assigned_name, 12524 constructor, 12525 arguments, 12526 ); 12527 } 12528} 12529 12530/// Code action to unwrap trivial one-statement anonymous functions into just a 12531/// reference to the function called 12532/// 12533/// For example, if the code action was used on the anonymous function here: 12534/// 12535/// ```gleam 12536/// list.map([1, 2, 3], fn(int) { 12537/// op(int) 12538/// }) 12539/// ``` 12540/// 12541/// it would become: 12542/// 12543/// ```gleam 12544/// list.map([1, 2, 3], op) 12545/// ``` 12546pub struct UnwrapAnonymousFunction<'a> { 12547 module: &'a Module, 12548 line_numbers: &'a LineNumbers, 12549 params: &'a CodeActionParams, 12550 functions: Vec<FunctionToUnwrap>, 12551} 12552 12553/// Helper struct, a target for [UnwrapAnonymousFunction] 12554struct FunctionToUnwrap { 12555 /// Location of the anonymous function to apply the action to. 12556 outer_function: SrcSpan, 12557 /// Location of the opening brace of the anonymous function. 12558 outer_function_body_start: u32, 12559 /// Location of the function being called inside the anonymous function. 12560 /// This will be all that's left after the action, plus any comments. 12561 inner_function: SrcSpan, 12562 // Location of the opening parenthesis of the inner function's argument list. 12563 inner_function_arguments_start: u32, 12564} 12565 12566impl<'a> UnwrapAnonymousFunction<'a> { 12567 pub fn new( 12568 module: &'a Module, 12569 line_numbers: &'a LineNumbers, 12570 params: &'a CodeActionParams, 12571 ) -> Self { 12572 Self { 12573 module, 12574 line_numbers, 12575 params, 12576 functions: vec![], 12577 } 12578 } 12579 12580 pub fn code_actions(mut self) -> Vec<CodeAction> { 12581 self.visit_typed_module(&self.module.ast); 12582 12583 let mut actions = Vec::with_capacity(self.functions.len()); 12584 for function in &self.functions { 12585 let mut edits = TextEdits::new(self.line_numbers); 12586 12587 // We need to delete the anonymous function's head and the opening 12588 // brace but preserve comments between it and the inner function call. 12589 // We set our endpoint at the start of the function body, and move 12590 // it on through any whitespace. 12591 let head_deletion_end = 12592 next_nonwhitespace(&self.module.code, function.outer_function_body_start + 1); 12593 edits.delete(SrcSpan { 12594 start: function.outer_function.start, 12595 end: head_deletion_end, 12596 }); 12597 12598 // Delete the inner function call's arguments. 12599 edits.delete(SrcSpan { 12600 start: function.inner_function_arguments_start, 12601 end: function.inner_function.end, 12602 }); 12603 12604 // To delete the tail we remove the function end (the '}') and any 12605 // whitespace before it. 12606 let tail_deletion_start = 12607 previous_nonwhitespace(&self.module.code, function.outer_function.end - 1); 12608 edits.delete(SrcSpan { 12609 start: tail_deletion_start, 12610 end: function.outer_function.end, 12611 }); 12612 12613 CodeActionBuilder::new("Remove anonymous function wrapper") 12614 .kind(CodeActionKind::RefactorRewrite) 12615 .changes(self.params.text_document.uri.clone(), edits.edits) 12616 .push_to(&mut actions); 12617 } 12618 actions 12619 } 12620 12621 /// If an anonymous function can be unwrapped, save it to our list 12622 /// 12623 /// We need to ensure our subjects: 12624 /// - are anonymous function literals (not captures) 12625 /// - only contain a single statement 12626 /// - that statement is a function call 12627 /// - that call's arguments exactly match the arguments of the enclosing 12628 /// function 12629 fn register_function( 12630 &mut self, 12631 location: &'a SrcSpan, 12632 kind: &'a FunctionLiteralKind, 12633 arguments: &'a [TypedArg], 12634 body: &'a Vec1<TypedStatement>, 12635 ) { 12636 let function_range = src_span_to_lsp_range(*location, self.line_numbers); 12637 if !within(self.params.range, function_range) { 12638 return; 12639 } 12640 12641 let outer_body = match kind { 12642 FunctionLiteralKind::Anonymous { head, .. } => SrcSpan::new( 12643 next_nonwhitespace(&self.module.code, head.end), 12644 location.end, 12645 ), 12646 _ => return, 12647 }; 12648 12649 // We can only apply to anonymous functions containing a single function call 12650 let [ 12651 TypedStatement::Expression(TypedExpr::Call { 12652 location: call_location, 12653 arguments: call_arguments, 12654 open_parenthesis: Some(arguments_start), 12655 .. 12656 }), 12657 ] = body.as_slice() 12658 else { 12659 return; 12660 }; 12661 12662 // We need the existing argument list for the fn to be a 1:1 match for 12663 // the args we pass to the called function, so we need to collect the 12664 // names used in both lists and check they're equal. 12665 12666 let outer_argument_names = arguments.iter().map(|a| match &a.names { 12667 ArgNames::Named { name, .. } => Some(name), 12668 // We can bail out early if any arguments are discarded, since 12669 // they couldn't match those actually used. 12670 ArgNames::Discard { .. } => None, 12671 // Anonymous functions can't have labelled arguments. 12672 ArgNames::NamedLabelled { .. } => unreachable!(), 12673 ArgNames::LabelledDiscard { .. } => unreachable!(), 12674 }); 12675 12676 let inner_argument_names = call_arguments.iter().map(|a| match &a.value { 12677 TypedExpr::Var { name, .. } => Some(name), 12678 // We can bail out early if any of these aren't variables, since 12679 // they couldn't match the inputs. 12680 _ => None, 12681 }); 12682 12683 if !inner_argument_names.eq(outer_argument_names) { 12684 return; 12685 } 12686 12687 self.functions.push(FunctionToUnwrap { 12688 outer_function: *location, 12689 outer_function_body_start: outer_body.start, 12690 inner_function: *call_location, 12691 inner_function_arguments_start: *arguments_start, 12692 }) 12693 } 12694} 12695 12696impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> { 12697 fn visit_typed_expr_fn( 12698 &mut self, 12699 location: &'ast SrcSpan, 12700 type_: &'ast Arc<Type>, 12701 kind: &'ast FunctionLiteralKind, 12702 arguments: &'ast [TypedArg], 12703 body: &'ast Vec1<TypedStatement>, 12704 return_annotation: &'ast Option<ast::TypeAst>, 12705 ) { 12706 let function_range = src_span_to_lsp_range(*location, self.line_numbers); 12707 if !within(self.params.range, function_range) { 12708 return; 12709 } 12710 12711 self.register_function(location, kind, arguments, body); 12712 12713 ast::visit::visit_typed_expr_fn( 12714 self, 12715 location, 12716 type_, 12717 kind, 12718 arguments, 12719 body, 12720 return_annotation, 12721 ) 12722 } 12723} 12724 12725/// Code action to create unknown modules when an import is added for a 12726/// module that doesn't exist. 12727/// 12728/// For example, if `import wobble/woo` is added to `src/wiggle.gleam`, 12729/// then a code action to create `src/wobble/woo.gleam` will be presented 12730/// when triggered over `import wobble/woo`. 12731pub struct CreateUnknownModule<'a, IO> { 12732 module: &'a Module, 12733 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 12734 lines: &'a LineNumbers, 12735 params: &'a CodeActionParams, 12736 paths: &'a ProjectPaths, 12737 error: &'a Option<Error>, 12738} 12739 12740impl<'a, IO> CreateUnknownModule<'a, IO> { 12741 pub fn new( 12742 module: &'a Module, 12743 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 12744 lines: &'a LineNumbers, 12745 params: &'a CodeActionParams, 12746 paths: &'a ProjectPaths, 12747 error: &'a Option<Error>, 12748 ) -> Self { 12749 Self { 12750 module, 12751 compiler, 12752 lines, 12753 params, 12754 paths, 12755 error, 12756 } 12757 } 12758 12759 pub fn code_actions(self) -> Vec<CodeAction> { 12760 // This code action can be derived from `UnknownModule` type errors. 12761 // If those errors don't exist for the current module, then we will not 12762 // suggest this action. 12763 let Some(Error::Type { failed_modules, .. }) = self.error else { 12764 return vec![]; 12765 }; 12766 let Some(failed_module) = failed_modules.get(&self.module.name) else { 12767 return vec![]; 12768 }; 12769 12770 let mut unknown_module_name = None; 12771 // We then need to find the `UnknownModule` error that is under the 12772 // cursor (if any, otherwise there's no action to suggest)! 12773 for error in &failed_module.errors { 12774 let TypeError::UnknownModule { location, name, .. } = error else { 12775 continue; 12776 }; 12777 let error_range = src_span_to_lsp_range(*location, self.lines); 12778 if !within(self.params.range, error_range) { 12779 continue; 12780 } 12781 // We've found the unknown module error!! 12782 unknown_module_name = Some(name); 12783 } 12784 12785 let Some(unknown_module_name) = unknown_module_name else { 12786 return vec![]; 12787 }; 12788 12789 // Now we need to check the module actually doesn't exist among the 12790 // importable ones! Imagine we've written `timestamp.to_string` and 12791 // `timestamp` is unknown: if there's any importable module in the form 12792 // `.../timestamp` then the most likely scenario is that the programmer 12793 // wanted to import that and not create a new `src/timestamp.gleam` 12794 // file! 12795 // So we check if any of the importable modules ends with the unknown 12796 // name and if that's the case we don't suggest this action. 12797 let importable_modules = self.compiler.project_compiler.get_importable_modules(); 12798 let unknown_module_can_be_imported = importable_modules 12799 .keys() 12800 .find(|module_name| module_name.split('/').next_back() == Some(unknown_module_name)) 12801 .is_some(); 12802 if unknown_module_can_be_imported { 12803 return vec![]; 12804 } 12805 12806 // We've made sure the module is not among the importable ones, so now 12807 // we figure out if the generated module needs to go under `src`, 12808 // `test`, or `dev` and we're good to actually generate it! 12809 let origin_directory = match self.module.origin { 12810 Origin::Src => self.paths.src_directory(), 12811 Origin::Test => self.paths.test_directory(), 12812 Origin::Dev => self.paths.dev_directory(), 12813 }; 12814 12815 let uri = url_from_path(&format!("{origin_directory}/{}.gleam", unknown_module_name)) 12816 .expect("origin directory is absolute"); 12817 12818 let mut actions = vec![]; 12819 CodeActionBuilder::new(&format!( 12820 "Create {}/{}.gleam", 12821 self.module.origin.folder_name(), 12822 unknown_module_name 12823 )) 12824 .kind(CodeActionKind::QuickFix) 12825 .document_changes(vec![DocumentChange::CreateFile(CreateFile { 12826 uri, 12827 options: Some(CreateFileOptions { 12828 overwrite: Some(false), 12829 ignore_if_exists: Some(true), 12830 }), 12831 annotation_id: None, 12832 })]) 12833 .push_to(&mut actions); 12834 12835 actions 12836 } 12837} 12838 12839/// Code action to discard unused variable. 12840pub struct DiscardUnusedVariable<'a> { 12841 module: &'a Module, 12842 params: &'a CodeActionParams, 12843 edits: TextEdits<'a>, 12844} 12845 12846#[derive(Debug)] 12847struct UnusedVariable<'a> { 12848 location: &'a SrcSpan, 12849 origin: &'a VariableOrigin, 12850} 12851 12852impl<'a> DiscardUnusedVariable<'a> { 12853 pub fn new( 12854 module: &'a Module, 12855 line_numbers: &'a LineNumbers, 12856 params: &'a CodeActionParams, 12857 ) -> Self { 12858 Self { 12859 module, 12860 params, 12861 edits: TextEdits::new(line_numbers), 12862 } 12863 } 12864 12865 pub fn code_actions(mut self) -> Vec<CodeAction> { 12866 let unused_variable = self 12867 .module 12868 .ast 12869 .type_info 12870 .warnings 12871 .iter() 12872 .find_map(|warning| { 12873 if let type_::Warning::UnusedVariable { location, origin } = warning 12874 && within( 12875 self.params.range, 12876 self.edits.src_span_to_lsp_range(*location), 12877 ) 12878 { 12879 Some(UnusedVariable { location, origin }) 12880 } else { 12881 None 12882 } 12883 }); 12884 12885 let Some(unused_variable) = unused_variable else { 12886 return vec![]; 12887 }; 12888 12889 match unused_variable.origin.syntax { 12890 type_::error::VariableSyntax::Variable(_) => { 12891 self.edits 12892 .insert(unused_variable.location.start, ("_").to_string()); 12893 } 12894 type_::error::VariableSyntax::LabelShorthand(_) => { 12895 self.edits 12896 .insert(unused_variable.location.end, (" _").to_string()); 12897 } 12898 type_::error::VariableSyntax::AssignmentPattern(location) => { 12899 self.edits.delete(SrcSpan { 12900 start: location.start, 12901 end: location.end, 12902 }); 12903 } 12904 type_::error::VariableSyntax::Generated => (), 12905 } 12906 12907 let mut action = Vec::with_capacity(1); 12908 CodeActionBuilder::new("Discard unused variable") 12909 .kind(CodeActionKind::QuickFix) 12910 .changes(self.params.text_document.uri.clone(), self.edits.edits) 12911 .preferred(true) 12912 .push_to(&mut action); 12913 action 12914 } 12915} 12916 12917pub fn code_action_fix_deprecated_pipe( 12918 module: &Module, 12919 line_numbers: &LineNumbers, 12920 params: &CodeActionParams, 12921 actions: &mut Vec<CodeAction>, 12922) { 12923 let uri = &params.text_document.uri; 12924 let mut deprecated_pipes: Vec<&SrcSpan> = module 12925 .ast 12926 .type_info 12927 .warnings 12928 .iter() 12929 .filter_map(|warning| { 12930 if let type_::Warning::PipeIntoCallWhichReturnsFunction { location } = warning { 12931 Some(location) 12932 } else { 12933 None 12934 } 12935 }) 12936 .collect(); 12937 12938 if deprecated_pipes.is_empty() { 12939 return; 12940 } 12941 12942 // Sort spans by start position, with longer spans coming first 12943 deprecated_pipes.sort_by_key(|span| (span.start, -(span.len() as i64))); 12944 12945 for location in deprecated_pipes { 12946 let range = src_span_to_lsp_range(*location, line_numbers); 12947 12948 // Check if the cursor is within this span 12949 if !within(params.range, range) { 12950 continue; 12951 } 12952 12953 let edit = TextEdit { 12954 range: Range::new(range.end, range.end), 12955 new_text: "()".into(), 12956 }; 12957 12958 CodeActionBuilder::new("Add extra parentheses") 12959 .kind(CodeActionKind::QuickFix) 12960 .changes(uri.clone(), vec![edit]) 12961 .preferred(true) 12962 .push_to(actions); 12963 } 12964}