Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🤖 Pick PR #53351 (Fix subtype reduction involving typ...) into release-5.0 #53422

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
113 changes: 113 additions & 0 deletions tests/baselines/reference/subtypeReductionUnionConstraints.symbols
Original file line number Diff line number Diff line change
@@ -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<T extends Node>(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 extends A | B>(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))
}

99 changes: 99 additions & 0 deletions tests/baselines/reference/subtypeReductionUnionConstraints.types
Original file line number Diff line number Diff line change
@@ -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<T extends Node>(node: Document | Node, predicate: (testNode: Node) => testNode is T): void {
>visitNodes : <T extends Node>(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 extends A | B>(t: T, x: A | B) {
>f1 : <T extends A | B>(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
}

39 changes: 39 additions & 0 deletions tests/cases/compiler/subtypeReductionUnionConstraints.ts
Original file line number Diff line number Diff line change
@@ -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<T extends Node>(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 extends A | B>(t: T, x: A | B) {
const a = [t, x]; // (A | B)[] by subtype reduction
}