Closed
Description
My proposal:
- For
instanceof
expressions, ifSymbol.hasInstance
exists on the RHS, narrow the expression according to the method's return value. - For classes, implicitly add a type-level
constructor
prototype property which istypeof classConstructor
, if such a property was not defined in the class body.
interface Foo { constructor: typeof Foo }
declare const Foo: {
[Symbol.hasInstance](x: any): x is Foo
}
declare const Bar: {
[Symbol.hasInstance](x: any): x is number
}
declare const foo: any
declare const bar: any
if (foo instanceof Foo) {
// foo should be inferred as a Foo
}
if (bar instanceof Bar) {
// bar should be inferred as a number
}
The rationale of this is that you can then (via compiler flag) by default define Symbol.hasInstance
on constructors to type check against their return type (like for new () => T
, defining [Symbol.hasInstance](x: any): x is T
). This would mostly mitigate a certain unsoundness issue with classes, making the workaround a bit more difficult to do:
class Foo {
bar: number = 1;
}
const foo1: Foo = new Foo() // Legal
const foo2: Foo = {bar: 2} // No longer legal
// Making these illegal are out of scope of this proposal
const foo3: Foo = {
constructor: Foo;
bar: 2,
}
const foo4: Foo = new class {
bar: number = 1
}
Notes:
- This has no effect on emit.
- This has no effect on JS compatibility.
- This will create new type errors, and thus will be moderately breaking.
- This brings type semantics more in line with JS runtime semantics.
- This shouldn't have a substantial effect on compiler performance, since the default
Symbol.hasInstance
is equivalent to legacyinstanceof
. - This would largely fix the premise of
instanceof
should not exclude possible structural type #17344 and would reduce the need for nominal typing (Support some non-structural (nominal) type matching #202) in a common use case.