Skip to content

Design Meeting Notes, 3/8/2019 #30279

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Higher order function type inference

#30215

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. Now list is contextually sensitive and we infer number for A, and the output type is number[].
        invoke(list, 42);
    • 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 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.
    • Only user test suite code that saw changes is RxJS, fp-ts, and Ramda.

      • RxJS: Looks like fixes. (@benlesh)
      • fp-ts: Better errors, fixes. One .d.ts file changed by creating a generic output type(!) (@gcanti)
      • Ramda: In some cases, better types. In a few, it's just people putting type parameters in the wrong place.
    • 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

#29813

  • 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!
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions