Property tracking of recursive non-object types in checkTypeRelatedTo

This commit is contained in:
Anders Hejlsberg 2017-04-03 10:56:35 -07:00
parent 0fd0903280
commit e416c7046e

View file

@ -8468,137 +8468,38 @@ namespace ts {
return result;
}
}
else if (target.flags & TypeFlags.Union) {
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) {
return result;
}
}
else if (target.flags & TypeFlags.Intersection) {
if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) {
return result;
}
}
else if (source.flags & TypeFlags.Intersection) {
// Check to see if any constituents of the intersection are immediately related to the target.
//
// Don't report errors though. Checking whether a constituent is related to the source is not actually
// useful and leads to some confusing error messages. Instead it is better to let the below checks
// take care of this, or to not elaborate at all. For instance,
//
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
//
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
// than to report that 'D' is not assignable to 'A' or 'B'.
//
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
// breaking the intersection apart.
if (result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false)) {
return result;
}
}
else if (target.flags & TypeFlags.TypeParameter) {
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
if (!(<MappedType>source).declaration.questionToken) {
const templateType = getTemplateTypeFromMappedType(<MappedType>source);
const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(<MappedType>source));
if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) {
return result;
}
}
}
}
else if (target.flags & TypeFlags.Index) {
// A keyof S is related to a keyof T if T is related to S.
if (source.flags & TypeFlags.Index) {
if (result = isRelatedTo((<IndexType>target).type, (<IndexType>source).type, /*reportErrors*/ false)) {
return result;
}
}
// 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((<IndexType>target).type);
if (constraint) {
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
return result;
}
}
}
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 S.
const constraint = getConstraintOfType(<IndexedAccessType>target);
if (constraint) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
}
if (source.flags & TypeFlags.TypeParameter) {
// A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X.
if (getObjectFlags(target) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>target) === getIndexType(source)) {
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(<MappedType>target));
const templateType = getTemplateTypeFromMappedType(<MappedType>target);
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
else {
let constraint = getConstraintOfTypeParameter(<TypeParameter>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) {
constraint = emptyObjectType;
}
// The constraint may need to be further instantiated with its 'this' type.
constraint = getTypeWithThisArgument(constraint, source);
// Report constraint errors only if the constraint is not the empty object type
const reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
}
}
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 = getConstraintOfType(<IndexedAccessType>source);
if (constraint) {
if (result = isRelatedTo(constraint, target, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
else if (target.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>source).indexType === (<IndexedAccessType>target).indexType) {
// if we have indexed access types with identical index types, see if relationship holds for
// the two object types.
if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, reportErrors)) {
return result;
}
}
}
else {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// We have type references to same target type, see if relationship holds for all type arguments
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, reportErrors)) {
if (target.flags & TypeFlags.Union) {
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) {
return result;
}
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
const apparentSource = getApparentType(source);
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
// relates to X. Thus, we include intersection types on the source side here.
if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
// Report structural errors only if we haven't reported any errors yet
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
else if (target.flags & TypeFlags.Intersection) {
if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) {
return result;
}
}
else if (source.flags & TypeFlags.Intersection) {
// Check to see if any constituents of the intersection are immediately related to the target.
//
// Don't report errors though. Checking whether a constituent is related to the source is not actually
// useful and leads to some confusing error messages. Instead it is better to let the below checks
// take care of this, or to not elaborate at all. For instance,
//
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
//
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
// than to report that 'D' is not assignable to 'A' or 'B'.
//
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
// breaking the intersection apart.
if (result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false)) {
return result;
}
}
if (source.flags & TypeFlags.StructuredOrTypeVariable || target.flags & TypeFlags.StructuredOrTypeVariable) {
if (result = recursiveTypeRelatedTo(source, target, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
@ -8620,13 +8521,7 @@ namespace ts {
function isIdenticalTo(source: Type, target: Type): Ternary {
let result: Ternary;
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// We have type references to same target type, see if all type arguments are identical
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, /*reportErrors*/ false)) {
return result;
}
}
return objectTypeRelatedTo(source, source, target, /*reportErrors*/ false);
return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false);
}
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
@ -8815,12 +8710,12 @@ namespace ts {
return result;
}
// Determine if two object types are related by structure. First, check if the result is already available in the global cache.
// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
// and issue an error. Otherwise, actually compare the structure of the two types.
function objectTypeRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary {
function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
if (overflow) {
return Ternary.False;
}
@ -8862,28 +8757,7 @@ namespace ts {
const saveExpandingFlags = expandingFlags;
if (!(expandingFlags & 1) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= 1;
if (!(expandingFlags & 2) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= 2;
let result: Ternary;
if (expandingFlags === 3) {
result = Ternary.Maybe;
}
else if (isGenericMappedType(source) || isGenericMappedType(target)) {
result = mappedTypeRelatedTo(source, target, reportErrors);
}
else {
result = propertiesRelatedTo(source, target, reportErrors);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors);
if (result) {
result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.String, reportErrors);
if (result) {
result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.Number, reportErrors);
}
}
}
}
}
let result = expandingFlags !== 3 ? structuredTypeRelatedTo(source, target, reportErrors) : Ternary.Maybe;
expandingFlags = saveExpandingFlags;
depth--;
if (result) {
@ -8900,6 +8774,141 @@ namespace ts {
return result;
}
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
let result: Ternary;
const saveErrorInfo = errorInfo;
if (target.flags & TypeFlags.TypeParameter) {
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
if (!(<MappedType>source).declaration.questionToken) {
const templateType = getTemplateTypeFromMappedType(<MappedType>source);
const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(<MappedType>source));
if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) {
return result;
}
}
}
}
else if (target.flags & TypeFlags.Index) {
// A keyof S is related to a keyof T if T is related to S.
if (source.flags & TypeFlags.Index) {
if (result = isRelatedTo((<IndexType>target).type, (<IndexType>source).type, /*reportErrors*/ false)) {
return result;
}
}
// 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((<IndexType>target).type);
if (constraint) {
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
return result;
}
}
}
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 S.
const constraint = getConstraintOfType(<IndexedAccessType>target);
if (constraint) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
}
if (source.flags & TypeFlags.TypeParameter) {
// A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X.
if (getObjectFlags(target) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>target) === getIndexType(source)) {
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(<MappedType>target));
const templateType = getTemplateTypeFromMappedType(<MappedType>target);
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
else {
let constraint = getConstraintOfTypeParameter(<TypeParameter>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) {
constraint = emptyObjectType;
}
// The constraint may need to be further instantiated with its 'this' type.
constraint = getTypeWithThisArgument(constraint, source);
// Report constraint errors only if the constraint is not the empty object type
const reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
}
}
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 = getConstraintOfType(<IndexedAccessType>source);
if (constraint) {
if (result = isRelatedTo(constraint, target, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
else if (target.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>source).indexType === (<IndexedAccessType>target).indexType) {
// if we have indexed access types with identical index types, see if relationship holds for
// the two object types.
if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, reportErrors)) {
return result;
}
}
}
else {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// We have type references to same target type, see if relationship holds for all type arguments
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, reportErrors)) {
return result;
}
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
if (relation !== identityRelation) {
source = getApparentType(source);
}
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
// relates to X. Thus, we include intersection types on the source side here.
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
// Report structural errors only if we haven't reported any errors yet
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive;
if (isGenericMappedType(source) || isGenericMappedType(target)) {
result = mappedTypeRelatedTo(source, target, reportStructuralErrors);
}
else {
result = propertiesRelatedTo(source, target, reportStructuralErrors);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors);
if (result) {
result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors);
if (result) {
result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors);
}
}
}
}
}
if (result) {
errorInfo = saveErrorInfo;
return result;
}
}
}
return Ternary.False;
}
// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
// that S and T are contra-variant whereas X and Y are co-variant.
@ -9148,12 +9157,12 @@ namespace ts {
return related;
}
function indexTypesRelatedTo(source: Type, originalSource: Type, target: Type, kind: IndexKind, reportErrors: boolean) {
function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean) {
if (relation === identityRelation) {
return indexTypesIdenticalTo(source, target, kind);
}
const targetInfo = getIndexInfoOfType(target, kind);
if (!targetInfo || ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive))) {
if (!targetInfo || targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive) {
// Index signature of type any permits assignment from everything but primitives
return Ternary.True;
}