Skip to content

Commit

Permalink
Add isIntersectionConstituent to relation key
Browse files Browse the repository at this point in the history
isIntersectionConstituent controls whether relation checking performs
excess property and common property checks. It is possible to fail a
relation check with excess property checks turned on, cache the result,
and then skip a relation check with excess property checks that would
have succeeded. #33133 provides an example of such a program.

Fixes #33133 the right way, so I reverted the fix at #33213
Fixes #34762 (by reverting #33213)
Fixes #33944 -- I added the test from #34646
  • Loading branch information
sandersn committed Oct 28, 2019
1 parent 05cbe5a commit 2e0b451
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 45 deletions.
19 changes: 10 additions & 9 deletions src/compiler/checker.ts
Expand Up @@ -14161,7 +14161,7 @@ namespace ts {
return true;
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
const related = relation.get(getRelationKey(source, target, relation));
const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation));
if (related !== undefined) {
return !!(related & RelationComparisonResult.Succeeded);
}
Expand Down Expand Up @@ -14567,7 +14567,7 @@ namespace ts {
// and we need to handle "each" relations before "some" relations for the same kind of type.
if (source.flags & TypeFlags.Union) {
result = relation === comparableRelation ?
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), isIntersectionConstituent) :
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)) :
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
}
else {
Expand Down Expand Up @@ -14605,7 +14605,7 @@ namespace ts {
//
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
// breaking the intersection apart.
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, /*isIntersectionConstituent*/ true);
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false);
}
if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) {
if (result = recursiveTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent)) {
Expand Down Expand Up @@ -14899,14 +14899,14 @@ namespace ts {
return result;
}

function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary {
function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary {
const sourceTypes = source.types;
if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) {
return Ternary.True;
}
const len = sourceTypes.length;
for (let i = 0; i < len; i++) {
const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1, /*headMessage*/ undefined, isIntersectionConstituent);
const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1);
if (related) {
return related;
}
Expand Down Expand Up @@ -14993,7 +14993,7 @@ namespace ts {
if (overflow) {
return Ternary.False;
}
const id = getRelationKey(source, target, relation);
const id = getRelationKey(source, target, isIntersectionConstituent, relation);
const entry = relation.get(id);
if (entry !== undefined) {
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
Expand Down Expand Up @@ -16207,17 +16207,18 @@ namespace ts {
* To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
* For other cases, the types ids are used.
*/
function getRelationKey(source: Type, target: Type, relation: Map<RelationComparisonResult>) {
function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, relation: Map<RelationComparisonResult>) {
if (relation === identityRelation && source.id > target.id) {
const temp = source;
source = target;
target = temp;
}
const intersection = isIntersectionConstituent ? "&" : "";
if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
const typeParameters: Type[] = [];
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters);
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters) + intersection;
}
return source.id + "," + target.id;
return source.id + "," + target.id + intersection;
}

// Invoke the callback for each underlying property symbol of the given symbol and return the first
Expand Down
26 changes: 26 additions & 0 deletions tests/baselines/reference/commonTypeIntersection.errors.txt
@@ -0,0 +1,26 @@
tests/cases/conformance/types/intersection/commonTypeIntersection.ts(2,5): error TS2322: Type '{ __typename?: "TypeTwo"; } & { a: boolean; }' is not assignable to type '{ __typename?: "TypeOne"; } & { a: boolean; }'.
Type '{ __typename?: "TypeTwo"; } & { a: boolean; }' is not assignable to type '{ __typename?: "TypeOne"; }'.
Types of property '__typename' are incompatible.
Type '"TypeTwo"' is not assignable to type '"TypeOne"'.
tests/cases/conformance/types/intersection/commonTypeIntersection.ts(4,5): error TS2322: Type '{ __typename?: "TypeTwo"; } & string' is not assignable to type '{ __typename?: "TypeOne"; } & string'.
Type '{ __typename?: "TypeTwo"; } & string' is not assignable to type '{ __typename?: "TypeOne"; }'.
Types of property '__typename' are incompatible.
Type '"TypeTwo"' is not assignable to type '"TypeOne"'.


==== tests/cases/conformance/types/intersection/commonTypeIntersection.ts (2 errors) ====
declare let x1: { __typename?: 'TypeTwo' } & { a: boolean };
let y1: { __typename?: 'TypeOne' } & { a: boolean} = x1; // No error!
~~
!!! error TS2322: Type '{ __typename?: "TypeTwo"; } & { a: boolean; }' is not assignable to type '{ __typename?: "TypeOne"; } & { a: boolean; }'.
!!! error TS2322: Type '{ __typename?: "TypeTwo"; } & { a: boolean; }' is not assignable to type '{ __typename?: "TypeOne"; }'.
!!! error TS2322: Types of property '__typename' are incompatible.
!!! error TS2322: Type '"TypeTwo"' is not assignable to type '"TypeOne"'.
declare let x2: { __typename?: 'TypeTwo' } & string;
let y2: { __typename?: 'TypeOne' } & string = x2; // No error!
~~
!!! error TS2322: Type '{ __typename?: "TypeTwo"; } & string' is not assignable to type '{ __typename?: "TypeOne"; } & string'.
!!! error TS2322: Type '{ __typename?: "TypeTwo"; } & string' is not assignable to type '{ __typename?: "TypeOne"; }'.
!!! error TS2322: Types of property '__typename' are incompatible.
!!! error TS2322: Type '"TypeTwo"' is not assignable to type '"TypeOne"'.

10 changes: 10 additions & 0 deletions tests/baselines/reference/commonTypeIntersection.js
@@ -0,0 +1,10 @@
//// [commonTypeIntersection.ts]
declare let x1: { __typename?: 'TypeTwo' } & { a: boolean };
let y1: { __typename?: 'TypeOne' } & { a: boolean} = x1; // No error!
declare let x2: { __typename?: 'TypeTwo' } & string;
let y2: { __typename?: 'TypeOne' } & string = x2; // No error!


//// [commonTypeIntersection.js]
var y1 = x1; // No error!
var y2 = x2; // No error!
21 changes: 21 additions & 0 deletions tests/baselines/reference/commonTypeIntersection.symbols
@@ -0,0 +1,21 @@
=== tests/cases/conformance/types/intersection/commonTypeIntersection.ts ===
declare let x1: { __typename?: 'TypeTwo' } & { a: boolean };
>x1 : Symbol(x1, Decl(commonTypeIntersection.ts, 0, 11))
>__typename : Symbol(__typename, Decl(commonTypeIntersection.ts, 0, 17))
>a : Symbol(a, Decl(commonTypeIntersection.ts, 0, 46))

let y1: { __typename?: 'TypeOne' } & { a: boolean} = x1; // No error!
>y1 : Symbol(y1, Decl(commonTypeIntersection.ts, 1, 3))
>__typename : Symbol(__typename, Decl(commonTypeIntersection.ts, 1, 9))
>a : Symbol(a, Decl(commonTypeIntersection.ts, 1, 38))
>x1 : Symbol(x1, Decl(commonTypeIntersection.ts, 0, 11))

declare let x2: { __typename?: 'TypeTwo' } & string;
>x2 : Symbol(x2, Decl(commonTypeIntersection.ts, 2, 11))
>__typename : Symbol(__typename, Decl(commonTypeIntersection.ts, 2, 17))

let y2: { __typename?: 'TypeOne' } & string = x2; // No error!
>y2 : Symbol(y2, Decl(commonTypeIntersection.ts, 3, 3))
>__typename : Symbol(__typename, Decl(commonTypeIntersection.ts, 3, 9))
>x2 : Symbol(x2, Decl(commonTypeIntersection.ts, 2, 11))

21 changes: 21 additions & 0 deletions tests/baselines/reference/commonTypeIntersection.types
@@ -0,0 +1,21 @@
=== tests/cases/conformance/types/intersection/commonTypeIntersection.ts ===
declare let x1: { __typename?: 'TypeTwo' } & { a: boolean };
>x1 : { __typename?: "TypeTwo"; } & { a: boolean; }
>__typename : "TypeTwo"
>a : boolean

let y1: { __typename?: 'TypeOne' } & { a: boolean} = x1; // No error!
>y1 : { __typename?: "TypeOne"; } & { a: boolean; }
>__typename : "TypeOne"
>a : boolean
>x1 : { __typename?: "TypeTwo"; } & { a: boolean; }

declare let x2: { __typename?: 'TypeTwo' } & string;
>x2 : { __typename?: "TypeTwo"; } & string
>__typename : "TypeTwo"

let y2: { __typename?: 'TypeOne' } & string = x2; // No error!
>y2 : { __typename?: "TypeOne"; } & string
>__typename : "TypeOne"
>x2 : { __typename?: "TypeTwo"; } & string

Expand Up @@ -5,20 +5,23 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t
Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"] & ChannelOfType<T, EmailChannel>["type"]'.
Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"] & ChannelOfType<T, EmailChannel>["type"]'.
Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type 'T' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type 'T & "text"'.
Type '"text" | "email"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type '"text"'.
Type '"text" | "email"' is not assignable to type '"text"'.
Type '"email"' is not assignable to type '"text"'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type 'T & "text"'.
Type '"text" | "email"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type '"text"'.
Type '"text" | "email"' is not assignable to type '"text"'.
Type '"email"' is not assignable to type '"text"'.


==== tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.ts (1 errors) ====
Expand Down Expand Up @@ -63,20 +66,23 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"] & ChannelOfType<T, EmailChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"] & ChannelOfType<T, EmailChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type 'T' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type '"text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type '"text"'.
!!! error TS2322: Type '"email"' is not assignable to type '"text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type '"text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type '"text"'.
!!! error TS2322: Type '"email"' is not assignable to type '"text"'.
}

const newTextChannel = makeNewChannel('text');
Expand Down

0 comments on commit 2e0b451

Please sign in to comment.