From 1718399e77c2ffd437393610b8bac250b60aec46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 18 Oct 2022 10:11:18 +0200 Subject: [PATCH] Fix an issue with types possibly being related to discriminated type despite of incompatible variance --- src/compiler/checker.ts | 22 +++++ ...tedUnionWithFailedVarianceCheck.errors.txt | 57 ++++++++++++ ...inatedUnionWithFailedVarianceCheck.symbols | 92 +++++++++++++++++++ ...iminatedUnionWithFailedVarianceCheck.types | 63 +++++++++++++ ...scriminatedUnionWithFailedVarianceCheck.ts | 32 +++++++ 5 files changed, 266 insertions(+) create mode 100644 tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.errors.txt create mode 100644 tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.symbols create mode 100644 tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.types create mode 100644 tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9d4e0a8341b9d..810da586cc75c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20367,6 +20367,28 @@ namespace ts { // Compare the remaining non-discriminant properties of each match. let result = Ternary.True; for (const type of matchingTypes) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(type) & ObjectFlags.Reference && (source as TypeReference).target === (type as TypeReference).target && + !isTupleType(source) && !(isMarkerType(source) || isMarkerType(type))) { + // When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType, + // and an empty array literal wouldn't be assignable to a `never[]` without this check. + if (isEmptyArrayLiteralType(source)) { + return Ternary.True; + } + // We have type references to the same generic type, and the type references are not marker + // type references (which are intended by be compared structurally). Obtain the variance + // information for the type parameters and relate the type arguments accordingly. + const variances = getVariances((source as TypeReference).target); + // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This + // effectively means we measure variance only from type parameter occurrences that aren't nested in + // recursive instantiations of the generic type. + if (variances === emptyArray) { + return Ternary.Unknown; + } + if (!typeArgumentsRelatedTo(getTypeArguments(source as TypeReference), getTypeArguments(type as TypeReference), variances, /* reportErrors */ false, IntersectionState.None)) { + return Ternary.False; + } + } + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); diff --git a/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.errors.txt b/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.errors.txt new file mode 100644 index 0000000000000..b0a0ec3b0eaad --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.errors.txt @@ -0,0 +1,57 @@ +tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts(20,3): error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne | ResultTwo'. + Type 'ResultOne' is not assignable to type 'ResultOne'. + Type 'StringType' is not assignable to type 'G'. + 'StringType' is assignable to the constraint of type 'G', but 'G' could be instantiated with a different subtype of constraint 'UnknownType'. +tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts(26,11): error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne'. + Type 'StringType' is not assignable to type 'G'. + 'StringType' is assignable to the constraint of type 'G', but 'G' could be instantiated with a different subtype of constraint 'UnknownType'. +tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts(27,11): error TS2741: Property 'other' is missing in type 'ResultOne' but required in type 'ResultTwo'. +tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts(28,11): error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne | ResultTwo'. + Type 'ResultOne' is not assignable to type 'ResultOne'. + + +==== tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts (4 errors) ==== + interface StringType { prop: string } + interface UnknownType { prop: unknown } + interface ResultOne { + type: "one"; + value: G["prop"]; + } + interface ResultTwo { + type: "two"; + other: G["prop"]; + } + + // repro #51180 + + function callback(): ResultOne | ResultTwo { + const dt: ResultOne = { + type: "one", + value: "abc", + }; + + return dt; // error + ~~~~~~~~~~ +!!! error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne | ResultTwo'. +!!! error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne'. +!!! error TS2322: Type 'StringType' is not assignable to type 'G'. +!!! error TS2322: 'StringType' is assignable to the constraint of type 'G', but 'G' could be instantiated with a different subtype of constraint 'UnknownType'. + } + + // repro #51180#issuecomment-1279445430 + + function callback2(s: ResultOne) { + const a1: ResultOne = s; // error + ~~ +!!! error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne'. +!!! error TS2322: Type 'StringType' is not assignable to type 'G'. +!!! error TS2322: 'StringType' is assignable to the constraint of type 'G', but 'G' could be instantiated with a different subtype of constraint 'UnknownType'. + const a2: ResultTwo = s; // error + ~~ +!!! error TS2741: Property 'other' is missing in type 'ResultOne' but required in type 'ResultTwo'. +!!! related TS2728 tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts:9:3: 'other' is declared here. + const m: ResultOne | ResultTwo = s; // error + ~ +!!! error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne | ResultTwo'. +!!! error TS2322: Type 'ResultOne' is not assignable to type 'ResultOne'. + } \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.symbols b/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.symbols new file mode 100644 index 0000000000000..55dd62f4c5228 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.symbols @@ -0,0 +1,92 @@ +=== tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts === +interface StringType { prop: string } +>StringType : Symbol(StringType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 0)) +>prop : Symbol(StringType.prop, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 22)) + +interface UnknownType { prop: unknown } +>UnknownType : Symbol(UnknownType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 37)) +>prop : Symbol(UnknownType.prop, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 23)) + +interface ResultOne { +>ResultOne : Symbol(ResultOne, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 39)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 2, 20)) +>UnknownType : Symbol(UnknownType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 37)) + + type: "one"; +>type : Symbol(ResultOne.type, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 2, 44)) + + value: G["prop"]; +>value : Symbol(ResultOne.value, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 3, 14)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 2, 20)) +} +interface ResultTwo { +>ResultTwo : Symbol(ResultTwo, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 5, 1)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 6, 20)) +>UnknownType : Symbol(UnknownType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 37)) + + type: "two"; +>type : Symbol(ResultTwo.type, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 6, 44)) + + other: G["prop"]; +>other : Symbol(ResultTwo.other, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 7, 14)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 6, 20)) +} + +// repro #51180 + +function callback(): ResultOne | ResultTwo { +>callback : Symbol(callback, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 9, 1)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 13, 18)) +>UnknownType : Symbol(UnknownType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 37)) +>ResultOne : Symbol(ResultOne, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 39)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 13, 18)) +>ResultTwo : Symbol(ResultTwo, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 5, 1)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 13, 18)) + + const dt: ResultOne = { +>dt : Symbol(dt, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 14, 7)) +>ResultOne : Symbol(ResultOne, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 39)) +>StringType : Symbol(StringType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 0)) + + type: "one", +>type : Symbol(type, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 14, 37)) + + value: "abc", +>value : Symbol(value, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 15, 16)) + + }; + + return dt; // error +>dt : Symbol(dt, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 14, 7)) +} + +// repro #51180#issuecomment-1279445430 + +function callback2(s: ResultOne) { +>callback2 : Symbol(callback2, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 20, 1)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 19)) +>UnknownType : Symbol(UnknownType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 37)) +>s : Symbol(s, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 42)) +>ResultOne : Symbol(ResultOne, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 39)) +>StringType : Symbol(StringType, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 0, 0)) + + const a1: ResultOne = s; // error +>a1 : Symbol(a1, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 25, 9)) +>ResultOne : Symbol(ResultOne, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 39)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 19)) +>s : Symbol(s, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 42)) + + const a2: ResultTwo = s; // error +>a2 : Symbol(a2, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 26, 9)) +>ResultTwo : Symbol(ResultTwo, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 5, 1)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 19)) +>s : Symbol(s, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 42)) + + const m: ResultOne | ResultTwo = s; // error +>m : Symbol(m, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 27, 9)) +>ResultOne : Symbol(ResultOne, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 1, 39)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 19)) +>ResultTwo : Symbol(ResultTwo, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 5, 1)) +>G : Symbol(G, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 19)) +>s : Symbol(s, Decl(discriminatedUnionWithFailedVarianceCheck.ts, 24, 42)) +} diff --git a/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.types b/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.types new file mode 100644 index 0000000000000..92367d8fbf6db --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionWithFailedVarianceCheck.types @@ -0,0 +1,63 @@ +=== tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts === +interface StringType { prop: string } +>prop : string + +interface UnknownType { prop: unknown } +>prop : unknown + +interface ResultOne { + type: "one"; +>type : "one" + + value: G["prop"]; +>value : G["prop"] +} +interface ResultTwo { + type: "two"; +>type : "two" + + other: G["prop"]; +>other : G["prop"] +} + +// repro #51180 + +function callback(): ResultOne | ResultTwo { +>callback : () => ResultOne | ResultTwo + + const dt: ResultOne = { +>dt : ResultOne +>{ type: "one", value: "abc", } : { type: "one"; value: string; } + + type: "one", +>type : "one" +>"one" : "one" + + value: "abc", +>value : string +>"abc" : "abc" + + }; + + return dt; // error +>dt : ResultOne +} + +// repro #51180#issuecomment-1279445430 + +function callback2(s: ResultOne) { +>callback2 : (s: ResultOne) => void +>s : ResultOne + + const a1: ResultOne = s; // error +>a1 : ResultOne +>s : ResultOne + + const a2: ResultTwo = s; // error +>a2 : ResultTwo +>s : ResultOne + + const m: ResultOne | ResultTwo = s; // error +>m : ResultOne | ResultTwo +>s : ResultOne +} diff --git a/tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts b/tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts new file mode 100644 index 0000000000000..782f1f66f8f09 --- /dev/null +++ b/tests/cases/compiler/discriminatedUnionWithFailedVarianceCheck.ts @@ -0,0 +1,32 @@ +// @strict: true +// @noEmit: true + +interface StringType { prop: string } +interface UnknownType { prop: unknown } +interface ResultOne { + type: "one"; + value: G["prop"]; +} +interface ResultTwo { + type: "two"; + other: G["prop"]; +} + +// repro #51180 + +function callback(): ResultOne | ResultTwo { + const dt: ResultOne = { + type: "one", + value: "abc", + }; + + return dt; // error +} + +// repro #51180#issuecomment-1279445430 + +function callback2(s: ResultOne) { + const a1: ResultOne = s; // error + const a2: ResultTwo = s; // error + const m: ResultOne | ResultTwo = s; // error +} \ No newline at end of file