Skip to content

Commit

Permalink
Parse class static blocks
Browse files Browse the repository at this point in the history
Summary:
Manual port of D65822124.

Class static blocks can't have return in them, so we have to check for
the Return param and error in the parser, instead of waiting for
SemanticValidator to error (where we'd have to track whether we are
currently in the top-level of a class static block).

We also have to forbid await expressions, which is easy to do by using
the forbidAwaitExpression_ flag in SemanticValidator.

The ESTree spec adds a StaticBlock node which is a BlockStatement.

NOTE: We have to make SH-specific changes for typed mode. We have to
allow return outside functions because we wrap in IIFE after the parse
step.

Reviewed By: fbmal7

Differential Revision: D66711283

fbshipit-source-id: 0b369d9cfd3f176f6c26d469c0960f038c6910cc
  • Loading branch information
avp authored and facebook-github-bot committed Dec 3, 2024
1 parent 9b403d0 commit 3ea2fe9
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 6 deletions.
1 change: 1 addition & 0 deletions include/hermes/AST/ESTree.def
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ ESTREE_LAST(LoopStatement)
ESTREE_NODE_0_ARGS(DebuggerStatement, Statement)
ESTREE_NODE_0_ARGS(EmptyStatement, Statement)
ESTREE_NODE_1_ARGS(BlockStatement, Statement, NodeList, body, false)
ESTREE_NODE_1_ARGS(StaticBlock, Statement, NodeList, body, false)
ESTREE_NODE_1_ARGS(BreakStatement, Statement, NodePtr, label, true)
ESTREE_NODE_1_ARGS(ContinueStatement, Statement, NodePtr, label, true)
ESTREE_NODE_1_ARGS(ThrowStatement, Statement, NodePtr, argument, false)
Expand Down
6 changes: 5 additions & 1 deletion lib/CompilerDriver/CompilerDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,10 @@ ESTree::NodePtr parseJS(
mode = parser::LazyParse;
}

bool shouldWrapInIIFE = cl::Typed && !cl::Script;
if (shouldWrapInIIFE)
context->setAllowReturnOutsideFunction(true);

Optional<ESTree::ProgramNode *> parsedJs;

{
Expand Down Expand Up @@ -843,7 +847,7 @@ ESTree::NodePtr parseJS(
}

// If we are executing in typed mode and not script, then wrap the program.
if (cl::Typed && !cl::Script) {
if (shouldWrapInIIFE) {
parsedAST = wrapInIIFE(context, llvh::cast<ESTree::ProgramNode>(parsedAST));
// In case this API decides it can fail in the future, check for a
// nullptr.
Expand Down
40 changes: 40 additions & 0 deletions lib/Parser/JSParserImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,10 @@ Optional<ESTree::Node *> JSParserImpl::parseStatement(Param param) {
case TokenKind::rw_break:
_RET(parseBreakStatement());
case TokenKind::rw_return:
if (!param.has(ParamReturn) && !context_.allowReturnOutsideFunction()) {
// Illegal location for a return statement, but we can keep parsing.
error(tok_->getSourceRange(), "'return' not in a function");
}
_RET(parseReturnStatement());
case TokenKind::rw_with:
_RET(parseWithStatement(param.get(ParamReturn)));
Expand Down Expand Up @@ -4911,6 +4915,42 @@ Optional<ESTree::Node *> JSParserImpl::parseClassElement(
}
} else if (checkAndEat(TokenKind::star)) {
special = SpecialKind::Generator;
} else if (isStatic && checkAndEat(TokenKind::l_brace)) {
// This is a static block.
// ES14.0 15.7
// ClassStaticBlock :
// static { ClassStaticBlockBody }
// ^
SMLoc braceLoc = tok_->getStartLoc();
ESTree::NodeList body;

{
// ClassStaticBlockStatementList :
// StatementList[~Yield, +Await, ~Return]opt
// ^
llvh::SaveAndRestore oldParamYield{paramYield_, false};
llvh::SaveAndRestore oldParamAwait{paramAwait_, true};
if (!parseStatementList(
Param{},
TokenKind::r_brace,
/* parseDirectives */ false,
AllowImportExport::No,
body)) {
return None;
}
}
if (!eat(
TokenKind::r_brace,
JSLexer::GrammarContext::AllowRegExp,
"at end of static block",
"static block starts here",
braceLoc))
return None;

return setLocation(
startLoc,
getPrevTokenEndLoc(),
new (context_) ESTree::StaticBlockNode(std::move(body)));
} else if (isStatic && staticIsPropertyName()) {
// This is the name of the property/method.
// We've already parsed 'static', but it must be used as the
Expand Down
10 changes: 10 additions & 0 deletions lib/Sema/SemanticResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,16 @@ void SemanticResolver::visit(ESTree::ClassPropertyNode *node) {
}
}

void SemanticResolver::visit(StaticBlockNode *node) {
if (compile_)
sm_.error(node->getSourceRange(), "class static blocks are not supported");
// ES14.0 15.7.1
// It is a Syntax Error if ClassStaticBlockStatementList Contains await is
// true.
llvh::SaveAndRestore<bool> oldForbidAwait{forbidAwaitExpression_, true};
visitESTreeChildren(*this, node);
}

void SemanticResolver::visit(ESTree::SuperNode *node, ESTree::Node *parent) {
// Error if we try to reference super but there is currently no valid binding
// to it.
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/SemanticResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ class SemanticResolver
void visit(ESTree::PrivateNameNode *node);
void visit(ESTree::ClassPrivatePropertyNode *node);
void visit(ESTree::ClassPropertyNode *node);
void visit(ESTree::StaticBlockNode *node);
void visit(ESTree::MethodDefinitionNode *node, ESTree::Node *parent);

void visit(ESTree::SuperNode *node, ESTree::Node *parent);
Expand Down
4 changes: 2 additions & 2 deletions test/AST/global-return.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast %s 2>&1 ) | %FileCheck --match-full-lines %s
// RUN: (! %hermesc -dump-ast %s 2>&1 ) | %FileCheck --match-full-lines %s

return;
//CHECK: {{.*}}global-return.js:10:1: error: 'return' not in a function
//CHECK-NEXT: return;
//CHECK-NEXT: ^~~~~~~
//CHECK-NEXT: ^~~~~~
22 changes: 22 additions & 0 deletions test/Parser/class-static-block-await-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast -pretty-json %s 2>&1 ) | %FileCheck --match-full-lines %s

async function foo() {
// This is fine.
class C extends (await 1) {
static {
// This is not fine.
await 1;
}
}
}

// CHECK:{{.*}}class-static-block-await-error.js:15:7: error: 'await' not in an async function
// CHECK-NEXT: await 1;
// CHECK-NEXT: ^~~~~~~
28 changes: 28 additions & 0 deletions test/Parser/class-static-block-return-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast -pretty-json %s 2>&1 ) | %FileCheck --match-full-lines %s

class C {
static {
return 1;
}
}
function foo() {
class C {
static {
return 1;
}
}
}

// CHECK:{{.*}}class-static-block-return-error.js:12:5: error: 'return' not in a function
// CHECK-NEXT: return 1;
// CHECK-NEXT: ^~~~~~
// CHECK:{{.*}}class-static-block-return-error.js:18:7: error: 'return' not in a function
// CHECK-NEXT: return 1;
// CHECK-NEXT: ^~~~~~
20 changes: 20 additions & 0 deletions test/Parser/class-static-block-yield-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast -pretty-json %s 2>&1 ) | %FileCheck --match-full-lines %s

function* foo() {
class C {
static {
yield 1;
}
}
}

// CHECK:{{.*}}class-static-block-yield-error.js:13:7: error: invalid expression
// CHECK-NEXT: yield 1;
// CHECK-NEXT: ^
64 changes: 64 additions & 0 deletions test/Parser/class-static-block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: %hermesc -dump-ast -pretty-json %s | %FileCheck %s --match-full-lines

// CHECK-LABEL: {
// CHECK-NEXT: "type": "Program",
// CHECK-NEXT: "body": [

class C {
// CHECK-NEXT: {
// CHECK-NEXT: "type": "ClassDeclaration",
// CHECK-NEXT: "id": {
// CHECK-NEXT: "type": "Identifier",
// CHECK-NEXT: "name": "C"
// CHECK-NEXT: },
// CHECK-NEXT: "superClass": null,
// CHECK-NEXT: "body": {
// CHECK-NEXT: "type": "ClassBody",
// CHECK-NEXT: "body": [

static {}
// CHECK-NEXT: {
// CHECK-NEXT: "type": "StaticBlock",
// CHECK-NEXT: "body": []
// CHECK-NEXT: },

static {
let x = 1;
}
// CHECK-NEXT: {
// CHECK-NEXT: "type": "StaticBlock",
// CHECK-NEXT: "body": [
// CHECK-NEXT: {
// CHECK-NEXT: "type": "VariableDeclaration",
// CHECK-NEXT: "kind": "let",
// CHECK-NEXT: "declarations": [
// CHECK-NEXT: {
// CHECK-NEXT: "type": "VariableDeclarator",
// CHECK-NEXT: "init": {
// CHECK-NEXT: "type": "NumericLiteral",
// CHECK-NEXT: "value": 1,
// CHECK-NEXT: "raw": "1"
// CHECK-NEXT: },
// CHECK-NEXT: "id": {
// CHECK-NEXT: "type": "Identifier",
// CHECK-NEXT: "name": "x"
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }

}
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
4 changes: 2 additions & 2 deletions test/Parser/object_break.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

// RUN: (! %hermesc -dump-transformed-ast %s 2>&1 ) | %FileCheck %s
// RUN: (! %hermes %s 2>&1 ) | %FileCheck %s

//CHECK: error: 'break' not within a loop or a switch
{
Expand All @@ -14,5 +14,5 @@
break;
}
})
return 2
2;
}
6 changes: 5 additions & 1 deletion tools/shermes/shermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,10 @@ ESTree::NodePtr parseJS(
(singleInputSourceMap.empty() || fileBufs.size() == 1) &&
"singleInputSourceMap can only be specified for a single input file");

bool shouldWrapInIIFE = cli::Typed && !cli::Script;
if (shouldWrapInIIFE)
context->setAllowReturnOutsideFunction(true);

// Whether a parse error ocurred in one of the inputs.
bool parseError = false;
for (std::unique_ptr<llvh::MemoryBuffer> &fileBuf : fileBufs) {
Expand Down Expand Up @@ -801,7 +805,7 @@ ESTree::NodePtr parseJS(
}

// If we are executing in typed mode and not script, then wrap the program.
if (cli::Typed && !cli::Script) {
if (shouldWrapInIIFE) {
parsedAST = wrapInIIFE(context, parsedAST);
// In case this API decides it can fail in the future, check for a
// nullptr.
Expand Down
3 changes: 3 additions & 0 deletions unsupported/juno/crates/hermes/src/parser/generated_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub enum NodeKind {
DebuggerStatement,
EmptyStatement,
BlockStatement,
StaticBlock,
BreakStatement,
ContinueStatement,
ThrowStatement,
Expand Down Expand Up @@ -338,6 +339,8 @@ extern "C" {
pub fn hermes_get_ForStatement_body(node: NodePtr) -> NodePtr;
// BlockStatement
pub fn hermes_get_BlockStatement_body(node: NodePtr) -> NodeListRef;
// StaticBlock
pub fn hermes_get_StaticBlock_body(node: NodePtr) -> NodeListRef;
// BreakStatement
pub fn hermes_get_BreakStatement_label(node: NodePtr) -> NodePtrOpt;
// ContinueStatement
Expand Down
9 changes: 9 additions & 0 deletions unsupported/juno/crates/juno/src/hparser/generated_cvt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ pub unsafe fn cvt_node_ptr<'parser, 'gc>(
template.metadata.range.end = if nr.source_range.is_empty() { template.metadata.range.start } else { cvt.cvt_smloc(nr.source_range.end.pred()) };
ast::builder::BlockStatement::build_template(gc, template)
}
NodeKind::StaticBlock => {
let body = cvt_node_list(cvt, gc, hermes_get_StaticBlock_body(n));
let mut template = ast::template::StaticBlock {
metadata: ast::TemplateMetadata {range, ..Default::default()},
body,
};
template.metadata.range.end = if nr.source_range.is_empty() { template.metadata.range.start } else { cvt.cvt_smloc(nr.source_range.end.pred()) };
ast::builder::StaticBlock::build_template(gc, template)
}
NodeKind::BreakStatement => {
let label = cvt_node_ptr_opt(cvt, gc, hermes_get_BreakStatement_label(n));
let mut template = ast::template::BreakStatement {
Expand Down

0 comments on commit 3ea2fe9

Please sign in to comment.