GeezerVison is showing github.com — Incorrect type inference for generic types with 'prototype' property · Issue #55199 · microsoft/TypeScript
Incorrect type inference for generic types with 'prototype' property #55199DescriptionBug Report
🔎 Search Terms
- generic type prototype inference
🕗 Version & Regression Information
- This changed between versions 4.7.4 and 5.2.0
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about
prototype
⏯ Playground Link
Playground link with relevant code
💻 Code
interface Type<T> {
new(...args: any[]): T;
}
interface Proto<T> {
prototype: T;
}
function injectWithType<T>(a: Type<T>): T { return null as T };
{
const okUnknown = injectWithType(Set); // Set<unknown> ⚠️
const okNumber = injectWithType<Set<number>>(Set); // Set<number> ✅
const okNumber2 = injectWithType(Set<number>); // Set<number> ✅
}
function injectWithProto<T>(a: Proto<T>): T { return null as T };
{
const okAny = injectWithProto(Set); // Set<any> ⚠️
const okNumber = injectWithProto<Set<number>>(Set); // Set<number> ✅
const unexpectedAny = injectWithProto(Set<number>); // Set<any> ‼️
}
The compiled version has a bug too - double parentheses (reproduced both with the injectWithType and the injectWithProto.):
🙁 Actual behavior
When using an interface or atype with the prototype property to declare class, generic type is getting wrong, especially in the ExpressionWithTypeArguments (‼️) case.
🙂 Expected behavior
(⚠️) - These lines should have the same type (Set<any> or Set<unknown>)
(‼️) - This line should correctly infer generic type (Set<number>)
const arrOfNum = injectWithProto(Set<number>) // should be Set<number>, not Set<any> Reacted by Viacheslav Osadchiy Activityjcalz commented on Jul 30, 2023 There's a lot going on here, could you reduce to a single bug report?
The stuff about the emitted JavaScript seems like a separate topic entirely... and I don't see how that's supposed to be a bug either; injectWithProto((Set)) is weird but does it do something incorrectly somewhere? And since instantiation expressions (#47607) were introduced in TS4.7, I wouldn't have any expectations at all for what JS is emitted if you try to use them on older versions. Maybe you could remove that part and be explicit about what specific single bug you're reporting here?
Are you saying that the type of Set.prototype should be Set<unknown> instead of Set<any>? Are you saying that the type of (Set<number>).prototype should be Set<number> instead of Set<any>? Which one of those is the bug you're reporting? ApplY3D commented on Jul 30, 2023 Joe Calzaretta (@jcalz) I'm reporting about type issue mainly. Both result types below should be equal Set<number>, not only the first one:
injectWithProto<Set<number>>(Set)
injectWithProto(Set<number>);
The compiled version is not breaking, but it's weird and might be related to type inference issue I'm repporting. jcalz commented on Jul 30, 2023 It's unrelated, so having it here is distracting at best. It also seems like you don't care primarily about Set<unknown> vs Set<any>, so maybe that's also just distracting.
If I were filing this issue it would be as a suggestion (not a bug report) that (Set<number>).prototype be of type Set<number>, or more generally that using an instantiation expression on a class constructor would make the prototype property have the same type as its instance type.
The typing of prototype is weird in TS anyway; we pretend that a class prototype has the same type as an instance, even though this is very likely untrue for any class with instance properties. And if you have a generic class Foo<T> then it has a single prototype Foo.prototype which would therefore need to be the the prototype for all possible instantiations of T, like an infinite intersection Foo<A> & Foo<B> & Foo<C> & ⋯, or forall T. Foo<T>... and Foo<any> is really the closest thing we have to that. So to some extent we can't really rely on prototype to have a fully accurate type.
But since class Bar extends Foo<X> {} has Bar.prototype of type Bar, it's not unreasonable to expect that (Foo<X>).prototype would be of type X also. I'd still be phrasing this as a feature request though. ApplY3D commented on Jul 30, 2023 Joe Calzaretta (@jcalz) The main issue is that I expect the same behavior for both function calls injectWithProto, but they are completely different. I care only about this.
...Both result types below should be equal Set<number>, not only the first one:
injectWithProto<Set<number>>(Set)
injectWithProto(Set<number>);
Reacted by Joe Calzaretta RyanCavanaugh commented on Jul 31, 2023 I'm confused too. Different lines of code may well have different behavior; that's how stuff gets done. Which line is wrong, and why? Can you post a straightforward example with no other things going on? ApplY3D commented on Aug 1, 2023 RyanCavanaugh commented on Aug 2, 2023 This isn't an actionable description of a bug. Reacted by Viacheslav Osadchiy and Bob
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|