refactor(semantic): checking label in ContinueStatement based on LabelBuilder (#2202)

This commit is contained in:
Dunqing 2024-01-29 18:24:42 +08:00 committed by GitHub
parent b694a6a76e
commit f59e87f9c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 55 additions and 47 deletions

View file

@ -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"))]

View file

@ -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;
}
}
@ -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.
@ -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::Program(_) | AstKind::Function(_) | AstKind::StaticBlock(_) => {
ctx.error(InvalidContinue(stmt.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::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,
_ => {}
}

View file

@ -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,

View file

@ -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 │ }
╰────

View file

@ -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;