From c6f0dfbc4d245bbbf3c06b04d990de45a1e35c04 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 29 Jan 2018 17:07:51 -0800 Subject: [PATCH] Add definitelyAssignableRelation --- src/compiler/checker.ts | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b0d70c1e89e..4376674ddaf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -538,6 +538,7 @@ namespace ts { const subtypeRelation = createMap(); const assignableRelation = createMap(); + const definitelyAssignableRelation = createMap(); const comparableRelation = createMap(); const identityRelation = createMap(); const enumRelation = createMap(); @@ -8128,8 +8129,11 @@ namespace ts { } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(baseExtendsType, combinedMapper) : extendsType; - // Return trueType for a definitely true extends check - if (isTypeAssignableTo(checkType, inferredExtendsType)) { + // Return trueType for a definitely true extends check. The definitely assignable relation excludes + // type variable constraints from consideration. Without the definitely assignable relation, the type + // type Foo = T extends { x: string } ? string : number + // would immediately resolve to 'string' instead of being deferred. + if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) { return instantiateType(baseTrueType, combinedMapper || mapper); } // Return a deferred type for a check that is neither definitely true nor definitely false @@ -9238,7 +9242,7 @@ namespace ts { if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true; if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true; if (s & TypeFlags.UniqueESSymbol || t & TypeFlags.UniqueESSymbol) return false; - if (relation === assignableRelation || relation === comparableRelation) { + if (relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) { if (s & TypeFlags.Any) return true; // Type number or any numeric literal type is assignable to any numeric enum type or any // numeric enum literal type. This rule exists for backwards compatibility reasons because @@ -9407,7 +9411,7 @@ namespace ts { target = (target).regularType; } if (source.flags & TypeFlags.Substitution) { - source = (source).substitute; + source = relation === definitelyAssignableRelation ? (source).typeParameter : (source).substitute; } if (target.flags & TypeFlags.Substitution) { target = (target).typeParameter; @@ -9538,7 +9542,7 @@ namespace ts { function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if ((relation === assignableRelation || relation === comparableRelation) && + if ((relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) && (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { return false; } @@ -9810,6 +9814,10 @@ namespace ts { return result; } + function getConstraintForRelation(type: Type) { + return relation === definitelyAssignableRelation ? undefined : getConstraintOfType(type); + } + function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { let result: Ternary; let originalErrorInfo: DiagnosticMessageChain; @@ -9835,7 +9843,7 @@ namespace ts { } // A type S is assignable to keyof T if S is assignable to keyof C, where C is the // constraint of T. - const constraint = getConstraintOfType((target).type); + const constraint = getConstraintForRelation((target).type); if (constraint) { if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) { return result; @@ -9845,7 +9853,7 @@ namespace ts { else if (target.flags & TypeFlags.IndexedAccess) { // A type S is related to a type T[K] if S is related to A[K], where K is string-like and // A is the apparent type of T. - const constraint = getConstraintOfIndexedAccess(target); + const constraint = getConstraintForRelation(target); if (constraint) { if (result = isRelatedTo(source, constraint, reportErrors)) { errorInfo = saveErrorInfo; @@ -9872,7 +9880,7 @@ namespace ts { } if (source.flags & TypeFlags.TypeParameter) { - let constraint = getConstraintOfTypeParameter(source); + let constraint = getConstraintForRelation(source); // A type parameter with no constraint is not related to the non-primitive object type. if (constraint || !(target.flags & TypeFlags.NonPrimitive)) { if (!constraint || constraint.flags & TypeFlags.Any) { @@ -9889,7 +9897,7 @@ namespace ts { else if (source.flags & TypeFlags.IndexedAccess) { // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and // A is the apparent type of S. - const constraint = getConstraintOfIndexedAccess(source); + const constraint = getConstraintForRelation(source); if (constraint) { if (result = isRelatedTo(constraint, target, reportErrors)) { errorInfo = saveErrorInfo; @@ -9906,11 +9914,13 @@ namespace ts { } } else if (source.flags & TypeFlags.Conditional) { - const constraint = getConstraintOfDistributiveConditionalType(source); - if (constraint) { - if (result = isRelatedTo(constraint, target, reportErrors)) { - errorInfo = saveErrorInfo; - return result; + if (relation !== definitelyAssignableRelation) { + const constraint = getConstraintOfDistributiveConditionalType(source); + if (constraint) { + if (result = isRelatedTo(constraint, target, reportErrors)) { + errorInfo = saveErrorInfo; + return result; + } } } if (result = isRelatedTo(getDefaultConstraintOfConditionalType(source), target, reportErrors)) {