-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Make instanceof
narrow better using an anonymous class returned from a function
#57317
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
Comments
Some extra WAT behavior here: function MakeClass<T>(someStuff: T) {
return class {
someStuff = someStuff;
};
}
const MadeClassNumber = MakeClass(123);
const MadeClassString = MakeClass("foo");
declare const someInstance: unknown;
if (
someInstance instanceof
(MadeClassNumber as typeof MadeClassNumber & { prototype: any })
) {
const someStuff = someInstance.someStuff;
// ^? const someStuff: number
} The problem is that the So an extra thing here is that If I reason about this correctly, only |
Seems like #17473, but #49863 doesn't fix this issue, only changes the inferred type from |
I think the symptom of the problem is similar to what's going on in #17473. In fact it seems to explain why currently it's always However, in this case I believe With Andarist's insightful discovery it's probably more accurate to title this issue "The prototype of an anonymous class returned from a function is not reified" which is different than "Infer constrained generic parameters after instanceof check" as in #17473 because there shouldn't be a generic at all in the prototype. |
Somewhat related to #56146, where a This also seems a bit like |
🔎 Search Terms
Function return, anonymous class, instanceof, mixins, narrowing,
🕗 Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about instanceof.
I used
every-ts
(kudos to Jake Bailey for making it so easy) so I'm pretty confident this is true unless some random commit fixed it temporarily.The closest entry is this: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-x-instanceof-foo-narrow-x-to-foo
However as I understand it as long as
if (x instanceof y)
compiles, it should be narrowingx
to basicallytypeof x & InstanceType<typeof y>
. There might be some other edge cases I'm not aware of but as you'll see in the playground it seems like this isn't happening as precisely as desired with an anonymous class returned from a function.⏯ Playground Link
https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAWQIYGsCmBhANqgZwIB4AVAPgAoC4BbTAZShGGAC5FSBKRAbwFgAUIkQAnTM1FII+InyEiRNekxbBEAXkTLGzVgG4FiAL5DTgoRAQEoKVABMcsggDkQtAEaZRmu1jyEBJQAjABMAMxchoJWYDZ2jgFETKIwYADmvmj+zpQARMBwcHlRQpbWtmDuXqIAknFQqJCYvmCYAO4JToFunt6UpYJVfXUNTRCYAHQ6qgaIAPTziPXA3gSIhIgABsM1WxvrMLYEABZwILj2iF6TZTEV2nSY9TbjmBzg6GBw7UhaaFATpNRE17HQBohyIgAAyTACsiAA-Ig2p00IlnL0ahCOKiukkCCk0ukBtEYOpqE8Xo1mog0q9mnB1OjukQsd4eAJhI96NS3tMnrNgPoFksVmsDtsmgBPfaglFwWw7areLa3QTGIA
💻 Code
🙁 Actual behavior
someInstance.someStuff
is inferred asany
.🙂 Expected behavior
someInstance.someStuff
is inferred asnumber
.Additional information about the issue
The code snippet is a simplified version of what I actually ran into. I was creating a bunch of classes for validation purposes. e.g.
const Month = new NumberInRange(1, 12);
,const Hour = new NumberInRange(0, 23);
,const Minute = NumberInRange(0, 59);
, or so on. You can imagine much more complicated validation of course.Later on I wanted to be able to write
x instanceof Hour
so that I could firstly know that it'd already been validated and secondly know that it was specifically anHour
and not some other type. This might sound more convoluted but it meant validation could occur in one place and I could avoid the problem of allnumber
s seeming the same at runtime. For various reasons, in this case a discriminated union didn't end up being as ergonomic but these classes were put into a union andinstanceof
was basically trying to fulfill the same niche.Now, you can always fix this from going from
const MadeClass1 = MakeClass(123);
toclass MadeClass1 extends MakeClass(123) {}
. So I've unblocked myself using that and I wouldn't classify the priority of fixing this very high. However I only happened to be informed enough that this seemed like a reasonable change to try to make. The errors I was getting had to do with the narrowing failing so I don't think the errors you get are intuitive and I think other developers might have given up this style.In general it might bring extra benefits in terms of consistency in the TypeScript codebase to treat it this way. The issue seems to be that in normal contexts all instances of
MadeClassNumber
are "typed"* asMakeClass<number>.(Anonymous class)
but the instanceof narrowing seems to lose track of that fact and will apply the base constraint of the parent class and then only narrow it based upon that. In this case that means it narrows toMakeClass<any>.(Anonymous class)
which doesn't matchInstanceType<typeof MadeClassNumber>
like one would expect it to.*I'm using the display of the type I happen to see but I understand there's no stable format nor way to refer to them by a valid identifier.
The text was updated successfully, but these errors were encountered: