Property tracking of recursive non-object types in checkTypeRelatedTo
This commit is contained in:
parent
0fd0903280
commit
e416c7046e
1 changed files with 169 additions and 160 deletions
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue