Skip to content

Commit 60733ff

Browse files
committed
[GR-52036] Backport to 24.0: Switch statement expression should not be parsed in switch block scope.
PullRequest: js/3063
2 parents 6a7a408 + 5a1d50a commit 60733ff

File tree

3 files changed

+150
-25
lines changed

3 files changed

+150
-25
lines changed

graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -3612,12 +3612,36 @@ private void switchStatement(boolean yield, boolean await) {
36123612
ParserContextBlockNode switchBlock;
36133613
SwitchNode switchStatement;
36143614
try {
3615+
assert type == TokenType.SWITCH; // tested in caller.
3616+
next();
3617+
3618+
/*
3619+
* Note: Identifier references in the switch expression need to be resolved in the scope
3620+
* of the outer block, so we must parse the expression before pushing the switch scope.
3621+
*/
3622+
expect(LPAREN);
3623+
int expressionLine = line;
3624+
Expression expression = expression(yield, await);
3625+
expect(RPAREN);
3626+
3627+
// Desugar expression to a synthetic let variable assignment in the outer block.
3628+
// This simplifies lexical scope analysis (the expression is outside the switch
3629+
// block).
3630+
// e.g.: let x = 1; switch (x) { case 0: let x = 2; } =>
3631+
// let x = 1; { let :switch = x; { let x; switch (:switch) { case 0: x = 2; } } }
3632+
if (useBlockScope()) {
3633+
IdentNode switchExprName = new IdentNode(Token.recast(expression.getToken(), IDENT), expression.getFinish(), lexer.stringIntern(SWITCH_BINDING_NAME));
3634+
VarNode varNode = new VarNode(expressionLine, Token.recast(expression.getToken(), LET), expression.getFinish(), switchExprName, expression, VarNode.IS_LET);
3635+
outerBlock.appendStatement(varNode);
3636+
declareVar(outerBlock.getScope(), varNode);
3637+
expression = switchExprName;
3638+
}
3639+
36153640
// Block to capture variables declared inside the switch statement.
36163641
switchBlock = newBlock(Scope.createSwitchBlock(lc.getCurrentScope()));
36173642
switchBlock.setFlag(Block.IS_SYNTHETIC | Block.IS_SWITCH_BLOCK);
36183643

3619-
// SWITCH tested in caller.
3620-
next();
3644+
expect(LBRACE);
36213645

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

36303654
try {
3631-
expect(LPAREN);
3632-
int expressionLine = line;
3633-
Expression expression = expression(yield, await);
3634-
expect(RPAREN);
3635-
3636-
expect(LBRACE);
3637-
3638-
// Desugar expression to a synthetic let variable assignment in the outer block.
3639-
// This simplifies lexical scope analysis (the expression is outside the switch
3640-
// block).
3641-
// e.g.: let x = 1; switch (x) { case 0: let x = 2; } =>
3642-
// let x = 1; { let :switch = x; { let x; switch (:switch) { case 0: x = 2; } } }
3643-
if (useBlockScope()) {
3644-
IdentNode switchExprName = new IdentNode(Token.recast(expression.getToken(), IDENT), expression.getFinish(), lexer.stringIntern(SWITCH_BINDING_NAME));
3645-
VarNode varNode = new VarNode(expressionLine, Token.recast(expression.getToken(), LET), expression.getFinish(), switchExprName, expression, VarNode.IS_LET);
3646-
outerBlock.appendStatement(varNode);
3647-
declareVar(outerBlock.getScope(), varNode);
3648-
expression = switchExprName;
3649-
}
3650-
36513655
while (type != RBRACE) {
36523656
// Prepare for next case.
36533657
Expression caseExpression = null;
@@ -3686,6 +3690,7 @@ private void switchStatement(boolean yield, boolean await) {
36863690
cases.add(caseNode);
36873691
}
36883692

3693+
assert type == RBRACE;
36893694
next();
36903695

36913696
switchStatement = new SwitchNode(switchLine, switchToken, finish, expression, cases, defaultCaseIndex);

graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/Scope.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -759,7 +759,7 @@ private String getScopeKindName() {
759759
*/
760760
static final class UseInfo {
761761
/** Used name. */
762-
String name;
762+
final String name;
763763
/** Resolved scope in which the symbol has been defined. Must be a parent scope. */
764764
Scope def;
765765
/** Local scope in which the symbol is used. */
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
6+
*/
7+
8+
/**
9+
* Tests that an identifier reference in a switch expression declared in an outer function scope
10+
* is correctly resolved when a binding with the same name is declared in the switch block scope.
11+
*/
12+
13+
load("assert.js");
14+
15+
const Type = {
16+
FLOAT: {},
17+
INTEGER: {},
18+
LONG: {},
19+
BIGDECIMAL: {},
20+
DATE: {},
21+
TIME: {},
22+
DATETIME: {},
23+
DURATION: {},
24+
};
25+
26+
function createFormatter(type, options) {
27+
return {type, options};
28+
}
29+
30+
var y = Type.BIGDECIMAL;
31+
var O = {decimalSeparator: "."};
32+
33+
var F1 = (function getFormatterForType(t) {
34+
return (function () {
35+
switch (t) {
36+
case Type.FLOAT:
37+
case Type.INTEGER:
38+
return createFormatter("Number");
39+
case Type.LONG:
40+
return createFormatter("BigInt");
41+
case Type.BIGDECIMAL:
42+
const t = {
43+
digits: 34,
44+
};
45+
return createFormatter("Decimal", t);
46+
case Type.DATE:
47+
return createFormatter("PlainDate");
48+
case Type.TIME:
49+
return createFormatter("PlainTime");
50+
case Type.DATETIME:
51+
return createFormatter("Instant");
52+
case Type.DURATION:
53+
return createFormatter("Duration");
54+
default:
55+
return null;
56+
}
57+
});
58+
})(y, O);
59+
60+
var F2 = (function getFormatterForType(t, y) {
61+
return (function () {
62+
switch (t) {
63+
case Type.FLOAT:
64+
case Type.INTEGER:
65+
return createFormatter("Number", y);
66+
case Type.LONG:
67+
return createFormatter("BigInt", y);
68+
case Type.BIGDECIMAL:
69+
const t = Object.assign({}, y, {
70+
digits: 34,
71+
});
72+
return createFormatter("Decimal", t);
73+
case Type.DATE:
74+
return createFormatter("PlainDate", y);
75+
case Type.TIME:
76+
return createFormatter("PlainTime", y);
77+
case Type.DATETIME:
78+
return createFormatter("Instant", y);
79+
case Type.DURATION:
80+
return createFormatter("Duration", y);
81+
default:
82+
return null;
83+
}
84+
});
85+
})(y, O);
86+
87+
var F3 = (function getFormatterForType(r, y) {
88+
let s = r;
89+
{
90+
let t = s;
91+
return (function () {
92+
switch (t) {
93+
case Type.FLOAT:
94+
case Type.INTEGER:
95+
return createFormatter("Number", y);
96+
case Type.LONG:
97+
return createFormatter("BigInt", y);
98+
case Type.BIGDECIMAL:
99+
const t = Object.assign({}, y, {
100+
digits: 34,
101+
});
102+
return createFormatter("Decimal", t);
103+
case Type.DATE:
104+
return createFormatter("PlainDate", y);
105+
case Type.TIME:
106+
return createFormatter("PlainTime", y);
107+
case Type.DATETIME:
108+
return createFormatter("Instant", y);
109+
case Type.DURATION:
110+
return createFormatter("Duration", y);
111+
default:
112+
return null;
113+
}
114+
});
115+
}
116+
})(y, O);
117+
118+
assertSame('{"type":"Decimal","options":{"digits":34}}', JSON.stringify(F1()));
119+
assertSame('{"type":"Decimal","options":{"decimalSeparator":".","digits":34}}', JSON.stringify(F2()));
120+
assertSame('{"type":"Decimal","options":{"decimalSeparator":".","digits":34}}', JSON.stringify(F3()));

0 commit comments

Comments
 (0)