@@ -8544,12 +8544,16 @@ namespace ts {
8544
8544
8545
8545
/** Return the inferred type for a binding element */
8546
8546
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
8547
- const pattern = declaration.parent;
8548
- let parentType = getTypeForBindingElementParent(pattern.parent);
8549
- // If no type or an any type was inferred for parent, infer that for the binding element
8550
- if (!parentType || isTypeAny(parentType)) {
8547
+ const parentType = getTypeForBindingElementParent(declaration.parent.parent);
8548
+ return parentType && getBindingElementTypeFromParentType(declaration, parentType);
8549
+ }
8550
+
8551
+ function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type {
8552
+ // If an any type was inferred for parent, infer that for the binding element
8553
+ if (isTypeAny(parentType)) {
8551
8554
return parentType;
8552
8555
}
8556
+ const pattern = declaration.parent;
8553
8557
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
8554
8558
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
8555
8559
parentType = getNonNullableType(parentType);
@@ -22633,30 +22637,6 @@ namespace ts {
22633
22637
return false;
22634
22638
}
22635
22639
22636
- function getPropertyAccess(expr: Expression) {
22637
- if (isAccessExpression(expr)) {
22638
- return expr;
22639
- }
22640
- if (isIdentifier(expr)) {
22641
- const symbol = getResolvedSymbol(expr);
22642
- if (isConstVariable(symbol)) {
22643
- const declaration = symbol.valueDeclaration!;
22644
- // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
22645
- if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer)) {
22646
- return declaration.initializer;
22647
- }
22648
- // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
22649
- if (isBindingElement(declaration) && !declaration.initializer) {
22650
- const parent = declaration.parent.parent;
22651
- if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer))) {
22652
- return declaration;
22653
- }
22654
- }
22655
- }
22656
- }
22657
- return undefined;
22658
- }
22659
-
22660
22640
function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
22661
22641
let propertyName;
22662
22642
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
@@ -23626,19 +23606,19 @@ namespace ts {
23626
23606
return false;
23627
23607
}
23628
23608
23629
- function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) {
23609
+ function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode ) {
23630
23610
let key: string | undefined;
23631
23611
let isKeySet = false;
23632
23612
let flowDepth = 0;
23633
23613
if (flowAnalysisDisabled) {
23634
23614
return errorType;
23635
23615
}
23636
- if (!reference. flowNode) {
23616
+ if (!flowNode) {
23637
23617
return declaredType;
23638
23618
}
23639
23619
flowInvocationCount++;
23640
23620
const sharedFlowStart = sharedFlowCount;
23641
- const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference. flowNode));
23621
+ const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode));
23642
23622
sharedFlowCount = sharedFlowStart;
23643
23623
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
23644
23624
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
@@ -24082,13 +24062,58 @@ namespace ts {
24082
24062
return result;
24083
24063
}
24084
24064
24065
+ function getCandidateDiscriminantPropertyAccess(expr: Expression) {
24066
+ if (isBindingPattern(reference)) {
24067
+ // When the reference is a binding pattern, we are narrowing a pesudo-reference in getNarrowedTypeOfSymbol.
24068
+ // An identifier for a destructuring variable declared in the same binding pattern is a candidate.
24069
+ if (isIdentifier(expr)) {
24070
+ const symbol = getResolvedSymbol(expr);
24071
+ const declaration = symbol.valueDeclaration;
24072
+ if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && reference === declaration.parent) {
24073
+ return declaration;
24074
+ }
24075
+ }
24076
+ }
24077
+ else if (isAccessExpression(expr)) {
24078
+ // An access expression is a candidate if the reference matches the left hand expression.
24079
+ if (isMatchingReference(reference, expr.expression)) {
24080
+ return expr;
24081
+ }
24082
+ }
24083
+ else if (isIdentifier(expr)) {
24084
+ const symbol = getResolvedSymbol(expr);
24085
+ if (isConstVariable(symbol)) {
24086
+ const declaration = symbol.valueDeclaration!;
24087
+ // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
24088
+ if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
24089
+ isMatchingReference(reference, declaration.initializer.expression)) {
24090
+ return declaration.initializer;
24091
+ }
24092
+ // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
24093
+ if (isBindingElement(declaration) && !declaration.initializer) {
24094
+ const parent = declaration.parent.parent;
24095
+ if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
24096
+ isMatchingReference(reference, parent.initializer)) {
24097
+ return declaration;
24098
+ }
24099
+ }
24100
+ }
24101
+ }
24102
+ return undefined;
24103
+ }
24104
+
24085
24105
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
24086
- let access, name;
24087
24106
const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType;
24088
- return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) &&
24089
- isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) &&
24090
- isDiscriminantProperty(type, name) ?
24091
- access : undefined;
24107
+ if (type.flags & TypeFlags.Union) {
24108
+ const access = getCandidateDiscriminantPropertyAccess(expr);
24109
+ if (access) {
24110
+ const name = getAccessedPropertyName(access);
24111
+ if (name && isDiscriminantProperty(type, name)) {
24112
+ return access;
24113
+ }
24114
+ }
24115
+ }
24116
+ return undefined;
24092
24117
}
24093
24118
24094
24119
function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type {
@@ -24901,6 +24926,50 @@ namespace ts {
24901
24926
}
24902
24927
}
24903
24928
24929
+ function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) {
24930
+ // If we have a non-rest binding element with no initializer declared as a const variable or a const-like
24931
+ // parameter (a parameter for which there are no assignments in the function body), and if the parent type
24932
+ // for the destructuring is a union type, one or more of the binding elements may represent discriminant
24933
+ // properties, and we want the effects of conditional checks on such discriminants to affect the types of
24934
+ // other binding elements from the same destructuring. Consider:
24935
+ //
24936
+ // type Action =
24937
+ // | { kind: 'A', payload: number }
24938
+ // | { kind: 'B', payload: string };
24939
+ //
24940
+ // function f1({ kind, payload }: Action) {
24941
+ // if (kind === 'A') {
24942
+ // payload.toFixed();
24943
+ // }
24944
+ // if (kind === 'B') {
24945
+ // payload.toUpperCase();
24946
+ // }
24947
+ // }
24948
+ //
24949
+ // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use
24950
+ // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
24951
+ // as if it occurred in the specified location. We then recompute the narrowed binding element type by
24952
+ // destructuring from the narrowed parent type.
24953
+ const declaration = symbol.valueDeclaration;
24954
+ if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) {
24955
+ const parent = declaration.parent.parent;
24956
+ if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) {
24957
+ const links = getNodeLinks(location);
24958
+ if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
24959
+ links.flags |= NodeCheckFlags.InCheckIdentifier;
24960
+ const parentType = getTypeForBindingElementParent(parent);
24961
+ links.flags &= ~NodeCheckFlags.InCheckIdentifier;
24962
+ if (parentType && parentType.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) {
24963
+ const pattern = declaration.parent;
24964
+ const narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode);
24965
+ return getBindingElementTypeFromParentType(declaration, narrowedType);
24966
+ }
24967
+ }
24968
+ }
24969
+ }
24970
+ return getTypeOfSymbol(symbol);
24971
+ }
24972
+
24904
24973
function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type {
24905
24974
const symbol = getResolvedSymbol(node);
24906
24975
if (symbol === unknownSymbol) {
@@ -24984,7 +25053,7 @@ namespace ts {
24984
25053
24985
25054
checkNestedBlockScopedBinding(node, symbol);
24986
25055
24987
- let type = getTypeOfSymbol (localOrExportSymbol);
25056
+ let type = getNarrowedTypeOfSymbol (localOrExportSymbol, node );
24988
25057
const assignmentKind = getAssignmentTargetKind(node);
24989
25058
24990
25059
if (assignmentKind) {
0 commit comments