Skip to content

Commit

Permalink
[GR-52036] Backport to 24.0: Switch statement expression should not b…
Browse files Browse the repository at this point in the history
…e parsed in switch block scope.

PullRequest: js/3063
  • Loading branch information
woess committed Feb 22, 2024
2 parents 6a7a408 + 5a1d50a commit 60733ff
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 25 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -3612,12 +3612,36 @@ private void switchStatement(boolean yield, boolean await) {
ParserContextBlockNode switchBlock;
SwitchNode switchStatement;
try {
assert type == TokenType.SWITCH; // tested in caller.
next();

/*
* Note: Identifier references in the switch expression need to be resolved in the scope
* of the outer block, so we must parse the expression before pushing the switch scope.
*/
expect(LPAREN);
int expressionLine = line;
Expression expression = expression(yield, await);
expect(RPAREN);

// Desugar expression to a synthetic let variable assignment in the outer block.
// This simplifies lexical scope analysis (the expression is outside the switch
// block).
// e.g.: let x = 1; switch (x) { case 0: let x = 2; } =>
// let x = 1; { let :switch = x; { let x; switch (:switch) { case 0: x = 2; } } }
if (useBlockScope()) {
IdentNode switchExprName = new IdentNode(Token.recast(expression.getToken(), IDENT), expression.getFinish(), lexer.stringIntern(SWITCH_BINDING_NAME));
VarNode varNode = new VarNode(expressionLine, Token.recast(expression.getToken(), LET), expression.getFinish(), switchExprName, expression, VarNode.IS_LET);
outerBlock.appendStatement(varNode);
declareVar(outerBlock.getScope(), varNode);
expression = switchExprName;
}

// Block to capture variables declared inside the switch statement.
switchBlock = newBlock(Scope.createSwitchBlock(lc.getCurrentScope()));
switchBlock.setFlag(Block.IS_SYNTHETIC | Block.IS_SWITCH_BLOCK);

// SWITCH tested in caller.
next();
expect(LBRACE);

// Create and add switch statement.
final ParserContextSwitchNode switchNode = new ParserContextSwitchNode();
Expand All @@ -3628,26 +3652,6 @@ private void switchStatement(boolean yield, boolean await) {
final ArrayList<CaseNode> cases = new ArrayList<>();

try {
expect(LPAREN);
int expressionLine = line;
Expression expression = expression(yield, await);
expect(RPAREN);

expect(LBRACE);

// Desugar expression to a synthetic let variable assignment in the outer block.
// This simplifies lexical scope analysis (the expression is outside the switch
// block).
// e.g.: let x = 1; switch (x) { case 0: let x = 2; } =>
// let x = 1; { let :switch = x; { let x; switch (:switch) { case 0: x = 2; } } }
if (useBlockScope()) {
IdentNode switchExprName = new IdentNode(Token.recast(expression.getToken(), IDENT), expression.getFinish(), lexer.stringIntern(SWITCH_BINDING_NAME));
VarNode varNode = new VarNode(expressionLine, Token.recast(expression.getToken(), LET), expression.getFinish(), switchExprName, expression, VarNode.IS_LET);
outerBlock.appendStatement(varNode);
declareVar(outerBlock.getScope(), varNode);
expression = switchExprName;
}

while (type != RBRACE) {
// Prepare for next case.
Expression caseExpression = null;
Expand Down Expand Up @@ -3686,6 +3690,7 @@ private void switchStatement(boolean yield, boolean await) {
cases.add(caseNode);
}

assert type == RBRACE;
next();

switchStatement = new SwitchNode(switchLine, switchToken, finish, expression, cases, defaultCaseIndex);
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -759,7 +759,7 @@ private String getScopeKindName() {
*/
static final class UseInfo {
/** Used name. */
String name;
final String name;
/** Resolved scope in which the symbol has been defined. Must be a parent scope. */
Scope def;
/** Local scope in which the symbol is used. */
Expand Down
120 changes: 120 additions & 0 deletions graal-js/src/com.oracle.truffle.js.test/js/GR-52036.js
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
*/

/**
* Tests that an identifier reference in a switch expression declared in an outer function scope
* is correctly resolved when a binding with the same name is declared in the switch block scope.
*/

load("assert.js");

const Type = {
FLOAT: {},
INTEGER: {},
LONG: {},
BIGDECIMAL: {},
DATE: {},
TIME: {},
DATETIME: {},
DURATION: {},
};

function createFormatter(type, options) {
return {type, options};
}

var y = Type.BIGDECIMAL;
var O = {decimalSeparator: "."};

var F1 = (function getFormatterForType(t) {
return (function () {
switch (t) {
case Type.FLOAT:
case Type.INTEGER:
return createFormatter("Number");
case Type.LONG:
return createFormatter("BigInt");
case Type.BIGDECIMAL:
const t = {
digits: 34,
};
return createFormatter("Decimal", t);
case Type.DATE:
return createFormatter("PlainDate");
case Type.TIME:
return createFormatter("PlainTime");
case Type.DATETIME:
return createFormatter("Instant");
case Type.DURATION:
return createFormatter("Duration");
default:
return null;
}
});
})(y, O);

var F2 = (function getFormatterForType(t, y) {
return (function () {
switch (t) {
case Type.FLOAT:
case Type.INTEGER:
return createFormatter("Number", y);
case Type.LONG:
return createFormatter("BigInt", y);
case Type.BIGDECIMAL:
const t = Object.assign({}, y, {
digits: 34,
});
return createFormatter("Decimal", t);
case Type.DATE:
return createFormatter("PlainDate", y);
case Type.TIME:
return createFormatter("PlainTime", y);
case Type.DATETIME:
return createFormatter("Instant", y);
case Type.DURATION:
return createFormatter("Duration", y);
default:
return null;
}
});
})(y, O);

var F3 = (function getFormatterForType(r, y) {
let s = r;
{
let t = s;
return (function () {
switch (t) {
case Type.FLOAT:
case Type.INTEGER:
return createFormatter("Number", y);
case Type.LONG:
return createFormatter("BigInt", y);
case Type.BIGDECIMAL:
const t = Object.assign({}, y, {
digits: 34,
});
return createFormatter("Decimal", t);
case Type.DATE:
return createFormatter("PlainDate", y);
case Type.TIME:
return createFormatter("PlainTime", y);
case Type.DATETIME:
return createFormatter("Instant", y);
case Type.DURATION:
return createFormatter("Duration", y);
default:
return null;
}
});
}
})(y, O);

assertSame('{"type":"Decimal","options":{"digits":34}}', JSON.stringify(F1()));
assertSame('{"type":"Decimal","options":{"decimalSeparator":".","digits":34}}', JSON.stringify(F2()));
assertSame('{"type":"Decimal","options":{"decimalSeparator":".","digits":34}}', JSON.stringify(F3()));

0 comments on commit 60733ff

Please sign in to comment.