@@ -8466,12 +8466,16 @@ namespace ts {
8466
8466
8467
8467
/** Return the inferred type for a binding element */
8468
8468
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
8469
- const pattern = declaration.parent;
8470
- let parentType = getTypeForBindingElementParent(pattern.parent);
8471
- // If no type or an any type was inferred for parent, infer that for the binding element
8472
- if (!parentType || isTypeAny(parentType)) {
8469
+ const parentType = getTypeForBindingElementParent(declaration.parent.parent);
8470
+ return parentType && getBindingElementTypeFromParentType(declaration, parentType);
8471
+ }
8472
+
8473
+ function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type {
8474
+ // If an any type was inferred for parent, infer that for the binding element
8475
+ if (isTypeAny(parentType)) {
8473
8476
return parentType;
8474
8477
}
8478
+ const pattern = declaration.parent;
8475
8479
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
8476
8480
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
8477
8481
parentType = getNonNullableType(parentType);
@@ -22534,30 +22538,6 @@ namespace ts {
22534
22538
return false;
22535
22539
}
22536
22540
22537
- function getPropertyAccess(expr: Expression) {
22538
- if (isAccessExpression(expr)) {
22539
- return expr;
22540
- }
22541
- if (isIdentifier(expr)) {
22542
- const symbol = getResolvedSymbol(expr);
22543
- if (isConstVariable(symbol)) {
22544
- const declaration = symbol.valueDeclaration!;
22545
- // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
22546
- if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer)) {
22547
- return declaration.initializer;
22548
- }
22549
- // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
22550
- if (isBindingElement(declaration) && !declaration.initializer) {
22551
- const parent = declaration.parent.parent;
22552
- if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer))) {
22553
- return declaration;
22554
- }
22555
- }
22556
- }
22557
- }
22558
- return undefined;
22559
- }
22560
-
22561
22541
function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
22562
22542
let propertyName;
22563
22543
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
@@ -23526,19 +23506,19 @@ namespace ts {
23526
23506
return false;
23527
23507
}
23528
23508
23529
- function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) {
23509
+ function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode ) {
23530
23510
let key: string | undefined;
23531
23511
let isKeySet = false;
23532
23512
let flowDepth = 0;
23533
23513
if (flowAnalysisDisabled) {
23534
23514
return errorType;
23535
23515
}
23536
- if (!reference. flowNode) {
23516
+ if (!flowNode) {
23537
23517
return declaredType;
23538
23518
}
23539
23519
flowInvocationCount++;
23540
23520
const sharedFlowStart = sharedFlowCount;
23541
- const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference. flowNode));
23521
+ const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode));
23542
23522
sharedFlowCount = sharedFlowStart;
23543
23523
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
23544
23524
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
@@ -23982,13 +23962,58 @@ namespace ts {
23982
23962
return result;
23983
23963
}
23984
23964
23965
+ function getCandidateDiscriminantPropertyAccess(expr: Expression) {
23966
+ if (isBindingPattern(reference)) {
23967
+ // When the reference is a binding pattern, we are narrowing a pesudo-reference in getNarrowedTypeOfSymbol.
23968
+ // An identifier for a destructuring variable declared in the same binding pattern is a candidate.
23969
+ if (isIdentifier(expr)) {
23970
+ const symbol = getResolvedSymbol(expr);
23971
+ const declaration = symbol.valueDeclaration;
23972
+ if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && reference === declaration.parent) {
23973
+ return declaration;
23974
+ }
23975
+ }
23976
+ }
23977
+ else if (isAccessExpression(expr)) {
23978
+ // An access expression is a candidate if the reference matches the left hand expression.
23979
+ if (isMatchingReference(reference, expr.expression)) {
23980
+ return expr;
23981
+ }
23982
+ }
23983
+ else if (isIdentifier(expr)) {
23984
+ const symbol = getResolvedSymbol(expr);
23985
+ if (isConstVariable(symbol)) {
23986
+ const declaration = symbol.valueDeclaration!;
23987
+ // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
23988
+ if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
23989
+ isMatchingReference(reference, declaration.initializer.expression)) {
23990
+ return declaration.initializer;
23991
+ }
23992
+ // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
23993
+ if (isBindingElement(declaration) && !declaration.initializer) {
23994
+ const parent = declaration.parent.parent;
23995
+ if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
23996
+ isMatchingReference(reference, parent.initializer)) {
23997
+ return declaration;
23998
+ }
23999
+ }
24000
+ }
24001
+ }
24002
+ return undefined;
24003
+ }
24004
+
23985
24005
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
23986
- let access, name;
23987
24006
const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType;
23988
- return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) &&
23989
- isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) &&
23990
- isDiscriminantProperty(type, name) ?
23991
- access : undefined;
24007
+ if (type.flags & TypeFlags.Union) {
24008
+ const access = getCandidateDiscriminantPropertyAccess(expr);
24009
+ if (access) {
24010
+ const name = getAccessedPropertyName(access);
24011
+ if (name && isDiscriminantProperty(type, name)) {
24012
+ return access;
24013
+ }
24014
+ }
24015
+ }
24016
+ return undefined;
23992
24017
}
23993
24018
23994
24019
function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type {
@@ -24801,6 +24826,50 @@ namespace ts {
24801
24826
}
24802
24827
}
24803
24828
24829
+ function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) {
24830
+ // If we have a non-rest binding element with no initializer declared as a const variable or a const-like
24831
+ // parameter (a parameter for which there are no assignments in the function body), and if the parent type
24832
+ // for the destructuring is a union type, one or more of the binding elements may represent discriminant
24833
+ // properties, and we want the effects of conditional checks on such discriminants to affect the types of
24834
+ // other binding elements from the same destructuring. Consider:
24835
+ //
24836
+ // type Action =
24837
+ // | { kind: 'A', payload: number }
24838
+ // | { kind: 'B', payload: string };
24839
+ //
24840
+ // function f1({ kind, payload }: Action) {
24841
+ // if (kind === 'A') {
24842
+ // payload.toFixed();
24843
+ // }
24844
+ // if (kind === 'B') {
24845
+ // payload.toUpperCase();
24846
+ // }
24847
+ // }
24848
+ //
24849
+ // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use
24850
+ // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
24851
+ // as if it occurred in the specified location. We then recompute the narrowed binding element type by
24852
+ // destructuring from the narrowed parent type.
24853
+ const declaration = symbol.valueDeclaration;
24854
+ if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken) {
24855
+ const parent = declaration.parent.parent;
24856
+ if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) && NodeFlags.Const || parent.kind === SyntaxKind.Parameter && !isSymbolAssigned(symbol)) {
24857
+ const links = getNodeLinks(location);
24858
+ if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
24859
+ links.flags |= NodeCheckFlags.InCheckIdentifier;
24860
+ const parentType = getTypeForBindingElementParent(parent);
24861
+ links.flags &= ~NodeCheckFlags.InCheckIdentifier;
24862
+ if (parentType && parentType.flags & TypeFlags.Union) {
24863
+ const pattern = declaration.parent;
24864
+ const narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode);
24865
+ return getBindingElementTypeFromParentType(declaration, narrowedType);
24866
+ }
24867
+ }
24868
+ }
24869
+ }
24870
+ return getTypeOfSymbol(symbol);
24871
+ }
24872
+
24804
24873
function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type {
24805
24874
const symbol = getResolvedSymbol(node);
24806
24875
if (symbol === unknownSymbol) {
@@ -24884,7 +24953,7 @@ namespace ts {
24884
24953
24885
24954
checkNestedBlockScopedBinding(node, symbol);
24886
24955
24887
- let type = getTypeOfSymbol (localOrExportSymbol);
24956
+ let type = getNarrowedTypeOfSymbol (localOrExportSymbol, node );
24888
24957
const assignmentKind = getAssignmentTargetKind(node);
24889
24958
24890
24959
if (assignmentKind) {
0 commit comments