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

Generic type parameter in plain function is checked contravariantly. #16797

Closed
HerringtonDarkholme opened this issue Jun 28, 2017 · 3 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@HerringtonDarkholme
Copy link
Contributor

HerringtonDarkholme commented Jun 28, 2017

I don't know if it is intended. But it seems very hard for users to understand.

Note in the #15104, it seems type parameter in generic type is checked co-variantly only when the type parameter is used in the parameter position of a callback (that is, a contravariant position in another contravariant position makes a cavariant position).

However, in the following example, Done is a plain function, not callback. So A is contravariantly checked. This might confuse old TS users.

TypeScript Version: 2.4.1

Code

class Animal {
    private animalTag: any
}
class Dog extends Animal {
    private dogTag: any
}
function trainDog(d: Dog) {  }
type Done = <A extends Animal>(result: A) => void
function cloneAnimal(done: Done): void {}
function cloneAnimal2(done: (result: Animal) => void): void {}
cloneAnimal(trainDog); // error
cloneAnimal2(trainDog); // compiles

Expected behavior:

All compiles.

Actual behavior:

cloneAnimal fails to compile. Argument of type '(d: Dog) => void' is not assignable to parameter of type 'Done'. Types of parameters 'd' and 'result' are incompatible. Type 'A' is not assignable to type 'Dog'. Type 'Animal' is not assignable to type 'Dog'. Property 'dogTag' is missing in type 'Animal'.

Related:
#16795
#16790

@HerringtonDarkholme HerringtonDarkholme changed the title Generic callback parameter is checked contravariantly. Generic type parameter in function is checked contravariantly. Jun 28, 2017
@HerringtonDarkholme HerringtonDarkholme changed the title Generic type parameter in function is checked contravariantly. Generic type parameter in plain function is checked contravariantly. Jun 28, 2017
@sylvanaar
Copy link

sylvanaar commented Jun 28, 2017

The rule applies to function parameters that are functions. So it applies to trainDog which you are passing a a parameter to cloneAnimal which expects a function that takes a parameter result that is a subtype of Animal. Your function trainDog takes a parameter d of type Dog. Callback parameters are checked bivariantly it is their return values that are checked covariantly, you are just returning void in both.

So you need to change Done so the parameter name matches, and the inheritance relation wont work because of bivariance checking of callback parameters

@DanielRosenwasser DanielRosenwasser added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 29, 2017
@HerringtonDarkholme
Copy link
Contributor Author

HerringtonDarkholme commented Jun 29, 2017

How about this reduced example? Without callback at all.

Note, enabling noStrictGenericChecks makes it compile.

class Animal { }
class Dog extends Animal {
    bark(): void {}
}

type TrainDog = (d: Dog) => void
type TrainAnimal = (a: Animal) => void
type TrainAnimalGen = <A extends Animal>(a: A) => void

declare var trainDog: TrainDog
declare var trainAnimal: TrainAnimal
declare var trainAnimalGen: TrainAnimalGen

trainAnimal = trainDog     // ok
trainAnimalGen = trainDog // error

@HerringtonDarkholme
Copy link
Contributor Author

After digging deeper, I'm sure this is caused by stricter generic comparison, not stricter variance check.

In the TrainAnimalGen, type parameter A is not related to Dog, just like the example below.

function train<A extends Animal>(a: A) { 
  a = new Dog // error, of course
}

The new stricter generic check compares parameter A with Dog instead of erasing it to Animal, so though functions are still bivariantly checked, A and Dog can never be related so the error.

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants