Conversation

ahejlsberg

In order to ensure that any generic type Foo<T> is at least co-variant with respect to T no matter how Foo uses T, TypeScript relates parameters bi-variantly (given that parameters are input positions, they naturally relate only contra-variantly). However, when source and target parameters both have function types with a single call signature, we know we are relating two callback parameters. In that case it is sufficient to only relate the parameters of the signatures co-variantly because, similar to return values, callback parameters are output positions. With this PR we introduce that change. This means that a Promise<T> or Observable<T>, where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) with respect to T, which solves a commonly reported issue.

This change finds several issues in our real world code suites that all appear to be legitimate inconsistencies.

Fixes #11022.
Fixes #14770.

@DanielRosenwasserDanielRosenwasser changed the title Covariant callbacks Covariant checking for callback parameters Apr 10, 2017
@ahejlsberg

@mhegazy Want to take a look?

@acutmore

@ShawnTalbert If not clear. This PR does not introduce any new syntax. Instead changes type assignability of generics where the type is only user in a function parameter position e.g Promise / Observable

interface Box<T> {
   foo(cb: (t: T => void)): void;
}

Before:

declare const bA: Box<Animal>;
const bC: Box<Cat> = bA; // no error 😿 

After:

declare const bA: Box<Animal>;
const bC: Box<Cat> = bA; // error 😺 

@ShawnTalbert

Yes, wrong issue, sorry

@bmcbarron

It appears that the solution was amended to ignore union with null/undefined. Should that be dependent on --strictNullChecks? At the moment, this does not seem to address #13513.

@ahejlsberg

@bmcbarron #13513 is fixed by this PR in --strictNullChecks mode. Without --strictNullChecks, undefined is assignable to any type and is effectively ignored in union types, but that has always been the case and isn't going to change.

@schotime

This doesn't fix this problem unfortunately and this is usually the case if the callbacks are stored in different files and imported.

class Animal {}
class Cat extends Animal {
    public meow() {}
}

let promise: Promise<Animal> = null;
promise.then((cat: Cat) => { //Should error here but does not
  cat.meow();
});

Sign up for free to subscribe to this conversation on . Already have an account? Sign in.
Breaking ChangeWould introduce errors in existing code
None yet

Successfully merging this pull request may close these issues.