Skip to content

Unrelated interface causes assignability to change #56099

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

Open
dragomirtitian opened this issue Oct 13, 2023 · 2 comments
Open

Unrelated interface causes assignability to change #56099

dragomirtitian opened this issue Oct 13, 2023 · 2 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@dragomirtitian
Copy link
Contributor

dragomirtitian commented Oct 13, 2023

πŸ”Ž Search Terms

5.3 regression

πŸ•— Version & Regression Information

  • This changed between versions 5.3.0-dev.20230823 and 5.3.0-dev.20230824

⏯ Playground Link

Playground Link

πŸ’» Code

export { }
interface Map<V> extends Collection<V> {
    flatMap<VM>(): Map<VM>;
}

interface Collection<V> {
    value: V; // sprinkle some covariance
    map: Map<V>;
    concat(): Collection<unknown>;
    flatMap(): Collection<V>;
    flatMap<VM>(): Collection<VM>;
}

// Comment out and it works like in 5.2
interface Keyed extends Collection<number> {
    concat(): Keyed;
}

type R = Map<never> extends Collection<infer V> ? V : "NO";

const r = null! as R;
const t: "NO" = r;

πŸ™ Actual behavior

R is unknown. Removing Keyed makes R "NO", even though Keyed is unused otherwise.

πŸ™‚ Expected behavior

Not really sure. I'd settle for the stable 5.2 behavior, which is for R to be "NO" although that also seems wrong (Why doesn't Map<never> extends Collection? If we remove flatMap(): Collection<V> from Collection then Map<never> extends Collection. This seems like the correct behavior.)

Additional information about the issue

No response

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Oct 13, 2023

Bisects to #53396, cc @ahejlsberg . Trying to find a more direct repro that involves only a single assignability check, but I suspect the self-reference in Keyed is a necessary component.

Edit to add: Adding variance annotations doesn't help.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Oct 16, 2023
@dragomirtitian dragomirtitian changed the title Unrelated interface causes assignably to change Unrelated interface causes assignability to change Oct 17, 2023
@ahejlsberg
Copy link
Member

Several interesting things going on here. The following

declare let c: Collection<never>;
declare let m: Map<never>;
c = m;  // Error

reports the error

Type 'Map<never>' is not assignable to type 'Collection<never>'.
  The types of 'flatMap().value' are incompatible between these types.
    Type 'any' is not assignable to type 'never'.(2322)

That's surprising, particularly since the interface Map<V> extends Collection<V> declaration doesn't have any errors and for any other V the two are assignable. The root of the problem here is erasure of type parameters. When relating the flatMap members, we erase the VM type parameter to any. Then, because Map<V> doesn't declare a non-generic overload of flatMap, we end up relating flatMap(): Collection<any> to flatMap(): Collection<never> and we then run into any not being assignable to never.

Now, there's something very fishy about the flatMap overloads in the first place--how, without actually having a parameter of type VM, can the generic overload know what to return? And, clearly, V and VM can be distinct and different types. Then, in the derived Map<V>, the non-generic overload has disappeared, making matters even more inconsistent. So, the declarations don't make sense, but because our overload checking is rather weak, we report no errors.

Anyway, the R type in the example really ought to resolve to never, but it doesn't due to the above.

But why does R sometimes resolve to unknown? The issue there comes up when we infer from Map<never> to Collection<V> in the conditional type. The generic flatMap<VM>(): Map<VM> in Map<never> gets erased to flatMap(): Map<unknown> from which we then infer to the non-generic flatMap(): Collection<V>. But, due to the logic in #53396, this inference only happens when Map<unknown> has a lower type ID than Map<never>. And that in turn depends on whether the Keyed interface declaration has been checked first (because it materializes Map<unknown> during the checking).

I'm going to think about possible fixes. We should probably discuss in our next design meeting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

3 participants