mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 07:13:45 -05:00
Merge pull request #4112 from Microsoft/nonObjectTypeConstraints
Support non-object type constraints
This commit is contained in:
@@ -4668,19 +4668,21 @@ namespace ts {
|
||||
let result: Ternary;
|
||||
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
|
||||
if (source === target) return Ternary.True;
|
||||
if (relation !== identityRelation) {
|
||||
if (isTypeAny(target)) return Ternary.True;
|
||||
if (source === undefinedType) return Ternary.True;
|
||||
if (source === nullType && target !== undefinedType) return Ternary.True;
|
||||
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
|
||||
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
|
||||
if (relation === assignableRelation) {
|
||||
if (isTypeAny(source)) return Ternary.True;
|
||||
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
|
||||
}
|
||||
if (relation === identityRelation) {
|
||||
return isIdenticalTo(source, target);
|
||||
}
|
||||
|
||||
if (relation !== identityRelation && source.flags & TypeFlags.FreshObjectLiteral) {
|
||||
if (isTypeAny(target)) return Ternary.True;
|
||||
if (source === undefinedType) return Ternary.True;
|
||||
if (source === nullType && target !== undefinedType) return Ternary.True;
|
||||
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
|
||||
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
|
||||
if (relation === assignableRelation) {
|
||||
if (isTypeAny(source)) return Ternary.True;
|
||||
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
|
||||
}
|
||||
|
||||
if (source.flags & TypeFlags.FreshObjectLiteral) {
|
||||
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
|
||||
if (reportErrors) {
|
||||
reportRelationError(headMessage, source, target);
|
||||
@@ -4696,78 +4698,66 @@ namespace ts {
|
||||
|
||||
let saveErrorInfo = errorInfo;
|
||||
|
||||
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.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 = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
|
||||
// Note that the "each" checks must precede the "some" checks to produce the correct results
|
||||
if (source.flags & TypeFlags.Union) {
|
||||
if (result = eachTypeRelatedToType(<UnionType>source, target, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) {
|
||||
if (result = typeParameterRelatedTo(<TypeParameter>source, <TypeParameter>target, reportErrors)) {
|
||||
else if (target.flags & TypeFlags.Intersection) {
|
||||
if (result = typeRelatedToEachType(source, <IntersectionType>target, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (relation !== identityRelation) {
|
||||
// Note that the "each" checks must precede the "some" checks to produce the correct results
|
||||
if (source.flags & TypeFlags.Union) {
|
||||
if (result = eachTypeRelatedToType(<UnionType>source, target, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (target.flags & TypeFlags.Intersection) {
|
||||
if (result = typeRelatedToEachType(source, <IntersectionType>target, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// It is necessary to try "each" checks on both sides because there may be nested "some" checks
|
||||
// on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or
|
||||
// A & B = (A & B) | (C & D).
|
||||
if (source.flags & TypeFlags.Intersection) {
|
||||
// If target is a union type the following check will report errors so we suppress them here
|
||||
if (result = someTypeRelatedToType(<IntersectionType>source, target, reportErrors && !(target.flags & TypeFlags.Union))) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (target.flags & TypeFlags.Union) {
|
||||
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
|
||||
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
|
||||
if (result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target)) {
|
||||
if (result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source)) {
|
||||
return result;
|
||||
}
|
||||
// It is necessary to try "some" checks on both sides because there may be nested "each" checks
|
||||
// on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or
|
||||
// A & B = (A & B) | (C & D).
|
||||
if (source.flags & TypeFlags.Intersection) {
|
||||
// If target is a union type the following check will report errors so we suppress them here
|
||||
if (result = someTypeRelatedToType(<IntersectionType>source, target, reportErrors && !(target.flags & TypeFlags.Union))) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (target.flags & TypeFlags.Union) {
|
||||
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Even if relationship doesn't hold for unions, type parameters, or generic type references,
|
||||
// it may hold in a structural comparison.
|
||||
// Report structural errors only if we haven't reported any errors yet
|
||||
let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo;
|
||||
// Identity relation does not use apparent type
|
||||
let sourceOrApparentType = 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 (sourceOrApparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) {
|
||||
if (result = objectTypeRelatedTo(sourceOrApparentType, <ObjectType>target, reportStructuralErrors)) {
|
||||
if (source.flags & TypeFlags.TypeParameter) {
|
||||
let constraint = getConstraintOfTypeParameter(<TypeParameter>source);
|
||||
if (!constraint || constraint.flags & TypeFlags.Any) {
|
||||
constraint = emptyObjectType;
|
||||
}
|
||||
// Report constraint errors only if the constraint is not the empty object type
|
||||
let reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
|
||||
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
|
||||
errorInfo = saveErrorInfo;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (source.flags & TypeFlags.TypeParameter && sourceOrApparentType.flags & TypeFlags.UnionOrIntersection) {
|
||||
// We clear the errors first because the following check often gives a better error than
|
||||
// the union or intersection comparison above if it is applicable.
|
||||
errorInfo = saveErrorInfo;
|
||||
if (result = isRelatedTo(sourceOrApparentType, target, reportErrors)) {
|
||||
return result;
|
||||
else {
|
||||
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.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 = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// Even if relationship doesn't hold for unions, intersections, or generic type references,
|
||||
// it may hold in a structural comparison.
|
||||
let apparentType = 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 (apparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) {
|
||||
// Report structural errors only if we haven't reported any errors yet
|
||||
let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo;
|
||||
if (result = objectTypeRelatedTo(apparentType, <ObjectType>target, reportStructuralErrors)) {
|
||||
errorInfo = saveErrorInfo;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4777,6 +4767,31 @@ namespace ts {
|
||||
return Ternary.False;
|
||||
}
|
||||
|
||||
function isIdenticalTo(source: Type, target: Type): Ternary {
|
||||
let result: Ternary;
|
||||
if (source.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType) {
|
||||
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
|
||||
// We have type references to same target type, see if all type arguments are identical
|
||||
if (result = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, /*reportErrors*/ false)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return objectTypeRelatedTo(<ObjectType>source, <ObjectType>target, /*reportErrors*/ false);
|
||||
}
|
||||
if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) {
|
||||
return typeParameterIdenticalTo(<TypeParameter>source, <TypeParameter>target);
|
||||
}
|
||||
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
|
||||
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
|
||||
if (result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target)) {
|
||||
if (result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
|
||||
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
|
||||
for (let prop of getPropertiesOfObjectType(source)) {
|
||||
if (!isKnownProperty(target, prop.name)) {
|
||||
@@ -4861,29 +4876,18 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
function typeParameterRelatedTo(source: TypeParameter, target: TypeParameter, reportErrors: boolean): Ternary {
|
||||
if (relation === identityRelation) {
|
||||
if (source.symbol.name !== target.symbol.name) {
|
||||
return Ternary.False;
|
||||
}
|
||||
// covers case when both type parameters does not have constraint (both equal to noConstraintType)
|
||||
if (source.constraint === target.constraint) {
|
||||
return Ternary.True;
|
||||
}
|
||||
if (source.constraint === noConstraintType || target.constraint === noConstraintType) {
|
||||
return Ternary.False;
|
||||
}
|
||||
return isRelatedTo(source.constraint, target.constraint, reportErrors);
|
||||
}
|
||||
else {
|
||||
while (true) {
|
||||
let constraint = getConstraintOfTypeParameter(source);
|
||||
if (constraint === target) return Ternary.True;
|
||||
if (!(constraint && constraint.flags & TypeFlags.TypeParameter)) break;
|
||||
source = <TypeParameter>constraint;
|
||||
}
|
||||
function typeParameterIdenticalTo(source: TypeParameter, target: TypeParameter): Ternary {
|
||||
if (source.symbol.name !== target.symbol.name) {
|
||||
return Ternary.False;
|
||||
}
|
||||
// covers case when both type parameters does not have constraint (both equal to noConstraintType)
|
||||
if (source.constraint === target.constraint) {
|
||||
return Ternary.True;
|
||||
}
|
||||
if (source.constraint === noConstraintType || target.constraint === noConstraintType) {
|
||||
return Ternary.False;
|
||||
}
|
||||
return isIdenticalTo(source.constraint, target.constraint);
|
||||
}
|
||||
|
||||
// Determine if two object types are related by structure. First, check if the result is already available in the global cache.
|
||||
|
||||
Reference in New Issue
Block a user