Skip to content

Commit 2bfd919

Browse files
authored
Narrow on element access of literal (#26424)
* Narrow literal element accesses This means that, for example, the tuple `[number, string?]` allows its second element to be narrowed with element access: ```ts export function f(pair: [number, string?]): string { return pair[1] ? pair[1] : 'nope'; } ``` * Update baselines * Cleanup * More cleanup * Test dashes in property names * More cleanup * Delete undead code
1 parent b9bd0d9 commit 2bfd919

10 files changed

+861
-55
lines changed

src/compiler/binder.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ namespace ts {
723723
case SyntaxKind.Identifier:
724724
case SyntaxKind.ThisKeyword:
725725
case SyntaxKind.PropertyAccessExpression:
726+
case SyntaxKind.ElementAccessExpression:
726727
return isNarrowableReference(expr);
727728
case SyntaxKind.CallExpression:
728729
return hasNarrowableArgument(<CallExpression>expr);
@@ -737,10 +738,11 @@ namespace ts {
737738
}
738739

739740
function isNarrowableReference(expr: Expression): boolean {
740-
return expr.kind === SyntaxKind.Identifier ||
741-
expr.kind === SyntaxKind.ThisKeyword ||
742-
expr.kind === SyntaxKind.SuperKeyword ||
743-
expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
741+
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
742+
isPropertyAccessExpression(expr) && isNarrowableReference(expr.expression) ||
743+
isElementAccessExpression(expr) && expr.argumentExpression &&
744+
(isStringLiteral(expr.argumentExpression) || isNumericLiteral(expr.argumentExpression)) &&
745+
isNarrowableReference(expr.expression);
744746
}
745747

746748
function hasNarrowableArgument(expr: CallExpression) {
@@ -2066,6 +2068,7 @@ namespace ts {
20662068
}
20672069
return checkStrictModeIdentifier(<Identifier>node);
20682070
case SyntaxKind.PropertyAccessExpression:
2071+
case SyntaxKind.ElementAccessExpression:
20692072
if (currentFlow && isNarrowableReference(<Expression>node)) {
20702073
node.flowNode = currentFlow;
20712074
}

src/compiler/checker.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9158,7 +9158,8 @@ namespace ts {
91589158
getNodeLinks(accessNode!).resolvedSymbol = prop;
91599159
}
91609160
}
9161-
return getTypeOfSymbol(prop);
9161+
const propType = getTypeOfSymbol(prop);
9162+
return accessExpression ? getFlowTypeOfReference(accessExpression, propType) : propType;
91629163
}
91639164
if (isTupleType(objectType)) {
91649165
const restType = getRestTypeOfTupleType(objectType);
@@ -13778,9 +13779,10 @@ namespace ts {
1377813779
case SyntaxKind.SuperKeyword:
1377913780
return target.kind === SyntaxKind.SuperKeyword;
1378013781
case SyntaxKind.PropertyAccessExpression:
13781-
return target.kind === SyntaxKind.PropertyAccessExpression &&
13782-
(<PropertyAccessExpression>source).name.escapedText === (<PropertyAccessExpression>target).name.escapedText &&
13783-
isMatchingReference((<PropertyAccessExpression>source).expression, (<PropertyAccessExpression>target).expression);
13782+
case SyntaxKind.ElementAccessExpression:
13783+
return (isPropertyAccessExpression(target) || isElementAccessExpression(target)) &&
13784+
getAccessedPropertyName(source as PropertyAccessExpression | ElementAccessExpression) === getAccessedPropertyName(target) &&
13785+
isMatchingReference((source as PropertyAccessExpression | ElementAccessExpression).expression, target.expression);
1378413786
case SyntaxKind.BindingElement:
1378513787
if (target.kind !== SyntaxKind.PropertyAccessExpression) return false;
1378613788
const t = target as PropertyAccessExpression;
@@ -13796,6 +13798,12 @@ namespace ts {
1379613798
return false;
1379713799
}
1379813800

13801+
function getAccessedPropertyName(access: PropertyAccessExpression | ElementAccessExpression): __String | undefined {
13802+
return isPropertyAccessExpression(access) ? access.name.escapedText :
13803+
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
13804+
undefined;
13805+
}
13806+
1379913807
function containsMatchingReference(source: Node, target: Node) {
1380013808
while (source.kind === SyntaxKind.PropertyAccessExpression) {
1380113809
source = (<PropertyAccessExpression>source).expression;
@@ -14438,7 +14446,10 @@ namespace ts {
1443814446
else if (flags & FlowFlags.Start) {
1443914447
// Check if we should continue with the control flow of the containing function.
1444014448
const container = (<FlowStart>flow).container;
14441-
if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) {
14449+
if (container && container !== flowContainer &&
14450+
reference.kind !== SyntaxKind.PropertyAccessExpression &&
14451+
reference.kind !== SyntaxKind.ElementAccessExpression &&
14452+
reference.kind !== SyntaxKind.ThisKeyword) {
1444214453
flow = container.flowNode!;
1444314454
continue;
1444414455
}
@@ -14555,7 +14566,10 @@ namespace ts {
1455514566
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
1455614567
}
1455714568
else if (isMatchingReferenceDiscriminant(expr, type)) {
14558-
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
14569+
type = narrowTypeByDiscriminant(
14570+
type,
14571+
expr as PropertyAccessExpression | ElementAccessExpression,
14572+
t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
1455914573
}
1456014574
return createFlowType(type, isIncomplete(flowType));
1456114575
}
@@ -14671,14 +14685,23 @@ namespace ts {
1467114685
}
1467214686

1467314687
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
14674-
return expr.kind === SyntaxKind.PropertyAccessExpression &&
14675-
computedType.flags & TypeFlags.Union &&
14676-
isMatchingReference(reference, (<PropertyAccessExpression>expr).expression) &&
14677-
isDiscriminantProperty(computedType, (<PropertyAccessExpression>expr).name.escapedText);
14688+
if (!(computedType.flags & TypeFlags.Union) ||
14689+
expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) {
14690+
return false;
14691+
}
14692+
const access = expr as PropertyAccessExpression | ElementAccessExpression;
14693+
const name = getAccessedPropertyName(access);
14694+
if (!name) {
14695+
return false;
14696+
}
14697+
return isMatchingReference(reference, access.expression) && isDiscriminantProperty(computedType, name);
1467814698
}
1467914699

14680-
function narrowTypeByDiscriminant(type: Type, propAccess: PropertyAccessExpression, narrowType: (t: Type) => Type): Type {
14681-
const propName = propAccess.name.escapedText;
14700+
function narrowTypeByDiscriminant(type: Type, access: PropertyAccessExpression | ElementAccessExpression, narrowType: (t: Type) => Type): Type {
14701+
const propName = getAccessedPropertyName(access);
14702+
if (!propName) {
14703+
return type;
14704+
}
1468214705
const propType = getTypeOfPropertyOfType(type, propName);
1468314706
const narrowedPropType = propType && narrowType(propType);
1468414707
return propType === narrowedPropType ? type : filterType(type, t => isTypeComparableTo(getTypeOfPropertyOfType(t, propName)!, narrowedPropType!));
@@ -14689,7 +14712,7 @@ namespace ts {
1468914712
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
1469014713
}
1469114714
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
14692-
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
14715+
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
1469314716
}
1469414717
if (containsMatchingReferenceDiscriminant(reference, expr)) {
1469514718
return declaredType;
@@ -14740,10 +14763,10 @@ namespace ts {
1474014763
return narrowTypeByEquality(type, operator, left, assumeTrue);
1474114764
}
1474214765
if (isMatchingReferenceDiscriminant(left, declaredType)) {
14743-
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
14766+
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
1474414767
}
1474514768
if (isMatchingReferenceDiscriminant(right, declaredType)) {
14746-
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
14769+
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
1474714770
}
1474814771
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {
1474914772
return declaredType;
@@ -14982,6 +15005,7 @@ namespace ts {
1498215005
case SyntaxKind.ThisKeyword:
1498315006
case SyntaxKind.SuperKeyword:
1498415007
case SyntaxKind.PropertyAccessExpression:
15008+
case SyntaxKind.ElementAccessExpression:
1498515009
return narrowTypeByTruthiness(type, expr, assumeTrue);
1498615010
case SyntaxKind.CallExpression:
1498715011
return narrowTypeByTypePredicate(type, <CallExpression>expr, assumeTrue);

tests/baselines/reference/constDeclarations-access3.types

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ M["x"] = 0;
135135
var a = M.x + 1;
136136
>a : number
137137
>M.x + 1 : number
138-
>M.x : number
138+
>M.x : 0
139139
>M : typeof M
140-
>x : number
140+
>x : 0
141141
>1 : 1
142142

143143
function f(v: number) { }
@@ -147,43 +147,43 @@ function f(v: number) { }
147147
f(M.x);
148148
>f(M.x) : void
149149
>f : (v: number) => void
150-
>M.x : number
150+
>M.x : 0
151151
>M : typeof M
152-
>x : number
152+
>x : 0
153153

154154
if (M.x) { }
155-
>M.x : number
155+
>M.x : 0
156156
>M : typeof M
157-
>x : number
157+
>x : 0
158158

159159
M.x;
160-
>M.x : number
160+
>M.x : 0
161161
>M : typeof M
162-
>x : number
162+
>x : 0
163163

164164
(M.x);
165-
>(M.x) : number
166-
>M.x : number
165+
>(M.x) : 0
166+
>M.x : 0
167167
>M : typeof M
168-
>x : number
168+
>x : 0
169169

170170
-M.x;
171171
>-M.x : number
172-
>M.x : number
172+
>M.x : 0
173173
>M : typeof M
174-
>x : number
174+
>x : 0
175175

176176
+M.x;
177177
>+M.x : number
178-
>M.x : number
178+
>M.x : 0
179179
>M : typeof M
180-
>x : number
180+
>x : 0
181181

182182
M.x.toString();
183183
>M.x.toString() : string
184184
>M.x.toString : (radix?: number) => string
185-
>M.x : number
185+
>M.x : 0
186186
>M : typeof M
187-
>x : number
187+
>x : 0
188188
>toString : (radix?: number) => string
189189

tests/baselines/reference/constDeclarations-access5.types

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ m["x"] = 0;
134134
var a = m.x + 1;
135135
>a : number
136136
>m.x + 1 : number
137-
>m.x : number
137+
>m.x : 0
138138
>m : typeof m
139-
>x : number
139+
>x : 0
140140
>1 : 1
141141

142142
function f(v: number) { }
@@ -146,44 +146,44 @@ function f(v: number) { }
146146
f(m.x);
147147
>f(m.x) : void
148148
>f : (v: number) => void
149-
>m.x : number
149+
>m.x : 0
150150
>m : typeof m
151-
>x : number
151+
>x : 0
152152

153153
if (m.x) { }
154-
>m.x : number
154+
>m.x : 0
155155
>m : typeof m
156-
>x : number
156+
>x : 0
157157

158158
m.x;
159-
>m.x : number
159+
>m.x : 0
160160
>m : typeof m
161-
>x : number
161+
>x : 0
162162

163163
(m.x);
164-
>(m.x) : number
165-
>m.x : number
164+
>(m.x) : 0
165+
>m.x : 0
166166
>m : typeof m
167-
>x : number
167+
>x : 0
168168

169169
-m.x;
170170
>-m.x : number
171-
>m.x : number
171+
>m.x : 0
172172
>m : typeof m
173-
>x : number
173+
>x : 0
174174

175175
+m.x;
176176
>+m.x : number
177-
>m.x : number
177+
>m.x : 0
178178
>m : typeof m
179-
>x : number
179+
>x : 0
180180

181181
m.x.toString();
182182
>m.x.toString() : string
183183
>m.x.toString : (radix?: number) => string
184-
>m.x : number
184+
>m.x : 0
185185
>m : typeof m
186-
>x : number
186+
>x : 0
187187
>toString : (radix?: number) => string
188188

189189
=== tests/cases/compiler/constDeclarations_access_1.ts ===

0 commit comments

Comments
 (0)