Aim: Discover higher-order types using proxies that wrap and monitor values at run-time.
Example
function foo(f, x, y) {
return f(x.g,x.a) * (y ? -1 : 1);
}
let f = (g,x) => g(x*10);
foo(f, {g: x => x*2, a: 10}, true);
// The example gist will report that foo has the type:
// (((number) => number, number) => number, { g : (number) => number, a : number }, boolean) => number
It is not possible to get the type of f at the call to foo, you have to monitor f as it gets called. Proxies can be used to implement 'type catching`. The same method can be used for objects. While it is possible to traverse objects when you encounter them there are some issues: 1) they might be very large, 2) extra traversals can cause side-effects, 3) it wont catch any updates to the object inside the function. Proxies can be used to lazily monitor object access.
Background I did some research that involved using Proxies to monitor JavaScript libraries with TypeScript definition files. The are some clear similarities with what we did and TypeWiz. The main difference is that we didn't care about collecting and outputting types, only checking that the library matched the definition. The same technique could be used to collect types in general.
Implementation Here is a gist that shows the basic idea (I hacked this together, consider yourself warned!). The part that concerns proxies:
function collectType<T>(v: T, register: (t: Type) => void): T {
if(typeof v === "function") {
return makeFunctionCollector(v,register);
}
if(typeof v === "object") {
return makeObjectCollector(v,register);
}
register(typeof v as any);
return v;
}
function makeFunctionCollector<T extends Function>(v: T, register: (t: Type) => void): T {
const funType = makeFunctionType();
register(funType);
return new Proxy(v,{
apply(target: T, thisArg: any, args: any[]) {
const callInfo = makeCallType();
funType.calls.push(callInfo);
const wrappedArgs = args.map(v => collectType(v,t => callInfo.args.push(t)));
const result = Reflect.apply(target,thisArg,wrappedArgs);
return collectType(result, t => callInfo.ret = t);
}
});
}
function makeObjectCollector<T extends Object>(v: T, register: (t: Type) => void): T {
const objType = makeObjectType();
register(objType);
return new Proxy(v,{
get(target: T, prop: string, receiver: any) {
return collectType(Reflect.get(target, prop, receiver), t => {
if(!objType.props[prop]) {
objType.props[prop] = []
}
objType.props[prop].push(t);
});
},
set(target: T, prop: string, receiver: any, val: any) {
return Reflect.set(target, prop, receiver, collectType(val, t => {
if(!objType.props[prop]) {
objType.props[prop] = []
}
objType.props[prop].push(t);
}));
}
});
}
When we want to collect a type, if we encounter an object or function, we replace them with a proxy that recursively collects types when called or accessed. The register callback is used to store the type information as we collect it (the full gist has more details).
Issues: Proxies are known to change the semantics of programs, they have a different identity to the object they wrap: this can affect equality tests. This shouldn't be a significant problem in this context because TypeWiz is not being run in production, and the types should still be valid even if different control flow paths are taken.
Conclusion I hope this is at least interesting, even if it doesn't get taken on board. The project seems really interesting so I'll be keeping an eye out regardless.
Aim: Discover higher-order types using proxies that wrap and monitor values at run-time.
Example
It is not possible to get the type of
f
at the call tofoo
, you have to monitorf
as it gets called. Proxies can be used to implement 'type catching`. The same method can be used for objects. While it is possible to traverse objects when you encounter them there are some issues: 1) they might be very large, 2) extra traversals can cause side-effects, 3) it wont catch any updates to the object inside the function. Proxies can be used to lazily monitor object access.Background I did some research that involved using Proxies to monitor JavaScript libraries with TypeScript definition files. The are some clear similarities with what we did and TypeWiz. The main difference is that we didn't care about collecting and outputting types, only checking that the library matched the definition. The same technique could be used to collect types in general.
Implementation Here is a gist that shows the basic idea (I hacked this together, consider yourself warned!). The part that concerns proxies:
When we want to collect a type, if we encounter an object or function, we replace them with a proxy that recursively collects types when called or accessed. The
register
callback is used to store the type information as we collect it (the full gist has more details).Issues: Proxies are known to change the semantics of programs, they have a different identity to the object they wrap: this can affect equality tests. This shouldn't be a significant problem in this context because TypeWiz is not being run in production, and the types should still be valid even if different control flow paths are taken.
Conclusion I hope this is at least interesting, even if it doesn't get taken on board. The project seems really interesting so I'll be keeping an eye out regardless.
All criticism and discussion welcome.