mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
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
This commit is contained in:
committed by
GitHub
parent
b9bd0d9a3f
commit
2bfd919b6a
@@ -723,6 +723,7 @@ namespace ts {
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.ThisKeyword:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return isNarrowableReference(expr);
|
||||
case SyntaxKind.CallExpression:
|
||||
return hasNarrowableArgument(<CallExpression>expr);
|
||||
@@ -737,10 +738,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
function isNarrowableReference(expr: Expression): boolean {
|
||||
return expr.kind === SyntaxKind.Identifier ||
|
||||
expr.kind === SyntaxKind.ThisKeyword ||
|
||||
expr.kind === SyntaxKind.SuperKeyword ||
|
||||
expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
|
||||
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
|
||||
isPropertyAccessExpression(expr) && isNarrowableReference(expr.expression) ||
|
||||
isElementAccessExpression(expr) && expr.argumentExpression &&
|
||||
(isStringLiteral(expr.argumentExpression) || isNumericLiteral(expr.argumentExpression)) &&
|
||||
isNarrowableReference(expr.expression);
|
||||
}
|
||||
|
||||
function hasNarrowableArgument(expr: CallExpression) {
|
||||
@@ -2066,6 +2068,7 @@ namespace ts {
|
||||
}
|
||||
return checkStrictModeIdentifier(<Identifier>node);
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
if (currentFlow && isNarrowableReference(<Expression>node)) {
|
||||
node.flowNode = currentFlow;
|
||||
}
|
||||
|
||||
@@ -9158,7 +9158,8 @@ namespace ts {
|
||||
getNodeLinks(accessNode!).resolvedSymbol = prop;
|
||||
}
|
||||
}
|
||||
return getTypeOfSymbol(prop);
|
||||
const propType = getTypeOfSymbol(prop);
|
||||
return accessExpression ? getFlowTypeOfReference(accessExpression, propType) : propType;
|
||||
}
|
||||
if (isTupleType(objectType)) {
|
||||
const restType = getRestTypeOfTupleType(objectType);
|
||||
@@ -13778,9 +13779,10 @@ namespace ts {
|
||||
case SyntaxKind.SuperKeyword:
|
||||
return target.kind === SyntaxKind.SuperKeyword;
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
return target.kind === SyntaxKind.PropertyAccessExpression &&
|
||||
(<PropertyAccessExpression>source).name.escapedText === (<PropertyAccessExpression>target).name.escapedText &&
|
||||
isMatchingReference((<PropertyAccessExpression>source).expression, (<PropertyAccessExpression>target).expression);
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return (isPropertyAccessExpression(target) || isElementAccessExpression(target)) &&
|
||||
getAccessedPropertyName(source as PropertyAccessExpression | ElementAccessExpression) === getAccessedPropertyName(target) &&
|
||||
isMatchingReference((source as PropertyAccessExpression | ElementAccessExpression).expression, target.expression);
|
||||
case SyntaxKind.BindingElement:
|
||||
if (target.kind !== SyntaxKind.PropertyAccessExpression) return false;
|
||||
const t = target as PropertyAccessExpression;
|
||||
@@ -13796,6 +13798,12 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAccessedPropertyName(access: PropertyAccessExpression | ElementAccessExpression): __String | undefined {
|
||||
return isPropertyAccessExpression(access) ? access.name.escapedText :
|
||||
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
|
||||
undefined;
|
||||
}
|
||||
|
||||
function containsMatchingReference(source: Node, target: Node) {
|
||||
while (source.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
source = (<PropertyAccessExpression>source).expression;
|
||||
@@ -14438,7 +14446,10 @@ namespace ts {
|
||||
else if (flags & FlowFlags.Start) {
|
||||
// Check if we should continue with the control flow of the containing function.
|
||||
const container = (<FlowStart>flow).container;
|
||||
if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) {
|
||||
if (container && container !== flowContainer &&
|
||||
reference.kind !== SyntaxKind.PropertyAccessExpression &&
|
||||
reference.kind !== SyntaxKind.ElementAccessExpression &&
|
||||
reference.kind !== SyntaxKind.ThisKeyword) {
|
||||
flow = container.flowNode!;
|
||||
continue;
|
||||
}
|
||||
@@ -14555,7 +14566,10 @@ namespace ts {
|
||||
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
}
|
||||
else if (isMatchingReferenceDiscriminant(expr, type)) {
|
||||
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
|
||||
type = narrowTypeByDiscriminant(
|
||||
type,
|
||||
expr as PropertyAccessExpression | ElementAccessExpression,
|
||||
t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
|
||||
}
|
||||
return createFlowType(type, isIncomplete(flowType));
|
||||
}
|
||||
@@ -14671,14 +14685,23 @@ namespace ts {
|
||||
}
|
||||
|
||||
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
|
||||
return expr.kind === SyntaxKind.PropertyAccessExpression &&
|
||||
computedType.flags & TypeFlags.Union &&
|
||||
isMatchingReference(reference, (<PropertyAccessExpression>expr).expression) &&
|
||||
isDiscriminantProperty(computedType, (<PropertyAccessExpression>expr).name.escapedText);
|
||||
if (!(computedType.flags & TypeFlags.Union) ||
|
||||
expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) {
|
||||
return false;
|
||||
}
|
||||
const access = expr as PropertyAccessExpression | ElementAccessExpression;
|
||||
const name = getAccessedPropertyName(access);
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return isMatchingReference(reference, access.expression) && isDiscriminantProperty(computedType, name);
|
||||
}
|
||||
|
||||
function narrowTypeByDiscriminant(type: Type, propAccess: PropertyAccessExpression, narrowType: (t: Type) => Type): Type {
|
||||
const propName = propAccess.name.escapedText;
|
||||
function narrowTypeByDiscriminant(type: Type, access: PropertyAccessExpression | ElementAccessExpression, narrowType: (t: Type) => Type): Type {
|
||||
const propName = getAccessedPropertyName(access);
|
||||
if (!propName) {
|
||||
return type;
|
||||
}
|
||||
const propType = getTypeOfPropertyOfType(type, propName);
|
||||
const narrowedPropType = propType && narrowType(propType);
|
||||
return propType === narrowedPropType ? type : filterType(type, t => isTypeComparableTo(getTypeOfPropertyOfType(t, propName)!, narrowedPropType!));
|
||||
@@ -14689,7 +14712,7 @@ namespace ts {
|
||||
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
|
||||
}
|
||||
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
|
||||
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
|
||||
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
|
||||
}
|
||||
if (containsMatchingReferenceDiscriminant(reference, expr)) {
|
||||
return declaredType;
|
||||
@@ -14740,10 +14763,10 @@ namespace ts {
|
||||
return narrowTypeByEquality(type, operator, left, assumeTrue);
|
||||
}
|
||||
if (isMatchingReferenceDiscriminant(left, declaredType)) {
|
||||
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
|
||||
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
|
||||
}
|
||||
if (isMatchingReferenceDiscriminant(right, declaredType)) {
|
||||
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
|
||||
return narrowTypeByDiscriminant(type, <PropertyAccessExpression | ElementAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
|
||||
}
|
||||
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {
|
||||
return declaredType;
|
||||
@@ -14982,6 +15005,7 @@ namespace ts {
|
||||
case SyntaxKind.ThisKeyword:
|
||||
case SyntaxKind.SuperKeyword:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return narrowTypeByTruthiness(type, expr, assumeTrue);
|
||||
case SyntaxKind.CallExpression:
|
||||
return narrowTypeByTypePredicate(type, <CallExpression>expr, assumeTrue);
|
||||
|
||||
Reference in New Issue
Block a user