Description
Higher order function type inference
Reference code
declare function pipe<A extends any[], B>(ab: (...args: A) => B): (...args: A) => B;
declare function invoke<A extends any[], R>(f: (...args: A) => R, ...args: A): R;
declare function box<T>(x: T): { value; T };
declare function list<T>(x: T): T[];
declare function map<T, U>(f: (t: T) => U): (a: T[]) => U[];
- Seemed to get a lot of enthusiasm on this issue.
- Builds on improvements to our inference - specifically, this does better when doing inference from other generic functions.
-
First, we added a change to make types with generic signatures contextually sensitive.
- We have multiple rounds of inference, and marking arguments as contextually sensitive means we can defer inferring from them to later rounds.
- This allows us to let types flow into function expressions to understand their output types.
- This is important each pass of our inference process is fundamentally just a left-to-right process over provided arguments.
- This example used to give
{}[]
before this change. Nowlist
is contextually sensitive and we infernumber
forA
, and the output type isnumber[]
.invoke(list, 42);
- We have multiple rounds of inference, and marking arguments as contextually sensitive means we can defer inferring from them to later rounds.
-
Another change is an extension of the last change - any invoked expression that has a generic call signature that can return a function is also contextually sensitive.
// Would previously be `{}[]`, now is `number[]` because the `map` is generic and returns a function, // and is now considered contextually sensitive. invoke(map(x => x * 2), [1, 2, 3, 4]); invoke(list, identity(42));
-
The last big change is that if the output type of a signature is a non-generic single signature type, then any type parameters which had no inferences will be "promoted" to fresh type variables on a version of the output type that is generic.
- Is this a problem for named type arguments?
- Any change to an invocation can change the way the type arguments are named in output types.
- In a sense, these fresh type variables get observed as existential types.
- Is this a problem for named type arguments?
-
Is there a way to opt out of this behavior?
- No.
- What if you specify a type parameter list on the output type?
- Yes.
- Can I specify an empty type parameter list?
- No.
- Huh.
- Anders: I feel relatively confident in the change; observed differences seem to have been questionable anyway.
- Huh.
- No.
- Can I specify an empty type parameter list?
- Yes.
-
Only user test suite code that saw changes is RxJS, fp-ts, and Ramda.
-
Infra: we didn't need the stupid
excludeArguments
array in overload resolution.- "That constellation of weird hacks was just weird."
-
- Limitations
-
We haven't changed anything for unannotated function expressions
declare function box<T>(a: T): { value: T }; pipe(x => [x], box); // this is (x: any) => { value: any[] }
In other words, we don't try to infer type parameters for
x
.
-
Cross-compilation incremental builds
- High level idea: serializing the information we have in
--watch
mode to disk to get way faster subsequent compiles. - New flag called:
--incremental
- Turned on by default for all composite projects, and defaults to a file called
.tsbuildinfo
. - But can be turned on for all projects!
- Turned on by default for all composite projects, and defaults to a file called
- Works best for files with modules
outFile
may need some changes because something will always need to cha- What's the
.tsbuildinfo
file look like?-
Here's the schema:
export interface SourceFileInfo { // List of helpers in own source files emitted if no prepend is present helpers?: string[]; prologues?: SourceFilePrologueInfo[]; } export interface BundleFileInfo { sections: BundleFileSection[]; sources?: SourceFileInfo; } export interface BundleBuildInfo { js?: BundleFileInfo; dts?: BundleFileInfo; commonSourceDirectory: string; sourceFiles: ReadonlyArray<string>; } export interface ProgramBuildInfo { fileInfos: MapLike<BuilderState.FileInfo>; options: CompilerOptions; referencedMap?: MapLike<string[]>; exportedModulesMap?: MapLike<string[]>; semanticDiagnosticsPerFile?: string[]; } export interface BuildInfo { bundle?: BundleBuildInfo; program?: ProgramBuildInfo; }
-
- You can just delete this and everything should be fine, right?
- Yes.
- Though, there's an open question of whether deleting the file should trigger a full rebuild in either
--watch
or--build
?- Seems like yes.
- Can MSBuild take advantage of this stuff?
- Talk about this offline.
.tsbuildinfo.json
so tooling can understand it?- Well, do we want people to be able to consume this file?
- No, but it's useful and people will.
- But we can't provide guarantees about this.
- What's the versioning story?
- Do these files need some sort of versioning?
- Maybe the the compiler can track this?
- The compiler needs to know what versions it's compatible with, so then why not a format version?
- Easier to just say that no version of TypeScript is compatible with
.tsbuildinfo
files from other compilations.- Resolution: Version number in file.
- No, but it's useful and people will.
- Well, do we want people to be able to consume this file?