Skip to content

Commit 7f835de

Browse files
committed
fix(checker): improve late-bindable access expression handling
- Rename isEntityNameOrElementAccessExpression to isLateBindableAccessExpression - Support mixed chains like obj.a['b'].c['d'] - Add skipParentheses handling for expressions like (obj.a)['b'] - Ensure PropertyAccessExpression name is Identifier (not PrivateIdentifier) - Add comprehensive test cases for mixed chains and parenthesized expressions
1 parent ec84ad1 commit 7f835de

File tree

5 files changed

+418
-9
lines changed

5 files changed

+418
-9
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13721,21 +13721,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1372113721
return false;
1372213722
}
1372313723
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
13724-
return isEntityNameOrElementAccessExpression(expr);
13724+
return isLateBindableAccessExpression(expr);
1372513725
}
1372613726

1372713727
/**
13728-
* Returns true if the expression is a valid late-bindable expression.
13729-
* A late-bindable expression is an entity name expression (Identifier or PropertyAccessExpression)
13730-
* or an ElementAccessExpression with a string or numeric literal key where the base expression
13731-
* is itself a valid late-bindable expression.
13728+
* Returns true if the expression is a valid late-bindable access expression.
13729+
* A late-bindable access expression is:
13730+
* - An Identifier
13731+
* - A PropertyAccessExpression where the base is a late-bindable access expression
13732+
* - An ElementAccessExpression with a string/numeric literal key where the base is a late-bindable access expression
13733+
*
13734+
* This supports mixed chains like: obj.a['b'].c['d']
13735+
* Parentheses are skipped to support expressions like: (obj.a)['b']
1373213736
*/
13733-
function isEntityNameOrElementAccessExpression(node: Node): boolean {
13734-
if (isEntityNameExpression(node)) {
13737+
function isLateBindableAccessExpression(node: Node): boolean {
13738+
node = skipParentheses(node);
13739+
13740+
if (isIdentifier(node)) {
1373513741
return true;
1373613742
}
13743+
// For PropertyAccessExpression, require the name to be an Identifier (not PrivateIdentifier)
13744+
if (isPropertyAccessExpression(node) && isIdentifier(node.name)) {
13745+
return isLateBindableAccessExpression(node.expression);
13746+
}
1373713747
if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression)) {
13738-
return isEntityNameOrElementAccessExpression(node.expression);
13748+
return isLateBindableAccessExpression(node.expression);
1373913749
}
1374013750
return false;
1374113751
}
@@ -52927,7 +52937,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5292752937

5292852938
function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) {
5292952939
// Even non-bindable names are allowed as late-bound implied index signatures so long as the name is a simple `a.b.c` or `a['b']` type name expression
52930-
if (isNonBindableDynamicName(node) && !isEntityNameOrElementAccessExpression(isElementAccessExpression(node) ? skipParentheses(node.argumentExpression) : (node as ComputedPropertyName).expression)) {
52940+
// isLateBindableAccessExpression handles skipParentheses internally
52941+
if (isNonBindableDynamicName(node) && !isLateBindableAccessExpression(isElementAccessExpression(node) ? node.argumentExpression : (node as ComputedPropertyName).expression)) {
5293152942
return grammarErrorOnNode(node, message);
5293252943
}
5293352944
}

tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,48 @@ type TypeMap3 = {
3535
type TypeMap4 = {
3636
[nested['inner']['key']]: string;
3737
}
38+
39+
// Mixed chain: element access followed by property access
40+
type TypeMap5 = {
41+
[nested['inner'].key]: string;
42+
}
43+
44+
// Mixed chain: property access followed by element access
45+
type TypeMap6 = {
46+
[nested.inner['key']]: string;
47+
}
48+
49+
// Complex mixed chain
50+
const deep = {
51+
a: {
52+
b: {
53+
c: {
54+
d: 'value' as const
55+
}
56+
}
57+
}
58+
};
59+
60+
type TypeMap7 = {
61+
[deep.a['b'].c['d']]: string;
62+
}
63+
64+
type TypeMap8 = {
65+
[deep['a'].b['c'].d]: string;
66+
}
67+
68+
// Parenthesized expressions
69+
type TypeMap9 = {
70+
[(nested.inner).key]: string;
71+
}
72+
73+
type TypeMap10 = {
74+
[(nested['inner']).key]: string;
75+
}
76+
77+
type TypeMap11 = {
78+
[(nested).inner.key]: string;
79+
}
3880

3981

4082
//// [enumKeysAsComputedPropertiesWithBracketNotation.js]
@@ -52,3 +94,13 @@ var nested = {
5294
key: 'hello'
5395
}
5496
};
97+
// Complex mixed chain
98+
var deep = {
99+
a: {
100+
b: {
101+
c: {
102+
d: 'value'
103+
}
104+
}
105+
}
106+
};

tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,112 @@ type TypeMap4 = {
7676
>'key' : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
7777
}
7878

79+
// Mixed chain: element access followed by property access
80+
type TypeMap5 = {
81+
>TypeMap5 : Symbol(TypeMap5, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 33, 1))
82+
83+
[nested['inner'].key]: string;
84+
>[nested['inner'].key] : Symbol([nested['inner'].key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 36, 17))
85+
>nested['inner'].key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
86+
>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5))
87+
>'inner' : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
88+
>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
89+
}
90+
91+
// Mixed chain: property access followed by element access
92+
type TypeMap6 = {
93+
>TypeMap6 : Symbol(TypeMap6, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 38, 1))
94+
95+
[nested.inner['key']]: string;
96+
>[nested.inner['key']] : Symbol([nested.inner['key']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 41, 17))
97+
>nested.inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
98+
>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5))
99+
>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
100+
>'key' : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
101+
}
102+
103+
// Complex mixed chain
104+
const deep = {
105+
>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5))
106+
107+
a: {
108+
>a : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14))
109+
110+
b: {
111+
>b : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8))
112+
113+
c: {
114+
>c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12))
115+
116+
d: 'value' as const
117+
>d : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16))
118+
>const : Symbol(const)
119+
}
120+
}
121+
}
122+
};
123+
124+
type TypeMap7 = {
125+
>TypeMap7 : Symbol(TypeMap7, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 54, 2))
126+
127+
[deep.a['b'].c['d']]: string;
128+
>[deep.a['b'].c['d']] : Symbol([deep.a['b'].c['d']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 56, 17))
129+
>deep.a['b'].c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12))
130+
>deep.a : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14))
131+
>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5))
132+
>a : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14))
133+
>'b' : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8))
134+
>c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12))
135+
>'d' : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16))
136+
}
137+
138+
type TypeMap8 = {
139+
>TypeMap8 : Symbol(TypeMap8, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 58, 1))
140+
141+
[deep['a'].b['c'].d]: string;
142+
>[deep['a'].b['c'].d] : Symbol([deep['a'].b['c'].d], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 60, 17))
143+
>deep['a'].b['c'].d : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16))
144+
>deep['a'].b : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8))
145+
>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5))
146+
>'a' : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14))
147+
>b : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8))
148+
>'c' : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12))
149+
>d : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16))
150+
}
151+
152+
// Parenthesized expressions
153+
type TypeMap9 = {
154+
>TypeMap9 : Symbol(TypeMap9, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 62, 1))
155+
156+
[(nested.inner).key]: string;
157+
>[(nested.inner).key] : Symbol([(nested.inner).key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 65, 17))
158+
>(nested.inner).key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
159+
>nested.inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
160+
>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5))
161+
>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
162+
>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
163+
}
164+
165+
type TypeMap10 = {
166+
>TypeMap10 : Symbol(TypeMap10, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 67, 1))
167+
168+
[(nested['inner']).key]: string;
169+
>[(nested['inner']).key] : Symbol([(nested['inner']).key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 69, 18))
170+
>(nested['inner']).key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
171+
>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5))
172+
>'inner' : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
173+
>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
174+
}
175+
176+
type TypeMap11 = {
177+
>TypeMap11 : Symbol(TypeMap11, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 71, 1))
178+
179+
[(nested).inner.key]: string;
180+
>[(nested).inner.key] : Symbol([(nested).inner.key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 73, 18))
181+
>(nested).inner.key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
182+
>(nested).inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
183+
>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5))
184+
>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16))
185+
>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12))
186+
}
187+

0 commit comments

Comments
 (0)