Skip to content

Commit

Permalink
🤖 Pick PR #53351 (Fix subtype reduction involving typ...) into releas…
Browse files Browse the repository at this point in the history
…e-5.0 (#53422)

Co-authored-by: Anders Hejlsberg <[email protected]>
  • Loading branch information
TypeScript Bot and ahejlsberg authored Mar 28, 2023
1 parent b345c3a commit 7e093f0
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
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
}

0 comments on commit 7e093f0

Please sign in to comment.