microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.68k stars 12.44k forks source link

`@typedef {Function}` generates a type that can be read, but can't be applied #50274

Open coolaj86 opened 2 years ago

coolaj86 commented 2 years ago

🔎 Search Terms

callback typedef function alias semantic jsdoc tsc tsserver

🕗 Version & Regression Information

Always, up to Version 4.7.4

Summary

You should be able to create a typedef of a Function and use that type for any applicable function.

For example:

/**
 * @typedef {Function} PersonGreet
 * @param {String} name - other's name
 * @returns {String} - the greeting
 */

/** @type PersonGreet */
let greet = function (name) {
    return `Hello ${name}, my name is Rob the Robot!`;
}

However, this fails:

The type of a function declaration must match the function's signature.

Parameter 'name' implicitly has an 'any' type.

Complete Example

⏯ Playground Link

https://www.typescriptlang.org/play?filetype=js#code/EQVwzgpgBGAuBOBLAxrYBuAUJg9AKj0yjygAFYBPABwgBMIAzKAbwHkAjAKwlQF8oAChHhgA9gDsiJUlXiia8SiwDKCROIDm-cQEMAthCllZ84UuZCREgOLwIEWPw12HUnNnyFiZSjXpNmADEQcVRECX5LMXFbe1gjGR14fRYoiKhRWAALYQS7WBB4cTAVNU1eN2wAGwdBYWioAF4WXixcAgSqJJS2Lh5HKCpO7r1SpHLBgDpdAzyHQuLU+ojKtPFJ5DsdWGhmhhCwiSgACioAShYiKGua2EHl8SaWtuvrhWjp-V2pmYgX188Pmo0DWsVqeHcr3uVnWzjiT32oVg4UecIcx0yOXgF2YVyh13yCygAAMABIQKpVURQAAkzExwk+Bl4ABooHoKFBflBECU6e8JEyILwAITErBQ1rYKGEorQ6JYKWYPSiWggGqTCAADyookUJWaa3QQA

💻 Code

"use strict";

/**
 * @typedef {Object} Person
 * @property {String} name
 * @property {PersonGreet} greet
 */

/**
 * @callback PersonGreet
 * @param {Person} other
 * @returns {String}
 */

let Person = {};

/**
 * @param {Object} p
 * @param {String} p.name
 * @returns {Person}
 */
Person.create = function (p) {
    let person = {};

    person.name = p.name;

    /** @type PersonGreet */
    person.greet = function (other) {
        return `Hello ${other.name}, my name is ${person.name}!`;
    };

    return person;
};

module.exports = Person;

🙁 Actual behavior

In the example you can see that the checker doesn't apply the type PersonGreet to the function being assigned to person.greet.

The type declared with /** @typedef {Function} */ is parsed, but it can't be used to to type a compliant function directly (although it is somewhat usable as an export that other modules can understand the type in some contexts).

🙂 Expected behavior

A type generated with @typedef {Function} should be able to be applied to functions.

Workaround

There's no documentation for this, but it just so happens that if you use the @callback alias some extra machinery kicks in and you can type functions as you would have expected.

/**
- * @typedef {Function} PersonGreet
+ * @callback PersonGreet
 * @param {String} name - other's name
 * @returns {String} - the greeting
 */

/** @type PersonGreet */
let greet = function (name) {
    return `Hello ${name}, my name is Rob the Robot!`;
}

The problem is that @callback is usually incorrect and confusing:

It's also possible to use TypeScript's proprietary arrow notation, but this generates other errors:

/**
- * @typedef {Function} PersonGreet
- * @param {String} name - other's name
- * @returns {String} - the greeting
+ * @typedef {(
+ *     other: Person
+ * ) => string} PersonGreet
 */

/** @type PersonGreet */
let greet = function (name) {
    return `Hello ${name}, my name is Rob the Robot!`;
}
JSDoc '@typedef' tag should either have a type annotation or be followed by '@property' or '@member' tags.
coolaj86 commented 2 years ago

When I originally went through the fill-out-form-wizard I was just following the steps. Having come back to look at this I realize that that process didn't result in clear communication about this bug (something is actually broken and requires a workaround, not simply an experience enhancement).

I've updated description and the text to better communicate the bug.

RyanCavanaugh commented 2 years ago

all functions are functions not all functions are callbacks in fact, most functions are not callbacks

I don't understand what this means. A callback is a pattern where a function is given a reference to another function, which is then invoked. For example [1, -2, 3].map(Math.abs) is a legal way to process an array -- what is the distinction you're trying to draw here?

coolaj86 commented 2 years ago

@RyanCavanaugh I don't want to start any side discussions that will detract from the bug in the issue. I'd be happy to carry on that conversation about my suggested workaround somewhere else. Perhaps here? https://gist.github.com/coolaj86/cfc802175d21c90d74fdc04fa27e23b0

The bug is that /** @typedef {Function} ... */ is not working correctly - it's not providing a @typeable type.

coolaj86 commented 2 years ago

In short: A callback defines that a function is used asynchronously. .map() is synchronous. It can accept an anonymous function, but trying to use a callback would result in buggy code.