Skip to content

Get return type from @type tag #25580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -20521,23 +20530,28 @@ 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
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnOrPromisedType);
}

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
Expand Down Expand Up @@ -21989,7 +22003,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) {
Expand Down Expand Up @@ -23049,7 +23063,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
Expand All @@ -23074,7 +23088,6 @@ namespace ts {
// then<U>(...): Promise<U>;
// }
//
const returnTypeNode = getEffectiveReturnTypeNode(node)!; // TODO: GH#18217
const returnType = getTypeFromTypeNode(returnTypeNode);

if (languageVersion >= ScriptTarget.ES2015) {
Expand Down Expand Up @@ -23484,15 +23497,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)) {
Expand All @@ -23505,6 +23515,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);
}
}
}
}

Expand Down Expand Up @@ -24767,7 +24784,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);
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4017,6 +4017,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
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/callbackTag2.types
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/callbackTagNamespace.types
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/checkJsdocTypeTag1.types
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions tests/baselines/reference/checkJsdocTypeTag2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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) ====
Expand Down Expand Up @@ -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);
6 changes: 3 additions & 3 deletions tests/baselines/reference/checkJsdocTypeTag2.types
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
27 changes: 26 additions & 1 deletion tests/baselines/reference/checkJsdocTypeTag5.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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'.

31 changes: 31 additions & 0 deletions tests/baselines/reference/checkJsdocTypeTag5.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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))

42 changes: 42 additions & 0 deletions tests/baselines/reference/checkJsdocTypeTag5.types
Original file line number Diff line number Diff line change
Expand Up @@ -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

20 changes: 20 additions & 0 deletions tests/baselines/reference/checkJsdocTypeTag6.errors.txt
Original file line number Diff line number Diff line change
@@ -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'.
}

14 changes: 14 additions & 0 deletions tests/baselines/reference/checkJsdocTypeTag6.symbols
Original file line number Diff line number Diff line change
@@ -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))
}

16 changes: 16 additions & 0 deletions tests/baselines/reference/checkJsdocTypeTag6.types
Original file line number Diff line number Diff line change
@@ -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
}

Loading