@@ -1339,6 +1339,7 @@ export const enum CheckMode {
13391339 // e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
13401340 // we need to preserve generic types instead of substituting them for constraints
13411341 TypeOnly = 1 << 6, // Called from getTypeOfExpression, diagnostics may be omitted
1342+ SkipConstraintsSubstitution = 1 << 7,
13421343}
13431344
13441345/** @internal */
@@ -30365,21 +30366,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3036530366 return contextualType && !isGenericType(contextualType);
3036630367 }
3036730368
30368- function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) {
30369- if (isNoInferType(type)) {
30370- type = (type as SubstitutionType).baseType;
30371- }
30369+ function shouldSubstituteConstraints(type: Type, reference: Node, checkMode?: CheckMode) {
3037230370 // When the type of a reference is or contains an instantiable type with a union type constraint, and
3037330371 // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or
3037430372 // has a contextual type containing no top-level instantiables (meaning constraints will determine
3037530373 // assignability), we substitute constraints for all instantiables in the type of the reference to give
3037630374 // control flow analysis an opportunity to narrow it further. For example, for a reference of a type
3037730375 // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute
3037830376 // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
30379- const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
30377+ return !(checkMode && checkMode & CheckMode.Inferential) &&
3038030378 someType(type, isGenericTypeWithUnionConstraint) &&
3038130379 (isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode));
30382- return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type;
30380+ }
30381+
30382+ function getNarrowableTypeForReference(type: Type, reference: Node, checkMode = CheckMode.Normal) {
30383+ if (isNoInferType(type)) {
30384+ type = (type as SubstitutionType).baseType;
30385+ }
30386+ if (checkMode & CheckMode.SkipConstraintsSubstitution) {
30387+ return type;
30388+ }
30389+ return shouldSubstituteConstraints(type, reference, checkMode) ? mapType(type, getBaseConstraintOrType) : type;
3038330390 }
3038430391
3038530392 function isExportOrExportExpression(location: Node) {
@@ -30811,7 +30818,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3081130818 const type = getTypeOfSymbol(symbol);
3081230819 const declaration = symbol.valueDeclaration;
3081330820 if (declaration) {
30814- // If we have a non-rest binding element with no initializer declared as a const variable or a const-like
30821+ // If we have a binding element with no initializer declared as a const variable or a const-like
3081530822 // parameter (a parameter for which there are no assignments in the function body), and if the parent type
3081630823 // for the destructuring is a union type, one or more of the binding elements may represent discriminant
3081730824 // properties, and we want the effects of conditional checks on such discriminants to affect the types of
@@ -30834,19 +30841,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3083430841 // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
3083530842 // as if it occurred in the specified location. We then recompute the narrowed binding element type by
3083630843 // destructuring from the narrowed parent type.
30837- if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) {
30844+ if (isBindingElement(declaration) && !declaration.initializer && declaration.parent.elements.length >= 2) {
3083830845 const parent = declaration.parent.parent;
3083930846 const rootDeclaration = getRootDeclaration(parent);
3084030847 if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.kind === SyntaxKind.Parameter) {
3084130848 const links = getNodeLinks(parent);
3084230849 if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
3084330850 links.flags |= NodeCheckFlags.InCheckIdentifier;
30844- const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
30845- const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType);
30851+ const parentType = getTypeForBindingElementParent(parent, shouldSubstituteConstraints(type, location) ? CheckMode.Normal : CheckMode.SkipConstraintsSubstitution);
30852+ const parentNarrowableType = parentType && getNarrowableTypeForReference(parentType, location);
30853+
3084630854 links.flags &= ~NodeCheckFlags.InCheckIdentifier;
30847- if (parentTypeConstraint && parentTypeConstraint .flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
30855+ if (parentNarrowableType && parentNarrowableType .flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
3084830856 const pattern = declaration.parent;
30849- const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint , /*flowContainer*/ undefined, location.flowNode);
30857+ const narrowedType = getFlowTypeOfReference(pattern, parentNarrowableType, parentNarrowableType , /*flowContainer*/ undefined, location.flowNode);
3085030858 if (narrowedType.flags & TypeFlags.Never) {
3085130859 return neverType;
3085230860 }
0 commit comments