-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Properties of instances of anonymous classes have type <any> after an instanceof guard. #17253
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
It seems working as intended to me (the behavior of If you still considered this is not the expected behavior, you may have to use something like this: function myInstanceOf<T>(obj: any, ctor: new (...args: any[]) => T): obj is T {
return obj instanceof ctor;
}
if(myInstanceOf(something, MyClassWithNumber)) {
something.value = 20;
something.value = "sdf";
// ^^^^^^^^^^^^^^^ [ts] Type '"sdf"' is not assignable to type 'number'
} NOTE: TypeScript is a structural type system, which means class A {
v: string;
}
class B {
v: string;
}
const a = new A();
const b: B = a; // passed, since they're in the same shape |
Thank you for your reply. I still have the impression that it would not be an error if TS narrowed the type without the myInstanceOf wrapper.
(emphasis added)
So the type of the property seems only to be forgotten (widened to any) when the class ctor is produced by a generic function and there is a "type roundtrip" involving a cast to any followed by an instanceof type guard. The generic function myInstanceOf compiles to a function that trivially forwards the arguments to the JS instanceof operator, so i'd expect that TS could safely apply the narrowing logic on its own without the need to be taught it by means of myInstanceOf. In any case, thank you for the myInstanceOf workaround, it works nicely. |
I ran into this and was incredibly surprised. This is a dangerous way for class Generic<T> {
constructor(public value: T) {}
}
class Specific<T extends number> {
constructor(public value: T) {}
}
function example(v: unknown) {
if (v instanceof Generic) {
let _: undefined = v.value; // Allowed!
// Expected v.value to be of type `unknown`.
}
if (v instanceof Specific) {
let _: undefined = v.value; // Allowed!
// Expected v.value to be of type `number`.
}
}; Basically TypeScript is far too generous here. I would expect it to assume the minimum bound ( This would be a breaking change so the default probably can't be change but I would definitely like to see a compiler option to do the safer thing in this case. (I looked for this option before finding this issue, I'm surprised it doesn't exist). |
I would also appreciate a compiler option to improve this behavior. Right now I'm working around the issue by using a type guard function instead of the const isGeneric = (x: unknown): x is Generic<unknown> => x instanceof Generic;
const isSpecific = (x: unknown): x is Specific<number> => x instanceof Specific;
function example2(v: unknown) {
if (isGeneric(v)) {
let _: undefined = v.value; // Errors now
}
if (isSpecific(v)) {
let _: undefined = v.value; // Errors now
}
}; |
Is there any interest in a PR implementing a compiler option as discussed? Is it possible for a dev to chime in? class A<param = something>
const DesiredA = A as {new: () => A};
const a: any = new A();
// we want this inference:
if (a instanceof A) { //infers to A<any>
}
//to equal this one:
if (a instancoef DesiredA) { //infers to A<something>
} |
I think this is the code that'd need to change, from checker.ts: function getTypeOfPrototypeProperty(prototype: Symbol): Type {
// TypeScript 1.0 spec (April 2014): 8.4
// Every class automatically contains a static property member named 'prototype',
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
// It is an error to explicitly declare a static property member with the name 'prototype'.
const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType;
return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType;
} Could be: function getTypeOfPrototypeProperty(prototype: Symbol): Type {
// TypeScript 1.0 spec (April 2014): 8.4
// Every class automatically contains a static property member named 'prototype',
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
// It is an error to explicitly declare a static property member with the name 'prototype'.
const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType;
return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, type => type.default ?? anyType)) : classType;
} |
Found temporary solution, which works fine for me - creating duplicated non-generic class with inheritance looks like this interface Foo {
foo: string;
}
class Bar<T extends Foo> {
constructor(readonly bar: T) {}
}
// creating a concrete class
class Temp extends Bar<Foo> {}
// taking type of that class to fix inference in generic class
// inside module - export BarProxy const instead of Bar class
const BarProxy = Bar as typeof Temp;
let a: unknown = new Bar({foo: 'foo'});
const barProxy: unknown = new BarProxy({foo: 'foo'});
console.log(a instanceof Bar);
console.log(a instanceof BarProxy);
console.log(barProxy instanceof Bar);
console.log(barProxy instanceof BarProxy);
if (a instanceof Bar) {
a.bar; // <-- a.bar should be 'Foo' instead of 'any'
}
if (a instanceof BarProxy) {
a.bar; // <-- works fine
} |
TypeScript Version: 2.4.1
Code
Expected behavior:
The last assignment should be marked as a type error by TS.
Actual behavior:
The last assignment is not marked as error. This is somewhat surprising, as the line
is righteously reported as a type error by TS.
Not a huge issue, but prevented me from using a class factory.
The text was updated successfully, but these errors were encountered: