diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a0a2fa19f1988..31745ce34437f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5604,6 +5604,11 @@ namespace ts { } } + // We normalize combinations of intersection and union types based on the distributive property of the '&' + // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection + // types with union type constituents into equivalent union types with intersection type constituents and + // effectively ensure that union types are always at the top level in type representations. + // // We do not perform structural deduplication on intersection types. Intersection types are created only by the & // type operator and we can't reduce those because we want to support recursive intersection types. For example, // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. @@ -5613,6 +5618,15 @@ namespace ts { if (types.length === 0) { return emptyObjectType; } + for (let i = 0; i < types.length; i++) { + const type = types[i]; + if (type.flags & TypeFlags.Union) { + // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of + // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. + return getUnionType(map((type).types, t => getIntersectionType(replaceElement(types, i, t))), + /*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments); + } + } const typeSet = [] as TypeSet; addTypesToIntersection(typeSet, types); if (typeSet.containsAny) { @@ -6560,7 +6574,9 @@ namespace ts { const saveErrorInfo = errorInfo; - // Note that these checks are specifically ordered to produce correct results. + // Note that these checks are specifically ordered to produce correct results. In particular, + // we need to deconstruct unions before intersections (because unions are always at the top), + // and we need to handle "each" relations before "some" relations for the same kind of type. if (source.flags & TypeFlags.Union) { if (relation === comparableRelation) { result = someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)); @@ -6568,44 +6584,36 @@ namespace ts { else { result = eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)); } - if (result) { return result; } } - else if (target.flags & TypeFlags.Intersection) { - result = typeRelatedToEachType(source, target as IntersectionType, reportErrors); - - if (result) { + else if (target.flags & TypeFlags.Union) { + if (result = typeRelatedToSomeType(source, target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) { return result; } } - else { - // It is necessary to try these "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) { - // 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(source, target, /*reportErrors*/ false)) { - return result; - } + else if (target.flags & TypeFlags.Intersection) { + if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) { + return result; } - if (target.flags & TypeFlags.Union) { - if (result = typeRelatedToSomeType(source, target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) { - 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(source, target, /*reportErrors*/ false)) { + return result; } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 2f77b3040b36e..884f4c94267f9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -532,6 +532,12 @@ namespace ts { : undefined; } + export function replaceElement(array: T[], index: number, value: T): T[] { + const result = array.slice(0); + result[index] = value; + return result; + } + /** * Performs a binary search, finding the index at which 'value' occurs in 'array'. * If no such index is found, returns the 2's-complement of first index at which diff --git a/tests/baselines/reference/errorMessagesIntersectionTypes04.errors.txt b/tests/baselines/reference/errorMessagesIntersectionTypes04.errors.txt index 7582c68ecec0b..d2e81844da151 100644 --- a/tests/baselines/reference/errorMessagesIntersectionTypes04.errors.txt +++ b/tests/baselines/reference/errorMessagesIntersectionTypes04.errors.txt @@ -1,7 +1,8 @@ tests/cases/compiler/errorMessagesIntersectionTypes04.ts(17,5): error TS2322: Type 'A & B' is not assignable to type 'number'. tests/cases/compiler/errorMessagesIntersectionTypes04.ts(18,5): error TS2322: Type 'A & B' is not assignable to type 'boolean'. tests/cases/compiler/errorMessagesIntersectionTypes04.ts(19,5): error TS2322: Type 'A & B' is not assignable to type 'string'. -tests/cases/compiler/errorMessagesIntersectionTypes04.ts(21,5): error TS2322: Type 'number & boolean' is not assignable to type 'string'. +tests/cases/compiler/errorMessagesIntersectionTypes04.ts(21,5): error TS2322: Type '(number & true) | (number & false)' is not assignable to type 'string'. + Type 'number & true' is not assignable to type 'string'. ==== tests/cases/compiler/errorMessagesIntersectionTypes04.ts (4 errors) ==== @@ -33,5 +34,6 @@ tests/cases/compiler/errorMessagesIntersectionTypes04.ts(21,5): error TS2322: Ty str = num_and_bool; ~~~ -!!! error TS2322: Type 'number & boolean' is not assignable to type 'string'. +!!! error TS2322: Type '(number & true) | (number & false)' is not assignable to type 'string'. +!!! error TS2322: Type 'number & true' is not assignable to type 'string'. } \ No newline at end of file diff --git a/tests/baselines/reference/intersectionAndUnionTypes.errors.txt b/tests/baselines/reference/intersectionAndUnionTypes.errors.txt index 5a04d4f25d382..62ddfd497a3db 100644 --- a/tests/baselines/reference/intersectionAndUnionTypes.errors.txt +++ b/tests/baselines/reference/intersectionAndUnionTypes.errors.txt @@ -30,28 +30,29 @@ tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(29,1): e Type 'A & B' is not assignable to type 'C | D'. Type 'A & B' is not assignable to type 'D'. Property 'd' is missing in type 'A & B'. -tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(31,1): error TS2322: Type 'A & B' is not assignable to type '(A | B) & (C | D)'. - Type 'A & B' is not assignable to type 'C | D'. +tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(31,1): error TS2322: Type 'A & B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. + Type 'A & B' is not assignable to type 'B & D'. Type 'A & B' is not assignable to type 'D'. -tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(32,1): error TS2322: Type 'A | B' is not assignable to type '(A | B) & (C | D)'. - Type 'A' is not assignable to type '(A | B) & (C | D)'. - Type 'A' is not assignable to type 'C | D'. - Type 'A' is not assignable to type 'D'. - Property 'd' is missing in type 'A'. -tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(33,1): error TS2322: Type 'C & D' is not assignable to type '(A | B) & (C | D)'. - Type 'C & D' is not assignable to type 'A | B'. +tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(32,1): error TS2322: Type 'A | B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. + Type 'A' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. + Type 'A' is not assignable to type 'B & D'. + Type 'A' is not assignable to type 'B'. +tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(33,1): error TS2322: Type 'C & D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. + Type 'C & D' is not assignable to type 'B & D'. Type 'C & D' is not assignable to type 'B'. -tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(34,1): error TS2322: Type 'C | D' is not assignable to type '(A | B) & (C | D)'. - Type 'C' is not assignable to type '(A | B) & (C | D)'. - Type 'C' is not assignable to type 'A | B'. +tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(34,1): error TS2322: Type 'C | D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. + Type 'C' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. + Type 'C' is not assignable to type 'B & D'. Type 'C' is not assignable to type 'B'. Property 'b' is missing in type 'C'. -tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(35,1): error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'A & B'. - Type '(A | B) & (C | D)' is not assignable to type 'A'. - Property 'a' is missing in type '(A | B) & (C | D)'. -tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'C & D'. - Type '(A | B) & (C | D)' is not assignable to type 'C'. - Property 'c' is missing in type '(A | B) & (C | D)'. +tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(35,1): error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'A & B'. + Type 'A & C' is not assignable to type 'A & B'. + Type 'A & C' is not assignable to type 'B'. + Property 'b' is missing in type 'A & C'. +tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'C & D'. + Type 'A & C' is not assignable to type 'C & D'. + Type 'A & C' is not assignable to type 'D'. + Property 'd' is missing in type 'A & C'. ==== tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts (14 errors) ==== @@ -127,38 +128,39 @@ tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): e y = anb; ~ -!!! error TS2322: Type 'A & B' is not assignable to type '(A | B) & (C | D)'. -!!! error TS2322: Type 'A & B' is not assignable to type 'C | D'. +!!! error TS2322: Type 'A & B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. +!!! error TS2322: Type 'A & B' is not assignable to type 'B & D'. !!! error TS2322: Type 'A & B' is not assignable to type 'D'. y = aob; ~ -!!! error TS2322: Type 'A | B' is not assignable to type '(A | B) & (C | D)'. -!!! error TS2322: Type 'A' is not assignable to type '(A | B) & (C | D)'. -!!! error TS2322: Type 'A' is not assignable to type 'C | D'. -!!! error TS2322: Type 'A' is not assignable to type 'D'. -!!! error TS2322: Property 'd' is missing in type 'A'. +!!! error TS2322: Type 'A | B' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. +!!! error TS2322: Type 'A' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. +!!! error TS2322: Type 'A' is not assignable to type 'B & D'. +!!! error TS2322: Type 'A' is not assignable to type 'B'. y = cnd; ~ -!!! error TS2322: Type 'C & D' is not assignable to type '(A | B) & (C | D)'. -!!! error TS2322: Type 'C & D' is not assignable to type 'A | B'. +!!! error TS2322: Type 'C & D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. +!!! error TS2322: Type 'C & D' is not assignable to type 'B & D'. !!! error TS2322: Type 'C & D' is not assignable to type 'B'. y = cod; ~ -!!! error TS2322: Type 'C | D' is not assignable to type '(A | B) & (C | D)'. -!!! error TS2322: Type 'C' is not assignable to type '(A | B) & (C | D)'. -!!! error TS2322: Type 'C' is not assignable to type 'A | B'. +!!! error TS2322: Type 'C | D' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. +!!! error TS2322: Type 'C' is not assignable to type '(A & C) | (A & D) | (B & C) | (B & D)'. +!!! error TS2322: Type 'C' is not assignable to type 'B & D'. !!! error TS2322: Type 'C' is not assignable to type 'B'. !!! error TS2322: Property 'b' is missing in type 'C'. anb = y; ~~~ -!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'A & B'. -!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'A'. -!!! error TS2322: Property 'a' is missing in type '(A | B) & (C | D)'. +!!! error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'A & B'. +!!! error TS2322: Type 'A & C' is not assignable to type 'A & B'. +!!! error TS2322: Type 'A & C' is not assignable to type 'B'. +!!! error TS2322: Property 'b' is missing in type 'A & C'. aob = y; // Ok cnd = y; ~~~ -!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'C & D'. -!!! error TS2322: Type '(A | B) & (C | D)' is not assignable to type 'C'. -!!! error TS2322: Property 'c' is missing in type '(A | B) & (C | D)'. +!!! error TS2322: Type '(A & C) | (A & D) | (B & C) | (B & D)' is not assignable to type 'C & D'. +!!! error TS2322: Type 'A & C' is not assignable to type 'C & D'. +!!! error TS2322: Type 'A & C' is not assignable to type 'D'. +!!! error TS2322: Property 'd' is missing in type 'A & C'. cod = y; // Ok \ No newline at end of file diff --git a/tests/baselines/reference/intersectionTypeNormalization.js b/tests/baselines/reference/intersectionTypeNormalization.js new file mode 100644 index 0000000000000..8309b17f19868 --- /dev/null +++ b/tests/baselines/reference/intersectionTypeNormalization.js @@ -0,0 +1,79 @@ +//// [intersectionTypeNormalization.ts] +interface A { a: string } +interface B { b: string } +interface C { c: string } +interface D { d: string } + +// Identical ways of writing the same type +type X1 = (A | B) & (C | D); +type X2 = A & (C | D) | B & (C | D) +type X3 = A & C | A & D | B & C | B & D; + +var x: X1; +var x: X2; +var x: X3; + +interface X { x: string } +interface Y { y: string } + +// Identical ways of writing the same type +type Y1 = (A | X & Y) & (C | D); +type Y2 = A & (C | D) | X & Y & (C | D) +type Y3 = A & C | A & D | X & Y & C | X & Y & D; + +var y: Y1; +var y: Y2; +var y: Y3; + +interface M { m: string } +interface N { n: string } + +// Identical ways of writing the same type +type Z1 = (A | X & (M | N)) & (C | D); +type Z2 = A & (C | D) | X & (M | N) & (C | D) +type Z3 = A & C | A & D | X & (M | N) & C | X & (M | N) & D; +type Z4 = A & C | A & D | X & M & C | X & N & C | X & M & D | X & N & D; + +var z: Z1; +var z: Z2; +var z: Z3; +var z: Z4; + +// Repro from #9919 + +type ToString = { + toString(): string; +} + +type BoxedValue = { kind: 'int', num: number } + | { kind: 'string', str: string } + +type IntersectionFail = BoxedValue & ToString + +type IntersectionInline = { kind: 'int', num: number } & ToString + | { kind: 'string', str: string } & ToString + +function getValueAsString(value: IntersectionFail): string { + if (value.kind === 'int') { + return '' + value.num; + } + return value.str; +} + +//// [intersectionTypeNormalization.js] +var x; +var x; +var x; +var y; +var y; +var y; +var z; +var z; +var z; +var z; +function getValueAsString(value) { + if (value.kind === 'int') { + return '' + value.num; + } + return value.str; +} diff --git a/tests/baselines/reference/intersectionTypeNormalization.symbols b/tests/baselines/reference/intersectionTypeNormalization.symbols new file mode 100644 index 0000000000000..a5a00c7040788 --- /dev/null +++ b/tests/baselines/reference/intersectionTypeNormalization.symbols @@ -0,0 +1,242 @@ +=== tests/cases/compiler/intersectionTypeNormalization.ts === +interface A { a: string } +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>a : Symbol(A.a, Decl(intersectionTypeNormalization.ts, 0, 13)) + +interface B { b: string } +>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 0, 25)) +>b : Symbol(B.b, Decl(intersectionTypeNormalization.ts, 1, 13)) + +interface C { c: string } +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>c : Symbol(C.c, Decl(intersectionTypeNormalization.ts, 2, 13)) + +interface D { d: string } +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>d : Symbol(D.d, Decl(intersectionTypeNormalization.ts, 3, 13)) + +// Identical ways of writing the same type +type X1 = (A | B) & (C | D); +>X1 : Symbol(X1, Decl(intersectionTypeNormalization.ts, 3, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 0, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type X2 = A & (C | D) | B & (C | D) +>X2 : Symbol(X2, Decl(intersectionTypeNormalization.ts, 6, 28)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 0, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type X3 = A & C | A & D | B & C | B & D; +>X3 : Symbol(X3, Decl(intersectionTypeNormalization.ts, 7, 35)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 0, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>B : Symbol(B, Decl(intersectionTypeNormalization.ts, 0, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +var x: X1; +>x : Symbol(x, Decl(intersectionTypeNormalization.ts, 10, 3), Decl(intersectionTypeNormalization.ts, 11, 3), Decl(intersectionTypeNormalization.ts, 12, 3)) +>X1 : Symbol(X1, Decl(intersectionTypeNormalization.ts, 3, 25)) + +var x: X2; +>x : Symbol(x, Decl(intersectionTypeNormalization.ts, 10, 3), Decl(intersectionTypeNormalization.ts, 11, 3), Decl(intersectionTypeNormalization.ts, 12, 3)) +>X2 : Symbol(X2, Decl(intersectionTypeNormalization.ts, 6, 28)) + +var x: X3; +>x : Symbol(x, Decl(intersectionTypeNormalization.ts, 10, 3), Decl(intersectionTypeNormalization.ts, 11, 3), Decl(intersectionTypeNormalization.ts, 12, 3)) +>X3 : Symbol(X3, Decl(intersectionTypeNormalization.ts, 7, 35)) + +interface X { x: string } +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>x : Symbol(X.x, Decl(intersectionTypeNormalization.ts, 14, 13)) + +interface Y { y: string } +>Y : Symbol(Y, Decl(intersectionTypeNormalization.ts, 14, 25)) +>y : Symbol(Y.y, Decl(intersectionTypeNormalization.ts, 15, 13)) + +// Identical ways of writing the same type +type Y1 = (A | X & Y) & (C | D); +>Y1 : Symbol(Y1, Decl(intersectionTypeNormalization.ts, 15, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>Y : Symbol(Y, Decl(intersectionTypeNormalization.ts, 14, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type Y2 = A & (C | D) | X & Y & (C | D) +>Y2 : Symbol(Y2, Decl(intersectionTypeNormalization.ts, 18, 32)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>Y : Symbol(Y, Decl(intersectionTypeNormalization.ts, 14, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type Y3 = A & C | A & D | X & Y & C | X & Y & D; +>Y3 : Symbol(Y3, Decl(intersectionTypeNormalization.ts, 19, 39)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>Y : Symbol(Y, Decl(intersectionTypeNormalization.ts, 14, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>Y : Symbol(Y, Decl(intersectionTypeNormalization.ts, 14, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +var y: Y1; +>y : Symbol(y, Decl(intersectionTypeNormalization.ts, 22, 3), Decl(intersectionTypeNormalization.ts, 23, 3), Decl(intersectionTypeNormalization.ts, 24, 3)) +>Y1 : Symbol(Y1, Decl(intersectionTypeNormalization.ts, 15, 25)) + +var y: Y2; +>y : Symbol(y, Decl(intersectionTypeNormalization.ts, 22, 3), Decl(intersectionTypeNormalization.ts, 23, 3), Decl(intersectionTypeNormalization.ts, 24, 3)) +>Y2 : Symbol(Y2, Decl(intersectionTypeNormalization.ts, 18, 32)) + +var y: Y3; +>y : Symbol(y, Decl(intersectionTypeNormalization.ts, 22, 3), Decl(intersectionTypeNormalization.ts, 23, 3), Decl(intersectionTypeNormalization.ts, 24, 3)) +>Y3 : Symbol(Y3, Decl(intersectionTypeNormalization.ts, 19, 39)) + +interface M { m: string } +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>m : Symbol(M.m, Decl(intersectionTypeNormalization.ts, 26, 13)) + +interface N { n: string } +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>n : Symbol(N.n, Decl(intersectionTypeNormalization.ts, 27, 13)) + +// Identical ways of writing the same type +type Z1 = (A | X & (M | N)) & (C | D); +>Z1 : Symbol(Z1, Decl(intersectionTypeNormalization.ts, 27, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type Z2 = A & (C | D) | X & (M | N) & (C | D) +>Z2 : Symbol(Z2, Decl(intersectionTypeNormalization.ts, 30, 38)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type Z3 = A & C | A & D | X & (M | N) & C | X & (M | N) & D; +>Z3 : Symbol(Z3, Decl(intersectionTypeNormalization.ts, 31, 45)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +type Z4 = A & C | A & D | X & M & C | X & N & C | X & M & D | X & N & D; +>Z4 : Symbol(Z4, Decl(intersectionTypeNormalization.ts, 32, 60)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>A : Symbol(A, Decl(intersectionTypeNormalization.ts, 0, 0)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>C : Symbol(C, Decl(intersectionTypeNormalization.ts, 1, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>M : Symbol(M, Decl(intersectionTypeNormalization.ts, 24, 10)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) +>X : Symbol(X, Decl(intersectionTypeNormalization.ts, 12, 10)) +>N : Symbol(N, Decl(intersectionTypeNormalization.ts, 26, 25)) +>D : Symbol(D, Decl(intersectionTypeNormalization.ts, 2, 25)) + +var z: Z1; +>z : Symbol(z, Decl(intersectionTypeNormalization.ts, 35, 3), Decl(intersectionTypeNormalization.ts, 36, 3), Decl(intersectionTypeNormalization.ts, 37, 3), Decl(intersectionTypeNormalization.ts, 38, 3)) +>Z1 : Symbol(Z1, Decl(intersectionTypeNormalization.ts, 27, 25)) + +var z: Z2; +>z : Symbol(z, Decl(intersectionTypeNormalization.ts, 35, 3), Decl(intersectionTypeNormalization.ts, 36, 3), Decl(intersectionTypeNormalization.ts, 37, 3), Decl(intersectionTypeNormalization.ts, 38, 3)) +>Z2 : Symbol(Z2, Decl(intersectionTypeNormalization.ts, 30, 38)) + +var z: Z3; +>z : Symbol(z, Decl(intersectionTypeNormalization.ts, 35, 3), Decl(intersectionTypeNormalization.ts, 36, 3), Decl(intersectionTypeNormalization.ts, 37, 3), Decl(intersectionTypeNormalization.ts, 38, 3)) +>Z3 : Symbol(Z3, Decl(intersectionTypeNormalization.ts, 31, 45)) + +var z: Z4; +>z : Symbol(z, Decl(intersectionTypeNormalization.ts, 35, 3), Decl(intersectionTypeNormalization.ts, 36, 3), Decl(intersectionTypeNormalization.ts, 37, 3), Decl(intersectionTypeNormalization.ts, 38, 3)) +>Z4 : Symbol(Z4, Decl(intersectionTypeNormalization.ts, 32, 60)) + +// Repro from #9919 + +type ToString = { +>ToString : Symbol(ToString, Decl(intersectionTypeNormalization.ts, 38, 10)) + + toString(): string; +>toString : Symbol(toString, Decl(intersectionTypeNormalization.ts, 42, 17)) +} + +type BoxedValue = { kind: 'int', num: number } +>BoxedValue : Symbol(BoxedValue, Decl(intersectionTypeNormalization.ts, 44, 1)) +>kind : Symbol(kind, Decl(intersectionTypeNormalization.ts, 46, 19)) +>num : Symbol(num, Decl(intersectionTypeNormalization.ts, 46, 32)) + + | { kind: 'string', str: string } +>kind : Symbol(kind, Decl(intersectionTypeNormalization.ts, 47, 19)) +>str : Symbol(str, Decl(intersectionTypeNormalization.ts, 47, 35)) + +type IntersectionFail = BoxedValue & ToString +>IntersectionFail : Symbol(IntersectionFail, Decl(intersectionTypeNormalization.ts, 47, 49)) +>BoxedValue : Symbol(BoxedValue, Decl(intersectionTypeNormalization.ts, 44, 1)) +>ToString : Symbol(ToString, Decl(intersectionTypeNormalization.ts, 38, 10)) + +type IntersectionInline = { kind: 'int', num: number } & ToString +>IntersectionInline : Symbol(IntersectionInline, Decl(intersectionTypeNormalization.ts, 49, 45)) +>kind : Symbol(kind, Decl(intersectionTypeNormalization.ts, 51, 27)) +>num : Symbol(num, Decl(intersectionTypeNormalization.ts, 51, 40)) +>ToString : Symbol(ToString, Decl(intersectionTypeNormalization.ts, 38, 10)) + + | { kind: 'string', str: string } & ToString +>kind : Symbol(kind, Decl(intersectionTypeNormalization.ts, 52, 27)) +>str : Symbol(str, Decl(intersectionTypeNormalization.ts, 52, 43)) +>ToString : Symbol(ToString, Decl(intersectionTypeNormalization.ts, 38, 10)) + +function getValueAsString(value: IntersectionFail): string { +>getValueAsString : Symbol(getValueAsString, Decl(intersectionTypeNormalization.ts, 52, 68)) +>value : Symbol(value, Decl(intersectionTypeNormalization.ts, 54, 26)) +>IntersectionFail : Symbol(IntersectionFail, Decl(intersectionTypeNormalization.ts, 47, 49)) + + if (value.kind === 'int') { +>value.kind : Symbol(kind, Decl(intersectionTypeNormalization.ts, 46, 19), Decl(intersectionTypeNormalization.ts, 47, 19)) +>value : Symbol(value, Decl(intersectionTypeNormalization.ts, 54, 26)) +>kind : Symbol(kind, Decl(intersectionTypeNormalization.ts, 46, 19), Decl(intersectionTypeNormalization.ts, 47, 19)) + + return '' + value.num; +>value.num : Symbol(num, Decl(intersectionTypeNormalization.ts, 46, 32)) +>value : Symbol(value, Decl(intersectionTypeNormalization.ts, 54, 26)) +>num : Symbol(num, Decl(intersectionTypeNormalization.ts, 46, 32)) + } + return value.str; +>value.str : Symbol(str, Decl(intersectionTypeNormalization.ts, 47, 35)) +>value : Symbol(value, Decl(intersectionTypeNormalization.ts, 54, 26)) +>str : Symbol(str, Decl(intersectionTypeNormalization.ts, 47, 35)) +} diff --git a/tests/baselines/reference/intersectionTypeNormalization.types b/tests/baselines/reference/intersectionTypeNormalization.types new file mode 100644 index 0000000000000..10ef50ae1b45a --- /dev/null +++ b/tests/baselines/reference/intersectionTypeNormalization.types @@ -0,0 +1,246 @@ +=== tests/cases/compiler/intersectionTypeNormalization.ts === +interface A { a: string } +>A : A +>a : string + +interface B { b: string } +>B : B +>b : string + +interface C { c: string } +>C : C +>c : string + +interface D { d: string } +>D : D +>d : string + +// Identical ways of writing the same type +type X1 = (A | B) & (C | D); +>X1 : X1 +>A : A +>B : B +>C : C +>D : D + +type X2 = A & (C | D) | B & (C | D) +>X2 : X1 +>A : A +>C : C +>D : D +>B : B +>C : C +>D : D + +type X3 = A & C | A & D | B & C | B & D; +>X3 : X1 +>A : A +>C : C +>A : A +>D : D +>B : B +>C : C +>B : B +>D : D + +var x: X1; +>x : X1 +>X1 : X1 + +var x: X2; +>x : X1 +>X2 : X1 + +var x: X3; +>x : X1 +>X3 : X1 + +interface X { x: string } +>X : X +>x : string + +interface Y { y: string } +>Y : Y +>y : string + +// Identical ways of writing the same type +type Y1 = (A | X & Y) & (C | D); +>Y1 : Y1 +>A : A +>X : X +>Y : Y +>C : C +>D : D + +type Y2 = A & (C | D) | X & Y & (C | D) +>Y2 : Y1 +>A : A +>C : C +>D : D +>X : X +>Y : Y +>C : C +>D : D + +type Y3 = A & C | A & D | X & Y & C | X & Y & D; +>Y3 : Y1 +>A : A +>C : C +>A : A +>D : D +>X : X +>Y : Y +>C : C +>X : X +>Y : Y +>D : D + +var y: Y1; +>y : Y1 +>Y1 : Y1 + +var y: Y2; +>y : Y1 +>Y2 : Y1 + +var y: Y3; +>y : Y1 +>Y3 : Y1 + +interface M { m: string } +>M : M +>m : string + +interface N { n: string } +>N : N +>n : string + +// Identical ways of writing the same type +type Z1 = (A | X & (M | N)) & (C | D); +>Z1 : Z1 +>A : A +>X : X +>M : M +>N : N +>C : C +>D : D + +type Z2 = A & (C | D) | X & (M | N) & (C | D) +>Z2 : Z1 +>A : A +>C : C +>D : D +>X : X +>M : M +>N : N +>C : C +>D : D + +type Z3 = A & C | A & D | X & (M | N) & C | X & (M | N) & D; +>Z3 : Z1 +>A : A +>C : C +>A : A +>D : D +>X : X +>M : M +>N : N +>C : C +>X : X +>M : M +>N : N +>D : D + +type Z4 = A & C | A & D | X & M & C | X & N & C | X & M & D | X & N & D; +>Z4 : Z1 +>A : A +>C : C +>A : A +>D : D +>X : X +>M : M +>C : C +>X : X +>N : N +>C : C +>X : X +>M : M +>D : D +>X : X +>N : N +>D : D + +var z: Z1; +>z : Z1 +>Z1 : Z1 + +var z: Z2; +>z : Z1 +>Z2 : Z1 + +var z: Z3; +>z : Z1 +>Z3 : Z1 + +var z: Z4; +>z : Z1 +>Z4 : Z1 + +// Repro from #9919 + +type ToString = { +>ToString : { toString(): string; } + + toString(): string; +>toString : () => string +} + +type BoxedValue = { kind: 'int', num: number } +>BoxedValue : BoxedValue +>kind : "int" +>num : number + + | { kind: 'string', str: string } +>kind : "string" +>str : string + +type IntersectionFail = BoxedValue & ToString +>IntersectionFail : IntersectionFail +>BoxedValue : BoxedValue +>ToString : { toString(): string; } + +type IntersectionInline = { kind: 'int', num: number } & ToString +>IntersectionInline : IntersectionInline +>kind : "int" +>num : number +>ToString : { toString(): string; } + + | { kind: 'string', str: string } & ToString +>kind : "string" +>str : string +>ToString : { toString(): string; } + +function getValueAsString(value: IntersectionFail): string { +>getValueAsString : (value: IntersectionFail) => string +>value : IntersectionFail +>IntersectionFail : IntersectionFail + + if (value.kind === 'int') { +>value.kind === 'int' : boolean +>value.kind : "int" | "string" +>value : IntersectionFail +>kind : "int" | "string" +>'int' : "int" + + return '' + value.num; +>'' + value.num : string +>'' : "" +>value.num : number +>value : { kind: "int"; num: number; } & { toString(): string; } +>num : number + } + return value.str; +>value.str : string +>value : { kind: "string"; str: string; } & { toString(): string; } +>str : string +} diff --git a/tests/baselines/reference/switchCaseWithIntersectionTypes01.errors.txt b/tests/baselines/reference/switchCaseWithIntersectionTypes01.errors.txt index ad2ca8bc123a9..d31a005842c4a 100644 --- a/tests/baselines/reference/switchCaseWithIntersectionTypes01.errors.txt +++ b/tests/baselines/reference/switchCaseWithIntersectionTypes01.errors.txt @@ -1,5 +1,6 @@ -tests/cases/conformance/types/typeRelationships/comparable/switchCaseWithIntersectionTypes01.ts(19,10): error TS2678: Type 'number & boolean' is not comparable to type 'string & number'. - Type 'number & boolean' is not comparable to type 'string'. +tests/cases/conformance/types/typeRelationships/comparable/switchCaseWithIntersectionTypes01.ts(19,10): error TS2678: Type '(number & true) | (number & false)' is not comparable to type 'string & number'. + Type 'number & false' is not comparable to type 'string & number'. + Type 'number & false' is not comparable to type 'string'. tests/cases/conformance/types/typeRelationships/comparable/switchCaseWithIntersectionTypes01.ts(23,10): error TS2678: Type 'boolean' is not comparable to type 'string & number'. @@ -24,8 +25,9 @@ tests/cases/conformance/types/typeRelationships/comparable/switchCaseWithInterse // Overlap in constituents case numAndBool: ~~~~~~~~~~ -!!! error TS2678: Type 'number & boolean' is not comparable to type 'string & number'. -!!! error TS2678: Type 'number & boolean' is not comparable to type 'string'. +!!! error TS2678: Type '(number & true) | (number & false)' is not comparable to type 'string & number'. +!!! error TS2678: Type 'number & false' is not comparable to type 'string & number'. +!!! error TS2678: Type 'number & false' is not comparable to type 'string'. break; // No relation diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.types b/tests/baselines/reference/typeGuardsWithInstanceOf.types index f54498f93c875..7fce8cd45594e 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.types +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.types @@ -25,7 +25,7 @@ if (!(result instanceof RegExp)) { } else if (!result.global) { >!result.global : boolean ->result.global : string & boolean +>result.global : (string & true) | (string & false) >result : I & RegExp ->global : string & boolean +>global : (string & true) | (string & false) } diff --git a/tests/cases/compiler/intersectionTypeNormalization.ts b/tests/cases/compiler/intersectionTypeNormalization.ts new file mode 100644 index 0000000000000..f31c3d49014b4 --- /dev/null +++ b/tests/cases/compiler/intersectionTypeNormalization.ts @@ -0,0 +1,60 @@ +interface A { a: string } +interface B { b: string } +interface C { c: string } +interface D { d: string } + +// Identical ways of writing the same type +type X1 = (A | B) & (C | D); +type X2 = A & (C | D) | B & (C | D) +type X3 = A & C | A & D | B & C | B & D; + +var x: X1; +var x: X2; +var x: X3; + +interface X { x: string } +interface Y { y: string } + +// Identical ways of writing the same type +type Y1 = (A | X & Y) & (C | D); +type Y2 = A & (C | D) | X & Y & (C | D) +type Y3 = A & C | A & D | X & Y & C | X & Y & D; + +var y: Y1; +var y: Y2; +var y: Y3; + +interface M { m: string } +interface N { n: string } + +// Identical ways of writing the same type +type Z1 = (A | X & (M | N)) & (C | D); +type Z2 = A & (C | D) | X & (M | N) & (C | D) +type Z3 = A & C | A & D | X & (M | N) & C | X & (M | N) & D; +type Z4 = A & C | A & D | X & M & C | X & N & C | X & M & D | X & N & D; + +var z: Z1; +var z: Z2; +var z: Z3; +var z: Z4; + +// Repro from #9919 + +type ToString = { + toString(): string; +} + +type BoxedValue = { kind: 'int', num: number } + | { kind: 'string', str: string } + +type IntersectionFail = BoxedValue & ToString + +type IntersectionInline = { kind: 'int', num: number } & ToString + | { kind: 'string', str: string } & ToString + +function getValueAsString(value: IntersectionFail): string { + if (value.kind === 'int') { + return '' + value.num; + } + return value.str; +} \ No newline at end of file