@@ -8544,12 +8544,16 @@ namespace ts {
85448544
85458545 /** Return the inferred type for a binding element */
85468546 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)) {
85518554 return parentType;
85528555 }
8556+ const pattern = declaration.parent;
85538557 // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
85548558 if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
85558559 parentType = getNonNullableType(parentType);
@@ -22633,30 +22637,6 @@ namespace ts {
2263322637 return false;
2263422638 }
2263522639
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-
2266022640 function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
2266122641 let propertyName;
2266222642 return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
@@ -23626,19 +23606,19 @@ namespace ts {
2362623606 return false;
2362723607 }
2362823608
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 ) {
2363023610 let key: string | undefined;
2363123611 let isKeySet = false;
2363223612 let flowDepth = 0;
2363323613 if (flowAnalysisDisabled) {
2363423614 return errorType;
2363523615 }
23636- if (!reference. flowNode) {
23616+ if (!flowNode) {
2363723617 return declaredType;
2363823618 }
2363923619 flowInvocationCount++;
2364023620 const sharedFlowStart = sharedFlowCount;
23641- const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference. flowNode));
23621+ const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode));
2364223622 sharedFlowCount = sharedFlowStart;
2364323623 // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
2364423624 // 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 {
2408224062 return result;
2408324063 }
2408424064
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+
2408524105 function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
24086- let access, name;
2408724106 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;
2409224117 }
2409324118
2409424119 function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type {
@@ -24901,6 +24926,50 @@ namespace ts {
2490124926 }
2490224927 }
2490324928
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+
2490424973 function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type {
2490524974 const symbol = getResolvedSymbol(node);
2490624975 if (symbol === unknownSymbol) {
@@ -24984,7 +25053,7 @@ namespace ts {
2498425053
2498525054 checkNestedBlockScopedBinding(node, symbol);
2498625055
24987- let type = getTypeOfSymbol (localOrExportSymbol);
25056+ let type = getNarrowedTypeOfSymbol (localOrExportSymbol, node );
2498825057 const assignmentKind = getAssignmentTargetKind(node);
2498925058
2499025059 if (assignmentKind) {
0 commit comments