Get return type from @type tag (#25580)

* Get return type from `@type` tag

This only happens in the checker, where the type is easily accessible.
The syntax-based check in getEffectiveReturnTypeNode as a fast path, and
for other uses that don't want to make a call to getTypeFromTypeNode.

Fixes #25525

* Implement PR suggestions

* Error when type tag isn't callable

* Fix lint
This commit is contained in:
Nathan Shively-Sanders
2018-07-12 10:49:41 -07:00
committed by GitHub
parent 66e9aaac18
commit bd7b97ce61
18 changed files with 237 additions and 41 deletions
+33 -16
View File
@@ -7566,12 +7566,21 @@ namespace ts {
const setter = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(declaration), SyntaxKind.SetAccessor);
return getAnnotatedAccessorType(setter);
}
const typeFromTag = getReturnTypeOfTypeTag(declaration);
if (typeFromTag) {
return typeFromTag;
}
if (nodeIsMissing((<FunctionLikeDeclaration>declaration).body)) {
return anyType;
}
}
function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) {
const typeTag = isInJavaScriptFile(node) ? getJSDocTypeTag(node) : undefined;
const signatures = typeTag && typeTag.typeExpression && getSignaturesOfType(getTypeFromTypeNode(typeTag.typeExpression), SignatureKind.Call);
return signatures && signatures.length === 1 ? getReturnTypeOfSignature(signatures[0]) : undefined;
}
function containsArgumentsReference(declaration: SignatureDeclaration): boolean {
const links = getNodeLinks(declaration);
if (links.containsArgumentsReference === undefined) {
@@ -20539,15 +20548,20 @@ namespace ts {
return type;
}
function getReturnOrPromisedType(node: FunctionLikeDeclaration | MethodSignature, functionFlags: FunctionFlags) {
const returnTypeNode = getEffectiveReturnTypeNode(node);
return returnTypeNode &&
((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async ?
checkAsyncFunctionReturnType(node, returnTypeNode) : // Async function
getTypeFromTypeNode(returnTypeNode)) || // AsyncGenerator function, Generator function, or normal function
getReturnTypeOfTypeTag(node); // type from JSDoc @type tag
}
function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
const functionFlags = getFunctionFlags(node);
const returnTypeNode = getEffectiveReturnTypeNode(node);
const returnOrPromisedType = returnTypeNode &&
((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async ?
checkAsyncFunctionReturnType(node) : // Async function
getTypeFromTypeNode(returnTypeNode)); // AsyncGenerator function, Generator function, or normal function
const returnOrPromisedType = getReturnOrPromisedType(node, functionFlags);
if ((functionFlags & FunctionFlags.Generator) === 0) { // Async function or normal function
// return is not necessary in the body of generators
@@ -20555,7 +20569,7 @@ namespace ts {
}
if (node.body) {
if (!returnTypeNode) {
if (!getEffectiveReturnTypeNode(node)) {
// There are some checks that are only performed in getReturnTypeFromBody, that may produce errors
// we need. An example is the noImplicitAny errors resulting from widening the return expression
// of a function. Because checking of function expression bodies is deferred, there was never an
@@ -22007,7 +22021,7 @@ namespace ts {
}
}
else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) {
checkAsyncFunctionReturnType(<FunctionLikeDeclaration>node);
checkAsyncFunctionReturnType(<FunctionLikeDeclaration>node, returnTypeNode);
}
}
if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) {
@@ -23067,7 +23081,7 @@ namespace ts {
*
* @param node The signature to check
*/
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature): Type {
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode): Type {
// As part of our emit for an async function, we will need to emit the entity name of
// the return type annotation as an expression. To meet the necessary runtime semantics
// for __awaiter, we must also check that the type of the declaration (e.g. the static
@@ -23092,7 +23106,6 @@ namespace ts {
// then<U>(...): Promise<U>;
// }
//
const returnTypeNode = getEffectiveReturnTypeNode(node)!; // TODO: GH#18217
const returnType = getTypeFromTypeNode(returnTypeNode);
if (languageVersion >= ScriptTarget.ES2015) {
@@ -23502,15 +23515,12 @@ namespace ts {
const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body;
checkSourceElement(body);
const returnTypeNode = getEffectiveReturnTypeNode(node);
if ((functionFlags & FunctionFlags.Generator) === 0) { // Async function or normal function
const returnOrPromisedType = returnTypeNode && (functionFlags & FunctionFlags.Async
? checkAsyncFunctionReturnType(node) // Async function
: getTypeFromTypeNode(returnTypeNode)); // normal function
const returnOrPromisedType = getReturnOrPromisedType(node, functionFlags);
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnOrPromisedType);
}
if (produceDiagnostics && !returnTypeNode) {
if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) {
// Report an implicit any error if there is no body, no explicit return type, and node is not a private method
// in an ambient context
if (noImplicitAny && nodeIsMissing(body) && !isPrivateWithinAmbient(node)) {
@@ -23523,6 +23533,13 @@ namespace ts {
// yielded values. The only way to trigger these errors is to try checking its return type.
getReturnTypeOfSignature(getSignatureFromDeclaration(node));
}
// A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature
if (isInJavaScriptFile(node)) {
const typeTag = getJSDocTypeTag(node);
if (typeTag && typeTag.typeExpression && !getSignaturesOfType(getTypeFromTypeNode(typeTag.typeExpression), SignatureKind.Call).length) {
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_be_callable);
}
}
}
}
@@ -24785,7 +24802,7 @@ namespace ts {
error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class);
}
}
else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func)) {
else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func) || getReturnTypeOfTypeTag(func)) {
if (functionFlags & FunctionFlags.Async) { // Async function
const promisedType = getPromisedTypeOfPromise(returnType);
const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
+4
View File
@@ -4021,6 +4021,10 @@
"category": "Error",
"code": 8029
},
"The type of a function declaration must be callable.": {
"category": "Error",
"code": 8030
},
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": {
"category": "Error",
"code": 9002
+1 -1
View File
@@ -43,7 +43,7 @@ var outside = n => n + 1;
/** @type {Final<{ fantasy }, { heroes }>} */
var noreturn = (barts, tidus, noctis) => "cecil"
>noreturn : Final<{ fantasy: any; }, { heroes: any; }>
>(barts, tidus, noctis) => "cecil" : (barts: { fantasy: any; }, tidus: { heroes: any; }, noctis: { heroes: any; } & { fantasy: any; }) => "cecil"
>(barts, tidus, noctis) => "cecil" : (barts: { fantasy: any; }, tidus: { heroes: any; }, noctis: { heroes: any; } & { fantasy: any; }) => "cecil" | "zidane"
>barts : { fantasy: any; }
>tidus : { heroes: any; }
>noctis : { heroes: any; } & { fantasy: any; }
@@ -11,7 +11,7 @@ var x = 1;
/** @type {NS.Nested.Inner} */
function f(space, peace) {
>f : (space: any, peace: any) => string
>f : (space: any, peace: any) => string | number
>space : any
>peace : any
@@ -61,7 +61,7 @@ y(1);
/** @type {function (number)} */
const x1 = (a) => a + 1;
>x1 : (arg0: number) => any
>(a) => a + 1 : (a: number) => number
>(a) => a + 1 : (a: number) => any
>a : number
>a + 1 : number
>a : number
@@ -3,8 +3,7 @@ tests/cases/conformance/jsdoc/0.js(6,5): error TS2322: Type '"hello"' is not ass
tests/cases/conformance/jsdoc/0.js(10,4): error TS2345: Argument of type '"string"' is not assignable to parameter of type 'number'.
tests/cases/conformance/jsdoc/0.js(17,1): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does not exist on type 'number'.
tests/cases/conformance/jsdoc/0.js(24,7): error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/0.js(24,19): error TS2322: Type 'number' is not assignable to type 'string'.
==== tests/cases/conformance/jsdoc/0.js (6 errors) ====
@@ -42,7 +41,6 @@ tests/cases/conformance/jsdoc/0.js(24,7): error TS2322: Type '(a: number) => num
/** @type {function (number): string} */
const x4 = (a) => a + 1;
~~
!!! error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
x4(0);
@@ -13,7 +13,7 @@ var n = "hello";
/** @type {function (number)} */
const x1 = (a) => a + 1;
>x1 : (arg0: number) => any
>(a) => a + 1 : (a: number) => number
>(a) => a + 1 : (a: number) => any
>a : number
>a + 1 : number
>a : number
@@ -47,7 +47,7 @@ a = x2(0);
/** @type {function (number): number} */
const x3 = (a) => a.concat("hi");
>x3 : (arg0: number) => number
>(a) => a.concat("hi") : (a: number) => any
>(a) => a.concat("hi") : (a: number) => number
>a : number
>a.concat("hi") : any
>a.concat : any
@@ -63,7 +63,7 @@ x3(0);
/** @type {function (number): string} */
const x4 = (a) => a + 1;
>x4 : (arg0: number) => string
>(a) => a + 1 : (a: number) => number
>(a) => a + 1 : (a: number) => string
>a : number
>a + 1 : number
>a : number
@@ -4,9 +4,11 @@ tests/cases/conformance/jsdoc/test.js(7,24): error TS2322: Type 'number' is not
tests/cases/conformance/jsdoc/test.js(10,17): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/test.js(12,14): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/test.js(14,24): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/test.js(34,5): error TS2322: Type '1 | 2' is not assignable to type '2 | 3'.
Type '1' is not assignable to type '2 | 3'.
==== tests/cases/conformance/jsdoc/test.js (6 errors) ====
==== tests/cases/conformance/jsdoc/test.js (7 errors) ====
// all 6 should error on return statement/expression
/** @type {(x: number) => string} */
function h(x) { return x }
@@ -33,4 +35,27 @@ tests/cases/conformance/jsdoc/test.js(14,24): error TS2322: Type 'number' is not
var k = function (x) { return x }
~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
/** @typedef {(x: 'hi' | 'bye') => 0 | 1 | 2} Argle */
/** @type {Argle} */
function blargle(s) {
return 0;
}
/** @type {0 | 1 | 2} - assignment should not error */
var zeroonetwo = blargle('hi')
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
/** @type {Gioconda} */
function monaLisa(sb) {
return typeof sb === 'string' ? 1 : 2;
}
/** @type {2 | 3} - overloads are not supported, so there will be an error */
var twothree = monaLisa(false);
~~~~~~~~
!!! error TS2322: Type '1 | 2' is not assignable to type '2 | 3'.
!!! error TS2322: Type '1' is not assignable to type '2 | 3'.
@@ -36,3 +36,34 @@ var k = function (x) { return x }
>x : Symbol(x, Decl(test.js, 13, 18))
>x : Symbol(x, Decl(test.js, 13, 18))
/** @typedef {(x: 'hi' | 'bye') => 0 | 1 | 2} Argle */
/** @type {Argle} */
function blargle(s) {
>blargle : Symbol(blargle, Decl(test.js, 13, 33))
>s : Symbol(s, Decl(test.js, 18, 17))
return 0;
}
/** @type {0 | 1 | 2} - assignment should not error */
var zeroonetwo = blargle('hi')
>zeroonetwo : Symbol(zeroonetwo, Decl(test.js, 23, 3))
>blargle : Symbol(blargle, Decl(test.js, 13, 33))
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
/** @type {Gioconda} */
function monaLisa(sb) {
>monaLisa : Symbol(monaLisa, Decl(test.js, 23, 30))
>sb : Symbol(sb, Decl(test.js, 28, 18))
return typeof sb === 'string' ? 1 : 2;
>sb : Symbol(sb, Decl(test.js, 28, 18))
}
/** @type {2 | 3} - overloads are not supported, so there will be an error */
var twothree = monaLisa(false);
>twothree : Symbol(twothree, Decl(test.js, 33, 3))
>monaLisa : Symbol(monaLisa, Decl(test.js, 23, 30))
@@ -40,3 +40,45 @@ var k = function (x) { return x }
>x : number
>x : number
/** @typedef {(x: 'hi' | 'bye') => 0 | 1 | 2} Argle */
/** @type {Argle} */
function blargle(s) {
>blargle : (s: "hi" | "bye") => 0 | 1 | 2
>s : "hi" | "bye"
return 0;
>0 : 0
}
/** @type {0 | 1 | 2} - assignment should not error */
var zeroonetwo = blargle('hi')
>zeroonetwo : 0 | 1 | 2
>blargle('hi') : 0 | 1 | 2
>blargle : (s: "hi" | "bye") => 0 | 1 | 2
>'hi' : "hi"
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
/** @type {Gioconda} */
function monaLisa(sb) {
>monaLisa : (sb: any) => 1 | 2
>sb : any
return typeof sb === 'string' ? 1 : 2;
>typeof sb === 'string' ? 1 : 2 : 1 | 2
>typeof sb === 'string' : boolean
>typeof sb : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
>sb : any
>'string' : "string"
>1 : 1
>2 : 2
}
/** @type {2 | 3} - overloads are not supported, so there will be an error */
var twothree = monaLisa(false);
>twothree : 2 | 3
>monaLisa(false) : 1 | 2
>monaLisa : (sb: any) => 1 | 2
>false : false
@@ -0,0 +1,20 @@
tests/cases/conformance/jsdoc/test.js(1,5): error TS8030: The type of a function declaration must be callable.
tests/cases/conformance/jsdoc/test.js(7,5): error TS2322: Type '(prop: any) => void' is not assignable to type '{ prop: string; }'.
Property 'prop' is missing in type '(prop: any) => void'.
==== tests/cases/conformance/jsdoc/test.js (2 errors) ====
/** @type {number} */
~~~~~~~~~~~~~~
!!! error TS8030: The type of a function declaration must be callable.
function f() {
return 1
}
/** @type {{ prop: string }} */
var g = function (prop) {
~
!!! error TS2322: Type '(prop: any) => void' is not assignable to type '{ prop: string; }'.
!!! error TS2322: Property 'prop' is missing in type '(prop: any) => void'.
}
@@ -0,0 +1,14 @@
=== tests/cases/conformance/jsdoc/test.js ===
/** @type {number} */
function f() {
>f : Symbol(f, Decl(test.js, 0, 0))
return 1
}
/** @type {{ prop: string }} */
var g = function (prop) {
>g : Symbol(g, Decl(test.js, 6, 3))
>prop : Symbol(prop, Decl(test.js, 6, 18))
}
@@ -0,0 +1,16 @@
=== tests/cases/conformance/jsdoc/test.js ===
/** @type {number} */
function f() {
>f : () => number
return 1
>1 : 1
}
/** @type {{ prop: string }} */
var g = function (prop) {
>g : { prop: string; }
>function (prop) {} : (prop: any) => void
>prop : any
}
@@ -1,8 +1,6 @@
tests/cases/conformance/jsdoc/0.js(5,3): error TS2322: Type 'number' is not assignable to type 'string | undefined'.
tests/cases/conformance/jsdoc/0.js(7,3): error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/0.js(11,3): error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/0.js(8,7): error TS2322: Type '"42"' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/0.js(11,20): error TS2322: Type '"lol"' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/0.js(13,15): error TS2322: Type '"0"' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/0.js(15,3): error TS2322: Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/0.js(19,5): error TS2322: Type 'number' is not assignable to type 'string'.
@@ -19,16 +17,14 @@ tests/cases/conformance/jsdoc/0.js(22,22): error TS2345: Argument of type '"0"'
!!! error TS2322: Type 'number' is not assignable to type 'string | undefined'.
/** @type {function(number): number} */
method1(n1) {
~~~~~~~
!!! error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
return "42";
~~~~~~~~~~~~
!!! error TS2322: Type '"42"' is not assignable to type 'number'.
},
/** @type {function(number): number} */
method2: (n1) => "lol",
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '(n1: number) => string' is not assignable to type '(arg0: number) => number'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
~~~~~
!!! error TS2322: Type '"lol"' is not assignable to type 'number'.
/** @type {function(number): number} */
arrowFunc: (num="0") => num + 42,
~~~~~~~
@@ -14,7 +14,7 @@ const obj = {
/** @type {function(number): number} */
method1(n1) {
>method1 : (n1: number) => string
>method1 : (n1: number) => number
>n1 : number
return "42";
@@ -24,7 +24,7 @@ const obj = {
/** @type {function(number): number} */
method2: (n1) => "lol",
>method2 : (arg0: number) => number
>(n1) => "lol" : (n1: number) => string
>(n1) => "lol" : (n1: number) => number
>n1 : number
>"lol" : "lol"
@@ -35,6 +35,6 @@ function g(vvvvv) {
/** @type {Cb} */
const cb = x => {}
>cb : Cb
>x => {} : (x: any) => void
>x => {} : (x: any) => any
>x : any
@@ -16,3 +16,23 @@ function i(x) { return x }
var j = x => x
/** @type {{ (x: number): string }} */
var k = function (x) { return x }
/** @typedef {(x: 'hi' | 'bye') => 0 | 1 | 2} Argle */
/** @type {Argle} */
function blargle(s) {
return 0;
}
/** @type {0 | 1 | 2} - assignment should not error */
var zeroonetwo = blargle('hi')
/** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */
/** @type {Gioconda} */
function monaLisa(sb) {
return typeof sb === 'string' ? 1 : 2;
}
/** @type {2 | 3} - overloads are not supported, so there will be an error */
var twothree = monaLisa(false);
@@ -0,0 +1,13 @@
// @checkJs: true
// @allowJs: true
// @noEmit: true
// @Filename: test.js
/** @type {number} */
function f() {
return 1
}
/** @type {{ prop: string }} */
var g = function (prop) {
}