-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Proposal: Type-side use of instanceof
keyword as an instance-query to allow instanceof checking
#58181
Comments
instanceof
keyword and functionalityinstanceof
keyword and functionality
Agreed. I don't think restricting For example, using this setup: class A { ... }
class B extends A { ... }
class C { ... }
// Basic examples of `instanceof ...` being used where you would use other types
const a1: instanceof A = new A()
const a2: typeof a1 = a1
type InstanceOfA = instanceof A
interface AlsoInstanceOfA extends InstanceOfA {
}
const a3: AlsoInstanceOfA = new A() These accurately represent the runtime, so they probably should be allowed: const b1: instanceof A = new B()
const b2: (instanceof A) & B = new B()
const b3: (typeof a1) & B = new B()
const b4: (instanceof A) & (instanceof B) = new B()
type AB = (instanceof A) & (instanceof B) // could be simplified under-the-hood to `instanceof B` because B extends A And these are not possible, so they should be type BC = (instanceof B) & (instanceof C)
type IndirectAC = AlsoInstanceOfA & (instanceof C) Also, presumably mapped types reduce an type MappedA = { [K in keyof InstanceOfA]: InstanceOfA[K] }
const a1: MappedA = new A()
const a2: InstanceOfA = a1 // Error: MappedA matches the structure of InstanceOfA but may not be an instance of A Side Note: Boolean@craigphicks I don't think your examples 1.2 and 2.3 do what you intend. My understanding is that would match |
@saltman424 - Actually I was just about to remove those Boolean examples. That can be achieved by changing the type of the Boolean types converter (the expando function without new). |
I assumed it would transfer it. Basically, my interpretation is that let a: instanceof A
a = ... as any as typeof a // this should always work, no matter the type of a
I think type AlwaysTrue<T extends object> = instanceof T extends T We can't just use a nominal check in that case because consider this variation: type AlwaysTrue<T extends object> = instanceof T extends Partial<T> We need to be able to check |
const a: instanceof A
type X = typeof a Then |
@craigphicks I think I understand your point: TypeScript currently only has structural types, so right now,
Another edge case if function f<T>(foo: T): void {
let temp: typeof foo = foo
// Today this works. Would this stop working because `T` might be an `instanceof` type, like in the call below?
foo = temp
}
f<instanceof A>(new A()) |
instanceof
keyword and functionalityinstanceof
keyword as an instance-query to allow instanceof checking
I would really appreciate this feature. Using JS's class Foo {
baz: string
}
class Bar {
baz: string
}
const something : Foo | Bar = new Bar()
// TypeScript clearly has the ability built-in to differentiate Foo from Bar under the hood when using instanceof
if (something instanceof Bar) {
something // TypeScript correctly narrows to Bar
} else {
something // Typescript correctly narrows to Foo
}
const errorIfBar<T>(obj : T) : ??? { // here something along the lines of `T excluding instanceof Bar` would suffice perfectly
if (obj instanceof Bar) throw new Error("got a Bar!")
return obj
} Whatever TypeScript is doing under the hood from the In the above example, when you want to explicitly specify a return type of a function that uses |
π Search Terms
type side
instanceof
keyword used for instanceof Querying.related issues:
#202 #31311 #42534 #50714 #55032
β Viability Checklist
β Suggestion
A new type-side keyword
instanceof
is proposed to allow an interface to be declared as mapping to a specific class or specific constructor variable. When type-checking for assignability of said interface, the type checker would not rely only on structural duck-typing, but also rely on the tracked class/variable from which the object was constructed.This proposal addresses runtime errors resulting from runtime use of run-side
instanceof
that are not caught by the type checker. It may also be useful for other reasons/purposes, as shown in "Motivating Examples and discussed in "Use Cases".Of course absence of this feature is not a bug, as the overwhelming majority of TypeScript use cases require not using this proposed feature. However, there are a minority of use cases where this feature would be essential and/or useful, and the absence of this feature is a limitation.
Notation
The type-side syntax of
instanceof
requires as its operand either a variable representing a class constructor, e.g.:or an instantiation statement for a generic class, e.g.:
The resulting type is called an "instance query type".
As shown above, an instance query type is displayed as a paraenthesized intersection with two componenets:
x instanceof Y
operator, and is called theinstanceof
component. It is represented in the type display by a constructor symbol, e.g.,instanceof A
orinstance C
above. This is the a new ability incorporated by this proposal.A
orC<number>
above.Although the instanceof query type is written as an paranthesized interseciton, it is not behave exactly the same as an intersection type. An instanceof query type is a unit type with some special rules.
Implementation
In order to ensure the prerequisite "This wouldn't be a breaking change in existing TypeScript/JavaScript code", the behavior of code not involving the
instanceof
instance-query operator will not change. Only the behavior of code that does involve theinstanceof
instance-query, and its resulting flow, will be affected.Workaround for "new"
That means that the
new
operator will not be affected by this proposal:does not automicallly beome
a: instanceof A
. A cast (as above) or a convenience function:is required.
Workaround for "instanceof" (the runtime usage)
Likewise the
instanceof
operator will not be affected by this proposal:A convenience function can be written instead:
π Motivating Examples
Examples 1:
This code (see comment) passes type checking but throws a runtime error:
The
DataView
constructor requires an actualArrayBuffer
instance, butUint8Array
is not anArrayBuffer
instance.With this proposal the user could write
safeDataVew
which takes aninstanceof ArrayBuffer
argument type:Passed a plain
ArrayBuffer
type the TS compiler will error:Try to fix that error but it just moves
Try again - still an error but the compiler tells us that Uint8Array is not an instanceof ArrayBuffer
Examples 2:
Class
instanceod
heirarchy is separate from the generic type heirarchy. They are separate, but both must be satisfied for type checking to pass. This example shows an example where theinstanceof
comopnent passes but the generic type component fails.Examples 3-a:
Intersection of instanceof-query types with
{}
as structure type resolves to:never
if the instanceof components are not in the same linear class hierarchyAs a special consider when the intersection of an instanceof query type with a structure-only type, e.g.:
The reason that the result is not
never
is that , for the purpose of compiuting intersection,Y
is internally "promoted" to an intanceof query type represented asThat makes sense because in js runtime, every object returned by a constructor is an instance of
Object
.It follows that, for any instance query type
type X = instanceof SomeClass
, the intersectionX & {}
is alwaysX
.Examples 4:
TypeScript considers classes with completely empty publicly declarad type structure as equivalent to
any
- although they might correspond to objects with rich but hidden functionality that need to be discriminated. This proposal would allow you to discriminate them.π» Use Cases
As shown in the motivating examples.
The text was updated successfully, but these errors were encountered: