diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4020504f9c07..a4055a4471632 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15468,10 +15468,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; @@ -38016,6 +38021,54 @@ 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: + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + return undefined; + } + const functionFlags = getFunctionFlags(func); + if (functionFlags !== FunctionFlags.Normal || !func.body) return undefined; + + const returnFlowNodes: FlowNode[] = []; + const bailedEarly = forEachReturnStatement(func.body, 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.flags & TypeFlags.Any ? unknownType : 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..ae018a745d4d3 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; @@ -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..b8f62c39d98da 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 @@ -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/coAndContraVariantInferences2.types b/tests/baselines/reference/coAndContraVariantInferences2.types index 5309c2785df83..1a9ecdde7af88 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/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.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 73c4b67290df8..e55bcd6512689 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 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/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index e61c1cb72beef..505d06b3810fc 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -325,4 +325,173 @@ 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(); + } + + 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 + } + + interface Named { + name: string; + } + + 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(); + } + } + + // 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); + } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index b5802c0e50a99..63de95b7f023b 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -279,6 +279,175 @@ 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(); +} + +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 +} + +interface Named { + name: string; +} + +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(); + } +} + +// 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] @@ -538,6 +707,152 @@ 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(); +} +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 +} +function inferFromTypePred(x) { + if (!isNamed(x)) { + throw new Error(); + } +} +function inferFromTypePredAny(x) { + if (!isNamed(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] @@ -630,3 +945,33 @@ 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; +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; +interface Named { + name: string; +} +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) => void; +declare function useAssertWithFuncExpr(pattern: string): void; diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index 8fd879787c205..a5b982b1ec3af 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -777,3 +777,362 @@ 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, --, --)) +} + +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)) +} + +interface Named { +>Named : Symbol(Named, Decl(inferTypePredicates.ts, 412, 1)) + + name: string; +>name : Symbol(Named.name, Decl(inferTypePredicates.ts, 414, 17)) +} + +declare function assertName(x: any): asserts x is Named; +>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, 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, 419, 45)) +>x : Symbol(x, Decl(inferTypePredicates.ts, 421, 27)) + + if (!isNamed(x)) { +>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, --, --)) + } +} + +function inferFromTypePredAny(x: any) { +>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, 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, --, --)) + } +} + +// 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 8bdedfc9c3532..631ec0b2ab7ff 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -1649,3 +1649,667 @@ 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 +> : ^^^^^^^^^^^^^^^^ +} + +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" +> : ^^^^^^^^ +} + +interface Named { + name: string; +>name : string +> : ^^^^^^ +} + +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 +> : ^^^^^^^^^^^^^^^^ + } +} + +// should return void, not "asserts pattern is string" +const assertWithFuncExpr = function (pattern: unknown) { +>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 +> : ^^^^^^^ + + 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) => void +> : ^ ^^ ^^^^^^^^^ +>pattern : string +> : ^^^^^^ +} + 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 c3cf3d637b590..12fc9fe73c8dc 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 d2edbfc4989e2..2f6c67453374f 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 158750ecd7e93..9f0cf3ed8360f 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 > : ^^^^^^^^^^^^^^^^^^ 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 13ee45cd74052..3833a557e370b 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..35a7400f7092e 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -279,3 +279,172 @@ 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(); +} + +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 +} + +interface Named { + name: string; +} + +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(); + } +} + +// 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); +} 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; }