Description
Hi everyone,
I'm currently starting my bachelor thesis, as part of the Honours Program at the University of Utrecht. The subject will be something with type inference in TypeScript. The final product - besides the written thesis - will be an implementation of the algorithm in TypeScript.
My plan is to fork the TypeScript compiler, and it'd be great if my changes would be merged into TypeScript. My subject is not yet fixed, so I'd like to discuss here which subject would be most suitable.
Here are the two ideas I've got:
1 - Control flow based typeguards.
Example:
let x: string | number = ...;
if (typeof x === number) {
// x: number
x = x.toExponentional();
// x: string
} else {
// x: string
x = x.substring(1);
// x: string
}
// x: string
console.log(x.substring(1));
A different kind of type guards could be implemented like this:
function expectString(x: any) x as string {
if (typeof x !== 'string') {
throw new Error('Expected string, got ' + typeof x);
}
}
let x: string | number = ...;
// x: string | number
expectString(x);
// x: string
This typeguard can be useful when checking the provided arguments in for instance a nodejs server or a public api.
2 - Improved inference using function bodies
Example:
function square(x) {
return x * x;
}
// Current: square(x: any): number
// Expected: square(x: number): number
function squareLength(x, y) {
return square(x) + square(y);
}
// Current: squareLength(x: any, y: any): number
// Expected: squareLength(x: number, y: number): number
Dificulties are polymorphic functions (like the identity function (x) => x
) and recursive functions. Algorithms that can handle these functions exist (Cartesian Product algorithm or (Extended) Monotone Framework). Such algorithm has been implemented for PHP (without type annotations, only the standard library had hard coded types). This would also be useful for editors which use the language services for JavaScript code, as a lot more types can be inferred. Support for objects will be limited (see below), given the time for my thesis. Limited support for objects is probably good for performance, too. Overall performance will depend on the precision, in some cases we can for example fallback to any to get reasonable performance.
Limited support for objects means that methods, that could be part of an object on the heap, won't be tracked, like the following example:
declare class A { foo(): string; }
declare class B { foo(): number; }
let x;
let y = x.foo();
The algorithm in the PHP implementation would infer y
to string | number
. Not supporting this means y is typed as any
, which would have my preference, as it would fit better in TypeScript. I haven't chosen a specific algorithm yet, I'd like to get some 'requirements' to choose one, for instance, what should happen with recursive functions (a function might call itself, or a function f
might call g
, that might call f
).
I'd like to get some feedback on these ideas. Would you be interested in merging these two, or one of these, into the compiler? Or do you have some related suggesions that I could implement?