Proposal: 'typeon' operator
The typeon operator references the type associated with an interface or class property, but in contrast to typeof, refers to it through the containing Type itself. I.e. directly queries the type set on the property rather than the type of an instance of it:
interfaceMyInterface{prop: number;}letval: typeonMyInterface.prop;// type of val is 'number'This would work with any property, including ones contained in anonymous and nested interfaces:
interfaceMyInterface{obj: {x: number;y: number;}}letmyX: typeonMyInterface.obj.x;// type of myX is now 'number'Generic interfaces:
interfaceMyInterface<T>{obj: {x: T;y: T;func: (arg: T)=>T;}}letf: typeonMyInterface<number>.obj.func;// type of f is now (arg: number) => numberIt would be possible to reference the property types at any scope, including within the referenced declaration itself:
interfaceMyInterface{val: string;anotherVal: typeonMyInterface.val;obj: {x: number,y: number;func(arg:number): typeonMyInterface.obj.x;}}The this type reference
A special this type reference could be supported for this very purpose. this would be scoped to the closest containing interface, and that would also apply to anonymous ones:
interfaceMyInterface{val: string;anotherVal: typeonthis.val;obj: {x: number,y: number;// Note: 'this' is scoped to the anonymous interface here. // 'typeon this.val' would give an error.func(arg: string): typeonthis.x;}}It would also be available in interfaces declared through type declarations and for purely anonymous ones:
typeMyType={name: string;age: number;query: (username: typeonthis.name)=>typeonthis.age;}lettest: {x: number,y: typeonthis.x}and to classes, which would also include references to the types of properties declared in base classes when using this (a possible extension could also include support for super):
classUserBase{id: string;}classUserextendsUserBase{age: number;chatWithUser(id: typeonthis.id): {id: typeonUser.id,age: typeonUser.age}{// The first argument for 'chatWith' is expected to be a string here, so this works.chatWith(id);..}}In a class declaration, typon this can only be used from instance positions. To reference static members, the name of the class would be used with typeof (note: no equivalent typeof this or a standalone this type is included in this proposal).
classExample{val: number;staticval: string;instanceFunc1(): typeonthis.val;// OK, resolves to 'number'instanceFunc2(): typeofExample.val;// OK, resolves to 'string'staticstaticFunc1(): typeonExample.val;// OK, consistent with external references.staticstaticFunc2(): typeofExample.val;// OK, resolves to 'string'}This was chosen to improve clarity and for consistency with generic classes, where the instantiated values of generic parameters are not available at static positions:
classExample<T>{// The example is given where T = numberval: T;staticval: string;instanceFunc1(): typeonthis.val;// OK, resolves to 'number'.instanceFunc2(): typeofExample.val;// OK, resolves to 'string'staticstaticFunc1(): typeonExample<number>.val;// OK, consistent with external references.staticstaticFunc2(): typeofExample.val;// OK, resolves to 'string'}letc=newExample<number>();Example use cases
Using these ad-hoc references would make it easier to express of the semantic intention for the usage of the type, and automatically "synchronize" with future changes to the type of the referenced property. This both reduces effort and prevents human errors:
classUserBase{// The type for 'id' has now changed, this usually means that all similar // semantic usages of it would need to be manually checked and updated, // this includes ones in derived classes.id: string|number;}classUserextendsUserBase{age: number;// The type of the 'id' argument and anonymous interface property has // automatically synchronized with the one set in the base class!// This ensures that they will always be consistent and will propagate type errors // into the body of the function.chatWithUser(id: typeonthis.id): {id: typeonUser.id,age: typeonUser.age}{// The first argument for 'chatWith' is expected to be a string here, // but now the type of 'id' has changed to string | number so will error.// This compile-time error is possible because the type was referenced with 'typeon'chatWith(id);// Error! type 'string' is not assignable from 'string | number'..}}Another effective use is to encapsulate and reference anonymous types within a class or interface declaration without needing to declare additional type aliases or interfaces. This yield a different style of coding:
classProcess{id: number;state: {memoryUsage: {real: number;virtual: number;private: number;};processorUsage: Array<{index: number,percentage: number}>;}constructor(){
...
}..}classOSQuery{staticqueryMemoryUsage(id: typeonProcess.id): typeonProcess.state.memoryUsage{return{real: OS.getRealUsage(id),virtual: OS.getVirtualUsage(id),private: OS.getPrivateUsage(id)};}staticqueryProcessorsUsage(id: typeonProcess.id): typeonProcess.state.processorUsage{
result: typeonProcess.state.processorUsage;for(leti=0;i<OS.processorCount;i++){result.push({index: i,percentage: OS.getProcessorUsage(id,i)})}returnresult;}staticqueryProcessState(id: typeonProcess.id): typeonProcess.state{return{memoryUsage: this.queryMemoryUsage(id),processorUsage: this.queryProcessorsUsage(id)}}..}In the conventional style of coding, 4 auxiliary interfaces or type aliases would need to be declared to achieve this:
interfaceProcessorUsageEntry{index: number;percentage: number;}interfaceMemoryUsage{real: number;virtual: number;private: number;}typeProcessorID=number;// aliased in case that associated type changesinterfaceProcessState{id: ProcessorID;memoryUsage: MemoryUsage;processorUsage: Array<ProcessorUsageEntry>}Reusing the syntax to reference a class instance type
It is also possible to naturally extend the syntax to reference a type that only includes members of a class instance (typeof MyClass only gives the type of the constructor):
classMyClass{instanceProp: number;staticstaticProp: string;constructor(arg: boolean){..}}leta: typeofMyClass;// 'a' now has type { staticProp: string, new (arg: boolean) }letb: typeonMyClass;// 'b' now has type { instanceProp: number }Equivalent reduction to named type aliases
This can be internally implemented in the compiler like this:
Source:
interfaceMyInterface{val1: string;val2: typeonthis.val1;val3: typeonthis.val2;obj: {x: number,y: number;func(arg: string): typeonthis.x;}}Reduction:
typeTypeOn_val1=string;typeTypeOn_val2=TypeOn_val1;typeTypeOn_val3=TypeOn_val2;typeTypeOn_obj_x=number;typeTypeOn_obj_func_returnType=TypeOn_obj_x;typeTypeOn_obj={x: TypeOn_obj_x;y: number;func(arg: string): TypeOn_obj_func_returnType;}interfaceMyInterface{val1: TypeOn_val1;val2: TypeOn_val2;val3: TypeOn_val3;obj: TypeOn_obj;}Example with generic interfaces:
Source:
interfaceMyGenericInterface<T>{val1: T;val2: typeonval1;obj: {a: Array<T>,func(arg: T): typeonthis.a;}}Reduction (this uses the new generic type alias declarations introduced in 1.6):
typeTypeOn_val1<T>=T;typeTypeOn_val2<T>=TypeOn_val1<T>;typeTypeOn_obj_a<T>=Array<T>;typeTypeOn_obj_func_returnType<T>=TypeOn_obj_a<T>;typeTypeOn_obj<T>={a: TypeOn_obj_a<T>;func(arg: T): TypeOn_obj_func_returnType<T>};interfaceMyGenericInterface<T>{val1: TypeOn_val1<T>;val2: TypeOn_val2<T>;obj: TypeOn_obj<T>;}Possible issues and their solutions
Detect and error on circular references:
interfaceSelfReferencingPropertyType{x: typeonthis.x;// Error: self reference}interfaceEndlessLoop{x: typeonthis.y;// Error: indirect self referencey: typeonthis.x;// Error: indirect self reference}This would happen automatically through the reduction described above. The error currently reported through type is "Error: type 'TypeOn_EndlessLoop_y' circularly references itself".
Current workarounds
It is currently possible to partially emulate this using typeof with a "dummy" instance of the type:
interfaceMyInterface{obj: {x: number,y: number}}letdummy: MyInterface;letval: typeofdummy.obj;And even:
interfaceMyInterface{obj: {x: number,y: typeofdummy.obj.x;}}interfaceAnotherInterface{func(arg:number): typeofdummy.obj;}vardummy: MyInterface;Though these workarounds requires a globally scoped variable, and not always possible or desirable for use in declaration files. They also cannot support keywords like this (or super) thus cannot be used within anonymous types.
References to generic parameters cannot be emulated:
interfaceMyInterface<T>{x: T;y: typeonthis.x;// Not possible to emulate this}[Originally described at #4555]
Proposal: 'typeon' operator
The
typeonoperator references the type associated with an interface or class property, but in contrast totypeof, refers to it through the containing Type itself. I.e. directly queries the type set on the property rather than the type of an instance of it:This would work with any property, including ones contained in anonymous and nested interfaces:
Generic interfaces:
It would be possible to reference the property types at any scope, including within the referenced declaration itself:
The
thistype referenceA special
thistype reference could be supported for this very purpose.thiswould be scoped to the closest containing interface, and that would also apply to anonymous ones:It would also be available in interfaces declared through
typedeclarations and for purely anonymous ones:and to classes, which would also include references to the types of properties declared in base classes when using
this(a possible extension could also include support forsuper):In a class declaration,
typon thiscan only be used from instance positions. To reference static members, the name of the class would be used withtypeof(note: no equivalenttypeof thisor a standalonethistype is included in this proposal).This was chosen to improve clarity and for consistency with generic classes, where the instantiated values of generic parameters are not available at static positions:
Example use cases
Using these ad-hoc references would make it easier to express of the semantic intention for the usage of the type, and automatically "synchronize" with future changes to the type of the referenced property. This both reduces effort and prevents human errors:
Another effective use is to encapsulate and reference anonymous types within a class or interface declaration without needing to declare additional type aliases or interfaces. This yield a different style of coding:
In the conventional style of coding, 4 auxiliary interfaces or type aliases would need to be declared to achieve this:
Reusing the syntax to reference a class instance type
It is also possible to naturally extend the syntax to reference a type that only includes members of a class instance (
typeof MyClassonly gives the type of the constructor):Equivalent reduction to named type aliases
This can be internally implemented in the compiler like this:
Source:
Reduction:
Example with generic interfaces:
Source:
Reduction (this uses the new generic type alias declarations introduced in 1.6):
Possible issues and their solutions
Detect and error on circular references:
This would happen automatically through the reduction described above. The error currently reported through
typeis "Error: type 'TypeOn_EndlessLoop_y' circularly references itself".Current workarounds
It is currently possible to partially emulate this using
typeofwith a "dummy" instance of the type:And even:
Though these workarounds requires a globally scoped variable, and not always possible or desirable for use in declaration files. They also cannot support keywords like
this(orsuper) thus cannot be used within anonymous types.References to generic parameters cannot be emulated:
[Originally described at #4555]