Skip to content

Commit 69d658d

Browse files
authored
Stop parsing constructs that make it impossible to erase as/satisfies (#4192)
1 parent 2aafbdd commit 69d658d

6 files changed

Lines changed: 925 additions & 7 deletions

File tree

internal/parser/parser.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4584,11 +4584,12 @@ func (p *Parser) parseBinaryExpressionOrHigher(precedence ast.OperatorPrecedence
45844584
}
45854585

45864586
func (p *Parser) parseBinaryExpressionRest(precedence ast.OperatorPrecedence, leftOperand *ast.Expression, pos int) *ast.Expression {
4587+
lastOperand := leftOperand
45874588
for {
45884589
// We either have a binary operator here, or we're finished. We call
45894590
// reScanGreaterToken so that we merge token sequences like > and = into >=
4590-
p.reScanGreaterThanToken()
4591-
newPrecedence := ast.GetBinaryOperatorPrecedence(p.token)
4591+
operator := p.reScanGreaterThanToken()
4592+
newPrecedence := ast.GetBinaryOperatorPrecedence(operator)
45924593
// Check the precedence to see if we should "take" this operator
45934594
// - For left associative operator (all operator but **), consume the operator,
45944595
// recursively call the function below, and parse binaryExpression as a rightOperand
@@ -4611,18 +4612,18 @@ func (p *Parser) parseBinaryExpressionRest(precedence ast.OperatorPrecedence, le
46114612
// a ** b - c
46124613
// ^token; leftOperand = b. Return b to the caller as a rightOperand
46134614
var consumeCurrentOperator bool
4614-
if p.token == ast.KindAsteriskAsteriskToken {
4615+
if operator == ast.KindAsteriskAsteriskToken {
46154616
consumeCurrentOperator = newPrecedence >= precedence
46164617
} else {
46174618
consumeCurrentOperator = newPrecedence > precedence
46184619
}
46194620
if !consumeCurrentOperator {
46204621
break
46214622
}
4622-
if p.token == ast.KindInKeyword && p.inDisallowInContext() {
4623+
if operator == ast.KindInKeyword && p.inDisallowInContext() {
46234624
break
46244625
}
4625-
if p.token == ast.KindAsKeyword || p.token == ast.KindSatisfiesKeyword {
4626+
if operator == ast.KindAsKeyword || operator == ast.KindSatisfiesKeyword {
46264627
// Make sure we *do* perform ASI for constructs like this:
46274628
// var x = foo
46284629
// as (Bar)
@@ -4631,16 +4632,28 @@ func (p *Parser) parseBinaryExpressionRest(precedence ast.OperatorPrecedence, le
46314632
if p.hasPrecedingLineBreak() {
46324633
break
46334634
} else {
4634-
keywordKind := p.token
46354635
p.nextToken()
4636-
if keywordKind == ast.KindSatisfiesKeyword {
4636+
// When we have 'a ## b as SomeType' or 'a ## b satisfies SomeType', where ## is some binary
4637+
// operator, we want to stop parsing on any following operator with a higher precedence than ##
4638+
// because continuing would make it impossible to erase the `as` or `satisfies` without changing
4639+
// the meaning of the expression. See https://github.com/microsoft/TypeScript/issues/63527.
4640+
lastPrecedence := ast.OperatorPrecedenceHighest
4641+
if ast.IsBinaryExpression(lastOperand) {
4642+
lastPrecedence = ast.GetBinaryOperatorPrecedence(lastOperand.AsBinaryExpression().OperatorToken.Kind)
4643+
}
4644+
if operator == ast.KindSatisfiesKeyword {
46374645
leftOperand = p.makeSatisfiesExpression(leftOperand, p.parseType())
46384646
} else {
46394647
leftOperand = p.makeAsExpression(leftOperand, p.parseType())
46404648
}
4649+
// Stop if the precedence of the next operator is too high.
4650+
if ast.GetBinaryOperatorPrecedence(p.reScanGreaterThanToken()) > lastPrecedence {
4651+
break
4652+
}
46414653
}
46424654
} else {
46434655
leftOperand = p.makeBinaryExpression(leftOperand, p.parseTokenNode(), p.parseBinaryExpressionOrHigher(newPrecedence), pos)
4656+
lastOperand = leftOperand
46444657
}
46454658
}
46464659
return leftOperand
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
disallowUnerasableAssertion.ts(9,36): error TS1005: ',' expected.
2+
disallowUnerasableAssertion.ts(10,43): error TS1005: ',' expected.
3+
disallowUnerasableAssertion.ts(33,37): error TS1005: ',' expected.
4+
disallowUnerasableAssertion.ts(34,44): error TS1005: ',' expected.
5+
disallowUnerasableAssertion.ts(39,43): error TS1005: ',' expected.
6+
disallowUnerasableAssertion.ts(40,57): error TS1005: ',' expected.
7+
disallowUnerasableAssertion.ts(63,44): error TS1005: ',' expected.
8+
disallowUnerasableAssertion.ts(64,58): error TS1005: ',' expected.
9+
10+
11+
==== disallowUnerasableAssertion.ts (8 errors) ====
12+
// https://github.com/microsoft/TypeScript/issues/63527
13+
14+
// Expressions of the form 'a ## b as T $$ c' where ## has lower precedence than $$ are errors
15+
// because 'as T' cannot be erased without changing the meaning of the expressions.
16+
17+
export const x01 = 1 as number * 2;
18+
export const x02 = 1 as any as number * 2;
19+
20+
export const x03 = 1 + 1 as number * 2; // Error
21+
~
22+
!!! error TS1005: ',' expected.
23+
export const x04 = 1 + 1 as any as number * 2; // Error
24+
~
25+
!!! error TS1005: ',' expected.
26+
export const x05 = 1 as number + 1 * 2;
27+
export const x06 = 1 as any as number + 1 * 2;
28+
29+
export const x07 = 1 * 1 as number + 2;
30+
export const x08 = 1 * 1 as any as number + 2;
31+
export const x09 = 1 as number * 1 + 2;
32+
export const x10 = 1 as any as number * 1 + 2;
33+
34+
export const x11 = (1 + 1 as number) * 2;
35+
export const x12 = (1 + 1 as any as number) * 2;
36+
export const x13 = (1 as number + 1) * 2;
37+
export const x14 = (1 as any as number + 1) * 2;
38+
39+
export const x15 = 1 + 1 as number === 2;
40+
export const x16 = 1 + 1 as any as number === 2;
41+
export const x17 = 1 + 1 as number > 2;
42+
export const x18 = 1 + 1 as any as number > 2;
43+
export const x19 = 1 + 1 as number >= 2;
44+
export const x20 = 1 + 1 as any as number >= 2;
45+
46+
export const x21 = 1 + 1 as number >> 2;
47+
export const x22 = 1 + 1 as any as number >> 2;
48+
export const x23 = 1 >> 1 as number + 2; // Error
49+
~
50+
!!! error TS1005: ',' expected.
51+
export const x24 = 1 >> 1 as any as number + 2; // Error
52+
~
53+
!!! error TS1005: ',' expected.
54+
55+
export const y01 = 1 satisfies number * 2;
56+
export const y02 = 1 satisfies any satisfies number * 2;
57+
58+
export const y03 = 1 + 1 satisfies number * 2; // Error
59+
~
60+
!!! error TS1005: ',' expected.
61+
export const y04 = 1 + 1 satisfies any satisfies number * 2; // Error
62+
~
63+
!!! error TS1005: ',' expected.
64+
export const y05 = 1 satisfies number + 1 * 2;
65+
export const y06 = 1 satisfies any satisfies number + 1 * 2;
66+
67+
export const y07 = 1 * 1 satisfies number + 2;
68+
export const y08 = 1 * 1 satisfies any satisfies number + 2;
69+
export const y09 = 1 satisfies number * 1 + 2;
70+
export const y10 = 1 satisfies any satisfies number * 1 + 2;
71+
72+
export const y11 = (1 + 1 satisfies number) * 2;
73+
export const y12 = (1 + 1 satisfies any satisfies number) * 2;
74+
export const y13 = (1 satisfies number + 1) * 2;
75+
export const y14 = (1 satisfies any satisfies number + 1) * 2;
76+
77+
export const y15 = 1 + 1 satisfies number === 2;
78+
export const y16 = 1 + 1 satisfies any satisfies number === 2;
79+
export const y17 = 1 + 1 satisfies number > 2;
80+
export const y18 = 1 + 1 satisfies any satisfies number > 2;
81+
export const y19 = 1 + 1 satisfies number >= 2;
82+
export const y20 = 1 + 1 satisfies any satisfies number >= 2;
83+
84+
export const y21 = 1 + 1 satisfies number >> 2;
85+
export const y22 = 1 + 1 satisfies any satisfies number >> 2;
86+
export const y23 = 1 >> 1 satisfies number + 2; // Error
87+
~
88+
!!! error TS1005: ',' expected.
89+
export const y24 = 1 >> 1 satisfies any satisfies number + 2; // Error
90+
~
91+
!!! error TS1005: ',' expected.
92+
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//// [tests/cases/compiler/disallowUnerasableAssertion.ts] ////
2+
3+
//// [disallowUnerasableAssertion.ts]
4+
// https://github.com/microsoft/TypeScript/issues/63527
5+
6+
// Expressions of the form 'a ## b as T $$ c' where ## has lower precedence than $$ are errors
7+
// because 'as T' cannot be erased without changing the meaning of the expressions.
8+
9+
export const x01 = 1 as number * 2;
10+
export const x02 = 1 as any as number * 2;
11+
12+
export const x03 = 1 + 1 as number * 2; // Error
13+
export const x04 = 1 + 1 as any as number * 2; // Error
14+
export const x05 = 1 as number + 1 * 2;
15+
export const x06 = 1 as any as number + 1 * 2;
16+
17+
export const x07 = 1 * 1 as number + 2;
18+
export const x08 = 1 * 1 as any as number + 2;
19+
export const x09 = 1 as number * 1 + 2;
20+
export const x10 = 1 as any as number * 1 + 2;
21+
22+
export const x11 = (1 + 1 as number) * 2;
23+
export const x12 = (1 + 1 as any as number) * 2;
24+
export const x13 = (1 as number + 1) * 2;
25+
export const x14 = (1 as any as number + 1) * 2;
26+
27+
export const x15 = 1 + 1 as number === 2;
28+
export const x16 = 1 + 1 as any as number === 2;
29+
export const x17 = 1 + 1 as number > 2;
30+
export const x18 = 1 + 1 as any as number > 2;
31+
export const x19 = 1 + 1 as number >= 2;
32+
export const x20 = 1 + 1 as any as number >= 2;
33+
34+
export const x21 = 1 + 1 as number >> 2;
35+
export const x22 = 1 + 1 as any as number >> 2;
36+
export const x23 = 1 >> 1 as number + 2; // Error
37+
export const x24 = 1 >> 1 as any as number + 2; // Error
38+
39+
export const y01 = 1 satisfies number * 2;
40+
export const y02 = 1 satisfies any satisfies number * 2;
41+
42+
export const y03 = 1 + 1 satisfies number * 2; // Error
43+
export const y04 = 1 + 1 satisfies any satisfies number * 2; // Error
44+
export const y05 = 1 satisfies number + 1 * 2;
45+
export const y06 = 1 satisfies any satisfies number + 1 * 2;
46+
47+
export const y07 = 1 * 1 satisfies number + 2;
48+
export const y08 = 1 * 1 satisfies any satisfies number + 2;
49+
export const y09 = 1 satisfies number * 1 + 2;
50+
export const y10 = 1 satisfies any satisfies number * 1 + 2;
51+
52+
export const y11 = (1 + 1 satisfies number) * 2;
53+
export const y12 = (1 + 1 satisfies any satisfies number) * 2;
54+
export const y13 = (1 satisfies number + 1) * 2;
55+
export const y14 = (1 satisfies any satisfies number + 1) * 2;
56+
57+
export const y15 = 1 + 1 satisfies number === 2;
58+
export const y16 = 1 + 1 satisfies any satisfies number === 2;
59+
export const y17 = 1 + 1 satisfies number > 2;
60+
export const y18 = 1 + 1 satisfies any satisfies number > 2;
61+
export const y19 = 1 + 1 satisfies number >= 2;
62+
export const y20 = 1 + 1 satisfies any satisfies number >= 2;
63+
64+
export const y21 = 1 + 1 satisfies number >> 2;
65+
export const y22 = 1 + 1 satisfies any satisfies number >> 2;
66+
export const y23 = 1 >> 1 satisfies number + 2; // Error
67+
export const y24 = 1 >> 1 satisfies any satisfies number + 2; // Error
68+
69+
70+
//// [disallowUnerasableAssertion.js]
71+
// https://github.com/microsoft/TypeScript/issues/63527
72+
// Expressions of the form 'a ## b as T $$ c' where ## has lower precedence than $$ are errors
73+
// because 'as T' cannot be erased without changing the meaning of the expressions.
74+
export const x01 = 1 * 2;
75+
export const x02 = 1 * 2;
76+
export const x03 = 1 + 1;
77+
* 2; // Error
78+
export const x04 = 1 + 1;
79+
* 2; // Error
80+
export const x05 = 1 + 1 * 2;
81+
export const x06 = 1 + 1 * 2;
82+
export const x07 = 1 * 1 + 2;
83+
export const x08 = 1 * 1 + 2;
84+
export const x09 = 1 * 1 + 2;
85+
export const x10 = 1 * 1 + 2;
86+
export const x11 = (1 + 1) * 2;
87+
export const x12 = (1 + 1) * 2;
88+
export const x13 = (1 + 1) * 2;
89+
export const x14 = (1 + 1) * 2;
90+
export const x15 = 1 + 1 === 2;
91+
export const x16 = 1 + 1 === 2;
92+
export const x17 = 1 + 1 > 2;
93+
export const x18 = 1 + 1 > 2;
94+
export const x19 = 1 + 1 >= 2;
95+
export const x20 = 1 + 1 >= 2;
96+
export const x21 = 1 + 1 >> 2;
97+
export const x22 = 1 + 1 >> 2;
98+
export const x23 = 1 >> 1;
99+
+2; // Error
100+
export const x24 = 1 >> 1;
101+
+2; // Error
102+
export const y01 = 1 * 2;
103+
export const y02 = 1 * 2;
104+
export const y03 = 1 + 1;
105+
* 2; // Error
106+
export const y04 = 1 + 1;
107+
* 2; // Error
108+
export const y05 = 1 + 1 * 2;
109+
export const y06 = 1 + 1 * 2;
110+
export const y07 = 1 * 1 + 2;
111+
export const y08 = 1 * 1 + 2;
112+
export const y09 = 1 * 1 + 2;
113+
export const y10 = 1 * 1 + 2;
114+
export const y11 = (1 + 1) * 2;
115+
export const y12 = (1 + 1) * 2;
116+
export const y13 = (1 + 1) * 2;
117+
export const y14 = (1 + 1) * 2;
118+
export const y15 = 1 + 1 === 2;
119+
export const y16 = 1 + 1 === 2;
120+
export const y17 = 1 + 1 > 2;
121+
export const y18 = 1 + 1 > 2;
122+
export const y19 = 1 + 1 >= 2;
123+
export const y20 = 1 + 1 >= 2;
124+
export const y21 = 1 + 1 >> 2;
125+
export const y22 = 1 + 1 >> 2;
126+
export const y23 = 1 >> 1;
127+
+2; // Error
128+
export const y24 = 1 >> 1;
129+
+2; // Error

0 commit comments

Comments
 (0)