From 8ee9170a25932b3783c5c608a65590964625b9d4 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 18 Sep 2021 19:35:53 +0300 Subject: [PATCH] fix(45919): allow using JSDoc types for arrow function with type predicate --- src/compiler/checker.ts | 12 ++--- src/compiler/utilities.ts | 4 ++ .../reference/assertionTypePredicates2.js | 50 ++++++++++++++++++ .../assertionTypePredicates2.symbols | 42 +++++++++++++++ .../reference/assertionTypePredicates2.types | 51 +++++++++++++++++++ .../controlFlow/assertionTypePredicates2.ts | 27 ++++++++++ 6 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/assertionTypePredicates2.js create mode 100644 tests/baselines/reference/assertionTypePredicates2.symbols create mode 100644 tests/baselines/reference/assertionTypePredicates2.types create mode 100644 tests/cases/conformance/controlFlow/assertionTypePredicates2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 51c5001204f02..68f41f2c7df0a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22983,10 +22983,10 @@ namespace ts { return isLengthPushOrUnshift || isElementAssignment; } - function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration) { - return (declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter || - declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) && - !!getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature); + function isDeclarationWithExplicitTypeAnnotation(node: Declaration) { + return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) && + !!(getEffectiveTypeAnnotationNode(node) || + isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer)); } function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { @@ -26322,10 +26322,6 @@ namespace ts { return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; } - function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { - return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; - } - function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { // Only function expressions, arrow functions, and object literal methods are contextually typed. return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8ac91f86f99dd..322e9adace14e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7418,4 +7418,8 @@ namespace ts { const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration); return !!declaration && (isParameter(declaration) || isCatchClauseVariableDeclaration(declaration)); } + + export function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { + return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; + } } diff --git a/tests/baselines/reference/assertionTypePredicates2.js b/tests/baselines/reference/assertionTypePredicates2.js new file mode 100644 index 0000000000000..8b6dcd509f19d --- /dev/null +++ b/tests/baselines/reference/assertionTypePredicates2.js @@ -0,0 +1,50 @@ +//// [assertionTypePredicates2.js] +/** + * @typedef {{ x: number }} A + */ + +/** + * @typedef { A & { y: number } } B + */ + +/** + * @param {A} a + * @returns { asserts a is B } + */ +const foo = (a) => { + if (/** @type { B } */ (a).y !== 0) throw TypeError(); + return undefined; +}; + +export const main = () => { + /** @type { A } */ + const a = { x: 1 }; + foo(a); +}; + + +//// [assertionTypePredicates2.js] +"use strict"; +/** + * @typedef {{ x: number }} A + */ +exports.__esModule = true; +exports.main = void 0; +/** + * @typedef { A & { y: number } } B + */ +/** + * @param {A} a + * @returns { asserts a is B } + */ +var foo = function (a) { + if ( /** @type { B } */(a).y !== 0) + throw TypeError(); + return undefined; +}; +var main = function () { + /** @type { A } */ + var a = { x: 1 }; + foo(a); +}; +exports.main = main; diff --git a/tests/baselines/reference/assertionTypePredicates2.symbols b/tests/baselines/reference/assertionTypePredicates2.symbols new file mode 100644 index 0000000000000..24086f27a213b --- /dev/null +++ b/tests/baselines/reference/assertionTypePredicates2.symbols @@ -0,0 +1,42 @@ +=== tests/cases/conformance/controlFlow/assertionTypePredicates2.js === +/** + * @typedef {{ x: number }} A + */ + +/** + * @typedef { A & { y: number } } B + */ + +/** + * @param {A} a + * @returns { asserts a is B } + */ +const foo = (a) => { +>foo : Symbol(foo, Decl(assertionTypePredicates2.js, 12, 5)) +>a : Symbol(a, Decl(assertionTypePredicates2.js, 12, 13)) + + if (/** @type { B } */ (a).y !== 0) throw TypeError(); +>(a).y : Symbol(y, Decl(assertionTypePredicates2.js, 5, 19)) +>a : Symbol(a, Decl(assertionTypePredicates2.js, 12, 13)) +>y : Symbol(y, Decl(assertionTypePredicates2.js, 5, 19)) +>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + return undefined; +>undefined : Symbol(undefined) + +}; + +export const main = () => { +>main : Symbol(main, Decl(assertionTypePredicates2.js, 17, 12)) + + /** @type { A } */ + const a = { x: 1 }; +>a : Symbol(a, Decl(assertionTypePredicates2.js, 19, 9)) +>x : Symbol(x, Decl(assertionTypePredicates2.js, 19, 15)) + + foo(a); +>foo : Symbol(foo, Decl(assertionTypePredicates2.js, 12, 5)) +>a : Symbol(a, Decl(assertionTypePredicates2.js, 19, 9)) + +}; + diff --git a/tests/baselines/reference/assertionTypePredicates2.types b/tests/baselines/reference/assertionTypePredicates2.types new file mode 100644 index 0000000000000..6b0b1fc396175 --- /dev/null +++ b/tests/baselines/reference/assertionTypePredicates2.types @@ -0,0 +1,51 @@ +=== tests/cases/conformance/controlFlow/assertionTypePredicates2.js === +/** + * @typedef {{ x: number }} A + */ + +/** + * @typedef { A & { y: number } } B + */ + +/** + * @param {A} a + * @returns { asserts a is B } + */ +const foo = (a) => { +>foo : (a: A) => asserts a is B +>(a) => { if (/** @type { B } */ (a).y !== 0) throw TypeError(); return undefined;} : (a: A) => asserts a is B +>a : A + + if (/** @type { B } */ (a).y !== 0) throw TypeError(); +>(a).y !== 0 : boolean +>(a).y : number +>(a) : B +>a : A +>y : number +>0 : 0 +>TypeError() : TypeError +>TypeError : TypeErrorConstructor + + return undefined; +>undefined : undefined + +}; + +export const main = () => { +>main : () => void +>() => { /** @type { A } */ const a = { x: 1 }; foo(a);} : () => void + + /** @type { A } */ + const a = { x: 1 }; +>a : A +>{ x: 1 } : { x: number; } +>x : number +>1 : 1 + + foo(a); +>foo(a) : void +>foo : (a: A) => asserts a is B +>a : A + +}; + diff --git a/tests/cases/conformance/controlFlow/assertionTypePredicates2.ts b/tests/cases/conformance/controlFlow/assertionTypePredicates2.ts new file mode 100644 index 0000000000000..915c9ae94f3e3 --- /dev/null +++ b/tests/cases/conformance/controlFlow/assertionTypePredicates2.ts @@ -0,0 +1,27 @@ +// @allowJs: true +// @checkJs: true +// @outDir: ./out +// @filename: assertionTypePredicates2.js + +/** + * @typedef {{ x: number }} A + */ + +/** + * @typedef { A & { y: number } } B + */ + +/** + * @param {A} a + * @returns { asserts a is B } + */ +const foo = (a) => { + if (/** @type { B } */ (a).y !== 0) throw TypeError(); + return undefined; +}; + +export const main = () => { + /** @type { A } */ + const a = { x: 1 }; + foo(a); +};