Skip to content

Commit 38fd980

Browse files
Hendi48linusg
authored andcommitted
LibJS: Improve function hoisting across blocks
The parser now keeps track of a scope chain so that it can hoist function declarations to the closest function scope.
1 parent 72f8d90 commit 38fd980

File tree

6 files changed

+79
-22
lines changed

6 files changed

+79
-22
lines changed

Userland/Libraries/LibJS/AST.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,4 +2377,9 @@ void ScopeNode::add_functions(NonnullRefPtrVector<FunctionDeclaration> functions
23772377
m_functions.extend(move(functions));
23782378
}
23792379

2380+
void ScopeNode::add_hoisted_functions(NonnullRefPtrVector<FunctionDeclaration> hoisted_functions)
2381+
{
2382+
m_hoisted_functions.extend(move(hoisted_functions));
2383+
}
2384+
23802385
}

Userland/Libraries/LibJS/AST.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ class ScopeNode : public Statement {
145145

146146
void add_variables(NonnullRefPtrVector<VariableDeclaration>);
147147
void add_functions(NonnullRefPtrVector<FunctionDeclaration>);
148+
void add_hoisted_functions(NonnullRefPtrVector<FunctionDeclaration>);
148149
NonnullRefPtrVector<VariableDeclaration> const& variables() const { return m_variables; }
149150
NonnullRefPtrVector<FunctionDeclaration> const& functions() const { return m_functions; }
151+
NonnullRefPtrVector<FunctionDeclaration> const& hoisted_functions() const { return m_hoisted_functions; }
150152

151153
protected:
152154
explicit ScopeNode(SourceRange source_range)
@@ -160,6 +162,7 @@ class ScopeNode : public Statement {
160162
NonnullRefPtrVector<Statement> m_children;
161163
NonnullRefPtrVector<VariableDeclaration> m_variables;
162164
NonnullRefPtrVector<FunctionDeclaration> m_functions;
165+
NonnullRefPtrVector<FunctionDeclaration> m_hoisted_functions;
163166
};
164167

165168
class Program final : public ScopeNode {

Userland/Libraries/LibJS/Interpreter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ const GlobalObject& Interpreter::global_object() const
8484
void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type, GlobalObject& global_object)
8585
{
8686
ScopeGuard guard([&] {
87+
for (auto& declaration : scope_node.hoisted_functions()) {
88+
lexical_environment()->put_into_environment(declaration.name(), { js_undefined(), DeclarationKind::Var });
89+
}
8790
for (auto& declaration : scope_node.functions()) {
8891
auto* function = OrdinaryFunctionObject::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lexical_environment(), declaration.kind(), declaration.is_strict_mode());
8992
vm().set_variable(declaration.name(), function, global_object);

Userland/Libraries/LibJS/Parser.cpp

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,18 @@ class ScopePusher {
3131
enum Type {
3232
Var = 1,
3333
Let = 2,
34-
Function = 3,
3534
};
3635

37-
ScopePusher(Parser& parser, unsigned mask)
36+
ScopePusher(Parser& parser, unsigned mask, Parser::Scope::Type scope_type)
3837
: m_parser(parser)
3938
, m_mask(mask)
4039
{
4140
if (m_mask & Var)
4241
m_parser.m_state.var_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
4342
if (m_mask & Let)
4443
m_parser.m_state.let_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
45-
if (m_mask & Function)
46-
m_parser.m_state.function_scopes.append(NonnullRefPtrVector<FunctionDeclaration>());
44+
45+
m_parser.m_state.current_scope = create<Parser::Scope>(scope_type, m_parser.m_state.current_scope);
4746
}
4847

4948
~ScopePusher()
@@ -52,8 +51,20 @@ class ScopePusher {
5251
m_parser.m_state.var_scopes.take_last();
5352
if (m_mask & Let)
5453
m_parser.m_state.let_scopes.take_last();
55-
if (m_mask & Function)
56-
m_parser.m_state.function_scopes.take_last();
54+
55+
auto& popped = m_parser.m_state.current_scope;
56+
m_parser.m_state.current_scope = popped->parent;
57+
}
58+
59+
void add_to_scope_node(NonnullRefPtr<ScopeNode> scope_node)
60+
{
61+
if (m_mask & Var)
62+
scope_node->add_variables(m_parser.m_state.var_scopes.last());
63+
if (m_mask & Let)
64+
scope_node->add_variables(m_parser.m_state.let_scopes.last());
65+
66+
scope_node->add_functions(m_parser.m_state.current_scope->function_declarations);
67+
scope_node->add_hoisted_functions(m_parser.m_state.current_scope->hoisted_function_declarations);
5768
}
5869

5970
Parser& m_parser;
@@ -180,6 +191,24 @@ Parser::ParserState::ParserState(Lexer l)
180191
{
181192
}
182193

194+
Parser::Scope::Scope(Parser::Scope::Type type, RefPtr<Parser::Scope> parent_scope)
195+
: type(type)
196+
, parent(move(parent_scope))
197+
{
198+
}
199+
200+
RefPtr<Parser::Scope> Parser::Scope::get_current_function_scope()
201+
{
202+
if (this->type == Parser::Scope::Function) {
203+
return *this;
204+
}
205+
auto result = this->parent;
206+
while (result->type != Parser::Scope::Function) {
207+
result = result->parent;
208+
}
209+
return result;
210+
}
211+
183212
Parser::Parser(Lexer lexer)
184213
: m_state(move(lexer))
185214
{
@@ -229,7 +258,7 @@ Associativity Parser::operator_associativity(TokenType type) const
229258
NonnullRefPtr<Program> Parser::parse_program(bool starts_in_strict_mode)
230259
{
231260
auto rule_start = push_start();
232-
ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Let | ScopePusher::Function);
261+
ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Let, Scope::Function);
233262
auto program = adopt_ref(*new Program({ m_filename, rule_start.position(), position() }));
234263
if (starts_in_strict_mode) {
235264
program->set_strict_mode();
@@ -258,9 +287,7 @@ NonnullRefPtr<Program> Parser::parse_program(bool starts_in_strict_mode)
258287
first = false;
259288
}
260289
if (m_state.var_scopes.size() == 1) {
261-
program->add_variables(m_state.var_scopes.last());
262-
program->add_variables(m_state.let_scopes.last());
263-
program->add_functions(m_state.function_scopes.last());
290+
scope.add_to_scope_node(program);
264291
} else {
265292
syntax_error("Unclosed lexical_environment");
266293
}
@@ -276,7 +303,8 @@ NonnullRefPtr<Declaration> Parser::parse_declaration()
276303
return parse_class_declaration();
277304
case TokenType::Function: {
278305
auto declaration = parse_function_node<FunctionDeclaration>();
279-
m_state.function_scopes.last().append(declaration);
306+
m_state.current_scope->function_declarations.append(declaration);
307+
m_state.current_scope->get_current_function_scope()->hoisted_function_declarations.append(declaration);
280308
return declaration;
281309
}
282310
case TokenType::Let:
@@ -399,7 +427,10 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
399427
TemporaryChange change(m_state.in_arrow_function_context, true);
400428
if (match(TokenType::CurlyOpen)) {
401429
// Parse a function body with statements
402-
return parse_block_statement(is_strict);
430+
ScopePusher scope(*this, ScopePusher::Var, Scope::Function);
431+
auto body = parse_block_statement(is_strict);
432+
scope.add_to_scope_node(body);
433+
return body;
403434
}
404435
if (match_expression()) {
405436
// Parse a function body which returns a single expression
@@ -1372,7 +1403,7 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
13721403
NonnullRefPtr<BlockStatement> Parser::parse_block_statement(bool& is_strict)
13731404
{
13741405
auto rule_start = push_start();
1375-
ScopePusher scope(*this, ScopePusher::Let);
1406+
ScopePusher scope(*this, ScopePusher::Let, Parser::Scope::Block);
13761407
auto block = create_ast_node<BlockStatement>({ m_state.current_token.filename(), rule_start.position(), position() });
13771408
consume(TokenType::CurlyOpen);
13781409

@@ -1404,8 +1435,7 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement(bool& is_strict)
14041435
m_state.strict_mode = initial_strict_mode_state;
14051436
m_state.string_legacy_octal_escape_sequence_in_scope = false;
14061437
consume(TokenType::CurlyClose);
1407-
block->add_variables(m_state.let_scopes.last());
1408-
block->add_functions(m_state.function_scopes.last());
1438+
scope.add_to_scope_node(block);
14091439
return block;
14101440
}
14111441

@@ -1418,7 +1448,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
14181448
TemporaryChange super_property_access_rollback(m_state.allow_super_property_lookup, !!(parse_options & FunctionNodeParseOptions::AllowSuperPropertyLookup));
14191449
TemporaryChange super_constructor_call_rollback(m_state.allow_super_constructor_call, !!(parse_options & FunctionNodeParseOptions::AllowSuperConstructorCall));
14201450

1421-
ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Function);
1451+
ScopePusher scope(*this, ScopePusher::Var, Parser::Scope::Function);
14221452

14231453
constexpr auto is_function_expression = IsSame<FunctionNodeType, FunctionExpression>;
14241454
auto is_generator = (parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0;
@@ -1460,8 +1490,8 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
14601490

14611491
m_state.function_parameters.take_last();
14621492

1463-
body->add_variables(m_state.var_scopes.last());
1464-
body->add_functions(m_state.function_scopes.last());
1493+
scope.add_to_scope_node(body);
1494+
14651495
return create_ast_node<FunctionNodeType>(
14661496
{ m_state.current_token.filename(), rule_start.position(), position() },
14671497
name, move(body), move(parameters), function_length,
@@ -1962,10 +1992,10 @@ NonnullRefPtr<IfStatement> Parser::parse_if_statement()
19621992
// Code matching this production is processed as if each matching occurrence of
19631993
// FunctionDeclaration[?Yield, ?Await, ~Default] was the sole StatementListItem
19641994
// of a BlockStatement occupying that position in the source code.
1965-
ScopePusher scope(*this, ScopePusher::Let);
1995+
ScopePusher scope(*this, ScopePusher::Let, Parser::Scope::Block);
19661996
auto block = create_ast_node<BlockStatement>({ m_state.current_token.filename(), rule_start.position(), position() });
19671997
block->append(parse_declaration());
1968-
block->add_functions(m_state.function_scopes.last());
1998+
scope.add_to_scope_node(block);
19691999
return block;
19702000
};
19712001

Userland/Libraries/LibJS/Parser.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,29 @@ class Parser {
196196

197197
[[nodiscard]] RulePosition push_start() { return { *this, position() }; }
198198

199+
struct Scope : public RefCounted<Scope> {
200+
enum Type {
201+
Function,
202+
Block,
203+
};
204+
205+
Type type;
206+
RefPtr<Scope> parent;
207+
208+
NonnullRefPtrVector<FunctionDeclaration> function_declarations;
209+
NonnullRefPtrVector<FunctionDeclaration> hoisted_function_declarations;
210+
211+
explicit Scope(Type, RefPtr<Scope>);
212+
RefPtr<Scope> get_current_function_scope();
213+
};
214+
199215
struct ParserState {
200216
Lexer lexer;
201217
Token current_token;
202218
Vector<Error> errors;
203219
Vector<NonnullRefPtrVector<VariableDeclaration>> var_scopes;
204220
Vector<NonnullRefPtrVector<VariableDeclaration>> let_scopes;
205-
Vector<NonnullRefPtrVector<FunctionDeclaration>> function_scopes;
221+
RefPtr<Scope> current_scope;
206222

207223
Vector<Vector<FunctionNode::Parameter>&> function_parameters;
208224

Userland/Libraries/LibJS/Tests/functions/function-hoisting.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test("basic functionality", () => {
88
});
99

1010
// First two calls produce a ReferenceError, but the declarations should be hoisted
11-
test.skip("functions are hoisted across non-lexical scopes", () => {
11+
test("functions are hoisted across non-lexical scopes", () => {
1212
expect(scopedHoisted).toBeUndefined();
1313
expect(callScopedHoisted).toBeUndefined();
1414
{

0 commit comments

Comments
 (0)