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

Couple apparent bugs with variance checking #53210

Open
masaeedu opened this issue Mar 12, 2023 · 6 comments
Open

Couple apparent bugs with variance checking #53210

masaeedu opened this issue Mar 12, 2023 · 6 comments
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@masaeedu
Copy link
Contributor

masaeedu commented Mar 12, 2023

Bug Report

🔎 Search Terms

variance, methods, generics

NB: I did not search very exhaustively, but I did minimize the examples, so should be pretty easy to review and close as dupe or "by design" or whatever.

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about strictFunctionTypes

⏯ Playground Link

Variance checking does not interact correctly with extends:

https://www.typescriptlang.org/play?#code/C4TwDgpgBAYgPAewK7CgQQDRWagQgPigF4o4ANKCAD2AgDsATAZ3WPXwAoA3ALijICUxQrgBQogMYI6TVAEM+8OhC4QATlmWq1hEl2FQuk6bKgAjRXDpIAtmfWaV63VDnGZqNRCZIANsD4tdTYzDgAWACYhAHpoqAB3AAsEBDAmIA

Variance checking does not interact correctly with "records with methods" types:

https://www.typescriptlang.org/play?#code/C4TwDgpgBAGgPAewK7CgZQHxQLxQN4BQUUAHgBQDOAXOgJQ0B2EAbhAE4EC+BBAxggwqoAhjXhNWbLLjykaFHFgXd+g1ACMxcJAwDWDBAHcG0qML4ChUQwAsECMNSgT2OKOoB05ACwAmWgRAA

💻 Code

Variance checking does not interact correctly with extends:

type F<out A, out B> = <X extends A = A>(v: X) => B

const a: F<never, never> = v => v
const b: F<unknown, never> = a
const result: never = b(42) // whoops

Variance checking does not interact correctly with "records with methods" types:

type X<out S> = {
  x(s: S): never
}

const a: X<never> = { x: s => s }
const b: X<unknown> = a
const whoops: never = b.x(42)

🙁 Actual behavior

In both cases the typechecker allows me to inhabit never.

🙂 Expected behavior

The typechecker should reject my incorrect variance annotations (F<out A, ...> and X<out S>) and not allow me to inhabit never.


Incidentally this came to light out of some discussion in the "forall for non-functions" issue here: #17574 (comment), but these things don't really seem materially related.

Related to #48240.

@scorbiclife
Copy link

Came here to report that the out keyword is optional and it also happens here:

type F<out A, out B> = <X>(v: X extends A ? X : never) => B

const a: F<never, never> = v => v
const b: F<number, never> = a
const result: never = b(42) // whoops

@masaeedu
Copy link
Contributor Author

@nightlyherb Hmm, not sure I'm following. The out keyword in the repros given is material, the point is to demonstrate that the typechecker agrees with the incorrect/unsound variance annotation given.

The snippet you gave also contains an out annotation, is that a typo?

@scorbiclife
Copy link

I just tried to say that all the examples you gave and the example I gave exhibits buggy behavior with or without variance annotations.

The buggy behavior without variance annotations might be a separate issue, but I thought it was worth a mention since it's so similar and it might be a related issue.

@masaeedu
Copy link
Contributor Author

The buggy behavior without variance annotations might be a separate issue, but I thought it was worth a mention since it's so similar and it might be a related issue.

@nightlyherb You're right that the issues are related, in fact I think they're mostly indistinguishable. The buggy behavior we're talking about is the ability to assign const b: ... = a, which is accepted due to the (incorrectly) assumed covariance of a particular parameter. If the typechecker correctly determined that the relevant type parameters were contravariant instead, both my incorrect out annotations and the assignment would be rejected.

@RyanCavanaugh
Copy link
Member

A type parameter appearing only in a constraint is kinda sus, and generally you would want to write X & A in a usage position wherever you write <X extends A>(... But that doesn't even work here. I think the variance is just not being measured correctly. Examples:

// Incorrect: Fails to error
type F1<out A> = <X>(v: X & A) => unknown;
// Correctly errors
type F2<out A> = <X>(v1: X, v2: A) => unknown;
// Correctly errors
type F3<out A> = <X>(v2: A) => X;

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Help Wanted You can do this labels Mar 14, 2023
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Mar 14, 2023
@scorbiclife
Copy link

scorbiclife commented Mar 15, 2023

I have encountered a situation where this sus usage could be useful.

// F seems to behave like a generic type contravariant over Xi,
// but typescript doesn't error on either in Xi or out Xi,
// so I cannot use this generic type.
type F<Xi> = <X extends Xi>(x: X) => X;

// I can instantiate this generic type manually
// to observe the contravariant behavior.
// number is a subtype of unknown and
// F<unknown> seems to be a subtype of F<number>
declare const funknown = <X extends unknown>(x: X) => X;
declare const fnumber = <X extends number>(x: X) => X;
const funknown2: typeof funknown = fnumber; // error
const fnumber2: typeof fnumber = funknown; // no error

// this does seem natural because `funknown` is assignable
// to a superset of types assignable from `fnumber`

May I ask, is my observation correct?
i.e. is F contravariant over Xi in current typescript?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
Development

No branches or pull requests

3 participants