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

Configure Feed

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

add action to wrap a function ref in its own anonymous fn

+462 -2
+7
compiler-core/src/ast/untyped.rs
··· 316 316 } 317 317 318 318 impl FunctionLiteralKind { 319 + pub fn is_anonymous(&self) -> bool { 320 + match self { 321 + FunctionLiteralKind::Anonymous { .. } => true, 322 + FunctionLiteralKind::Capture { .. } | FunctionLiteralKind::Use { .. } => false, 323 + } 324 + } 325 + 319 326 pub fn is_capture(&self) -> bool { 320 327 match self { 321 328 FunctionLiteralKind::Capture { .. } => true,
+135
language-server/src/code_action.rs
··· 4174 4174 } 4175 4175 } 4176 4176 4177 + /// A set of variable names used in some gleam. Useful for passing to [NameGenerator::reserve_variable_names]. 4177 4178 struct VariablesNames { 4178 4179 names: HashSet<EcoString>, 4179 4180 } 4180 4181 4181 4182 impl VariablesNames { 4183 + /// Creates a `VariableNames` by collecting all variables used in a list of statements. 4182 4184 fn from_statements(statements: &[TypedStatement]) -> Self { 4183 4185 let mut variables = Self { 4184 4186 names: HashSet::new(), ··· 4187 4189 for statement in statements { 4188 4190 variables.visit_typed_statement(statement); 4189 4191 } 4192 + variables 4193 + } 4194 + 4195 + /// Creates a `VariableNames` by collecting all variables used within an expression. 4196 + fn from_expression(expression: &TypedExpr) -> Self { 4197 + let mut variables = Self { 4198 + names: HashSet::new(), 4199 + }; 4200 + 4201 + variables.visit_typed_expr(expression); 4190 4202 variables 4191 4203 } 4192 4204 } ··· 11375 11387 } 11376 11388 } 11377 11389 } 11390 + 11391 + /// Code action to turn a function used as a reference into a one-statement anonymous function. 11392 + /// 11393 + /// For example, if the code action was used on `op` here: 11394 + /// 11395 + /// ```gleam 11396 + /// list.map([1, 2, 3], op) 11397 + /// ``` 11398 + /// 11399 + /// it would become: 11400 + /// 11401 + /// ```gleam 11402 + /// list.map([1, 2, 3], fn(int) { 11403 + /// op(int) 11404 + /// }) 11405 + /// ``` 11406 + pub struct WrapInAnonymousFunction<'a> { 11407 + module: &'a Module, 11408 + line_numbers: &'a LineNumbers, 11409 + params: &'a CodeActionParams, 11410 + functions: Vec<FunctionToWrap>, 11411 + } 11412 + 11413 + /// Helper struct, a target for [WrapInAnonymousFunction]. 11414 + struct FunctionToWrap { 11415 + location: SrcSpan, 11416 + arguments: Vec<Arc<Type>>, 11417 + variables_names: VariablesNames, 11418 + } 11419 + 11420 + impl<'a> WrapInAnonymousFunction<'a> { 11421 + pub fn new( 11422 + module: &'a Module, 11423 + line_numbers: &'a LineNumbers, 11424 + params: &'a CodeActionParams, 11425 + ) -> Self { 11426 + Self { 11427 + module, 11428 + line_numbers, 11429 + params, 11430 + functions: vec![], 11431 + } 11432 + } 11433 + 11434 + pub fn code_actions(mut self) -> Vec<CodeAction> { 11435 + self.visit_typed_module(&self.module.ast); 11436 + 11437 + let mut actions = Vec::with_capacity(self.functions.len()); 11438 + for target in self.functions { 11439 + let mut name_generator = NameGenerator::new(); 11440 + name_generator.reserve_variable_names(target.variables_names); 11441 + let arguments = target 11442 + .arguments 11443 + .iter() 11444 + .map(|t| name_generator.generate_name_from_type(t)) 11445 + .join(", "); 11446 + 11447 + let mut edits = TextEdits::new(self.line_numbers); 11448 + edits.insert(target.location.start, format!("fn({arguments}) {{ ")); 11449 + edits.insert(target.location.end, format!("({arguments}) }}")); 11450 + 11451 + CodeActionBuilder::new("Wrap in anonymous function") 11452 + .kind(CodeActionKind::REFACTOR_REWRITE) 11453 + .changes(self.params.text_document.uri.clone(), edits.edits) 11454 + .push_to(&mut actions); 11455 + } 11456 + actions 11457 + } 11458 + } 11459 + 11460 + impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> { 11461 + fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { 11462 + let expression_range = src_span_to_lsp_range(expression.location(), self.line_numbers); 11463 + if !overlaps(self.params.range, expression_range) { 11464 + return; 11465 + } 11466 + 11467 + let is_excluded = if let TypedExpr::Fn { kind, .. } = expression { 11468 + kind.is_anonymous() || kind.is_capture() 11469 + } else { 11470 + false 11471 + }; 11472 + 11473 + if let Type::Fn { arguments, .. } = &*expression.type_() 11474 + && !is_excluded 11475 + { 11476 + self.functions.push(FunctionToWrap { 11477 + location: expression.location(), 11478 + arguments: arguments.clone(), 11479 + variables_names: VariablesNames::from_expression(expression), 11480 + }); 11481 + }; 11482 + 11483 + ast::visit::visit_typed_expr(self, expression); 11484 + } 11485 + 11486 + /// We don't want to apply to functions that are being explicitly called 11487 + /// already, so we need to intercept visits to function calls and bounce 11488 + /// them out again so they don't end up in our impl for visit_typed_expr. 11489 + /// Otherwise this is the same as []. 11490 + fn visit_typed_expr_call( 11491 + &mut self, 11492 + _location: &'ast SrcSpan, 11493 + _type: &'ast Arc<Type>, 11494 + fun: &'ast TypedExpr, 11495 + arguments: &'ast [TypedCallArg], 11496 + _argument_parentheses: &'ast Option<SrcSpan>, 11497 + ) { 11498 + // We only need to do this interception for explicit calls, so if any 11499 + // of our arguments are explicit we re-enter the visitor as usual. 11500 + if arguments.iter().any(|a| a.is_implicit()) { 11501 + self.visit_typed_expr(fun); 11502 + } else { 11503 + // We still want to visit other nodes nested in the function being 11504 + // called so we bounce the call back out. 11505 + ast::visit::visit_typed_expr(self, fun); 11506 + } 11507 + 11508 + for argument in arguments { 11509 + self.visit_typed_call_arg(argument); 11510 + } 11511 + } 11512 + }
+3 -2
language-server/src/engine.rs
··· 45 45 GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, MergeCaseBranches, 46 46 PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveBlock, RemoveEchos, 47 47 RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, 48 - UseLabelShorthandSyntax, WrapInBlock, code_action_add_missing_patterns, 49 - code_action_convert_qualified_constructor_to_unqualified, 48 + UseLabelShorthandSyntax, WrapInAnonymousFunction, WrapInBlock, 49 + code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, 50 50 code_action_convert_unqualified_constructor_to_qualified, code_action_import_module, 51 51 code_action_inexhaustive_let_to_case, 52 52 }, ··· 468 468 actions.extend(ExtractFunction::new(module, &lines, &params).code_actions()); 469 469 GenerateDynamicDecoder::new(module, &lines, &params, &mut actions, &this.compiler) 470 470 .code_actions(); 471 + actions.extend(WrapInAnonymousFunction::new(module, &lines, &params).code_actions()); 471 472 GenerateJsonEncoder::new( 472 473 module, 473 474 &lines,
+81
language-server/src/tests/action.rs
··· 140 140 const MERGE_CASE_BRANCHES: &str = "Merge case branches"; 141 141 const ADD_MISSING_TYPE_PARAMETER: &str = "Add missing type parameter"; 142 142 const REPLACE_UNDERSCORE_WITH_TYPE: &str = "Replace `_` with type"; 143 + const WRAP_IN_ANONYMOUS_FUNCTION: &str = "Wrap in anonymous function"; 143 144 144 145 macro_rules! assert_code_action { 145 146 ($title:expr, $code:literal, $range:expr $(,)?) => { ··· 12951 12952 } 12952 12953 12953 12954 #[test] 12955 + fn wrap_uncalled_function_in_anonymous_function() { 12956 + assert_code_action!( 12957 + WRAP_IN_ANONYMOUS_FUNCTION, 12958 + "pub fn main() { 12959 + op 12960 + } 12961 + 12962 + fn op(i) { 12963 + todo 12964 + } 12965 + ", 12966 + find_position_of("op").to_selection() 12967 + ); 12968 + } 12969 + 12970 + #[test] 12954 12971 fn replace_nested_underscore_with_generic_type() { 12955 12972 assert_code_action!( 12956 12973 REPLACE_UNDERSCORE_WITH_TYPE, ··· 12963 12980 ); 12964 12981 } 12965 12982 12983 + fn wrap_uncalled_constructor_in_anonymous_function() { 12984 + assert_code_action!( 12985 + WRAP_IN_ANONYMOUS_FUNCTION, 12986 + "pub fn main() { 12987 + Record 12988 + } 12989 + 12990 + type Record { 12991 + Record(i: Int) 12992 + } 12993 + ", 12994 + find_position_of("Record").to_selection() 12995 + ); 12996 + } 12997 + 12966 12998 #[test] 12967 12999 fn replace_nested_underscore_with_function_return_type() { 12968 13000 assert_code_action!( ··· 12976 13008 ); 12977 13009 } 12978 13010 13011 + fn wrap_call_arg_in_anonymous_function() { 13012 + assert_code_action!( 13013 + WRAP_IN_ANONYMOUS_FUNCTION, 13014 + "import gleam/list 13015 + 13016 + pub fn main() { 13017 + list.map([1, 2, 3], op) 13018 + } 13019 + 13020 + fn op(i: Int) -> Int { 13021 + todo 13022 + } 13023 + ", 13024 + find_position_of("op").to_selection() 13025 + ); 13026 + } 13027 + 12979 13028 #[test] 12980 13029 fn replace_nested_underscore_in_let_annotation() { 12981 13030 assert_code_action!( ··· 12986 13035 } 12987 13036 "#, 12988 13037 find_position_of("_").to_selection() 13038 + ); 13039 + } 13040 + 13041 + #[test] 13042 + fn wrap_function_in_anonymous_function_without_shadowing() { 13043 + assert_code_action!( 13044 + WRAP_IN_ANONYMOUS_FUNCTION, 13045 + "pub fn main() { 13046 + int 13047 + } 13048 + 13049 + fn int(i: Int) { 13050 + todo 13051 + } 13052 + ", 13053 + find_position_of("int").to_selection() 12989 13054 ); 12990 13055 } 12991 13056 ··· 13040 13105 find_position_of("_").to_selection() 13041 13106 ); 13042 13107 } 13108 + 13109 + #[test] 13110 + fn wrap_assignment_in_anonymous_function() { 13111 + assert_code_action!( 13112 + WRAP_IN_ANONYMOUS_FUNCTION, 13113 + "pub fn main() { 13114 + let op = op_factory(1, 2, 3) 13115 + } 13116 + 13117 + fn op_factory(a: Int, b: Int, c: Int) -> fn(Int) -> Int { 13118 + todo 13119 + } 13120 + ", 13121 + find_position_of("op_factory").to_selection() 13122 + ); 13123 + }
+23
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_assignment_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n let op = op_factory(1, 2, 3)\n}\n\nfn op_factory(a: Int, b: Int, c: Int) -> fn(Int) -> Int {\n todo\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + let op = op_factory(1, 2, 3) 8 + 9 + } 10 + 11 + fn op_factory(a: Int, b: Int, c: Int) -> fn(Int) -> Int { 12 + todo 13 + } 14 + 15 + 16 + ----- AFTER ACTION 17 + pub fn main() { 18 + let op = fn(int) { op_factory(1, 2, 3)(int) } 19 + } 20 + 21 + fn op_factory(a: Int, b: Int, c: Int) -> fn(Int) -> Int { 22 + todo 23 + }
+27
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_call_arg_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "import gleam/list\n\npub fn main() {\n list.map([1, 2, 3], op)\n}\n\nfn op(i: Int) -> Int {\n todo\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + import gleam/list 7 + 8 + pub fn main() { 9 + list.map([1, 2, 3], op) 10 + 11 + } 12 + 13 + fn op(i: Int) -> Int { 14 + todo 15 + } 16 + 17 + 18 + ----- AFTER ACTION 19 + import gleam/list 20 + 21 + pub fn main() { 22 + list.map([1, 2, 3], fn(int) { op(int) }) 23 + } 24 + 25 + fn op(i: Int) -> Int { 26 + todo 27 + }
+32
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_final_pipeline_step_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n 1 |> wibble |> wobble\n}\n\nfn wibble(i) {\n todo\n}\n\nfn wobble(i) {\n todo\n}\n\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + 1 |> wibble |> wobble 8 + 9 + } 10 + 11 + fn wibble(i) { 12 + todo 13 + } 14 + 15 + fn wobble(i) { 16 + todo 17 + } 18 + 19 + 20 + 21 + ----- AFTER ACTION 22 + pub fn main() { 23 + 1 |> wibble |> fn(value) { wobble(value) } 24 + } 25 + 26 + fn wibble(i) { 27 + todo 28 + } 29 + 30 + fn wobble(i) { 31 + todo 32 + }
+23
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_function_in_anonymous_function_without_shadowing.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n int\n}\n\nfn int(i: Int) {\n todo\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + int 8 + 9 + } 10 + 11 + fn int(i: Int) { 12 + todo 13 + } 14 + 15 + 16 + ----- AFTER ACTION 17 + pub fn main() { 18 + fn(int_2) { int(int_2) } 19 + } 20 + 21 + fn int(i: Int) { 22 + todo 23 + }
+21
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_imported_function_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "import gleam/list\nimport gleam/int\n\npub fn main() {\n list.map([1, 2, 3], int.is_even)\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + import gleam/list 7 + import gleam/int 8 + 9 + pub fn main() { 10 + list.map([1, 2, 3], int.is_even) 11 + 12 + } 13 + 14 + 15 + ----- AFTER ACTION 16 + import gleam/list 17 + import gleam/int 18 + 19 + pub fn main() { 20 + list.map([1, 2, 3], fn(int) { int.is_even(int) }) 21 + }
+32
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_pipeline_step_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n 1 |> wibble |> wobble\n}\n\nfn wibble(i) {\n todo\n}\n\nfn wobble(i) {\n todo\n}\n\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + 1 |> wibble |> wobble 8 + 9 + } 10 + 11 + fn wibble(i) { 12 + todo 13 + } 14 + 15 + fn wobble(i) { 16 + todo 17 + } 18 + 19 + 20 + 21 + ----- AFTER ACTION 22 + pub fn main() { 23 + 1 |> fn(int) { wibble(int) } |> wobble 24 + } 25 + 26 + fn wibble(i) { 27 + todo 28 + } 29 + 30 + fn wobble(i) { 31 + todo 32 + }
+32
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_record_field_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n let r = Record(wibble)\n}\n\ntype Record {\n Record(wibbler: fn(Int) -> Int)\n}\n\nfn wibble(v) {\n todo\n}\n\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + let r = Record(wibble) 8 + 9 + } 10 + 11 + type Record { 12 + Record(wibbler: fn(Int) -> Int) 13 + } 14 + 15 + fn wibble(v) { 16 + todo 17 + } 18 + 19 + 20 + 21 + ----- AFTER ACTION 22 + pub fn main() { 23 + let r = Record(fn(int) { wibble(int) }) 24 + } 25 + 26 + type Record { 27 + Record(wibbler: fn(Int) -> Int) 28 + } 29 + 30 + fn wibble(v) { 31 + todo 32 + }
+23
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_uncalled_constructor_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n Record\n}\n\ntype Record {\n Record(i: Int)\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + Record 8 + 9 + } 10 + 11 + type Record { 12 + Record(i: Int) 13 + } 14 + 15 + 16 + ----- AFTER ACTION 17 + pub fn main() { 18 + fn(int) { Record(int) } 19 + } 20 + 21 + type Record { 22 + Record(i: Int) 23 + }
+23
language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_uncalled_function_in_anonymous_function.snap
··· 1 + --- 2 + source: language-server/src/tests/action.rs 3 + expression: "pub fn main() {\n op\n}\n\nfn op(i) {\n todo\n}\n" 4 + --- 5 + ----- BEFORE ACTION 6 + pub fn main() { 7 + op 8 + 9 + } 10 + 11 + fn op(i) { 12 + todo 13 + } 14 + 15 + 16 + ----- AFTER ACTION 17 + pub fn main() { 18 + fn(value) { op(value) } 19 + } 20 + 21 + fn op(i) { 22 + todo 23 + }