Skip to content

Typescript fails to throw error when a generic is assigned to another generic of whom it's generic type is a superset #13970

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

Closed
scamden opened this issue Feb 9, 2017 · 9 comments
Labels
Canonical This issue contains a lengthy and complete description of a particular problem, solution, or design

Comments

@scamden
Copy link

scamden commented Feb 9, 2017

TypeScript Version: 2.1.4

Code

// A *self-contained* demonstration of the problem follows...
export interface MyInterface {
  myFunction(): Promise<number | string>;
}

export class MyClass implements MyInterface {
  async myFunction() { // inferred return type Promise<number | string | string[]>
    let num = 1;
    let text = 'text';
    if (num) {
      return num;
    }
    if (text) {
      return text;
    }
    return [text];
  }
}

Expected behavior:
TS throws an error that MyClass incorrectly implements MyInterface
type Promise<number | string | string[]> not assignable to Promise<number | string>

otherwise a client using the class as MyInterface can call the method and only handle a return promise of string or number and have errors when the promise is resolved with the array.

Actual behavior:
No error is thrown. (It's worth noting this doesn't repro without the generic. If for example the method were not async.) This also is not thrown if the return type is manually specified as Promise<number | string | string[]> just to be clear inference is not to blame.

@scamden scamden changed the title Typescript fails to throw error when a class implements an interface with generic return value and includes a superset of the types in the union of that generic Typescript fails to throw error when a class implements an interface with genericized return value and includes a superset of the types in the union of that generic Feb 9, 2017
@scamden
Copy link
Author

scamden commented Feb 9, 2017

here's an even simpler example that doesn't require classes or interfaces

const myPromise: Promise<string> = 
  new Promise<string | string[]>((resolve) => { resolve(['anything']) })

myPromise.then(value => {
  value.substr(0, 3); //throws an error because arrays lack this function
});

and using inference rather than explicit same thing:

 const myPromise: Promise<string> = new Promise((resolve) => {
    let text = '';
    if (text) {
      resolve(text);
    }
    resolve([text]);
  });

  myPromise.then(value => {
    value.substr(0, 3); //throws an error because arrays lack this function
  });

@scamden
Copy link
Author

scamden commented Feb 9, 2017

code example without the generic to prove that normally a superset is unassignable

function myFunction() { // inferred return 'number | string | string[]'
  let num = 1;
  let text = 'text';
  if (num) {
    return num;
  }
  if (text) {
    return text;
  }
  return [text];
}
const myValue: string | number = myFunction(); //ts throws unassignable error

@scamden scamden changed the title Typescript fails to throw error when a class implements an interface with genericized return value and includes a superset of the types in the union of that generic Typescript fails to throw error when a generic is assigned to another generic of whom it's generic type is a superset Feb 9, 2017
@scamden
Copy link
Author

scamden commented Feb 14, 2017

@gmoothart @evmar @bb @joshk @huerlisi sorry to ping you all. not sure who should take a look at this. seems like a pretty major bug.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Feb 21, 2017

This is actually a result of structural compatibility and function parameter bivariance. Essentially when Promise#then is compared between the two, the parameter type of arguments are compared in a bivariant way. Since the contravariant side succeeds, the two are compatible in either direction.

This is undesirable, but we're thinking about fixes. See this section in our FAQ for more details.

@DanielRosenwasser DanielRosenwasser added the Canonical This issue contains a lengthy and complete description of a particular problem, solution, or design label Feb 21, 2017
@scamden
Copy link
Author

scamden commented Feb 27, 2017

@DanielRosenwasser ok :) i don't fully understand the terms you used but i think i get the gist. glad to hear it's being worked on :)

@mhegazy
Copy link
Contributor

mhegazy commented May 18, 2017

The correct error should be reported with #15104.

@scamden
Copy link
Author

scamden commented May 18, 2017 via email

@DanielRosenwasser
Copy link
Member

@DanielRosenwasser ok :) i don't fully understand the terms you used but i think i get the gist. glad to hear it's being worked on :)

Sorry about that - that was a bit more jargon-y than I realized. 😄

image

@scamden
Copy link
Author

scamden commented May 18, 2017 via email

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Canonical This issue contains a lengthy and complete description of a particular problem, solution, or design
Projects
None yet
Development

No branches or pull requests

3 participants