Skip to content

Commit

Permalink
refactor(semantic): checking label in ContinueStatement based on Labe…
Browse files Browse the repository at this point in the history
…lBuilder (#2202)
  • Loading branch information
Dunqing authored Jan 29, 2024
1 parent b694a6a commit f59e87f
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 47 deletions.
13 changes: 13 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,19 @@ pub enum Statement<'a> {
Declaration(Declaration<'a>),
}

impl<'a> Statement<'a> {
pub fn is_iteration_statement(&self) -> bool {
matches!(
self,
Statement::DoWhileStatement(_)
| Statement::ForInStatement(_)
| Statement::ForOfStatement(_)
| Statement::ForStatement(_)
| Statement::WhileStatement(_)
)
}
}

/// Directive Prologue
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
Expand Down
73 changes: 29 additions & 44 deletions crates/oxc_semantic/src/checker/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,32 @@ struct InvalidLabelJumpTarget(#[label] Span);
#[diagnostic()]
struct InvalidLabelTarget(#[label("This label is used, but not defined")] Span);

fn check_label(label: &LabelIdentifier, ctx: &SemanticBuilder) {
fn check_label(label: &LabelIdentifier, ctx: &SemanticBuilder, is_continue: bool) {
#[derive(Debug, Error, Diagnostic)]
#[error(
"A `{0}` statement can only jump to a label of an enclosing `for`, `while` or `do while` statement."
)]
#[diagnostic()]
struct InvalidLabelNonIteration(
&'static str,
#[label("This is an non-iteration statement")] Span,
#[label("for this label")] Span,
);

if ctx.label_builder.is_inside_labeled_statement() {
for labeled in ctx.label_builder.get_accessible_labels() {
if label.name == labeled.name {
if is_continue
&& matches!(ctx.nodes.kind(labeled.id), AstKind::LabeledStatement(stmt) if {
let mut body = &stmt.body;
while let Statement::LabeledStatement(stmt) = body {
body = &stmt.body;
}
!body.is_iteration_statement()
})
{
ctx.error(InvalidLabelNonIteration("continue", labeled.span, label.span));
}
return;
}
}
Expand All @@ -617,7 +639,7 @@ fn check_break_statement<'a>(stmt: &BreakStatement, node: &AstNode<'a>, ctx: &Se
struct InvalidBreak(#[label] Span);

if let Some(label) = &stmt.label {
return check_label(label, ctx);
return check_label(label, ctx, false);
}

// It is a Syntax Error if this BreakStatement is not nested, directly or indirectly (but not crossing function or static initialization block boundaries), within an IterationStatement or a SwitchStatement.
Expand Down Expand Up @@ -649,53 +671,16 @@ fn check_continue_statement<'a>(
))]
struct InvalidContinue(#[label] Span);

#[derive(Debug, Error, Diagnostic)]
#[error(
"A `{0}` statement can only jump to a label of an enclosing `for`, `while` or `do while` statement."
)]
#[diagnostic()]
struct InvalidLabelNonIteration(
&'static str,
#[label("This is an non-iteration statement")] Span,
#[label("for this label")] Span,
);
if let Some(label) = &stmt.label {
return check_label(label, ctx, true);
}

// It is a Syntax Error if this ContinueStatement is not nested, directly or indirectly (but not crossing function or static initialization block boundaries), within an IterationStatement.
for node_id in ctx.nodes.ancestors(node.id()).skip(1) {
match ctx.nodes.kind(node_id) {
AstKind::Program(_) => {
return stmt.label.as_ref().map_or_else(
|| ctx.error(InvalidContinue(stmt.span)),
|label| ctx.error(InvalidLabelTarget(label.span)),
);
}
AstKind::Function(_) | AstKind::StaticBlock(_) => {
return stmt.label.as_ref().map_or_else(
|| ctx.error(InvalidContinue(stmt.span)),
|label| ctx.error(InvalidLabelJumpTarget(label.span)),
);
AstKind::Program(_) | AstKind::Function(_) | AstKind::StaticBlock(_) => {
ctx.error(InvalidContinue(stmt.span));
}
AstKind::LabeledStatement(labeled_statement) => match &stmt.label {
Some(label) if label.name == labeled_statement.label.name => {
if matches!(
labeled_statement.body,
Statement::LabeledStatement(_)
| Statement::DoWhileStatement(_)
| Statement::WhileStatement(_)
| Statement::ForStatement(_)
| Statement::ForInStatement(_)
| Statement::ForOfStatement(_)
) {
break;
}
return ctx.error(InvalidLabelNonIteration(
"continue",
labeled_statement.label.span,
label.span,
));
}
_ => {}
},
kind if kind.is_iteration_statement() && stmt.label.is_none() => break,
_ => {}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_semantic/src/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::AstNodeId;

#[derive(Debug)]
pub struct Label<'a> {
id: AstNodeId,
pub id: AstNodeId,
pub name: &'a str,
pub span: Span,
used: bool,
Expand Down
5 changes: 3 additions & 2 deletions tasks/coverage/parser_test262.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28292,11 +28292,12 @@ Expect Syntax Error: "language/import/import-attributes/json-named-bindings.js"
23 │ }
╰────

× Jump target cannot cross function boundary.
× Use of undefined label
╭─[language/statements/class/static-init-invalid-undefined-continue-target.js:21:1]
21 │ x: while (false) {
22 │ continue y;
· ─
· ┬
· ╰── This label is used, but not defined
23 │ }
╰────

Expand Down
9 changes: 9 additions & 0 deletions tasks/coverage/parser_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6688,6 +6688,15 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
╰────
help: A `continue` statement can only be used within an enclosing `for`, `while` or `do while`

× Illegal continue statement: no surrounding iteration statement
╭─[compiler/invalidContinueInDownlevelAsync.ts:2:1]
2 │ if (true) {
3 │ continue;
· ─────────
4 │ }
╰────
help: A `continue` statement can only be used within an enclosing `for`, `while` or `do while`

× Expected `;` but found `[`
╭─[compiler/invalidLetInForOfAndForIn_ES5.ts:5:1]
5 │ var let = 10;
Expand Down

0 comments on commit f59e87f

Please sign in to comment.