Skip to content

Commit c5a41e0

Browse files
committed
Fix parsing of x=>y=>{x;y;}
quick-lint-js parses the following code incorrectly [1]: (x => y => { x; y; }); This confuses the linter, leading to false positives: hello.js:1:14: warning: use of undeclared variable: x [E0057] Why does this false positive happen? The inner function is sent to the linter immediately, then the outer function's parameter list is sent to the linter: visit_enter_function_scope visit_variable_declaration // y visit_enter_function_scope_body visit_variable_use // x visit_variable_use // y visit_exit_function_scope visit_enter_function_scope visit_variable_declaration // x visit_enter_function_scope_body visit_exit_function_scope This didn't happen in the past. Commit 8087d21 changed the behavior, causing arrow functions with statements to be visited immediately, but deferring visitation for arrow functions with expressions. Fix the parser to visit arrow functions with expressions immediately too: visit_enter_function_scope visit_variable_declaration // x visit_enter_function_scope_body visit_enter_function_scope visit_variable_declaration // y visit_enter_function_scope_body visit_variable_use // x visit_variable_use // y visit_exit_function_scope visit_exit_function_scope This fixes the false positive. Cleanup will happen in future commits. [1] #636
1 parent 8a14979 commit c5a41e0

File tree

3 files changed

+74
-16
lines changed

3 files changed

+74
-16
lines changed

docs/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
quick-lint-js' version numbers are arbitrary. quick-lint-js does *not* adhere to
77
Semantic Versioning.
88

9+
## Unreleased
10+
11+
### Fixed
12+
13+
* Curried arrow functions like `a => b => { a; b; }` no longer falsely reports
14+
[E0057][]. (Thanks to [Christian Mund][] for reporting.)
15+
916
## 2.0.0 (2022-02-08)
1017

1118
[Downloads](https://c.quick-lint-js.com/releases/2.0.0/)
@@ -372,6 +379,7 @@ Beta release.
372379

373380
[AidenThing]: https://github.com/AidenThing
374381
[Amir]: https://github.com/ahmafi
382+
[Christian Mund]: https://github.com/kkkrist
375383
[Daniel La Rocque]: https://github.com/dlarocque
376384
[David Vasileff]: https://github.com/dav000
377385
[Erlliam Mejia]: https://github.com/erlliam

src/quick-lint-js/parse-expression-inl.h

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,11 @@ void parser::visit_expression(expression* ast, Visitor& v,
3838
this->visit_expression(child, v, context);
3939
}
4040
};
41-
auto visit_parameters = [&]() {
42-
for (expression* parameter : ast->children()) {
43-
this->visit_binding_element(parameter, v, variable_kind::_parameter,
44-
/*declaring_token=*/std::nullopt,
45-
/*init_kind=*/variable_init_kind::normal);
46-
}
47-
};
4841
switch (ast->kind()) {
4942
case expression_kind::_class:
5043
case expression_kind::_invalid:
5144
case expression_kind::_missing:
45+
case expression_kind::arrow_function_with_expression:
5246
case expression_kind::arrow_function_with_statements:
5347
case expression_kind::function:
5448
case expression_kind::import:
@@ -77,15 +71,6 @@ void parser::visit_expression(expression* ast, Visitor& v,
7771
visit_children();
7872
break;
7973
}
80-
case expression_kind::arrow_function_with_expression: {
81-
auto* arrow = static_cast<expression::arrow_function_with_expression*>(ast);
82-
v.visit_enter_function_scope();
83-
visit_parameters();
84-
v.visit_enter_function_scope_body();
85-
this->visit_expression(arrow->body_, v, variable_context::rhs);
86-
v.visit_exit_function_scope();
87-
break;
88-
}
8974
case expression_kind::assignment: {
9075
expression* lhs = ast->child_0();
9176
expression* rhs = ast->child_1();
@@ -1839,11 +1824,31 @@ expression* parser::parse_arrow_function_body_impl(
18391824
return this->make_expression<expression::arrow_function_with_statements>(
18401825
attributes, ast->children(), parameter_list_begin, span_end);
18411826
} else {
1827+
v.visit_enter_function_scope();
1828+
1829+
// TODO(strager): Avoid creating a temporary expression just to iterate over
1830+
// the parameters.
1831+
expression* ast =
1832+
this->make_expression<expression::arrow_function_with_expression>(
1833+
attributes, std::forward<Args>(args)...,
1834+
this->make_expression<expression::_invalid>(this->peek().span()),
1835+
parameter_list_begin);
1836+
for (expression* parameter : ast->children()) {
1837+
this->visit_binding_element(parameter, v, variable_kind::_parameter,
1838+
/*declaring_token=*/std::nullopt,
1839+
/*init_kind=*/variable_init_kind::normal);
1840+
}
1841+
1842+
v.visit_enter_function_scope_body();
1843+
// TODO(strager): Use parse_and_visit_expression instead.
18421844
expression* body =
18431845
this->parse_expression(v, precedence{
18441846
.commas = false,
18451847
.in_operator = allow_in_operator,
18461848
});
1849+
this->visit_expression(body, v, variable_context::rhs);
1850+
v.visit_exit_function_scope();
1851+
18471852
return this->make_expression<expression::arrow_function_with_expression>(
18481853
attributes, std::forward<Args>(args)..., body, parameter_list_begin);
18491854
}

test/test-parse-function.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,51 @@ TEST(test_parse, arrow_function_expression_with_statements) {
513513
}
514514
}
515515

516+
TEST(test_parse, nested_arrow_function) {
517+
for (string8_view code : {
518+
u8"(x => y => (x, y));"_sv,
519+
u8"(x => { (y => (x, y)); });"_sv,
520+
u8"(x => y => { x; y; });"_sv,
521+
u8"(x => { (y => { x; y; }); });"_sv,
522+
}) {
523+
spy_visitor v = parse_and_visit_statement(code);
524+
SCOPED_TRACE(out_string8(code));
525+
EXPECT_THAT(v.visits, ElementsAre("visit_enter_function_scope", //
526+
"visit_variable_declaration", // x
527+
"visit_enter_function_scope_body", //
528+
"visit_enter_function_scope", //
529+
"visit_variable_declaration", // y
530+
"visit_enter_function_scope_body", //
531+
"visit_variable_use", // x
532+
"visit_variable_use", // y
533+
"visit_exit_function_scope", //
534+
"visit_exit_function_scope"));
535+
EXPECT_THAT(
536+
v.variable_declarations,
537+
ElementsAre(
538+
spy_visitor::visited_variable_declaration{
539+
u8"x", variable_kind::_parameter, variable_init_kind::normal},
540+
spy_visitor::visited_variable_declaration{
541+
u8"y", variable_kind::_parameter, variable_init_kind::normal}));
542+
EXPECT_THAT(v.variable_uses,
543+
ElementsAre(spy_visitor::visited_variable_use{u8"x"},
544+
spy_visitor::visited_variable_use{u8"y"}));
545+
}
546+
547+
{
548+
spy_visitor v = parse_and_visit_statement(u8"(a => ((b = a) => {}));"_sv);
549+
EXPECT_THAT(v.visits, ElementsAre("visit_enter_function_scope", //
550+
"visit_variable_declaration", // a
551+
"visit_enter_function_scope_body", //
552+
"visit_enter_function_scope", //
553+
"visit_variable_use", // a
554+
"visit_variable_declaration", // b
555+
"visit_enter_function_scope_body", //
556+
"visit_exit_function_scope", //
557+
"visit_exit_function_scope"));
558+
}
559+
}
560+
516561
TEST(test_parse, function_statements_allow_trailing_commas_in_parameter_list) {
517562
{
518563
spy_visitor v = parse_and_visit_statement(u8"function f(x,) { y; });"_sv);

0 commit comments

Comments
 (0)