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