Fix creation of composite union type predicates (#54169)

This commit is contained in:
Anders Hejlsberg 2023-05-19 13:15:46 -07:00 committed by GitHub
parent 2b7d517907
commit 215fe6ef75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 439 additions and 18 deletions

View File

@ -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 {

View 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'.
}

View 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))
}

View 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
}

View 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
}