mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Fix creation of composite union type predicates (#54169)
This commit is contained in:
parent
2b7d517907
commit
215fe6ef75
@ -16515,36 +16515,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined {
|
||||
let first: TypePredicate | undefined;
|
||||
let last: TypePredicate | undefined;
|
||||
const types: Type[] = [];
|
||||
for (const sig of signatures) {
|
||||
const pred = getTypePredicateOfSignature(sig);
|
||||
if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) {
|
||||
if (kind !== TypeFlags.Intersection) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return; // intersections demand all members be type predicates for the result to have a predicate
|
||||
if (pred) {
|
||||
// Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions.
|
||||
if (pred.kind !== TypePredicateKind.This && pred.kind !== TypePredicateKind.Identifier || last && !typePredicateKindsMatch(last, pred)) {
|
||||
return undefined;
|
||||
}
|
||||
last = pred;
|
||||
types.push(pred.type);
|
||||
}
|
||||
|
||||
if (first) {
|
||||
if (!typePredicateKindsMatch(first, pred)) {
|
||||
// No common type predicate.
|
||||
else {
|
||||
// In composite union signatures we permit and ignore signatures with a return type `false`.
|
||||
const returnType = kind !== TypeFlags.Intersection ? getReturnTypeOfSignature(sig) : undefined;
|
||||
if (returnType !== falseType && returnType !== regularFalseType) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else {
|
||||
first = pred;
|
||||
}
|
||||
types.push(pred.type);
|
||||
}
|
||||
if (!first) {
|
||||
// No signatures had a type predicate.
|
||||
if (!last) {
|
||||
return undefined;
|
||||
}
|
||||
const compositeType = getUnionOrIntersectionType(types, kind);
|
||||
return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, compositeType);
|
||||
return createTypePredicate(last.kind, last.parameterName, last.parameterIndex, compositeType);
|
||||
}
|
||||
|
||||
function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean {
|
||||
|
||||
69
tests/baselines/reference/typePredicatesInUnion3.errors.txt
Normal file
69
tests/baselines/reference/typePredicatesInUnion3.errors.txt
Normal file
@ -0,0 +1,69 @@
|
||||
tests/cases/compiler/typePredicatesInUnion3.ts(59,24): error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number'.
|
||||
Type 'null' is not assignable to type 'number'.
|
||||
|
||||
|
||||
==== tests/cases/compiler/typePredicatesInUnion3.ts (1 errors) ====
|
||||
// A union of function types is considered a type predicate if at least one constituent is a type
|
||||
// predicate and the other constituents are matching type predicates or functions returning `false`.
|
||||
|
||||
type P1 = (x: unknown) => x is string;
|
||||
type P2 = (x: unknown) => x is number;
|
||||
|
||||
type F1 = (x: unknown) => false;
|
||||
type F2 = (x: unknown) => boolean;
|
||||
type F3 = (x: unknown) => string;
|
||||
|
||||
function f1(x: unknown, p: P1 | P2) {
|
||||
if (p(x)) {
|
||||
x; // string | number
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: unknown, p: P1 | P2 | F1) {
|
||||
if (p(x)) {
|
||||
x; // string | number
|
||||
}
|
||||
}
|
||||
|
||||
function f3(x: unknown, p: P1 | P2 | F2) {
|
||||
if (p(x)) {
|
||||
x; // unknown
|
||||
}
|
||||
}
|
||||
|
||||
function f4(x: unknown, p: P1 | P2 | F3) {
|
||||
if (p(x)) {
|
||||
x; // unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #54143
|
||||
|
||||
type HasAttribute<T> = T & { attribute: number };
|
||||
|
||||
class Type1 {
|
||||
attribute: number | null = null;
|
||||
predicate(): this is HasAttribute<Type1> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Type2 {
|
||||
attribute: number | null = null;
|
||||
predicate(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function assertType<T>(_val: T) {
|
||||
}
|
||||
|
||||
declare const val: Type1 | Type2;
|
||||
|
||||
if (val.predicate()) {
|
||||
assertType<number>(val.attribute); // Error
|
||||
~~~~~~~~~~~~~
|
||||
!!! error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number'.
|
||||
!!! error TS2345: Type 'null' is not assignable to type 'number'.
|
||||
}
|
||||
|
||||
153
tests/baselines/reference/typePredicatesInUnion3.symbols
Normal file
153
tests/baselines/reference/typePredicatesInUnion3.symbols
Normal file
@ -0,0 +1,153 @@
|
||||
=== tests/cases/compiler/typePredicatesInUnion3.ts ===
|
||||
// A union of function types is considered a type predicate if at least one constituent is a type
|
||||
// predicate and the other constituents are matching type predicates or functions returning `false`.
|
||||
|
||||
type P1 = (x: unknown) => x is string;
|
||||
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 3, 11))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 3, 11))
|
||||
|
||||
type P2 = (x: unknown) => x is number;
|
||||
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 4, 11))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 4, 11))
|
||||
|
||||
type F1 = (x: unknown) => false;
|
||||
>F1 : Symbol(F1, Decl(typePredicatesInUnion3.ts, 4, 38))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 6, 11))
|
||||
|
||||
type F2 = (x: unknown) => boolean;
|
||||
>F2 : Symbol(F2, Decl(typePredicatesInUnion3.ts, 6, 32))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 7, 11))
|
||||
|
||||
type F3 = (x: unknown) => string;
|
||||
>F3 : Symbol(F3, Decl(typePredicatesInUnion3.ts, 7, 34))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 8, 11))
|
||||
|
||||
function f1(x: unknown, p: P1 | P2) {
|
||||
>f1 : Symbol(f1, Decl(typePredicatesInUnion3.ts, 8, 33))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 10, 12))
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 10, 23))
|
||||
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
|
||||
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
|
||||
|
||||
if (p(x)) {
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 10, 23))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 10, 12))
|
||||
|
||||
x; // string | number
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 10, 12))
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: unknown, p: P1 | P2 | F1) {
|
||||
>f2 : Symbol(f2, Decl(typePredicatesInUnion3.ts, 14, 1))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 16, 12))
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 16, 23))
|
||||
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
|
||||
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
|
||||
>F1 : Symbol(F1, Decl(typePredicatesInUnion3.ts, 4, 38))
|
||||
|
||||
if (p(x)) {
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 16, 23))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 16, 12))
|
||||
|
||||
x; // string | number
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 16, 12))
|
||||
}
|
||||
}
|
||||
|
||||
function f3(x: unknown, p: P1 | P2 | F2) {
|
||||
>f3 : Symbol(f3, Decl(typePredicatesInUnion3.ts, 20, 1))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 22, 12))
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 22, 23))
|
||||
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
|
||||
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
|
||||
>F2 : Symbol(F2, Decl(typePredicatesInUnion3.ts, 6, 32))
|
||||
|
||||
if (p(x)) {
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 22, 23))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 22, 12))
|
||||
|
||||
x; // unknown
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 22, 12))
|
||||
}
|
||||
}
|
||||
|
||||
function f4(x: unknown, p: P1 | P2 | F3) {
|
||||
>f4 : Symbol(f4, Decl(typePredicatesInUnion3.ts, 26, 1))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 28, 12))
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 28, 23))
|
||||
>P1 : Symbol(P1, Decl(typePredicatesInUnion3.ts, 0, 0))
|
||||
>P2 : Symbol(P2, Decl(typePredicatesInUnion3.ts, 3, 38))
|
||||
>F3 : Symbol(F3, Decl(typePredicatesInUnion3.ts, 7, 34))
|
||||
|
||||
if (p(x)) {
|
||||
>p : Symbol(p, Decl(typePredicatesInUnion3.ts, 28, 23))
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 28, 12))
|
||||
|
||||
x; // unknown
|
||||
>x : Symbol(x, Decl(typePredicatesInUnion3.ts, 28, 12))
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #54143
|
||||
|
||||
type HasAttribute<T> = T & { attribute: number };
|
||||
>HasAttribute : Symbol(HasAttribute, Decl(typePredicatesInUnion3.ts, 32, 1))
|
||||
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 36, 18))
|
||||
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 36, 18))
|
||||
>attribute : Symbol(attribute, Decl(typePredicatesInUnion3.ts, 36, 28))
|
||||
|
||||
class Type1 {
|
||||
>Type1 : Symbol(Type1, Decl(typePredicatesInUnion3.ts, 36, 49))
|
||||
|
||||
attribute: number | null = null;
|
||||
>attribute : Symbol(Type1.attribute, Decl(typePredicatesInUnion3.ts, 38, 13))
|
||||
|
||||
predicate(): this is HasAttribute<Type1> {
|
||||
>predicate : Symbol(Type1.predicate, Decl(typePredicatesInUnion3.ts, 39, 36))
|
||||
>HasAttribute : Symbol(HasAttribute, Decl(typePredicatesInUnion3.ts, 32, 1))
|
||||
>Type1 : Symbol(Type1, Decl(typePredicatesInUnion3.ts, 36, 49))
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Type2 {
|
||||
>Type2 : Symbol(Type2, Decl(typePredicatesInUnion3.ts, 43, 1))
|
||||
|
||||
attribute: number | null = null;
|
||||
>attribute : Symbol(Type2.attribute, Decl(typePredicatesInUnion3.ts, 45, 13))
|
||||
|
||||
predicate(): boolean {
|
||||
>predicate : Symbol(Type2.predicate, Decl(typePredicatesInUnion3.ts, 46, 36))
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function assertType<T>(_val: T) {
|
||||
>assertType : Symbol(assertType, Decl(typePredicatesInUnion3.ts, 50, 1))
|
||||
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 52, 20))
|
||||
>_val : Symbol(_val, Decl(typePredicatesInUnion3.ts, 52, 23))
|
||||
>T : Symbol(T, Decl(typePredicatesInUnion3.ts, 52, 20))
|
||||
}
|
||||
|
||||
declare const val: Type1 | Type2;
|
||||
>val : Symbol(val, Decl(typePredicatesInUnion3.ts, 55, 13))
|
||||
>Type1 : Symbol(Type1, Decl(typePredicatesInUnion3.ts, 36, 49))
|
||||
>Type2 : Symbol(Type2, Decl(typePredicatesInUnion3.ts, 43, 1))
|
||||
|
||||
if (val.predicate()) {
|
||||
>val.predicate : Symbol(predicate, Decl(typePredicatesInUnion3.ts, 39, 36), Decl(typePredicatesInUnion3.ts, 46, 36))
|
||||
>val : Symbol(val, Decl(typePredicatesInUnion3.ts, 55, 13))
|
||||
>predicate : Symbol(predicate, Decl(typePredicatesInUnion3.ts, 39, 36), Decl(typePredicatesInUnion3.ts, 46, 36))
|
||||
|
||||
assertType<number>(val.attribute); // Error
|
||||
>assertType : Symbol(assertType, Decl(typePredicatesInUnion3.ts, 50, 1))
|
||||
>val.attribute : Symbol(attribute, Decl(typePredicatesInUnion3.ts, 38, 13), Decl(typePredicatesInUnion3.ts, 45, 13))
|
||||
>val : Symbol(val, Decl(typePredicatesInUnion3.ts, 55, 13))
|
||||
>attribute : Symbol(attribute, Decl(typePredicatesInUnion3.ts, 38, 13), Decl(typePredicatesInUnion3.ts, 45, 13))
|
||||
}
|
||||
|
||||
141
tests/baselines/reference/typePredicatesInUnion3.types
Normal file
141
tests/baselines/reference/typePredicatesInUnion3.types
Normal file
@ -0,0 +1,141 @@
|
||||
=== tests/cases/compiler/typePredicatesInUnion3.ts ===
|
||||
// A union of function types is considered a type predicate if at least one constituent is a type
|
||||
// predicate and the other constituents are matching type predicates or functions returning `false`.
|
||||
|
||||
type P1 = (x: unknown) => x is string;
|
||||
>P1 : (x: unknown) => x is string
|
||||
>x : unknown
|
||||
|
||||
type P2 = (x: unknown) => x is number;
|
||||
>P2 : (x: unknown) => x is number
|
||||
>x : unknown
|
||||
|
||||
type F1 = (x: unknown) => false;
|
||||
>F1 : (x: unknown) => false
|
||||
>x : unknown
|
||||
>false : false
|
||||
|
||||
type F2 = (x: unknown) => boolean;
|
||||
>F2 : (x: unknown) => boolean
|
||||
>x : unknown
|
||||
|
||||
type F3 = (x: unknown) => string;
|
||||
>F3 : (x: unknown) => string
|
||||
>x : unknown
|
||||
|
||||
function f1(x: unknown, p: P1 | P2) {
|
||||
>f1 : (x: unknown, p: P1 | P2) => void
|
||||
>x : unknown
|
||||
>p : P1 | P2
|
||||
|
||||
if (p(x)) {
|
||||
>p(x) : boolean
|
||||
>p : P1 | P2
|
||||
>x : unknown
|
||||
|
||||
x; // string | number
|
||||
>x : string | number
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: unknown, p: P1 | P2 | F1) {
|
||||
>f2 : (x: unknown, p: P1 | P2 | F1) => void
|
||||
>x : unknown
|
||||
>p : P1 | P2 | F1
|
||||
|
||||
if (p(x)) {
|
||||
>p(x) : boolean
|
||||
>p : P1 | P2 | F1
|
||||
>x : unknown
|
||||
|
||||
x; // string | number
|
||||
>x : string | number
|
||||
}
|
||||
}
|
||||
|
||||
function f3(x: unknown, p: P1 | P2 | F2) {
|
||||
>f3 : (x: unknown, p: P1 | P2 | F2) => void
|
||||
>x : unknown
|
||||
>p : P1 | P2 | F2
|
||||
|
||||
if (p(x)) {
|
||||
>p(x) : boolean
|
||||
>p : P1 | P2 | F2
|
||||
>x : unknown
|
||||
|
||||
x; // unknown
|
||||
>x : unknown
|
||||
}
|
||||
}
|
||||
|
||||
function f4(x: unknown, p: P1 | P2 | F3) {
|
||||
>f4 : (x: unknown, p: P1 | P2 | F3) => void
|
||||
>x : unknown
|
||||
>p : P1 | P2 | F3
|
||||
|
||||
if (p(x)) {
|
||||
>p(x) : string | boolean
|
||||
>p : P1 | P2 | F3
|
||||
>x : unknown
|
||||
|
||||
x; // unknown
|
||||
>x : unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #54143
|
||||
|
||||
type HasAttribute<T> = T & { attribute: number };
|
||||
>HasAttribute : HasAttribute<T>
|
||||
>attribute : number
|
||||
|
||||
class Type1 {
|
||||
>Type1 : Type1
|
||||
|
||||
attribute: number | null = null;
|
||||
>attribute : number | null
|
||||
|
||||
predicate(): this is HasAttribute<Type1> {
|
||||
>predicate : () => this is HasAttribute<Type1>
|
||||
|
||||
return true;
|
||||
>true : true
|
||||
}
|
||||
}
|
||||
|
||||
class Type2 {
|
||||
>Type2 : Type2
|
||||
|
||||
attribute: number | null = null;
|
||||
>attribute : number | null
|
||||
|
||||
predicate(): boolean {
|
||||
>predicate : () => boolean
|
||||
|
||||
return true;
|
||||
>true : true
|
||||
}
|
||||
}
|
||||
|
||||
function assertType<T>(_val: T) {
|
||||
>assertType : <T>(_val: T) => void
|
||||
>_val : T
|
||||
}
|
||||
|
||||
declare const val: Type1 | Type2;
|
||||
>val : Type1 | Type2
|
||||
|
||||
if (val.predicate()) {
|
||||
>val.predicate() : boolean
|
||||
>val.predicate : (() => this is HasAttribute<Type1>) | (() => boolean)
|
||||
>val : Type1 | Type2
|
||||
>predicate : (() => this is HasAttribute<Type1>) | (() => boolean)
|
||||
|
||||
assertType<number>(val.attribute); // Error
|
||||
>assertType<number>(val.attribute) : void
|
||||
>assertType : <T>(_val: T) => void
|
||||
>val.attribute : number | null
|
||||
>val : Type1 | Type2
|
||||
>attribute : number | null
|
||||
}
|
||||
|
||||
63
tests/cases/compiler/typePredicatesInUnion3.ts
Normal file
63
tests/cases/compiler/typePredicatesInUnion3.ts
Normal file
@ -0,0 +1,63 @@
|
||||
// @strict: true
|
||||
// @noEmit: true
|
||||
|
||||
// A union of function types is considered a type predicate if at least one constituent is a type
|
||||
// predicate and the other constituents are matching type predicates or functions returning `false`.
|
||||
|
||||
type P1 = (x: unknown) => x is string;
|
||||
type P2 = (x: unknown) => x is number;
|
||||
|
||||
type F1 = (x: unknown) => false;
|
||||
type F2 = (x: unknown) => boolean;
|
||||
type F3 = (x: unknown) => string;
|
||||
|
||||
function f1(x: unknown, p: P1 | P2) {
|
||||
if (p(x)) {
|
||||
x; // string | number
|
||||
}
|
||||
}
|
||||
|
||||
function f2(x: unknown, p: P1 | P2 | F1) {
|
||||
if (p(x)) {
|
||||
x; // string | number
|
||||
}
|
||||
}
|
||||
|
||||
function f3(x: unknown, p: P1 | P2 | F2) {
|
||||
if (p(x)) {
|
||||
x; // unknown
|
||||
}
|
||||
}
|
||||
|
||||
function f4(x: unknown, p: P1 | P2 | F3) {
|
||||
if (p(x)) {
|
||||
x; // unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #54143
|
||||
|
||||
type HasAttribute<T> = T & { attribute: number };
|
||||
|
||||
class Type1 {
|
||||
attribute: number | null = null;
|
||||
predicate(): this is HasAttribute<Type1> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Type2 {
|
||||
attribute: number | null = null;
|
||||
predicate(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function assertType<T>(_val: T) {
|
||||
}
|
||||
|
||||
declare const val: Type1 | Type2;
|
||||
|
||||
if (val.predicate()) {
|
||||
assertType<number>(val.attribute); // Error
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user