Skip to content

Commit 3bacdd3

Browse files
committed
make function-wrapping action wrap any uncalled function
plus a bunch more tests
1 parent 769b571 commit 3bacdd3

10 files changed

+399
-32
lines changed

compiler-core/src/language_server/code_action.rs

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7582,41 +7582,46 @@ impl<'a> WrapInAnonymousFunction<'a> {
75827582
}
75837583

75847584
impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> {
7585-
fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
7586-
let function_range = src_span_to_lsp_range(arg.location, self.line_numbers);
7587-
if !overlaps(self.params.range, function_range) {
7585+
fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
7586+
let expression_range = src_span_to_lsp_range(expression.location(), self.line_numbers);
7587+
if !overlaps(self.params.range, expression_range) {
75887588
return;
75897589
}
75907590

7591-
if let Type::Fn { ref arguments, .. } = *arg.value.type_() {
7592-
let variables_names = VariablesNames::from_expression(&arg.value);
7593-
7591+
if let Type::Fn { ref arguments, .. } = *expression.type_() {
75947592
self.functions.push(FunctionToWrap {
7595-
location: arg.location,
7593+
location: expression.location(),
75967594
arguments: arguments.clone(),
7597-
variables_names,
7595+
variables_names: VariablesNames::from_expression(expression),
75987596
});
7599-
}
7600-
}
7597+
};
76017598

7602-
fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
7603-
let function_range = src_span_to_lsp_range(assignment.location, self.line_numbers);
7604-
if !overlaps(self.params.range, function_range) {
7605-
return;
7606-
}
7599+
ast::visit::visit_typed_expr(self, expression);
7600+
}
76077601

7608-
if assignment.kind != AssignmentKind::Let {
7609-
return;
7602+
/// We don't want to apply to functions that are being explicitly called
7603+
/// already, so we need to intercept visits to function calls and bounce
7604+
/// them out again so they don't end up in our impl for visit_typed_expr.
7605+
/// Otherwise this is the same as [ast::visit::visit_typed_expr_call].
7606+
fn visit_typed_expr_call(
7607+
&mut self,
7608+
_location: &'ast SrcSpan,
7609+
_type: &'ast Arc<Type>,
7610+
fun: &'ast TypedExpr,
7611+
arguments: &'ast [TypedCallArg],
7612+
) {
7613+
// We only need to do this interception for explicit calls, so if any
7614+
// of our arguments are explicit we re-enter the visitor as usual.
7615+
if arguments.iter().any(|a| a.is_implicit()) {
7616+
self.visit_typed_expr(fun);
7617+
} else {
7618+
// We still want to visit other nodes nested in the function being
7619+
// called so we bounce the call back out.
7620+
ast::visit::visit_typed_expr(self, fun);
76107621
}
76117622

7612-
if let Type::Fn { ref arguments, .. } = *assignment.value.type_() {
7613-
let variables_names = VariablesNames::from_expression(&assignment.value);
7614-
7615-
self.functions.push(FunctionToWrap {
7616-
location: assignment.value.location(),
7617-
arguments: arguments.clone(),
7618-
variables_names,
7619-
});
7623+
for argument in arguments {
7624+
self.visit_typed_call_arg(argument);
76207625
}
76217626
}
76227627
}
@@ -7690,7 +7695,7 @@ impl<'a> UnwrapAnonymousFunction<'a> {
76907695
}
76917696

76927697
/// If an anonymous function can be unwrapped, save it to our list
7693-
///
7698+
///
76947699
/// We need to ensure our subjects:
76957700
/// - are anonymous function literals (not captures)
76967701
/// - only contain a single statement
@@ -7763,11 +7768,11 @@ impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
77637768
fn visit_typed_expr_fn(
77647769
&mut self,
77657770
location: &'ast SrcSpan,
7766-
_type: &'ast Arc<Type>,
7771+
type_: &'ast Arc<Type>,
77677772
kind: &'ast FunctionLiteralKind,
77687773
arguments: &'ast [TypedArg],
77697774
body: &'ast Vec1<TypedStatement>,
7770-
_return_annotation: &'ast Option<ast::TypeAst>,
7775+
return_annotation: &'ast Option<ast::TypeAst>,
77717776
) {
77727777
let function_range = src_span_to_lsp_range(*location, self.line_numbers);
77737778
if !overlaps(self.params.range, function_range) {
@@ -7776,8 +7781,14 @@ impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
77767781

77777782
self.register_function(location, kind, arguments, body);
77787783

7779-
for statement in body {
7780-
self.visit_typed_statement(statement);
7781-
}
7784+
ast::visit::visit_typed_expr_fn(
7785+
self,
7786+
location,
7787+
type_,
7788+
kind,
7789+
arguments,
7790+
body,
7791+
return_annotation,
7792+
)
77827793
}
77837794
}

compiler-core/src/language_server/tests/action.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9135,6 +9135,22 @@ fn remove_block_does_not_unwrap_a_block_with_multiple_statements() {
91359135
);
91369136
}
91379137

9138+
#[test]
9139+
fn wrap_uncalled_function_in_anonymous_function() {
9140+
assert_code_action!(
9141+
WRAP_IN_ANONYMOUS_FUNCTION,
9142+
"pub fn main() {
9143+
op
9144+
}
9145+
9146+
fn op(i) {
9147+
todo
9148+
}
9149+
",
9150+
find_position_of("op").to_selection()
9151+
);
9152+
}
9153+
91389154
#[test]
91399155
fn wrap_call_arg_in_anonymous_function() {
91409156
assert_code_action!(
@@ -9153,6 +9169,22 @@ fn op(i: Int) -> Int {
91539169
);
91549170
}
91559171

9172+
#[test]
9173+
fn wrap_function_in_anonymous_function_without_shadowing() {
9174+
assert_code_action!(
9175+
WRAP_IN_ANONYMOUS_FUNCTION,
9176+
"pub fn main() {
9177+
int
9178+
}
9179+
9180+
fn int(i: Int) {
9181+
todo
9182+
}
9183+
",
9184+
find_position_of("int").to_selection()
9185+
);
9186+
}
9187+
91569188
#[test]
91579189
fn wrap_assignment_in_anonymous_function() {
91589190
assert_code_action!(
@@ -9169,6 +9201,118 @@ fn op_factory(a: Int, b: Int, c: Int) -> fn(Int) -> Int {
91699201
);
91709202
}
91719203

9204+
#[test]
9205+
fn wrap_imported_function_in_anonymous_function() {
9206+
let source = "import gleam/list
9207+
import gleam/int
9208+
9209+
pub fn main() {
9210+
list.map([1, 2, 3], int.is_even)
9211+
}
9212+
";
9213+
9214+
let int_source = "pub fn is_even(int) { int % 2 == 0 }";
9215+
9216+
assert_code_action!(
9217+
WRAP_IN_ANONYMOUS_FUNCTION,
9218+
TestProject::for_source(source).add_module("gleam/int", int_source),
9219+
find_position_of("int.is_even").to_selection()
9220+
);
9221+
}
9222+
9223+
#[test]
9224+
fn wrap_anonymous_function_in_anonymous_function() {
9225+
assert_code_action!(
9226+
WRAP_IN_ANONYMOUS_FUNCTION,
9227+
"pub fn main() {
9228+
let f = fn(in) { ception(in) }
9229+
}
9230+
9231+
",
9232+
find_position_of("fn(in)").to_selection()
9233+
);
9234+
}
9235+
9236+
#[test]
9237+
fn wrap_pipeline_step_in_anonymous_function() {
9238+
assert_code_action!(
9239+
WRAP_IN_ANONYMOUS_FUNCTION,
9240+
"pub fn main() {
9241+
1 |> wibble |> wobble
9242+
}
9243+
9244+
fn wibble(i) {
9245+
todo
9246+
}
9247+
9248+
fn wobble(i) {
9249+
todo
9250+
}
9251+
9252+
",
9253+
find_position_of("wibble").to_selection()
9254+
);
9255+
}
9256+
9257+
#[test]
9258+
fn wrap_final_pipeline_step_in_anonymous_function() {
9259+
assert_code_action!(
9260+
WRAP_IN_ANONYMOUS_FUNCTION,
9261+
"pub fn main() {
9262+
1 |> wibble |> wobble
9263+
}
9264+
9265+
fn wibble(i) {
9266+
todo
9267+
}
9268+
9269+
fn wobble(i) {
9270+
todo
9271+
}
9272+
9273+
",
9274+
find_position_of("wobble").to_selection()
9275+
);
9276+
}
9277+
9278+
#[test]
9279+
fn wrap_record_field_in_anonymous_function() {
9280+
assert_code_action!(
9281+
WRAP_IN_ANONYMOUS_FUNCTION,
9282+
"pub fn main() {
9283+
let r = Record(wibble)
9284+
}
9285+
9286+
type Record {
9287+
Record(wibbler: fn(Int) -> Int)
9288+
}
9289+
9290+
fn wibble(v) {
9291+
todo
9292+
}
9293+
9294+
",
9295+
find_position_of("wibble").to_selection()
9296+
);
9297+
}
9298+
9299+
#[test]
9300+
fn dont_wrap_functions_that_are_already_being_called() {
9301+
assert_no_code_actions!(
9302+
WRAP_IN_ANONYMOUS_FUNCTION,
9303+
"pub fn main() {
9304+
wibble(1)
9305+
}
9306+
9307+
fn wibble(i) {
9308+
todo
9309+
}
9310+
9311+
",
9312+
find_position_of("wibble").to_selection()
9313+
);
9314+
}
9315+
91729316
#[test]
91739317
fn unwrap_trivial_anonymous_function() {
91749318
assert_code_action!(
@@ -9228,7 +9372,7 @@ fn unwrap_anonymous_function_unavailable_with_different_args() {
92289372
"import gleam/list
92299373
92309374
const another_int = 7
9231-
9375+
92329376
pub fn main() {
92339377
list.map([1, 2, 3], fn(int) { op(another_int) })
92349378
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
source: compiler-core/src/language_server/tests/action.rs
3+
expression: "pub fn main() {\n let f = fn(in) { ception(in) }\n}\n\n"
4+
---
5+
----- BEFORE ACTION
6+
pub fn main() {
7+
let f = fn(in) { ception(in) }
8+
9+
}
10+
11+
12+
13+
----- AFTER ACTION
14+
pub fn main() {
15+
let f = fn(value) { fn(in) { ception(in) }(value) }
16+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
source: compiler-core/src/language_server/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+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
source: compiler-core/src/language_server/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+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: compiler-core/src/language_server/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+
}

0 commit comments

Comments
 (0)