Skip to content

Commit f3eb218

Browse files
committed
make function-wrapping action wrap any uncalled function
plus a bunch more tests
1 parent 8b43e4d commit f3eb218

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
@@ -8279,41 +8279,46 @@ impl<'a> WrapInAnonymousFunction<'a> {
82798279
}
82808280

82818281
impl<'ast> ast::visit::Visit<'ast> for WrapInAnonymousFunction<'ast> {
8282-
fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
8283-
let function_range = src_span_to_lsp_range(arg.location, self.line_numbers);
8284-
if !overlaps(self.params.range, function_range) {
8282+
fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
8283+
let expression_range = src_span_to_lsp_range(expression.location(), self.line_numbers);
8284+
if !overlaps(self.params.range, expression_range) {
82858285
return;
82868286
}
82878287

8288-
if let Type::Fn { ref arguments, .. } = *arg.value.type_() {
8289-
let variables_names = VariablesNames::from_expression(&arg.value);
8290-
8288+
if let Type::Fn { ref arguments, .. } = *expression.type_() {
82918289
self.functions.push(FunctionToWrap {
8292-
location: arg.location,
8290+
location: expression.location(),
82938291
arguments: arguments.clone(),
8294-
variables_names,
8292+
variables_names: VariablesNames::from_expression(expression),
82958293
});
8296-
}
8297-
}
8294+
};
82988295

8299-
fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
8300-
let function_range = src_span_to_lsp_range(assignment.location, self.line_numbers);
8301-
if !overlaps(self.params.range, function_range) {
8302-
return;
8303-
}
8296+
ast::visit::visit_typed_expr(self, expression);
8297+
}
83048298

8305-
if assignment.kind != AssignmentKind::Let {
8306-
return;
8299+
/// We don't want to apply to functions that are being explicitly called
8300+
/// already, so we need to intercept visits to function calls and bounce
8301+
/// them out again so they don't end up in our impl for visit_typed_expr.
8302+
/// Otherwise this is the same as [ast::visit::visit_typed_expr_call].
8303+
fn visit_typed_expr_call(
8304+
&mut self,
8305+
_location: &'ast SrcSpan,
8306+
_type: &'ast Arc<Type>,
8307+
fun: &'ast TypedExpr,
8308+
arguments: &'ast [TypedCallArg],
8309+
) {
8310+
// We only need to do this interception for explicit calls, so if any
8311+
// of our arguments are explicit we re-enter the visitor as usual.
8312+
if arguments.iter().any(|a| a.is_implicit()) {
8313+
self.visit_typed_expr(fun);
8314+
} else {
8315+
// We still want to visit other nodes nested in the function being
8316+
// called so we bounce the call back out.
8317+
ast::visit::visit_typed_expr(self, fun);
83078318
}
83088319

8309-
if let Type::Fn { ref arguments, .. } = *assignment.value.type_() {
8310-
let variables_names = VariablesNames::from_expression(&assignment.value);
8311-
8312-
self.functions.push(FunctionToWrap {
8313-
location: assignment.value.location(),
8314-
arguments: arguments.clone(),
8315-
variables_names,
8316-
});
8320+
for argument in arguments {
8321+
self.visit_typed_call_arg(argument);
83178322
}
83188323
}
83198324
}
@@ -8387,7 +8392,7 @@ impl<'a> UnwrapAnonymousFunction<'a> {
83878392
}
83888393

83898394
/// If an anonymous function can be unwrapped, save it to our list
8390-
///
8395+
///
83918396
/// We need to ensure our subjects:
83928397
/// - are anonymous function literals (not captures)
83938398
/// - only contain a single statement
@@ -8460,11 +8465,11 @@ impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
84608465
fn visit_typed_expr_fn(
84618466
&mut self,
84628467
location: &'ast SrcSpan,
8463-
_type: &'ast Arc<Type>,
8468+
type_: &'ast Arc<Type>,
84648469
kind: &'ast FunctionLiteralKind,
84658470
arguments: &'ast [TypedArg],
84668471
body: &'ast Vec1<TypedStatement>,
8467-
_return_annotation: &'ast Option<ast::TypeAst>,
8472+
return_annotation: &'ast Option<ast::TypeAst>,
84688473
) {
84698474
let function_range = src_span_to_lsp_range(*location, self.line_numbers);
84708475
if !overlaps(self.params.range, function_range) {
@@ -8473,8 +8478,14 @@ impl<'ast> ast::visit::Visit<'ast> for UnwrapAnonymousFunction<'ast> {
84738478

84748479
self.register_function(location, kind, arguments, body);
84758480

8476-
for statement in body {
8477-
self.visit_typed_statement(statement);
8478-
}
8481+
ast::visit::visit_typed_expr_fn(
8482+
self,
8483+
location,
8484+
type_,
8485+
kind,
8486+
arguments,
8487+
body,
8488+
return_annotation,
8489+
)
84798490
}
84808491
}

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

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9799,6 +9799,22 @@ fn remove_unreachable_branches_does_not_pop_up_if_all_branches_are_reachable() {
97999799
);
98009800
}
98019801

9802+
#[test]
9803+
fn wrap_uncalled_function_in_anonymous_function() {
9804+
assert_code_action!(
9805+
WRAP_IN_ANONYMOUS_FUNCTION,
9806+
"pub fn main() {
9807+
op
9808+
}
9809+
9810+
fn op(i) {
9811+
todo
9812+
}
9813+
",
9814+
find_position_of("op").to_selection()
9815+
);
9816+
}
9817+
98029818
#[test]
98039819
fn wrap_call_arg_in_anonymous_function() {
98049820
assert_code_action!(
@@ -9817,6 +9833,22 @@ fn op(i: Int) -> Int {
98179833
);
98189834
}
98199835

9836+
#[test]
9837+
fn wrap_function_in_anonymous_function_without_shadowing() {
9838+
assert_code_action!(
9839+
WRAP_IN_ANONYMOUS_FUNCTION,
9840+
"pub fn main() {
9841+
int
9842+
}
9843+
9844+
fn int(i: Int) {
9845+
todo
9846+
}
9847+
",
9848+
find_position_of("int").to_selection()
9849+
);
9850+
}
9851+
98209852
#[test]
98219853
fn wrap_assignment_in_anonymous_function() {
98229854
assert_code_action!(
@@ -9833,6 +9865,118 @@ fn op_factory(a: Int, b: Int, c: Int) -> fn(Int) -> Int {
98339865
);
98349866
}
98359867

9868+
#[test]
9869+
fn wrap_imported_function_in_anonymous_function() {
9870+
let source = "import gleam/list
9871+
import gleam/int
9872+
9873+
pub fn main() {
9874+
list.map([1, 2, 3], int.is_even)
9875+
}
9876+
";
9877+
9878+
let int_source = "pub fn is_even(int) { int % 2 == 0 }";
9879+
9880+
assert_code_action!(
9881+
WRAP_IN_ANONYMOUS_FUNCTION,
9882+
TestProject::for_source(source).add_module("gleam/int", int_source),
9883+
find_position_of("int.is_even").to_selection()
9884+
);
9885+
}
9886+
9887+
#[test]
9888+
fn wrap_anonymous_function_in_anonymous_function() {
9889+
assert_code_action!(
9890+
WRAP_IN_ANONYMOUS_FUNCTION,
9891+
"pub fn main() {
9892+
let f = fn(in) { ception(in) }
9893+
}
9894+
9895+
",
9896+
find_position_of("fn(in)").to_selection()
9897+
);
9898+
}
9899+
9900+
#[test]
9901+
fn wrap_pipeline_step_in_anonymous_function() {
9902+
assert_code_action!(
9903+
WRAP_IN_ANONYMOUS_FUNCTION,
9904+
"pub fn main() {
9905+
1 |> wibble |> wobble
9906+
}
9907+
9908+
fn wibble(i) {
9909+
todo
9910+
}
9911+
9912+
fn wobble(i) {
9913+
todo
9914+
}
9915+
9916+
",
9917+
find_position_of("wibble").to_selection()
9918+
);
9919+
}
9920+
9921+
#[test]
9922+
fn wrap_final_pipeline_step_in_anonymous_function() {
9923+
assert_code_action!(
9924+
WRAP_IN_ANONYMOUS_FUNCTION,
9925+
"pub fn main() {
9926+
1 |> wibble |> wobble
9927+
}
9928+
9929+
fn wibble(i) {
9930+
todo
9931+
}
9932+
9933+
fn wobble(i) {
9934+
todo
9935+
}
9936+
9937+
",
9938+
find_position_of("wobble").to_selection()
9939+
);
9940+
}
9941+
9942+
#[test]
9943+
fn wrap_record_field_in_anonymous_function() {
9944+
assert_code_action!(
9945+
WRAP_IN_ANONYMOUS_FUNCTION,
9946+
"pub fn main() {
9947+
let r = Record(wibble)
9948+
}
9949+
9950+
type Record {
9951+
Record(wibbler: fn(Int) -> Int)
9952+
}
9953+
9954+
fn wibble(v) {
9955+
todo
9956+
}
9957+
9958+
",
9959+
find_position_of("wibble").to_selection()
9960+
);
9961+
}
9962+
9963+
#[test]
9964+
fn dont_wrap_functions_that_are_already_being_called() {
9965+
assert_no_code_actions!(
9966+
WRAP_IN_ANONYMOUS_FUNCTION,
9967+
"pub fn main() {
9968+
wibble(1)
9969+
}
9970+
9971+
fn wibble(i) {
9972+
todo
9973+
}
9974+
9975+
",
9976+
find_position_of("wibble").to_selection()
9977+
);
9978+
}
9979+
98369980
#[test]
98379981
fn unwrap_trivial_anonymous_function() {
98389982
assert_code_action!(
@@ -9892,7 +10036,7 @@ fn unwrap_anonymous_function_unavailable_with_different_args() {
989210036
"import gleam/list
989310037
989410038
const another_int = 7
9895-
10039+
989610040
pub fn main() {
989710041
list.map([1, 2, 3], fn(int) { op(another_int) })
989810042
}
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)