diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e374efbfba36..92e3133f40a91 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16199,6 +16199,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { i--; const source = types[i]; if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) { + // A type parameter with a union constraint may be a subtype of some union, but not a subtype of the + // individual constituents of that union. For example, `T extends A | B` is a subtype of `A | B`, but not + // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the + // type parameter is a subtype of a union of all the other types. + if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { + if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { + orderedRemoveItemAt(types, i); + } + continue; + } // Find the first property with a unit type, if any. When constituents have a property by the same name // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype // reduction of large discriminated union types. diff --git a/tests/baselines/reference/subtypeReductionUnionConstraints.symbols b/tests/baselines/reference/subtypeReductionUnionConstraints.symbols new file mode 100644 index 0000000000000..926ecfd2e3974 --- /dev/null +++ b/tests/baselines/reference/subtypeReductionUnionConstraints.symbols @@ -0,0 +1,113 @@ +=== tests/cases/compiler/subtypeReductionUnionConstraints.ts === +// Repro from #53311 + +type FooNode = { +>FooNode : Symbol(FooNode, Decl(subtypeReductionUnionConstraints.ts, 0, 0)) + + kind: 'foo'; +>kind : Symbol(kind, Decl(subtypeReductionUnionConstraints.ts, 2, 16)) + + children: Node[]; +>children : Symbol(children, Decl(subtypeReductionUnionConstraints.ts, 3, 16)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) + +}; + +type BarNode = { +>BarNode : Symbol(BarNode, Decl(subtypeReductionUnionConstraints.ts, 5, 2)) + + kind: 'bar'; +>kind : Symbol(kind, Decl(subtypeReductionUnionConstraints.ts, 7, 16)) +} + +type Node = FooNode | BarNode; +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) +>FooNode : Symbol(FooNode, Decl(subtypeReductionUnionConstraints.ts, 0, 0)) +>BarNode : Symbol(BarNode, Decl(subtypeReductionUnionConstraints.ts, 5, 2)) + +type Document = { +>Document : Symbol(Document, Decl(subtypeReductionUnionConstraints.ts, 11, 30)) + + kind: 'document'; +>kind : Symbol(kind, Decl(subtypeReductionUnionConstraints.ts, 13, 17)) + + children: Node[]; +>children : Symbol(children, Decl(subtypeReductionUnionConstraints.ts, 14, 21)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) + +}; + +declare function isNode(node: unknown): node is Node; +>isNode : Symbol(isNode, Decl(subtypeReductionUnionConstraints.ts, 16, 2)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 18, 24)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 18, 24)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) + +declare function isBar(node: Node): node is BarNode; +>isBar : Symbol(isBar, Decl(subtypeReductionUnionConstraints.ts, 18, 53)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 19, 23)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 19, 23)) +>BarNode : Symbol(BarNode, Decl(subtypeReductionUnionConstraints.ts, 5, 2)) + +export function visitNodes(node: Document | Node, predicate: (testNode: Node) => testNode is T): void { +>visitNodes : Symbol(visitNodes, Decl(subtypeReductionUnionConstraints.ts, 19, 52)) +>T : Symbol(T, Decl(subtypeReductionUnionConstraints.ts, 21, 27)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 21, 43)) +>Document : Symbol(Document, Decl(subtypeReductionUnionConstraints.ts, 11, 30)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) +>predicate : Symbol(predicate, Decl(subtypeReductionUnionConstraints.ts, 21, 65)) +>testNode : Symbol(testNode, Decl(subtypeReductionUnionConstraints.ts, 21, 78)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) +>testNode : Symbol(testNode, Decl(subtypeReductionUnionConstraints.ts, 21, 78)) +>T : Symbol(T, Decl(subtypeReductionUnionConstraints.ts, 21, 27)) + + isNode(node) && predicate(node); +>isNode : Symbol(isNode, Decl(subtypeReductionUnionConstraints.ts, 16, 2)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 21, 43)) +>predicate : Symbol(predicate, Decl(subtypeReductionUnionConstraints.ts, 21, 65)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 21, 43)) + + if (!isNode(node) || !isBar(node)) { +>isNode : Symbol(isNode, Decl(subtypeReductionUnionConstraints.ts, 16, 2)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 21, 43)) +>isBar : Symbol(isBar, Decl(subtypeReductionUnionConstraints.ts, 18, 53)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 21, 43)) + + const nodes: Node[] = node.children; +>nodes : Symbol(nodes, Decl(subtypeReductionUnionConstraints.ts, 24, 13)) +>Node : Symbol(Node, Decl(subtypeReductionUnionConstraints.ts, 9, 1)) +>node.children : Symbol(children, Decl(subtypeReductionUnionConstraints.ts, 3, 16), Decl(subtypeReductionUnionConstraints.ts, 14, 21)) +>node : Symbol(node, Decl(subtypeReductionUnionConstraints.ts, 21, 43)) +>children : Symbol(children, Decl(subtypeReductionUnionConstraints.ts, 3, 16), Decl(subtypeReductionUnionConstraints.ts, 14, 21)) + } +} + +// Repro from #53311 + +type A = { a: string }; +>A : Symbol(A, Decl(subtypeReductionUnionConstraints.ts, 26, 1)) +>a : Symbol(a, Decl(subtypeReductionUnionConstraints.ts, 30, 10)) + +type B = { b: string }; +>B : Symbol(B, Decl(subtypeReductionUnionConstraints.ts, 30, 23)) +>b : Symbol(b, Decl(subtypeReductionUnionConstraints.ts, 31, 10)) + +function f1(t: T, x: A | B) { +>f1 : Symbol(f1, Decl(subtypeReductionUnionConstraints.ts, 31, 23)) +>T : Symbol(T, Decl(subtypeReductionUnionConstraints.ts, 33, 12)) +>A : Symbol(A, Decl(subtypeReductionUnionConstraints.ts, 26, 1)) +>B : Symbol(B, Decl(subtypeReductionUnionConstraints.ts, 30, 23)) +>t : Symbol(t, Decl(subtypeReductionUnionConstraints.ts, 33, 29)) +>T : Symbol(T, Decl(subtypeReductionUnionConstraints.ts, 33, 12)) +>x : Symbol(x, Decl(subtypeReductionUnionConstraints.ts, 33, 34)) +>A : Symbol(A, Decl(subtypeReductionUnionConstraints.ts, 26, 1)) +>B : Symbol(B, Decl(subtypeReductionUnionConstraints.ts, 30, 23)) + + const a = [t, x]; // (A | B)[] by subtype reduction +>a : Symbol(a, Decl(subtypeReductionUnionConstraints.ts, 34, 9)) +>t : Symbol(t, Decl(subtypeReductionUnionConstraints.ts, 33, 29)) +>x : Symbol(x, Decl(subtypeReductionUnionConstraints.ts, 33, 34)) +} + diff --git a/tests/baselines/reference/subtypeReductionUnionConstraints.types b/tests/baselines/reference/subtypeReductionUnionConstraints.types new file mode 100644 index 0000000000000..35ccf8ca5316e --- /dev/null +++ b/tests/baselines/reference/subtypeReductionUnionConstraints.types @@ -0,0 +1,99 @@ +=== tests/cases/compiler/subtypeReductionUnionConstraints.ts === +// Repro from #53311 + +type FooNode = { +>FooNode : { kind: 'foo'; children: Node[]; } + + kind: 'foo'; +>kind : "foo" + + children: Node[]; +>children : Node[] + +}; + +type BarNode = { +>BarNode : { kind: 'bar'; } + + kind: 'bar'; +>kind : "bar" +} + +type Node = FooNode | BarNode; +>Node : FooNode | BarNode + +type Document = { +>Document : { kind: 'document'; children: Node[]; } + + kind: 'document'; +>kind : "document" + + children: Node[]; +>children : Node[] + +}; + +declare function isNode(node: unknown): node is Node; +>isNode : (node: unknown) => node is Node +>node : unknown + +declare function isBar(node: Node): node is BarNode; +>isBar : (node: Node) => node is BarNode +>node : Node + +export function visitNodes(node: Document | Node, predicate: (testNode: Node) => testNode is T): void { +>visitNodes : (node: Document | Node, predicate: (testNode: Node) => testNode is T) => void +>node : Node | Document +>predicate : (testNode: Node) => testNode is T +>testNode : Node + + isNode(node) && predicate(node); +>isNode(node) && predicate(node) : boolean +>isNode(node) : boolean +>isNode : (node: unknown) => node is Node +>node : Node | Document +>predicate(node) : boolean +>predicate : (testNode: Node) => testNode is T +>node : Node + + if (!isNode(node) || !isBar(node)) { +>!isNode(node) || !isBar(node) : boolean +>!isNode(node) : boolean +>isNode(node) : boolean +>isNode : (node: unknown) => node is Node +>node : Node | Document +>!isBar(node) : boolean +>isBar(node) : boolean +>isBar : (node: Node) => node is BarNode +>node : Node + + const nodes: Node[] = node.children; +>nodes : Node[] +>node.children : Node[] +>node : FooNode | Document +>children : Node[] + } +} + +// Repro from #53311 + +type A = { a: string }; +>A : { a: string; } +>a : string + +type B = { b: string }; +>B : { b: string; } +>b : string + +function f1(t: T, x: A | B) { +>f1 : (t: T, x: A | B) => void +>t : T +>x : A | B + + const a = [t, x]; // (A | B)[] by subtype reduction +>a : (A | B)[] +>[t, x] : (A | B)[] +>t : T +>x : A | B +} + diff --git a/tests/cases/compiler/subtypeReductionUnionConstraints.ts b/tests/cases/compiler/subtypeReductionUnionConstraints.ts new file mode 100644 index 0000000000000..614b8683856b4 --- /dev/null +++ b/tests/cases/compiler/subtypeReductionUnionConstraints.ts @@ -0,0 +1,39 @@ +// @strict: true +// @noEmit: true + +// Repro from #53311 + +type FooNode = { + kind: 'foo'; + children: Node[]; +}; + +type BarNode = { + kind: 'bar'; +} + +type Node = FooNode | BarNode; + +type Document = { + kind: 'document'; + children: Node[]; +}; + +declare function isNode(node: unknown): node is Node; +declare function isBar(node: Node): node is BarNode; + +export function visitNodes(node: Document | Node, predicate: (testNode: Node) => testNode is T): void { + isNode(node) && predicate(node); + if (!isNode(node) || !isBar(node)) { + const nodes: Node[] = node.children; + } +} + +// Repro from #53311 + +type A = { a: string }; +type B = { b: string }; + +function f1(t: T, x: A | B) { + const a = [t, x]; // (A | B)[] by subtype reduction +}