Skip to content

Commit

Permalink
Improve unknown narrowing by negated type predicates (#60795)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist authored Dec 18, 2024
1 parent 52717ac commit 0dda037
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29404,8 +29404,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (checkDerived) {
return filterType(type, t => !isTypeDerivedFrom(t, candidate));
}
type = type.flags & TypeFlags.Unknown ? unknownUnionType : type;
const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false);
return filterType(type, t => !isTypeSubsetOf(t, trueType));
return recombineUnknownType(filterType(type, t => !isTypeSubsetOf(t, trueType)));
}
if (type.flags & TypeFlags.AnyOrUnknown) {
return candidate;
Expand Down
89 changes: 89 additions & 0 deletions tests/baselines/reference/narrowUnknownByTypePredicate.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//// [tests/cases/compiler/narrowUnknownByTypePredicate.ts] ////

=== narrowUnknownByTypePredicate.ts ===
declare function isNotNullish(value: unknown): value is {};
>isNotNullish : Symbol(isNotNullish, Decl(narrowUnknownByTypePredicate.ts, 0, 0))
>value : Symbol(value, Decl(narrowUnknownByTypePredicate.ts, 0, 30))
>value : Symbol(value, Decl(narrowUnknownByTypePredicate.ts, 0, 30))

declare function isNullish(value: unknown): value is null | undefined;
>isNullish : Symbol(isNullish, Decl(narrowUnknownByTypePredicate.ts, 0, 59))
>value : Symbol(value, Decl(narrowUnknownByTypePredicate.ts, 1, 27))
>value : Symbol(value, Decl(narrowUnknownByTypePredicate.ts, 1, 27))

declare const value1: unknown;
>value1 : Symbol(value1, Decl(narrowUnknownByTypePredicate.ts, 3, 13))

if (isNotNullish(value1)) {
>isNotNullish : Symbol(isNotNullish, Decl(narrowUnknownByTypePredicate.ts, 0, 0))
>value1 : Symbol(value1, Decl(narrowUnknownByTypePredicate.ts, 3, 13))

value1;
>value1 : Symbol(value1, Decl(narrowUnknownByTypePredicate.ts, 3, 13))
}

declare const value2: unknown;
>value2 : Symbol(value2, Decl(narrowUnknownByTypePredicate.ts, 8, 13))

if (!isNotNullish(value2)) {
>isNotNullish : Symbol(isNotNullish, Decl(narrowUnknownByTypePredicate.ts, 0, 0))
>value2 : Symbol(value2, Decl(narrowUnknownByTypePredicate.ts, 8, 13))

value2;
>value2 : Symbol(value2, Decl(narrowUnknownByTypePredicate.ts, 8, 13))
}

declare const value3: unknown;
>value3 : Symbol(value3, Decl(narrowUnknownByTypePredicate.ts, 13, 13))

if (isNullish(value3)) {
>isNullish : Symbol(isNullish, Decl(narrowUnknownByTypePredicate.ts, 0, 59))
>value3 : Symbol(value3, Decl(narrowUnknownByTypePredicate.ts, 13, 13))

value3;
>value3 : Symbol(value3, Decl(narrowUnknownByTypePredicate.ts, 13, 13))
}

declare const value4: unknown;
>value4 : Symbol(value4, Decl(narrowUnknownByTypePredicate.ts, 18, 13))

if (!isNullish(value4)) {
>isNullish : Symbol(isNullish, Decl(narrowUnknownByTypePredicate.ts, 0, 59))
>value4 : Symbol(value4, Decl(narrowUnknownByTypePredicate.ts, 18, 13))

value4;
>value4 : Symbol(value4, Decl(narrowUnknownByTypePredicate.ts, 18, 13))
}

declare class A { foo: string; }
>A : Symbol(A, Decl(narrowUnknownByTypePredicate.ts, 21, 1))
>foo : Symbol(A.foo, Decl(narrowUnknownByTypePredicate.ts, 23, 17))

declare function isA(value: unknown): value is A;
>isA : Symbol(isA, Decl(narrowUnknownByTypePredicate.ts, 23, 32))
>value : Symbol(value, Decl(narrowUnknownByTypePredicate.ts, 24, 21))
>value : Symbol(value, Decl(narrowUnknownByTypePredicate.ts, 24, 21))
>A : Symbol(A, Decl(narrowUnknownByTypePredicate.ts, 21, 1))

declare const value5: unknown;
>value5 : Symbol(value5, Decl(narrowUnknownByTypePredicate.ts, 26, 13))

if (isA(value5)) {
>isA : Symbol(isA, Decl(narrowUnknownByTypePredicate.ts, 23, 32))
>value5 : Symbol(value5, Decl(narrowUnknownByTypePredicate.ts, 26, 13))

value5;
>value5 : Symbol(value5, Decl(narrowUnknownByTypePredicate.ts, 26, 13))
}

declare const value6: unknown;
>value6 : Symbol(value6, Decl(narrowUnknownByTypePredicate.ts, 31, 13))

if (!isA(value6)) {
>isA : Symbol(isA, Decl(narrowUnknownByTypePredicate.ts, 23, 32))
>value6 : Symbol(value6, Decl(narrowUnknownByTypePredicate.ts, 31, 13))

value6;
>value6 : Symbol(value6, Decl(narrowUnknownByTypePredicate.ts, 31, 13))
}

135 changes: 135 additions & 0 deletions tests/baselines/reference/narrowUnknownByTypePredicate.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//// [tests/cases/compiler/narrowUnknownByTypePredicate.ts] ////

=== narrowUnknownByTypePredicate.ts ===
declare function isNotNullish(value: unknown): value is {};
>isNotNullish : (value: unknown) => value is {}
> : ^ ^^ ^^^^^
>value : unknown
> : ^^^^^^^

declare function isNullish(value: unknown): value is null | undefined;
>isNullish : (value: unknown) => value is null | undefined
> : ^ ^^ ^^^^^
>value : unknown
> : ^^^^^^^

declare const value1: unknown;
>value1 : unknown
> : ^^^^^^^

if (isNotNullish(value1)) {
>isNotNullish(value1) : boolean
> : ^^^^^^^
>isNotNullish : (value: unknown) => value is {}
> : ^ ^^ ^^^^^
>value1 : unknown
> : ^^^^^^^

value1;
>value1 : {}
> : ^^
}

declare const value2: unknown;
>value2 : unknown
> : ^^^^^^^

if (!isNotNullish(value2)) {
>!isNotNullish(value2) : boolean
> : ^^^^^^^
>isNotNullish(value2) : boolean
> : ^^^^^^^
>isNotNullish : (value: unknown) => value is {}
> : ^ ^^ ^^^^^
>value2 : unknown
> : ^^^^^^^

value2;
>value2 : null | undefined
> : ^^^^^^^^^^^^^^^^
}

declare const value3: unknown;
>value3 : unknown
> : ^^^^^^^

if (isNullish(value3)) {
>isNullish(value3) : boolean
> : ^^^^^^^
>isNullish : (value: unknown) => value is null | undefined
> : ^ ^^ ^^^^^
>value3 : unknown
> : ^^^^^^^

value3;
>value3 : null | undefined
> : ^^^^^^^^^^^^^^^^
}

declare const value4: unknown;
>value4 : unknown
> : ^^^^^^^

if (!isNullish(value4)) {
>!isNullish(value4) : boolean
> : ^^^^^^^
>isNullish(value4) : boolean
> : ^^^^^^^
>isNullish : (value: unknown) => value is null | undefined
> : ^ ^^ ^^^^^
>value4 : unknown
> : ^^^^^^^

value4;
>value4 : {}
> : ^^
}

declare class A { foo: string; }
>A : A
> : ^
>foo : string
> : ^^^^^^

declare function isA(value: unknown): value is A;
>isA : (value: unknown) => value is A
> : ^ ^^ ^^^^^
>value : unknown
> : ^^^^^^^

declare const value5: unknown;
>value5 : unknown
> : ^^^^^^^

if (isA(value5)) {
>isA(value5) : boolean
> : ^^^^^^^
>isA : (value: unknown) => value is A
> : ^ ^^ ^^^^^
>value5 : unknown
> : ^^^^^^^

value5;
>value5 : A
> : ^
}

declare const value6: unknown;
>value6 : unknown
> : ^^^^^^^

if (!isA(value6)) {
>!isA(value6) : boolean
> : ^^^^^^^
>isA(value6) : boolean
> : ^^^^^^^
>isA : (value: unknown) => value is A
> : ^ ^^ ^^^^^
>value6 : unknown
> : ^^^^^^^

value6;
>value6 : unknown
> : ^^^^^^^
}

38 changes: 38 additions & 0 deletions tests/cases/compiler/narrowUnknownByTypePredicate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @strict: true
// @noEmit: true

declare function isNotNullish(value: unknown): value is {};
declare function isNullish(value: unknown): value is null | undefined;

declare const value1: unknown;
if (isNotNullish(value1)) {
value1;
}

declare const value2: unknown;
if (!isNotNullish(value2)) {
value2;
}

declare const value3: unknown;
if (isNullish(value3)) {
value3;
}

declare const value4: unknown;
if (!isNullish(value4)) {
value4;
}

declare class A { foo: string; }
declare function isA(value: unknown): value is A;

declare const value5: unknown;
if (isA(value5)) {
value5;
}

declare const value6: unknown;
if (!isA(value6)) {
value6;
}

0 comments on commit 0dda037

Please sign in to comment.