Skip to content

Unexpected behavior when static methods are used in an array #41977

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

Open
AndrewKushnir opened this issue Dec 15, 2020 · 4 comments
Open

Unexpected behavior when static methods are used in an array #41977

AndrewKushnir opened this issue Dec 15, 2020 · 4 comments
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Milestone

Comments

@AndrewKushnir
Copy link

TypeScript Version: 4.2.0-dev.20201211 (also tested with 4.2.0)

Search Terms: static method(s)

Code

class S {
  static f(a: number|string): void {}
}

function g(a: number): void {}

// Uncomment to see a different behavior of the `output` const type.
// type G = typeof g;

const y = [S.f, g];
const output: ((ctrl: number|string) => void)[] = y;

Expected behavior:
Consistent type checking result (an error) for the output const with and without type G = typeof g.

Actual behavior:
If the type G = typeof g is present, the error is thrown (as expected), otherwise there is no error thrown.

Playground Link:
Playground link to reproduce the error.

// cc @alxhub @rkirov

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Dec 15, 2020

Excellent repro btw.

This dates back to at least 3.3.3. A fix might be difficult...

@RyanCavanaugh
Copy link
Member

Yeah this is longstanding and a problem with subtyping being non-directional in the presence of bivariant functions.

A similar repro elucidates the problem. Observe the .d.ts output of this program:

interface S {
  f(a: number | string): void;
}
declare const S: S;
function g(a: number): void { }

// No-op lines
g;
S.f;

const arr = [g, S.f];

In this example, we give arr the type (typeof g)[];. If you swap the no-op lines, we instead give it the type ((a: string | number) => void). This is because the union is formed in order of the type IDs, and the type IDs are assigned in the order in which the types were manifested.

@RyanCavanaugh
Copy link
Member

Hypothetical fix from talking to @ahejlsberg and @weswigham is to remove method bivariance in the subtype relationship but keep it in the assignability relationship. This would definitely fix this issue, the question is simply a matter of what side effects it would have.

@ahejlsberg
Copy link
Member

I've been experimenting a little, and it looks like we can fix this issue by making --strictFunctionTypes slightly stricter. When we're in that mode, we check function types bi-variantly when the target is a method or constructor. If we instead check bi-variantly only when both source and target are a method or constructor, we fix this issue in the OP. This change appears to have no effect on our tests.

I'm convinced that changing the strict subtype relation to always check function types strictly (regardless of the --strictFunctionTypes setting) is a breaking change that we probably can't do. Too much code out there relies on the fact that we pick the "first" of two mutually assignable types in subtype reduction and would break if we pick a stricter type.

I will put up a PR with my suggested change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
4 participants