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

Configure Feed

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

fix record field label range and support renaming from the declaration

+176 -25
+3 -3
CHANGELOG.md
··· 44 44 ### Language server 45 45 46 46 - The language server now supports go-to-definition, find-references and rename 47 - for record fields. These work on labelled arguments, on labelled patterns, on 48 - record updates and on `record.field` accesses, both within a module and across 49 - modules. For example: 47 + for record fields. These work on the field declaration, on labelled arguments, 48 + on labelled patterns, on record updates and on `record.field` accesses, both 49 + within a module and across modules. For example: 50 50 51 51 ```gleam 52 52 pub type Person {
+37 -10
compiler-core/src/ast.rs
··· 17 17 use crate::analyse::Inferred; 18 18 use crate::ast::typed::pairwise_all; 19 19 use crate::bit_array; 20 - use crate::build::{ExpressionPosition, Located, Target, module_erlang_name}; 20 + use crate::build::{ExpressionPosition, LabelContainer, Located, Target, module_erlang_name}; 21 21 use crate::exhaustiveness::CompiledCase; 22 22 use crate::parse::{LiteralFloatValue, SpannedString}; 23 23 use crate::type_::error::VariableOrigin; ··· 1146 1146 .iter() 1147 1147 .find(|constructor| constructor.location.contains(byte_index)) 1148 1148 { 1149 - if let Some(annotation) = constructor 1149 + if let Some(argument) = constructor 1150 1150 .arguments 1151 1151 .iter() 1152 - .find(|arg| arg.location.contains(byte_index)) 1153 - .and_then(|arg| arg.ast.find_node(byte_index, arg.type_.clone())) 1152 + .find(|argument| argument.location.contains(byte_index)) 1154 1153 { 1155 - return Some(annotation); 1154 + // The cursor is on the field's label in its declaration. 1155 + if let Some((label_location, label)) = &argument.label 1156 + && label_location.contains(byte_index) 1157 + { 1158 + return Some(Located::Label { 1159 + location: *label_location, 1160 + type_: argument.type_.clone(), 1161 + label: label.clone(), 1162 + container: Some(LabelContainer::Definition { 1163 + type_name: self.name.clone(), 1164 + constructor: constructor.name.clone(), 1165 + }), 1166 + }); 1167 + } 1168 + 1169 + // The cursor is on the field's type annotation. 1170 + if let Some(annotation) = argument.ast.find_node(byte_index, argument.type_.clone()) 1171 + { 1172 + return Some(annotation); 1173 + } 1156 1174 } 1157 1175 1158 1176 return Some(Located::VariantConstructorDefinition(constructor)); ··· 1722 1740 location: self.location, 1723 1741 type_: self.value.type_(), 1724 1742 label: label.clone(), 1725 - container: called_function 1726 - .record_constructor_name() 1727 - .map(|c| (container_type, c)), 1743 + container: called_function.record_constructor_name().map(|c| { 1744 + LabelContainer::Usage { 1745 + type_: container_type, 1746 + constructor: c, 1747 + } 1748 + }), 1728 1749 }) 1729 1750 } else { 1730 1751 None ··· 1792 1813 location: self.location, 1793 1814 type_: self.value.type_(), 1794 1815 label: label.clone(), 1795 - container: constructor.map(|c| (container_type.clone(), c.clone())), 1816 + container: constructor.map(|c| LabelContainer::Usage { 1817 + type_: container_type.clone(), 1818 + constructor: c.clone(), 1819 + }), 1796 1820 }) 1797 1821 } else { 1798 1822 None ··· 1819 1843 location: self.location, 1820 1844 type_: self.value.type_(), 1821 1845 label: label.clone(), 1822 - container: constructor.map(|c| (container_type.clone(), c.clone())), 1846 + container: constructor.map(|c| LabelContainer::Usage { 1847 + type_: container_type.clone(), 1848 + constructor: c.clone(), 1849 + }), 1823 1850 }) 1824 1851 } else { 1825 1852 None
+35 -4
compiler-core/src/build.rs
··· 484 484 /// The type of the labelled argument value (used for hover). 485 485 type_: std::sync::Arc<Type>, 486 486 label: EcoString, 487 - /// The type being constructed/matched and the constructor name. 488 - /// Used for go-to-definition. 489 - container: Option<(std::sync::Arc<Type>, EcoString)>, 487 + /// The record type the label belongs to, used for go-to-definition, 488 + /// find-references and rename. `None` when it can't be determined. 489 + container: Option<LabelContainer>, 490 490 }, 491 491 ModuleName { 492 492 location: SrcSpan, ··· 503 503 ModuleImport(&'a TypedImport), 504 504 ModuleCustomType(&'a TypedCustomType), 505 505 ModuleTypeAlias(&'a TypedTypeAlias), 506 + } 507 + 508 + /// The record type a field label belongs to. Used to resolve go-to-definition, 509 + /// find-references and rename for record fields. 510 + #[derive(Debug, Clone, PartialEq)] 511 + pub enum LabelContainer { 512 + /// A label usage (`record.field`, a labelled argument or pattern, a record 513 + /// update) where the value's type is known. The type carries the module 514 + /// and name it belongs to. 515 + Usage { 516 + type_: std::sync::Arc<Type>, 517 + constructor: EcoString, 518 + }, 519 + /// A label at its declaration in a custom type. The type is being defined 520 + /// in the current module, so only its name is needed. 521 + Definition { 522 + type_name: EcoString, 523 + constructor: EcoString, 524 + }, 506 525 } 507 526 508 527 impl<'a> Located<'a> { ··· 609 628 Self::Arg(_) => None, 610 629 Self::Annotation { type_, .. } => self.type_location(importable_modules, type_.clone()), 611 630 Self::Label { 612 - container: Some((container_type, constructor)), 631 + container: Some(LabelContainer::Usage { 632 + type_: container_type, 633 + constructor, 634 + }), 613 635 label, 614 636 .. 615 637 } => self.label_definition_location( ··· 618 640 label, 619 641 Some(constructor), 620 642 ), 643 + // Already at the declaration; go-to-definition jumps to itself. 644 + Self::Label { 645 + container: Some(LabelContainer::Definition { .. }), 646 + location, 647 + .. 648 + } => Some(DefinitionLocation { 649 + module: None, 650 + span: *location, 651 + }), 621 652 Self::Label { .. } => None, 622 653 Self::TypeVariable { .. } => None, 623 654 Self::ModuleName { module_name, .. } => Some(DefinitionLocation {
+21 -5
language-server/src/reference.rs
··· 12 12 self, ArgNames, AssignName, BitArraySize, ClauseGuard, CustomType, Function, 13 13 ModuleConstant, Pattern, RecordConstructor, SrcSpan, TypedExpr, TypedModule, visit::Visit, 14 14 }, 15 - build::Located, 15 + build::{LabelContainer, Located}, 16 16 type_::{ 17 17 ModuleInterface, ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant, 18 18 error::{Named, VariableOrigin}, ··· 383 383 TypedExpr::RecordAccess { 384 384 record, 385 385 label, 386 - field_start, 387 386 location, 388 387 .. 389 388 }, ··· 395 394 type_module, 396 395 type_name, 397 396 label: label.clone(), 398 - location: SrcSpan::new(*field_start, location.end), 397 + // `field_start` is the start of the whole `record.field` 398 + // expression, not the field. The field label is the trailing 399 + // part of the access, so we recover its span from the end. 400 + location: SrcSpan::new(location.end - label.len() as u32, location.end), 399 401 }), 400 402 401 403 Located::Label { 402 - container: Some((container_type, _)), 404 + container: Some(LabelContainer::Usage { type_, .. }), 403 405 label, 404 406 location, 405 407 .. 406 - } => container_type 408 + } => type_ 407 409 .named_type_name() 408 410 .map(|(type_module, type_name)| Referenced::Label { 409 411 type_module, ··· 411 413 label: label.clone(), 412 414 location, 413 415 }), 416 + 417 + // A label at its declaration in a custom type, which lives in the 418 + // current module. 419 + Located::Label { 420 + container: Some(LabelContainer::Definition { type_name, .. }), 421 + label, 422 + location, 423 + .. 424 + } => Some(Referenced::Label { 425 + type_module: current_module.clone(), 426 + type_name, 427 + label: label.clone(), 428 + location, 429 + }), 414 430 415 431 Located::Pattern(_) 416 432 | Located::ClauseGuard(_)
+11
language-server/src/tests/definition.rs
··· 1032 1032 } 1033 1033 1034 1034 #[test] 1035 + fn goto_definition_record_field_from_declaration() { 1036 + assert_goto!( 1037 + " 1038 + pub type Person { 1039 + Person(name: String, age: Int) 1040 + }", 1041 + find_position_of("name: String").under_char('n') 1042 + ); 1043 + } 1044 + 1045 + #[test] 1035 1046 fn goto_definition_constructor_label() { 1036 1047 assert_goto!( 1037 1048 "
+18
language-server/src/tests/rename.rs
··· 2950 2950 find_position_of("value.wibble").under_char('i') 2951 2951 ); 2952 2952 } 2953 + 2954 + #[test] 2955 + fn rename_record_field_from_definition() { 2956 + assert_rename!( 2957 + " 2958 + type Wibble { 2959 + Wibble(wibble: Int) 2960 + } 2961 + 2962 + pub fn main() { 2963 + let value = Wibble(wibble: 1) 2964 + value.wibble 2965 + } 2966 + ", 2967 + "wobble", 2968 + find_position_of("wibble: Int").under_char('w') 2969 + ); 2970 + }
+18
language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_record_field_from_declaration.snap
··· 1 + --- 2 + source: language-server/src/tests/definition.rs 3 + assertion_line: 1033 4 + expression: output 5 + --- 6 + ----- Jumping from `src/app.gleam` 7 + 8 + pub type Person { 9 + Person(name: String, age: Int) 10 + 11 + } 12 + 13 + ----- Jumped to `src/app.gleam` 14 + 15 + pub type Person { 16 + Person(name: String, age: Int) 17 + ↑▔▔▔ 18 + }
+1 -1
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_record_field_across_modules.snap
··· 16 16 pub fn main() { 17 17 let value = wibble.Wibble(wibble: 1) 18 18 value.wibble 19 - ↑▔▔▔▔▔ 19 + ↑▔▔▔▔▔ 20 20 } 21 21 22 22
+1 -1
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_record_field_expands_label_shorthand.snap
··· 14 14 let wibble = 1 15 15 let value = Wibble(wibble:) 16 16 value.wibble 17 - ↑▔▔▔▔▔ 17 + ↑▔▔▔▔▔ 18 18 } 19 19 20 20
+1 -1
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_record_field_from_access.snap
··· 13 13 pub fn main() { 14 14 let value = Wibble(wibble: 1) 15 15 value.wibble 16 - ↑▔▔▔▔▔ 16 + ↑▔▔▔▔▔ 17 17 } 18 18 19 19
+30
language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_record_field_from_definition.snap
··· 1 + --- 2 + source: language-server/src/tests/rename.rs 3 + assertion_line: 2556 4 + expression: "\ntype Wibble {\n Wibble(wibble: Int)\n}\n\npub fn main() {\n let value = Wibble(wibble: 1)\n value.wibble\n}\n" 5 + --- 6 + ----- BEFORE RENAME 7 + -- app.gleam 8 + 9 + type Wibble { 10 + Wibble(wibble: Int) 11 + ↑▔▔▔▔▔ 12 + } 13 + 14 + pub fn main() { 15 + let value = Wibble(wibble: 1) 16 + value.wibble 17 + } 18 + 19 + 20 + ----- AFTER RENAME 21 + -- app.gleam 22 + 23 + type Wibble { 24 + Wibble(wobble: Int) 25 + } 26 + 27 + pub fn main() { 28 + let value = Wibble(wobble: 1) 29 + value.wobble 30 + }