Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / language_server / code_action.rs
244 kB 7144 lines
1use std::{collections::HashSet, iter, sync::Arc}; 2 3use crate::{ 4 Error, STDLIB_PACKAGE_NAME, analyse, 5 ast::{ 6 self, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType, 7 FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern, 8 PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind, 9 TypedArg, TypedAssignment, TypedExpr, TypedModuleConstant, TypedPattern, 10 TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse, 11 visit::Visit as _, 12 }, 13 build::{Located, Module}, 14 config::PackageConfig, 15 exhaustiveness::CompiledCase, 16 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, 17 line_numbers::LineNumbers, 18 parse::{extra::ModuleExtra, lexer::str_to_keyword}, 19 strings::to_snake_case, 20 type_::{ 21 self, FieldMap, ModuleValueConstructor, Type, TypeVar, TypedCallArg, ValueConstructor, 22 error::{ModuleSuggestion, VariableOrigin}, 23 printer::{Names, Printer}, 24 }, 25}; 26use ecow::{EcoString, eco_format}; 27use im::HashMap; 28use itertools::Itertools; 29use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url}; 30use vec1::{Vec1, vec1}; 31 32use super::{ 33 TextEdits, 34 compiler::LspProjectCompiler, 35 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import}, 36 engine::{overlaps, within}, 37 files::FileSystemProxy, 38 reference::find_variable_references, 39 src_span_to_lsp_range, url_from_path, 40}; 41 42#[derive(Debug)] 43pub struct CodeActionBuilder { 44 action: CodeAction, 45} 46 47impl CodeActionBuilder { 48 pub fn new(title: &str) -> Self { 49 Self { 50 action: CodeAction { 51 title: title.to_string(), 52 kind: None, 53 diagnostics: None, 54 edit: None, 55 command: None, 56 is_preferred: None, 57 disabled: None, 58 data: None, 59 }, 60 } 61 } 62 63 pub fn kind(mut self, kind: CodeActionKind) -> Self { 64 self.action.kind = Some(kind); 65 self 66 } 67 68 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self { 69 let mut edit = self.action.edit.take().unwrap_or_default(); 70 let mut changes = edit.changes.take().unwrap_or_default(); 71 _ = changes.insert(uri, edits); 72 73 edit.changes = Some(changes); 74 self.action.edit = Some(edit); 75 self 76 } 77 78 pub fn preferred(mut self, is_preferred: bool) -> Self { 79 self.action.is_preferred = Some(is_preferred); 80 self 81 } 82 83 pub fn push_to(self, actions: &mut Vec<CodeAction>) { 84 actions.push(self.action); 85 } 86} 87 88/// A small helper function to get the indentation at a given position. 89fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize { 90 let mut indent_size = 0; 91 let line_start = *line_numbers 92 .line_starts 93 .get(line as usize) 94 .expect("Line number should be valid"); 95 96 let mut chars = code[line_start as usize..].chars(); 97 while chars.next() == Some(' ') { 98 indent_size += 1; 99 } 100 101 indent_size 102} 103 104/// Code action to remove literal tuples in case subjects, essentially making 105/// the elements of the tuples into the case's subjects. 106/// 107/// The code action is only available for the i'th subject if: 108/// - it is a non-empty tuple, and 109/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses. 110/// 111/// # Basic example: 112/// 113/// The following case expression: 114/// 115/// ```gleam 116/// case #(1, 2) { 117/// #(a, b) -> 0 118/// } 119/// ``` 120/// 121/// Becomes: 122/// 123/// ```gleam 124/// case 1, 2 { 125/// a, b -> 0 126/// } 127/// ``` 128/// 129/// # Another example: 130/// 131/// The following case expression does not produce any code action 132/// 133/// ```gleam 134/// case #(1, 2) { 135/// a -> 0 // <- the pattern is not a tuple 136/// } 137/// ``` 138pub struct RedundantTupleInCaseSubject<'a> { 139 edits: TextEdits<'a>, 140 code: &'a EcoString, 141 extra: &'a ModuleExtra, 142 params: &'a CodeActionParams, 143 module: &'a ast::TypedModule, 144 hovered: bool, 145} 146 147impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> { 148 fn visit_typed_expr_case( 149 &mut self, 150 location: &'ast SrcSpan, 151 type_: &'ast Arc<Type>, 152 subjects: &'ast [TypedExpr], 153 clauses: &'ast [ast::TypedClause], 154 compiled_case: &'ast CompiledCase, 155 ) { 156 for (subject_idx, subject) in subjects.iter().enumerate() { 157 let TypedExpr::Tuple { 158 location, elements, .. 159 } = subject 160 else { 161 continue; 162 }; 163 164 // Ignore empty tuple 165 if elements.is_empty() { 166 continue; 167 } 168 169 // We cannot rewrite clauses whose i-th pattern is not a discard or 170 // tuples. 171 let all_ith_patterns_are_tuples_or_discards = clauses 172 .iter() 173 .map(|clause| clause.pattern.get(subject_idx)) 174 .all(|pattern| { 175 matches!( 176 pattern, 177 Some(Pattern::Tuple { .. } | Pattern::Discard { .. }) 178 ) 179 }); 180 181 if !all_ith_patterns_are_tuples_or_discards { 182 continue; 183 } 184 185 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location())); 186 187 for clause in clauses { 188 match clause.pattern.get(subject_idx) { 189 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens( 190 *location, 191 elements.last().map(|element| element.location()), 192 ), 193 Some(Pattern::Discard { location, .. }) => { 194 self.discard_tuple_items(*location, elements.len()) 195 } 196 _ => panic!("safe: we've just checked all patterns must be discards/tuples"), 197 } 198 } 199 let range = self.edits.src_span_to_lsp_range(*location); 200 self.hovered = self.hovered || overlaps(self.params.range, range); 201 } 202 203 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case) 204 } 205} 206 207impl<'a> RedundantTupleInCaseSubject<'a> { 208 pub fn new( 209 module: &'a Module, 210 line_numbers: &'a LineNumbers, 211 params: &'a CodeActionParams, 212 ) -> Self { 213 Self { 214 edits: TextEdits::new(line_numbers), 215 code: &module.code, 216 extra: &module.extra, 217 params, 218 module: &module.ast, 219 hovered: false, 220 } 221 } 222 223 pub fn code_actions(mut self) -> Vec<CodeAction> { 224 self.visit_typed_module(self.module); 225 if !self.hovered { 226 return vec![]; 227 } 228 229 self.edits.edits.sort_by_key(|edit| edit.range.start); 230 231 let mut actions = vec![]; 232 CodeActionBuilder::new("Remove redundant tuples") 233 .kind(CodeActionKind::REFACTOR_REWRITE) 234 .changes(self.params.text_document.uri.clone(), self.edits.edits) 235 .preferred(true) 236 .push_to(&mut actions); 237 238 actions 239 } 240 241 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) { 242 let tuple_code = self 243 .code 244 .get(location.start as usize..location.end as usize) 245 .expect("valid span"); 246 247 // Delete `#` 248 self.edits 249 .delete(SrcSpan::new(location.start, location.start + 1)); 250 251 // Delete `(` 252 let (lparen_offset, _) = tuple_code 253 .match_indices('(') 254 // Ignore in comments 255 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32)) 256 .expect("`(` not found in tuple"); 257 258 self.edits.delete(SrcSpan::new( 259 location.start + lparen_offset as u32, 260 location.start + lparen_offset as u32 + 1, 261 )); 262 263 // Delete trailing `,` (if applicable) 264 if let Some(last_elem_location) = last_elem_location { 265 // Get the code after the last element until the tuple's `)` 266 let code_after_last_elem = self 267 .code 268 .get(last_elem_location.end as usize..location.end as usize) 269 .expect("valid span"); 270 271 if let Some((trailing_comma_offset, _)) = code_after_last_elem 272 .rmatch_indices(',') 273 // Ignore in comments 274 .find(|(i, _)| { 275 !self 276 .extra 277 .is_within_comment(last_elem_location.end + *i as u32) 278 }) 279 { 280 self.edits.delete(SrcSpan::new( 281 last_elem_location.end + trailing_comma_offset as u32, 282 last_elem_location.end + trailing_comma_offset as u32 + 1, 283 )); 284 } 285 } 286 287 // Delete ) 288 self.edits 289 .delete(SrcSpan::new(location.end - 1, location.end)); 290 } 291 292 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) { 293 // Replace the old discard with multiple discard, one for each of the 294 // tuple items. 295 self.edits.replace( 296 discard_location, 297 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(), 298 ) 299 } 300} 301 302/// Builder for code action to convert `let assert` into a case expression. 303/// 304pub struct LetAssertToCase<'a> { 305 module: &'a Module, 306 params: &'a CodeActionParams, 307 actions: Vec<CodeAction>, 308 edits: TextEdits<'a>, 309} 310 311impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> { 312 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 313 // To prevent weird behaviour when `let assert` statements are nested, 314 // we only check for the code action between the `let` and `=`. 315 let code_action_location = 316 SrcSpan::new(assignment.location.start, assignment.value.location().start); 317 let code_action_range = 318 src_span_to_lsp_range(code_action_location, self.edits.line_numbers); 319 320 self.visit_typed_expr(&assignment.value); 321 322 // Only offer the code action if the cursor is over the statement 323 if !overlaps(code_action_range, self.params.range) { 324 return; 325 } 326 327 // This pattern only applies to `let assert` 328 let AssignmentKind::Assert { message, .. } = &assignment.kind else { 329 return; 330 }; 331 332 // Get the source code for the tested expression 333 let location = assignment.value.location(); 334 let expr = self 335 .module 336 .code 337 .get(location.start as usize..location.end as usize) 338 .expect("Location must be valid"); 339 340 // Get the source code for the pattern 341 let pattern_location = assignment.pattern.location(); 342 let pattern = self 343 .module 344 .code 345 .get(pattern_location.start as usize..pattern_location.end as usize) 346 .expect("Location must be valid"); 347 348 let message = message.as_ref().map(|message| { 349 let location = message.location(); 350 self.module 351 .code 352 .get(location.start as usize..location.end as usize) 353 .expect("Location must be valid") 354 }); 355 356 let range = src_span_to_lsp_range(assignment.location, self.edits.line_numbers); 357 358 // Figure out which variables are assigned in the pattern 359 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern); 360 361 let assigned = match variables.len() { 362 0 => "_", 363 1 => variables.first().expect("Variables is length one"), 364 _ => &format!("#({})", variables.join(", ")), 365 }; 366 367 let mut new_text = format!("let {assigned} = "); 368 let panic_message = if let Some(message) = message { 369 &format!("panic as {message}") 370 } else { 371 "panic" 372 }; 373 let clauses = vec![ 374 // The existing pattern 375 CaseClause { 376 pattern, 377 // `_` is not a valid expression, so if we are not 378 // binding any variables in the pattern, we simply return Nil. 379 expression: if assigned == "_" { "Nil" } else { assigned }, 380 }, 381 CaseClause { 382 pattern: "_", 383 expression: panic_message, 384 }, 385 ]; 386 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text); 387 388 let uri = &self.params.text_document.uri; 389 390 CodeActionBuilder::new("Convert to case") 391 .kind(CodeActionKind::REFACTOR_REWRITE) 392 .changes(uri.clone(), vec![TextEdit { range, new_text }]) 393 .preferred(false) 394 .push_to(&mut self.actions); 395 } 396} 397 398impl<'a> LetAssertToCase<'a> { 399 pub fn new( 400 module: &'a Module, 401 line_numbers: &'a LineNumbers, 402 params: &'a CodeActionParams, 403 ) -> Self { 404 Self { 405 module, 406 params, 407 actions: Vec::new(), 408 edits: TextEdits::new(line_numbers), 409 } 410 } 411 412 pub fn code_actions(mut self) -> Vec<CodeAction> { 413 self.visit_typed_module(&self.module.ast); 414 self.actions 415 } 416} 417 418struct PatternVariableFinder { 419 pattern_variables: Vec<EcoString>, 420} 421 422impl PatternVariableFinder { 423 fn new() -> Self { 424 Self { 425 pattern_variables: Vec::new(), 426 } 427 } 428 429 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> { 430 let mut finder = Self::new(); 431 finder.visit_typed_pattern(pattern); 432 finder.pattern_variables 433 } 434} 435 436impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder { 437 fn visit_typed_pattern_variable( 438 &mut self, 439 _location: &'ast SrcSpan, 440 name: &'ast EcoString, 441 _type: &'ast Arc<Type>, 442 _origin: &'ast VariableOrigin, 443 ) { 444 self.pattern_variables.push(name.clone()); 445 } 446 447 fn visit_typed_pattern_assign( 448 &mut self, 449 location: &'ast SrcSpan, 450 name: &'ast EcoString, 451 pattern: &'ast TypedPattern, 452 ) { 453 self.pattern_variables.push(name.clone()); 454 ast::visit::visit_typed_pattern_assign(self, location, name, pattern); 455 } 456 457 fn visit_typed_pattern_string_prefix( 458 &mut self, 459 _location: &'ast SrcSpan, 460 _left_location: &'ast SrcSpan, 461 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, 462 _right_location: &'ast SrcSpan, 463 _left_side_string: &'ast EcoString, 464 right_side_assignment: &'ast AssignName, 465 ) { 466 if let Some((name, _)) = left_side_assignment { 467 self.pattern_variables.push(name.clone()); 468 } 469 if let AssignName::Variable(name) = right_side_assignment { 470 self.pattern_variables.push(name.clone()); 471 } 472 } 473} 474 475pub fn code_action_inexhaustive_let_to_case( 476 module: &Module, 477 line_numbers: &LineNumbers, 478 params: &CodeActionParams, 479 error: &Option<Error>, 480 actions: &mut Vec<CodeAction>, 481) { 482 let Some(Error::Type { errors, .. }) = error else { 483 return; 484 }; 485 let inexhaustive_assignments = errors 486 .iter() 487 .filter_map(|error| match error { 488 type_::Error::InexhaustiveLetAssignment { location, missing } => { 489 Some((*location, missing)) 490 } 491 _ => None, 492 }) 493 .collect_vec(); 494 495 if inexhaustive_assignments.is_empty() { 496 return; 497 } 498 499 for (location, missing) in inexhaustive_assignments { 500 let mut text_edits = TextEdits::new(line_numbers); 501 502 let range = text_edits.src_span_to_lsp_range(location); 503 if !overlaps(params.range, range) { 504 return; 505 } 506 507 let Some(Located::Statement(TypedStatement::Assignment(TypedAssignment { 508 value, 509 pattern, 510 kind: AssignmentKind::Let, 511 location, 512 compiled_case: _, 513 annotation: _, 514 }))) = module.find_node(location.start) 515 else { 516 continue; 517 }; 518 519 // Get the source code for the tested expression 520 let value_location = value.location(); 521 let expr = module 522 .code 523 .get(value_location.start as usize..value_location.end as usize) 524 .expect("Location must be valid"); 525 526 // Get the source code for the pattern 527 let pattern_location = pattern.location(); 528 let pattern_code = module 529 .code 530 .get(pattern_location.start as usize..pattern_location.end as usize) 531 .expect("Location must be valid"); 532 533 let range = text_edits.src_span_to_lsp_range(*location); 534 535 // Figure out which variables are assigned in the pattern 536 let variables = PatternVariableFinder::find_variables_in_pattern(pattern); 537 538 let assigned = match variables.len() { 539 0 => "_", 540 1 => variables.first().expect("Variables is length one"), 541 _ => &format!("#({})", variables.join(", ")), 542 }; 543 544 let mut new_text = format!("let {assigned} = "); 545 print_case_expression( 546 range.start.character as usize, 547 expr, 548 iter::once(CaseClause { 549 pattern: pattern_code, 550 expression: if assigned == "_" { "Nil" } else { assigned }, 551 }) 552 .chain(missing.iter().map(|pattern| CaseClause { 553 pattern, 554 expression: "todo", 555 })) 556 .collect(), 557 &mut new_text, 558 ); 559 560 let uri = &params.text_document.uri; 561 562 text_edits.replace(*location, new_text); 563 564 CodeActionBuilder::new("Convert to case") 565 .kind(CodeActionKind::QUICKFIX) 566 .changes(uri.clone(), text_edits.edits) 567 .preferred(true) 568 .push_to(actions); 569 } 570} 571 572struct CaseClause<'a> { 573 pattern: &'a str, 574 expression: &'a str, 575} 576 577fn print_case_expression( 578 indent_size: usize, 579 subject: &str, 580 clauses: Vec<CaseClause<'_>>, 581 buffer: &mut String, 582) { 583 let indent = " ".repeat(indent_size); 584 585 // Print the beginning of the expression 586 buffer.push_str("case "); 587 buffer.push_str(subject); 588 buffer.push_str(" {"); 589 590 for clause in clauses.iter() { 591 // Print the newline and indentation for this clause 592 buffer.push('\n'); 593 buffer.push_str(&indent); 594 // Indent this clause one level deeper than the case expression 595 buffer.push_str(" "); 596 597 // Print the clause 598 buffer.push_str(clause.pattern); 599 buffer.push_str(" -> "); 600 buffer.push_str(clause.expression); 601 } 602 603 // If there are no clauses to print, the closing brace should be 604 // on the same line as the opening one, with no space between. 605 if !clauses.is_empty() { 606 buffer.push('\n'); 607 buffer.push_str(&indent); 608 } 609 buffer.push('}'); 610} 611 612/// Builder for code action to apply the label shorthand syntax on arguments 613/// where the label has the same name as the variable. 614/// 615pub struct UseLabelShorthandSyntax<'a> { 616 module: &'a Module, 617 params: &'a CodeActionParams, 618 edits: TextEdits<'a>, 619} 620 621impl<'a> UseLabelShorthandSyntax<'a> { 622 pub fn new( 623 module: &'a Module, 624 line_numbers: &'a LineNumbers, 625 params: &'a CodeActionParams, 626 ) -> Self { 627 Self { 628 module, 629 params, 630 edits: TextEdits::new(line_numbers), 631 } 632 } 633 634 pub fn code_actions(mut self) -> Vec<CodeAction> { 635 self.visit_typed_module(&self.module.ast); 636 if self.edits.edits.is_empty() { 637 return vec![]; 638 } 639 let mut action = Vec::with_capacity(1); 640 CodeActionBuilder::new("Use label shorthand syntax") 641 .kind(CodeActionKind::REFACTOR) 642 .changes(self.params.text_document.uri.clone(), self.edits.edits) 643 .preferred(false) 644 .push_to(&mut action); 645 action 646 } 647} 648 649impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> { 650 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 651 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 652 let is_selected = overlaps(arg_range, self.params.range); 653 654 match arg { 655 CallArg { 656 label: Some(label), 657 value: TypedExpr::Var { name, location, .. }, 658 .. 659 } if is_selected && !arg.uses_label_shorthand() && label == name => { 660 self.edits.delete(*location) 661 } 662 _ => (), 663 } 664 665 ast::visit::visit_typed_call_arg(self, arg) 666 } 667 668 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) { 669 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 670 let is_selected = overlaps(arg_range, self.params.range); 671 672 match arg { 673 CallArg { 674 label: Some(label), 675 value: TypedPattern::Variable { name, location, .. }, 676 .. 677 } if is_selected && !arg.uses_label_shorthand() && label == name => { 678 self.edits.delete(*location) 679 } 680 _ => (), 681 } 682 683 ast::visit::visit_typed_pattern_call_arg(self, arg) 684 } 685} 686 687/// Builder for code action to apply the fill in the missing labelled arguments 688/// of the selected function call. 689/// 690pub struct FillInMissingLabelledArgs<'a> { 691 module: &'a Module, 692 params: &'a CodeActionParams, 693 edits: TextEdits<'a>, 694 use_right_hand_side_location: Option<SrcSpan>, 695 selected_call: Option<SelectedCall<'a>>, 696} 697 698struct SelectedCall<'a> { 699 location: SrcSpan, 700 field_map: &'a FieldMap, 701 arguments: Vec<CallArg<()>>, 702 kind: SelectedCallKind, 703} 704 705enum SelectedCallKind { 706 Value, 707 Pattern, 708} 709 710impl<'a> FillInMissingLabelledArgs<'a> { 711 pub fn new( 712 module: &'a Module, 713 line_numbers: &'a LineNumbers, 714 params: &'a CodeActionParams, 715 ) -> Self { 716 Self { 717 module, 718 params, 719 edits: TextEdits::new(line_numbers), 720 use_right_hand_side_location: None, 721 selected_call: None, 722 } 723 } 724 725 pub fn code_actions(mut self) -> Vec<CodeAction> { 726 self.visit_typed_module(&self.module.ast); 727 728 if let Some(SelectedCall { 729 location: call_location, 730 field_map, 731 arguments, 732 kind, 733 }) = self.selected_call 734 { 735 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback()); 736 let missing_labels = field_map.missing_labels(&arguments); 737 738 // If we're applying the code action to a use call, then we know 739 // that the last missing argument is going to be implicitly inserted 740 // by the compiler, so in that case we don't want to also add that 741 // last label to the completions. 742 let missing_labels = missing_labels.iter().peekable(); 743 let mut missing_labels = if is_use_call { 744 missing_labels.dropping_back(1) 745 } else { 746 missing_labels 747 }; 748 749 // If we couldn't find any missing label to insert we just return. 750 if missing_labels.peek().is_none() { 751 return vec![]; 752 } 753 754 // Now we need to figure out if there's a comma at the end of the 755 // arguments list: 756 // 757 // call(one, |) 758 // ^ Cursor here, with a comma behind 759 // 760 // call(one|) 761 // ^ Cursor here, no comma behind, we'll have to add one! 762 // 763 let label_insertion_start = call_location.end - 1; 764 let has_comma_after_last_argument = if let Some(last_arg) = arguments 765 .iter() 766 .filter(|arg| !arg.is_implicit()) 767 .next_back() 768 { 769 self.module 770 .code 771 .get(last_arg.location.end as usize..=label_insertion_start as usize) 772 .is_some_and(|text| text.contains(',')) 773 } else { 774 false 775 }; 776 777 let format_label = match kind { 778 SelectedCallKind::Value => |label| format!("{label}: todo"), 779 SelectedCallKind::Pattern => |label| format!("{label}:"), 780 }; 781 782 let labels_list = missing_labels.map(format_label).join(", "); 783 784 let has_no_explicit_arguments = arguments 785 .iter() 786 .filter(|arg| !arg.is_implicit()) 787 .peekable() 788 .peek() 789 .is_none(); 790 791 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument { 792 labels_list 793 } else { 794 format!(", {labels_list}") 795 }; 796 797 self.edits.insert(label_insertion_start, labels_list); 798 799 let mut action = Vec::with_capacity(1); 800 CodeActionBuilder::new("Fill labels") 801 .kind(CodeActionKind::QUICKFIX) 802 .changes(self.params.text_document.uri.clone(), self.edits.edits) 803 .preferred(true) 804 .push_to(&mut action); 805 return action; 806 } 807 808 vec![] 809 } 810 811 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> { 812 CallArg { 813 label: argument.label.clone(), 814 location: argument.location, 815 value: (), 816 implicit: argument.implicit, 817 } 818 } 819} 820 821impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> { 822 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 823 // If we're adding labels to a use call the correct location of the 824 // function we need to add labels to is `use_right_hand_side_location`. 825 // So we store it for when we're typing the use call. 826 let previous = self.use_right_hand_side_location; 827 self.use_right_hand_side_location = Some(use_.right_hand_side_location); 828 ast::visit::visit_typed_use(self, use_); 829 self.use_right_hand_side_location = previous; 830 } 831 832 fn visit_typed_expr_call( 833 &mut self, 834 location: &'ast SrcSpan, 835 type_: &'ast Arc<Type>, 836 fun: &'ast TypedExpr, 837 args: &'ast [TypedCallArg], 838 ) { 839 let call_range = self.edits.src_span_to_lsp_range(*location); 840 if !within(self.params.range, call_range) { 841 return; 842 } 843 844 if let Some(field_map) = fun.field_map() { 845 let location = self.use_right_hand_side_location.unwrap_or(*location); 846 self.selected_call = Some(SelectedCall { 847 location, 848 field_map, 849 arguments: args.iter().map(Self::empty_argument).collect(), 850 kind: SelectedCallKind::Value, 851 }) 852 } 853 854 // We only want to take into account the innermost function call 855 // containing the current selection so we can't stop at the first call 856 // we find (the outermost one) and have to keep traversing it in case 857 // we're inside a nested call. 858 let previous = self.use_right_hand_side_location; 859 self.use_right_hand_side_location = None; 860 ast::visit::visit_typed_expr_call(self, location, type_, fun, args); 861 self.use_right_hand_side_location = previous; 862 } 863 864 fn visit_typed_pattern_constructor( 865 &mut self, 866 location: &'ast SrcSpan, 867 name_location: &'ast SrcSpan, 868 name: &'ast EcoString, 869 arguments: &'ast Vec<CallArg<TypedPattern>>, 870 module: &'ast Option<(EcoString, SrcSpan)>, 871 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 872 spread: &'ast Option<SrcSpan>, 873 type_: &'ast Arc<Type>, 874 ) { 875 let call_range = self.edits.src_span_to_lsp_range(*location); 876 if !within(self.params.range, call_range) { 877 return; 878 } 879 880 if let Some(field_map) = constructor.field_map() { 881 self.selected_call = Some(SelectedCall { 882 location: *location, 883 field_map, 884 arguments: arguments.iter().map(Self::empty_argument).collect(), 885 kind: SelectedCallKind::Pattern, 886 }) 887 } 888 889 ast::visit::visit_typed_pattern_constructor( 890 self, 891 location, 892 name_location, 893 name, 894 arguments, 895 module, 896 constructor, 897 spread, 898 type_, 899 ); 900 } 901} 902 903struct MissingImport { 904 location: SrcSpan, 905 suggestions: Vec<ImportSuggestion>, 906} 907 908struct ImportSuggestion { 909 // The name to replace with, if the user made a typo 910 name: EcoString, 911 // The optional module to import, if suggesting an importable module 912 import: Option<EcoString>, 913} 914 915pub fn code_action_import_module( 916 module: &Module, 917 line_numbers: &LineNumbers, 918 params: &CodeActionParams, 919 error: &Option<Error>, 920 actions: &mut Vec<CodeAction>, 921) { 922 let uri = &params.text_document.uri; 923 let Some(Error::Type { errors, .. }) = error else { 924 return; 925 }; 926 927 let missing_imports = errors 928 .into_iter() 929 .filter_map(|e| match e { 930 type_::Error::UnknownModule { 931 location, 932 suggestions, 933 .. 934 } => suggest_imports(*location, suggestions), 935 _ => None, 936 }) 937 .collect_vec(); 938 939 if missing_imports.is_empty() { 940 return; 941 } 942 943 let first_import_pos = position_of_first_definition_if_import(module, line_numbers); 944 let first_is_import = first_import_pos.is_some(); 945 let import_location = first_import_pos.unwrap_or_default(); 946 947 let after_import_newlines = 948 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code); 949 950 for missing_import in missing_imports { 951 let range = src_span_to_lsp_range(missing_import.location, line_numbers); 952 if !overlaps(params.range, range) { 953 continue; 954 } 955 956 for suggestion in missing_import.suggestions { 957 let mut edits = vec![TextEdit { 958 range, 959 new_text: suggestion.name.to_string(), 960 }]; 961 if let Some(import) = &suggestion.import { 962 edits.push(get_import_edit( 963 import_location, 964 import, 965 &after_import_newlines, 966 )) 967 }; 968 969 let title = match &suggestion.import { 970 Some(import) => &format!("Import `{import}`"), 971 _ => &format!("Did you mean `{}`", suggestion.name), 972 }; 973 974 CodeActionBuilder::new(title) 975 .kind(CodeActionKind::QUICKFIX) 976 .changes(uri.clone(), edits) 977 .preferred(true) 978 .push_to(actions); 979 } 980 } 981} 982 983fn suggest_imports( 984 location: SrcSpan, 985 importable_modules: &[ModuleSuggestion], 986) -> Option<MissingImport> { 987 let suggestions = importable_modules 988 .iter() 989 .map(|suggestion| { 990 let imported_name = suggestion.last_name_component(); 991 match suggestion { 992 ModuleSuggestion::Importable(name) => ImportSuggestion { 993 name: imported_name.into(), 994 import: Some(name.clone()), 995 }, 996 ModuleSuggestion::Imported(_) => ImportSuggestion { 997 name: imported_name.into(), 998 import: None, 999 }, 1000 } 1001 }) 1002 .collect_vec(); 1003 1004 if suggestions.is_empty() { 1005 None 1006 } else { 1007 Some(MissingImport { 1008 location, 1009 suggestions, 1010 }) 1011 } 1012} 1013 1014pub fn code_action_add_missing_patterns( 1015 module: &Module, 1016 line_numbers: &LineNumbers, 1017 params: &CodeActionParams, 1018 error: &Option<Error>, 1019 actions: &mut Vec<CodeAction>, 1020) { 1021 let uri = &params.text_document.uri; 1022 let Some(Error::Type { errors, .. }) = error else { 1023 return; 1024 }; 1025 let missing_patterns = errors 1026 .iter() 1027 .filter_map(|error| match error { 1028 type_::Error::InexhaustiveCaseExpression { location, missing } => { 1029 Some((*location, missing)) 1030 } 1031 _ => None, 1032 }) 1033 .collect_vec(); 1034 1035 if missing_patterns.is_empty() { 1036 return; 1037 } 1038 1039 for (location, missing) in missing_patterns { 1040 let mut edits = TextEdits::new(line_numbers); 1041 let range = edits.src_span_to_lsp_range(location); 1042 if !overlaps(params.range, range) { 1043 return; 1044 } 1045 1046 let Some(Located::Expression { 1047 expression: TypedExpr::Case { 1048 clauses, subjects, .. 1049 }, 1050 .. 1051 }) = module.find_node(location.start) 1052 else { 1053 continue; 1054 }; 1055 1056 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line); 1057 1058 let indent = " ".repeat(indent_size); 1059 1060 // Insert the missing patterns just after the final clause, or just before 1061 // the closing brace if there are no clauses 1062 1063 let insert_at = clauses 1064 .last() 1065 .map(|clause| clause.location.end) 1066 .unwrap_or(location.end - 1); 1067 1068 for pattern in missing { 1069 let new_text = format!("\n{indent} {pattern} -> todo"); 1070 edits.insert(insert_at, new_text); 1071 } 1072 1073 // Add a newline + indent after the last pattern if there are no clauses 1074 // 1075 // This improves the generated code for this case: 1076 // ```gleam 1077 // case True {} 1078 // ``` 1079 // This produces: 1080 // ```gleam 1081 // case True { 1082 // True -> todo 1083 // False -> todo 1084 // } 1085 // ``` 1086 // Instead of: 1087 // ```gleam 1088 // case True { 1089 // True -> todo 1090 // False -> todo} 1091 // ``` 1092 // 1093 if clauses.is_empty() { 1094 let last_subject_location = subjects 1095 .last() 1096 .expect("Case expressions have at least one subject") 1097 .location() 1098 .end; 1099 1100 // Find the opening brace of the case expression 1101 let chars = module.code[last_subject_location as usize..].chars(); 1102 let mut start_brace_location = last_subject_location; 1103 for char in chars { 1104 start_brace_location += 1; 1105 if char == '{' { 1106 break; 1107 } 1108 } 1109 1110 // Remove any blank spaces/lines between the start brace and end brace 1111 edits.delete(SrcSpan::new(start_brace_location, insert_at)); 1112 edits.insert(insert_at, format!("\n{indent}")); 1113 } 1114 1115 CodeActionBuilder::new("Add missing patterns") 1116 .kind(CodeActionKind::QUICKFIX) 1117 .changes(uri.clone(), edits.edits) 1118 .preferred(true) 1119 .push_to(actions); 1120 } 1121} 1122 1123/// Builder for code action to add annotations to an assignment or function 1124/// 1125pub struct AddAnnotations<'a> { 1126 module: &'a Module, 1127 params: &'a CodeActionParams, 1128 edits: TextEdits<'a>, 1129 printer: Printer<'a>, 1130} 1131 1132impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> { 1133 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 1134 self.visit_typed_expr(&assignment.value); 1135 1136 // We only offer this code action between `let` and `=`, because 1137 // otherwise it could lead to confusing behaviour if inside a block 1138 // which is part of a let binding. 1139 let pattern_location = assignment.pattern.location(); 1140 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 1141 let code_action_range = self.edits.src_span_to_lsp_range(location); 1142 1143 // Only offer the code action if the cursor is over the statement 1144 if !overlaps(code_action_range, self.params.range) { 1145 return; 1146 } 1147 1148 // We don't need to add an annotation if there already is one 1149 if assignment.annotation.is_some() { 1150 return; 1151 } 1152 1153 // Various expressions such as pipelines and `use` expressions generate assignments 1154 // internally. However, these cannot be annotated and so we don't offer a code action here. 1155 if matches!(assignment.kind, AssignmentKind::Generated) { 1156 return; 1157 } 1158 1159 self.edits.insert( 1160 pattern_location.end, 1161 format!(": {}", self.printer.print_type(&assignment.type_())), 1162 ); 1163 } 1164 1165 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { 1166 let code_action_range = self.edits.src_span_to_lsp_range(constant.location); 1167 1168 // Only offer the code action if the cursor is over the statement 1169 if !overlaps(code_action_range, self.params.range) { 1170 return; 1171 } 1172 1173 // We don't need to add an annotation if there already is one 1174 if constant.annotation.is_some() { 1175 return; 1176 } 1177 1178 self.edits.insert( 1179 constant.name_location.end, 1180 format!(": {}", self.printer.print_type(&constant.type_)), 1181 ); 1182 } 1183 1184 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 1185 ast::visit::visit_typed_function(self, fun); 1186 1187 let code_action_range = self.edits.src_span_to_lsp_range(fun.location); 1188 1189 // Only offer the code action if the cursor is over the statement 1190 if !overlaps(code_action_range, self.params.range) { 1191 return; 1192 } 1193 1194 // Annotate each argument separately 1195 for argument in fun.arguments.iter() { 1196 // Don't annotate the argument if it's already annotated 1197 if argument.annotation.is_some() { 1198 continue; 1199 } 1200 1201 self.edits.insert( 1202 argument.location.end, 1203 format!(": {}", self.printer.print_type(&argument.type_)), 1204 ); 1205 } 1206 1207 // Annotate the return type if it isn't already annotated 1208 if fun.return_annotation.is_none() { 1209 self.edits.insert( 1210 fun.location.end, 1211 format!(" -> {}", self.printer.print_type(&fun.return_type)), 1212 ); 1213 } 1214 } 1215 1216 fn visit_typed_expr_fn( 1217 &mut self, 1218 location: &'ast SrcSpan, 1219 type_: &'ast Arc<Type>, 1220 kind: &'ast FunctionLiteralKind, 1221 args: &'ast [TypedArg], 1222 body: &'ast Vec1<TypedStatement>, 1223 return_annotation: &'ast Option<ast::TypeAst>, 1224 ) { 1225 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 1226 1227 // If the function doesn't have a head, we can't annotate it 1228 let location = match kind { 1229 // Function captures don't need any type annotations 1230 FunctionLiteralKind::Capture { .. } => return, 1231 FunctionLiteralKind::Anonymous { head } => head, 1232 FunctionLiteralKind::Use { location } => location, 1233 }; 1234 1235 let code_action_range = self.edits.src_span_to_lsp_range(*location); 1236 1237 // Only offer the code action if the cursor is over the expression 1238 if !overlaps(code_action_range, self.params.range) { 1239 return; 1240 } 1241 1242 // Annotate each argument separately 1243 for argument in args.iter() { 1244 // Don't annotate the argument if it's already annotated 1245 if argument.annotation.is_some() { 1246 continue; 1247 } 1248 1249 self.edits.insert( 1250 argument.location.end, 1251 format!(": {}", self.printer.print_type(&argument.type_)), 1252 ); 1253 } 1254 1255 // Annotate the return type if it isn't already annotated, and this is 1256 // an anonymous function. 1257 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) { 1258 let return_type = &type_.return_type().expect("Type must be a function"); 1259 let pretty_type = self.printer.print_type(return_type); 1260 self.edits 1261 .insert(location.end, format!(" -> {pretty_type}")); 1262 } 1263 } 1264} 1265 1266impl<'a> AddAnnotations<'a> { 1267 pub fn new( 1268 module: &'a Module, 1269 line_numbers: &'a LineNumbers, 1270 params: &'a CodeActionParams, 1271 ) -> Self { 1272 Self { 1273 module, 1274 params, 1275 edits: TextEdits::new(line_numbers), 1276 // We need to use the same printer for all the edits because otherwise 1277 // we could get duplicate type variable names. 1278 printer: Printer::new(&module.ast.names), 1279 } 1280 } 1281 1282 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) { 1283 self.visit_typed_module(&self.module.ast); 1284 1285 let uri = &self.params.text_document.uri; 1286 1287 let title = match self.edits.edits.len() { 1288 // We don't offer a code action if there is no action to perform 1289 0 => return, 1290 1 => "Add type annotation", 1291 _ => "Add type annotations", 1292 }; 1293 1294 CodeActionBuilder::new(title) 1295 .kind(CodeActionKind::REFACTOR) 1296 .changes(uri.clone(), self.edits.edits) 1297 .preferred(false) 1298 .push_to(actions); 1299 } 1300} 1301 1302pub struct QualifiedConstructor<'a> { 1303 import: &'a Import<EcoString>, 1304 module_aliased: bool, 1305 used_name: EcoString, 1306 constructor: EcoString, 1307 layer: ast::Layer, 1308} 1309 1310impl QualifiedConstructor<'_> { 1311 fn constructor_import(&self) -> String { 1312 if self.layer.is_value() { 1313 self.constructor.to_string() 1314 } else { 1315 format!("type {}", self.constructor) 1316 } 1317 } 1318} 1319 1320pub struct QualifiedToUnqualifiedImportFirstPass<'a> { 1321 module: &'a Module, 1322 params: &'a CodeActionParams, 1323 line_numbers: &'a LineNumbers, 1324 qualified_constructor: Option<QualifiedConstructor<'a>>, 1325} 1326 1327impl<'a> QualifiedToUnqualifiedImportFirstPass<'a> { 1328 fn new( 1329 module: &'a Module, 1330 params: &'a CodeActionParams, 1331 line_numbers: &'a LineNumbers, 1332 ) -> Self { 1333 Self { 1334 module, 1335 params, 1336 line_numbers, 1337 qualified_constructor: None, 1338 } 1339 } 1340 1341 fn get_module_import( 1342 &self, 1343 module_name: &EcoString, 1344 constructor: &EcoString, 1345 layer: ast::Layer, 1346 ) -> Option<&'a Import<EcoString>> { 1347 let mut matching_import = None; 1348 1349 for def in &self.module.ast.definitions { 1350 if let ast::Definition::Import(import) = def { 1351 let imported = if layer.is_value() { 1352 &import.unqualified_values 1353 } else { 1354 &import.unqualified_types 1355 }; 1356 1357 if import.module != *module_name 1358 && imported.iter().any(|imp| imp.used_name() == constructor) 1359 { 1360 return None; 1361 } 1362 1363 if import.module == *module_name { 1364 matching_import = Some(import); 1365 } 1366 } 1367 } 1368 1369 matching_import 1370 } 1371} 1372 1373impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast> { 1374 fn visit_typed_expr_fn( 1375 &mut self, 1376 location: &'ast SrcSpan, 1377 type_: &'ast Arc<Type>, 1378 kind: &'ast FunctionLiteralKind, 1379 args: &'ast [TypedArg], 1380 body: &'ast Vec1<TypedStatement>, 1381 return_annotation: &'ast Option<ast::TypeAst>, 1382 ) { 1383 for arg in args { 1384 if let Some(annotation) = &arg.annotation { 1385 self.visit_type_ast(annotation); 1386 } 1387 } 1388 if let Some(return_) = return_annotation { 1389 self.visit_type_ast(return_); 1390 } 1391 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 1392 } 1393 1394 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 1395 for arg in &fun.arguments { 1396 if let Some(annotation) = &arg.annotation { 1397 self.visit_type_ast(annotation); 1398 } 1399 } 1400 1401 if let Some(return_annotation) = &fun.return_annotation { 1402 self.visit_type_ast(return_annotation); 1403 } 1404 ast::visit::visit_typed_function(self, fun); 1405 } 1406 1407 fn visit_type_ast_constructor( 1408 &mut self, 1409 location: &'ast SrcSpan, 1410 name_location: &'ast SrcSpan, 1411 module: &'ast Option<(EcoString, SrcSpan)>, 1412 name: &'ast EcoString, 1413 arguments: &'ast Vec<ast::TypeAst>, 1414 ) { 1415 let range = src_span_to_lsp_range(*location, self.line_numbers); 1416 if overlaps(self.params.range, range) { 1417 if let Some((module_alias, _)) = module { 1418 if let Some(import) = self.module.find_node(location.end).and_then(|node| { 1419 if let Located::Annotation { type_, .. } = node { 1420 if let Some((module, _)) = type_.named_type_name() { 1421 return self.get_module_import(&module, name, ast::Layer::Type); 1422 } 1423 } 1424 None 1425 }) { 1426 self.qualified_constructor = Some(QualifiedConstructor { 1427 import, 1428 module_aliased: import.as_name.is_some(), 1429 used_name: module_alias.clone(), 1430 constructor: name.clone(), 1431 layer: ast::Layer::Type, 1432 }); 1433 } 1434 } 1435 } 1436 ast::visit::visit_type_ast_constructor( 1437 self, 1438 location, 1439 name_location, 1440 module, 1441 name, 1442 arguments, 1443 ); 1444 } 1445 1446 fn visit_typed_expr_module_select( 1447 &mut self, 1448 location: &'ast SrcSpan, 1449 field_start: &'ast u32, 1450 type_: &'ast Arc<Type>, 1451 label: &'ast EcoString, 1452 module_name: &'ast EcoString, 1453 module_alias: &'ast EcoString, 1454 constructor: &'ast ModuleValueConstructor, 1455 ) { 1456 // When hovering over a Record Value Constructor, we want to expand the source span to 1457 // include the module name: 1458 // option.Some 1459 // ↑ 1460 // This allows us to offer a code action when hovering over the module name. 1461 let range = src_span_to_lsp_range(*location, self.line_numbers); 1462 if overlaps(self.params.range, range) { 1463 if let ModuleValueConstructor::Record { 1464 name: constructor_name, 1465 .. 1466 } = constructor 1467 { 1468 if let Some(import) = 1469 self.get_module_import(module_name, constructor_name, ast::Layer::Value) 1470 { 1471 self.qualified_constructor = Some(QualifiedConstructor { 1472 import, 1473 module_aliased: import.as_name.is_some(), 1474 used_name: module_alias.clone(), 1475 constructor: constructor_name.clone(), 1476 layer: ast::Layer::Value, 1477 }); 1478 } 1479 } 1480 } 1481 ast::visit::visit_typed_expr_module_select( 1482 self, 1483 location, 1484 field_start, 1485 type_, 1486 label, 1487 module_name, 1488 module_alias, 1489 constructor, 1490 ) 1491 } 1492 1493 fn visit_typed_pattern_constructor( 1494 &mut self, 1495 location: &'ast SrcSpan, 1496 name_location: &'ast SrcSpan, 1497 name: &'ast EcoString, 1498 arguments: &'ast Vec<CallArg<TypedPattern>>, 1499 module: &'ast Option<(EcoString, SrcSpan)>, 1500 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 1501 spread: &'ast Option<SrcSpan>, 1502 type_: &'ast Arc<Type>, 1503 ) { 1504 let range = src_span_to_lsp_range(*location, self.line_numbers); 1505 if overlaps(self.params.range, range) { 1506 if let Some((module_alias, _)) = module { 1507 if let analyse::Inferred::Known(constructor) = constructor { 1508 if let Some(import) = 1509 self.get_module_import(&constructor.module, name, ast::Layer::Value) 1510 { 1511 self.qualified_constructor = Some(QualifiedConstructor { 1512 import, 1513 module_aliased: import.as_name.is_some(), 1514 used_name: module_alias.clone(), 1515 constructor: name.clone(), 1516 layer: ast::Layer::Value, 1517 }); 1518 } 1519 } 1520 } 1521 } 1522 ast::visit::visit_typed_pattern_constructor( 1523 self, 1524 location, 1525 name_location, 1526 name, 1527 arguments, 1528 module, 1529 constructor, 1530 spread, 1531 type_, 1532 ); 1533 } 1534} 1535 1536pub struct QualifiedToUnqualifiedImportSecondPass<'a> { 1537 module: &'a Module, 1538 params: &'a CodeActionParams, 1539 edits: TextEdits<'a>, 1540 qualified_constructor: QualifiedConstructor<'a>, 1541} 1542 1543impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> { 1544 pub fn new( 1545 module: &'a Module, 1546 params: &'a CodeActionParams, 1547 line_numbers: &'a LineNumbers, 1548 qualified_constructor: QualifiedConstructor<'a>, 1549 ) -> Self { 1550 Self { 1551 module, 1552 params, 1553 edits: TextEdits::new(line_numbers), 1554 qualified_constructor, 1555 } 1556 } 1557 1558 pub fn code_actions(mut self) -> Vec<CodeAction> { 1559 self.visit_typed_module(&self.module.ast); 1560 if self.edits.edits.is_empty() { 1561 return vec![]; 1562 } 1563 self.edit_import(); 1564 let mut action = Vec::with_capacity(1); 1565 CodeActionBuilder::new(&format!( 1566 "Unqualify {}.{}", 1567 self.qualified_constructor.used_name, self.qualified_constructor.constructor 1568 )) 1569 .kind(CodeActionKind::REFACTOR) 1570 .changes(self.params.text_document.uri.clone(), self.edits.edits) 1571 .preferred(false) 1572 .push_to(&mut action); 1573 action 1574 } 1575 1576 fn remove_module_qualifier(&mut self, location: SrcSpan) { 1577 self.edits.delete(SrcSpan { 1578 start: location.start, 1579 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus . 1580 }) 1581 } 1582 1583 fn edit_import(&mut self) { 1584 let QualifiedConstructor { 1585 constructor, 1586 layer, 1587 import, 1588 .. 1589 } = &self.qualified_constructor; 1590 let is_imported = if layer.is_value() { 1591 import 1592 .unqualified_values 1593 .iter() 1594 .any(|value| value.used_name() == constructor) 1595 } else { 1596 import 1597 .unqualified_types 1598 .iter() 1599 .any(|type_| type_.used_name() == constructor) 1600 }; 1601 if is_imported { 1602 return; 1603 } 1604 let (insert_pos, new_text) = self.determine_insert_position_and_text(); 1605 let span = SrcSpan::new(insert_pos, insert_pos); 1606 self.edits.replace(span, new_text); 1607 } 1608 1609 fn find_last_char_before_closing_brace(&self) -> Option<(usize, char)> { 1610 let QualifiedConstructor { 1611 import: Import { location, .. }, 1612 .. 1613 } = self.qualified_constructor; 1614 let import_code = self.get_import_code(); 1615 let closing_brace_pos = import_code.rfind('}')?; 1616 1617 let bytes = import_code.as_bytes(); 1618 let mut pos = closing_brace_pos; 1619 while pos > 0 { 1620 pos -= 1; 1621 let c = (*bytes.get(pos)?) as char; 1622 if c.is_whitespace() { 1623 continue; 1624 } 1625 if c == '{' { 1626 break; 1627 } 1628 return Some((location.start as usize + pos, c)); 1629 } 1630 None 1631 } 1632 1633 fn get_import_code(&self) -> &str { 1634 let QualifiedConstructor { 1635 import: Import { location, .. }, 1636 .. 1637 } = self.qualified_constructor; 1638 self.module 1639 .code 1640 .get(location.start as usize..location.end as usize) 1641 .expect("import not found") 1642 } 1643 1644 fn determine_insert_position_and_text(&self) -> (u32, String) { 1645 let QualifiedConstructor { module_aliased, .. } = &self.qualified_constructor; 1646 1647 let name = self.qualified_constructor.constructor_import(); 1648 let import_code = self.get_import_code(); 1649 let has_brace = import_code.contains('}'); 1650 1651 if has_brace { 1652 self.insert_into_braced_import(name) 1653 } else { 1654 self.insert_into_unbraced_import(name, *module_aliased) 1655 } 1656 } 1657 1658 // Handle inserting into an unbraced import 1659 fn insert_into_unbraced_import(&self, name: String, module_aliased: bool) -> (u32, String) { 1660 let QualifiedConstructor { 1661 import: Import { location, .. }, 1662 .. 1663 } = self.qualified_constructor; 1664 if !module_aliased { 1665 // Case: import module 1666 (location.end, format!(".{{{}}}", name)) 1667 } else { 1668 // Case: import module as alias 1669 let import_code = &self.get_import_code(); 1670 let as_pos = import_code 1671 .find(" as ") 1672 .expect("Expected ' as ' in import statement"); 1673 let before_as_pos = import_code 1674 .get(..as_pos) 1675 .and_then(|s| s.rfind(|c: char| !c.is_whitespace())) 1676 .map(|pos| location.start as usize + pos + 1) 1677 .expect("Expected non-whitespace character before ' as '"); 1678 (before_as_pos as u32, format!(".{{{}}}", name)) 1679 } 1680 } 1681 1682 // Handle inserting into a braced import 1683 fn insert_into_braced_import(&self, name: String) -> (u32, String) { 1684 let QualifiedConstructor { 1685 import: Import { location, .. }, 1686 .. 1687 } = self.qualified_constructor; 1688 if let Some((pos, c)) = self.find_last_char_before_closing_brace() { 1689 // Case: import module.{Existing, } (as alias) 1690 if c == ',' { 1691 (pos as u32 + 1, format!(" {}", name)) 1692 } else { 1693 // Case: import module.{Existing} (as alias) 1694 (pos as u32 + 1, format!(", {}", name)) 1695 } 1696 } else { 1697 // Case: import module.{} (as alias) 1698 let import_code = self.get_import_code(); 1699 let left_brace_pos = import_code 1700 .find('{') 1701 .map(|pos| location.start as usize + pos) 1702 .expect("Expected '{' in import statement"); 1703 (left_brace_pos as u32 + 1, name) 1704 } 1705 } 1706} 1707 1708impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> { 1709 fn visit_typed_expr_fn( 1710 &mut self, 1711 location: &'ast SrcSpan, 1712 type_: &'ast Arc<Type>, 1713 kind: &'ast FunctionLiteralKind, 1714 args: &'ast [TypedArg], 1715 body: &'ast Vec1<TypedStatement>, 1716 return_annotation: &'ast Option<ast::TypeAst>, 1717 ) { 1718 for arg in args { 1719 if let Some(annotation) = &arg.annotation { 1720 self.visit_type_ast(annotation); 1721 } 1722 } 1723 if let Some(return_) = return_annotation { 1724 self.visit_type_ast(return_); 1725 } 1726 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 1727 } 1728 1729 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 1730 for arg in &fun.arguments { 1731 if let Some(annotation) = &arg.annotation { 1732 self.visit_type_ast(annotation); 1733 } 1734 } 1735 1736 if let Some(return_annotation) = &fun.return_annotation { 1737 self.visit_type_ast(return_annotation); 1738 } 1739 ast::visit::visit_typed_function(self, fun); 1740 } 1741 1742 fn visit_type_ast_constructor( 1743 &mut self, 1744 location: &'ast SrcSpan, 1745 name_location: &'ast SrcSpan, 1746 module: &'ast Option<(EcoString, SrcSpan)>, 1747 name: &'ast EcoString, 1748 arguments: &'ast Vec<ast::TypeAst>, 1749 ) { 1750 if let Some((module_name, _)) = module { 1751 let QualifiedConstructor { 1752 used_name, 1753 constructor, 1754 layer, 1755 .. 1756 } = &self.qualified_constructor; 1757 1758 if !layer.is_value() && used_name == module_name && name == constructor { 1759 self.remove_module_qualifier(*location); 1760 } 1761 } 1762 ast::visit::visit_type_ast_constructor( 1763 self, 1764 location, 1765 name_location, 1766 module, 1767 name, 1768 arguments, 1769 ); 1770 } 1771 1772 fn visit_typed_expr_module_select( 1773 &mut self, 1774 location: &'ast SrcSpan, 1775 field_start: &'ast u32, 1776 type_: &'ast Arc<Type>, 1777 label: &'ast EcoString, 1778 module_name: &'ast EcoString, 1779 module_alias: &'ast EcoString, 1780 constructor: &'ast ModuleValueConstructor, 1781 ) { 1782 if let ModuleValueConstructor::Record { name, .. } = constructor { 1783 let QualifiedConstructor { 1784 used_name, 1785 constructor, 1786 layer, 1787 .. 1788 } = &self.qualified_constructor; 1789 1790 if layer.is_value() && used_name == module_alias && name == constructor { 1791 self.remove_module_qualifier(*location); 1792 } 1793 } 1794 ast::visit::visit_typed_expr_module_select( 1795 self, 1796 location, 1797 field_start, 1798 type_, 1799 label, 1800 module_name, 1801 module_alias, 1802 constructor, 1803 ) 1804 } 1805 1806 fn visit_typed_pattern_constructor( 1807 &mut self, 1808 location: &'ast SrcSpan, 1809 name_location: &'ast SrcSpan, 1810 name: &'ast EcoString, 1811 arguments: &'ast Vec<CallArg<TypedPattern>>, 1812 module: &'ast Option<(EcoString, SrcSpan)>, 1813 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 1814 spread: &'ast Option<SrcSpan>, 1815 type_: &'ast Arc<Type>, 1816 ) { 1817 if let Some((module_alias, _)) = module { 1818 if let analyse::Inferred::Known(_) = constructor { 1819 let QualifiedConstructor { 1820 used_name, 1821 constructor, 1822 layer, 1823 .. 1824 } = &self.qualified_constructor; 1825 1826 if layer.is_value() && used_name == module_alias && name == constructor { 1827 self.remove_module_qualifier(*location); 1828 } 1829 } 1830 } 1831 ast::visit::visit_typed_pattern_constructor( 1832 self, 1833 location, 1834 name_location, 1835 name, 1836 arguments, 1837 module, 1838 constructor, 1839 spread, 1840 type_, 1841 ); 1842 } 1843} 1844 1845pub fn code_action_convert_qualified_constructor_to_unqualified( 1846 module: &Module, 1847 line_numbers: &LineNumbers, 1848 params: &CodeActionParams, 1849 actions: &mut Vec<CodeAction>, 1850) { 1851 let mut first_pass = QualifiedToUnqualifiedImportFirstPass::new(module, params, line_numbers); 1852 first_pass.visit_typed_module(&module.ast); 1853 let Some(qualified_constructor) = first_pass.qualified_constructor else { 1854 return; 1855 }; 1856 let second_pass = QualifiedToUnqualifiedImportSecondPass::new( 1857 module, 1858 params, 1859 line_numbers, 1860 qualified_constructor, 1861 ); 1862 let new_actions = second_pass.code_actions(); 1863 actions.extend(new_actions); 1864} 1865 1866struct UnqualifiedConstructor<'a> { 1867 module_name: EcoString, 1868 constructor: &'a ast::UnqualifiedImport, 1869 layer: ast::Layer, 1870} 1871 1872struct UnqualifiedToQualifiedImportFirstPass<'a> { 1873 module: &'a Module, 1874 params: &'a CodeActionParams, 1875 line_numbers: &'a LineNumbers, 1876 unqualified_constructor: Option<UnqualifiedConstructor<'a>>, 1877} 1878 1879impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> { 1880 fn new( 1881 module: &'a Module, 1882 params: &'a CodeActionParams, 1883 line_numbers: &'a LineNumbers, 1884 ) -> Self { 1885 Self { 1886 module, 1887 params, 1888 line_numbers, 1889 unqualified_constructor: None, 1890 } 1891 } 1892 1893 fn get_module_import_from_value_constructor( 1894 &mut self, 1895 module_name: &EcoString, 1896 constructor_name: &EcoString, 1897 ) { 1898 self.unqualified_constructor = 1899 self.module 1900 .ast 1901 .definitions 1902 .iter() 1903 .find_map(|def| match def { 1904 ast::Definition::Import(import) if import.module == *module_name => import 1905 .unqualified_values 1906 .iter() 1907 .find(|value| value.used_name() == constructor_name) 1908 .and_then(|value| { 1909 Some(UnqualifiedConstructor { 1910 constructor: value, 1911 module_name: import.used_name()?, 1912 layer: ast::Layer::Value, 1913 }) 1914 }), 1915 _ => None, 1916 }) 1917 } 1918 1919 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) { 1920 self.unqualified_constructor = 1921 self.module 1922 .ast 1923 .definitions 1924 .iter() 1925 .find_map(|def| match def { 1926 ast::Definition::Import(import) => { 1927 if let Some(ty) = import 1928 .unqualified_types 1929 .iter() 1930 .find(|ty| ty.used_name() == constructor_name) 1931 { 1932 return Some(UnqualifiedConstructor { 1933 constructor: ty, 1934 module_name: import.used_name()?, 1935 layer: ast::Layer::Type, 1936 }); 1937 } 1938 None 1939 } 1940 _ => None, 1941 }) 1942 } 1943} 1944 1945impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> { 1946 fn visit_typed_expr_fn( 1947 &mut self, 1948 location: &'ast SrcSpan, 1949 type_: &'ast Arc<Type>, 1950 kind: &'ast FunctionLiteralKind, 1951 args: &'ast [TypedArg], 1952 body: &'ast Vec1<TypedStatement>, 1953 return_annotation: &'ast Option<ast::TypeAst>, 1954 ) { 1955 for arg in args { 1956 if let Some(annotation) = &arg.annotation { 1957 self.visit_type_ast(annotation); 1958 } 1959 } 1960 if let Some(return_) = return_annotation { 1961 self.visit_type_ast(return_); 1962 } 1963 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 1964 } 1965 1966 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 1967 for arg in &fun.arguments { 1968 if let Some(annotation) = &arg.annotation { 1969 self.visit_type_ast(annotation); 1970 } 1971 } 1972 1973 if let Some(return_annotation) = &fun.return_annotation { 1974 self.visit_type_ast(return_annotation); 1975 } 1976 ast::visit::visit_typed_function(self, fun); 1977 } 1978 fn visit_type_ast_constructor( 1979 &mut self, 1980 location: &'ast SrcSpan, 1981 name_location: &'ast SrcSpan, 1982 module: &'ast Option<(EcoString, SrcSpan)>, 1983 name: &'ast EcoString, 1984 arguments: &'ast Vec<ast::TypeAst>, 1985 ) { 1986 if module.is_none() 1987 && overlaps( 1988 self.params.range, 1989 src_span_to_lsp_range(*location, self.line_numbers), 1990 ) 1991 { 1992 self.get_module_import_from_type_constructor(name); 1993 } 1994 1995 ast::visit::visit_type_ast_constructor( 1996 self, 1997 location, 1998 name_location, 1999 module, 2000 name, 2001 arguments, 2002 ); 2003 } 2004 2005 fn visit_typed_expr_var( 2006 &mut self, 2007 location: &'ast SrcSpan, 2008 constructor: &'ast ValueConstructor, 2009 name: &'ast EcoString, 2010 ) { 2011 let range = src_span_to_lsp_range(*location, self.line_numbers); 2012 if overlaps(self.params.range, range) { 2013 if let Some(module_name) = match &constructor.variant { 2014 type_::ValueConstructorVariant::ModuleConstant { module, .. } 2015 | type_::ValueConstructorVariant::ModuleFn { module, .. } 2016 | type_::ValueConstructorVariant::Record { module, .. } => Some(module), 2017 2018 type_::ValueConstructorVariant::LocalVariable { .. } 2019 | type_::ValueConstructorVariant::LocalConstant { .. } => None, 2020 } { 2021 self.get_module_import_from_value_constructor(module_name, name); 2022 } 2023 } 2024 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2025 } 2026 2027 fn visit_typed_pattern_constructor( 2028 &mut self, 2029 location: &'ast SrcSpan, 2030 name_location: &'ast SrcSpan, 2031 name: &'ast EcoString, 2032 arguments: &'ast Vec<CallArg<TypedPattern>>, 2033 module: &'ast Option<(EcoString, SrcSpan)>, 2034 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 2035 spread: &'ast Option<SrcSpan>, 2036 type_: &'ast Arc<Type>, 2037 ) { 2038 if module.is_none() 2039 && overlaps( 2040 self.params.range, 2041 src_span_to_lsp_range(*location, self.line_numbers), 2042 ) 2043 { 2044 if let analyse::Inferred::Known(constructor) = constructor { 2045 self.get_module_import_from_value_constructor(&constructor.module, name); 2046 } 2047 } 2048 2049 ast::visit::visit_typed_pattern_constructor( 2050 self, 2051 location, 2052 name_location, 2053 name, 2054 arguments, 2055 module, 2056 constructor, 2057 spread, 2058 type_, 2059 ); 2060 } 2061} 2062 2063struct UnqualifiedToQualifiedImportSecondPass<'a> { 2064 module: &'a Module, 2065 params: &'a CodeActionParams, 2066 edits: TextEdits<'a>, 2067 unqualified_constructor: UnqualifiedConstructor<'a>, 2068} 2069 2070impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> { 2071 pub fn new( 2072 module: &'a Module, 2073 params: &'a CodeActionParams, 2074 line_numbers: &'a LineNumbers, 2075 unqualified_constructor: UnqualifiedConstructor<'a>, 2076 ) -> Self { 2077 Self { 2078 module, 2079 params, 2080 edits: TextEdits::new(line_numbers), 2081 unqualified_constructor, 2082 } 2083 } 2084 2085 fn add_module_qualifier(&mut self, location: SrcSpan) { 2086 let src_span = SrcSpan::new( 2087 location.start, 2088 location.start + self.unqualified_constructor.constructor.used_name().len() as u32, 2089 ); 2090 2091 self.edits.replace( 2092 src_span, 2093 format!( 2094 "{}.{}", 2095 self.unqualified_constructor.module_name, 2096 self.unqualified_constructor.constructor.name 2097 ), 2098 ); 2099 } 2100 2101 pub fn code_actions(mut self) -> Vec<CodeAction> { 2102 self.visit_typed_module(&self.module.ast); 2103 if self.edits.edits.is_empty() { 2104 return vec![]; 2105 } 2106 self.edit_import(); 2107 let mut action = Vec::with_capacity(1); 2108 let UnqualifiedConstructor { 2109 module_name, 2110 constructor, 2111 .. 2112 } = self.unqualified_constructor; 2113 CodeActionBuilder::new(&format!( 2114 "Qualify {} as {}.{}", 2115 constructor.used_name(), 2116 module_name, 2117 constructor.name, 2118 )) 2119 .kind(CodeActionKind::REFACTOR) 2120 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2121 .preferred(false) 2122 .push_to(&mut action); 2123 action 2124 } 2125 2126 fn edit_import(&mut self) { 2127 let UnqualifiedConstructor { 2128 constructor: 2129 ast::UnqualifiedImport { 2130 location: constructor_import_span, 2131 .. 2132 }, 2133 .. 2134 } = self.unqualified_constructor; 2135 2136 let mut last_char_pos = constructor_import_span.end as usize; 2137 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2138 last_char_pos += 1; 2139 } 2140 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") { 2141 last_char_pos += 1; 2142 } 2143 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { 2144 last_char_pos += 1; 2145 } 2146 2147 self.edits.delete(SrcSpan::new( 2148 constructor_import_span.start, 2149 last_char_pos as u32, 2150 )); 2151 } 2152} 2153 2154impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> { 2155 fn visit_typed_expr_fn( 2156 &mut self, 2157 location: &'ast SrcSpan, 2158 type_: &'ast Arc<Type>, 2159 kind: &'ast FunctionLiteralKind, 2160 args: &'ast [TypedArg], 2161 body: &'ast Vec1<TypedStatement>, 2162 return_annotation: &'ast Option<ast::TypeAst>, 2163 ) { 2164 for arg in args { 2165 if let Some(annotation) = &arg.annotation { 2166 self.visit_type_ast(annotation); 2167 } 2168 } 2169 if let Some(return_) = return_annotation { 2170 self.visit_type_ast(return_); 2171 } 2172 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 2173 } 2174 2175 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 2176 for arg in &fun.arguments { 2177 if let Some(annotation) = &arg.annotation { 2178 self.visit_type_ast(annotation); 2179 } 2180 } 2181 2182 if let Some(return_annotation) = &fun.return_annotation { 2183 self.visit_type_ast(return_annotation); 2184 } 2185 ast::visit::visit_typed_function(self, fun); 2186 } 2187 2188 fn visit_type_ast_constructor( 2189 &mut self, 2190 location: &'ast SrcSpan, 2191 name_location: &'ast SrcSpan, 2192 module: &'ast Option<(EcoString, SrcSpan)>, 2193 name: &'ast EcoString, 2194 arguments: &'ast Vec<ast::TypeAst>, 2195 ) { 2196 if module.is_none() { 2197 let UnqualifiedConstructor { 2198 constructor, layer, .. 2199 } = &self.unqualified_constructor; 2200 if !layer.is_value() && constructor.used_name() == name { 2201 self.add_module_qualifier(*location); 2202 } 2203 } 2204 ast::visit::visit_type_ast_constructor( 2205 self, 2206 location, 2207 name_location, 2208 module, 2209 name, 2210 arguments, 2211 ); 2212 } 2213 2214 fn visit_typed_expr_var( 2215 &mut self, 2216 location: &'ast SrcSpan, 2217 constructor: &'ast ValueConstructor, 2218 name: &'ast EcoString, 2219 ) { 2220 let UnqualifiedConstructor { 2221 constructor: wanted_constructor, 2222 layer, 2223 .. 2224 } = &self.unqualified_constructor; 2225 2226 if layer.is_value() 2227 && wanted_constructor.used_name() == name 2228 && !constructor.is_local_variable() 2229 { 2230 self.add_module_qualifier(*location); 2231 } 2232 ast::visit::visit_typed_expr_var(self, location, constructor, name); 2233 } 2234 2235 fn visit_typed_pattern_constructor( 2236 &mut self, 2237 location: &'ast SrcSpan, 2238 name_location: &'ast SrcSpan, 2239 name: &'ast EcoString, 2240 arguments: &'ast Vec<CallArg<TypedPattern>>, 2241 module: &'ast Option<(EcoString, SrcSpan)>, 2242 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 2243 spread: &'ast Option<SrcSpan>, 2244 type_: &'ast Arc<Type>, 2245 ) { 2246 if module.is_none() { 2247 let UnqualifiedConstructor { 2248 constructor: wanted_constructor, 2249 layer, 2250 .. 2251 } = &self.unqualified_constructor; 2252 if layer.is_value() && wanted_constructor.used_name() == name { 2253 self.add_module_qualifier(*location); 2254 } 2255 } 2256 ast::visit::visit_typed_pattern_constructor( 2257 self, 2258 location, 2259 name_location, 2260 name, 2261 arguments, 2262 module, 2263 constructor, 2264 spread, 2265 type_, 2266 ); 2267 } 2268} 2269 2270pub fn code_action_convert_unqualified_constructor_to_qualified( 2271 module: &Module, 2272 line_numbers: &LineNumbers, 2273 params: &CodeActionParams, 2274 actions: &mut Vec<CodeAction>, 2275) { 2276 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers); 2277 first_pass.visit_typed_module(&module.ast); 2278 let Some(unqualified_constructor) = first_pass.unqualified_constructor else { 2279 return; 2280 }; 2281 let second_pass = UnqualifiedToQualifiedImportSecondPass::new( 2282 module, 2283 params, 2284 line_numbers, 2285 unqualified_constructor, 2286 ); 2287 let new_actions = second_pass.code_actions(); 2288 actions.extend(new_actions); 2289} 2290 2291/// Builder for code action to apply the convert from use action, turning a use 2292/// expression into a regular function call. 2293/// 2294pub struct ConvertFromUse<'a> { 2295 module: &'a Module, 2296 params: &'a CodeActionParams, 2297 edits: TextEdits<'a>, 2298 selected_use: Option<&'a TypedUse>, 2299} 2300 2301impl<'a> ConvertFromUse<'a> { 2302 pub fn new( 2303 module: &'a Module, 2304 line_numbers: &'a LineNumbers, 2305 params: &'a CodeActionParams, 2306 ) -> Self { 2307 Self { 2308 module, 2309 params, 2310 edits: TextEdits::new(line_numbers), 2311 selected_use: None, 2312 } 2313 } 2314 2315 pub fn code_actions(mut self) -> Vec<CodeAction> { 2316 self.visit_typed_module(&self.module.ast); 2317 2318 let Some(use_) = self.selected_use else { 2319 return vec![]; 2320 }; 2321 2322 let TypedExpr::Call { args, fun, .. } = use_.call.as_ref() else { 2323 return vec![]; 2324 }; 2325 2326 // If the use callback we're desugaring is using labels, that means we 2327 // have to add the last argument's label when writing the callback; 2328 // otherwise, it would result in invalid code. 2329 // 2330 // use acc, item <- list.fold(over: list, from: 1) 2331 // todo 2332 // 2333 // Needs to be rewritten as: 2334 // 2335 // list.fold(over: list, from: 1, with: fn(acc, item) { ... }) 2336 // ^^^^^ We cannot forget to add this label back! 2337 // 2338 let callback_label = if args.iter().any(|arg| arg.label.is_some()) { 2339 fun.field_map() 2340 .and_then(|field_map| field_map.missing_labels(args).last().cloned()) 2341 .map(|label| eco_format!("{label}: ")) 2342 .unwrap_or(EcoString::from("")) 2343 } else { 2344 EcoString::from("") 2345 }; 2346 2347 // The use callback is not necessarily the last argument. If you have 2348 // the following function: `wibble(a a, b b) { todo }` 2349 // And use it like this: `use <- wibble(b: 1)`, the first argument `a` 2350 // is going to be the use callback, not the last one! 2351 let use_callback = args.iter().find(|arg| arg.is_use_implicit_callback()); 2352 let Some(CallArg { 2353 implicit: Some(ImplicitCallArgOrigin::Use), 2354 value: TypedExpr::Fn { body, type_, .. }, 2355 .. 2356 }) = use_callback 2357 else { 2358 return vec![]; 2359 }; 2360 2361 // If there's arguments on the left hand side of the function we extract 2362 // those so we can paste them back as the anonymous function arguments. 2363 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) { 2364 let assignments_range = 2365 use_.assignments_location.start as usize..use_.assignments_location.end as usize; 2366 self.module 2367 .code 2368 .get(assignments_range) 2369 .expect("use assignments") 2370 } else { 2371 "" 2372 }; 2373 2374 // We first delete everything on the left hand side of use and the use 2375 // arrow. 2376 self.edits.delete(SrcSpan { 2377 start: use_.location.start, 2378 end: use_.right_hand_side_location.start, 2379 }); 2380 2381 let use_line_end = use_.right_hand_side_location.end; 2382 let use_rhs_function_has_some_explicit_args = args 2383 .iter() 2384 .filter(|arg| !arg.is_use_implicit_callback()) 2385 .peekable() 2386 .peek() 2387 .is_some(); 2388 2389 let use_rhs_function_ends_with_closed_parentheses = self 2390 .module 2391 .code 2392 .get(use_line_end as usize - 1..use_line_end as usize) 2393 == Some(")"); 2394 2395 let last_explicit_arg = args.iter().filter(|arg| !arg.is_implicit()).next_back(); 2396 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end); 2397 2398 // This is the piece of code between the end of the last argument and 2399 // the end of the use_expression: 2400 // 2401 // use <- wibble(a, b, ) 2402 // ^^^^^ This piece right here, from `,` included 2403 // up to `)` excluded. 2404 // 2405 let text_after_last_argument = self 2406 .module 2407 .code 2408 .get(last_arg_end as usize..use_line_end as usize - 1); 2409 let use_rhs_has_comma_after_last_argument = 2410 text_after_last_argument.is_some_and(|code| code.contains(',')); 2411 let needs_space_before_callback = 2412 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' ')); 2413 2414 if use_rhs_function_ends_with_closed_parentheses { 2415 // If the function on the right hand side of use ends with a closed 2416 // parentheses then we have to remove it and add it later at the end 2417 // of the anonymous function we're inserting. 2418 // 2419 // use <- wibble() 2420 // ^ To add the fn() we need to first remove this 2421 // 2422 // So here we write over the last closed parentheses to remove it. 2423 let callback_start = format!("{callback_label}fn({assignments}) {{"); 2424 self.edits.replace( 2425 SrcSpan { 2426 start: use_line_end - 1, 2427 end: use_line_end, 2428 }, 2429 // If the function on the rhs of use has other orguments besides 2430 // the implicit fn expression then we need to put a comma after 2431 // the last argument. 2432 if use_rhs_function_has_some_explicit_args && !use_rhs_has_comma_after_last_argument 2433 { 2434 format!(", {callback_start}") 2435 } else if needs_space_before_callback { 2436 format!(" {callback_start}") 2437 } else { 2438 callback_start.to_string() 2439 }, 2440 ) 2441 } else { 2442 // On the other hand, if the function on the right hand side doesn't 2443 // end with a closed parenthese then we have to manually add it. 2444 // 2445 // use <- wibble 2446 // ^ No parentheses 2447 // 2448 self.edits 2449 .insert(use_line_end, format!("(fn({}) {{", assignments)) 2450 }; 2451 2452 // Then we have to increase indentation for all the lines of the use 2453 // body. 2454 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location()); 2455 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location()); 2456 2457 for line in first_fn_expression_range.start.line..=use_body_range.end.line { 2458 self.edits.edits.push(TextEdit { 2459 range: Range { 2460 start: Position { line, character: 0 }, 2461 end: Position { line, character: 0 }, 2462 }, 2463 new_text: " ".to_string(), 2464 }) 2465 } 2466 2467 let final_line_indentation = " ".repeat(use_body_range.start.character as usize); 2468 self.edits.insert( 2469 use_.call.location().end, 2470 format!("\n{final_line_indentation}}})"), 2471 ); 2472 2473 let mut action = Vec::with_capacity(1); 2474 CodeActionBuilder::new("Convert from `use`") 2475 .kind(CodeActionKind::REFACTOR_REWRITE) 2476 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2477 .preferred(false) 2478 .push_to(&mut action); 2479 action 2480 } 2481} 2482 2483impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> { 2484 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 2485 let use_range = self.edits.src_span_to_lsp_range(use_.location); 2486 2487 // If the use expression is using patterns that are not just variable 2488 // assignments then we can't automatically rewrite it as it would result 2489 // in a syntax error as we can't pattern match in an anonymous function 2490 // head. 2491 // At the same time we can't safely add bindings inside the anonymous 2492 // function body by picking placeholder names as we'd risk shadowing 2493 // variables coming from the outer scope. 2494 // So we just skip those use expressions we can't safely rewrite! 2495 if within(self.params.range, use_range) 2496 && use_ 2497 .assignments 2498 .iter() 2499 .all(|assignment| assignment.pattern.is_variable()) 2500 { 2501 self.selected_use = Some(use_); 2502 } 2503 2504 // We still want to visit the use expression so that we always end up 2505 // picking the innermost, most relevant use under the cursor. 2506 self.visit_typed_expr(&use_.call); 2507 } 2508} 2509 2510/// Builder for code action to apply the convert to use action. 2511/// 2512pub struct ConvertToUse<'a> { 2513 module: &'a Module, 2514 params: &'a CodeActionParams, 2515 edits: TextEdits<'a>, 2516 selected_call: Option<CallLocations>, 2517} 2518 2519/// All the locations we'll need to transform a function call into a use 2520/// expression. 2521/// 2522struct CallLocations { 2523 call_span: SrcSpan, 2524 called_function_span: SrcSpan, 2525 callback_args_span: Option<SrcSpan>, 2526 arg_before_callback_span: Option<SrcSpan>, 2527 callback_body_span: SrcSpan, 2528} 2529 2530impl<'a> ConvertToUse<'a> { 2531 pub fn new( 2532 module: &'a Module, 2533 line_numbers: &'a LineNumbers, 2534 params: &'a CodeActionParams, 2535 ) -> Self { 2536 Self { 2537 module, 2538 params, 2539 edits: TextEdits::new(line_numbers), 2540 selected_call: None, 2541 } 2542 } 2543 2544 pub fn code_actions(mut self) -> Vec<CodeAction> { 2545 self.visit_typed_module(&self.module.ast); 2546 2547 let Some(CallLocations { 2548 call_span, 2549 called_function_span, 2550 callback_args_span, 2551 arg_before_callback_span, 2552 callback_body_span, 2553 }) = self.selected_call 2554 else { 2555 return vec![]; 2556 }; 2557 2558 // This is the nesting level of the `use` keyword we've inserted, we 2559 // want to move the entire body of the anonymous function to this level. 2560 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character; 2561 let indentation = " ".repeat(use_nesting_level as usize); 2562 2563 // First we move the callback arguments to the left hand side of the 2564 // call and add the `use` keyword. 2565 let left_hand_side_text = if let Some(args_location) = callback_args_span { 2566 let args_start = args_location.start as usize; 2567 let args_end = args_location.end as usize; 2568 let args_text = self.module.code.get(args_start..args_end).expect("fn args"); 2569 format!("use {args_text} <- ") 2570 } else { 2571 "use <- ".into() 2572 }; 2573 2574 self.edits.insert(call_span.start, left_hand_side_text); 2575 2576 match arg_before_callback_span { 2577 // If the function call has no other arguments besides the callback then 2578 // we just have to remove the `fn(...) {` part. 2579 // 2580 // wibble(fn(...) { ... }) 2581 // ^^^^^^^^^^ This goes from the end of the called function 2582 // To the start of the first thing in the anonymous 2583 // function's body. 2584 // 2585 None => self.edits.replace( 2586 SrcSpan::new(called_function_span.end, callback_body_span.start), 2587 format!("\n{indentation}"), 2588 ), 2589 // If it has other arguments we'll have to remove those and add a closed 2590 // parentheses too: 2591 // 2592 // wibble(1, 2, fn(...) { ... }) 2593 // ^^^^^^^^^^^ We have to replace this with a `)`, it 2594 // goes from the end of the second-to-last 2595 // argument to the start of the first thing 2596 // in the anonymous function's body. 2597 // 2598 Some(arg_before_callback) => self.edits.replace( 2599 SrcSpan::new(arg_before_callback.end, callback_body_span.start), 2600 format!(")\n{indentation}"), 2601 ), 2602 }; 2603 2604 // Then we have to remove two spaces of indentation from each line of 2605 // the callback function's body. 2606 let body_range = self.edits.src_span_to_lsp_range(callback_body_span); 2607 for line in body_range.start.line + 1..=body_range.end.line { 2608 self.edits.delete_range(Range::new( 2609 Position { line, character: 0 }, 2610 Position { line, character: 2 }, 2611 )) 2612 } 2613 2614 // Then we have to remove the anonymous fn closing `}` and the call's 2615 // closing `)`. 2616 self.edits 2617 .delete(SrcSpan::new(callback_body_span.end, call_span.end)); 2618 2619 let mut action = Vec::with_capacity(1); 2620 CodeActionBuilder::new("Convert to `use`") 2621 .kind(CodeActionKind::REFACTOR_REWRITE) 2622 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2623 .preferred(false) 2624 .push_to(&mut action); 2625 action 2626 } 2627} 2628 2629impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> { 2630 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 2631 // The cursor has to be inside the last statement of the function to 2632 // offer the code action. 2633 let last_statement_range = self.edits.src_span_to_lsp_range(fun.body.last().location()); 2634 if within(self.params.range, last_statement_range) { 2635 if let Some(call_data) = turn_statement_into_use(fun.body.last()) { 2636 self.selected_call = Some(call_data); 2637 } 2638 } 2639 2640 ast::visit::visit_typed_function(self, fun) 2641 } 2642 2643 fn visit_typed_expr_fn( 2644 &mut self, 2645 location: &'ast SrcSpan, 2646 type_: &'ast Arc<Type>, 2647 kind: &'ast FunctionLiteralKind, 2648 args: &'ast [TypedArg], 2649 body: &'ast Vec1<TypedStatement>, 2650 return_annotation: &'ast Option<ast::TypeAst>, 2651 ) { 2652 // The cursor has to be inside the last statement of the body to 2653 // offer the code action. 2654 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location()); 2655 if within(self.params.range, last_statement_range) { 2656 if let Some(call_data) = turn_statement_into_use(body.last()) { 2657 self.selected_call = Some(call_data); 2658 } 2659 } 2660 2661 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 2662 } 2663 2664 fn visit_typed_expr_block( 2665 &mut self, 2666 location: &'ast SrcSpan, 2667 statements: &'ast [TypedStatement], 2668 ) { 2669 let Some(last_statement) = statements.last() else { 2670 return; 2671 }; 2672 2673 // The cursor has to be inside the last statement of the block to offer 2674 // the code action. 2675 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location()); 2676 if within(self.params.range, statement_range) { 2677 // Only the last statement of a block can be turned into a use! 2678 if let Some(selected_call) = turn_statement_into_use(last_statement) { 2679 self.selected_call = Some(selected_call) 2680 } 2681 } 2682 2683 ast::visit::visit_typed_expr_block(self, location, statements); 2684 } 2685} 2686 2687fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> { 2688 match statement { 2689 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None, 2690 ast::Statement::Expression(expression) => turn_expression_into_use(expression), 2691 } 2692} 2693 2694fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> { 2695 let TypedExpr::Call { 2696 args, 2697 location: call_span, 2698 fun: called_function, 2699 .. 2700 } = expr 2701 else { 2702 return None; 2703 }; 2704 2705 // The function arguments in the ast are reordered using function's field map. 2706 // This means that in the `args` array they might not appear in the same order 2707 // in which they are written by the user. Since the rest of the code relies 2708 // on their order in the written code we first have to sort them by their 2709 // source position. 2710 let args = args 2711 .iter() 2712 .sorted_by_key(|arg| arg.location.start) 2713 .collect_vec(); 2714 2715 let CallArg { 2716 value: last_arg, 2717 implicit: None, 2718 .. 2719 } = args.last()? 2720 else { 2721 return None; 2722 }; 2723 2724 let TypedExpr::Fn { 2725 args: callback_args, 2726 body, 2727 .. 2728 } = last_arg 2729 else { 2730 return None; 2731 }; 2732 2733 let callback_args_span = match (callback_args.first(), callback_args.last()) { 2734 (Some(first), Some(last)) => Some(first.location.merge(&last.location)), 2735 _ => None, 2736 }; 2737 2738 let arg_before_callback_span = if args.len() >= 2 { 2739 args.get(args.len() - 2).map(|call_arg| call_arg.location) 2740 } else { 2741 None 2742 }; 2743 2744 let callback_body_span = body.first().location().merge(&body.last().last_location()); 2745 2746 Some(CallLocations { 2747 call_span: *call_span, 2748 called_function_span: called_function.location(), 2749 callback_args_span, 2750 arg_before_callback_span, 2751 callback_body_span, 2752 }) 2753} 2754 2755/// Builder for code action to extract expression into a variable. 2756/// The action will wrap the expression in a block if needed in the appropriate scope. 2757/// 2758/// For using the code action on the following selection: 2759/// 2760/// ```gleam 2761/// fn void() { 2762/// case result { 2763/// Ok(value) -> 2 * value + 1 2764/// // ^^^^^^^^^ 2765/// Error(_) -> panic 2766/// } 2767/// } 2768/// ``` 2769/// 2770/// Will result: 2771/// 2772/// ```gleam 2773/// fn void() { 2774/// case result { 2775/// Ok(value) -> { 2776/// let int = 2 * value 2777/// int + 1 2778/// } 2779/// Error(_) -> panic 2780/// } 2781/// } 2782/// ``` 2783pub struct ExtractVariable<'a> { 2784 module: &'a Module, 2785 params: &'a CodeActionParams, 2786 edits: TextEdits<'a>, 2787 position: Option<ExtractVariablePosition>, 2788 selected_expression: Option<(SrcSpan, Arc<Type>)>, 2789 statement_before_selected_expression: Option<SrcSpan>, 2790 latest_statement: Option<SrcSpan>, 2791 to_be_wrapped: bool, 2792} 2793 2794/// The Position of the selected code 2795#[derive(PartialEq, Eq, Copy, Clone, Debug)] 2796enum ExtractVariablePosition { 2797 InsideCaptureBody, 2798 /// Full statements (i.e. assignments, `use`s, and simple expressions). 2799 TopLevelStatement, 2800 /// The call on the right hand side of a pipe `|>`. 2801 PipelineCall, 2802 /// The right hand side of the `->` in a case expression. 2803 InsideCaseClause, 2804 // A call argument. This can also be a `use` callback. 2805 CallArg, 2806} 2807 2808impl<'a> ExtractVariable<'a> { 2809 pub fn new( 2810 module: &'a Module, 2811 line_numbers: &'a LineNumbers, 2812 params: &'a CodeActionParams, 2813 ) -> Self { 2814 Self { 2815 module, 2816 params, 2817 edits: TextEdits::new(line_numbers), 2818 position: None, 2819 selected_expression: None, 2820 latest_statement: None, 2821 statement_before_selected_expression: None, 2822 to_be_wrapped: false, 2823 } 2824 } 2825 2826 pub fn code_actions(mut self) -> Vec<CodeAction> { 2827 self.visit_typed_module(&self.module.ast); 2828 2829 let (Some((expression_span, expression_type)), Some(insert_location)) = ( 2830 self.selected_expression, 2831 self.statement_before_selected_expression, 2832 ) else { 2833 return vec![]; 2834 }; 2835 2836 let mut name_generator = NameGenerator::new(); 2837 let variable_name = name_generator.generate_name_from_type(&expression_type); 2838 2839 let content = self 2840 .module 2841 .code 2842 .get(expression_span.start as usize..expression_span.end as usize) 2843 .expect("selected expression"); 2844 2845 let range = self.edits.src_span_to_lsp_range(insert_location); 2846 2847 let indent_size = 2848 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 2849 2850 let mut indent = " ".repeat(indent_size); 2851 2852 // We insert the variable declaration 2853 // Wrap in a block if needed 2854 let mut insertion = format!("let {variable_name} = {content}"); 2855 if self.to_be_wrapped { 2856 let line_end = self 2857 .edits 2858 .line_numbers 2859 .line_starts 2860 .get((range.end.line + 1) as usize) 2861 .expect("Line number should be valid"); 2862 2863 self.edits.insert(*line_end, format!("{indent}}}\n")); 2864 indent += " "; 2865 insertion = format!("{{\n{indent}{insertion}"); 2866 }; 2867 self.edits 2868 .insert(insert_location.start, insertion + &format!("\n{indent}")); 2869 self.edits 2870 .replace(expression_span, String::from(variable_name)); 2871 2872 let mut action = Vec::with_capacity(1); 2873 CodeActionBuilder::new("Extract variable") 2874 .kind(CodeActionKind::REFACTOR_EXTRACT) 2875 .changes(self.params.text_document.uri.clone(), self.edits.edits) 2876 .preferred(false) 2877 .push_to(&mut action); 2878 action 2879 } 2880 2881 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F) 2882 where 2883 F: Fn(&mut Self), 2884 { 2885 self.at_optional_position(Some(position), fun); 2886 } 2887 2888 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F) 2889 where 2890 F: Fn(&mut Self), 2891 { 2892 let previous_statement = self.latest_statement; 2893 let previous_position = self.position; 2894 self.position = position; 2895 fun(self); 2896 self.position = previous_position; 2897 self.latest_statement = previous_statement; 2898 } 2899} 2900 2901impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> { 2902 fn visit_typed_statement(&mut self, stmt: &'ast TypedStatement) { 2903 let range = self.edits.src_span_to_lsp_range(stmt.location()); 2904 if !within(self.params.range, range) { 2905 self.latest_statement = Some(stmt.location()); 2906 ast::visit::visit_typed_statement(self, stmt); 2907 return; 2908 } 2909 2910 match self.position { 2911 // A capture body is comprised of just a single expression statement 2912 // that is inserted by the compiler, we don't really want to put 2913 // anything before that; so in this case we avoid tracking it. 2914 Some(ExtractVariablePosition::InsideCaptureBody) => {} 2915 Some(ExtractVariablePosition::PipelineCall) => { 2916 // Insert above the pipeline start 2917 self.latest_statement = Some(stmt.location()); 2918 } 2919 _ => { 2920 // Insert below the previous statement 2921 self.latest_statement = Some(stmt.location()); 2922 self.statement_before_selected_expression = self.latest_statement; 2923 } 2924 } 2925 2926 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 2927 ast::visit::visit_typed_statement(this, stmt); 2928 }); 2929 } 2930 2931 fn visit_typed_expr_pipeline( 2932 &mut self, 2933 location: &'ast SrcSpan, 2934 first_value: &'ast TypedPipelineAssignment, 2935 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 2936 finally: &'ast TypedExpr, 2937 finally_kind: &'ast PipelineAssignmentKind, 2938 ) { 2939 let expr_range = self.edits.src_span_to_lsp_range(*location); 2940 if !within(self.params.range, expr_range) { 2941 ast::visit::visit_typed_expr_pipeline( 2942 self, 2943 location, 2944 first_value, 2945 assignments, 2946 finally, 2947 finally_kind, 2948 ); 2949 return; 2950 }; 2951 2952 // When visiting the assignments or the final pipeline call we want to 2953 // keep track of out position so that we can avoid extracting those. 2954 let all_assignments = 2955 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment)); 2956 2957 for assignment in all_assignments { 2958 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 2959 this.visit_typed_pipeline_assignment(assignment); 2960 }); 2961 } 2962 2963 self.at_position(ExtractVariablePosition::PipelineCall, |this| { 2964 this.visit_typed_expr(finally) 2965 }); 2966 } 2967 2968 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 2969 let expr_location = expr.location(); 2970 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 2971 if !within(self.params.range, expr_range) { 2972 ast::visit::visit_typed_expr(self, expr); 2973 return; 2974 } 2975 2976 // If the expression is a top level statement we don't want to extract 2977 // it into a variable. It would mean we would turn this: 2978 // 2979 // ```gleam 2980 // pub fn main() { 2981 // let wibble = 1 2982 // // ^ cursor here 2983 // } 2984 // 2985 // // into: 2986 // 2987 // pub fn main() { 2988 // let int = 1 2989 // let wibble = int 2990 // } 2991 // ``` 2992 // 2993 // Not all that useful! 2994 // 2995 match self.position { 2996 Some( 2997 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall, 2998 ) => { 2999 self.at_optional_position(None, |this| { 3000 ast::visit::visit_typed_expr(this, expr); 3001 }); 3002 return; 3003 } 3004 Some( 3005 ExtractVariablePosition::InsideCaptureBody 3006 | ExtractVariablePosition::InsideCaseClause 3007 | ExtractVariablePosition::CallArg, 3008 ) 3009 | None => { 3010 match expr { 3011 // We don't extract variables, they're already good. 3012 // And we don't extract module selects by themselves but always 3013 // want to consider those as part of a function call. 3014 TypedExpr::Var { .. } | TypedExpr::ModuleSelect { .. } => (), 3015 _ => { 3016 self.selected_expression = Some((expr_location, expr.type_())); 3017 3018 if !matches!(self.position, Some(ExtractVariablePosition::CallArg)) { 3019 self.statement_before_selected_expression = self.latest_statement; 3020 } 3021 } 3022 } 3023 } 3024 } 3025 3026 match expr { 3027 // Expressions that don't make sense to extract 3028 TypedExpr::Panic { .. } 3029 | TypedExpr::Echo { .. } 3030 | TypedExpr::Block { .. } 3031 | TypedExpr::ModuleSelect { .. } 3032 | TypedExpr::Invalid { .. } 3033 | TypedExpr::Var { .. } => (), 3034 3035 TypedExpr::Int { location, .. } 3036 | TypedExpr::Float { location, .. } 3037 | TypedExpr::String { location, .. } 3038 | TypedExpr::Pipeline { location, .. } 3039 | TypedExpr::Fn { location, .. } 3040 | TypedExpr::Todo { location, .. } 3041 | TypedExpr::List { location, .. } 3042 | TypedExpr::Call { location, .. } 3043 | TypedExpr::BinOp { location, .. } 3044 | TypedExpr::Case { location, .. } 3045 | TypedExpr::RecordAccess { location, .. } 3046 | TypedExpr::Tuple { location, .. } 3047 | TypedExpr::TupleIndex { location, .. } 3048 | TypedExpr::BitArray { location, .. } 3049 | TypedExpr::RecordUpdate { location, .. } 3050 | TypedExpr::NegateBool { location, .. } 3051 | TypedExpr::NegateInt { location, .. } => { 3052 if let Some(ExtractVariablePosition::CallArg) = self.position { 3053 // Don't update latest statement, we don't want to insert the extracted 3054 // variable inside the parenthesis where the call argument is located. 3055 } else { 3056 self.statement_before_selected_expression = self.latest_statement; 3057 }; 3058 self.selected_expression = Some((*location, expr.type_())); 3059 } 3060 } 3061 3062 ast::visit::visit_typed_expr(self, expr); 3063 } 3064 3065 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 3066 let range = self.edits.src_span_to_lsp_range(use_.call.location()); 3067 if !within(self.params.range, range) { 3068 ast::visit::visit_typed_use(self, use_); 3069 return; 3070 } 3071 3072 // Insert code under the `use` 3073 self.statement_before_selected_expression = Some(use_.call.location()); 3074 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { 3075 ast::visit::visit_typed_use(this, use_); 3076 }); 3077 } 3078 3079 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 3080 let range = self.edits.src_span_to_lsp_range(clause.location()); 3081 if !within(self.params.range, range) { 3082 ast::visit::visit_typed_clause(self, clause); 3083 return; 3084 } 3085 3086 // Insert code after the `->` 3087 self.latest_statement = Some(clause.then.location()); 3088 self.to_be_wrapped = true; 3089 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| { 3090 ast::visit::visit_typed_clause(this, clause); 3091 }); 3092 } 3093 3094 fn visit_typed_expr_block( 3095 &mut self, 3096 location: &'ast SrcSpan, 3097 statements: &'ast [TypedStatement], 3098 ) { 3099 let range = self.edits.src_span_to_lsp_range(*location); 3100 if !within(self.params.range, range) { 3101 ast::visit::visit_typed_expr_block(self, location, statements); 3102 return; 3103 } 3104 3105 // Don't extract block as variable 3106 let mut position = self.position; 3107 if let Some(ExtractVariablePosition::InsideCaseClause) = position { 3108 position = None; 3109 self.to_be_wrapped = false; 3110 } 3111 3112 self.at_optional_position(position, |this| { 3113 ast::visit::visit_typed_expr_block(this, location, statements); 3114 }); 3115 } 3116 3117 fn visit_typed_expr_fn( 3118 &mut self, 3119 location: &'ast SrcSpan, 3120 type_: &'ast Arc<Type>, 3121 kind: &'ast FunctionLiteralKind, 3122 args: &'ast [TypedArg], 3123 body: &'ast Vec1<TypedStatement>, 3124 return_annotation: &'ast Option<ast::TypeAst>, 3125 ) { 3126 let range = self.edits.src_span_to_lsp_range(*location); 3127 if !within(self.params.range, range) { 3128 ast::visit::visit_typed_expr_fn( 3129 self, 3130 location, 3131 type_, 3132 kind, 3133 args, 3134 body, 3135 return_annotation, 3136 ); 3137 return; 3138 } 3139 3140 let position = match kind { 3141 // If a fn is a capture `int.wibble(1, _)` its body will consist of 3142 // just a single expression statement. When visiting we must record 3143 // we're inside a capture body. 3144 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody), 3145 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement), 3146 FunctionLiteralKind::Anonymous { .. } => self.position, 3147 }; 3148 3149 self.at_optional_position(position, |this| { 3150 ast::visit::visit_typed_expr_fn( 3151 this, 3152 location, 3153 type_, 3154 kind, 3155 args, 3156 body, 3157 return_annotation, 3158 ); 3159 }); 3160 } 3161 3162 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { 3163 let range = self.edits.src_span_to_lsp_range(arg.location); 3164 if !within(self.params.range, range) { 3165 ast::visit::visit_typed_call_arg(self, arg); 3166 return; 3167 } 3168 3169 let position = if arg.is_use_implicit_callback() { 3170 Some(ExtractVariablePosition::TopLevelStatement) 3171 } else { 3172 Some(ExtractVariablePosition::CallArg) 3173 }; 3174 3175 self.at_optional_position(position, |this| { 3176 ast::visit::visit_typed_call_arg(this, arg); 3177 }); 3178 } 3179 3180 // We don't want to offer the action if the cursor is over some invalid 3181 // piece of code. 3182 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, _type_: &'ast Arc<Type>) { 3183 let invalid_range = self.edits.src_span_to_lsp_range(*location); 3184 if within(self.params.range, invalid_range) { 3185 self.selected_expression = None; 3186 } 3187 } 3188} 3189 3190/// Builder for code action to convert a literal use into a const. 3191/// 3192/// For using the code action on each of the following lines: 3193/// 3194/// ```gleam 3195/// fn void() { 3196/// let var = [1, 2, 3] 3197/// let res = function("Statement", var) 3198/// } 3199/// ``` 3200/// 3201/// Both value literals will become: 3202/// 3203/// ```gleam 3204/// const var = [1, 2, 3] 3205/// const string = "Statement" 3206/// 3207/// fn void() { 3208/// let res = function(string, var) 3209/// } 3210/// ``` 3211pub struct ExtractConstant<'a> { 3212 module: &'a Module, 3213 params: &'a CodeActionParams, 3214 edits: TextEdits<'a>, 3215 /// The whole selected expression 3216 selected_expression: Option<SrcSpan>, 3217 /// The location of the start of the function containing the expression 3218 container_function_start: Option<u32>, 3219 /// The variant of the extractable expression being extracted (if any) 3220 variant_of_extractable: Option<ExtractableToConstant>, 3221 /// The name of the newly created constant 3222 name_to_use: Option<EcoString>, 3223 /// The right hand side expression of the newly created constant 3224 value_to_use: Option<EcoString>, 3225} 3226 3227/// Used when an expression can be extracted to a constant 3228enum ExtractableToConstant { 3229 /// Used for collections and operator uses. This means that elements 3230 /// inside, are also extractable as constants. 3231 ComposedValue, 3232 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings 3233 /// and type variants (not records). 3234 SingleValue, 3235 /// Used for whole variable assignments. If the right hand side of the 3236 /// expression can be extracted, the whole expression extracted and use the 3237 /// local variable as a constant. 3238 Assignment, 3239} 3240 3241fn can_be_constant( 3242 module: &Module, 3243 expr: &TypedExpr, 3244 module_constants: Option<&HashSet<&EcoString>>, 3245) -> bool { 3246 // We pass the `module_constants` on recursion to not compute them each time 3247 let mc = match module_constants { 3248 None => &module 3249 .ast 3250 .definitions 3251 .iter() 3252 .filter_map(|definition| match definition { 3253 ast::Definition::ModuleConstant(module_constant) => Some(&module_constant.name), 3254 3255 ast::Definition::Function(_) 3256 | ast::Definition::TypeAlias(_) 3257 | ast::Definition::CustomType(_) 3258 | ast::Definition::Import(_) => None, 3259 }) 3260 .collect(), 3261 Some(mc) => mc, 3262 }; 3263 3264 match expr { 3265 // Attempt to extract whole list as long as it's comprised of only literals 3266 TypedExpr::List { elements, tail, .. } => { 3267 elements 3268 .iter() 3269 .all(|element| can_be_constant(module, element, Some(mc))) 3270 && tail.is_none() 3271 } 3272 3273 // Attempt to extract whole bit array as long as it's made up of literals 3274 TypedExpr::BitArray { segments, .. } => { 3275 segments 3276 .iter() 3277 .all(|segment| can_be_constant(module, &segment.value, Some(mc))) 3278 && segments.iter().all(|segment| { 3279 segment.options.iter().all(|option| match option { 3280 ast::BitArrayOption::Size { value, .. } => { 3281 can_be_constant(module, value, Some(mc)) 3282 } 3283 3284 ast::BitArrayOption::Bytes { .. } 3285 | ast::BitArrayOption::Int { .. } 3286 | ast::BitArrayOption::Float { .. } 3287 | ast::BitArrayOption::Bits { .. } 3288 | ast::BitArrayOption::Utf8 { .. } 3289 | ast::BitArrayOption::Utf16 { .. } 3290 | ast::BitArrayOption::Utf32 { .. } 3291 | ast::BitArrayOption::Utf8Codepoint { .. } 3292 | ast::BitArrayOption::Utf16Codepoint { .. } 3293 | ast::BitArrayOption::Utf32Codepoint { .. } 3294 | ast::BitArrayOption::Signed { .. } 3295 | ast::BitArrayOption::Unsigned { .. } 3296 | ast::BitArrayOption::Big { .. } 3297 | ast::BitArrayOption::Little { .. } 3298 | ast::BitArrayOption::Native { .. } 3299 | ast::BitArrayOption::Unit { .. } => true, 3300 }) 3301 }) 3302 } 3303 3304 // Attempt to extract whole tuple as long as it's comprised of only literals 3305 TypedExpr::Tuple { elements, .. } => elements 3306 .iter() 3307 .all(|element| can_be_constant(module, element, Some(mc))), 3308 3309 // Extract literals directly 3310 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true, 3311 3312 // Extract non-record types directly 3313 TypedExpr::Var { 3314 constructor, name, .. 3315 } => { 3316 matches!( 3317 constructor.variant, 3318 type_::ValueConstructorVariant::Record { arity: 0, .. } 3319 ) || mc.contains(name) 3320 } 3321 3322 // Extract record types as long as arguments can be constant 3323 TypedExpr::Call { args, fun, .. } => { 3324 fun.is_record_builder() 3325 && args 3326 .iter() 3327 .all(|arg| can_be_constant(module, &arg.value, module_constants)) 3328 } 3329 3330 // Extract concat binary operation if both sides can be constants 3331 TypedExpr::BinOp { 3332 name, left, right, .. 3333 } => { 3334 matches!(name, ast::BinOp::Concatenate) 3335 && can_be_constant(module, left, Some(mc)) 3336 && can_be_constant(module, right, Some(mc)) 3337 } 3338 3339 TypedExpr::Block { .. } 3340 | TypedExpr::Pipeline { .. } 3341 | TypedExpr::Fn { .. } 3342 | TypedExpr::Case { .. } 3343 | TypedExpr::RecordAccess { .. } 3344 | TypedExpr::ModuleSelect { .. } 3345 | TypedExpr::TupleIndex { .. } 3346 | TypedExpr::Todo { .. } 3347 | TypedExpr::Panic { .. } 3348 | TypedExpr::Echo { .. } 3349 | TypedExpr::RecordUpdate { .. } 3350 | TypedExpr::NegateBool { .. } 3351 | TypedExpr::NegateInt { .. } 3352 | TypedExpr::Invalid { .. } => false, 3353 } 3354} 3355 3356/// Takes the list of already existing constants and functions and creates a 3357/// name that doesn't conflict with them 3358/// 3359fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString { 3360 let mut name_generator = NameGenerator::new(); 3361 let already_taken_names = VariablesNames { 3362 names: module 3363 .ast 3364 .definitions 3365 .iter() 3366 .filter_map(|definition| match definition { 3367 ast::Definition::ModuleConstant(constant) => Some(constant.name.clone()), 3368 ast::Definition::Function(function) => function.name.as_ref().map(|n| n.1.clone()), 3369 3370 ast::Definition::TypeAlias(_) 3371 | ast::Definition::CustomType(_) 3372 | ast::Definition::Import(_) => None, 3373 }) 3374 .collect(), 3375 }; 3376 name_generator.reserve_variable_names(already_taken_names); 3377 3378 name_generator.generate_name_from_type(&expr.type_()) 3379} 3380 3381impl<'a> ExtractConstant<'a> { 3382 pub fn new( 3383 module: &'a Module, 3384 line_numbers: &'a LineNumbers, 3385 params: &'a CodeActionParams, 3386 ) -> Self { 3387 Self { 3388 module, 3389 params, 3390 edits: TextEdits::new(line_numbers), 3391 selected_expression: None, 3392 container_function_start: None, 3393 variant_of_extractable: None, 3394 name_to_use: None, 3395 value_to_use: None, 3396 } 3397 } 3398 3399 pub fn code_actions(mut self) -> Vec<CodeAction> { 3400 self.visit_typed_module(&self.module.ast); 3401 3402 let ( 3403 Some(expr_span), 3404 Some(function_start), 3405 Some(type_of_extractable), 3406 Some(new_const_name), 3407 Some(const_value), 3408 ) = ( 3409 self.selected_expression, 3410 self.container_function_start, 3411 self.variant_of_extractable, 3412 self.name_to_use, 3413 self.value_to_use, 3414 ) 3415 else { 3416 return vec![]; 3417 }; 3418 3419 // We insert the constant declaration 3420 self.edits.insert( 3421 function_start, 3422 format!("const {new_const_name} = {const_value}\n\n"), 3423 ); 3424 3425 // We remove or replace the selected expression 3426 match type_of_extractable { 3427 // The whole expression is deleted for assignments 3428 ExtractableToConstant::Assignment => { 3429 let range = self 3430 .edits 3431 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 3432 3433 let indent_size = 3434 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 3435 3436 let expr_span_with_new_line = SrcSpan { 3437 // We remove leading indentation + 1 to remove the newline with it 3438 start: expr_span.start - (indent_size as u32 + 1), 3439 end: expr_span.end, 3440 }; 3441 self.edits.delete(expr_span_with_new_line); 3442 } 3443 3444 // Only right hand side is replaced for collection or values 3445 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => { 3446 self.edits.replace(expr_span, String::from(new_const_name)); 3447 } 3448 } 3449 3450 let mut action = Vec::with_capacity(1); 3451 CodeActionBuilder::new("Extract constant") 3452 .kind(CodeActionKind::REFACTOR_EXTRACT) 3453 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3454 .preferred(false) 3455 .push_to(&mut action); 3456 action 3457 } 3458} 3459 3460impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> { 3461 /// To get the position of the function containing the value or assignment 3462 /// to extract 3463 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 3464 let fun_location = fun.location; 3465 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { 3466 start: fun_location.start, 3467 end: fun.end_position, 3468 }); 3469 3470 if !within(self.params.range, fun_range) { 3471 return; 3472 } 3473 self.container_function_start = Some(fun.location.start); 3474 3475 ast::visit::visit_typed_function(self, fun); 3476 } 3477 3478 /// To extract the whole assignment 3479 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 3480 let expr_location = assignment.location; 3481 3482 // We only offer this code action for extracting the whole assignment 3483 // between `let` and `=`. 3484 let pattern_location = assignment.pattern.location(); 3485 let location = SrcSpan::new(assignment.location.start, pattern_location.end); 3486 let code_action_range = self.edits.src_span_to_lsp_range(location); 3487 3488 if !within(self.params.range, code_action_range) { 3489 ast::visit::visit_typed_assignment(self, assignment); 3490 return; 3491 } 3492 3493 // Has to be variable because patterns can't be constants. 3494 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None) 3495 { 3496 self.variant_of_extractable = Some(ExtractableToConstant::Assignment); 3497 self.selected_expression = Some(expr_location); 3498 self.name_to_use = match &assignment.pattern { 3499 Pattern::Variable { name, .. } => Some(name.clone()), 3500 _ => None, 3501 }; 3502 self.value_to_use = Some(EcoString::from( 3503 self.module 3504 .code 3505 .get( 3506 (assignment.value.location().start as usize) 3507 ..(assignment.location.end as usize), 3508 ) 3509 .expect("selected expression"), 3510 )); 3511 } 3512 } 3513 3514 /// To extract only the literal 3515 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { 3516 let expr_location = expr.location(); 3517 let expr_range = self.edits.src_span_to_lsp_range(expr_location); 3518 3519 if !within(self.params.range, expr_range) { 3520 ast::visit::visit_typed_expr(self, expr); 3521 return; 3522 } 3523 3524 // Keep going down recursively if: 3525 // - It's no extractable has been found yet (`None`). 3526 // - It's a collection, which may or may not contain a value that can 3527 // be extracted. 3528 // - It's a binary operator, which may or may not operate on 3529 // extractable values. 3530 if matches!( 3531 self.variant_of_extractable, 3532 None | Some(ExtractableToConstant::ComposedValue) 3533 ) && can_be_constant(self.module, expr, None) 3534 { 3535 self.variant_of_extractable = match expr { 3536 TypedExpr::Var { .. } 3537 | TypedExpr::Int { .. } 3538 | TypedExpr::Float { .. } 3539 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue), 3540 3541 TypedExpr::List { .. } 3542 | TypedExpr::Tuple { .. } 3543 | TypedExpr::BitArray { .. } 3544 | TypedExpr::BinOp { .. } 3545 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue), 3546 3547 TypedExpr::Block { .. } 3548 | TypedExpr::Pipeline { .. } 3549 | TypedExpr::Fn { .. } 3550 | TypedExpr::Case { .. } 3551 | TypedExpr::RecordAccess { .. } 3552 | TypedExpr::ModuleSelect { .. } 3553 | TypedExpr::TupleIndex { .. } 3554 | TypedExpr::Todo { .. } 3555 | TypedExpr::Panic { .. } 3556 | TypedExpr::Echo { .. } 3557 | TypedExpr::RecordUpdate { .. } 3558 | TypedExpr::NegateBool { .. } 3559 | TypedExpr::NegateInt { .. } 3560 | TypedExpr::Invalid { .. } => None, 3561 }; 3562 3563 self.selected_expression = Some(expr_location); 3564 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr)); 3565 self.value_to_use = Some(EcoString::from( 3566 self.module 3567 .code 3568 .get((expr_location.start as usize)..(expr_location.end as usize)) 3569 .expect("selected expression"), 3570 )); 3571 } 3572 3573 ast::visit::visit_typed_expr(self, expr); 3574 } 3575} 3576 3577/// Builder for code action to apply the "expand function capture" action. 3578/// 3579pub struct ExpandFunctionCapture<'a> { 3580 module: &'a Module, 3581 params: &'a CodeActionParams, 3582 edits: TextEdits<'a>, 3583 function_capture_data: Option<FunctionCaptureData>, 3584} 3585 3586pub struct FunctionCaptureData { 3587 function_span: SrcSpan, 3588 hole_span: SrcSpan, 3589 hole_type: Arc<Type>, 3590 reserved_names: VariablesNames, 3591} 3592 3593impl<'a> ExpandFunctionCapture<'a> { 3594 pub fn new( 3595 module: &'a Module, 3596 line_numbers: &'a LineNumbers, 3597 params: &'a CodeActionParams, 3598 ) -> Self { 3599 Self { 3600 module, 3601 params, 3602 edits: TextEdits::new(line_numbers), 3603 function_capture_data: None, 3604 } 3605 } 3606 3607 pub fn code_actions(mut self) -> Vec<CodeAction> { 3608 self.visit_typed_module(&self.module.ast); 3609 3610 let Some(FunctionCaptureData { 3611 function_span, 3612 hole_span, 3613 hole_type, 3614 reserved_names, 3615 }) = self.function_capture_data 3616 else { 3617 return vec![]; 3618 }; 3619 3620 let mut name_generator = NameGenerator::new(); 3621 name_generator.reserve_variable_names(reserved_names); 3622 let name = name_generator.generate_name_from_type(&hole_type); 3623 3624 self.edits.replace(hole_span, name.clone().into()); 3625 self.edits.insert(function_span.end, " }".into()); 3626 self.edits 3627 .insert(function_span.start, format!("fn({name}) {{ ")); 3628 3629 let mut action = Vec::with_capacity(1); 3630 CodeActionBuilder::new("Expand function capture") 3631 .kind(CodeActionKind::REFACTOR_REWRITE) 3632 .changes(self.params.text_document.uri.clone(), self.edits.edits) 3633 .preferred(false) 3634 .push_to(&mut action); 3635 action 3636 } 3637} 3638 3639impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> { 3640 fn visit_typed_expr_fn( 3641 &mut self, 3642 location: &'ast SrcSpan, 3643 type_: &'ast Arc<Type>, 3644 kind: &'ast FunctionLiteralKind, 3645 args: &'ast [TypedArg], 3646 body: &'ast Vec1<TypedStatement>, 3647 return_annotation: &'ast Option<ast::TypeAst>, 3648 ) { 3649 let fn_range = self.edits.src_span_to_lsp_range(*location); 3650 if within(self.params.range, fn_range) && kind.is_capture() { 3651 if let [arg] = args { 3652 self.function_capture_data = Some(FunctionCaptureData { 3653 function_span: *location, 3654 hole_span: arg.location, 3655 hole_type: arg.type_.clone(), 3656 reserved_names: VariablesNames::from_statements(body), 3657 }); 3658 } 3659 } 3660 3661 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation) 3662 } 3663} 3664 3665struct VariablesNames { 3666 names: HashSet<EcoString>, 3667} 3668 3669impl VariablesNames { 3670 fn from_statements(statements: &[TypedStatement]) -> Self { 3671 let mut variables = Self { 3672 names: HashSet::new(), 3673 }; 3674 3675 for statement in statements { 3676 variables.visit_typed_statement(statement); 3677 } 3678 variables 3679 } 3680} 3681 3682impl<'ast> ast::visit::Visit<'ast> for VariablesNames { 3683 fn visit_typed_expr_var( 3684 &mut self, 3685 _location: &'ast SrcSpan, 3686 _constructor: &'ast ValueConstructor, 3687 name: &'ast EcoString, 3688 ) { 3689 let _ = self.names.insert(name.clone()); 3690 } 3691} 3692 3693/// Builder for code action to apply the "generate dynamic decoder action. 3694/// 3695pub struct GenerateDynamicDecoder<'a> { 3696 module: &'a Module, 3697 params: &'a CodeActionParams, 3698 edits: TextEdits<'a>, 3699 printer: Printer<'a>, 3700 actions: &'a mut Vec<CodeAction>, 3701} 3702 3703const DECODE_MODULE: &str = "gleam/dynamic/decode"; 3704 3705impl<'a> GenerateDynamicDecoder<'a> { 3706 pub fn new( 3707 module: &'a Module, 3708 line_numbers: &'a LineNumbers, 3709 params: &'a CodeActionParams, 3710 actions: &'a mut Vec<CodeAction>, 3711 ) -> Self { 3712 let printer = Printer::new(&module.ast.names); 3713 Self { 3714 module, 3715 params, 3716 edits: TextEdits::new(line_numbers), 3717 printer, 3718 actions, 3719 } 3720 } 3721 3722 pub fn code_actions(&mut self) { 3723 self.visit_typed_module(&self.module.ast); 3724 } 3725 3726 fn custom_type_decoder_body( 3727 &mut self, 3728 custom_type: &CustomType<Arc<Type>>, 3729 ) -> Option<EcoString> { 3730 // We cannot generate a decoder for an external type with no constructors! 3731 let constructors_size = custom_type.constructors.len(); 3732 let (first, rest) = custom_type.constructors.split_first()?; 3733 let mode = EncodingMode::for_custom_type(custom_type); 3734 3735 // We generate a decoder for a type with a single constructor: it does not 3736 // require pattern matching on a tag as there's no variants to tell apart. 3737 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag { 3738 return self.constructor_decoder(mode, custom_type, first, 0); 3739 } 3740 3741 // Otherwise we need to generate a decoder that has to tell apart different 3742 // variants, depending on the mode we might have to decode a type field or 3743 // plain strings! 3744 let module = self.printer.print_module(DECODE_MODULE); 3745 let discriminant = if mode == EncodingMode::PlainString { 3746 eco_format!("use variant <- {module}.then({module}.string)") 3747 } else { 3748 eco_format!("use variant <- {module}.field(\"type\", {module}.string)") 3749 }; 3750 3751 let mut branches = Vec::with_capacity(constructors_size); 3752 for constructor in iter::once(first).chain(rest) { 3753 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?; 3754 let name = to_snake_case(&constructor.name); 3755 branches.push(eco_format!(r#" "{name}" -> {body}"#)); 3756 } 3757 3758 let cases = branches.join("\n"); 3759 let type_name = &custom_type.name; 3760 Some(eco_format!( 3761 r#"{{ 3762 {discriminant} 3763 case variant {{ 3764{cases} 3765 _ -> {module}.failure(todo as "Zero value for {type_name}", "{type_name}") 3766 }} 3767}}"#, 3768 )) 3769 } 3770 3771 fn constructor_decoder( 3772 &mut self, 3773 mode: EncodingMode, 3774 custom_type: &ast::TypedCustomType, 3775 constructor: &TypedRecordConstructor, 3776 nesting: usize, 3777 ) -> Option<EcoString> { 3778 let decode_module = self.printer.print_module(DECODE_MODULE); 3779 let constructor_name = &constructor.name; 3780 3781 // If the constructor was encoded as a plain string with no additional 3782 // fields it means there's nothing else to decode and we can just 3783 // succeed. 3784 if mode == EncodingMode::PlainString { 3785 return Some(eco_format!("{decode_module}.success({constructor_name})")); 3786 } 3787 3788 // Otherwise we have to decode all the constructor fields to build it. 3789 let mut fields = Vec::with_capacity(constructor.arguments.len()); 3790 for argument in constructor.arguments.iter() { 3791 let (_, name) = argument.label.as_ref()?; 3792 let field = RecordField { 3793 label: RecordLabel::Labeled(name), 3794 type_: &argument.type_, 3795 }; 3796 fields.push(field); 3797 } 3798 3799 let mut decoder_printer = DecoderPrinter::new( 3800 &self.module.ast.names, 3801 custom_type.name.clone(), 3802 self.module.name.clone(), 3803 ); 3804 3805 let decoders = fields 3806 .iter() 3807 .map(|field| decoder_printer.decode_field(field, nesting + 2)) 3808 .join("\n"); 3809 3810 let indent = " ".repeat(nesting); 3811 3812 Some(if decoders.is_empty() { 3813 eco_format!("{decode_module}.success({constructor_name})") 3814 } else { 3815 let field_names = fields 3816 .iter() 3817 .map(|field| format!("{}:", field.label.variable_name())) 3818 .join(", "); 3819 3820 eco_format!( 3821 "{{ 3822{decoders} 3823{indent} {decode_module}.success({constructor_name}({field_names})) 3824{indent}}}", 3825 ) 3826 }) 3827 } 3828} 3829 3830impl<'ast> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast> { 3831 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 3832 let range = self.edits.src_span_to_lsp_range(custom_type.location); 3833 if !overlaps(self.params.range, range) { 3834 return; 3835 } 3836 3837 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name)); 3838 let Some(function_body) = self.custom_type_decoder_body(custom_type) else { 3839 return; 3840 }; 3841 3842 let parameters = match custom_type.parameters.len() { 3843 0 => EcoString::new(), 3844 _ => eco_format!( 3845 "({})", 3846 custom_type 3847 .parameters 3848 .iter() 3849 .map(|(_, name)| name) 3850 .join(", ") 3851 ), 3852 }; 3853 3854 let decoder_type = self.printer.print_type(&Type::Named { 3855 publicity: ast::Publicity::Public, 3856 package: STDLIB_PACKAGE_NAME.into(), 3857 module: DECODE_MODULE.into(), 3858 name: "Decoder".into(), 3859 args: vec![], 3860 inferred_variant: None, 3861 }); 3862 3863 let function = format!( 3864 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}", 3865 type_name = custom_type.name, 3866 ); 3867 3868 self.edits.insert(custom_type.end_position, function); 3869 maybe_import(&mut self.edits, self.module, DECODE_MODULE); 3870 3871 CodeActionBuilder::new("Generate dynamic decoder") 3872 .kind(CodeActionKind::REFACTOR) 3873 .preferred(false) 3874 .changes( 3875 self.params.text_document.uri.clone(), 3876 std::mem::take(&mut self.edits.edits), 3877 ) 3878 .push_to(self.actions); 3879 } 3880} 3881 3882/// If `module_name` is not already imported inside `module`, adds an edit to 3883/// add that import. 3884/// This function also makes sure not to import a module in itself. 3885/// 3886fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) { 3887 if module.ast.names.is_imported(module_name) || module.name == module_name { 3888 return; 3889 } 3890 3891 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers); 3892 let first_is_import = first_import_pos.is_some(); 3893 let import_location = first_import_pos.unwrap_or_default(); 3894 let after_import_newlines = add_newlines_after_import( 3895 import_location, 3896 first_is_import, 3897 edits.line_numbers, 3898 &module.code, 3899 ); 3900 3901 edits.edits.push(get_import_edit( 3902 import_location, 3903 module_name, 3904 &after_import_newlines, 3905 )); 3906} 3907 3908struct DecoderPrinter<'a> { 3909 printer: Printer<'a>, 3910 /// The name of the root type we are printing a decoder for 3911 type_name: EcoString, 3912 /// The module name of the root type we are printing a decoder for 3913 type_module: EcoString, 3914} 3915 3916struct RecordField<'a> { 3917 label: RecordLabel<'a>, 3918 type_: &'a Type, 3919} 3920 3921enum RecordLabel<'a> { 3922 Labeled(&'a str), 3923 Unlabeled(usize), 3924} 3925 3926impl RecordLabel<'_> { 3927 fn field_key(&self) -> EcoString { 3928 match self { 3929 RecordLabel::Labeled(label) => eco_format!("\"{label}\""), 3930 RecordLabel::Unlabeled(index) => { 3931 eco_format!("{index}") 3932 } 3933 } 3934 } 3935 3936 fn variable_name(&self) -> EcoString { 3937 match self { 3938 RecordLabel::Labeled(label) => (*label).into(), 3939 &RecordLabel::Unlabeled(mut index) => { 3940 let mut characters = Vec::new(); 3941 let alphabet_length = 26; 3942 let alphabet_offset = b'a'; 3943 loop { 3944 let alphabet_index = (index % alphabet_length) as u8; 3945 characters.push((alphabet_offset + alphabet_index) as char); 3946 index /= alphabet_length; 3947 3948 if index == 0 { 3949 break; 3950 } 3951 index -= 1; 3952 } 3953 characters.into_iter().rev().collect() 3954 } 3955 } 3956 } 3957} 3958 3959impl<'a> DecoderPrinter<'a> { 3960 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self { 3961 Self { 3962 type_name, 3963 type_module, 3964 printer: Printer::new(names), 3965 } 3966 } 3967 3968 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString { 3969 let module_name = self.printer.print_module(DECODE_MODULE); 3970 if type_.is_bit_array() { 3971 eco_format!("{module_name}.bit_array") 3972 } else if type_.is_bool() { 3973 eco_format!("{module_name}.bool") 3974 } else if type_.is_float() { 3975 eco_format!("{module_name}.float") 3976 } else if type_.is_int() { 3977 eco_format!("{module_name}.int") 3978 } else if type_.is_string() { 3979 eco_format!("{module_name}.string") 3980 } else { 3981 match type_.tuple_types() { 3982 Some(types) => { 3983 let fields = types 3984 .iter() 3985 .enumerate() 3986 .map(|(index, type_)| RecordField { 3987 type_, 3988 label: RecordLabel::Unlabeled(index), 3989 }) 3990 .collect_vec(); 3991 let decoders = fields 3992 .iter() 3993 .map(|field| self.decode_field(field, indent + 2)) 3994 .join("\n"); 3995 let mut field_names = fields.iter().map(|field| field.label.variable_name()); 3996 3997 eco_format!( 3998 "{{ 3999{decoders} 4000 4001{indent} {module_name}.success(#({fields})) 4002{indent}}}", 4003 fields = field_names.join(", "), 4004 indent = " ".repeat(indent) 4005 ) 4006 } 4007 _ => { 4008 let type_information = type_.named_type_information(); 4009 let type_information = 4010 type_information.as_ref().map(|(module, name, arguments)| { 4011 (module.as_str(), name.as_str(), arguments.as_slice()) 4012 }); 4013 4014 match type_information { 4015 Some(("gleam/dynamic", "Dynamic", _)) => { 4016 eco_format!("{module_name}.dynamic") 4017 } 4018 Some(("gleam", "List", [element])) => { 4019 eco_format!("{module_name}.list({})", self.decoder_for(element, indent)) 4020 } 4021 Some(("gleam/option", "Option", [some])) => { 4022 eco_format!( 4023 "{module_name}.optional({})", 4024 self.decoder_for(some, indent) 4025 ) 4026 } 4027 Some(("gleam/dict", "Dict", [key, value])) => { 4028 eco_format!( 4029 "{module_name}.dict({}, {})", 4030 self.decoder_for(key, indent), 4031 self.decoder_for(value, indent) 4032 ) 4033 } 4034 Some((module, name, _)) 4035 if module == self.type_module && name == self.type_name => 4036 { 4037 eco_format!("{}_decoder()", to_snake_case(name)) 4038 } 4039 _ => eco_format!( 4040 r#"todo as "Decoder for {}""#, 4041 self.printer.print_type(type_) 4042 ), 4043 } 4044 } 4045 } 4046 } 4047 } 4048 4049 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4050 let decoder = self.decoder_for(field.type_, indent); 4051 4052 eco_format!( 4053 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#, 4054 indent = " ".repeat(indent), 4055 variable = field.label.variable_name(), 4056 field = field.label.field_key(), 4057 module = self.printer.print_module(DECODE_MODULE) 4058 ) 4059 } 4060} 4061 4062/// Builder for code action to apply the "Generate to-JSON function" action. 4063/// 4064pub struct GenerateJsonEncoder<'a> { 4065 module: &'a Module, 4066 params: &'a CodeActionParams, 4067 edits: TextEdits<'a>, 4068 printer: Printer<'a>, 4069 actions: &'a mut Vec<CodeAction>, 4070 config: &'a PackageConfig, 4071} 4072 4073const JSON_MODULE: &str = "gleam/json"; 4074const JSON_PACKAGE_NAME: &str = "gleam_json"; 4075 4076#[derive(Eq, PartialEq, Copy, Clone)] 4077enum EncodingMode { 4078 PlainString, 4079 ObjectWithTypeTag, 4080 ObjectWithNoTypeTag, 4081} 4082 4083impl EncodingMode { 4084 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self { 4085 match type_.constructors.as_slice() { 4086 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString, 4087 [_constructor] => EncodingMode::ObjectWithNoTypeTag, 4088 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => { 4089 EncodingMode::PlainString 4090 } 4091 _constructors => EncodingMode::ObjectWithTypeTag, 4092 } 4093 } 4094} 4095 4096impl<'a> GenerateJsonEncoder<'a> { 4097 pub fn new( 4098 module: &'a Module, 4099 line_numbers: &'a LineNumbers, 4100 params: &'a CodeActionParams, 4101 actions: &'a mut Vec<CodeAction>, 4102 config: &'a PackageConfig, 4103 ) -> Self { 4104 let printer = Printer::new(&module.ast.names); 4105 Self { 4106 module, 4107 params, 4108 edits: TextEdits::new(line_numbers), 4109 printer, 4110 actions, 4111 config, 4112 } 4113 } 4114 4115 pub fn code_actions(&mut self) { 4116 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME) 4117 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME) 4118 { 4119 self.visit_typed_module(&self.module.ast); 4120 } 4121 } 4122 4123 fn custom_type_encoder_body( 4124 &mut self, 4125 record_name: EcoString, 4126 custom_type: &CustomType<Arc<Type>>, 4127 ) -> Option<EcoString> { 4128 // We cannot generate a decoder for an external type with no constructors! 4129 let constructors_size = custom_type.constructors.len(); 4130 let (first, rest) = custom_type.constructors.split_first()?; 4131 let mode = EncodingMode::for_custom_type(custom_type); 4132 4133 // We generate an encoder for a type with a single constructor: it does not 4134 // require pattern matching on the argument as we can access all its fields 4135 // with the usual record access syntax. 4136 if rest.is_empty() { 4137 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?; 4138 let unpacking = if first.arguments.is_empty() { 4139 "" 4140 } else { 4141 &eco_format!( 4142 "let {name}({fields}:) = {record_name}\n ", 4143 name = first.name, 4144 fields = first 4145 .arguments 4146 .iter() 4147 .filter_map(|argument| { 4148 argument.label.as_ref().map(|(_location, label)| label) 4149 }) 4150 .join(":, ") 4151 ) 4152 }; 4153 return Some(eco_format!("{unpacking}{encoder}")); 4154 } 4155 4156 // Otherwise we generate an encoder for a type with multiple constructors: 4157 // it will need to pattern match on the various constructors and encode each 4158 // one separately. 4159 let mut branches = Vec::with_capacity(constructors_size); 4160 for constructor in iter::once(first).chain(rest) { 4161 let RecordConstructor { name, .. } = constructor; 4162 let encoder = 4163 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?; 4164 let unpacking = if constructor.arguments.is_empty() { 4165 "" 4166 } else { 4167 &eco_format!( 4168 "({}:)", 4169 constructor 4170 .arguments 4171 .iter() 4172 .filter_map(|argument| { 4173 argument.label.as_ref().map(|(_location, label)| label) 4174 }) 4175 .join(":, ") 4176 ) 4177 }; 4178 branches.push(eco_format!(" {name}{unpacking} -> {encoder}")); 4179 } 4180 4181 let branches = branches.join("\n"); 4182 Some(eco_format!( 4183 "case {record_name} {{ 4184{branches} 4185 }}", 4186 )) 4187 } 4188 4189 fn constructor_encoder( 4190 &mut self, 4191 mode: EncodingMode, 4192 constructor: &TypedRecordConstructor, 4193 type_name: EcoString, 4194 nesting: usize, 4195 ) -> Option<EcoString> { 4196 let json_module = self.printer.print_module(JSON_MODULE); 4197 let tag = to_snake_case(&constructor.name); 4198 let indent = " ".repeat(nesting); 4199 4200 // If the variant is encoded as a simple json string we just call the 4201 // `json.string` with the variant tag as an argument. 4202 if mode == EncodingMode::PlainString { 4203 return Some(eco_format!("{json_module}.string(\"{tag}\")")); 4204 } 4205 4206 // Otherwise we turn it into an object with a `type` tag field. 4207 let mut encoder_printer = 4208 JsonEncoderPrinter::new(&self.module.ast.names, type_name, self.module.name.clone()); 4209 4210 // These are the fields of the json object to encode. 4211 let mut fields = Vec::with_capacity(constructor.arguments.len()); 4212 if mode == EncodingMode::ObjectWithTypeTag { 4213 // Any needed type tag is always going to be the first field in the object 4214 fields.push(eco_format!( 4215 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))" 4216 )); 4217 } 4218 4219 for argument in constructor.arguments.iter() { 4220 let (_, label) = argument.label.as_ref()?; 4221 let field = RecordField { 4222 label: RecordLabel::Labeled(label), 4223 type_: &argument.type_, 4224 }; 4225 let encoder = encoder_printer.encode_field(&field, nesting + 2); 4226 fields.push(encoder); 4227 } 4228 4229 let fields = fields.join(",\n"); 4230 Some(eco_format!( 4231 "{json_module}.object([ 4232{fields}, 4233{indent}])" 4234 )) 4235 } 4236} 4237 4238impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> { 4239 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { 4240 let range = self.edits.src_span_to_lsp_range(custom_type.location); 4241 if !overlaps(self.params.range, range) { 4242 return; 4243 } 4244 4245 let record_name = to_snake_case(&custom_type.name); 4246 let name = eco_format!("{record_name}_to_json"); 4247 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else { 4248 return; 4249 }; 4250 4251 let json_type = self.printer.print_type(&Type::Named { 4252 publicity: ast::Publicity::Public, 4253 package: JSON_PACKAGE_NAME.into(), 4254 module: JSON_MODULE.into(), 4255 name: "Json".into(), 4256 args: vec![], 4257 inferred_variant: None, 4258 }); 4259 4260 let type_ = if custom_type.parameters.is_empty() { 4261 custom_type.name.clone() 4262 } else { 4263 let parameters = custom_type 4264 .parameters 4265 .iter() 4266 .map(|(_, name)| name) 4267 .join(", "); 4268 eco_format!("{}({})", custom_type.name, parameters) 4269 }; 4270 4271 let function = format!( 4272 " 4273 4274fn {name}({record_name}: {type_}) -> {json_type} {{ 4275 {encoder} 4276}}", 4277 ); 4278 4279 self.edits.insert(custom_type.end_position, function); 4280 maybe_import(&mut self.edits, self.module, JSON_MODULE); 4281 4282 CodeActionBuilder::new("Generate to-JSON function") 4283 .kind(CodeActionKind::REFACTOR) 4284 .preferred(false) 4285 .changes( 4286 self.params.text_document.uri.clone(), 4287 std::mem::take(&mut self.edits.edits), 4288 ) 4289 .push_to(self.actions); 4290 } 4291} 4292 4293struct JsonEncoderPrinter<'a> { 4294 printer: Printer<'a>, 4295 /// The name of the root type we are printing an encoder for 4296 type_name: EcoString, 4297 /// The module name of the root type we are printing an encoder for 4298 type_module: EcoString, 4299} 4300 4301impl<'a> JsonEncoderPrinter<'a> { 4302 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self { 4303 Self { 4304 type_name, 4305 type_module, 4306 printer: Printer::new(names), 4307 } 4308 } 4309 4310 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString { 4311 let module_name = self.printer.print_module(JSON_MODULE); 4312 let is_capture = encoded_value == "_"; 4313 let maybe_capture = |mut function: EcoString| { 4314 if is_capture { 4315 function 4316 } else { 4317 function.push('('); 4318 function.push_str(encoded_value); 4319 function.push(')'); 4320 function 4321 } 4322 }; 4323 4324 if type_.is_bool() { 4325 maybe_capture(eco_format!("{module_name}.bool")) 4326 } else if type_.is_float() { 4327 maybe_capture(eco_format!("{module_name}.float")) 4328 } else if type_.is_int() { 4329 maybe_capture(eco_format!("{module_name}.int")) 4330 } else if type_.is_string() { 4331 maybe_capture(eco_format!("{module_name}.string")) 4332 } else { 4333 match type_.tuple_types() { 4334 Some(types) => { 4335 let (tuple, new_indent) = if is_capture { 4336 ("value", indent + 4) 4337 } else { 4338 (encoded_value, indent + 2) 4339 }; 4340 4341 let encoders = types 4342 .iter() 4343 .enumerate() 4344 .map(|(index, type_)| { 4345 self.encoder_for(&format!("{tuple}.{index}"), type_, new_indent) 4346 }) 4347 .collect_vec(); 4348 4349 if is_capture { 4350 eco_format!( 4351 "fn(value) {{ 4352{indent} {module_name}.preprocessed_array([ 4353{indent} {encoders}, 4354{indent} ]) 4355{indent}}}", 4356 indent = " ".repeat(indent), 4357 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 4358 ) 4359 } else { 4360 eco_format!( 4361 "{module_name}.preprocessed_array([ 4362{indent} {encoders}, 4363{indent}])", 4364 indent = " ".repeat(indent), 4365 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), 4366 ) 4367 } 4368 } 4369 _ => { 4370 let type_information = type_.named_type_information(); 4371 let type_information: Option<(&str, &str, &[Arc<Type>])> = 4372 type_information.as_ref().map(|(module, name, arguments)| { 4373 (module.as_str(), name.as_str(), arguments.as_slice()) 4374 }); 4375 4376 match type_information { 4377 Some(("gleam", "List", [element])) => { 4378 eco_format!( 4379 "{module_name}.array({encoded_value}, {map_function})", 4380 map_function = self.encoder_for("_", element, indent) 4381 ) 4382 } 4383 Some(("gleam/option", "Option", [some])) => { 4384 eco_format!( 4385 "case {encoded_value} {{ 4386{indent} {none} -> {module_name}.null() 4387{indent} {some}(value) -> {encoder} 4388{indent}}}", 4389 indent = " ".repeat(indent), 4390 none = self 4391 .printer 4392 .print_constructor(&"gleam/option".into(), &"None".into()), 4393 some = self 4394 .printer 4395 .print_constructor(&"gleam/option".into(), &"Some".into()), 4396 encoder = self.encoder_for("value", some, indent + 2) 4397 ) 4398 } 4399 Some(("gleam/dict", "Dict", [key, value])) => { 4400 let stringify_function = match key 4401 .named_type_information() 4402 .as_ref() 4403 .map(|(module, name, arguments)| { 4404 (module.as_str(), name.as_str(), arguments.as_slice()) 4405 }) { 4406 Some(("gleam", "String", [])) => "fn(string) { string }", 4407 _ => &format!( 4408 r#"todo as "Function to stringify {}""#, 4409 self.printer.print_type(key) 4410 ), 4411 }; 4412 eco_format!( 4413 "{module_name}.dict({encoded_value}, {stringify_function}, {})", 4414 self.encoder_for("_", value, indent) 4415 ) 4416 } 4417 Some((module, name, _)) 4418 if module == self.type_module && name == self.type_name => 4419 { 4420 maybe_capture(eco_format!("{}_to_json", to_snake_case(name))) 4421 } 4422 _ => eco_format!( 4423 r#"todo as "Encoder for {}""#, 4424 self.printer.print_type(type_) 4425 ), 4426 } 4427 } 4428 } 4429 } 4430 } 4431 4432 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { 4433 let field_name = field.label.variable_name(); 4434 let encoder = self.encoder_for(&field_name, field.type_, indent); 4435 4436 eco_format!( 4437 r#"{indent}#("{field_name}", {encoder})"#, 4438 indent = " ".repeat(indent), 4439 ) 4440 } 4441} 4442 4443/// Builder for code action to pattern match on things like (anonymous) function 4444/// arguments or variables. 4445/// For example: 4446/// 4447/// ```gleam 4448/// pub fn wibble(arg: #(key, value)) { 4449/// // ^ [pattern match on argument] 4450/// } 4451/// 4452/// // Generates 4453/// 4454/// pub fn wibble(arg: #(key, value)) { 4455/// let #(value_0, value_1) = arg 4456/// } 4457/// ``` 4458/// 4459/// Another example with variables: 4460/// 4461/// ```gleam 4462/// pub fn main() { 4463/// let pair = #(1, 3) 4464/// // ^ [pattern match on value] 4465/// } 4466/// 4467/// // Generates 4468/// 4469/// pub fn main() { 4470/// let pair = #(1, 3) 4471/// let #(value_0, value_1) = pair 4472/// } 4473/// ``` 4474/// 4475pub struct PatternMatchOnValue<'a, A> { 4476 module: &'a Module, 4477 params: &'a CodeActionParams, 4478 compiler: &'a LspProjectCompiler<A>, 4479 selected_value: Option<PatternMatchedValue<'a>>, 4480 edits: TextEdits<'a>, 4481} 4482 4483/// A value we might want to pattern match on. 4484/// Each variant will also contain all the info needed to know how to properly 4485/// print and format the corresponding pattern matching code; that's why you'll 4486/// see `Range`s and `SrcSpan` besides the type of the thing being matched. 4487/// 4488pub enum PatternMatchedValue<'a> { 4489 FunctionArgument { 4490 /// The argument being pattern matched on. 4491 /// 4492 arg: &'a TypedArg, 4493 /// The first statement inside the function body. Used to correctly 4494 /// position the inserted pattern matching. 4495 /// 4496 first_statement: &'a TypedStatement, 4497 /// The range of the entire function holding the argument. 4498 /// 4499 function_range: Range, 4500 }, 4501 LetVariable { 4502 variable_name: &'a EcoString, 4503 variable_type: &'a Arc<Type>, 4504 /// The location of the entire let assignment the variable is part of, 4505 /// so that we can add the pattern matching _after_ it. 4506 /// 4507 assignment_location: SrcSpan, 4508 }, 4509} 4510 4511impl<'a, IO> PatternMatchOnValue<'a, IO> 4512where 4513 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, 4514{ 4515 pub fn new( 4516 module: &'a Module, 4517 line_numbers: &'a LineNumbers, 4518 params: &'a CodeActionParams, 4519 compiler: &'a LspProjectCompiler<IO>, 4520 ) -> Self { 4521 Self { 4522 module, 4523 params, 4524 compiler, 4525 selected_value: None, 4526 edits: TextEdits::new(line_numbers), 4527 } 4528 } 4529 4530 pub fn code_actions(mut self) -> Vec<CodeAction> { 4531 self.visit_typed_module(&self.module.ast); 4532 4533 let action_title = match self.selected_value { 4534 Some(PatternMatchedValue::FunctionArgument { 4535 arg, 4536 first_statement: function_body, 4537 function_range, 4538 }) => { 4539 self.match_on_function_argument(arg, function_body, function_range); 4540 "Pattern match on argument" 4541 } 4542 Some(PatternMatchedValue::LetVariable { 4543 variable_name, 4544 variable_type, 4545 assignment_location, 4546 }) => { 4547 self.match_on_let_variable(variable_name, variable_type, assignment_location); 4548 "Pattern match on variable" 4549 } 4550 None => return vec![], 4551 }; 4552 4553 if self.edits.edits.is_empty() { 4554 return vec![]; 4555 } 4556 4557 let mut action = Vec::with_capacity(1); 4558 CodeActionBuilder::new(action_title) 4559 .kind(CodeActionKind::REFACTOR_REWRITE) 4560 .changes(self.params.text_document.uri.clone(), self.edits.edits) 4561 .preferred(false) 4562 .push_to(&mut action); 4563 action 4564 } 4565 4566 fn match_on_function_argument( 4567 &mut self, 4568 arg: &TypedArg, 4569 first_statement: &TypedStatement, 4570 function_range: Range, 4571 ) { 4572 let Some(arg_name) = arg.get_variable_name() else { 4573 return; 4574 }; 4575 4576 let Some(patterns) = self.type_to_destructure_patterns(arg.type_.as_ref()) else { 4577 return; 4578 }; 4579 4580 let first_statement_location = first_statement.location(); 4581 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location); 4582 4583 // If we're trying to insert the pattern matching on the same 4584 // line as the one where the function is defined we will want to 4585 // put it on a new line instead. So in that case the nesting will 4586 // be the default 2 spaces. 4587 let needs_newline = function_range.start.line == first_statement_range.start.line; 4588 let nesting = if needs_newline { 4589 String::from(" ") 4590 } else { 4591 " ".repeat(first_statement_range.start.character as usize) 4592 }; 4593 4594 let pattern_matching = if patterns.len() == 1 { 4595 let pattern = patterns.first(); 4596 format!("let {pattern} = {arg_name}") 4597 } else { 4598 let patterns = patterns 4599 .iter() 4600 .map(|p| format!(" {nesting}{p} -> todo")) 4601 .join("\n"); 4602 format!("case {arg_name} {{\n{patterns}\n{nesting}}}") 4603 }; 4604 4605 let pattern_matching = if needs_newline { 4606 format!("\n{nesting}{pattern_matching}") 4607 } else { 4608 pattern_matching 4609 }; 4610 4611 let has_empty_body = match first_statement { 4612 ast::Statement::Expression(TypedExpr::Todo { 4613 kind: TodoKind::EmptyFunction { .. }, 4614 .. 4615 }) => true, 4616 _ => false, 4617 }; 4618 4619 // If the pattern matching is added to a function with an empty 4620 // body then we do not add any nesting after it, or we would be 4621 // increasing the nesting of the closing `}`! 4622 let pattern_matching = if has_empty_body { 4623 format!("{pattern_matching}\n") 4624 } else { 4625 format!("{pattern_matching}\n{nesting}") 4626 }; 4627 4628 self.edits 4629 .insert(first_statement_location.start, pattern_matching); 4630 } 4631 4632 fn match_on_let_variable( 4633 &mut self, 4634 variable_name: &EcoString, 4635 variable_type: &Arc<Type>, 4636 assignment_location: SrcSpan, 4637 ) { 4638 let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref()) else { 4639 return; 4640 }; 4641 4642 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); 4643 let nesting = " ".repeat(assignment_range.start.character as usize); 4644 4645 let pattern_matching = if patterns.len() == 1 { 4646 let pattern = patterns.first(); 4647 format!("let {pattern} = {variable_name}") 4648 } else { 4649 let patterns = patterns 4650 .iter() 4651 .map(|p| format!(" {nesting}{p} -> todo")) 4652 .join("\n"); 4653 format!("case {variable_name} {{\n{patterns}\n{nesting}}}") 4654 }; 4655 4656 self.edits.insert( 4657 assignment_location.end, 4658 format!("\n{nesting}{pattern_matching}"), 4659 ); 4660 } 4661 4662 /// Will produce a pattern that can be used on the left hand side of a let 4663 /// assignment to destructure a value of the given type. For example given 4664 /// this type: 4665 /// 4666 /// ```gleam 4667 /// pub type Wibble { 4668 /// Wobble(Int, label: String) 4669 /// } 4670 /// ``` 4671 /// 4672 /// The produced pattern will look like this: `Wobble(value_0, label:)`. 4673 /// The pattern will use the correct qualified/unqualified name for the 4674 /// constructor if it comes from another package. 4675 /// 4676 /// The function will only produce a list of patterns that can be used from 4677 /// the current module. So if the type comes from another module it must be 4678 /// public! Otherwise this function will return an empty vec. 4679 /// 4680 fn type_to_destructure_patterns(&mut self, type_: &Type) -> Option<Vec1<EcoString>> { 4681 match type_ { 4682 Type::Fn { .. } => None, 4683 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow()), 4684 Type::Named { 4685 module: type_module, 4686 name: type_name, 4687 .. 4688 } => { 4689 let patterns = 4690 get_type_constructors(self.compiler, &self.module.name, type_module, type_name) 4691 .iter() 4692 .filter_map(|c| self.record_constructor_to_destructure_pattern(c)) 4693 .collect_vec(); 4694 4695 Vec1::try_from_vec(patterns).ok() 4696 } 4697 // We don't want to suggest this action for empty tuple as it 4698 // doesn't make a lot of sense to match on those. 4699 Type::Tuple { elements } if elements.is_empty() => None, 4700 Type::Tuple { elements } => Some(vec1![eco_format!( 4701 "#({})", 4702 (0..elements.len() as u32) 4703 .map(|i| format!("value_{i}")) 4704 .join(", ") 4705 )]), 4706 } 4707 } 4708 4709 fn type_var_to_destructure_patterns(&mut self, type_var: &TypeVar) -> Option<Vec1<EcoString>> { 4710 match type_var { 4711 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, 4712 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_), 4713 } 4714 } 4715 4716 /// Given the value constructor of a record, returns a string with the 4717 /// pattern used to match on that specific variant. 4718 /// 4719 /// Note how: 4720 /// - If the constructor is internal to another module or comes from another 4721 /// module, then this returns `None` since one cannot pattern match on it. 4722 /// - If the provided `ValueConstructor` is not a record constructor this 4723 /// will return `None`. 4724 /// 4725 fn record_constructor_to_destructure_pattern( 4726 &self, 4727 constructor: &ValueConstructor, 4728 ) -> Option<EcoString> { 4729 let type_::ValueConstructorVariant::Record { 4730 name: constructor_name, 4731 arity: constructor_arity, 4732 module: constructor_module, 4733 field_map, 4734 .. 4735 } = &constructor.variant 4736 else { 4737 // The constructor should always be a record, in case it's not 4738 // there's not much we can do and just fail. 4739 return None; 4740 }; 4741 4742 // Since the constructor is a record constructor we know that its type 4743 // is either `Named` or a `Fn` type, in either case we have to get the 4744 // arguments types out of it. 4745 let Some(arguments_types) = constructor 4746 .type_ 4747 .fn_types() 4748 .map(|(arguments_types, _return)| arguments_types) 4749 .or_else(|| constructor.type_.constructor_types()) 4750 else { 4751 // This should never happen but just in case we don't want to unwrap 4752 // and panic. 4753 return None; 4754 }; 4755 4756 let mut name_generator = NameGenerator::new(); 4757 let index_to_label = match field_map { 4758 None => HashMap::new(), 4759 Some(field_map) => { 4760 name_generator.reserve_all_labels(field_map); 4761 4762 field_map 4763 .fields 4764 .iter() 4765 .map(|(label, index)| (index, label)) 4766 .collect::<HashMap<_, _>>() 4767 } 4768 }; 4769 4770 let mut pattern = 4771 pretty_constructor_name(self.module, constructor_module, constructor_name)?; 4772 4773 if *constructor_arity == 0 { 4774 return Some(pattern); 4775 } 4776 4777 pattern.push('('); 4778 let args = (0..*constructor_arity as u32) 4779 .map(|i| match index_to_label.get(&i) { 4780 Some(label) => eco_format!("{label}:"), 4781 None => match arguments_types.get(i as usize) { 4782 None => name_generator.rename_to_avoid_shadowing(EcoString::from("value")), 4783 Some(type_) => name_generator.generate_name_from_type(type_), 4784 }, 4785 }) 4786 .join(", "); 4787 4788 pattern.push_str(&args); 4789 pattern.push(')'); 4790 Some(pattern) 4791 } 4792} 4793 4794impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> 4795where 4796 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, 4797{ 4798 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 4799 // If we're not inside the function there's no point in exploring its 4800 // ast further. 4801 let function_span = SrcSpan { 4802 start: fun.location.start, 4803 end: fun.end_position, 4804 }; 4805 let function_range = self.edits.src_span_to_lsp_range(function_span); 4806 if !within(self.params.range, function_range) { 4807 return; 4808 } 4809 4810 for arg in &fun.arguments { 4811 // If the cursor is placed on one of the arguments, then we can try 4812 // and generate code for that one. 4813 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 4814 if within(self.params.range, arg_range) { 4815 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 4816 arg, 4817 first_statement: fun.body.first(), 4818 function_range, 4819 }); 4820 return; 4821 } 4822 } 4823 4824 // If the cursor is not on any of the function arguments then we keep 4825 // exploring the function body as we might want to destructure the 4826 // argument of an expression function! 4827 ast::visit::visit_typed_function(self, fun); 4828 } 4829 4830 fn visit_typed_expr_fn( 4831 &mut self, 4832 location: &'ast SrcSpan, 4833 type_: &'ast Arc<Type>, 4834 kind: &'ast FunctionLiteralKind, 4835 args: &'ast [TypedArg], 4836 body: &'ast Vec1<TypedStatement>, 4837 return_annotation: &'ast Option<ast::TypeAst>, 4838 ) { 4839 // If we're not inside the function there's no point in exploring its 4840 // ast further. 4841 let function_range = self.edits.src_span_to_lsp_range(*location); 4842 if !within(self.params.range, function_range) { 4843 return; 4844 } 4845 4846 for arg in args { 4847 // If the cursor is placed on one of the arguments, then we can try 4848 // and generate code for that one. 4849 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 4850 if within(self.params.range, arg_range) { 4851 self.selected_value = Some(PatternMatchedValue::FunctionArgument { 4852 arg, 4853 first_statement: body.first(), 4854 function_range, 4855 }); 4856 return; 4857 } 4858 } 4859 4860 // If the cursor is not on any of the function arguments then we keep 4861 // exploring the function body as we might want to destructure the 4862 // argument of an expression function! 4863 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 4864 } 4865 4866 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 4867 if let Pattern::Variable { 4868 name, 4869 location, 4870 type_, 4871 .. 4872 } = &assignment.pattern 4873 { 4874 let variable_range = self.edits.src_span_to_lsp_range(*location); 4875 if within(self.params.range, variable_range) { 4876 self.selected_value = Some(PatternMatchedValue::LetVariable { 4877 variable_name: name, 4878 variable_type: type_, 4879 assignment_location: assignment.location, 4880 }); 4881 // If we've found the variable to pattern match on, there's no 4882 // point in keeping traversing the AST. 4883 return; 4884 } 4885 } 4886 4887 ast::visit::visit_typed_assignment(self, assignment); 4888 } 4889} 4890 4891/// Given a type and its module, returns a list of its *importable* 4892/// constructors. 4893/// 4894/// Since this focuses just on importable constructors, if either the module or 4895/// the type are internal the returned array will be empty! 4896/// 4897fn get_type_constructors<'a, 'b, IO>( 4898 compiler: &'a LspProjectCompiler<IO>, 4899 current_module: &'b EcoString, 4900 type_module: &'b EcoString, 4901 type_name: &'b EcoString, 4902) -> Vec<&'a ValueConstructor> 4903where 4904 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, 4905{ 4906 let type_is_inside_current_module = current_module == type_module; 4907 let module_interface = if !type_is_inside_current_module { 4908 // If the type is outside of the module we're in, we can only pattern 4909 // match on it if the module can be imported. 4910 // The `get_module_interface` already takes care of making this check. 4911 compiler.get_module_interface(type_module) 4912 } else { 4913 // However, if the type is defined in the module we're in, we can always 4914 // pattern match on it. So we get the current module's interface. 4915 compiler 4916 .modules 4917 .get(current_module) 4918 .map(|module| &module.ast.type_info) 4919 }; 4920 4921 let Some(module_interface) = module_interface else { 4922 return vec![]; 4923 }; 4924 4925 // If the type is in an internal module that is not the current one, we 4926 // cannot use its constructors! 4927 if !type_is_inside_current_module && module_interface.is_internal { 4928 return vec![]; 4929 } 4930 4931 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else { 4932 return vec![]; 4933 }; 4934 4935 constructors 4936 .variants 4937 .iter() 4938 .filter_map(|variant| { 4939 let constructor = module_interface.values.get(&variant.name)?; 4940 if type_is_inside_current_module || constructor.publicity.is_public() { 4941 Some(constructor) 4942 } else { 4943 None 4944 } 4945 }) 4946 .collect_vec() 4947} 4948 4949/// Returns a pretty printed record constructor name, the way it would be used 4950/// inside the given `module` (with the correct name and qualification). 4951/// 4952/// If the constructor cannot be used inside the module because it's not 4953/// imported, then this function will return `None`. 4954/// 4955fn pretty_constructor_name( 4956 module: &Module, 4957 constructor_module: &EcoString, 4958 constructor_name: &EcoString, 4959) -> Option<EcoString> { 4960 match module 4961 .ast 4962 .names 4963 .named_constructor(constructor_module, constructor_name) 4964 { 4965 type_::printer::NameContextInformation::Unimported(_) => None, 4966 type_::printer::NameContextInformation::Unqualified(constructor_name) => { 4967 Some(eco_format!("{constructor_name}")) 4968 } 4969 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => { 4970 Some(eco_format!("{module_name}.{constructor_name}")) 4971 } 4972 } 4973} 4974 4975/// Builder for the "generate function" code action. 4976/// Whenever someone hovers an invalid expression that is inferred to have a 4977/// function type the language server can generate a function definition for it. 4978/// For example: 4979/// 4980/// ```gleam 4981/// pub fn main() { 4982/// wibble(1, 2, "hello") 4983/// // ^ [generate function] 4984/// } 4985/// ``` 4986/// 4987/// Will generate the following definition: 4988/// 4989/// ```gleam 4990/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a { 4991/// todo 4992/// } 4993/// ``` 4994/// 4995pub struct GenerateFunction<'a> { 4996 module: &'a Module, 4997 params: &'a CodeActionParams, 4998 edits: TextEdits<'a>, 4999 last_visited_function_end: Option<u32>, 5000 function_to_generate: Option<FunctionToGenerate<'a>>, 5001} 5002 5003struct FunctionToGenerate<'a> { 5004 name: &'a str, 5005 arguments_types: Vec<Arc<Type>>, 5006 5007 /// The arguments actually supplied as input to the function, if any. 5008 /// A function to generate might as well be just a name passed as an argument 5009 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually 5010 /// have any actual arguments! 5011 given_arguments: Option<&'a [TypedCallArg]>, 5012 return_type: Arc<Type>, 5013 previous_function_end: Option<u32>, 5014} 5015 5016impl<'a> GenerateFunction<'a> { 5017 pub fn new( 5018 module: &'a Module, 5019 line_numbers: &'a LineNumbers, 5020 params: &'a CodeActionParams, 5021 ) -> Self { 5022 Self { 5023 module, 5024 params, 5025 edits: TextEdits::new(line_numbers), 5026 last_visited_function_end: None, 5027 function_to_generate: None, 5028 } 5029 } 5030 5031 pub fn code_actions(mut self) -> Vec<CodeAction> { 5032 self.visit_typed_module(&self.module.ast); 5033 5034 let Some(FunctionToGenerate { 5035 name, 5036 arguments_types, 5037 given_arguments, 5038 previous_function_end: Some(insert_at), 5039 return_type, 5040 }) = self.function_to_generate 5041 else { 5042 return vec![]; 5043 }; 5044 5045 // Labels do not share the same namespace as argument so we use two separate 5046 // generators to avoid renaming a label in case it shares a name with an argument. 5047 let mut label_names = NameGenerator::new(); 5048 let mut argument_names = NameGenerator::new(); 5049 let mut printer = Printer::new(&self.module.ast.names); 5050 let arguments = arguments_types 5051 .iter() 5052 .enumerate() 5053 .map(|(index, argument_type)| { 5054 let call_argument = given_arguments.and_then(|arguments| arguments.get(index)); 5055 let (label, name) = 5056 argument_names.generate_label_and_name(call_argument, argument_type); 5057 let pretty_type = printer.print_type(argument_type); 5058 if let Some(label) = label { 5059 let label = label_names.rename_to_avoid_shadowing(label.clone()); 5060 format!("{label} {name}: {pretty_type}") 5061 } else { 5062 format!("{name}: {pretty_type}") 5063 } 5064 }) 5065 .join(", "); 5066 5067 let return_type = printer.print_type(&return_type); 5068 5069 self.edits.insert( 5070 insert_at, 5071 format!("\n\nfn {name}({arguments}) -> {return_type} {{\n todo\n}}"), 5072 ); 5073 5074 let mut action = Vec::with_capacity(1); 5075 CodeActionBuilder::new("Generate function") 5076 .kind(CodeActionKind::REFACTOR_REWRITE) 5077 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5078 .preferred(false) 5079 .push_to(&mut action); 5080 action 5081 } 5082 5083 fn try_save_function_to_generate( 5084 &mut self, 5085 function_name_location: SrcSpan, 5086 function_type: &Arc<Type>, 5087 given_arguments: Option<&'a [TypedCallArg]>, 5088 ) { 5089 let name_range = function_name_location.start as usize..function_name_location.end as usize; 5090 let candidate_name = self.module.code.get(name_range); 5091 match (candidate_name, function_type.fn_types()) { 5092 (None, _) | (_, None) => (), 5093 (Some(name), _) if !is_valid_lowercase_name(name) => (), 5094 (Some(name), Some((arguments_types, return_type))) => { 5095 self.function_to_generate = Some(FunctionToGenerate { 5096 name, 5097 arguments_types, 5098 given_arguments, 5099 return_type, 5100 previous_function_end: self.last_visited_function_end, 5101 }) 5102 } 5103 } 5104 } 5105} 5106 5107impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> { 5108 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { 5109 self.last_visited_function_end = Some(fun.end_position); 5110 ast::visit::visit_typed_function(self, fun); 5111 } 5112 5113 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 5114 let invalid_range = self.edits.src_span_to_lsp_range(*location); 5115 if within(self.params.range, invalid_range) { 5116 self.try_save_function_to_generate(*location, type_, None); 5117 } 5118 5119 ast::visit::visit_typed_expr_invalid(self, location, type_); 5120 } 5121 5122 fn visit_typed_expr_call( 5123 &mut self, 5124 location: &'ast SrcSpan, 5125 type_: &'ast Arc<Type>, 5126 fun: &'ast TypedExpr, 5127 args: &'ast [TypedCallArg], 5128 ) { 5129 // If the function being called is invalid we need to generate a 5130 // function that has the proper labels. 5131 let fun_range = self.edits.src_span_to_lsp_range(fun.location()); 5132 5133 if within(self.params.range, fun_range) && fun.is_invalid() { 5134 if labels_are_correct(args) { 5135 self.try_save_function_to_generate(fun.location(), &fun.type_(), Some(args)); 5136 } 5137 } else { 5138 ast::visit::visit_typed_expr_call(self, location, type_, fun, args); 5139 } 5140 } 5141} 5142 5143/// Builder for the "generate variant" code action. This will generate a variant 5144/// for a type if it can tell the type it should come from. It will work with 5145/// non-existing variants both used as expressions 5146/// 5147/// ```gleam 5148/// let a = IDoNotExist(1) 5149/// // ^^^^^^^^^^^ It would generate this variant here 5150/// ``` 5151/// 5152/// And as patterns: 5153/// 5154/// ```gleam 5155/// let assert IDoNotExist(1) = todo 5156/// ^^^^^^^^^^^ It would generate this variant here 5157/// ``` 5158/// 5159pub struct GenerateVariant<'a, IO> { 5160 module: &'a Module, 5161 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 5162 params: &'a CodeActionParams, 5163 line_numbers: &'a LineNumbers, 5164 variant_to_generate: Option<VariantToGenerate<'a>>, 5165} 5166 5167struct VariantToGenerate<'a> { 5168 name: &'a str, 5169 end_position: u32, 5170 arguments_types: Vec<Arc<Type>>, 5171 5172 /// Wether the type we're adding the variant to is written with braces or 5173 /// not. We need this information to add braces when missing. 5174 /// 5175 type_braces: TypeBraces, 5176 5177 /// The module this variant will be added to. 5178 /// 5179 module_name: EcoString, 5180 5181 /// The arguments actually supplied as input to the variant, if any. 5182 /// A variant to generate might as well be just a name passed as an argument 5183 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually 5184 /// have any actual arguments! 5185 /// 5186 given_arguments: Option<Arguments<'a>>, 5187} 5188 5189#[derive(Debug, Clone, Copy)] 5190enum TypeBraces { 5191 /// If the type is written like this: `pub type Wibble` 5192 HasBraces, 5193 /// If the type is written like this: `pub type Wibble {}` 5194 NoBraces, 5195} 5196 5197/// The arguments to an invalid call or pattern we can use to generate a variant. 5198/// 5199enum Arguments<'a> { 5200 /// These are the arguments provided to the invalid variant constructor 5201 /// when it's used as a function: `let a = Wibble(1, 2)`. 5202 /// 5203 Expressions(&'a [TypedCallArg]), 5204 /// These are the arguments provided to the invalid variant constructor when 5205 /// it's used in a pattern: `let assert Wibble(1, 2) = a` 5206 /// 5207 Patterns(&'a [CallArg<TypedPattern>]), 5208} 5209 5210/// An invalid variant might be used both as a pattern in a case expression or 5211/// as a regular value in an expression. We want to generate the variant in both 5212/// cases, so we use this enum to tell apart the two cases and be able to reuse 5213/// most of the code for both as they are very similar. 5214/// 5215enum Argument<'a> { 5216 Expression(&'a TypedCallArg), 5217 Pattern(&'a CallArg<TypedPattern>), 5218} 5219 5220impl<'a> Arguments<'a> { 5221 fn get(&self, index: usize) -> Option<Argument<'a>> { 5222 match self { 5223 Arguments::Patterns(call_args) => call_args.get(index).map(Argument::Pattern), 5224 Arguments::Expressions(call_args) => call_args.get(index).map(Argument::Expression), 5225 } 5226 } 5227 5228 fn types(&self) -> Vec<Arc<Type>> { 5229 match self { 5230 Arguments::Expressions(call_args) => call_args 5231 .iter() 5232 .map(|argument| argument.value.type_()) 5233 .collect_vec(), 5234 5235 Arguments::Patterns(call_args) => call_args 5236 .iter() 5237 .map(|argument| argument.value.type_()) 5238 .collect_vec(), 5239 } 5240 } 5241} 5242 5243impl Argument<'_> { 5244 fn label(&self) -> Option<EcoString> { 5245 match self { 5246 Argument::Expression(call_arg) => call_arg.label.clone(), 5247 Argument::Pattern(call_arg) => call_arg.label.clone(), 5248 } 5249 } 5250} 5251 5252impl<'a, IO> GenerateVariant<'a, IO> 5253where 5254 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone, 5255{ 5256 pub fn new( 5257 module: &'a Module, 5258 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>, 5259 line_numbers: &'a LineNumbers, 5260 params: &'a CodeActionParams, 5261 ) -> Self { 5262 Self { 5263 module, 5264 params, 5265 compiler, 5266 line_numbers, 5267 variant_to_generate: None, 5268 } 5269 } 5270 5271 pub fn code_actions(mut self) -> Vec<CodeAction> { 5272 self.visit_typed_module(&self.module.ast); 5273 5274 let Some(VariantToGenerate { 5275 name, 5276 arguments_types, 5277 given_arguments, 5278 module_name, 5279 end_position, 5280 type_braces, 5281 }) = &self.variant_to_generate 5282 else { 5283 return vec![]; 5284 }; 5285 5286 let Some((variant_module, variant_edits)) = self.edits_to_create_variant( 5287 name, 5288 arguments_types, 5289 given_arguments, 5290 module_name, 5291 *end_position, 5292 *type_braces, 5293 ) else { 5294 return vec![]; 5295 }; 5296 5297 let mut action = Vec::with_capacity(1); 5298 CodeActionBuilder::new("Generate variant") 5299 .kind(CodeActionKind::REFACTOR_REWRITE) 5300 .changes(variant_module, variant_edits) 5301 .preferred(false) 5302 .push_to(&mut action); 5303 action 5304 } 5305 5306 /// Returns the edits needed to add this new variant to the given module. 5307 /// It also returns the uri of the module the edits should be applied to. 5308 /// 5309 fn edits_to_create_variant( 5310 &self, 5311 variant_name: &str, 5312 arguments_types: &[Arc<Type>], 5313 given_arguments: &Option<Arguments<'_>>, 5314 module_name: &EcoString, 5315 end_position: u32, 5316 type_braces: TypeBraces, 5317 ) -> Option<(Url, Vec<TextEdit>)> { 5318 let mut label_names = NameGenerator::new(); 5319 let mut printer = Printer::new(&self.module.ast.names); 5320 let arguments = arguments_types 5321 .iter() 5322 .enumerate() 5323 .map(|(index, argument_type)| { 5324 let label = given_arguments 5325 .as_ref() 5326 .and_then(|arguments| arguments.get(index)?.label()) 5327 .map(|label| label_names.rename_to_avoid_shadowing(label)); 5328 5329 let pretty_type = printer.print_type(argument_type); 5330 if let Some(arg_label) = label { 5331 format!("{arg_label}: {pretty_type}") 5332 } else { 5333 format!("{pretty_type}") 5334 } 5335 }) 5336 .join(", "); 5337 5338 let variant = if arguments.is_empty() { 5339 variant_name.to_string() 5340 } else { 5341 format!("{variant_name}({arguments})") 5342 }; 5343 5344 let (new_text, insert_at) = match type_braces { 5345 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1), 5346 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position), 5347 }; 5348 5349 if *module_name == self.module.name { 5350 // If we're editing the current module we can use the line numbers that 5351 // were already computed before-hand without wasting any time to add the 5352 // new edit. 5353 let mut edits = TextEdits::new(self.line_numbers); 5354 edits.insert(insert_at, new_text); 5355 Some((self.params.text_document.uri.clone(), edits.edits)) 5356 } else { 5357 // Otherwise we're changing a different module and we need to get its 5358 // code and line numbers to properly apply the new edit. 5359 let module = self 5360 .compiler 5361 .modules 5362 .get(module_name) 5363 .expect("module to exist"); 5364 let line_numbers = LineNumbers::new(&module.code); 5365 let mut edits = TextEdits::new(&line_numbers); 5366 edits.insert(insert_at, new_text); 5367 Some((url_from_path(module.input_path.as_str())?, edits.edits)) 5368 } 5369 } 5370 5371 fn try_save_variant_to_generate( 5372 &mut self, 5373 function_name_location: SrcSpan, 5374 function_type: &Arc<Type>, 5375 given_arguments: Option<Arguments<'a>>, 5376 ) { 5377 let variant_to_generate = 5378 self.variant_to_generate(function_name_location, function_type, given_arguments); 5379 if variant_to_generate.is_some() { 5380 self.variant_to_generate = variant_to_generate; 5381 } 5382 } 5383 5384 fn variant_to_generate( 5385 &mut self, 5386 function_name_location: SrcSpan, 5387 type_: &Arc<Type>, 5388 given_arguments: Option<Arguments<'a>>, 5389 ) -> Option<VariantToGenerate<'a>> { 5390 let name_range = function_name_location.start as usize..function_name_location.end as usize; 5391 let name = self.module.code.get(name_range).expect("valid code range"); 5392 if !is_valid_uppercase_name(name) { 5393 return None; 5394 } 5395 5396 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) { 5397 (Some(result), _) => result, 5398 (None, Some(arguments)) => (arguments.types(), type_.clone()), 5399 (None, None) => (vec![], type_.clone()), 5400 }; 5401 5402 let (module_name, type_name, _) = custom_type.named_type_information()?; 5403 let module = self.compiler.modules.get(&module_name)?; 5404 let (end_position, type_braces) = 5405 (module.ast.definitions.iter()).find_map(|definition| match definition { 5406 ast::Definition::CustomType(custom_type) if custom_type.name == type_name => { 5407 // If there's already a variant with this name then we definitely 5408 // don't want to generate a new variant with the same name! 5409 let variant_with_this_name_already_exists = custom_type 5410 .constructors 5411 .iter() 5412 .map(|constructor| &constructor.name) 5413 .any(|existing_constructor_name| existing_constructor_name == name); 5414 if variant_with_this_name_already_exists { 5415 return None; 5416 } 5417 let type_braces = if custom_type.end_position == custom_type.location.end { 5418 TypeBraces::NoBraces 5419 } else { 5420 TypeBraces::HasBraces 5421 }; 5422 Some((custom_type.end_position, type_braces)) 5423 } 5424 _ => None, 5425 })?; 5426 5427 Some(VariantToGenerate { 5428 name, 5429 arguments_types, 5430 given_arguments, 5431 module_name, 5432 end_position, 5433 type_braces, 5434 }) 5435 } 5436} 5437 5438impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> 5439where 5440 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone, 5441{ 5442 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 5443 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 5444 if within(self.params.range, invalid_range) { 5445 self.try_save_variant_to_generate(*location, type_, None); 5446 } 5447 ast::visit::visit_typed_expr_invalid(self, location, type_); 5448 } 5449 5450 fn visit_typed_expr_call( 5451 &mut self, 5452 location: &'ast SrcSpan, 5453 type_: &'ast Arc<Type>, 5454 fun: &'ast TypedExpr, 5455 args: &'ast [TypedCallArg], 5456 ) { 5457 // If the function being called is invalid we need to generate a 5458 // function that has the proper labels. 5459 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers); 5460 if within(self.params.range, fun_range) && fun.is_invalid() { 5461 if labels_are_correct(args) { 5462 self.try_save_variant_to_generate( 5463 fun.location(), 5464 &fun.type_(), 5465 Some(Arguments::Expressions(args)), 5466 ); 5467 } 5468 } else { 5469 ast::visit::visit_typed_expr_call(self, location, type_, fun, args); 5470 } 5471 } 5472 5473 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) { 5474 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); 5475 if within(self.params.range, invalid_range) { 5476 self.try_save_variant_to_generate(*location, type_, None); 5477 } 5478 ast::visit::visit_typed_pattern_invalid(self, location, type_); 5479 } 5480 5481 fn visit_typed_pattern_constructor( 5482 &mut self, 5483 location: &'ast SrcSpan, 5484 name_location: &'ast SrcSpan, 5485 name: &'ast EcoString, 5486 arguments: &'ast Vec<CallArg<TypedPattern>>, 5487 module: &'ast Option<(EcoString, SrcSpan)>, 5488 constructor: &'ast analyse::Inferred<type_::PatternConstructor>, 5489 spread: &'ast Option<SrcSpan>, 5490 type_: &'ast Arc<Type>, 5491 ) { 5492 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers); 5493 // TODO)) Solo se il pattern non è valido!!!!! 5494 if within(self.params.range, pattern_range) { 5495 if labels_are_correct(arguments) { 5496 self.try_save_variant_to_generate( 5497 *name_location, 5498 type_, 5499 Some(Arguments::Patterns(arguments)), 5500 ); 5501 } 5502 } else { 5503 ast::visit::visit_typed_pattern_constructor( 5504 self, 5505 location, 5506 name_location, 5507 name, 5508 arguments, 5509 module, 5510 constructor, 5511 spread, 5512 type_, 5513 ); 5514 } 5515 } 5516} 5517 5518#[must_use] 5519/// Checks the labels in the given arguments are correct: that is there's no 5520/// duplicate labels and all labelled arguments come after the unlabelled ones. 5521fn labels_are_correct<A>(args: &[CallArg<A>]) -> bool { 5522 let mut labelled_arg_found = false; 5523 let mut used_labels = HashSet::new(); 5524 5525 for arg in args { 5526 match &arg.label { 5527 // Labels are invalid if there's duplicate ones or if an unlabelled 5528 // argument comes after a labelled one. 5529 Some(label) if used_labels.contains(label) => return false, 5530 None if labelled_arg_found => return false, 5531 // Otherwise we just add the label to the used ones. 5532 Some(label) => { 5533 labelled_arg_found = true; 5534 let _ = used_labels.insert(label); 5535 } 5536 None => {} 5537 } 5538 } 5539 5540 true 5541} 5542 5543struct NameGenerator { 5544 used_names: HashSet<EcoString>, 5545} 5546 5547impl NameGenerator { 5548 pub fn new() -> Self { 5549 NameGenerator { 5550 used_names: HashSet::new(), 5551 } 5552 } 5553 5554 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString { 5555 let mut i = 1; 5556 let mut candidate_name = base.clone(); 5557 5558 loop { 5559 if self.used_names.contains(&candidate_name) { 5560 i += 1; 5561 candidate_name = eco_format!("{base}_{i}"); 5562 } else { 5563 let _ = self.used_names.insert(candidate_name.clone()); 5564 return candidate_name; 5565 } 5566 } 5567 } 5568 5569 /// Given an argument type and the actual call argument (if any), comes up 5570 /// with a label and a name to use for that argument when generating a 5571 /// function. 5572 /// 5573 pub fn generate_label_and_name( 5574 &mut self, 5575 call_argument: Option<&CallArg<TypedExpr>>, 5576 argument_type: &Arc<Type>, 5577 ) -> (Option<EcoString>, EcoString) { 5578 let label = call_argument.and_then(|argument| argument.label.clone()); 5579 let argument_name = call_argument 5580 // We always favour a name derived from the expression (for example if 5581 // the argument is a variable) 5582 .and_then(|argument| self.generate_name_from_expression(&argument.value)) 5583 // If we don't have such a name and there's a label we use that name. 5584 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?))) 5585 // If all else fails we fallback to using a name derived from the 5586 // argument's type. 5587 .unwrap_or_else(|| self.generate_name_from_type(argument_type)); 5588 5589 (label, argument_name) 5590 } 5591 5592 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString { 5593 let type_to_base_name = |type_: &Arc<Type>| { 5594 type_ 5595 .named_type_name() 5596 .map(|(_type_module, type_name)| to_snake_case(&type_name)) 5597 .filter(|name| is_valid_lowercase_name(name)) 5598 .unwrap_or(EcoString::from("value")) 5599 }; 5600 5601 let base_name = match type_.list_type() { 5602 None => type_to_base_name(type_), 5603 // If we're coming up with a name for a list we want to use the 5604 // plural form for the name of the inner type. For example: 5605 // `List(Pokemon)` should generate `pokemons`. 5606 Some(inner_type) => { 5607 let base_name = type_to_base_name(&inner_type); 5608 // If the inner type name already ends in "s" we leave it as it 5609 // is, or it would look funny. 5610 if base_name.ends_with('s') { 5611 base_name 5612 } else { 5613 eco_format!("{base_name}s") 5614 } 5615 } 5616 }; 5617 5618 self.rename_to_avoid_shadowing(base_name) 5619 } 5620 5621 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> { 5622 match expression { 5623 // If the argument is a record, we can't use it as an argument name. 5624 // Similarly, we don't want to base the variable name off a 5625 // compiler-generated variable like `_pipe`. 5626 TypedExpr::Var { 5627 name, constructor, .. 5628 } if !constructor.variant.is_record() 5629 && !constructor.variant.is_generated_variable() => 5630 { 5631 Some(self.rename_to_avoid_shadowing(name.clone())) 5632 } 5633 _ => None, 5634 } 5635 } 5636 5637 pub fn add_used_name(&mut self, name: EcoString) { 5638 let _ = self.used_names.insert(name); 5639 } 5640 5641 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) { 5642 field_map 5643 .fields 5644 .iter() 5645 .for_each(|(label, _)| self.add_used_name(label.clone())); 5646 } 5647 5648 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) { 5649 variable_names 5650 .names 5651 .iter() 5652 .for_each(|name| self.add_used_name(name.clone())); 5653 } 5654} 5655 5656#[must_use] 5657fn is_valid_lowercase_name(name: &str) -> bool { 5658 if !name.starts_with(|char: char| char.is_ascii_lowercase()) { 5659 return false; 5660 } 5661 5662 for char in name.chars() { 5663 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_'; 5664 if !is_valid_char { 5665 return false; 5666 } 5667 } 5668 5669 str_to_keyword(name).is_none() 5670} 5671 5672#[must_use] 5673fn is_valid_uppercase_name(name: &str) -> bool { 5674 if !name.starts_with(|char: char| char.is_ascii_uppercase()) { 5675 return false; 5676 } 5677 5678 for char in name.chars() { 5679 if !char.is_ascii_alphanumeric() { 5680 return false; 5681 } 5682 } 5683 5684 true 5685} 5686 5687/// Code action to rewrite a single-step pipeline into a regular function call. 5688/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`. 5689/// 5690pub struct ConvertToFunctionCall<'a> { 5691 module: &'a Module, 5692 params: &'a CodeActionParams, 5693 edits: TextEdits<'a>, 5694 locations: Option<ConvertToFunctionCallLocations>, 5695} 5696 5697/// All the different locations the "Convert to function call" code action needs 5698/// to properly rewrite a pipeline into a function call. 5699/// 5700struct ConvertToFunctionCallLocations { 5701 /// This is the location of the value being piped into a call. 5702 /// 5703 /// ```gleam 5704 /// [1, 2, 3] |> list.length 5705 /// // ^^^^^^^^^ This one here 5706 /// ``` 5707 /// 5708 first_value: SrcSpan, 5709 5710 /// This is the location of the call the value is being piped into. 5711 /// 5712 /// ```gleam 5713 /// [1, 2, 3] |> list.length 5714 /// // ^^^^^^^^^^^ This one here 5715 /// ``` 5716 /// 5717 call: SrcSpan, 5718 5719 /// This is the kind of desugaring that is taking place when piping 5720 /// `first_value` into `call`. 5721 /// 5722 call_kind: PipelineAssignmentKind, 5723} 5724 5725impl<'a> ConvertToFunctionCall<'a> { 5726 pub fn new( 5727 module: &'a Module, 5728 line_numbers: &'a LineNumbers, 5729 params: &'a CodeActionParams, 5730 ) -> Self { 5731 Self { 5732 module, 5733 params, 5734 edits: TextEdits::new(line_numbers), 5735 locations: None, 5736 } 5737 } 5738 5739 pub fn code_actions(mut self) -> Vec<CodeAction> { 5740 self.visit_typed_module(&self.module.ast); 5741 5742 // If we couldn't find a pipeline to rewrite we don't return any action. 5743 let Some(ConvertToFunctionCallLocations { 5744 first_value, 5745 call, 5746 call_kind, 5747 }) = self.locations 5748 else { 5749 return vec![]; 5750 }; 5751 5752 // We first delete the first value of the pipeline as it's going to be 5753 // inlined as a function call argument. 5754 self.edits.delete(SrcSpan { 5755 start: first_value.start, 5756 end: call.start, 5757 }); 5758 5759 // Then we have to insert the piped value in the appropriate position. 5760 // This will change based on how the pipeline is being desugared, we 5761 // know this thanks to the `call_kind` 5762 let first_value_text = self 5763 .module 5764 .code 5765 .get(first_value.start as usize..first_value.end as usize) 5766 .expect("invalid code span") 5767 .to_string(); 5768 5769 match call_kind { 5770 // When piping into a `_` we replace the hole with the piped value: 5771 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`. 5772 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text), 5773 5774 // When piping is desguared as a function call we need to add the 5775 // missing parentheses: 5776 // `[1, 2] |> length` becomes `length([1, 2])` 5777 PipelineAssignmentKind::FunctionCall => { 5778 self.edits.insert(call.end, format!("({first_value_text})")) 5779 } 5780 5781 // When the piped value is inserted as the first argument there's two 5782 // possible scenarios: 5783 // - there's a second argument as well: in that case we insert it 5784 // before the second arg and add a comma 5785 // - there's no other argument: `[1, 2] |> length()` becomes 5786 // `length([1, 2])`, we insert the value between the empty 5787 // parentheses 5788 PipelineAssignmentKind::FirstArgument { 5789 second_argument: Some(SrcSpan { start, .. }), 5790 } => self.edits.insert(start, format!("{first_value_text}, ")), 5791 PipelineAssignmentKind::FirstArgument { 5792 second_argument: None, 5793 } => self.edits.insert(call.end - 1, first_value_text), 5794 5795 // When the value is piped into an echo, to rewrite the pipeline we 5796 // have to insert the value after the `echo` with no parentheses: 5797 // `a |> echo` is rewritten as `echo a`. 5798 PipelineAssignmentKind::Echo => { 5799 self.edits.insert(call.end, format!(" {first_value_text}")) 5800 } 5801 } 5802 5803 let mut action = Vec::with_capacity(1); 5804 CodeActionBuilder::new("Convert to function call") 5805 .kind(CodeActionKind::REFACTOR_REWRITE) 5806 .changes(self.params.text_document.uri.clone(), self.edits.edits) 5807 .preferred(false) 5808 .push_to(&mut action); 5809 action 5810 } 5811} 5812 5813impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> { 5814 fn visit_typed_expr_pipeline( 5815 &mut self, 5816 location: &'ast SrcSpan, 5817 first_value: &'ast TypedPipelineAssignment, 5818 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 5819 finally: &'ast TypedExpr, 5820 finally_kind: &'ast PipelineAssignmentKind, 5821 ) { 5822 let pipeline_range = self.edits.src_span_to_lsp_range(*location); 5823 if within(self.params.range, pipeline_range) { 5824 // We will always desugar the pipeline's first step. If there's no 5825 // intermediate assignment it means we're dealing with a single step 5826 // pipeline and the call is `finally`. 5827 let (call, call_kind) = assignments 5828 .first() 5829 .map(|(call, kind)| (call.location, *kind)) 5830 .unwrap_or_else(|| (finally.location(), *finally_kind)); 5831 5832 self.locations = Some(ConvertToFunctionCallLocations { 5833 first_value: first_value.location, 5834 call, 5835 call_kind, 5836 }); 5837 5838 ast::visit::visit_typed_expr_pipeline( 5839 self, 5840 location, 5841 first_value, 5842 assignments, 5843 finally, 5844 finally_kind, 5845 ); 5846 } 5847 } 5848} 5849 5850/// Builder for code action to inline a variable. 5851/// 5852pub struct InlineVariable<'a> { 5853 module: &'a Module, 5854 params: &'a CodeActionParams, 5855 edits: TextEdits<'a>, 5856 actions: Vec<CodeAction>, 5857} 5858 5859impl<'a> InlineVariable<'a> { 5860 pub fn new( 5861 module: &'a Module, 5862 line_numbers: &'a LineNumbers, 5863 params: &'a CodeActionParams, 5864 ) -> Self { 5865 Self { 5866 module, 5867 params, 5868 edits: TextEdits::new(line_numbers), 5869 actions: Vec::new(), 5870 } 5871 } 5872 5873 pub fn code_actions(mut self) -> Vec<CodeAction> { 5874 self.visit_typed_module(&self.module.ast); 5875 5876 self.actions 5877 } 5878 5879 fn maybe_inline(&mut self, location: SrcSpan) { 5880 let variable_location = 5881 match find_variable_references(&self.module.ast, location).as_slice() { 5882 [only_reference] => only_reference.location, 5883 _ => return, 5884 }; 5885 5886 let Some(ast::Statement::Assignment(assignment)) = 5887 self.module.ast.find_statement(location.start) 5888 else { 5889 return; 5890 }; 5891 5892 // If the assignment does not simple bind a variable, for example: 5893 // ```gleam 5894 // let #(first, second, third) 5895 // io.println(first) 5896 // // ^ Inline here 5897 // ``` 5898 // We can't inline it. 5899 if !matches!(assignment.pattern, Pattern::Variable { .. }) { 5900 return; 5901 } 5902 5903 // If the assignment was generated by the compiler, it doesn't have a 5904 // syntactical representation, so we can't inline it. 5905 if matches!(assignment.kind, AssignmentKind::Generated) { 5906 return; 5907 } 5908 5909 let value_location = assignment.value.location(); 5910 let value = self 5911 .module 5912 .code 5913 .get(value_location.start as usize..value_location.end as usize) 5914 .expect("Span is valid"); 5915 5916 self.edits.replace(variable_location, value.into()); 5917 5918 let mut location = assignment.location; 5919 5920 let mut chars = self.module.code[location.end as usize..].chars(); 5921 // Delete any whitespace after the removed statement 5922 while chars.next().is_some_and(char::is_whitespace) { 5923 location.end += 1; 5924 } 5925 5926 self.edits.delete(location); 5927 5928 CodeActionBuilder::new("Inline variable") 5929 .kind(CodeActionKind::REFACTOR_INLINE) 5930 .changes( 5931 self.params.text_document.uri.clone(), 5932 std::mem::take(&mut self.edits.edits), 5933 ) 5934 .preferred(false) 5935 .push_to(&mut self.actions); 5936 } 5937} 5938 5939impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> { 5940 fn visit_typed_expr_var( 5941 &mut self, 5942 location: &'ast SrcSpan, 5943 constructor: &'ast ValueConstructor, 5944 _name: &'ast EcoString, 5945 ) { 5946 let range = self.edits.src_span_to_lsp_range(*location); 5947 5948 if !overlaps(self.params.range, range) { 5949 return; 5950 } 5951 5952 let type_::ValueConstructorVariant::LocalVariable { location, origin } = 5953 &constructor.variant 5954 else { 5955 return; 5956 }; 5957 5958 // We can only inline it if it comes from a regular variable pattern, 5959 // not a complex pattern or generated assignment. 5960 match origin { 5961 VariableOrigin::Variable(_) => {} 5962 VariableOrigin::LabelShorthand(_) 5963 | VariableOrigin::AssignmentPattern 5964 | VariableOrigin::Generated => return, 5965 } 5966 5967 self.maybe_inline(*location); 5968 } 5969 5970 fn visit_typed_pattern_variable( 5971 &mut self, 5972 location: &'ast SrcSpan, 5973 _name: &'ast EcoString, 5974 _type: &'ast Arc<Type>, 5975 origin: &'ast VariableOrigin, 5976 ) { 5977 // We can only inline it if it is a regular variable pattern 5978 match origin { 5979 VariableOrigin::Variable(_) => {} 5980 VariableOrigin::LabelShorthand(_) 5981 | VariableOrigin::AssignmentPattern 5982 | VariableOrigin::Generated => return, 5983 } 5984 5985 let range = self.edits.src_span_to_lsp_range(*location); 5986 5987 if !overlaps(self.params.range, range) { 5988 return; 5989 } 5990 5991 self.maybe_inline(*location); 5992 } 5993} 5994 5995/// Builder for the "convert to pipe" code action. 5996/// 5997/// ```gleam 5998/// pub fn main() { 5999/// wibble(wobble, woo) 6000/// // ^ [convert to pipe] 6001/// } 6002/// ``` 6003/// 6004/// Will turn the code into the following pipeline: 6005/// 6006/// ```gleam 6007/// pub fn main() { 6008/// wobble |> wibble(woo) 6009/// } 6010/// ``` 6011/// 6012pub struct ConvertToPipe<'a> { 6013 module: &'a Module, 6014 params: &'a CodeActionParams, 6015 edits: TextEdits<'a>, 6016 argument_to_pipe: Option<ConvertToPipeArg<'a>>, 6017 /// this will be true if we're visiting the call on the right hand side of a 6018 /// use expression. So we can skip it and not try to turn it into a 6019 /// function. 6020 visiting_use_call: bool, 6021} 6022 6023/// Holds all the data needed by the "convert to pipe" code action to properly 6024/// rewrite a call into a pipe. Here's what each span means: 6025/// 6026/// ```gleam 6027/// wibble(wobb|le, woo) 6028/// // ^^^^^^^^^^^^^^^^^^^^ call 6029/// // ^^^^^^ called 6030/// // ^^^^^^^ arg 6031/// // ^^^ next arg 6032/// ``` 6033/// 6034/// In this example `position` is 0, since the cursor is over the first 6035/// argument. 6036/// 6037pub struct ConvertToPipeArg<'a> { 6038 /// The span of the called function. 6039 called: SrcSpan, 6040 /// The span of the entire function call. 6041 call: SrcSpan, 6042 /// The position (0-based) of the argument. 6043 position: usize, 6044 /// The argument we have to pipe. 6045 arg: &'a TypedCallArg, 6046 /// The span of the argument following the one we have to pipe, if there's 6047 /// any. 6048 next_arg: Option<SrcSpan>, 6049} 6050 6051impl<'a> ConvertToPipe<'a> { 6052 pub fn new( 6053 module: &'a Module, 6054 line_numbers: &'a LineNumbers, 6055 params: &'a CodeActionParams, 6056 ) -> Self { 6057 Self { 6058 module, 6059 params, 6060 edits: TextEdits::new(line_numbers), 6061 visiting_use_call: false, 6062 argument_to_pipe: None, 6063 } 6064 } 6065 6066 pub fn code_actions(mut self) -> Vec<CodeAction> { 6067 self.visit_typed_module(&self.module.ast); 6068 6069 let Some(ConvertToPipeArg { 6070 called, 6071 call, 6072 position, 6073 arg, 6074 next_arg, 6075 }) = self.argument_to_pipe 6076 else { 6077 return vec![]; 6078 }; 6079 6080 let arg_range = if arg.uses_label_shorthand() { 6081 arg.location.start as usize..arg.location.end as usize - 1 6082 } else if arg.label.is_some() { 6083 let value = arg.value.location(); 6084 value.start as usize..value.end as usize 6085 } else { 6086 arg.location.start as usize..arg.location.end as usize 6087 }; 6088 6089 let arg_text = self.module.code.get(arg_range).expect("invalid srcspan"); 6090 let arg_text = match arg.value { 6091 // If the expression being piped is a binary operation with 6092 // precedence lower than pipes then we have to wrap it in curly 6093 // braces to not mess with the order of operations. 6094 TypedExpr::BinOp { name, .. } if name.precedence() < PIPE_PRECEDENCE => { 6095 &format!("{{ {arg_text} }}") 6096 } 6097 _ => arg_text, 6098 }; 6099 6100 match next_arg { 6101 // When extracting an argument we never want to remove any explicit 6102 // label that was written down, so in case it is labelled (be it a 6103 // shorthand or not) we'll always replace the value with a `_` 6104 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()), 6105 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()), 6106 6107 // Now we can deal with unlabelled arguments: 6108 // If we're removing the first argument and there's other arguments 6109 // after it, we need to delete the comma that was separating the 6110 // two. 6111 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan { 6112 start: arg.location.start, 6113 end: next_arg.start, 6114 }), 6115 // Otherwise, if we're deleting the first argument and there's 6116 // no other arguments following it, we remove the call's 6117 // parentheses. 6118 None if position == 0 => self.edits.delete(SrcSpan { 6119 start: called.end, 6120 end: call.end, 6121 }), 6122 // In all other cases we're piping something that is not the first 6123 // argument so we just replace it with an `_`. 6124 _ => self.edits.replace(arg.location, "_".into()), 6125 }; 6126 6127 // Finally we can add the argument that was removed as the first step 6128 // of the newly defined pipeline. 6129 self.edits.insert(call.start, format!("{arg_text} |> ")); 6130 6131 let mut action = Vec::with_capacity(1); 6132 CodeActionBuilder::new("Convert to pipe") 6133 .kind(CodeActionKind::REFACTOR_REWRITE) 6134 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6135 .preferred(false) 6136 .push_to(&mut action); 6137 action 6138 } 6139} 6140 6141impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> { 6142 fn visit_typed_expr_call( 6143 &mut self, 6144 location: &'ast SrcSpan, 6145 _type_: &'ast Arc<Type>, 6146 fun: &'ast TypedExpr, 6147 args: &'ast [TypedCallArg], 6148 ) { 6149 if args.iter().any(|arg| arg.is_capture_hole()) { 6150 return; 6151 } 6152 6153 // If we're visiting the typed function produced by typing a use, we 6154 // skip the thing itself and only visit its arguments and called 6155 // function, that is the body of the use. 6156 if self.visiting_use_call { 6157 self.visiting_use_call = false; 6158 ast::visit::visit_typed_expr(self, fun); 6159 args.iter() 6160 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg)); 6161 return; 6162 } 6163 6164 // We only visit a call if the cursor is somewhere within its location, 6165 // otherwise we skip it entirely. 6166 let call_range = self.edits.src_span_to_lsp_range(*location); 6167 if !within(self.params.range, call_range) { 6168 return; 6169 } 6170 6171 // If the cursor is over any of the arguments then we'll use that as 6172 // the one to extract. 6173 // Otherwise the cursor must be over the called function, in that case 6174 // we extract the first argument (if there's one): 6175 // 6176 // ```gleam 6177 // wibble(wobble, woo) 6178 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here 6179 // // ^^^ pipe the second argument if I'm here 6180 // ``` 6181 let argument_to_pipe = args 6182 .iter() 6183 .enumerate() 6184 .find_map(|(position, arg)| { 6185 let arg_range = self.edits.src_span_to_lsp_range(arg.location); 6186 if within(self.params.range, arg_range) { 6187 Some((position, arg)) 6188 } else { 6189 None 6190 } 6191 }) 6192 .or_else(|| args.first().map(|arg| (0, arg))); 6193 6194 // If we're not hovering over any of the arguments _or_ there's no 6195 // argument to extract at all we just return, there's nothing we can do 6196 // on this call or any of its arguments (since we've determined the 6197 // cursor is not over any of those). 6198 let Some((position, arg)) = argument_to_pipe else { 6199 return; 6200 }; 6201 6202 self.argument_to_pipe = Some(ConvertToPipeArg { 6203 called: fun.location(), 6204 call: *location, 6205 position, 6206 arg, 6207 next_arg: args.get(position + 1).map(|arg| arg.location), 6208 }) 6209 } 6210 6211 fn visit_typed_expr_pipeline( 6212 &mut self, 6213 _location: &'ast SrcSpan, 6214 first_value: &'ast TypedPipelineAssignment, 6215 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 6216 _finally: &'ast TypedExpr, 6217 _finally_kind: &'ast PipelineAssignmentKind, 6218 ) { 6219 // We can only apply the action on the first step of a pipeline, so we 6220 // visit just that one and skip all the others. 6221 ast::visit::visit_typed_pipeline_assignment(self, first_value); 6222 } 6223 6224 fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 6225 self.visiting_use_call = true; 6226 ast::visit::visit_typed_use(self, use_); 6227 } 6228} 6229 6230/// Code action to interpolate a string. If the cursor is inside the string 6231/// (not selecting anything) the language server will offer to split it: 6232/// 6233/// ```gleam 6234/// "wibble | wobble" 6235/// // ^ [Split string] 6236/// // Will produce the following 6237/// "wibble " <> todo <> " wobble" 6238/// ``` 6239/// 6240/// If the cursor is selecting an entire valid gleam name, then the language 6241/// server will offer to interpolate it as a variable: 6242/// 6243/// ```gleam 6244/// "wibble wobble woo" 6245/// // ^^^^^^ [Interpolate variable] 6246/// // Will produce the following 6247/// "wibble " <> wobble <> " woo" 6248/// ``` 6249/// 6250/// > Note: the cursor won't end up right after the inserted variable/todo. 6251/// > that's a bit annoying, but in a future LSP version we will be able to 6252/// > isnert tab stops to allow one to jump to the newly added variable/todo. 6253/// 6254pub struct InterpolateString<'a> { 6255 module: &'a Module, 6256 params: &'a CodeActionParams, 6257 edits: TextEdits<'a>, 6258 string_interpolation: Option<(SrcSpan, StringInterpolation)>, 6259 string_literal_position: StringLiteralPosition, 6260} 6261 6262#[derive(Debug, Clone, Copy, Eq, PartialEq)] 6263pub enum StringLiteralPosition { 6264 FirstPipelineStep, 6265 Other, 6266} 6267 6268#[derive(Clone, Copy)] 6269enum StringInterpolation { 6270 InterpolateValue { value_location: SrcSpan }, 6271 SplitString { split_at: u32 }, 6272} 6273 6274impl<'a> InterpolateString<'a> { 6275 pub fn new( 6276 module: &'a Module, 6277 line_numbers: &'a LineNumbers, 6278 params: &'a CodeActionParams, 6279 ) -> Self { 6280 Self { 6281 module, 6282 params, 6283 edits: TextEdits::new(line_numbers), 6284 string_interpolation: None, 6285 string_literal_position: StringLiteralPosition::Other, 6286 } 6287 } 6288 6289 pub fn code_actions(mut self) -> Vec<CodeAction> { 6290 self.visit_typed_module(&self.module.ast); 6291 6292 let Some((string_location, interpolation)) = self.string_interpolation else { 6293 return vec![]; 6294 }; 6295 6296 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 6297 self.edits.insert(string_location.start, "{ ".into()); 6298 } 6299 6300 match interpolation { 6301 StringInterpolation::InterpolateValue { value_location } => { 6302 let name = self 6303 .module 6304 .code 6305 .get(value_location.start as usize..value_location.end as usize) 6306 .expect("invalid value range"); 6307 6308 if is_valid_lowercase_name(name) { 6309 self.edits 6310 .insert(value_location.start, format!("\" <> {name} <> \"")); 6311 self.edits.delete(value_location); 6312 } else if self.can_split_string_at(value_location.end) { 6313 // If the string is not a valid name we just try and split 6314 // the string at the end of the selection. 6315 self.edits 6316 .insert(value_location.end, "\" <> todo <> \"".into()); 6317 } else { 6318 // Otherwise there's no meaningful action we can do. 6319 return vec![]; 6320 } 6321 } 6322 6323 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => { 6324 self.edits.insert(split_at, "\" <> todo <> \"".into()); 6325 } 6326 6327 StringInterpolation::SplitString { .. } => return vec![], 6328 }; 6329 6330 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { 6331 self.edits.insert(string_location.end, " }".into()); 6332 } 6333 6334 let mut action = Vec::with_capacity(1); 6335 CodeActionBuilder::new("Interpolate string") 6336 .kind(CodeActionKind::REFACTOR_REWRITE) 6337 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6338 .preferred(false) 6339 .push_to(&mut action); 6340 action 6341 } 6342 6343 fn can_split_string_at(&self, at: u32) -> bool { 6344 self.string_interpolation 6345 .is_some_and(|(string_location, _)| { 6346 !(at <= string_location.start + 1 || at >= string_location.end - 1) 6347 }) 6348 } 6349 6350 fn visit_literal_string( 6351 &mut self, 6352 string_location: SrcSpan, 6353 string_position: StringLiteralPosition, 6354 ) { 6355 // We can only interpolate/split a string if the cursor is somewhere 6356 // within its location, otherwise we skip it. 6357 let string_range = self.edits.src_span_to_lsp_range(string_location); 6358 if !within(self.params.range, string_range) { 6359 return; 6360 } 6361 6362 let selection @ SrcSpan { start, end } = 6363 self.edits.lsp_range_to_src_span(self.params.range); 6364 6365 let interpolation = if start == end { 6366 StringInterpolation::SplitString { split_at: start } 6367 } else { 6368 StringInterpolation::InterpolateValue { 6369 value_location: selection, 6370 } 6371 }; 6372 self.string_interpolation = Some((string_location, interpolation)); 6373 self.string_literal_position = string_position; 6374 } 6375} 6376 6377impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> { 6378 fn visit_typed_expr_string( 6379 &mut self, 6380 location: &'ast SrcSpan, 6381 _type_: &'ast Arc<Type>, 6382 _value: &'ast EcoString, 6383 ) { 6384 self.visit_literal_string(*location, StringLiteralPosition::Other); 6385 } 6386 6387 fn visit_typed_expr_pipeline( 6388 &mut self, 6389 _location: &'ast SrcSpan, 6390 first_value: &'ast TypedPipelineAssignment, 6391 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], 6392 finally: &'ast TypedExpr, 6393 _finally_kind: &'ast PipelineAssignmentKind, 6394 ) { 6395 if first_value.value.is_literal_string() { 6396 self.visit_literal_string( 6397 first_value.location, 6398 StringLiteralPosition::FirstPipelineStep, 6399 ); 6400 } else { 6401 ast::visit::visit_typed_pipeline_assignment(self, first_value); 6402 } 6403 6404 assignments 6405 .iter() 6406 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a)); 6407 self.visit_typed_expr(finally); 6408 } 6409} 6410 6411/// Code action to replace a `..` in a pattern with all the missing fields that 6412/// have not been explicitly provided; labelled ones are introduced with the 6413/// shorthand syntax. 6414/// 6415/// ```gleam 6416/// pub type Pokemon { 6417/// Pokemon(Int, name: String, moves: List(String)) 6418/// } 6419/// 6420/// pub fn main() { 6421/// let Pokemon(..) = todo 6422/// // ^^ Cursor over the spread 6423/// } 6424/// ``` 6425/// Would become 6426/// ```gleam 6427/// pub fn main() { 6428/// let Pokemon(int, name:, moves:) = todo 6429/// } 6430/// 6431pub struct FillUnusedFields<'a> { 6432 module: &'a Module, 6433 params: &'a CodeActionParams, 6434 edits: TextEdits<'a>, 6435 data: Option<FillUnusedFieldsData>, 6436} 6437 6438pub struct FillUnusedFieldsData { 6439 /// All the missing positional and labelled fields. 6440 positional: Vec<Arc<Type>>, 6441 labelled: Vec<(EcoString, Arc<Type>)>, 6442 /// We need this in order to tell where the missing positional arguments 6443 /// should be inserted. 6444 first_labelled_argument_start: Option<u32>, 6445 /// The end of the final argument before the spread, if there's any. 6446 /// We'll use this to delete everything that comes after the final argument, 6447 /// after adding all the ignored fields. 6448 last_argument_end: Option<u32>, 6449 spread_location: SrcSpan, 6450} 6451 6452impl<'a> FillUnusedFields<'a> { 6453 pub fn new( 6454 module: &'a Module, 6455 line_numbers: &'a LineNumbers, 6456 params: &'a CodeActionParams, 6457 ) -> Self { 6458 Self { 6459 module, 6460 params, 6461 edits: TextEdits::new(line_numbers), 6462 data: None, 6463 } 6464 } 6465 6466 pub fn code_actions(mut self) -> Vec<CodeAction> { 6467 self.visit_typed_module(&self.module.ast); 6468 6469 let Some(FillUnusedFieldsData { 6470 positional, 6471 labelled, 6472 first_labelled_argument_start, 6473 last_argument_end, 6474 spread_location, 6475 }) = self.data 6476 else { 6477 return vec![]; 6478 }; 6479 6480 // Do not suggest this code action if there's no ignored fields at all. 6481 if positional.is_empty() && labelled.is_empty() { 6482 return vec![]; 6483 }; 6484 6485 // We add all the missing positional arguments before the first 6486 // labelled one (and so after all the already existing positional ones). 6487 if !positional.is_empty() { 6488 // We want to make sure that all positional args will have a name 6489 // that's different from any label. So we add those as already used 6490 // names. 6491 let mut names = NameGenerator::new(); 6492 for (label, _) in labelled.iter() { 6493 names.add_used_name(label.clone()); 6494 } 6495 6496 let positional_args = positional 6497 .iter() 6498 .map(|type_| names.generate_name_from_type(type_)) 6499 .join(", "); 6500 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); 6501 6502 // The positional arguments are going to be followed by some other 6503 // arguments if there's some already existing labelled args 6504 // (`last_argument_end.is_some`), of if we're adding those labelled args 6505 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the 6506 // final positional argument we're adding to separate it from the ones that 6507 // are going to come after. 6508 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); 6509 let positional_args = if has_arguments_after { 6510 format!("{positional_args}, ") 6511 } else { 6512 positional_args 6513 }; 6514 6515 self.edits.insert(insert_at, positional_args); 6516 } 6517 6518 if !labelled.is_empty() { 6519 // If there's labelled arguments to add, we replace the existing spread 6520 // with the arguments to be added. This way commas and all should already 6521 // be correct. 6522 let labelled_args = labelled 6523 .iter() 6524 .map(|(label, _)| format!("{label}:")) 6525 .join(", "); 6526 self.edits.replace(spread_location, labelled_args); 6527 } else if let Some(delete_start) = last_argument_end { 6528 // However, if there's no labelled arguments to insert we still need 6529 // to delete the entire spread: we start deleting from the end of the 6530 // final argument, if there's one. 6531 // This way we also get rid of any comma separating the last argument 6532 // and the spread to be removed. 6533 self.edits 6534 .delete(SrcSpan::new(delete_start, spread_location.end)) 6535 } else { 6536 // Otherwise we just delete the spread. 6537 self.edits.delete(spread_location) 6538 } 6539 6540 let mut action = Vec::with_capacity(1); 6541 CodeActionBuilder::new("Fill unused fields") 6542 .kind(CodeActionKind::REFACTOR_REWRITE) 6543 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6544 .preferred(false) 6545 .push_to(&mut action); 6546 action 6547 } 6548} 6549 6550impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> { 6551 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { 6552 // We can only interpolate/split a string if the cursor is somewhere 6553 // within its location, otherwise we skip it. 6554 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); 6555 if !within(self.params.range, pattern_range) { 6556 return; 6557 } 6558 6559 if let TypedPattern::Constructor { 6560 arguments, 6561 spread: Some(spread_location), 6562 .. 6563 } = pattern 6564 { 6565 if let Some(PatternUnusedArguments { 6566 positional, 6567 labelled, 6568 }) = pattern.unused_arguments() 6569 { 6570 // If there's any unused argument that's being ignored we want to 6571 // suggest the code action. 6572 let first_labelled_argument_start = arguments 6573 .iter() 6574 .find(|arg| !arg.is_implicit() && arg.label.is_some()) 6575 .map(|arg| arg.location.start); 6576 6577 let last_argument_end = arguments 6578 .iter() 6579 .filter(|arg| !arg.is_implicit()) 6580 .next_back() 6581 .map(|arg| arg.location.end); 6582 6583 self.data = Some(FillUnusedFieldsData { 6584 positional, 6585 labelled, 6586 first_labelled_argument_start, 6587 last_argument_end, 6588 spread_location: *spread_location, 6589 }); 6590 }; 6591 } 6592 6593 ast::visit::visit_typed_pattern(self, pattern); 6594 } 6595} 6596 6597/// Code action to remove an echo. 6598/// 6599pub struct RemoveEchos<'a> { 6600 module: &'a Module, 6601 params: &'a CodeActionParams, 6602 edits: TextEdits<'a>, 6603 is_hovering_echo: bool, 6604 echo_spans_to_delete: Vec<SrcSpan>, 6605 // We need to keep a reference to the two latest pipeline assignments we 6606 // run into to properly delete an echo that's inside a pipeline. 6607 latest_pipe_step: Option<SrcSpan>, 6608 second_to_latest_pipe_step: Option<SrcSpan>, 6609} 6610 6611impl<'a> RemoveEchos<'a> { 6612 pub fn new( 6613 module: &'a Module, 6614 line_numbers: &'a LineNumbers, 6615 params: &'a CodeActionParams, 6616 ) -> Self { 6617 Self { 6618 module, 6619 params, 6620 edits: TextEdits::new(line_numbers), 6621 is_hovering_echo: false, 6622 echo_spans_to_delete: vec![], 6623 latest_pipe_step: None, 6624 second_to_latest_pipe_step: None, 6625 } 6626 } 6627 6628 pub fn code_actions(mut self) -> Vec<CodeAction> { 6629 self.visit_typed_module(&self.module.ast); 6630 6631 // We only want to trigger the action if we're over one of the echos in 6632 // the module 6633 if !self.is_hovering_echo { 6634 return vec![]; 6635 }; 6636 6637 for span in self.echo_spans_to_delete { 6638 self.edits.delete(span); 6639 } 6640 6641 let mut action = Vec::with_capacity(1); 6642 CodeActionBuilder::new("Remove all `echo`s from this module") 6643 .kind(CodeActionKind::REFACTOR_REWRITE) 6644 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6645 .preferred(false) 6646 .push_to(&mut action); 6647 action 6648 } 6649} 6650 6651impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> { 6652 fn visit_typed_expr_echo( 6653 &mut self, 6654 location: &'ast SrcSpan, 6655 type_: &'ast Arc<Type>, 6656 expression: &'ast Option<Box<TypedExpr>>, 6657 ) { 6658 // We also want to trigger the action if we're hovering over the expression 6659 // being printed. So we create a unique span starting from the start of echo 6660 // end ending at the end of the expression. 6661 // 6662 // ``` 6663 // echo 1 + 2 6664 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're 6665 // inside it, not just the keyword 6666 // ``` 6667 // 6668 let echo_range = self.edits.src_span_to_lsp_range(*location); 6669 if within(self.params.range, echo_range) { 6670 self.is_hovering_echo = true; 6671 } 6672 6673 if let Some(expression) = expression { 6674 // If there's an expression we delete everything we find until its 6675 // start (excluded). 6676 let span_to_delete = SrcSpan::new(location.start, expression.location().start); 6677 self.echo_spans_to_delete.push(span_to_delete); 6678 } else { 6679 // Othwerise we know we're inside a pipeline, we take the closest step 6680 // that is not echo itself and delete everything from its end until the 6681 // end of the echo keyword: 6682 // 6683 // ```txt 6684 // wibble |> echo |> wobble 6685 // ^^^^^^^^ This span right here 6686 // ``` 6687 let step_preceding_echo = self 6688 .latest_pipe_step 6689 .filter(|l| l != location) 6690 .or(self.second_to_latest_pipe_step); 6691 if let Some(step_preceding_echo) = step_preceding_echo { 6692 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4); 6693 self.echo_spans_to_delete.push(span_to_delete); 6694 } 6695 } 6696 6697 ast::visit::visit_typed_expr_echo(self, location, type_, expression); 6698 } 6699 6700 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { 6701 if self.latest_pipe_step.is_some() { 6702 self.second_to_latest_pipe_step = self.latest_pipe_step; 6703 } 6704 self.latest_pipe_step = Some(assignment.location); 6705 ast::visit::visit_typed_pipeline_assignment(self, assignment); 6706 } 6707} 6708 6709/// Code action to wrap assignment and case clause values in a block. 6710/// 6711/// ```gleam 6712/// pub type PokemonType { 6713/// Fire 6714/// Water 6715/// } 6716/// 6717/// pub fn main() { 6718/// let pokemon_type: PokemonType = todo 6719/// case pokemon_type { 6720/// Water -> soak() 6721/// ^^^^^^ Cursor over the spread 6722/// Fire -> burn() 6723/// } 6724/// } 6725/// ``` 6726/// Becomes 6727/// ```gleam 6728/// pub type PokemonType { 6729/// Fire 6730/// Water 6731/// } 6732/// 6733/// pub fn main() { 6734/// let pokemon_type: PokemonType = todo 6735/// case pokemon_type { 6736/// Water -> { 6737/// soak() 6738/// } 6739/// Fire -> burn() 6740/// } 6741/// } 6742/// ``` 6743/// 6744pub struct WrapInBlock<'a> { 6745 module: &'a Module, 6746 params: &'a CodeActionParams, 6747 edits: TextEdits<'a>, 6748 selected_expression: Option<SrcSpan>, 6749} 6750 6751impl<'a> WrapInBlock<'a> { 6752 pub fn new( 6753 module: &'a Module, 6754 line_numbers: &'a LineNumbers, 6755 params: &'a CodeActionParams, 6756 ) -> Self { 6757 Self { 6758 module, 6759 params, 6760 edits: TextEdits::new(line_numbers), 6761 selected_expression: None, 6762 } 6763 } 6764 6765 pub fn code_actions(mut self) -> Vec<CodeAction> { 6766 self.visit_typed_module(&self.module.ast); 6767 6768 let Some(expr_span) = self.selected_expression else { 6769 return vec![]; 6770 }; 6771 6772 let Some(expr_string) = self 6773 .module 6774 .code 6775 .get(expr_span.start as usize..(expr_span.end as usize + 1)) 6776 else { 6777 return vec![]; 6778 }; 6779 6780 let range = self 6781 .edits 6782 .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); 6783 6784 let indent_size = 6785 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); 6786 6787 let expr_indent_size = indent_size + 2; 6788 6789 let indent = " ".repeat(indent_size); 6790 let inner_indent = " ".repeat(expr_indent_size); 6791 6792 self.edits.replace( 6793 expr_span, 6794 format!("{{\n{inner_indent}{expr_string}{indent}}}"), 6795 ); 6796 6797 let mut action = Vec::with_capacity(1); 6798 CodeActionBuilder::new("Wrap in block") 6799 .kind(CodeActionKind::REFACTOR_EXTRACT) 6800 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6801 .preferred(false) 6802 .push_to(&mut action); 6803 action 6804 } 6805} 6806 6807impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> { 6808 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { 6809 ast::visit::visit_typed_expr(self, &assignment.value); 6810 if !within( 6811 self.params.range, 6812 self.edits 6813 .src_span_to_lsp_range(assignment.value.location()), 6814 ) { 6815 return; 6816 } 6817 match assignment.value.as_ref() { 6818 // To avoid wrapping the same expression in multiple, nested blocks. 6819 TypedExpr::Block { .. } => {} 6820 TypedExpr::RecordAccess { .. } 6821 | TypedExpr::Int { .. } 6822 | TypedExpr::Float { .. } 6823 | TypedExpr::String { .. } 6824 | TypedExpr::Pipeline { .. } 6825 | TypedExpr::Var { .. } 6826 | TypedExpr::Fn { .. } 6827 | TypedExpr::List { .. } 6828 | TypedExpr::Call { .. } 6829 | TypedExpr::BinOp { .. } 6830 | TypedExpr::Case { .. } 6831 | TypedExpr::ModuleSelect { .. } 6832 | TypedExpr::Tuple { .. } 6833 | TypedExpr::TupleIndex { .. } 6834 | TypedExpr::Todo { .. } 6835 | TypedExpr::Panic { .. } 6836 | TypedExpr::Echo { .. } 6837 | TypedExpr::BitArray { .. } 6838 | TypedExpr::RecordUpdate { .. } 6839 | TypedExpr::NegateBool { .. } 6840 | TypedExpr::NegateInt { .. } 6841 | TypedExpr::Invalid { .. } => { 6842 self.selected_expression = Some(assignment.value.location()); 6843 } 6844 }; 6845 ast::visit::visit_typed_assignment(self, assignment); 6846 } 6847 6848 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { 6849 ast::visit::visit_typed_clause(self, clause); 6850 6851 if !within( 6852 self.params.range, 6853 self.edits.src_span_to_lsp_range(clause.then.location()), 6854 ) { 6855 return; 6856 } 6857 6858 // To avoid wrapping the same expression in multiple, nested blocks. 6859 if !matches!(clause.then, TypedExpr::Block { .. }) { 6860 self.selected_expression = Some(clause.then.location()); 6861 }; 6862 6863 ast::visit::visit_typed_clause(self, clause); 6864 } 6865} 6866 6867/// Code action to fix wrong binary operators when the compiler can easily tell 6868/// what the correct alternative is. 6869/// 6870/// ```gleam 6871/// 6872/// ``` 6873/// 6874pub struct FixBinaryOperation<'a> { 6875 module: &'a Module, 6876 params: &'a CodeActionParams, 6877 edits: TextEdits<'a>, 6878 fix: Option<(SrcSpan, ast::BinOp)>, 6879} 6880 6881impl<'a> FixBinaryOperation<'a> { 6882 pub fn new( 6883 module: &'a Module, 6884 line_numbers: &'a LineNumbers, 6885 params: &'a CodeActionParams, 6886 ) -> Self { 6887 Self { 6888 module, 6889 params, 6890 edits: TextEdits::new(line_numbers), 6891 fix: None, 6892 } 6893 } 6894 6895 pub fn code_actions(mut self) -> Vec<CodeAction> { 6896 self.visit_typed_module(&self.module.ast); 6897 6898 let Some((location, replacement)) = self.fix else { 6899 return vec![]; 6900 }; 6901 6902 self.edits.replace(location, replacement.name().into()); 6903 6904 let mut action = Vec::with_capacity(1); 6905 CodeActionBuilder::new(&format!("Use `{}`", replacement.name())) 6906 .kind(CodeActionKind::REFACTOR_REWRITE) 6907 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6908 .preferred(true) 6909 .push_to(&mut action); 6910 action 6911 } 6912} 6913 6914impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> { 6915 fn visit_typed_expr_bin_op( 6916 &mut self, 6917 location: &'ast SrcSpan, 6918 type_: &'ast Arc<Type>, 6919 name: &'ast ast::BinOp, 6920 name_location: &'ast SrcSpan, 6921 left: &'ast TypedExpr, 6922 right: &'ast TypedExpr, 6923 ) { 6924 let binop_range = self.edits.src_span_to_lsp_range(*location); 6925 if !within(self.params.range, binop_range) { 6926 return; 6927 } 6928 6929 if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() { 6930 self.fix = name.float_equivalent().map(|fix| (*name_location, fix)); 6931 } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() { 6932 self.fix = name.int_equivalent().map(|fix| (*name_location, fix)) 6933 } else if *name == ast::BinOp::AddInt 6934 && left.type_().is_string() 6935 && right.type_().is_string() 6936 { 6937 self.fix = Some((*name_location, ast::BinOp::Concatenate)) 6938 } 6939 6940 ast::visit::visit_typed_expr_bin_op( 6941 self, 6942 location, 6943 type_, 6944 name, 6945 name_location, 6946 left, 6947 right, 6948 ); 6949 } 6950} 6951 6952/// Code action builder to automatically fix segments that have a value that's 6953/// guaranteed to overflow. 6954/// 6955pub struct FixTruncatedBitArraySegment<'a> { 6956 module: &'a Module, 6957 params: &'a CodeActionParams, 6958 edits: TextEdits<'a>, 6959 truncation: Option<BitArraySegmentTruncation>, 6960} 6961 6962impl<'a> FixTruncatedBitArraySegment<'a> { 6963 pub fn new( 6964 module: &'a Module, 6965 line_numbers: &'a LineNumbers, 6966 params: &'a CodeActionParams, 6967 ) -> Self { 6968 Self { 6969 module, 6970 params, 6971 edits: TextEdits::new(line_numbers), 6972 truncation: None, 6973 } 6974 } 6975 6976 pub fn code_actions(mut self) -> Vec<CodeAction> { 6977 self.visit_typed_module(&self.module.ast); 6978 6979 let Some(truncation) = self.truncation else { 6980 return vec![]; 6981 }; 6982 6983 let replacement = truncation.truncated_into.to_string(); 6984 self.edits 6985 .replace(truncation.value_location, replacement.clone()); 6986 6987 let mut action = Vec::with_capacity(1); 6988 CodeActionBuilder::new(&format!("Replace with `{}`", replacement)) 6989 .kind(CodeActionKind::REFACTOR_REWRITE) 6990 .changes(self.params.text_document.uri.clone(), self.edits.edits) 6991 .preferred(true) 6992 .push_to(&mut action); 6993 action 6994 } 6995} 6996 6997impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> { 6998 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) { 6999 let segment_range = self.edits.src_span_to_lsp_range(segment.location); 7000 if !within(self.params.range, segment_range) { 7001 return; 7002 } 7003 7004 if let Some(truncation) = segment.check_for_truncated_value() { 7005 self.truncation = Some(truncation); 7006 } 7007 7008 ast::visit::visit_typed_expr_bit_array_segment(self, segment); 7009 } 7010} 7011 7012/// Code action builder to remove unused imports and values. 7013/// 7014pub struct RemoveUnusedImports<'a> { 7015 module: &'a Module, 7016 params: &'a CodeActionParams, 7017 imports: Vec<&'a Import<EcoString>>, 7018 edits: TextEdits<'a>, 7019} 7020 7021#[derive(Debug)] 7022enum UnusedImport { 7023 Value(SrcSpan), 7024 Module(SrcSpan), 7025 ModuleAlias(SrcSpan), 7026} 7027 7028impl UnusedImport { 7029 fn location(&self) -> SrcSpan { 7030 match self { 7031 UnusedImport::Value(location) 7032 | UnusedImport::Module(location) 7033 | UnusedImport::ModuleAlias(location) => *location, 7034 } 7035 } 7036} 7037 7038impl<'a> RemoveUnusedImports<'a> { 7039 pub fn new( 7040 module: &'a Module, 7041 line_numbers: &'a LineNumbers, 7042 params: &'a CodeActionParams, 7043 ) -> Self { 7044 Self { 7045 module, 7046 params, 7047 edits: TextEdits::new(line_numbers), 7048 imports: vec![], 7049 } 7050 } 7051 7052 pub fn code_actions(mut self) -> Vec<CodeAction> { 7053 // If there's no import in the module then there can't be any unused 7054 // import to remove. 7055 self.visit_typed_module(&self.module.ast); 7056 if self.imports.is_empty() { 7057 return vec![]; 7058 } 7059 7060 let unused_imports = (self.module.ast.type_info.warnings.iter()) 7061 .filter_map(|warning| match warning { 7062 type_::Warning::UnusedImportedValue { location, .. } => { 7063 Some(UnusedImport::Value(*location)) 7064 } 7065 type_::Warning::UnusedImportedModule { location, .. } => { 7066 Some(UnusedImport::Module(*location)) 7067 } 7068 type_::Warning::UnusedImportedModuleAlias { location, .. } => { 7069 Some(UnusedImport::ModuleAlias(*location)) 7070 } 7071 _ => None, 7072 }) 7073 .collect_vec(); 7074 7075 println!("{:#?}", unused_imports); 7076 println!("{:#?}", self.params.range); 7077 7078 // If the cursor is not over any of the unused imports then we don't offer 7079 // the code action. 7080 let hovering_unused_import = unused_imports.iter().any(|import| { 7081 let unused_range = self.edits.src_span_to_lsp_range(import.location()); 7082 overlaps(self.params.range, unused_range) 7083 }); 7084 if !hovering_unused_import { 7085 return vec![]; 7086 } 7087 7088 // Otherwise we start removing all unused imports: 7089 for import in unused_imports { 7090 match import { 7091 // When an entire module is unused we can delete its entire location 7092 // in the source code. 7093 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { 7094 if self.edits.line_numbers.spans_entire_line(&location) { 7095 // If the unused module spans over the entire line then 7096 // we also take care of removing the following newline 7097 // characther! 7098 self.edits.delete(SrcSpan { 7099 start: location.start, 7100 end: location.end + 1, 7101 }) 7102 } else { 7103 self.edits.delete(location) 7104 } 7105 } 7106 7107 // When removing unused imported values we have to be a bit more 7108 // careful: an unused value might be followed by a comma that we 7109 // also need to remove! So we have to look for this imported 7110 // value and also delete all characters between it and the 7111 // following imported value. For example: 7112 // 7113 // ```gleam 7114 // import wibble.{wobble, woo} 7115 // // ^^^^^^^^^^^ If wobble is unused we must remove 7116 // // all of this! 7117 // ``` 7118 // 7119 UnusedImport::Value(_) => continue, 7120 } 7121 } 7122 7123 let mut action = Vec::with_capacity(1); 7124 CodeActionBuilder::new("Remove unused imports") 7125 .kind(CodeActionKind::REFACTOR_REWRITE) 7126 .changes(self.params.text_document.uri.clone(), self.edits.edits) 7127 .preferred(true) 7128 .push_to(&mut action); 7129 action 7130 } 7131} 7132 7133impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> { 7134 fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) { 7135 self.imports = module 7136 .definitions 7137 .iter() 7138 .filter_map(|definition| match definition { 7139 ast::Definition::Import(import) => Some(import), 7140 _ => None, 7141 }) 7142 .collect_vec(); 7143 } 7144}