Skip to content

Commit 5cdb0d6

Browse files
committed
Merge pull request microsoft#4112 from Microsoft/nonObjectTypeConstraints
Support non-object type constraints
2 parents c8b4a2e + 0a71f2f commit 5cdb0d6

27 files changed

+367
-103
lines changed

src/compiler/checker.ts

Lines changed: 89 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4668,19 +4668,21 @@ namespace ts {
46684668
let result: Ternary;
46694669
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
46704670
if (source === target) return Ternary.True;
4671-
if (relation !== identityRelation) {
4672-
if (isTypeAny(target)) return Ternary.True;
4673-
if (source === undefinedType) return Ternary.True;
4674-
if (source === nullType && target !== undefinedType) return Ternary.True;
4675-
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
4676-
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
4677-
if (relation === assignableRelation) {
4678-
if (isTypeAny(source)) return Ternary.True;
4679-
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
4680-
}
4671+
if (relation === identityRelation) {
4672+
return isIdenticalTo(source, target);
4673+
}
4674+
4675+
if (isTypeAny(target)) return Ternary.True;
4676+
if (source === undefinedType) return Ternary.True;
4677+
if (source === nullType && target !== undefinedType) return Ternary.True;
4678+
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
4679+
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
4680+
if (relation === assignableRelation) {
4681+
if (isTypeAny(source)) return Ternary.True;
4682+
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
46814683
}
46824684

4683-
if (relation !== identityRelation && source.flags & TypeFlags.FreshObjectLiteral) {
4685+
if (source.flags & TypeFlags.FreshObjectLiteral) {
46844686
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
46854687
if (reportErrors) {
46864688
reportRelationError(headMessage, source, target);
@@ -4696,78 +4698,66 @@ namespace ts {
46964698

46974699
let saveErrorInfo = errorInfo;
46984700

4699-
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
4700-
// We have type references to same target type, see if relationship holds for all type arguments
4701-
if (result = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
4701+
// Note that the "each" checks must precede the "some" checks to produce the correct results
4702+
if (source.flags & TypeFlags.Union) {
4703+
if (result = eachTypeRelatedToType(<UnionType>source, target, reportErrors)) {
47024704
return result;
47034705
}
47044706
}
4705-
else if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) {
4706-
if (result = typeParameterRelatedTo(<TypeParameter>source, <TypeParameter>target, reportErrors)) {
4707+
else if (target.flags & TypeFlags.Intersection) {
4708+
if (result = typeRelatedToEachType(source, <IntersectionType>target, reportErrors)) {
47074709
return result;
47084710
}
47094711
}
4710-
else if (relation !== identityRelation) {
4711-
// Note that the "each" checks must precede the "some" checks to produce the correct results
4712-
if (source.flags & TypeFlags.Union) {
4713-
if (result = eachTypeRelatedToType(<UnionType>source, target, reportErrors)) {
4712+
else {
4713+
// It is necessary to try "some" checks on both sides because there may be nested "each" checks
4714+
// on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or
4715+
// A & B = (A & B) | (C & D).
4716+
if (source.flags & TypeFlags.Intersection) {
4717+
// If target is a union type the following check will report errors so we suppress them here
4718+
if (result = someTypeRelatedToType(<IntersectionType>source, target, reportErrors && !(target.flags & TypeFlags.Union))) {
47144719
return result;
47154720
}
47164721
}
4717-
else if (target.flags & TypeFlags.Intersection) {
4718-
if (result = typeRelatedToEachType(source, <IntersectionType>target, reportErrors)) {
4722+
if (target.flags & TypeFlags.Union) {
4723+
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors)) {
47194724
return result;
47204725
}
47214726
}
4722-
else {
4723-
// It is necessary to try "each" checks on both sides because there may be nested "some" checks
4724-
// on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or
4725-
// A & B = (A & B) | (C & D).
4726-
if (source.flags & TypeFlags.Intersection) {
4727-
// If target is a union type the following check will report errors so we suppress them here
4728-
if (result = someTypeRelatedToType(<IntersectionType>source, target, reportErrors && !(target.flags & TypeFlags.Union))) {
4729-
return result;
4730-
}
4731-
}
4732-
if (target.flags & TypeFlags.Union) {
4733-
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors)) {
4734-
return result;
4735-
}
4736-
}
4737-
}
4738-
}
4739-
else {
4740-
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
4741-
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
4742-
if (result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target)) {
4743-
if (result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source)) {
4744-
return result;
4745-
}
4746-
}
4747-
}
47484727
}
47494728

4750-
// Even if relationship doesn't hold for unions, type parameters, or generic type references,
4751-
// it may hold in a structural comparison.
4752-
// Report structural errors only if we haven't reported any errors yet
4753-
let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo;
4754-
// Identity relation does not use apparent type
4755-
let sourceOrApparentType = relation === identityRelation ? source : getApparentType(source);
4756-
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
4757-
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
4758-
// relates to X. Thus, we include intersection types on the source side here.
4759-
if (sourceOrApparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) {
4760-
if (result = objectTypeRelatedTo(sourceOrApparentType, <ObjectType>target, reportStructuralErrors)) {
4729+
if (source.flags & TypeFlags.TypeParameter) {
4730+
let constraint = getConstraintOfTypeParameter(<TypeParameter>source);
4731+
if (!constraint || constraint.flags & TypeFlags.Any) {
4732+
constraint = emptyObjectType;
4733+
}
4734+
// Report constraint errors only if the constraint is not the empty object type
4735+
let reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
4736+
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
47614737
errorInfo = saveErrorInfo;
47624738
return result;
47634739
}
47644740
}
4765-
else if (source.flags & TypeFlags.TypeParameter && sourceOrApparentType.flags & TypeFlags.UnionOrIntersection) {
4766-
// We clear the errors first because the following check often gives a better error than
4767-
// the union or intersection comparison above if it is applicable.
4768-
errorInfo = saveErrorInfo;
4769-
if (result = isRelatedTo(sourceOrApparentType, target, reportErrors)) {
4770-
return result;
4741+
else {
4742+
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
4743+
// We have type references to same target type, see if relationship holds for all type arguments
4744+
if (result = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
4745+
return result;
4746+
}
4747+
}
4748+
// Even if relationship doesn't hold for unions, intersections, or generic type references,
4749+
// it may hold in a structural comparison.
4750+
let apparentType = getApparentType(source);
4751+
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
4752+
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
4753+
// relates to X. Thus, we include intersection types on the source side here.
4754+
if (apparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) {
4755+
// Report structural errors only if we haven't reported any errors yet
4756+
let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo;
4757+
if (result = objectTypeRelatedTo(apparentType, <ObjectType>target, reportStructuralErrors)) {
4758+
errorInfo = saveErrorInfo;
4759+
return result;
4760+
}
47714761
}
47724762
}
47734763

@@ -4777,6 +4767,31 @@ namespace ts {
47774767
return Ternary.False;
47784768
}
47794769

4770+
function isIdenticalTo(source: Type, target: Type): Ternary {
4771+
let result: Ternary;
4772+
if (source.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType) {
4773+
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
4774+
// We have type references to same target type, see if all type arguments are identical
4775+
if (result = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, /*reportErrors*/ false)) {
4776+
return result;
4777+
}
4778+
}
4779+
return objectTypeRelatedTo(<ObjectType>source, <ObjectType>target, /*reportErrors*/ false);
4780+
}
4781+
if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) {
4782+
return typeParameterIdenticalTo(<TypeParameter>source, <TypeParameter>target);
4783+
}
4784+
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union ||
4785+
source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Intersection) {
4786+
if (result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target)) {
4787+
if (result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source)) {
4788+
return result;
4789+
}
4790+
}
4791+
}
4792+
return Ternary.False;
4793+
}
4794+
47804795
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
47814796
for (let prop of getPropertiesOfObjectType(source)) {
47824797
if (!isKnownProperty(target, prop.name)) {
@@ -4861,29 +4876,18 @@ namespace ts {
48614876
return result;
48624877
}
48634878

4864-
function typeParameterRelatedTo(source: TypeParameter, target: TypeParameter, reportErrors: boolean): Ternary {
4865-
if (relation === identityRelation) {
4866-
if (source.symbol.name !== target.symbol.name) {
4867-
return Ternary.False;
4868-
}
4869-
// covers case when both type parameters does not have constraint (both equal to noConstraintType)
4870-
if (source.constraint === target.constraint) {
4871-
return Ternary.True;
4872-
}
4873-
if (source.constraint === noConstraintType || target.constraint === noConstraintType) {
4874-
return Ternary.False;
4875-
}
4876-
return isRelatedTo(source.constraint, target.constraint, reportErrors);
4879+
function typeParameterIdenticalTo(source: TypeParameter, target: TypeParameter): Ternary {
4880+
if (source.symbol.name !== target.symbol.name) {
4881+
return Ternary.False;
48774882
}
4878-
else {
4879-
while (true) {
4880-
let constraint = getConstraintOfTypeParameter(source);
4881-
if (constraint === target) return Ternary.True;
4882-
if (!(constraint && constraint.flags & TypeFlags.TypeParameter)) break;
4883-
source = <TypeParameter>constraint;
4884-
}
4883+
// covers case when both type parameters does not have constraint (both equal to noConstraintType)
4884+
if (source.constraint === target.constraint) {
4885+
return Ternary.True;
4886+
}
4887+
if (source.constraint === noConstraintType || target.constraint === noConstraintType) {
48854888
return Ternary.False;
48864889
}
4890+
return isIdenticalTo(source.constraint, target.constraint);
48874891
}
48884892

48894893
// Determine if two object types are related by structure. First, check if the result is already available in the global cache.

tests/baselines/reference/apparentTypeSupertype.errors.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSupertype.ts(9,7): error TS2415: Class 'Derived<U>' incorrectly extends base class 'Base'.
22
Types of property 'x' are incompatible.
33
Type 'U' is not assignable to type 'string'.
4+
Type 'String' is not assignable to type 'string'.
45

56

67
==== tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSupertype.ts (1 errors) ====
@@ -17,5 +18,6 @@ tests/cases/conformance/types/typeRelationships/apparentType/apparentTypeSuperty
1718
!!! error TS2415: Class 'Derived<U>' incorrectly extends base class 'Base'.
1819
!!! error TS2415: Types of property 'x' are incompatible.
1920
!!! error TS2415: Type 'U' is not assignable to type 'string'.
21+
!!! error TS2415: Type 'String' is not assignable to type 'string'.
2022
x: U;
2123
}

tests/baselines/reference/assignmentCompatWithNumericIndexer.errors.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
1212
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithNumericIndexer.ts(33,9): error TS2322: Type 'A<T>' is not assignable to type '{ [x: number]: Derived; }'.
1313
Index signatures are incompatible.
1414
Type 'T' is not assignable to type 'Derived'.
15+
Type 'Base' is not assignable to type 'Derived'.
1516
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithNumericIndexer.ts(36,9): error TS2322: Type '{ [x: number]: Derived2; }' is not assignable to type 'A<T>'.
1617
Index signatures are incompatible.
1718
Type 'Derived2' is not assignable to type 'T'.
1819
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithNumericIndexer.ts(37,9): error TS2322: Type 'A<T>' is not assignable to type '{ [x: number]: Derived2; }'.
1920
Index signatures are incompatible.
2021
Type 'T' is not assignable to type 'Derived2'.
22+
Type 'Base' is not assignable to type 'Derived2'.
2123

2224

2325
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithNumericIndexer.ts (6 errors) ====
@@ -72,6 +74,7 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
7274
!!! error TS2322: Type 'A<T>' is not assignable to type '{ [x: number]: Derived; }'.
7375
!!! error TS2322: Index signatures are incompatible.
7476
!!! error TS2322: Type 'T' is not assignable to type 'Derived'.
77+
!!! error TS2322: Type 'Base' is not assignable to type 'Derived'.
7578

7679
var b2: { [x: number]: Derived2; }
7780
a = b2; // error
@@ -84,6 +87,7 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
8487
!!! error TS2322: Type 'A<T>' is not assignable to type '{ [x: number]: Derived2; }'.
8588
!!! error TS2322: Index signatures are incompatible.
8689
!!! error TS2322: Type 'T' is not assignable to type 'Derived2'.
90+
!!! error TS2322: Type 'Base' is not assignable to type 'Derived2'.
8791

8892
var b3: { [x: number]: T; }
8993
a = b3; // ok

0 commit comments

Comments
 (0)