Skip to content
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

Type System Bug: Function.prototype.call variance error #52239

Closed
ColinTimBarndt opened this issue Jan 14, 2023 · 7 comments
Closed

Type System Bug: Function.prototype.call variance error #52239

ColinTimBarndt opened this issue Jan 14, 2023 · 7 comments

Comments

@ColinTimBarndt
Copy link

Bug Report

🔎 Search Terms

(is:open label:Bug) function, call, prototype method, variance

🕗 Version & Regression Information

  • This is a type system error
  • This is the behavior in every version I tried (3.3, 4.9.4, 5.0.0-dev.20230113), and there is no FAQ about this

⏯ Playground Link

Playground link with relevant code

💻 Code

interface Animal {
    getAttention(): void;
}

class Cat implements Animal {
    private meow(): void {
        console.log("meow!");
    }
    getAttention() {
        this.meow();
    }
}

class Dog implements Animal {
    private bark(): void {
        console.log("bark!");
    }
    getAttention() {
        this.bark();
    }
}

function animalGetsAttention(it: Animal, method: (this: Animal) => void) {
    method.call(it);
}

animalGetsAttention(new Dog(), Cat.prototype.getAttention); // Error: this.meow is not a function

🙁 Actual behavior

The code passed the type checker and compiled even though it is incorrect and fails at runtime.

🙂 Expected behavior

The compiler should not allow calling the method; the type system should not allow generalizing this in this case (as far as I understand it, as Cat.prototype.getAttention should require this to extend Cat and not Cat to extend Animal).
I believe this has to do with Co- and Contravariance.

@fatcerberus
Copy link

fatcerberus commented Jan 14, 2023

Nothing to do with variance in this case. this simply doesn't factor into the type check here at all.

Methods don't have automatic this types for performance reasons (my understanding is that this makes all methods implicitly generic under the hood); effectively the default is this: any. Specifying the this type for getAttention explicitly (as either this: Cat or ideally, this: this) makes your example error as expected.

@fatcerberus
Copy link

Found the relevant issue: #35451

@ColinTimBarndt
Copy link
Author

Methods don't have automatic this types for performance reasons (my understanding is that this makes all methods implicitly generic under the hood). Specifying the this type for getAttention explicitly (as either this: this or this: Cat) makes your example error as expected.

True, and I did not know this was possible. Though, the type-checker still accepted code that is not valid. It should either not accept it when calling the prototype or when declaring the function and calling the private method.

Code
interface Animal {
    getAttention(): void;
}

class Cat implements Animal {
    private meow(): void {
        console.log("meow!");
    }
    getAttention(this: this) {
        this.meow();
    }
}

class Dog implements Animal {
    private bark(): void {
        console.log("bark!");
    }
    getAttention(this: this) {
        this.bark();
    }
}

function animalGetsAttention(it: Animal, method: (this: Animal) => void) {
    method.call(it);
}

// Argument of type '(this: Cat) => void' is not assignable to parameter of type '(this: Animal) => void'.
animalGetsAttention(new Dog(), Cat.prototype.getAttention);

Found the relevant issue: #35451

If the compiler accepts invalid code for performance reasons, then why is there no tsconfig compiler option for stricter type checking in that case? It would add better type safety for code bases which are not class-heavy.

@fatcerberus
Copy link

Though, the type-checker still accepted code that is not valid.

Just for future reference, this is unavoidable in general; it's explicitly called out in the design goals for TS that the compiler tries to "strike a balance between correctness and productivity". Plus the type system seeks to be friendly to idiomatic JS patterns as possible, which as you can probably imagine is its own entire can of worms. Tradeoffs will inevitably be made.

If the compiler accepts invalid code for performance reasons, then why is there no tsconfig compiler option for stricter type checking in that case?

Interestingly, discussion in #6739 suggests this behavior was intended to be behind a flag (so not the default), but the perf implications were ultimately considered severe enough that it wasn't considered worth the tradeoff even then.

@ColinTimBarndt
Copy link
Author

So this is a "Won't fix", then I will close this issue.

@ColinTimBarndt ColinTimBarndt closed this as not planned Won't fix, can't repro, duplicate, stale Jan 14, 2023
@MartinJohns
Copy link
Contributor

Relevant non-goal from the TypeScript Design Goals:

Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.

@fatcerberus
Copy link

So this is a "Won't fix"

Considering that #35451 was closed as design limitation, most likely yes, unfortunately. Disclaimer: I'm not a TypeScript maintainer, I just lurk around here a lot (probably more than is healthy, actually...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants