Skip to content

Commit 215fe6e

Browse files
authored
Fix creation of composite union type predicates (#54169)
1 parent 2b7d517 commit 215fe6e

File tree

5 files changed

+439
-18
lines changed

5 files changed

+439
-18
lines changed

src/compiler/checker.ts

+13-18
Original file line numberDiff line numberDiff line change
@@ -16515,36 +16515,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1651516515
}
1651616516

1651716517
function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined {
16518-
let first: TypePredicate | undefined;
16518+
let last: TypePredicate | undefined;
1651916519
const types: Type[] = [];
1652016520
for (const sig of signatures) {
1652116521
const pred = getTypePredicateOfSignature(sig);
16522-
if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) {
16523-
if (kind !== TypeFlags.Intersection) {
16524-
continue;
16525-
}
16526-
else {
16527-
return; // intersections demand all members be type predicates for the result to have a predicate
16528-
}
16529-
}
16530-
16531-
if (first) {
16532-
if (!typePredicateKindsMatch(first, pred)) {
16533-
// No common type predicate.
16522+
if (pred) {
16523+
// Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions.
16524+
if (pred.kind !== TypePredicateKind.This && pred.kind !== TypePredicateKind.Identifier || last && !typePredicateKindsMatch(last, pred)) {
1653416525
return undefined;
1653516526
}
16527+
last = pred;
16528+
types.push(pred.type);
1653616529
}
1653716530
else {
16538-
first = pred;
16531+
// In composite union signatures we permit and ignore signatures with a return type `false`.
16532+
const returnType = kind !== TypeFlags.Intersection ? getReturnTypeOfSignature(sig) : undefined;
16533+
if (returnType !== falseType && returnType !== regularFalseType) {
16534+
return undefined;
16535+
}
1653916536
}
16540-
types.push(pred.type);
1654116537
}
16542-
if (!first) {
16543-
// No signatures had a type predicate.
16538+
if (!last) {
1654416539
return undefined;
1654516540
}
1654616541
const compositeType = getUnionOrIntersectionType(types, kind);
16547-
return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, compositeType);
16542+
return createTypePredicate(last.kind, last.parameterName, last.parameterIndex, compositeType);
1654816543
}
1654916544

1655016545
function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
tests/cases/compiler/typePredicatesInUnion3.ts(59,24): error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number'.
2+
Type 'null' is not assignable to type 'number'.
3+
4+
5+
==== tests/cases/compiler/typePredicatesInUnion3.ts (1 errors) ====
6+
// A union of function types is considered a type predicate if at least one constituent is a type
7+
// predicate and the other constituents are matching type predicates or functions returning `false`.
8+
9+
type P1 = (x: unknown) => x is string;
10+
type P2 = (x: unknown) => x is number;
11+
12+
type F1 = (x: unknown) => false;
13+
type F2 = (x: unknown) => boolean;
14+
type F3 = (x: unknown) => string;
15+
16+
function f1(x: unknown, p: P1 | P2) {
17+
if (p(x)) {
18+
x; // string | number
19+
}
20+
}
21+
22+
function f2(x: unknown, p: P1 | P2 | F1) {
23+
if (p(x)) {
24+
x; // string | number
25+
}
26+
}
27+
28+
function f3(x: unknown, p: P1 | P2 | F2) {
29+
if (p(x)) {
30+
x; // unknown
31+
}
32+
}
33+
34+
function f4(x: unknown, p: P1 | P2 | F3) {
35+
if (p(x)) {
36+
x; // unknown
37+
}
38+
}
39+
40+
// Repro from #54143
41+
42+
type HasAttribute<T> = T & { attribute: number };
43+
44+
class Type1 {
45+
attribute: number | null = null;
46+
predicate(): this is HasAttribute<Type1> {
47+
return true;
48+
}
49+
}
50+
51+
class Type2 {
52+
attribute: number | null = null;
53+
predicate(): boolean {
54+
return true;
55+
}
56+
}
57+
58+
function assertType<T>(_val: T) {
59+
}
60+
61+
declare const val: Type1 | Type2;
62+
63+
if (val.predicate()) {
64+
assertType<number>(val.attribute); // Error
65+
~~~~~~~~~~~~~
66+
!!! error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number'.
67+
!!! error TS2345: Type 'null' is not assignable to type 'number'.
68+
}
69+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
=== tests/cases/compiler/typePredicatesInUnion3.ts ===
2+
// A union of function types is considered a type predicate if at least one constituent is a type
3+
// predicate and the other constituents are matching type predicates or functions returning `false`.
4+
5+
type P1 = (x: unknown) => x is string;
6+
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
7+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 3, 11))
8+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 3, 11))
9+
10+
type P2 = (x: unknown) => x is number;
11+
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
12+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 4, 11))
13+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 4, 11))
14+
15+
type F1 = (x: unknown) => false;
16+
>F1 : Symbol(F1, Decl(typePredicatesInUnion3.ts, 4, 38))
17+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 6, 11))
18+
19+
type F2 = (x: unknown) => boolean;
20+
>F2 : Symbol(F2, Decl(typePredicatesInUnion3.ts, 6, 32))
21+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 7, 11))
22+
23+
type F3 = (x: unknown) => string;
24+
>F3 : Symbol(F3, Decl(typePredicatesInUnion3.ts, 7, 34))
25+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 8, 11))
26+
27+
function f1(x: unknown, p: P1 | P2) {
28+
>f1 : Symbol(f1, Decl(typePredicatesInUnion3.ts, 8, 33))
29+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 10, 12))
30+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 10, 23))
31+
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
32+
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
33+
34+
if (p(x)) {
35+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 10, 23))
36+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 10, 12))
37+
38+
x; // string | number
39+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 10, 12))
40+
}
41+
}
42+
43+
function f2(x: unknown, p: P1 | P2 | F1) {
44+
>f2 : Symbol(f2, Decl(typePredicatesInUnion3.ts, 14, 1))
45+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 16, 12))
46+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 16, 23))
47+
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
48+
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
49+
>F1 : Symbol(F1, Decl(typePredicatesInUnion3.ts, 4, 38))
50+
51+
if (p(x)) {
52+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 16, 23))
53+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 16, 12))
54+
55+
x; // string | number
56+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 16, 12))
57+
}
58+
}
59+
60+
function f3(x: unknown, p: P1 | P2 | F2) {
61+
>f3 : Symbol(f3, Decl(typePredicatesInUnion3.ts, 20, 1))
62+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 22, 12))
63+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 22, 23))
64+
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
65+
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
66+
>F2 : Symbol(F2, Decl(typePredicatesInUnion3.ts, 6, 32))
67+
68+
if (p(x)) {
69+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 22, 23))
70+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 22, 12))
71+
72+
x; // unknown
73+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 22, 12))
74+
}
75+
}
76+
77+
function f4(x: unknown, p: P1 | P2 | F3) {
78+
>f4 : Symbol(f4, Decl(typePredicatesInUnion3.ts, 26, 1))
79+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 28, 12))
80+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 28, 23))
81+
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
82+
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
83+
>F3 : Symbol(F3, Decl(typePredicatesInUnion3.ts, 7, 34))
84+
85+
if (p(x)) {
86+
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 28, 23))
87+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 28, 12))
88+
89+
x; // unknown
90+
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 28, 12))
91+
}
92+
}
93+
94+
// Repro from #54143
95+
96+
type HasAttribute<T> = T & { attribute: number };
97+
>HasAttribute : Symbol(HasAttribute, Decl(typePredicatesInUnion3.ts, 32, 1))
98+
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 36, 18))
99+
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 36, 18))
100+
>attribute : Symbol(attribute, Decl(typePredicatesInUnion3.ts, 36, 28))
101+
102+
class Type1 {
103+
>Type1 : Symbol(Type1, Decl(typePredicatesInUnion3.ts, 36, 49))
104+
105+
attribute: number | null = null;
106+
>attribute : Symbol(Type1.attribute, Decl(typePredicatesInUnion3.ts, 38, 13))
107+
108+
predicate(): this is HasAttribute<Type1> {
109+
>predicate : Symbol(Type1.predicate, Decl(typePredicatesInUnion3.ts, 39, 36))
110+
>HasAttribute : Symbol(HasAttribute, Decl(typePredicatesInUnion3.ts, 32, 1))
111+
>Type1 : Symbol(Type1, Decl(typePredicatesInUnion3.ts, 36, 49))
112+
113+
return true;
114+
}
115+
}
116+
117+
class Type2 {
118+
>Type2 : Symbol(Type2, Decl(typePredicatesInUnion3.ts, 43, 1))
119+
120+
attribute: number | null = null;
121+
>attribute : Symbol(Type2.attribute, Decl(typePredicatesInUnion3.ts, 45, 13))
122+
123+
predicate(): boolean {
124+
>predicate : Symbol(Type2.predicate, Decl(typePredicatesInUnion3.ts, 46, 36))
125+
126+
return true;
127+
}
128+
}
129+
130+
function assertType<T>(_val: T) {
131+
>assertType : Symbol(assertType, Decl(typePredicatesInUnion3.ts, 50, 1))
132+
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 52, 20))
133+
>_val : Symbol(_val, Decl(typePredicatesInUnion3.ts, 52, 23))
134+
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 52, 20))
135+
}
136+
137+
declare const val: Type1 | Type2;
138+
>val : Symbol(val, Decl(typePredicatesInUnion3.ts, 55, 13))
139+
>Type1 : Symbol(Type1, Decl(typePredicatesInUnion3.ts, 36, 49))
140+
>Type2 : Symbol(Type2, Decl(typePredicatesInUnion3.ts, 43, 1))
141+
142+
if (val.predicate()) {
143+
>val.predicate : Symbol(predicate, Decl(typePredicatesInUnion3.ts, 39, 36), Decl(typePredicatesInUnion3.ts, 46, 36))
144+
>val : Symbol(val, Decl(typePredicatesInUnion3.ts, 55, 13))
145+
>predicate : Symbol(predicate, Decl(typePredicatesInUnion3.ts, 39, 36), Decl(typePredicatesInUnion3.ts, 46, 36))
146+
147+
assertType<number>(val.attribute); // Error
148+
>assertType : Symbol(assertType, Decl(typePredicatesInUnion3.ts, 50, 1))
149+
>val.attribute : Symbol(attribute, Decl(typePredicatesInUnion3.ts, 38, 13), Decl(typePredicatesInUnion3.ts, 45, 13))
150+
>val : Symbol(val, Decl(typePredicatesInUnion3.ts, 55, 13))
151+
>attribute : Symbol(attribute, Decl(typePredicatesInUnion3.ts, 38, 13), Decl(typePredicatesInUnion3.ts, 45, 13))
152+
}
153+

0 commit comments

Comments
 (0)