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