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

Configure Feed

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

allow pattern matching on function return value

author
Giacomo Cavalieri
committer
Louis Pilfold
date (Jun 4, 2026, 2:44 PM +0100) commit b9a9366d parent 5728e791 change-id ulsyslvo
+392 -2
+26 -1
CHANGELOG.md
··· 34 34 35 35 ### Language server 36 36 37 + - The "pattern match on value" code action can now be used to pattern match on 38 + values returned by function calls. For example: 39 + 40 + ```gleam 41 + pub fn main() { 42 + load_user() 43 + // ^^ Triggering the code action over here 44 + } 45 + 46 + fn load_user() -> Result(User, Nil) { todo } 47 + ``` 48 + 49 + Will produce the following code: 50 + 51 + ```gleam 52 + pub fn main() { 53 + case load_user() { 54 + Ok(value) -> todo 55 + Error(value) -> todo 56 + } 57 + } 58 + ``` 59 + 60 + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 61 + 37 62 ### Formatter 38 63 39 64 ### Bug fixes ··· 70 95 ([Gavin Morrow](https://github.com/gavinmorrow)) 71 96 72 97 - Fixed a bug where the compiler would raise a warning for truncated int 73 - segments when compiling a function with a JavaScript external. 98 + segments when compiling a function with a JavaScript external. 74 99 ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+86 -1
language-server/src/code_action.rs
··· 5505 5505 /// 5506 5506 #[derive(Clone)] 5507 5507 pub enum PatternMatchedValue<'a> { 5508 + /// A statement we can match on. For example, function calls, record 5509 + /// and tuple accesses: 5510 + /// 5511 + /// ```gleam 5512 + /// pub fn wibble() { 5513 + /// wobble(1, 2) 5514 + /// //^^^^ This 5515 + /// } 5516 + /// ``` 5517 + /// 5518 + Statement { 5519 + /// The span covering the entire statement: 5520 + /// ```gleam 5521 + /// wobble(1, 2) 5522 + /// //^^^^^^^^^^^^ This 5523 + /// ``` 5524 + location: SrcSpan, 5525 + /// The statement's type defining what we will be pattern matching 5526 + /// on. 5527 + type_: Arc<Type>, 5528 + }, 5508 5529 FunctionArgument { 5509 5530 /// The argument being pattern matched on. 5510 5531 /// ··· 5644 5665 "Pattern match on variable" 5645 5666 } 5646 5667 5668 + Some(PatternMatchedValue::Statement { location, type_ }) => { 5669 + self.match_on_statement(location, type_); 5670 + "Pattern match on value" 5671 + } 5672 + 5647 5673 Some(PatternMatchedValue::ClausePatternVariable { 5648 5674 variable_type, 5649 5675 variable_location, ··· 5767 5793 5768 5794 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); 5769 5795 let nesting = " ".repeat(assignment_range.start.character as usize); 5770 - 5771 5796 let pattern_matching = if patterns.len() == 1 { 5772 5797 let pattern = patterns.first(); 5773 5798 format!("let {pattern} = {variable_name}") ··· 5785 5810 ); 5786 5811 } 5787 5812 5813 + fn match_on_statement(&mut self, statement_location: SrcSpan, type_: Arc<Type>) { 5814 + let Some(patterns) = 5815 + self.type_to_destructure_patterns(type_.as_ref(), &mut NameGenerator::new()) 5816 + else { 5817 + return; 5818 + }; 5819 + 5820 + if patterns.len() == 1 { 5821 + let pattern = patterns.first(); 5822 + self.edits 5823 + .insert(statement_location.start, format!("let {pattern} = ")); 5824 + } else { 5825 + let statement_range = self.edits.src_span_to_lsp_range(statement_location); 5826 + let nesting = " ".repeat(statement_range.start.character as usize); 5827 + let patterns = patterns 5828 + .iter() 5829 + .map(|p| format!(" {nesting}{p} -> todo")) 5830 + .join("\n"); 5831 + self.edits.insert(statement_location.start, "case ".into()); 5832 + self.edits.insert( 5833 + statement_location.end, 5834 + format!(" {{\n{patterns}\n{nesting}}}"), 5835 + ) 5836 + } 5837 + } 5838 + 5788 5839 fn match_on_clause_variable( 5789 5840 &mut self, 5790 5841 variable_type: Arc<Type>, ··· 5870 5921 ) -> Option<Vec1<EcoString>> { 5871 5922 match type_ { 5872 5923 Type::Fn { .. } => None, 5924 + 5925 + // Pattern matchin on `Nil` is not all that useful. 5926 + Type::Named { .. } if type_.is_nil() => None, 5927 + 5873 5928 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names), 5874 5929 5875 5930 // We special case lists, they don't have "regular" constructors ··· 6047 6102 // exploring the function body as we might want to destructure the 6048 6103 // argument of an expression function! 6049 6104 ast::visit::visit_typed_function(self, fun); 6105 + } 6106 + 6107 + fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { 6108 + let statement_range = self.edits.src_span_to_lsp_range(statement.location()); 6109 + if !within(self.params.range, statement_range) { 6110 + return; 6111 + } 6112 + 6113 + ast::visit::visit_typed_statement(self, statement); 6114 + match statement { 6115 + // If we haven't found any more specific selected expression, then 6116 + // we are going to select the statement to pattern match on (if it 6117 + // can be meaningfully pattern matched on). 6118 + ast::Statement::Expression( 6119 + TypedExpr::Call { type_, .. } 6120 + | TypedExpr::ModuleSelect { type_, .. } 6121 + | TypedExpr::RecordAccess { type_, .. } 6122 + | TypedExpr::TupleIndex { type_, .. }, 6123 + ) if self.selected_value.is_none() => { 6124 + self.selected_value = Some(PatternMatchedValue::Statement { 6125 + location: statement.location(), 6126 + type_: type_.clone(), 6127 + }) 6128 + } 6129 + 6130 + ast::Statement::Expression(_) 6131 + | ast::Statement::Assignment(_) 6132 + | ast::Statement::Use(_) 6133 + | ast::Statement::Assert(_) => (), 6134 + } 6050 6135 } 6051 6136 6052 6137 fn visit_typed_expr_fn(
+128
language-server/src/tests/action.rs
··· 8175 8175 } 8176 8176 8177 8177 #[test] 8178 + fn pattern_match_on_call_statement() { 8179 + assert_code_action!( 8180 + PATTERN_MATCH_ON_VALUE, 8181 + " 8182 + pub fn main() { 8183 + wibble() 8184 + } 8185 + 8186 + pub fn wibble() -> Result(Int, String) { todo } 8187 + ", 8188 + find_position_of("wibble").to_selection() 8189 + ); 8190 + } 8191 + 8192 + #[test] 8193 + fn pattern_match_on_record_select_statement() { 8194 + assert_code_action!( 8195 + PATTERN_MATCH_ON_VALUE, 8196 + " 8197 + pub fn main() { 8198 + let wibble = Wibble(Ok(1)) 8199 + wibble.wobble 8200 + } 8201 + 8202 + pub type Wibble { Wibble(wobble: Result(Int, Nil)) } 8203 + ", 8204 + find_position_of("wibble").nth_occurrence(2).to_selection() 8205 + ); 8206 + } 8207 + 8208 + #[test] 8209 + fn pattern_match_on_tuple_index_statement() { 8210 + assert_code_action!( 8211 + PATTERN_MATCH_ON_VALUE, 8212 + " 8213 + pub fn main() { 8214 + let wibble = #(Ok(1), 2) 8215 + wibble.0 8216 + } 8217 + ", 8218 + find_position_of("wibble").nth_occurrence(2).to_selection() 8219 + ); 8220 + } 8221 + 8222 + #[test] 8223 + fn pattern_match_on_module_select_statement() { 8224 + assert_code_action!( 8225 + PATTERN_MATCH_ON_VALUE, 8226 + TestProject::for_source( 8227 + " 8228 + import wibble 8229 + 8230 + pub fn main() { 8231 + wibble.wobble 8232 + } 8233 + " 8234 + ) 8235 + .add_module("wibble", "pub const wobble = Ok(1)"), 8236 + find_position_of("wibble").nth_occurrence(2).to_selection() 8237 + ); 8238 + } 8239 + 8240 + #[test] 8241 + fn pattern_match_on_call_statement_returning_nil() { 8242 + assert_no_code_actions!( 8243 + PATTERN_MATCH_ON_VALUE, 8244 + " 8245 + pub fn main() { 8246 + wibble() 8247 + } 8248 + 8249 + pub fn wibble() -> Nil { todo } 8250 + ", 8251 + find_position_of("wibble").to_selection() 8252 + ); 8253 + } 8254 + 8255 + #[test] 8256 + fn pattern_match_on_call_statement_in_the_middle_of_a_function_body() { 8257 + assert_code_action!( 8258 + PATTERN_MATCH_ON_VALUE, 8259 + " 8260 + pub fn main() { 8261 + wibble() 8262 + Nil 8263 + } 8264 + 8265 + pub fn wibble() -> Result(Int, String) { todo } 8266 + ", 8267 + find_position_of("wibble").to_selection() 8268 + ); 8269 + } 8270 + 8271 + #[test] 8272 + fn pattern_match_on_value_picks_innermost_value_and_not_outer_call() { 8273 + assert_no_code_actions!( 8274 + PATTERN_MATCH_ON_VALUE, 8275 + " 8276 + pub fn main() { 8277 + wibble({ 8278 + let match_on_me = wibble(todo) 8279 + }) 8280 + } 8281 + 8282 + pub fn wibble(a) -> Result(Int, String) { todo } 8283 + ", 8284 + find_position_of("match_on_me").to_selection() 8285 + ); 8286 + } 8287 + 8288 + #[test] 8289 + fn pattern_match_on_value_picks_innermost_value_and_not_outer_call_2() { 8290 + assert_code_action!( 8291 + PATTERN_MATCH_ON_VARIABLE, 8292 + " 8293 + pub fn main() { 8294 + wibble({ 8295 + let match_on_me = wibble(todo) 8296 + }) 8297 + } 8298 + 8299 + pub fn wibble(a) -> Result(Int, String) { todo } 8300 + ", 8301 + find_position_of("match_on_me").to_selection() 8302 + ); 8303 + } 8304 + 8305 + #[test] 8178 8306 fn pattern_match_on_clause_variable() { 8179 8307 assert_code_action!( 8180 8308 PATTERN_MATCH_ON_VARIABLE,
+24
language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_call_statement.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn main() {\n wibble()\n}\n\npub fn wibble() -> Result(Int, String) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn main() { 8 + wibble() 9 + 10 + } 11 + 12 + pub fn wibble() -> Result(Int, String) { todo } 13 + 14 + 15 + ----- AFTER ACTION 16 + 17 + pub fn main() { 18 + case wibble() { 19 + Ok(value) -> todo 20 + Error(value) -> todo 21 + } 22 + } 23 + 24 + pub fn wibble() -> Result(Int, String) { todo }
+26
language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_call_statement_in_the_middle_of_a_function_body.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn main() {\n wibble()\n Nil\n}\n\npub fn wibble() -> Result(Int, String) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn main() { 8 + wibble() 9 + 10 + Nil 11 + } 12 + 13 + pub fn wibble() -> Result(Int, String) { todo } 14 + 15 + 16 + ----- AFTER ACTION 17 + 18 + pub fn main() { 19 + case wibble() { 20 + Ok(value) -> todo 21 + Error(value) -> todo 22 + } 23 + Nil 24 + } 25 + 26 + pub fn wibble() -> Result(Int, String) { todo }
+25
language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_module_select_statement.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\nimport wibble\n\npub fn main() {\n wibble.wobble\n}\n " 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + import wibble 8 + 9 + pub fn main() { 10 + wibble.wobble 11 + 12 + } 13 + 14 + 15 + 16 + ----- AFTER ACTION 17 + 18 + import wibble 19 + 20 + pub fn main() { 21 + case wibble.wobble { 22 + Ok(value) -> todo 23 + Error(value) -> todo 24 + } 25 + }
+26
language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_record_select_statement.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn main() {\n let wibble = Wibble(Ok(1))\n wibble.wobble\n}\n\npub type Wibble { Wibble(wobble: Result(Int, Nil)) }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn main() { 8 + let wibble = Wibble(Ok(1)) 9 + wibble.wobble 10 + 11 + } 12 + 13 + pub type Wibble { Wibble(wobble: Result(Int, Nil)) } 14 + 15 + 16 + ----- AFTER ACTION 17 + 18 + pub fn main() { 19 + let wibble = Wibble(Ok(1)) 20 + case wibble.wobble { 21 + Ok(value) -> todo 22 + Error(value) -> todo 23 + } 24 + } 25 + 26 + pub type Wibble { Wibble(wobble: Result(Int, Nil)) }
+22
language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_tuple_index_statement.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn main() {\n let wibble = #(Ok(1), 2)\n wibble.0\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn main() { 8 + let wibble = #(Ok(1), 2) 9 + wibble.0 10 + 11 + } 12 + 13 + 14 + ----- AFTER ACTION 15 + 16 + pub fn main() { 17 + let wibble = #(Ok(1), 2) 18 + case wibble.0 { 19 + Ok(value) -> todo 20 + Error(value) -> todo 21 + } 22 + }
+29
language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_value_picks_innermost_value_and_not_outer_call_2.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "\npub fn main() {\n wibble({\n let match_on_me = wibble(todo)\n })\n}\n\npub fn wibble(a) -> Result(Int, String) { todo }\n" 4 + --- 5 + ----- BEFORE ACTION 6 + 7 + pub fn main() { 8 + wibble({ 9 + let match_on_me = wibble(todo) 10 + 11 + }) 12 + } 13 + 14 + pub fn wibble(a) -> Result(Int, String) { todo } 15 + 16 + 17 + ----- AFTER ACTION 18 + 19 + pub fn main() { 20 + wibble({ 21 + let match_on_me = wibble(todo) 22 + case match_on_me { 23 + Ok(value) -> todo 24 + Error(value) -> todo 25 + } 26 + }) 27 + } 28 + 29 + pub fn wibble(a) -> Result(Int, String) { todo }