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