Skip to content

checkJs should be as strict as TS about matching function arity #31888

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

Closed
golopot opened this issue Jun 13, 2019 · 12 comments
Closed

checkJs should be as strict as TS about matching function arity #31888

golopot opened this issue Jun 13, 2019 · 12 comments
Assignees
Labels
Bug A bug in TypeScript Experimentation Needed Someone needs to try this out to see what happens Suggestion An idea for TypeScript

Comments

@golopot
Copy link

golopot commented Jun 13, 2019

TypeScript Version: 3.6.0-dev.20190613

Search Terms:
overload
higher order function
checkjs

Code

// id.d.ts
declare type Unary = (a: number) => void;
declare type Binary = (a: number, b: number) => void;
export declare function id(fn: Unary): Unary;
export declare function id(fn: Binary): Binary;
export {};
// id.js
export const id = fn => fn;
// index.js
import {id} from './id'

// expected f: Binary
// actual   f: Unary
const f = id((a, b) => {});

// Error: Expected 1 arguments, but got 2.
f(1, 2);
// tsconfig.json
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "outDir": "dist",
    "noEmit": true,
    "strict": false,
    "esModuleInterop": true
  }
}

Expected behavior:
f is a binary function.
Actual behavior:
f is a unary function.
Playground Link:
N/A
Related Issues:
This issue makes util.promisify in @types/node unusable with checkjs. See
DefinitelyTyped/DefinitelyTyped#33340

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Jun 25, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.6.0 milestone Jun 25, 2019
@sandersn
Copy link
Member

sandersn commented Aug 7, 2019

Here's a smaller js-only repro (not required, but makes it easier for me to test):

/** @param {{
    (fn: (a: number) => void): number;
    (fn: (a: number, b: number) => void): string
}} id */
export function test(id) {
    /** @type {string} */
    var val = id((a, b) => a + b);
}

Errors (with strict on):

  1. on val, because the compiler resolves the wrong overload (expected because of this bug).
  2. on parameters (a, b), because their types are implicitly any (unexpected, perhaps caused by this bug?).

@sandersn
Copy link
Member

sandersn commented Aug 7, 2019

Unfortunately, this is a side effect of the way that we handle functions in Javascript. If a function doesn't have all of its parameters annotated, then the compiler doesn't require all of them to be passed. Internally, this means the signature gets marked with minArgumentCount: 0. This, in turn, means that (a, b) => {} can match Unary — since it doesn't require any parameters, passing one parameter is better than nothing.

To work around this, annotate (a, b) => {} with types. Here's one way to do it:

/**
 * @param {number} a
 * @param {number} b
 */
(a, b) => {}

To fix this, we'd need to make minArgumentCount: 2 just like it is in TS. I have a couple of ideas:

  1. Require all parameters with "strict": true. Few people have strict on in JS projects, but we already turn off index-signatures-everywhere with strict on. This actually needs to go under a specific strict flag, but noImplicitAny seems like a fine one since you'll get a no-implicit-any error in the above example without type annotations — the unary overload blocks contextual typing from the binary overload.
  2. Require all parameters for arrow functions. This assumes that arrow functions are used only as expressions, which is not correct.

2a. Require all parameters for arrow functions that are not immediately assigned.
2aa. Require all parameters for arrow functions that have a contextual type.
2ab. Require all parameters for arrow functions that are immediately used as an argument.

There are a lot of options here.

@brendankenny
Copy link
Contributor

  • Require all parameters with "strict": true. Few people have strict on in JS projects, but we already turn off index-signatures-everywhere with strict on. This actually need to go under a specific strict flag, but noImplicitAny seems like a fine one since you'll get a no-implicit-any error in the above example without type annotations — the unary overload blocks contextual typing from the binary overload.

This option sounds great! (independent of if the others should be done, too)

As you said, the error is already given elsewhere with noImplicitAny, and this also has the benefit of alerting a call of a remote function to the problem as well (which could be helpful if e.g. the caller doesn't control the any-paramed function code and so doesn't get notified of the arity problem in their own code)

@sandersn
Copy link
Member

sandersn commented Aug 8, 2019

@DanielRosenwasser @weswigham for additional opinions.

@DanielRosenwasser
Copy link
Member

If the function has a contextual type, then I think it's fair game to treat it similarly to a function with parameter annotations.

@weswigham
Copy link
Member

Unfortunately, this is a side effect of the way that we handle functions in Javascript. If a function doesn't have all of its parameters annotated, then the compiler doesn't require all of them to be passed. Internally, this means the signature gets marked with minArgumentCount: 0.

Tbh, I'm a fan of just not doing this and bringing the JS behavior inline with TS - we have a way in js to specify optional arguments, and so if errors are on, you can fix the error, and if errors aren't on, the required arity isn't going to be unjustly noisy or anything, since errors aren't on.

@sandersn
Copy link
Member

You mean with @param [name] ? I'm not sure that checkJs is a strong enough signal from the user that we should require jsdoc annotations where we can't determine the intended arity of a function. Maybe it is though.

@weswigham
Copy link
Member

I think it is. It's worth noting that we also still get arity hints from parameter initializers, in addition to jsdoc comments.

@sandersn
Copy link
Member

I think this is a pretty easy change to make, so I think we should just run the user tests on a PR to see how many new errors turn up. Looking at actual code will make it easier to think about how it will affect people, I think.

@sandersn sandersn added Experimentation Needed Someone needs to try this out to see what happens Suggestion An idea for TypeScript labels Aug 21, 2019
@sandersn sandersn changed the title Bug on type of overloaded higher order function with checkJs checkJs should be as strict as TS about matching function arity Aug 21, 2019
sandersn added a commit that referenced this issue Dec 6, 2019
This is an experiment in fixing #31888. This is the simplest experiment,
which requires all parameters to be provided in JS, same as TS. If this
doesn't break the user tests that are written in JS too badly, then I
won't proceed to the more complex experiments, which are to require all
paremeters for contextually typed functions, or to require all
parameters when `"noImplicitAny": true`.
@sandersn
Copy link
Member

sandersn commented Dec 6, 2019

The simplest solution -- requiring all parameters in JS -- is up at #35153

@sandersn
Copy link
Member

@weswigham More details at the PR, but the upshot is that lots of webpack breaks, which is evidence that JSDoc is too onerous a requirement for specifying optional parameters.

@sandersn sandersn removed this from the TypeScript 3.8.0 milestone Jan 8, 2020
@sandersn
Copy link
Member

sandersn commented Apr 2, 2020

I'm going to close this because I think Wesley's change in 3.9 #37173 addresses this bug adequately.

@sandersn sandersn closed this as completed Apr 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Experimentation Needed Someone needs to try this out to see what happens Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants