From 257abf68c9f64425b2e3e0eabd68bfbc0cd32d9b Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Fri, 10 May 2024 12:03:37 -0400 Subject: [PATCH 01/10] Infer AssertsIdentifier type predicates --- src/compiler/checker.ts | 54 +++- .../reference/assertionTypePredicates1.js | 8 +- .../reference/assertionTypePredicates1.types | 16 +- ...assCanExtendConstructorFunction.errors.txt | 4 +- .../classCanExtendConstructorFunction.types | 4 +- .../coAndContraVariantInferences2.types | 4 +- .../dependentDestructuredVariables.js | 7 +- .../dependentDestructuredVariables.types | 12 +- .../reference/discriminatedUnionTypes1.types | 4 +- .../flatArrayNoExcessiveStackDepth.js | 2 +- .../flatArrayNoExcessiveStackDepth.types | 8 +- .../reference/inferTypePredicates.errors.txt | 82 +++++ .../reference/inferTypePredicates.js | 169 ++++++++++ .../reference/inferTypePredicates.symbols | 168 ++++++++++ .../reference/inferTypePredicates.types | 299 ++++++++++++++++++ .../reference/narrowByBooleanComparison.types | 4 +- .../reference/narrowingByTypeofInSwitch.types | 8 +- .../reference/narrowingUnionToUnion.js | 4 +- .../reference/narrowingUnionToUnion.types | 8 +- .../reference/neverReturningFunctions1.types | 16 +- ...privateNamesAssertion(target=es2022).types | 8 +- ...privateNamesAssertion(target=esnext).types | 8 +- .../reference/reachabilityChecks4.types | 4 +- .../baselines/reference/unknownControlFlow.js | 2 +- .../reference/unknownControlFlow.types | 4 +- tests/cases/compiler/inferTypePredicates.ts | 82 +++++ .../codeFixUnusedIdentifier_all_delete.ts | 2 +- 27 files changed, 922 insertions(+), 69 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 42e33531450ec..98a3c359d323d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15308,10 +15308,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { createTypePredicateFromTypePredicateNode(type, signature) : jsdocPredicate || noTypePredicate; } - else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) { + else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & (TypeFlags.Boolean | TypeFlags.VoidLike)) && getParameterCount(signature) > 0) { const { declaration } = signature; signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop - signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; + if (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) { + signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; + } + else if (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.VoidLike) { + signature.resolvedTypePredicate = getTypeAssertionFromBody(declaration) || noTypePredicate; + } } else { signature.resolvedTypePredicate = noTypePredicate; @@ -37856,6 +37861,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; } + function getTypeAssertionFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined { + switch (func.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return undefined; + } + const functionFlags = getFunctionFlags(func); + if (functionFlags !== FunctionFlags.Normal || !func.body) return undefined; + + const returnFlowNodes: FlowNode[] = []; + const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => { + if (!returnStatement.flowNode) { + return true; + } + returnFlowNodes.push(returnStatement.flowNode); + }); + if (bailedEarly) return undefined; + if (functionHasImplicitReturn(func)) { + returnFlowNodes.push(func.endFlowNode!); + } + if (!returnFlowNodes.length) return undefined; + + return forEach(func.parameters, (param, i) => { + const initType = getTypeOfSymbol(param.symbol); + if (!initType || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) { + return; + } + const typesAtReturn: Type[] = []; + const bailedEarly = forEach(returnFlowNodes, flowNode => { + const type = getFlowTypeOfReference(param.name, initType, initType, func, flowNode); + if (type === initType) return true; + typesAtReturn.push(type); + }); + if (bailedEarly) return; + // The asserted type might union back to be the same as the initType, which would not be a useful assertion. + // An assignability check covers this, but a void initType can become an undefined type through control flow analysis. + // Since void is not assignable to undefined, we patch initType to handle this, too. + const assertedType = getUnionType(typesAtReturn, UnionReduction.Subtype); + const patchedInitType = mapType(initType, t => t.flags & TypeFlags.Void ? undefinedType : t); + if (isTypeAssignableTo(patchedInitType, assertedType)) return; + return createTypePredicate(TypePredicateKind.AssertsIdentifier, unescapeLeadingUnderscores(param.name.escapedText), i, assertedType); + }); + } + /** * TypeScript Specification 1.0 (6.3) - July 2014 * An explicitly typed function whose return type isn't the Void type, diff --git a/tests/baselines/reference/assertionTypePredicates1.js b/tests/baselines/reference/assertionTypePredicates1.js index 4f1b02f42677a..848416deddce1 100644 --- a/tests/baselines/reference/assertionTypePredicates1.js +++ b/tests/baselines/reference/assertionTypePredicates1.js @@ -397,7 +397,7 @@ declare function assertIsArrayOfStrings(value: unknown): asserts value is string declare function assertDefined(value: T): asserts value is NonNullable; declare function f01(x: unknown): void; declare function f02(x: string | undefined): void; -declare function f03(x: string | undefined, assert: (value: unknown) => asserts value): void; +declare function f03(x: string | undefined, assert: (value: unknown) => asserts value): asserts x is string; declare namespace Debug { function assert(value: unknown, message?: string): asserts value; function assertDefined(value: T): asserts value is NonNullable; @@ -409,14 +409,14 @@ declare class Test { assertIsTest2(): asserts this is Test2; assertThis(): asserts this; bar(): void; - foo(x: unknown): void; + foo(x: unknown): asserts x is string; baz(x: number): void; } declare class Test2 extends Test { z: number; } declare class Derived extends Test { - foo(x: unknown): void; + foo(x: unknown): asserts x is string; baz(x: number): void; } declare function f11(items: Test[]): void; @@ -429,7 +429,7 @@ declare class Wat { get p2(): asserts this is string; set p2(x: asserts this is string); } -declare function f20(x: unknown): void; +declare function f20(x: unknown): asserts x is string; interface Thing { good: boolean; isGood(): asserts this is GoodThing; diff --git a/tests/baselines/reference/assertionTypePredicates1.types b/tests/baselines/reference/assertionTypePredicates1.types index e1670c5b5822f..abb26100c32fd 100644 --- a/tests/baselines/reference/assertionTypePredicates1.types +++ b/tests/baselines/reference/assertionTypePredicates1.types @@ -406,8 +406,8 @@ function f02(x: string | undefined) { } function f03(x: string | undefined, assert: (value: unknown) => asserts value) { ->f03 : (x: string | undefined, assert: (value: unknown) => asserts value) => void -> : ^ ^^ ^^ ^^ ^^^^^^^^^ +>f03 : (x: string | undefined, assert: (value: unknown) => asserts value) => asserts x is string +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : string | undefined > : ^^^^^^^^^^^^^^^^^^ >assert : (value: unknown) => asserts value @@ -656,8 +656,8 @@ class Test { > : ^^^^ } foo(x: unknown) { ->foo : (x: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>foo : (x: unknown) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : unknown > : ^^^^^^^ @@ -767,8 +767,8 @@ class Derived extends Test { > : ^^^^ foo(x: unknown) { ->foo : (x: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>foo : (x: unknown) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : unknown > : ^^^^^^^ @@ -918,8 +918,8 @@ declare class Wat { } function f20(x: unknown) { ->f20 : (x: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>f20 : (x: unknown) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt index 6be00dcf3fb7c..8e25440d2c7dc 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt +++ b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt @@ -1,6 +1,6 @@ first.js(23,9): error TS2554: Expected 1 arguments, but got 0. first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'. - Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'. + Type '(files: string[], format: "csv" | "json" | "xmlolololol") => asserts format is "csv" | "json"' is not assignable to type '(supplies?: any[]) => void'. Target signature provides too few arguments. Expected 2 or more, but got 1. first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type. generic.js(19,19): error TS2554: Expected 1 arguments, but got 0. @@ -52,7 +52,7 @@ second.ts(17,15): error TS2345: Argument of type 'string' is not assignable to p load(files, format) { ~~~~ !!! error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'. -!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'. +!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => asserts format is "csv" | "json"' is not assignable to type '(supplies?: any[]) => void'. !!! error TS2416: Target signature provides too few arguments. Expected 2 or more, but got 1. if (format === "xmlolololol") { throw new Error("please do not use XML. It was a joke."); diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types index e3633aa2a3e97..9f2cdb91c36ce 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.types +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -170,8 +170,8 @@ class Sql extends Wagon { * This is not assignable, so should have a type error */ load(files, format) { ->load : (files: Array, format: "csv" | "json" | "xmlolololol") => void -> : ^ ^^ ^^ ^^ ^^^^^^^^^ +>load : (files: Array, format: "csv" | "json" | "xmlolololol") => asserts format is "csv" | "json" +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >files : string[] > : ^^^^^^^^ >format : "csv" | "json" | "xmlolololol" diff --git a/tests/baselines/reference/coAndContraVariantInferences2.types b/tests/baselines/reference/coAndContraVariantInferences2.types index 7e4168ba80b08..5d4c98c5b2ade 100644 --- a/tests/baselines/reference/coAndContraVariantInferences2.types +++ b/tests/baselines/reference/coAndContraVariantInferences2.types @@ -266,8 +266,8 @@ declare function assertNode(node: Node | undefined, test: ((node: Node) => boole > : ^^^^ function foo(node: FunctionDeclaration | CaseClause) { ->foo : (node: FunctionDeclaration | CaseClause) => void -> : ^ ^^ ^^^^^^^^^ +>foo : (node: FunctionDeclaration | CaseClause) => asserts node is FunctionDeclaration +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >node : CaseClause | FunctionDeclaration > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/baselines/reference/dependentDestructuredVariables.js b/tests/baselines/reference/dependentDestructuredVariables.js index 7d32808909bf7..9b1082cbab399 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.js +++ b/tests/baselines/reference/dependentDestructuredVariables.js @@ -954,14 +954,17 @@ declare function foo({ value1, test1, test2, test3, test4, test5, test6, test7, test8?: any; test9?: any; }): void; -declare function fa1(x: [true, number] | [false, string]): void; +declare function fa1(x: [true, number] | [false, string]): asserts x is [false, string]; declare function fa2(x: { guard: true; value: number; } | { guard: false; value: string; -}): void; +}): asserts x is { + guard: false; + value: string; +}; declare const fa3: (...args: [true, number] | [false, string]) => void; interface ClientEvents { warn: [message: string]; diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types index 2c9d385bebe84..1a2716aa2259b 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.types +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -1565,8 +1565,8 @@ function foo({ // Repro from #49772 function fa1(x: [true, number] | [false, string]) { ->fa1 : (x: [true, number] | [false, string]) => void -> : ^ ^^ ^^^^^^^^^ +>fa1 : (x: [true, number] | [false, string]) => asserts x is [false, string] +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : [true, number] | [false, string] > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >true : true @@ -1609,8 +1609,8 @@ function fa1(x: [true, number] | [false, string]) { } function fa2(x: { guard: true, value: number } | { guard: false, value: string }) { ->fa2 : (x: { guard: true; value: number; } | { guard: false; value: string; }) => void -> : ^ ^^ ^^^^^^^^^ +>fa2 : (x: { guard: true; value: number; } | { guard: false; value: string; }) => asserts x is { guard: false; value: string; } +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^ >x : { guard: true; value: number; } | { guard: false; value: string; } > : ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^ >guard : true @@ -1669,8 +1669,8 @@ const fa3: (...args: [true, number] | [false, string]) => void = (guard, value) > : ^^^^ >false : false > : ^^^^^ ->(guard, value) => { if (guard) { for (;;) { value; // number } } else { while (!!true) { value; // string } }} : (guard: boolean, value: string | number) => void -> : ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>(guard, value) => { if (guard) { for (;;) { value; // number } } else { while (!!true) { value; // string } }} : (guard: boolean, value: string | number) => asserts guard is false +> : ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >guard : boolean > : ^^^^^^^ >value : string | number diff --git a/tests/baselines/reference/discriminatedUnionTypes1.types b/tests/baselines/reference/discriminatedUnionTypes1.types index 6504bd122acff..13c858a32e966 100644 --- a/tests/baselines/reference/discriminatedUnionTypes1.types +++ b/tests/baselines/reference/discriminatedUnionTypes1.types @@ -659,8 +659,8 @@ function f7(m: Message) { } function f8(m: Message) { ->f8 : (m: Message) => void -> : ^ ^^ ^^^^^^^^^ +>f8 : (m: Message) => asserts m is { kind: "A"; x: string; } | { kind: "B" | "C"; y: number; } +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >m : Message > : ^^^^^^^ diff --git a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js index 217c8bfb4d279..47161c32be9ff 100644 --- a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js +++ b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js @@ -47,5 +47,5 @@ declare const foo: unknown[]; declare const bar: string[]; interface Foo extends Array { } -declare const repro_43249: (value: unknown) => void; +declare const repro_43249: (value: unknown) => asserts value is string; declare function f(x: FlatArray, y: FlatArray): void; diff --git a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types index e98b06239fdb3..189c545ad51cb 100644 --- a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types +++ b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types @@ -36,10 +36,10 @@ interface Foo extends Array {} // Repros from comments in #43249 const repro_43249 = (value: unknown) => { ->repro_43249 : (value: unknown) => void -> : ^ ^^ ^^^^^^^^^ ->(value: unknown) => { if (typeof value !== "string") { throw new Error("No"); } const match = value.match(/anything/) || []; const [, extracted] = match;} : (value: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>repro_43249 : (value: unknown) => asserts value is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>(value: unknown) => { if (typeof value !== "string") { throw new Error("No"); } const match = value.match(/anything/) || []; const [, extracted] = match;} : (value: unknown) => asserts value is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >value : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index e61c1cb72beef..49949f1b62428 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -325,4 +325,86 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 if (foobarPred(foobar)) { foobar.foo; } + + function assertIsNumber(x: unknown) { + if (typeof x !== 'number') { + throw new Error(); + } + } + + function assertIsSmallNumber(x: unknown) { + if (typeof x === 'number' && x < 10) { + return; + } + throw new Error(); + } + + function assertMultipleReturns(x: unknown) { + if (x instanceof Date) { + return; + } else if (x instanceof RegExp) { + return; + } else { + throw new Error(); + } + } + + function assertChained(x: number | string) { + assertIsNumber(x); + } + + function assertOneParam(a: unknown, b: unknown) { + assertIsSmallNumber(b); + } + + function nonAssertion(a: number | string) { + if (typeof a === 'number') { + return; + } else if (typeof a === 'string') { + return; + } + throw new Error(); + } + + function justAssert(x: unknown) { + throw new Error(); + } + + function assertMultiple(a: unknown, b: unknown) { + assertIsNumber(a); + assertIsNumber(b); + } + + // should not return "asserts x is Date | undefined". + function assertOptional(x?: Date) { + if (x) { + return; + } + } + + // should not return "asserts x is {} | null | undefined". + function splitUnknown(x: unknown) { + if (x === null) { + return; + } else if (x === undefined) { + return; + } + } + + function assertionViaInfiniteLoop(x: string | number) { + if (typeof x === 'string') { + for (;;) {} + } + } + + function booleanOrVoid(a: boolean | void) { + if (typeof a === "undefined") { + a + } + a + } + + function assertTrue(x: boolean) { + if (!x) throw new Error(); + } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index b5802c0e50a99..8c9ea4fb1b0e4 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -279,6 +279,88 @@ const foobarPred = (fb: typeof foobar) => fb.type === "foo"; if (foobarPred(foobar)) { foobar.foo; } + +function assertIsNumber(x: unknown) { + if (typeof x !== 'number') { + throw new Error(); + } +} + +function assertIsSmallNumber(x: unknown) { + if (typeof x === 'number' && x < 10) { + return; + } + throw new Error(); +} + +function assertMultipleReturns(x: unknown) { + if (x instanceof Date) { + return; + } else if (x instanceof RegExp) { + return; + } else { + throw new Error(); + } +} + +function assertChained(x: number | string) { + assertIsNumber(x); +} + +function assertOneParam(a: unknown, b: unknown) { + assertIsSmallNumber(b); +} + +function nonAssertion(a: number | string) { + if (typeof a === 'number') { + return; + } else if (typeof a === 'string') { + return; + } + throw new Error(); +} + +function justAssert(x: unknown) { + throw new Error(); +} + +function assertMultiple(a: unknown, b: unknown) { + assertIsNumber(a); + assertIsNumber(b); +} + +// should not return "asserts x is Date | undefined". +function assertOptional(x?: Date) { + if (x) { + return; + } +} + +// should not return "asserts x is {} | null | undefined". +function splitUnknown(x: unknown) { + if (x === null) { + return; + } else if (x === undefined) { + return; + } +} + +function assertionViaInfiniteLoop(x: string | number) { + if (typeof x === 'string') { + for (;;) {} + } +} + +function booleanOrVoid(a: boolean | void) { + if (typeof a === "undefined") { + a + } + a +} + +function assertTrue(x: boolean) { + if (!x) throw new Error(); +} //// [inferTypePredicates.js] @@ -538,6 +620,80 @@ var foobarPred = function (fb) { return fb.type === "foo"; }; if (foobarPred(foobar)) { foobar.foo; } +function assertIsNumber(x) { + if (typeof x !== 'number') { + throw new Error(); + } +} +function assertIsSmallNumber(x) { + if (typeof x === 'number' && x < 10) { + return; + } + throw new Error(); +} +function assertMultipleReturns(x) { + if (x instanceof Date) { + return; + } + else if (x instanceof RegExp) { + return; + } + else { + throw new Error(); + } +} +function assertChained(x) { + assertIsNumber(x); +} +function assertOneParam(a, b) { + assertIsSmallNumber(b); +} +function nonAssertion(a) { + if (typeof a === 'number') { + return; + } + else if (typeof a === 'string') { + return; + } + throw new Error(); +} +function justAssert(x) { + throw new Error(); +} +function assertMultiple(a, b) { + assertIsNumber(a); + assertIsNumber(b); +} +// should not return "asserts x is Date | undefined". +function assertOptional(x) { + if (x) { + return; + } +} +// should not return "asserts x is {} | null | undefined". +function splitUnknown(x) { + if (x === null) { + return; + } + else if (x === undefined) { + return; + } +} +function assertionViaInfiniteLoop(x) { + if (typeof x === 'string') { + for (;;) { } + } +} +function booleanOrVoid(a) { + if (typeof a === "undefined") { + a; + } + a; +} +function assertTrue(x) { + if (!x) + throw new Error(); +} //// [inferTypePredicates.d.ts] @@ -630,3 +786,16 @@ declare const foobarPred: (fb: typeof foobar) => fb is { type: "foo"; foo: number; }; +declare function assertIsNumber(x: unknown): asserts x is number; +declare function assertIsSmallNumber(x: unknown): asserts x is number; +declare function assertMultipleReturns(x: unknown): asserts x is RegExp | Date; +declare function assertChained(x: number | string): asserts x is number; +declare function assertOneParam(a: unknown, b: unknown): asserts b is number; +declare function nonAssertion(a: number | string): void; +declare function justAssert(x: unknown): void; +declare function assertMultiple(a: unknown, b: unknown): asserts a is number; +declare function assertOptional(x?: Date): void; +declare function splitUnknown(x: unknown): void; +declare function assertionViaInfiniteLoop(x: string | number): asserts x is number; +declare function booleanOrVoid(a: boolean | void): void; +declare function assertTrue(x: boolean): asserts x is true; diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index 8fd879787c205..297a50df5b911 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -777,3 +777,171 @@ if (foobarPred(foobar)) { >foo : Symbol(foo, Decl(inferTypePredicates.ts, 271, 18)) } +function assertIsNumber(x: unknown) { +>assertIsNumber : Symbol(assertIsNumber, Decl(inferTypePredicates.ts, 277, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 279, 24)) + + if (typeof x !== 'number') { +>x : Symbol(x, Decl(inferTypePredicates.ts, 279, 24)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function assertIsSmallNumber(x: unknown) { +>assertIsSmallNumber : Symbol(assertIsSmallNumber, Decl(inferTypePredicates.ts, 283, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 285, 29)) + + if (typeof x === 'number' && x < 10) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 285, 29)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 285, 29)) + + return; + } + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + +function assertMultipleReturns(x: unknown) { +>assertMultipleReturns : Symbol(assertMultipleReturns, Decl(inferTypePredicates.ts, 290, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 292, 31)) + + if (x instanceof Date) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 292, 31)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --)) + + return; + } else if (x instanceof RegExp) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 292, 31)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + return; + } else { + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function assertChained(x: number | string) { +>assertChained : Symbol(assertChained, Decl(inferTypePredicates.ts, 300, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 302, 23)) + + assertIsNumber(x); +>assertIsNumber : Symbol(assertIsNumber, Decl(inferTypePredicates.ts, 277, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 302, 23)) +} + +function assertOneParam(a: unknown, b: unknown) { +>assertOneParam : Symbol(assertOneParam, Decl(inferTypePredicates.ts, 304, 1)) +>a : Symbol(a, Decl(inferTypePredicates.ts, 306, 24)) +>b : Symbol(b, Decl(inferTypePredicates.ts, 306, 35)) + + assertIsSmallNumber(b); +>assertIsSmallNumber : Symbol(assertIsSmallNumber, Decl(inferTypePredicates.ts, 283, 1)) +>b : Symbol(b, Decl(inferTypePredicates.ts, 306, 35)) +} + +function nonAssertion(a: number | string) { +>nonAssertion : Symbol(nonAssertion, Decl(inferTypePredicates.ts, 308, 1)) +>a : Symbol(a, Decl(inferTypePredicates.ts, 310, 22)) + + if (typeof a === 'number') { +>a : Symbol(a, Decl(inferTypePredicates.ts, 310, 22)) + + return; + } else if (typeof a === 'string') { +>a : Symbol(a, Decl(inferTypePredicates.ts, 310, 22)) + + return; + } + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + +function justAssert(x: unknown) { +>justAssert : Symbol(justAssert, Decl(inferTypePredicates.ts, 317, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 319, 20)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + +function assertMultiple(a: unknown, b: unknown) { +>assertMultiple : Symbol(assertMultiple, Decl(inferTypePredicates.ts, 321, 1)) +>a : Symbol(a, Decl(inferTypePredicates.ts, 323, 24)) +>b : Symbol(b, Decl(inferTypePredicates.ts, 323, 35)) + + assertIsNumber(a); +>assertIsNumber : Symbol(assertIsNumber, Decl(inferTypePredicates.ts, 277, 1)) +>a : Symbol(a, Decl(inferTypePredicates.ts, 323, 24)) + + assertIsNumber(b); +>assertIsNumber : Symbol(assertIsNumber, Decl(inferTypePredicates.ts, 277, 1)) +>b : Symbol(b, Decl(inferTypePredicates.ts, 323, 35)) +} + +// should not return "asserts x is Date | undefined". +function assertOptional(x?: Date) { +>assertOptional : Symbol(assertOptional, Decl(inferTypePredicates.ts, 326, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 329, 24)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --)) + + if (x) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 329, 24)) + + return; + } +} + +// should not return "asserts x is {} | null | undefined". +function splitUnknown(x: unknown) { +>splitUnknown : Symbol(splitUnknown, Decl(inferTypePredicates.ts, 333, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 336, 22)) + + if (x === null) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 336, 22)) + + return; + } else if (x === undefined) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 336, 22)) +>undefined : Symbol(undefined) + + return; + } +} + +function assertionViaInfiniteLoop(x: string | number) { +>assertionViaInfiniteLoop : Symbol(assertionViaInfiniteLoop, Decl(inferTypePredicates.ts, 342, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 344, 34)) + + if (typeof x === 'string') { +>x : Symbol(x, Decl(inferTypePredicates.ts, 344, 34)) + + for (;;) {} + } +} + +function booleanOrVoid(a: boolean | void) { +>booleanOrVoid : Symbol(booleanOrVoid, Decl(inferTypePredicates.ts, 348, 1)) +>a : Symbol(a, Decl(inferTypePredicates.ts, 350, 23)) + + if (typeof a === "undefined") { +>a : Symbol(a, Decl(inferTypePredicates.ts, 350, 23)) + + a +>a : Symbol(a, Decl(inferTypePredicates.ts, 350, 23)) + } + a +>a : Symbol(a, Decl(inferTypePredicates.ts, 350, 23)) +} + +function assertTrue(x: boolean) { +>assertTrue : Symbol(assertTrue, Decl(inferTypePredicates.ts, 355, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 357, 20)) + + if (!x) throw new Error(); +>x : Symbol(x, Decl(inferTypePredicates.ts, 357, 20)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index 8bdedfc9c3532..3876c7346cf8d 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -1649,3 +1649,302 @@ if (foobarPred(foobar)) { > : ^^^^^^ } +function assertIsNumber(x: unknown) { +>assertIsNumber : (x: unknown) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (typeof x !== 'number') { +>typeof x !== 'number' : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } +} + +function assertIsSmallNumber(x: unknown) { +>assertIsSmallNumber : (x: unknown) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (typeof x === 'number' && x < 10) { +>typeof x === 'number' && x < 10 : boolean +> : ^^^^^^^ +>typeof x === 'number' : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ +>'number' : "number" +> : ^^^^^^^^ +>x < 10 : boolean +> : ^^^^^^^ +>x : number +> : ^^^^^^ +>10 : 10 +> : ^^ + + return; + } + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +} + +function assertMultipleReturns(x: unknown) { +>assertMultipleReturns : (x: unknown) => asserts x is RegExp | Date +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (x instanceof Date) { +>x instanceof Date : boolean +> : ^^^^^^^ +>x : unknown +> : ^^^^^^^ +>Date : DateConstructor +> : ^^^^^^^^^^^^^^^ + + return; + } else if (x instanceof RegExp) { +>x instanceof RegExp : boolean +> : ^^^^^^^ +>x : unknown +> : ^^^^^^^ +>RegExp : RegExpConstructor +> : ^^^^^^^^^^^^^^^^^ + + return; + } else { + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } +} + +function assertChained(x: number | string) { +>assertChained : (x: number | string) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number +> : ^^^^^^^^^^^^^^^ + + assertIsNumber(x); +>assertIsNumber(x) : void +> : ^^^^ +>assertIsNumber : (x: unknown) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number +> : ^^^^^^^^^^^^^^^ +} + +function assertOneParam(a: unknown, b: unknown) { +>assertOneParam : (a: unknown, b: unknown) => asserts b is number +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>a : unknown +> : ^^^^^^^ +>b : unknown +> : ^^^^^^^ + + assertIsSmallNumber(b); +>assertIsSmallNumber(b) : void +> : ^^^^ +>assertIsSmallNumber : (x: unknown) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>b : unknown +> : ^^^^^^^ +} + +function nonAssertion(a: number | string) { +>nonAssertion : (a: number | string) => void +> : ^ ^^ ^^^^^^^^^ +>a : string | number +> : ^^^^^^^^^^^^^^^ + + if (typeof a === 'number') { +>typeof a === 'number' : boolean +> : ^^^^^^^ +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : string | number +> : ^^^^^^^^^^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + + return; + } else if (typeof a === 'string') { +>typeof a === 'string' : boolean +> : ^^^^^^^ +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : string +> : ^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + + return; + } + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +} + +function justAssert(x: unknown) { +>justAssert : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +} + +function assertMultiple(a: unknown, b: unknown) { +>assertMultiple : (a: unknown, b: unknown) => asserts a is number +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>a : unknown +> : ^^^^^^^ +>b : unknown +> : ^^^^^^^ + + assertIsNumber(a); +>assertIsNumber(a) : void +> : ^^^^ +>assertIsNumber : (x: unknown) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>a : unknown +> : ^^^^^^^ + + assertIsNumber(b); +>assertIsNumber(b) : void +> : ^^^^ +>assertIsNumber : (x: unknown) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>b : unknown +> : ^^^^^^^ +} + +// should not return "asserts x is Date | undefined". +function assertOptional(x?: Date) { +>assertOptional : (x?: Date) => void +> : ^ ^^^ ^^^^^^^^^ +>x : Date | undefined +> : ^^^^^^^^^^^^^^^^ + + if (x) { +>x : Date | undefined +> : ^^^^^^^^^^^^^^^^ + + return; + } +} + +// should not return "asserts x is {} | null | undefined". +function splitUnknown(x: unknown) { +>splitUnknown : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (x === null) { +>x === null : boolean +> : ^^^^^^^ +>x : unknown +> : ^^^^^^^ + + return; + } else if (x === undefined) { +>x === undefined : boolean +> : ^^^^^^^ +>x : {} | undefined +> : ^^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return; + } +} + +function assertionViaInfiniteLoop(x: string | number) { +>assertionViaInfiniteLoop : (x: string | number) => asserts x is number +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number +> : ^^^^^^^^^^^^^^^ + + if (typeof x === 'string') { +>typeof x === 'string' : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number +> : ^^^^^^^^^^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + + for (;;) {} + } +} + +function booleanOrVoid(a: boolean | void) { +>booleanOrVoid : (a: boolean | void) => void +> : ^ ^^ ^^^^^^^^^ +>a : boolean | void +> : ^^^^^^^^^^^^^^ + + if (typeof a === "undefined") { +>typeof a === "undefined" : boolean +> : ^^^^^^^ +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : boolean | void +> : ^^^^^^^^^^^^^^ +>"undefined" : "undefined" +> : ^^^^^^^^^^^ + + a +>a : undefined +> : ^^^^^^^^^ + } + a +>a : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +} + +function assertTrue(x: boolean) { +>assertTrue : (x: boolean) => asserts x is true +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^ +>x : boolean +> : ^^^^^^^ + + if (!x) throw new Error(); +>!x : boolean +> : ^^^^^^^ +>x : boolean +> : ^^^^^^^ +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +} + diff --git a/tests/baselines/reference/narrowByBooleanComparison.types b/tests/baselines/reference/narrowByBooleanComparison.types index 3354afb8f3e18..4c4c4382d71e3 100644 --- a/tests/baselines/reference/narrowByBooleanComparison.types +++ b/tests/baselines/reference/narrowByBooleanComparison.types @@ -212,8 +212,8 @@ function test2(x: unknown) { // https://github.com/microsoft/TypeScript/issues/50712 function test3(foo: unknown) { ->test3 : (foo: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>test3 : (foo: unknown) => asserts foo is string | any[] +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >foo : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/narrowingByTypeofInSwitch.types b/tests/baselines/reference/narrowingByTypeofInSwitch.types index 44263196a5ab3..8fce0a249b734 100644 --- a/tests/baselines/reference/narrowingByTypeofInSwitch.types +++ b/tests/baselines/reference/narrowingByTypeofInSwitch.types @@ -1192,8 +1192,8 @@ function unknownNarrowing(x: unknown) { } function keyofNarrowing(k: keyof S) { ->keyofNarrowing : (k: keyof S) => void -> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^ +>keyofNarrowing : (k: keyof S) => asserts k is (keyof S & number) | (keyof S & symbol) | (keyof S & string) +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >k : keyof S > : ^^^^^^^ @@ -1625,8 +1625,8 @@ function fallThroughTestWithTempalte(x: string | number | boolean | object) { } function keyofNarrowingWithTemplate(k: keyof S) { ->keyofNarrowingWithTemplate : (k: keyof S) => void -> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^ +>keyofNarrowingWithTemplate : (k: keyof S) => asserts k is (keyof S & number) | (keyof S & symbol) | (keyof S & string) +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >k : keyof S > : ^^^^^^^ diff --git a/tests/baselines/reference/narrowingUnionToUnion.js b/tests/baselines/reference/narrowingUnionToUnion.js index efe6819d094e2..04e936831c3cf 100644 --- a/tests/baselines/reference/narrowingUnionToUnion.js +++ b/tests/baselines/reference/narrowingUnionToUnion.js @@ -498,12 +498,12 @@ type EmptyString = '' | null | undefined; declare function isEmpty(value: string | EmptyString): value is EmptyString; declare let test: string | null | undefined; declare function assert(value: any): asserts value is T; -declare function test1(foo: number | string | boolean): void; +declare function test1(foo: number | string | boolean): asserts foo is string | 1; declare function check1(x: unknown): x is (string | 0); declare function check2(x: unknown): x is ("hello" | 0); declare function test3(x: unknown): void; declare function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null; -declare function f1x(obj: (string | number)[] | null): void; +declare function f1x(obj: (string | number)[] | null): asserts obj is string[] | null; type MyDiscriminatedUnion = { type: 'A'; aProp: number; diff --git a/tests/baselines/reference/narrowingUnionToUnion.types b/tests/baselines/reference/narrowingUnionToUnion.types index 3f5f10e6e31e5..d85a09ad7157f 100644 --- a/tests/baselines/reference/narrowingUnionToUnion.types +++ b/tests/baselines/reference/narrowingUnionToUnion.types @@ -569,8 +569,8 @@ declare function assert(value: any): asserts value is T >value : any function test1(foo: number | string | boolean) { ->test1 : (foo: number | string | boolean) => void -> : ^ ^^ ^^^^^^^^^ +>test1 : (foo: number | string | boolean) => asserts foo is string | 1 +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >foo : string | number | boolean > : ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -722,8 +722,8 @@ function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asser > : ^^^^^^^^^^^^^^^^^^^^^^^^^^ function f1x(obj: (string | number)[] | null) { ->f1x : (obj: (string | number)[] | null) => void -> : ^ ^^ ^^^^^^^^^ +>f1x : (obj: (string | number)[] | null) => asserts obj is string[] | null +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >obj : (string | number)[] | null > : ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/baselines/reference/neverReturningFunctions1.types b/tests/baselines/reference/neverReturningFunctions1.types index 414d0799fe73f..5d5a0c788dcb4 100644 --- a/tests/baselines/reference/neverReturningFunctions1.types +++ b/tests/baselines/reference/neverReturningFunctions1.types @@ -17,8 +17,8 @@ function fail(message?: string): never { } function f01(x: string | undefined) { ->f01 : (x: string | undefined) => void -> : ^ ^^ ^^^^^^^^^ +>f01 : (x: string | undefined) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : string | undefined > : ^^^^^^^^^^^^^^^^^^ @@ -96,8 +96,8 @@ function f03(x: string) { } function f11(x: string | undefined, fail: (message?: string) => never) { ->f11 : (x: string | undefined, fail: (message?: string) => never) => void -> : ^ ^^ ^^ ^^ ^^^^^^^^^ +>f11 : (x: string | undefined, fail: (message?: string) => never) => asserts x is string +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : string | undefined > : ^^^^^^^^^^^^^^^^^^ >fail : (message?: string) => never @@ -198,8 +198,8 @@ namespace Debug { } function f21(x: string | undefined) { ->f21 : (x: string | undefined) => void -> : ^ ^^ ^^^^^^^^^ +>f21 : (x: string | undefined) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : string | undefined > : ^^^^^^^^^^^^^^^^^^ @@ -336,8 +336,8 @@ class Test { > : ^^^^^^^^^^^^^^^^^^ } f1(x: string | undefined) { ->f1 : (x: string | undefined) => void -> : ^ ^^ ^^^^^^^^^ +>f1 : (x: string | undefined) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >x : string | undefined > : ^^^^^^^^^^^^^^^^^^ diff --git a/tests/baselines/reference/privateNamesAssertion(target=es2022).types b/tests/baselines/reference/privateNamesAssertion(target=es2022).types index 16c3df1dec82f..b5d2a5054506e 100644 --- a/tests/baselines/reference/privateNamesAssertion(target=es2022).types +++ b/tests/baselines/reference/privateNamesAssertion(target=es2022).types @@ -30,8 +30,8 @@ class Foo { } } m1(v: unknown) { ->m1 : (v: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>m1 : (v: unknown) => asserts v is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >v : unknown > : ^^^^^^^ @@ -77,8 +77,8 @@ class Foo2 { } } m1(v: unknown) { ->m1 : (v: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>m1 : (v: unknown) => asserts v is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >v : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/privateNamesAssertion(target=esnext).types b/tests/baselines/reference/privateNamesAssertion(target=esnext).types index 16c3df1dec82f..b5d2a5054506e 100644 --- a/tests/baselines/reference/privateNamesAssertion(target=esnext).types +++ b/tests/baselines/reference/privateNamesAssertion(target=esnext).types @@ -30,8 +30,8 @@ class Foo { } } m1(v: unknown) { ->m1 : (v: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>m1 : (v: unknown) => asserts v is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >v : unknown > : ^^^^^^^ @@ -77,8 +77,8 @@ class Foo2 { } } m1(v: unknown) { ->m1 : (v: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>m1 : (v: unknown) => asserts v is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ >v : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/reachabilityChecks4.types b/tests/baselines/reference/reachabilityChecks4.types index d4863f80d4003..8cd183aa898b2 100644 --- a/tests/baselines/reference/reachabilityChecks4.types +++ b/tests/baselines/reference/reachabilityChecks4.types @@ -56,8 +56,8 @@ declare function fail(): never; > : ^^^^^^ function f1(x: 0 | 1 | 2) { ->f1 : (x: 0 | 1 | 2) => void -> : ^ ^^ ^^^^^^^^^ +>f1 : (x: 0 | 1 | 2) => asserts x is 1 | 2 +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ >x : 0 | 1 | 2 > : ^^^^^^^^^ diff --git a/tests/baselines/reference/unknownControlFlow.js b/tests/baselines/reference/unknownControlFlow.js index 8d6caa1281ba9..ff2d5e02ab7ea 100644 --- a/tests/baselines/reference/unknownControlFlow.js +++ b/tests/baselines/reference/unknownControlFlow.js @@ -866,7 +866,7 @@ declare function fx3(value: T & ({} | null)): void; declare function fx4(value: T & ({} | null)): void; declare function fx5(value: T & ({} | null)): void; declare function fx10(x: string | number, y: number): void; -declare function SendBlob(encoding: unknown): void; +declare function SendBlob(encoding: unknown): asserts encoding is "utf8" | undefined; declare function doSomething1(value: T): T; declare function doSomething2(value: unknown): void; type TypeA = { diff --git a/tests/baselines/reference/unknownControlFlow.types b/tests/baselines/reference/unknownControlFlow.types index 12a20e64e1ea3..567fa2a8e6b7a 100644 --- a/tests/baselines/reference/unknownControlFlow.types +++ b/tests/baselines/reference/unknownControlFlow.types @@ -1370,8 +1370,8 @@ function fx10(x: string | number, y: number) { // Repros from #50706 function SendBlob(encoding: unknown) { ->SendBlob : (encoding: unknown) => void -> : ^ ^^ ^^^^^^^^^ +>SendBlob : (encoding: unknown) => asserts encoding is "utf8" | undefined +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >encoding : unknown > : ^^^^^^^ diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index 9b996ee8c8414..4d9847dcdbf00 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -279,3 +279,85 @@ const foobarPred = (fb: typeof foobar) => fb.type === "foo"; if (foobarPred(foobar)) { foobar.foo; } + +function assertIsNumber(x: unknown) { + if (typeof x !== 'number') { + throw new Error(); + } +} + +function assertIsSmallNumber(x: unknown) { + if (typeof x === 'number' && x < 10) { + return; + } + throw new Error(); +} + +function assertMultipleReturns(x: unknown) { + if (x instanceof Date) { + return; + } else if (x instanceof RegExp) { + return; + } else { + throw new Error(); + } +} + +function assertChained(x: number | string) { + assertIsNumber(x); +} + +function assertOneParam(a: unknown, b: unknown) { + assertIsSmallNumber(b); +} + +function nonAssertion(a: number | string) { + if (typeof a === 'number') { + return; + } else if (typeof a === 'string') { + return; + } + throw new Error(); +} + +function justAssert(x: unknown) { + throw new Error(); +} + +function assertMultiple(a: unknown, b: unknown) { + assertIsNumber(a); + assertIsNumber(b); +} + +// should not return "asserts x is Date | undefined". +function assertOptional(x?: Date) { + if (x) { + return; + } +} + +// should not return "asserts x is {} | null | undefined". +function splitUnknown(x: unknown) { + if (x === null) { + return; + } else if (x === undefined) { + return; + } +} + +function assertionViaInfiniteLoop(x: string | number) { + if (typeof x === 'string') { + for (;;) {} + } +} + +function booleanOrVoid(a: boolean | void) { + if (typeof a === "undefined") { + a + } + a +} + +function assertTrue(x: boolean) { + if (!x) throw new Error(); +} diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts index eeef35e44735a..9cc310fb87ab8 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts @@ -68,7 +68,7 @@ import { x } from "foo"; import { x2, used2 } from "foo"; used1; used2; -function f() { +function f(a, b) { } function g(a) { return a; } function h(c) { return c; } From f89458821e3ab91f1a027ef8a9ed4900ba8a6002 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Fri, 10 May 2024 14:23:40 -0400 Subject: [PATCH 02/10] a few more tests --- tests/cases/compiler/inferTypePredicates.ts | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index 4d9847dcdbf00..6ba79a3971810 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -361,3 +361,27 @@ function booleanOrVoid(a: boolean | void) { function assertTrue(x: boolean) { if (!x) throw new Error(); } + +function assertNonNullish(x: T) { + if (x != null) { + return; + } + throw new Error(); +} + +function assertIsShortString(x: unknown) { + if (typeof x !== 'string') { + throw new Error('Expected string'); + } else if (x.length > 10) { + throw new Error('Expected short string'); + } +} + +function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E') { + if (x === 'A') { + return; // type of x here is 'A' + } else if (x === 'B' || x === 'C') { + throw new Error(); + } + // implicit return; type of x here is 'D' | E' +} From 03ce90fa248248ec3fdb50b5f1fa3e33299e7eba Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Sun, 12 May 2024 09:54:45 -0400 Subject: [PATCH 03/10] do not run on arrow functions --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 98a3c359d323d..fd4924bd6e197 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37866,13 +37866,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: + case SyntaxKind.ArrowFunction: return undefined; } const functionFlags = getFunctionFlags(func); if (functionFlags !== FunctionFlags.Normal || !func.body) return undefined; const returnFlowNodes: FlowNode[] = []; - const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => { + const bailedEarly = forEachReturnStatement(func.body, returnStatement => { if (!returnStatement.flowNode) { return true; } From 6214e93df9a7e13350adf9317470ab9d018f0652 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Sun, 12 May 2024 10:03:40 -0400 Subject: [PATCH 04/10] exclude methods as well --- src/compiler/checker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fd4924bd6e197..9d305a576d2f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37867,6 +37867,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: return undefined; } const functionFlags = getFunctionFlags(func); From 7b622716ab555f22ce9c867ddefc8c356d4be620 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Sun, 12 May 2024 10:03:47 -0400 Subject: [PATCH 05/10] new tests and baselines --- .../reference/inferTypePredicates.errors.txt | 53 ++++ .../reference/inferTypePredicates.js | 111 +++++++++ .../reference/inferTypePredicates.symbols | 115 +++++++++ .../reference/inferTypePredicates.types | 234 ++++++++++++++++++ tests/cases/compiler/inferTypePredicates.ts | 29 +++ 5 files changed, 542 insertions(+) diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index 49949f1b62428..6c68e82d93ef0 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -407,4 +407,57 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 function assertTrue(x: boolean) { if (!x) throw new Error(); } + + function assertNonNullish(x: T) { + if (x != null) { + return; + } + throw new Error(); + } + + function assertIsShortString(x: unknown) { + if (typeof x !== 'string') { + throw new Error('Expected string'); + } else if (x.length > 10) { + throw new Error('Expected short string'); + } + } + + function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E') { + if (x === 'A') { + return; // type of x here is 'A' + } else if (x === 'B' || x === 'C') { + throw new Error(); + } + // implicit return; type of x here is 'D' | E' + } + + // this is not expected to be inferred as an assertion type predicate + // due to https://github.com/microsoft/TypeScript/issues/34523 + const assertNumberArrow = (base: string | number) => { + if (typeof base !== 'number') { + throw new Error(); + } + }; + + assertNumberArrow('hello'); // should ok + + class Test { + // Methods are not inferred as assertion type predicates becasue you + // can easily run into TS2776 (https://github.com/microsoft/TypeScript/pull/33622). + assert(value: unknown) { + if (typeof value === 'number') { + return; + } + throw new Error(); + } + } + + function fTest(x: unknown) { + const t1 = new Test(); + t1.assert(typeof x === "string"); // should ok + + const t2: Test = new Test(); + t2.assert(typeof x === "string"); // should ok + } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index 8c9ea4fb1b0e4..c3afe770899d5 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -361,6 +361,59 @@ function booleanOrVoid(a: boolean | void) { function assertTrue(x: boolean) { if (!x) throw new Error(); } + +function assertNonNullish(x: T) { + if (x != null) { + return; + } + throw new Error(); +} + +function assertIsShortString(x: unknown) { + if (typeof x !== 'string') { + throw new Error('Expected string'); + } else if (x.length > 10) { + throw new Error('Expected short string'); + } +} + +function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E') { + if (x === 'A') { + return; // type of x here is 'A' + } else if (x === 'B' || x === 'C') { + throw new Error(); + } + // implicit return; type of x here is 'D' | E' +} + +// this is not expected to be inferred as an assertion type predicate +// due to https://github.com/microsoft/TypeScript/issues/34523 +const assertNumberArrow = (base: string | number) => { + if (typeof base !== 'number') { + throw new Error(); + } +}; + +assertNumberArrow('hello'); // should ok + +class Test { + // Methods are not inferred as assertion type predicates becasue you + // can easily run into TS2776 (https://github.com/microsoft/TypeScript/pull/33622). + assert(value: unknown) { + if (typeof value === 'number') { + return; + } + throw new Error(); + } +} + +function fTest(x: unknown) { + const t1 = new Test(); + t1.assert(typeof x === "string"); // should ok + + const t2: Test = new Test(); + t2.assert(typeof x === "string"); // should ok +} //// [inferTypePredicates.js] @@ -694,6 +747,56 @@ function assertTrue(x) { if (!x) throw new Error(); } +function assertNonNullish(x) { + if (x != null) { + return; + } + throw new Error(); +} +function assertIsShortString(x) { + if (typeof x !== 'string') { + throw new Error('Expected string'); + } + else if (x.length > 10) { + throw new Error('Expected short string'); + } +} +function assertABC(x) { + if (x === 'A') { + return; // type of x here is 'A' + } + else if (x === 'B' || x === 'C') { + throw new Error(); + } + // implicit return; type of x here is 'D' | E' +} +// this is not expected to be inferred as an assertion type predicate +// due to https://github.com/microsoft/TypeScript/issues/34523 +var assertNumberArrow = function (base) { + if (typeof base !== 'number') { + throw new Error(); + } +}; +assertNumberArrow('hello'); // should ok +var Test = /** @class */ (function () { + function Test() { + } + // Methods are not inferred as assertion type predicates becasue you + // can easily run into TS2776 (https://github.com/microsoft/TypeScript/pull/33622). + Test.prototype.assert = function (value) { + if (typeof value === 'number') { + return; + } + throw new Error(); + }; + return Test; +}()); +function fTest(x) { + var t1 = new Test(); + t1.assert(typeof x === "string"); // should ok + var t2 = new Test(); + t2.assert(typeof x === "string"); // should ok +} //// [inferTypePredicates.d.ts] @@ -799,3 +902,11 @@ declare function splitUnknown(x: unknown): void; declare function assertionViaInfiniteLoop(x: string | number): asserts x is number; declare function booleanOrVoid(a: boolean | void): void; declare function assertTrue(x: boolean): asserts x is true; +declare function assertNonNullish(x: T): asserts x is NonNullable; +declare function assertIsShortString(x: unknown): asserts x is string; +declare function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E'): asserts x is "A" | "D" | "E"; +declare const assertNumberArrow: (base: string | number) => void; +declare class Test { + assert(value: unknown): void; +} +declare function fTest(x: unknown): void; diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index 297a50df5b911..42683ec353e73 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -945,3 +945,118 @@ function assertTrue(x: boolean) { >Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) } +function assertNonNullish(x: T) { +>assertNonNullish : Symbol(assertNonNullish, Decl(inferTypePredicates.ts, 359, 1)) +>T : Symbol(T, Decl(inferTypePredicates.ts, 361, 26)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 361, 29)) +>T : Symbol(T, Decl(inferTypePredicates.ts, 361, 26)) + + if (x != null) { +>x : Symbol(x, Decl(inferTypePredicates.ts, 361, 29)) + + return; + } + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + +function assertIsShortString(x: unknown) { +>assertIsShortString : Symbol(assertIsShortString, Decl(inferTypePredicates.ts, 366, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 368, 29)) + + if (typeof x !== 'string') { +>x : Symbol(x, Decl(inferTypePredicates.ts, 368, 29)) + + throw new Error('Expected string'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + } else if (x.length > 10) { +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 368, 29)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + throw new Error('Expected short string'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E') { +>assertABC : Symbol(assertABC, Decl(inferTypePredicates.ts, 374, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 376, 19)) + + if (x === 'A') { +>x : Symbol(x, Decl(inferTypePredicates.ts, 376, 19)) + + return; // type of x here is 'A' + } else if (x === 'B' || x === 'C') { +>x : Symbol(x, Decl(inferTypePredicates.ts, 376, 19)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 376, 19)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + // implicit return; type of x here is 'D' | E' +} + +// this is not expected to be inferred as an assertion type predicate +// due to https://github.com/microsoft/TypeScript/issues/34523 +const assertNumberArrow = (base: string | number) => { +>assertNumberArrow : Symbol(assertNumberArrow, Decl(inferTypePredicates.ts, 387, 5)) +>base : Symbol(base, Decl(inferTypePredicates.ts, 387, 27)) + + if (typeof base !== 'number') { +>base : Symbol(base, Decl(inferTypePredicates.ts, 387, 27)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +}; + +assertNumberArrow('hello'); // should ok +>assertNumberArrow : Symbol(assertNumberArrow, Decl(inferTypePredicates.ts, 387, 5)) + +class Test { +>Test : Symbol(Test, Decl(inferTypePredicates.ts, 393, 27)) + + // Methods are not inferred as assertion type predicates becasue you + // can easily run into TS2776 (https://github.com/microsoft/TypeScript/pull/33622). + assert(value: unknown) { +>assert : Symbol(Test.assert, Decl(inferTypePredicates.ts, 395, 12)) +>value : Symbol(value, Decl(inferTypePredicates.ts, 398, 9)) + + if (typeof value === 'number') { +>value : Symbol(value, Decl(inferTypePredicates.ts, 398, 9)) + + return; + } + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function fTest(x: unknown) { +>fTest : Symbol(fTest, Decl(inferTypePredicates.ts, 404, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 406, 15)) + + const t1 = new Test(); +>t1 : Symbol(t1, Decl(inferTypePredicates.ts, 407, 7)) +>Test : Symbol(Test, Decl(inferTypePredicates.ts, 393, 27)) + + t1.assert(typeof x === "string"); // should ok +>t1.assert : Symbol(Test.assert, Decl(inferTypePredicates.ts, 395, 12)) +>t1 : Symbol(t1, Decl(inferTypePredicates.ts, 407, 7)) +>assert : Symbol(Test.assert, Decl(inferTypePredicates.ts, 395, 12)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 406, 15)) + + const t2: Test = new Test(); +>t2 : Symbol(t2, Decl(inferTypePredicates.ts, 410, 7)) +>Test : Symbol(Test, Decl(inferTypePredicates.ts, 393, 27)) +>Test : Symbol(Test, Decl(inferTypePredicates.ts, 393, 27)) + + t2.assert(typeof x === "string"); // should ok +>t2.assert : Symbol(Test.assert, Decl(inferTypePredicates.ts, 395, 12)) +>t2 : Symbol(t2, Decl(inferTypePredicates.ts, 410, 7)) +>assert : Symbol(Test.assert, Decl(inferTypePredicates.ts, 395, 12)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 406, 15)) +} + diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index 3876c7346cf8d..85c9317bd9251 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -1948,3 +1948,237 @@ function assertTrue(x: boolean) { > : ^^^^^^^^^^^^^^^^ } +function assertNonNullish(x: T) { +>assertNonNullish : (x: T) => asserts x is NonNullable +> : ^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : T +> : ^ + + if (x != null) { +>x != null : boolean +> : ^^^^^^^ +>x : T +> : ^ + + return; + } + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +} + +function assertIsShortString(x: unknown) { +>assertIsShortString : (x: unknown) => asserts x is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (typeof x !== 'string') { +>typeof x !== 'string' : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + + throw new Error('Expected string'); +>new Error('Expected string') : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>'Expected string' : "Expected string" +> : ^^^^^^^^^^^^^^^^^ + + } else if (x.length > 10) { +>x.length > 10 : boolean +> : ^^^^^^^ +>x.length : number +> : ^^^^^^ +>x : string +> : ^^^^^^ +>length : number +> : ^^^^^^ +>10 : 10 +> : ^^ + + throw new Error('Expected short string'); +>new Error('Expected short string') : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>'Expected short string' : "Expected short string" +> : ^^^^^^^^^^^^^^^^^^^^^^^ + } +} + +function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E') { +>assertABC : (x: "A" | "B" | "C" | "D" | "E") => asserts x is "A" | "D" | "E" +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : "A" | "B" | "C" | "D" | "E" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + if (x === 'A') { +>x === 'A' : boolean +> : ^^^^^^^ +>x : "A" | "B" | "C" | "D" | "E" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'A' : "A" +> : ^^^ + + return; // type of x here is 'A' + } else if (x === 'B' || x === 'C') { +>x === 'B' || x === 'C' : boolean +> : ^^^^^^^ +>x === 'B' : boolean +> : ^^^^^^^ +>x : "B" | "C" | "D" | "E" +> : ^^^^^^^^^^^^^^^^^^^^^ +>'B' : "B" +> : ^^^ +>x === 'C' : boolean +> : ^^^^^^^ +>x : "C" | "D" | "E" +> : ^^^^^^^^^^^^^^^ +>'C' : "C" +> : ^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } + // implicit return; type of x here is 'D' | E' +} + +// this is not expected to be inferred as an assertion type predicate +// due to https://github.com/microsoft/TypeScript/issues/34523 +const assertNumberArrow = (base: string | number) => { +>assertNumberArrow : (base: string | number) => void +> : ^ ^^ ^^^^^^^^^ +>(base: string | number) => { if (typeof base !== 'number') { throw new Error(); }} : (base: string | number) => void +> : ^ ^^ ^^^^^^^^^ +>base : string | number +> : ^^^^^^^^^^^^^^^ + + if (typeof base !== 'number') { +>typeof base !== 'number' : boolean +> : ^^^^^^^ +>typeof base : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>base : string | number +> : ^^^^^^^^^^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } +}; + +assertNumberArrow('hello'); // should ok +>assertNumberArrow('hello') : void +> : ^^^^ +>assertNumberArrow : (base: string | number) => void +> : ^ ^^ ^^^^^^^^^ +>'hello' : "hello" +> : ^^^^^^^ + +class Test { +>Test : Test +> : ^^^^ + + // Methods are not inferred as assertion type predicates becasue you + // can easily run into TS2776 (https://github.com/microsoft/TypeScript/pull/33622). + assert(value: unknown) { +>assert : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>value : unknown +> : ^^^^^^^ + + if (typeof value === 'number') { +>typeof value === 'number' : boolean +> : ^^^^^^^ +>typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : unknown +> : ^^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + + return; + } + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } +} + +function fTest(x: unknown) { +>fTest : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + const t1 = new Test(); +>t1 : Test +> : ^^^^ +>new Test() : Test +> : ^^^^ +>Test : typeof Test +> : ^^^^^^^^^^^ + + t1.assert(typeof x === "string"); // should ok +>t1.assert(typeof x === "string") : void +> : ^^^^ +>t1.assert : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>t1 : Test +> : ^^^^ +>assert : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>typeof x === "string" : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ +>"string" : "string" +> : ^^^^^^^^ + + const t2: Test = new Test(); +>t2 : Test +> : ^^^^ +>new Test() : Test +> : ^^^^ +>Test : typeof Test +> : ^^^^^^^^^^^ + + t2.assert(typeof x === "string"); // should ok +>t2.assert(typeof x === "string") : void +> : ^^^^ +>t2.assert : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>t2 : Test +> : ^^^^ +>assert : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>typeof x === "string" : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ +>"string" : "string" +> : ^^^^^^^^ +} + diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index 6ba79a3971810..041e401e78ae5 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -385,3 +385,32 @@ function assertABC(x: 'A' | 'B' | 'C' | 'D' | 'E') { } // implicit return; type of x here is 'D' | E' } + +// this is not expected to be inferred as an assertion type predicate +// due to https://github.com/microsoft/TypeScript/issues/34523 +const assertNumberArrow = (base: string | number) => { + if (typeof base !== 'number') { + throw new Error(); + } +}; + +assertNumberArrow('hello'); // should ok + +class Test { + // Methods are not inferred as assertion type predicates becasue you + // can easily run into TS2776 (https://github.com/microsoft/TypeScript/pull/33622). + assert(value: unknown) { + if (typeof value === 'number') { + return; + } + throw new Error(); + } +} + +function fTest(x: unknown) { + const t1 = new Test(); + t1.assert(typeof x === "string"); // should ok + + const t2: Test = new Test(); + t2.assert(typeof x === "string"); // should ok +} From 06426aa126927e336dd5c6865acba9ea2623621f Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Sun, 12 May 2024 11:15:38 -0400 Subject: [PATCH 06/10] more tests, fix bug with any --- src/compiler/checker.ts | 2 +- .../reference/inferTypePredicates.errors.txt | 40 +++++ .../reference/inferTypePredicates.js | 85 ++++++++++ .../reference/inferTypePredicates.symbols | 96 +++++++++++ .../reference/inferTypePredicates.types | 150 ++++++++++++++++++ tests/cases/compiler/inferTypePredicates.ts | 40 +++++ 6 files changed, 412 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9d305a576d2f0..72f857d0e3b01 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37902,7 +37902,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // An assignability check covers this, but a void initType can become an undefined type through control flow analysis. // Since void is not assignable to undefined, we patch initType to handle this, too. const assertedType = getUnionType(typesAtReturn, UnionReduction.Subtype); - const patchedInitType = mapType(initType, t => t.flags & TypeFlags.Void ? undefinedType : t); + const patchedInitType = mapType(initType, t => t.flags & TypeFlags.Void ? undefinedType : t.flags & TypeFlags.Any ? unknownType : t); if (isTypeAssignableTo(patchedInitType, assertedType)) return; return createTypePredicate(TypePredicateKind.AssertsIdentifier, unescapeLeadingUnderscores(param.name.escapedText), i, assertedType); }); diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index 6c68e82d93ef0..86fda855bec26 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -460,4 +460,44 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 const t2: Test = new Test(); t2.assert(typeof x === "string"); // should ok } + + interface Named { + name: string; + } + interface Aged { + age: number; + } + + declare function assertName(x: any): asserts x is Named; + declare function isNamed(x: any): x is Named; + + function inferFromTypePred(x: unknown) { + if (!isNamed(x)) { + throw new Error(); + } + } + + function inferFromTypePredAny(x: any) { + if (!isNamed(x)) { + throw new Error(); + } + } + + class Namer { + assertName(x: unknown) { + if (!isNamed(x)) { + throw new Error(); + } + } + assert(value: unknown) { + if (typeof value === 'number') { + return; + } + throw new Error(); + } + bar(x: Aged) { + this.assertName(x); + x.age // ok + } + } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index c3afe770899d5..a22e3afc01654 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -414,6 +414,46 @@ function fTest(x: unknown) { const t2: Test = new Test(); t2.assert(typeof x === "string"); // should ok } + +interface Named { + name: string; +} +interface Aged { + age: number; +} + +declare function assertName(x: any): asserts x is Named; +declare function isNamed(x: any): x is Named; + +function inferFromTypePred(x: unknown) { + if (!isNamed(x)) { + throw new Error(); + } +} + +function inferFromTypePredAny(x: any) { + if (!isNamed(x)) { + throw new Error(); + } +} + +class Namer { + assertName(x: unknown) { + if (!isNamed(x)) { + throw new Error(); + } + } + assert(value: unknown) { + if (typeof value === 'number') { + return; + } + throw new Error(); + } + bar(x: Aged) { + this.assertName(x); + x.age // ok + } +} //// [inferTypePredicates.js] @@ -797,6 +837,36 @@ function fTest(x) { var t2 = new Test(); t2.assert(typeof x === "string"); // should ok } +function inferFromTypePred(x) { + if (!isNamed(x)) { + throw new Error(); + } +} +function inferFromTypePredAny(x) { + if (!isNamed(x)) { + throw new Error(); + } +} +var Namer = /** @class */ (function () { + function Namer() { + } + Namer.prototype.assertName = function (x) { + if (!isNamed(x)) { + throw new Error(); + } + }; + Namer.prototype.assert = function (value) { + if (typeof value === 'number') { + return; + } + throw new Error(); + }; + Namer.prototype.bar = function (x) { + this.assertName(x); + x.age; // ok + }; + return Namer; +}()); //// [inferTypePredicates.d.ts] @@ -910,3 +980,18 @@ declare class Test { assert(value: unknown): void; } declare function fTest(x: unknown): void; +interface Named { + name: string; +} +interface Aged { + age: number; +} +declare function assertName(x: any): asserts x is Named; +declare function isNamed(x: any): x is Named; +declare function inferFromTypePred(x: unknown): asserts x is Named; +declare function inferFromTypePredAny(x: any): asserts x is Named; +declare class Namer { + assertName(x: unknown): void; + assert(value: unknown): void; + bar(x: Aged): void; +} diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index 42683ec353e73..80a0972d66873 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -1060,3 +1060,99 @@ function fTest(x: unknown) { >x : Symbol(x, Decl(inferTypePredicates.ts, 406, 15)) } +interface Named { +>Named : Symbol(Named, Decl(inferTypePredicates.ts, 412, 1)) + + name: string; +>name : Symbol(Named.name, Decl(inferTypePredicates.ts, 414, 17)) +} +interface Aged { +>Aged : Symbol(Aged, Decl(inferTypePredicates.ts, 416, 1)) + + age: number; +>age : Symbol(Aged.age, Decl(inferTypePredicates.ts, 417, 16)) +} + +declare function assertName(x: any): asserts x is Named; +>assertName : Symbol(assertName, Decl(inferTypePredicates.ts, 419, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 421, 28)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 421, 28)) +>Named : Symbol(Named, Decl(inferTypePredicates.ts, 412, 1)) + +declare function isNamed(x: any): x is Named; +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 422, 25)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 422, 25)) +>Named : Symbol(Named, Decl(inferTypePredicates.ts, 412, 1)) + +function inferFromTypePred(x: unknown) { +>inferFromTypePred : Symbol(inferFromTypePred, Decl(inferTypePredicates.ts, 422, 45)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 424, 27)) + + if (!isNamed(x)) { +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 424, 27)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function inferFromTypePredAny(x: any) { +>inferFromTypePredAny : Symbol(inferFromTypePredAny, Decl(inferTypePredicates.ts, 428, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 430, 30)) + + if (!isNamed(x)) { +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 430, 30)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +class Namer { +>Namer : Symbol(Namer, Decl(inferTypePredicates.ts, 434, 1)) + + assertName(x: unknown) { +>assertName : Symbol(Namer.assertName, Decl(inferTypePredicates.ts, 436, 13)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 437, 13)) + + if (!isNamed(x)) { +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 437, 13)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + } + assert(value: unknown) { +>assert : Symbol(Namer.assert, Decl(inferTypePredicates.ts, 441, 3)) +>value : Symbol(value, Decl(inferTypePredicates.ts, 442, 9)) + + if (typeof value === 'number') { +>value : Symbol(value, Decl(inferTypePredicates.ts, 442, 9)) + + return; + } + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + bar(x: Aged) { +>bar : Symbol(Namer.bar, Decl(inferTypePredicates.ts, 447, 3)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 448, 6)) +>Aged : Symbol(Aged, Decl(inferTypePredicates.ts, 416, 1)) + + this.assertName(x); +>this.assertName : Symbol(Namer.assertName, Decl(inferTypePredicates.ts, 436, 13)) +>this : Symbol(Namer, Decl(inferTypePredicates.ts, 434, 1)) +>assertName : Symbol(Namer.assertName, Decl(inferTypePredicates.ts, 436, 13)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 448, 6)) + + x.age // ok +>x.age : Symbol(Aged.age, Decl(inferTypePredicates.ts, 417, 16)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 448, 6)) +>age : Symbol(Aged.age, Decl(inferTypePredicates.ts, 417, 16)) + } +} + diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index 85c9317bd9251..495123e30f041 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -2182,3 +2182,153 @@ function fTest(x: unknown) { > : ^^^^^^^^ } +interface Named { + name: string; +>name : string +> : ^^^^^^ +} +interface Aged { + age: number; +>age : number +> : ^^^^^^ +} + +declare function assertName(x: any): asserts x is Named; +>assertName : (x: any) => asserts x is Named +> : ^ ^^ ^^^^^ +>x : any +> : ^^^ + +declare function isNamed(x: any): x is Named; +>isNamed : (x: any) => x is Named +> : ^ ^^ ^^^^^ +>x : any +> : ^^^ + +function inferFromTypePred(x: unknown) { +>inferFromTypePred : (x: unknown) => asserts x is Named +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (!isNamed(x)) { +>!isNamed(x) : boolean +> : ^^^^^^^ +>isNamed(x) : boolean +> : ^^^^^^^ +>isNamed : (x: any) => x is Named +> : ^ ^^ ^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } +} + +function inferFromTypePredAny(x: any) { +>inferFromTypePredAny : (x: any) => asserts x is Named +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ +>x : any +> : ^^^ + + if (!isNamed(x)) { +>!isNamed(x) : boolean +> : ^^^^^^^ +>isNamed(x) : boolean +> : ^^^^^^^ +>isNamed : (x: any) => x is Named +> : ^ ^^ ^^^^^^^^^^^^^^^ +>x : any +> : ^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } +} + +class Namer { +>Namer : Namer +> : ^^^^^ + + assertName(x: unknown) { +>assertName : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + if (!isNamed(x)) { +>!isNamed(x) : boolean +> : ^^^^^^^ +>isNamed(x) : boolean +> : ^^^^^^^ +>isNamed : (x: any) => x is Named +> : ^ ^^ ^^^^^^^^^^^^^^^ +>x : unknown +> : ^^^^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } + } + assert(value: unknown) { +>assert : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>value : unknown +> : ^^^^^^^ + + if (typeof value === 'number') { +>typeof value === 'number' : boolean +> : ^^^^^^^ +>typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : unknown +> : ^^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + + return; + } + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } + bar(x: Aged) { +>bar : (x: Aged) => void +> : ^ ^^ ^^^^^^^^^ +>x : Aged +> : ^^^^ + + this.assertName(x); +>this.assertName(x) : void +> : ^^^^ +>this.assertName : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>this : this +> : ^^^^ +>assertName : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>x : Aged +> : ^^^^ + + x.age // ok +>x.age : number +> : ^^^^^^ +>x : Aged +> : ^^^^ +>age : number +> : ^^^^^^ + } +} + diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index 041e401e78ae5..d7a821e9e4656 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -414,3 +414,43 @@ function fTest(x: unknown) { const t2: Test = new Test(); t2.assert(typeof x === "string"); // should ok } + +interface Named { + name: string; +} +interface Aged { + age: number; +} + +declare function assertName(x: any): asserts x is Named; +declare function isNamed(x: any): x is Named; + +function inferFromTypePred(x: unknown) { + if (!isNamed(x)) { + throw new Error(); + } +} + +function inferFromTypePredAny(x: any) { + if (!isNamed(x)) { + throw new Error(); + } +} + +class Namer { + assertName(x: unknown) { + if (!isNamed(x)) { + throw new Error(); + } + } + assert(value: unknown) { + if (typeof value === 'number') { + return; + } + throw new Error(); + } + bar(x: Aged) { + this.assertName(x); + x.age // ok + } +} From 4f8c04491395e7ea8a9e124c4aea385429a8f224 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Sun, 12 May 2024 11:22:07 -0400 Subject: [PATCH 07/10] accept baselines --- tests/baselines/reference/assertionTypePredicates1.js | 4 ++-- tests/baselines/reference/assertionTypePredicates1.types | 8 ++++---- .../classCanExtendConstructorFunction.errors.txt | 4 ++-- .../reference/classCanExtendConstructorFunction.types | 4 ++-- .../controlFlowCommaExpressionAssertionMultiple.types | 8 ++++---- .../reference/dependentDestructuredVariables.types | 4 ++-- .../baselines/reference/flatArrayNoExcessiveStackDepth.js | 2 +- .../reference/flatArrayNoExcessiveStackDepth.types | 8 ++++---- tests/baselines/reference/neverReturningFunctions1.types | 4 ++-- .../reference/privateNamesAssertion(target=es2022).types | 8 ++++---- .../reference/privateNamesAssertion(target=esnext).types | 8 ++++---- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/baselines/reference/assertionTypePredicates1.js b/tests/baselines/reference/assertionTypePredicates1.js index 848416deddce1..ae018a745d4d3 100644 --- a/tests/baselines/reference/assertionTypePredicates1.js +++ b/tests/baselines/reference/assertionTypePredicates1.js @@ -409,14 +409,14 @@ declare class Test { assertIsTest2(): asserts this is Test2; assertThis(): asserts this; bar(): void; - foo(x: unknown): asserts x is string; + foo(x: unknown): void; baz(x: number): void; } declare class Test2 extends Test { z: number; } declare class Derived extends Test { - foo(x: unknown): asserts x is string; + foo(x: unknown): void; baz(x: number): void; } declare function f11(items: Test[]): void; diff --git a/tests/baselines/reference/assertionTypePredicates1.types b/tests/baselines/reference/assertionTypePredicates1.types index abb26100c32fd..b8f62c39d98da 100644 --- a/tests/baselines/reference/assertionTypePredicates1.types +++ b/tests/baselines/reference/assertionTypePredicates1.types @@ -656,8 +656,8 @@ class Test { > : ^^^^ } foo(x: unknown) { ->foo : (x: unknown) => asserts x is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ >x : unknown > : ^^^^^^^ @@ -767,8 +767,8 @@ class Derived extends Test { > : ^^^^ foo(x: unknown) { ->foo : (x: unknown) => asserts x is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : (x: unknown) => void +> : ^ ^^ ^^^^^^^^^ >x : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt index 8e25440d2c7dc..6be00dcf3fb7c 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt +++ b/tests/baselines/reference/classCanExtendConstructorFunction.errors.txt @@ -1,6 +1,6 @@ first.js(23,9): error TS2554: Expected 1 arguments, but got 0. first.js(31,5): error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'. - Type '(files: string[], format: "csv" | "json" | "xmlolololol") => asserts format is "csv" | "json"' is not assignable to type '(supplies?: any[]) => void'. + Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'. Target signature provides too few arguments. Expected 2 or more, but got 1. first.js(47,24): error TS2507: Type '(numberEaten: number) => void' is not a constructor function type. generic.js(19,19): error TS2554: Expected 1 arguments, but got 0. @@ -52,7 +52,7 @@ second.ts(17,15): error TS2345: Argument of type 'string' is not assignable to p load(files, format) { ~~~~ !!! error TS2416: Property 'load' in type 'Sql' is not assignable to the same property in base type 'Wagon'. -!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => asserts format is "csv" | "json"' is not assignable to type '(supplies?: any[]) => void'. +!!! error TS2416: Type '(files: string[], format: "csv" | "json" | "xmlolololol") => void' is not assignable to type '(supplies?: any[]) => void'. !!! error TS2416: Target signature provides too few arguments. Expected 2 or more, but got 1. if (format === "xmlolololol") { throw new Error("please do not use XML. It was a joke."); diff --git a/tests/baselines/reference/classCanExtendConstructorFunction.types b/tests/baselines/reference/classCanExtendConstructorFunction.types index 9f2cdb91c36ce..e3633aa2a3e97 100644 --- a/tests/baselines/reference/classCanExtendConstructorFunction.types +++ b/tests/baselines/reference/classCanExtendConstructorFunction.types @@ -170,8 +170,8 @@ class Sql extends Wagon { * This is not assignable, so should have a type error */ load(files, format) { ->load : (files: Array, format: "csv" | "json" | "xmlolololol") => asserts format is "csv" | "json" -> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>load : (files: Array, format: "csv" | "json" | "xmlolololol") => void +> : ^ ^^ ^^ ^^ ^^^^^^^^^ >files : string[] > : ^^^^^^^^ >format : "csv" | "json" | "xmlolololol" diff --git a/tests/baselines/reference/controlFlowCommaExpressionAssertionMultiple.types b/tests/baselines/reference/controlFlowCommaExpressionAssertionMultiple.types index 99aa4265aa523..f1e70bb080fc5 100644 --- a/tests/baselines/reference/controlFlowCommaExpressionAssertionMultiple.types +++ b/tests/baselines/reference/controlFlowCommaExpressionAssertionMultiple.types @@ -7,8 +7,8 @@ function Narrow(value: any): asserts value is T {} >value : any function func(foo: any, bar: any) { ->func : (foo: any, bar: any) => void -> : ^ ^^ ^^ ^^ ^^^^^^^^^ +>func : (foo: any, bar: any) => asserts foo is number +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ >foo : any >bar : any @@ -36,8 +36,8 @@ function func(foo: any, bar: any) { } function func2(foo: any, bar: any, baz: any) { ->func2 : (foo: any, bar: any, baz: any) => void -> : ^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^^ +>func2 : (foo: any, bar: any, baz: any) => asserts foo is number +> : ^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ >foo : any >bar : any >baz : any diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types index 1a2716aa2259b..4c548f70f9285 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.types +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -1669,8 +1669,8 @@ const fa3: (...args: [true, number] | [false, string]) => void = (guard, value) > : ^^^^ >false : false > : ^^^^^ ->(guard, value) => { if (guard) { for (;;) { value; // number } } else { while (!!true) { value; // string } }} : (guard: boolean, value: string | number) => asserts guard is false -> : ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>(guard, value) => { if (guard) { for (;;) { value; // number } } else { while (!!true) { value; // string } }} : (guard: boolean, value: string | number) => void +> : ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ >guard : boolean > : ^^^^^^^ >value : string | number diff --git a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js index 47161c32be9ff..217c8bfb4d279 100644 --- a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js +++ b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.js @@ -47,5 +47,5 @@ declare const foo: unknown[]; declare const bar: string[]; interface Foo extends Array { } -declare const repro_43249: (value: unknown) => asserts value is string; +declare const repro_43249: (value: unknown) => void; declare function f(x: FlatArray, y: FlatArray): void; diff --git a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types index 189c545ad51cb..e98b06239fdb3 100644 --- a/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types +++ b/tests/baselines/reference/flatArrayNoExcessiveStackDepth.types @@ -36,10 +36,10 @@ interface Foo extends Array {} // Repros from comments in #43249 const repro_43249 = (value: unknown) => { ->repro_43249 : (value: unknown) => asserts value is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->(value: unknown) => { if (typeof value !== "string") { throw new Error("No"); } const match = value.match(/anything/) || []; const [, extracted] = match;} : (value: unknown) => asserts value is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>repro_43249 : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>(value: unknown) => { if (typeof value !== "string") { throw new Error("No"); } const match = value.match(/anything/) || []; const [, extracted] = match;} : (value: unknown) => void +> : ^ ^^ ^^^^^^^^^ >value : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/neverReturningFunctions1.types b/tests/baselines/reference/neverReturningFunctions1.types index 5d5a0c788dcb4..5f7c8e1240072 100644 --- a/tests/baselines/reference/neverReturningFunctions1.types +++ b/tests/baselines/reference/neverReturningFunctions1.types @@ -336,8 +336,8 @@ class Test { > : ^^^^^^^^^^^^^^^^^^ } f1(x: string | undefined) { ->f1 : (x: string | undefined) => asserts x is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>f1 : (x: string | undefined) => void +> : ^ ^^ ^^^^^^^^^ >x : string | undefined > : ^^^^^^^^^^^^^^^^^^ diff --git a/tests/baselines/reference/privateNamesAssertion(target=es2022).types b/tests/baselines/reference/privateNamesAssertion(target=es2022).types index b5d2a5054506e..16c3df1dec82f 100644 --- a/tests/baselines/reference/privateNamesAssertion(target=es2022).types +++ b/tests/baselines/reference/privateNamesAssertion(target=es2022).types @@ -30,8 +30,8 @@ class Foo { } } m1(v: unknown) { ->m1 : (v: unknown) => asserts v is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>m1 : (v: unknown) => void +> : ^ ^^ ^^^^^^^^^ >v : unknown > : ^^^^^^^ @@ -77,8 +77,8 @@ class Foo2 { } } m1(v: unknown) { ->m1 : (v: unknown) => asserts v is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>m1 : (v: unknown) => void +> : ^ ^^ ^^^^^^^^^ >v : unknown > : ^^^^^^^ diff --git a/tests/baselines/reference/privateNamesAssertion(target=esnext).types b/tests/baselines/reference/privateNamesAssertion(target=esnext).types index b5d2a5054506e..16c3df1dec82f 100644 --- a/tests/baselines/reference/privateNamesAssertion(target=esnext).types +++ b/tests/baselines/reference/privateNamesAssertion(target=esnext).types @@ -30,8 +30,8 @@ class Foo { } } m1(v: unknown) { ->m1 : (v: unknown) => asserts v is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>m1 : (v: unknown) => void +> : ^ ^^ ^^^^^^^^^ >v : unknown > : ^^^^^^^ @@ -77,8 +77,8 @@ class Foo2 { } } m1(v: unknown) { ->m1 : (v: unknown) => asserts v is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>m1 : (v: unknown) => void +> : ^ ^^ ^^^^^^^^^ >v : unknown > : ^^^^^^^ From 16a42f330bb721d8b2639ec4b67ddf8a2b04f6c2 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Mon, 13 May 2024 09:13:38 -0400 Subject: [PATCH 08/10] remove irrelevant tests --- .../reference/inferTypePredicates.errors.txt | 21 ----- .../reference/inferTypePredicates.js | 49 ----------- .../reference/inferTypePredicates.symbols | 79 ++++------------- .../reference/inferTypePredicates.types | 84 ------------------- tests/cases/compiler/inferTypePredicates.ts | 21 ----- 5 files changed, 14 insertions(+), 240 deletions(-) diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index 86fda855bec26..c80205359c1ac 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -464,9 +464,6 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 interface Named { name: string; } - interface Aged { - age: number; - } declare function assertName(x: any): asserts x is Named; declare function isNamed(x: any): x is Named; @@ -482,22 +479,4 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 throw new Error(); } } - - class Namer { - assertName(x: unknown) { - if (!isNamed(x)) { - throw new Error(); - } - } - assert(value: unknown) { - if (typeof value === 'number') { - return; - } - throw new Error(); - } - bar(x: Aged) { - this.assertName(x); - x.age // ok - } - } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index a22e3afc01654..60f95d3867029 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -418,9 +418,6 @@ function fTest(x: unknown) { interface Named { name: string; } -interface Aged { - age: number; -} declare function assertName(x: any): asserts x is Named; declare function isNamed(x: any): x is Named; @@ -436,24 +433,6 @@ function inferFromTypePredAny(x: any) { throw new Error(); } } - -class Namer { - assertName(x: unknown) { - if (!isNamed(x)) { - throw new Error(); - } - } - assert(value: unknown) { - if (typeof value === 'number') { - return; - } - throw new Error(); - } - bar(x: Aged) { - this.assertName(x); - x.age // ok - } -} //// [inferTypePredicates.js] @@ -847,26 +826,6 @@ function inferFromTypePredAny(x) { throw new Error(); } } -var Namer = /** @class */ (function () { - function Namer() { - } - Namer.prototype.assertName = function (x) { - if (!isNamed(x)) { - throw new Error(); - } - }; - Namer.prototype.assert = function (value) { - if (typeof value === 'number') { - return; - } - throw new Error(); - }; - Namer.prototype.bar = function (x) { - this.assertName(x); - x.age; // ok - }; - return Namer; -}()); //// [inferTypePredicates.d.ts] @@ -983,15 +942,7 @@ declare function fTest(x: unknown): void; interface Named { name: string; } -interface Aged { - age: number; -} declare function assertName(x: any): asserts x is Named; declare function isNamed(x: any): x is Named; declare function inferFromTypePred(x: unknown): asserts x is Named; declare function inferFromTypePredAny(x: any): asserts x is Named; -declare class Namer { - assertName(x: unknown): void; - assert(value: unknown): void; - bar(x: Aged): void; -} diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index 80a0972d66873..0506415b8e42f 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -1066,32 +1066,26 @@ interface Named { name: string; >name : Symbol(Named.name, Decl(inferTypePredicates.ts, 414, 17)) } -interface Aged { ->Aged : Symbol(Aged, Decl(inferTypePredicates.ts, 416, 1)) - - age: number; ->age : Symbol(Aged.age, Decl(inferTypePredicates.ts, 417, 16)) -} declare function assertName(x: any): asserts x is Named; ->assertName : Symbol(assertName, Decl(inferTypePredicates.ts, 419, 1)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 421, 28)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 421, 28)) +>assertName : Symbol(assertName, Decl(inferTypePredicates.ts, 416, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 418, 28)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 418, 28)) >Named : Symbol(Named, Decl(inferTypePredicates.ts, 412, 1)) declare function isNamed(x: any): x is Named; ->isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 422, 25)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 422, 25)) +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 418, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 419, 25)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 419, 25)) >Named : Symbol(Named, Decl(inferTypePredicates.ts, 412, 1)) function inferFromTypePred(x: unknown) { ->inferFromTypePred : Symbol(inferFromTypePred, Decl(inferTypePredicates.ts, 422, 45)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 424, 27)) +>inferFromTypePred : Symbol(inferFromTypePred, Decl(inferTypePredicates.ts, 419, 45)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 421, 27)) if (!isNamed(x)) { ->isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 424, 27)) +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 418, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 421, 27)) throw new Error(); >Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) @@ -1099,60 +1093,15 @@ function inferFromTypePred(x: unknown) { } function inferFromTypePredAny(x: any) { ->inferFromTypePredAny : Symbol(inferFromTypePredAny, Decl(inferTypePredicates.ts, 428, 1)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 430, 30)) +>inferFromTypePredAny : Symbol(inferFromTypePredAny, Decl(inferTypePredicates.ts, 425, 1)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 427, 30)) if (!isNamed(x)) { ->isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 430, 30)) - - throw new Error(); ->Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) - } -} - -class Namer { ->Namer : Symbol(Namer, Decl(inferTypePredicates.ts, 434, 1)) - - assertName(x: unknown) { ->assertName : Symbol(Namer.assertName, Decl(inferTypePredicates.ts, 436, 13)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 437, 13)) - - if (!isNamed(x)) { ->isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 421, 56)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 437, 13)) +>isNamed : Symbol(isNamed, Decl(inferTypePredicates.ts, 418, 56)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 427, 30)) - throw new Error(); ->Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) - } - } - assert(value: unknown) { ->assert : Symbol(Namer.assert, Decl(inferTypePredicates.ts, 441, 3)) ->value : Symbol(value, Decl(inferTypePredicates.ts, 442, 9)) - - if (typeof value === 'number') { ->value : Symbol(value, Decl(inferTypePredicates.ts, 442, 9)) - - return; - } throw new Error(); >Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) } - bar(x: Aged) { ->bar : Symbol(Namer.bar, Decl(inferTypePredicates.ts, 447, 3)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 448, 6)) ->Aged : Symbol(Aged, Decl(inferTypePredicates.ts, 416, 1)) - - this.assertName(x); ->this.assertName : Symbol(Namer.assertName, Decl(inferTypePredicates.ts, 436, 13)) ->this : Symbol(Namer, Decl(inferTypePredicates.ts, 434, 1)) ->assertName : Symbol(Namer.assertName, Decl(inferTypePredicates.ts, 436, 13)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 448, 6)) - - x.age // ok ->x.age : Symbol(Aged.age, Decl(inferTypePredicates.ts, 417, 16)) ->x : Symbol(x, Decl(inferTypePredicates.ts, 448, 6)) ->age : Symbol(Aged.age, Decl(inferTypePredicates.ts, 417, 16)) - } } diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index 495123e30f041..f29c668bb9ec5 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -2187,11 +2187,6 @@ interface Named { >name : string > : ^^^^^^ } -interface Aged { - age: number; ->age : number -> : ^^^^^^ -} declare function assertName(x: any): asserts x is Named; >assertName : (x: any) => asserts x is Named @@ -2253,82 +2248,3 @@ function inferFromTypePredAny(x: any) { } } -class Namer { ->Namer : Namer -> : ^^^^^ - - assertName(x: unknown) { ->assertName : (x: unknown) => void -> : ^ ^^ ^^^^^^^^^ ->x : unknown -> : ^^^^^^^ - - if (!isNamed(x)) { ->!isNamed(x) : boolean -> : ^^^^^^^ ->isNamed(x) : boolean -> : ^^^^^^^ ->isNamed : (x: any) => x is Named -> : ^ ^^ ^^^^^^^^^^^^^^^ ->x : unknown -> : ^^^^^^^ - - throw new Error(); ->new Error() : Error -> : ^^^^^ ->Error : ErrorConstructor -> : ^^^^^^^^^^^^^^^^ - } - } - assert(value: unknown) { ->assert : (value: unknown) => void -> : ^ ^^ ^^^^^^^^^ ->value : unknown -> : ^^^^^^^ - - if (typeof value === 'number') { ->typeof value === 'number' : boolean -> : ^^^^^^^ ->typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->value : unknown -> : ^^^^^^^ ->'number' : "number" -> : ^^^^^^^^ - - return; - } - throw new Error(); ->new Error() : Error -> : ^^^^^ ->Error : ErrorConstructor -> : ^^^^^^^^^^^^^^^^ - } - bar(x: Aged) { ->bar : (x: Aged) => void -> : ^ ^^ ^^^^^^^^^ ->x : Aged -> : ^^^^ - - this.assertName(x); ->this.assertName(x) : void -> : ^^^^ ->this.assertName : (x: unknown) => void -> : ^ ^^ ^^^^^^^^^ ->this : this -> : ^^^^ ->assertName : (x: unknown) => void -> : ^ ^^ ^^^^^^^^^ ->x : Aged -> : ^^^^ - - x.age // ok ->x.age : number -> : ^^^^^^ ->x : Aged -> : ^^^^ ->age : number -> : ^^^^^^ - } -} - diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index d7a821e9e4656..515ba45ba03fa 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -418,9 +418,6 @@ function fTest(x: unknown) { interface Named { name: string; } -interface Aged { - age: number; -} declare function assertName(x: any): asserts x is Named; declare function isNamed(x: any): x is Named; @@ -436,21 +433,3 @@ function inferFromTypePredAny(x: any) { throw new Error(); } } - -class Namer { - assertName(x: unknown) { - if (!isNamed(x)) { - throw new Error(); - } - } - assert(value: unknown) { - if (typeof value === 'number') { - return; - } - throw new Error(); - } - bar(x: Aged) { - this.assertName(x); - x.age // ok - } -} From b242dbdbd293f1eb5f2f0f9b57b75e124d80ea80 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Wed, 15 May 2024 09:01:55 -0400 Subject: [PATCH 09/10] accept error baseline --- .../reference/inferTypePredicates.errors.txt | 21 +++++- .../reference/inferTypePredicates.js | 29 +++++++++ .../reference/inferTypePredicates.symbols | 31 +++++++++ .../reference/inferTypePredicates.types | 65 +++++++++++++++++++ tests/cases/compiler/inferTypePredicates.ts | 15 +++++ 5 files changed, 160 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index c80205359c1ac..77af67dd20b7a 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -15,9 +15,10 @@ inferTypePredicates.ts(115,7): error TS2322: Type 'string | number' is not assig Type 'string' is not assignable to type 'number'. inferTypePredicates.ts(133,7): error TS2740: Type '{}' is missing the following properties from type 'Date': toDateString, toTimeString, toLocaleDateString, toLocaleTimeString, and 37 more. inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1' but required in type 'C2'. +inferTypePredicates.ts(446,3): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. -==== inferTypePredicates.ts (11 errors) ==== +==== inferTypePredicates.ts (12 errors) ==== // https://github.com/microsoft/TypeScript/issues/16069 const numsOrNull = [1, 2, 3, 4, null]; @@ -479,4 +480,22 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 throw new Error(); } } + + // should return void, not "asserts pattern is string" + const assertWithFuncExpr = function (pattern: unknown) { + if (typeof pattern !== 'string') { + throw new TypeError('invalid pattern') + } + + if (pattern.length > 1024) { + throw new TypeError('pattern is too long') + } + } + + function useAssertWithFuncExpr(pattern: string) { + assertWithFuncExpr(pattern); + ~~~~~~~~~~~~~~~~~~ +!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. +!!! related TS2782 inferTypePredicates.ts:435:7: 'assertWithFuncExpr' needs an explicit type annotation. + } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index 60f95d3867029..85ba2e7f77bd6 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -433,6 +433,21 @@ function inferFromTypePredAny(x: any) { throw new Error(); } } + +// should return void, not "asserts pattern is string" +const assertWithFuncExpr = function (pattern: unknown) { + if (typeof pattern !== 'string') { + throw new TypeError('invalid pattern') + } + + if (pattern.length > 1024) { + throw new TypeError('pattern is too long') + } +} + +function useAssertWithFuncExpr(pattern: string) { + assertWithFuncExpr(pattern); +} //// [inferTypePredicates.js] @@ -826,6 +841,18 @@ function inferFromTypePredAny(x) { throw new Error(); } } +// should return void, not "asserts pattern is string" +var assertWithFuncExpr = function (pattern) { + if (typeof pattern !== 'string') { + throw new TypeError('invalid pattern'); + } + if (pattern.length > 1024) { + throw new TypeError('pattern is too long'); + } +}; +function useAssertWithFuncExpr(pattern) { + assertWithFuncExpr(pattern); +} //// [inferTypePredicates.d.ts] @@ -946,3 +973,5 @@ declare function assertName(x: any): asserts x is Named; declare function isNamed(x: any): x is Named; declare function inferFromTypePred(x: unknown): asserts x is Named; declare function inferFromTypePredAny(x: any): asserts x is Named; +declare const assertWithFuncExpr: (pattern: unknown) => asserts pattern is string; +declare function useAssertWithFuncExpr(pattern: string): void; diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index 0506415b8e42f..a5b982b1ec3af 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -1105,3 +1105,34 @@ function inferFromTypePredAny(x: any) { } } +// should return void, not "asserts pattern is string" +const assertWithFuncExpr = function (pattern: unknown) { +>assertWithFuncExpr : Symbol(assertWithFuncExpr, Decl(inferTypePredicates.ts, 434, 5)) +>pattern : Symbol(pattern, Decl(inferTypePredicates.ts, 434, 37)) + + if (typeof pattern !== 'string') { +>pattern : Symbol(pattern, Decl(inferTypePredicates.ts, 434, 37)) + + throw new TypeError('invalid pattern') +>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + + if (pattern.length > 1024) { +>pattern.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>pattern : Symbol(pattern, Decl(inferTypePredicates.ts, 434, 37)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + throw new TypeError('pattern is too long') +>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +function useAssertWithFuncExpr(pattern: string) { +>useAssertWithFuncExpr : Symbol(useAssertWithFuncExpr, Decl(inferTypePredicates.ts, 442, 1)) +>pattern : Symbol(pattern, Decl(inferTypePredicates.ts, 444, 31)) + + assertWithFuncExpr(pattern); +>assertWithFuncExpr : Symbol(assertWithFuncExpr, Decl(inferTypePredicates.ts, 434, 5)) +>pattern : Symbol(pattern, Decl(inferTypePredicates.ts, 444, 31)) +} + diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index f29c668bb9ec5..d0af00bb72204 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -2248,3 +2248,68 @@ function inferFromTypePredAny(x: any) { } } +// should return void, not "asserts pattern is string" +const assertWithFuncExpr = function (pattern: unknown) { +>assertWithFuncExpr : (pattern: unknown) => asserts pattern is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>function (pattern: unknown) { if (typeof pattern !== 'string') { throw new TypeError('invalid pattern') } if (pattern.length > 1024) { throw new TypeError('pattern is too long') }} : (pattern: unknown) => asserts pattern is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>pattern : unknown +> : ^^^^^^^ + + if (typeof pattern !== 'string') { +>typeof pattern !== 'string' : boolean +> : ^^^^^^^ +>typeof pattern : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>pattern : unknown +> : ^^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + + throw new TypeError('invalid pattern') +>new TypeError('invalid pattern') : TypeError +> : ^^^^^^^^^ +>TypeError : TypeErrorConstructor +> : ^^^^^^^^^^^^^^^^^^^^ +>'invalid pattern' : "invalid pattern" +> : ^^^^^^^^^^^^^^^^^ + } + + if (pattern.length > 1024) { +>pattern.length > 1024 : boolean +> : ^^^^^^^ +>pattern.length : number +> : ^^^^^^ +>pattern : string +> : ^^^^^^ +>length : number +> : ^^^^^^ +>1024 : 1024 +> : ^^^^ + + throw new TypeError('pattern is too long') +>new TypeError('pattern is too long') : TypeError +> : ^^^^^^^^^ +>TypeError : TypeErrorConstructor +> : ^^^^^^^^^^^^^^^^^^^^ +>'pattern is too long' : "pattern is too long" +> : ^^^^^^^^^^^^^^^^^^^^^ + } +} + +function useAssertWithFuncExpr(pattern: string) { +>useAssertWithFuncExpr : (pattern: string) => void +> : ^ ^^ ^^^^^^^^^ +>pattern : string +> : ^^^^^^ + + assertWithFuncExpr(pattern); +>assertWithFuncExpr(pattern) : void +> : ^^^^ +>assertWithFuncExpr : (pattern: unknown) => asserts pattern is string +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>pattern : string +> : ^^^^^^ +} + diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index 515ba45ba03fa..35a7400f7092e 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -433,3 +433,18 @@ function inferFromTypePredAny(x: any) { throw new Error(); } } + +// should return void, not "asserts pattern is string" +const assertWithFuncExpr = function (pattern: unknown) { + if (typeof pattern !== 'string') { + throw new TypeError('invalid pattern') + } + + if (pattern.length > 1024) { + throw new TypeError('pattern is too long') + } +} + +function useAssertWithFuncExpr(pattern: string) { + assertWithFuncExpr(pattern); +} From 7f8343ac2967a74a2586f667164d45a53ea7fba0 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Wed, 15 May 2024 09:02:40 -0400 Subject: [PATCH 10/10] exclude funtion expressions + accept baselines --- src/compiler/checker.ts | 1 + .../reference/inferTypePredicates.errors.txt | 6 +----- tests/baselines/reference/inferTypePredicates.js | 2 +- tests/baselines/reference/inferTypePredicates.types | 12 ++++++------ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 72f857d0e3b01..5a17cd9224a5c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37867,6 +37867,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: case SyntaxKind.MethodDeclaration: return undefined; } diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index 77af67dd20b7a..505d06b3810fc 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -15,10 +15,9 @@ inferTypePredicates.ts(115,7): error TS2322: Type 'string | number' is not assig Type 'string' is not assignable to type 'number'. inferTypePredicates.ts(133,7): error TS2740: Type '{}' is missing the following properties from type 'Date': toDateString, toTimeString, toLocaleDateString, toLocaleTimeString, and 37 more. inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1' but required in type 'C2'. -inferTypePredicates.ts(446,3): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. -==== inferTypePredicates.ts (12 errors) ==== +==== inferTypePredicates.ts (11 errors) ==== // https://github.com/microsoft/TypeScript/issues/16069 const numsOrNull = [1, 2, 3, 4, null]; @@ -494,8 +493,5 @@ inferTypePredicates.ts(446,3): error TS2775: Assertions require every name in th function useAssertWithFuncExpr(pattern: string) { assertWithFuncExpr(pattern); - ~~~~~~~~~~~~~~~~~~ -!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation. -!!! related TS2782 inferTypePredicates.ts:435:7: 'assertWithFuncExpr' needs an explicit type annotation. } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index 85ba2e7f77bd6..63de95b7f023b 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -973,5 +973,5 @@ declare function assertName(x: any): asserts x is Named; declare function isNamed(x: any): x is Named; declare function inferFromTypePred(x: unknown): asserts x is Named; declare function inferFromTypePredAny(x: any): asserts x is Named; -declare const assertWithFuncExpr: (pattern: unknown) => asserts pattern is string; +declare const assertWithFuncExpr: (pattern: unknown) => void; declare function useAssertWithFuncExpr(pattern: string): void; diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index d0af00bb72204..631ec0b2ab7ff 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -2250,10 +2250,10 @@ function inferFromTypePredAny(x: any) { // should return void, not "asserts pattern is string" const assertWithFuncExpr = function (pattern: unknown) { ->assertWithFuncExpr : (pattern: unknown) => asserts pattern is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->function (pattern: unknown) { if (typeof pattern !== 'string') { throw new TypeError('invalid pattern') } if (pattern.length > 1024) { throw new TypeError('pattern is too long') }} : (pattern: unknown) => asserts pattern is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>assertWithFuncExpr : (pattern: unknown) => void +> : ^ ^^ ^^^^^^^^^ +>function (pattern: unknown) { if (typeof pattern !== 'string') { throw new TypeError('invalid pattern') } if (pattern.length > 1024) { throw new TypeError('pattern is too long') }} : (pattern: unknown) => void +> : ^ ^^ ^^^^^^^^^ >pattern : unknown > : ^^^^^^^ @@ -2307,8 +2307,8 @@ function useAssertWithFuncExpr(pattern: string) { assertWithFuncExpr(pattern); >assertWithFuncExpr(pattern) : void > : ^^^^ ->assertWithFuncExpr : (pattern: unknown) => asserts pattern is string -> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>assertWithFuncExpr : (pattern: unknown) => void +> : ^ ^^ ^^^^^^^^^ >pattern : string > : ^^^^^^ }