⭐️ A friendly language for building type-safe, scalable systems!

allow 'pattern match on variable' on use variables

authored by giacomocavalieri.me and committed by Louis Pilfold 98e0ed93 ad4848b0

+31
CHANGELOG.md
··· 160 160 161 161 ### Language server 162 162 163 + - It is now possible to use the "Pattern match on variable" code action on 164 + variables on the left hand side of a `use`. For example: 165 + 166 + ```gleam 167 + pub type User { 168 + User(id: Int, name: String) 169 + } 170 + 171 + pub fn main() { 172 + use user <- result.try(load_user()) 173 + // ^^^^ Triggering the code action here 174 + todo 175 + } 176 + ``` 177 + 178 + Would result in the following code: 179 + 180 + ```gleam 181 + pub type User { 182 + User(id: Int, name: String) 183 + } 184 + 185 + pub fn main() { 186 + use user <- result.try(load_user()) 187 + let User(id:, name:) = user 188 + todo 189 + } 190 + ``` 191 + 192 + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 193 + 163 194 ### Formatter 164 195 165 196 - The formatter now allows more control over how lists are split. By adding a
+12
compiler-core/src/ast.rs
··· 2982 2982 } 2983 2983 self.call.find_node(byte_index) 2984 2984 } 2985 + 2986 + pub fn callback_arguments<'a>(&'a self) -> Option<&'a Vec<TypedArg>> { 2987 + let TypedExpr::Call { args, .. } = self.call.as_ref() else { 2988 + return None; 2989 + }; 2990 + let callback = args.iter().last()?; 2991 + let TypedExpr::Fn { args, .. } = &callback.value else { 2992 + // The expression might be invalid so we have to return a None here 2993 + return None; 2994 + }; 2995 + Some(args) 2996 + } 2985 2997 } 2986 2998 2987 2999 pub type TypedStatement = Statement<Arc<Type>, TypedExpr>;
+53 -3
compiler-core/src/language_server/code_action.rs
··· 3 3 use crate::{ 4 4 Error, STDLIB_PACKAGE_NAME, analyse, 5 5 ast::{ 6 - self, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType, 6 + self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType, 7 7 FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern, 8 8 PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind, 9 9 TypedArg, TypedAssignment, TypedExpr, TypedModuleConstant, TypedPattern, ··· 4498 4498 /// 4499 4499 assignment_location: SrcSpan, 4500 4500 }, 4501 + UseVariable { 4502 + variable_name: &'a EcoString, 4503 + variable_type: &'a Arc<Type>, 4504 + /// The location of the entire use expression the variable is part of, 4505 + /// so that we can add the pattern matching _after_ it. 4506 + /// 4507 + use_location: SrcSpan, 4508 + }, 4501 4509 } 4502 4510 4503 4511 impl<'a, IO> PatternMatchOnValue<'a, IO> ··· 4534 4542 Some(PatternMatchedValue::LetVariable { 4535 4543 variable_name, 4536 4544 variable_type, 4537 - assignment_location, 4545 + assignment_location: location, 4546 + }) 4547 + | Some(PatternMatchedValue::UseVariable { 4548 + variable_name, 4549 + variable_type, 4550 + use_location: location, 4538 4551 }) => { 4539 - self.match_on_let_variable(variable_name, variable_type, assignment_location); 4552 + self.match_on_let_variable(variable_name, variable_type, location); 4540 4553 "Pattern match on variable" 4541 4554 } 4542 4555 None => return vec![], ··· 4877 4890 } 4878 4891 4879 4892 ast::visit::visit_typed_assignment(self, assignment); 4893 + } 4894 + 4895 + fn visit_typed_use(&mut self, use_: &'ast TypedUse) { 4896 + if let Some(assignments) = use_.callback_arguments() { 4897 + for variable in assignments { 4898 + let ast::Arg { 4899 + names: ArgNames::Named { name, .. }, 4900 + location: variable_location, 4901 + type_, 4902 + .. 4903 + } = variable 4904 + else { 4905 + continue; 4906 + }; 4907 + 4908 + // If we use a pattern in a use assignment, that will end up 4909 + // being called `_use` something. We don't want to offer the 4910 + // action when hovering a pattern so we ignore those. 4911 + if name.starts_with("_use") { 4912 + continue; 4913 + } 4914 + 4915 + let variable_range = self.edits.src_span_to_lsp_range(*variable_location); 4916 + if within(self.params.range, variable_range) { 4917 + self.selected_value = Some(PatternMatchedValue::UseVariable { 4918 + variable_name: name, 4919 + variable_type: type_, 4920 + use_location: use_.location, 4921 + }); 4922 + // If we've found the variable to pattern match on, there's no 4923 + // point in keeping traversing the AST. 4924 + return; 4925 + } 4926 + } 4927 + } 4928 + 4929 + ast::visit::visit_typed_use(self, use_); 4880 4930 } 4881 4931 } 4882 4932
+50
compiler-core/src/language_server/tests/action.rs
··· 6988 6988 } 6989 6989 6990 6990 #[test] 6991 + fn pattern_match_on_use_assignment() { 6992 + assert_code_action!( 6993 + PATTERN_MATCH_ON_VARIABLE, 6994 + " 6995 + pub fn main() { 6996 + use var <- f 6997 + } 6998 + 6999 + fn f(g) { g(#(1, 2)) } 7000 + ", 7001 + find_position_of("var").to_selection() 7002 + ); 7003 + } 7004 + 7005 + #[test] 7006 + fn pattern_match_on_use_assignment_with_multiple_constructors() { 7007 + assert_code_action!( 7008 + PATTERN_MATCH_ON_VARIABLE, 7009 + " 7010 + pub type Wibble { 7011 + Wobble 7012 + Woo 7013 + } 7014 + 7015 + pub fn main() { 7016 + use var <- f 7017 + } 7018 + 7019 + fn f(g) { g(Wobble) } 7020 + ", 7021 + find_position_of("var").to_selection() 7022 + ); 7023 + } 7024 + 7025 + #[test] 7026 + fn pattern_match_on_pattern_use_assignment() { 7027 + assert_no_code_actions!( 7028 + PATTERN_MATCH_ON_VARIABLE, 7029 + " 7030 + pub fn main() { 7031 + use #(a, b) <- f 7032 + } 7033 + 7034 + fn f(g) { g(#(1, 2)) } 7035 + ", 7036 + find_position_of("#").to_selection() 7037 + ); 7038 + } 7039 + 7040 + #[test] 6991 7041 fn pattern_match_on_argument_works_on_fn_arguments() { 6992 7042 assert_code_action!( 6993 7043 PATTERN_MATCH_ON_ARGUMENT,
+22
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__pattern_match_on_use_assignment.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub fn main() {\n use var <- f\n}\n\nfn f(g) { g(#(1, 2)) }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn main() { 8 + use var <- f 9 + 10 + } 11 + 12 + fn f(g) { g(#(1, 2)) } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub fn main() { 18 + use var <- f 19 + let #(value_0, value_1) = var 20 + } 21 + 22 + fn f(g) { g(#(1, 2)) }
+35
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__pattern_match_on_use_assignment_with_multiple_constructors.snap
··· 1 + --- 2 + source: compiler-core/src/language_server/tests/action.rs 3 + expression: "\npub type Wibble {\n Wobble\n Woo\n}\n\npub fn main() {\n use var <- f\n}\n\nfn f(g) { g(Wobble) }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub type Wibble { 8 + Wobble 9 + Woo 10 + } 11 + 12 + pub fn main() { 13 + use var <- f 14 + 15 + } 16 + 17 + fn f(g) { g(Wobble) } 18 + 19 + 20 + ----- AFTER ACTION 21 + 22 + pub type Wibble { 23 + Wobble 24 + Woo 25 + } 26 + 27 + pub fn main() { 28 + use var <- f 29 + case var { 30 + Wobble -> todo 31 + Woo -> todo 32 + } 33 + } 34 + 35 + fn f(g) { g(Wobble) }