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

Extra generic parameters incorrectly make generic types compatible #41553

Closed
cutthroat opened this issue Nov 16, 2020 · 5 comments
Closed

Extra generic parameters incorrectly make generic types compatible #41553

cutthroat opened this issue Nov 16, 2020 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@cutthroat
Copy link

TypeScript Version: 4.0.5

Search Terms:
generic, incorrect, assignable, multiple, multi, two, parameter, compatible, unsound, unsafe

Expected behavior:
The type NotOk<number, number> below should not be assignable to NotOk<string, string>

Actual behavior:
It is.

Code

// Ok

interface I1<X> {
  m1: Ok<X>;
}

class Ok<X> {
  constructor(public x: X) {}

  fOk(i1: I1<X>) { i1 = i1; }
}

// !!! Fails to type-check as expected
const ok: Ok<string> = new Ok(0);


// Not ok

interface I2<X, Y> {
  m2: NotOk<X, Y>;
}

class NotOk<X, Y> {
  constructor(public x: X) {}

  fNotOk(i2: I2<X, Y>) { i2 = i2; }
}

// !!! Type-checks but should not
const notOk: NotOk<string, string> = new NotOk(0);

Playground Link: Provided

Related Issues:
Somewhat related: #30634, #31006

Extra:
The interface I2 is needed for when inlined the type-checker fails correctly:

class NowOk<X, Y> {
  constructor(public x: X) {}

  fNowOk(i2: { m2: NowOk<X, Y>; }) { i2 = i2; }
}

// Fails to type-check as expected
const nowOk: NowOk<string, string> = new NowOk(0);

However, this behavior could simply be due to a limitation of the unification mechanism used by the type checker.

@MartinJohns
Copy link
Contributor

MartinJohns commented Nov 16, 2020

Working as intended. You're not using Y anywhere.

What is structural typing?
Why is A<string> assignable to A<number> for interface A<T> { }?

I did not read correctly and was mistaken.

@cutthroat
Copy link
Author

I believe Y is not needed to cause a structural difference since the class NotOk already contains the field x: X, and instantiating it with say X = number and X = string should be a structural difference. Isn't this the reason why the ok assignment above fails to type-check?

In any case, adding a field of type Y to NotOk (which I believe qualifies as an use) still type-checks:

// Not ok

interface I2<X, Y> {
  m2: NotOk2<X, Y>;
}

class NotOk2<X, Y> {
  constructor(public x: X, public y: Y) {}

  fNotOk2(i2: I2<X, Y>) { i2 = i2; }
}

// !!! Type-checks but should not
const notOk2: NotOk2<string, string> = new NotOk2(0, 0);

Because NotOk and NotOk2 type-check, I can cast, e.g., a number to a string:

// !!! Type-checks but should not
const notOk: NotOk<string, string> = new NotOk(0);

// !!! undefined
console.log(notOk.x.length);

@MartinJohns
Copy link
Contributor

You're right, I misread the code. This really seems strange.

const val1 = new NotOk<number, string>(0);
const val2: NotOk<string, string> = val1;
val1.x // number
val2.x // string

Interestingly the error shows up when you remove the fNotOk2 method.

@cutthroat
Copy link
Author

Yes, fNotOk2 seems to be essential, as well as the interface I2. NotOk is the simplest example I could get.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Nov 16, 2020
@RyanCavanaugh
Copy link
Member

See #39947 - this is fixed in the nightly build

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants