Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track recursive homomorphic mapped types by the symbol of their target #55638

Merged
merged 3 commits into from
Sep 11, 2023

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Sep 5, 2023

With this PR, when a homomorphic mapped type is applied to a type with a symbol, we use the symbol of that type as the recursion identity. This is a better strategy than using the symbol of the mapped type, which doesn't work well for recursive mapped types because they end up being considered deeply nested after just three levels.

type Id<T> = { [K in keyof T]: Id<T[K]> };

type Foo1 = Id<{ x: { y: { z: { a: { b: { c: number } } } } } }>;
type Foo2 = Id<{ x: { y: { z: { a: { b: { c: string } } } } } }>;

declare const foo1: Foo1;
const foo2: Foo2 = foo1;  // Now errors, previously didn't

Note that this PR isn't a general solution to the problem of distinguishing finite and infinite types, rather it is a targeted fix for situations where mapped types are applied to object types that clearly originate in distinct declarations.

Fixes #55535.

@ahejlsberg
Copy link
Member Author

@typescript-bot test this
@typescript-bot user test this inline
@typescript-bot run dt
@typescript-bot perf test faster
@typescript-bot test top100

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 5, 2023

Heya @ahejlsberg, I've started to run the tsc-only perf test suite on this PR at cd21478. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 5, 2023

Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at cd21478. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 5, 2023

Heya @ahejlsberg, I've started to run the diff-based user code test suite on this PR at cd21478. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 5, 2023

Heya @ahejlsberg, I've started to run the diff-based top-repos suite on this PR at cd21478. You can monitor the build here.

Update: The results are in!

@Andarist
Copy link
Contributor

Andarist commented Sep 5, 2023

This is awesome, thank you! ❤️

@typescript-bot
Copy link
Collaborator

@ahejlsberg
The results of the perf run you requested are in!

Here they are:

Compiler

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Angular - node (v16.17.1, x64)
Memory used 300,278k (± 0.01%) 300,288k (± 0.01%) ~ 300,258k 300,299k p=0.521 n=6
Parse Time 3.00s (± 0.17%) 3.00s (± 0.25%) ~ 2.99s 3.01s p=0.241 n=6
Bind Time 0.93s (± 0.00%) 0.93s (± 0.00%) ~ 0.93s 0.93s p=1.000 n=6
Check Time 9.33s (± 0.23%) 9.32s (± 0.36%) ~ 9.29s 9.37s p=0.627 n=6
Emit Time 7.61s (± 0.26%) 7.63s (± 0.39%) ~ 7.58s 7.66s p=0.461 n=6
Total Time 20.88s (± 0.18%) 20.88s (± 0.22%) ~ 20.80s 20.94s p=0.868 n=6
Compiler-Unions - node (v16.17.1, x64)
Memory used 193,935k (± 0.03%) 193,930k (± 0.03%) ~ 193,837k 193,973k p=0.575 n=6
Parse Time 1.58s (± 0.26%) 1.58s (± 0.00%) ~ 1.58s 1.58s p=0.405 n=6
Bind Time 0.79s (± 0.65%) 0.79s (± 0.65%) ~ 0.79s 0.80s p=1.000 n=6
Check Time 9.91s (± 0.56%) 9.92s (± 0.35%) ~ 9.88s 9.97s p=1.000 n=6
Emit Time 2.74s (± 0.27%) 2.74s (± 0.00%) ~ 2.74s 2.74s p=0.598 n=6
Total Time 15.02s (± 0.35%) 15.03s (± 0.26%) ~ 14.99s 15.09s p=0.810 n=6
Monaco - node (v16.17.1, x64)
Memory used 347,170k (± 0.01%) 347,202k (± 0.00%) ~ 347,182k 347,213k p=0.066 n=6
Parse Time 2.69s (± 0.19%) 2.69s (± 0.19%) ~ 2.68s 2.69s p=0.069 n=6
Bind Time 0.99s (± 0.00%) 0.99s (± 0.00%) ~ 0.99s 0.99s p=1.000 n=6
Check Time 7.92s (± 0.19%) 7.94s (± 0.35%) ~ 7.91s 7.99s p=0.254 n=6
Emit Time 4.26s (± 0.43%) 4.27s (± 0.35%) ~ 4.25s 4.29s p=0.222 n=6
Total Time 15.87s (± 0.06%) 15.89s (± 0.21%) ~ 15.85s 15.93s p=0.156 n=6
TFS - node (v16.17.1, x64)
Memory used 301,169k (± 0.01%) 301,178k (± 0.00%) ~ 301,166k 301,195k p=0.470 n=6
Parse Time 2.18s (± 0.63%) 2.18s (± 0.24%) ~ 2.17s 2.18s p=0.796 n=6
Bind Time 1.11s (± 0.37%) 1.11s (± 0.47%) ~ 1.10s 1.11s p=0.114 n=6
Check Time 7.22s (± 0.54%) 7.22s (± 0.25%) ~ 7.19s 7.24s p=0.808 n=6
Emit Time 3.98s (± 0.37%) 3.98s (± 0.42%) ~ 3.96s 4.01s p=0.743 n=6
Total Time 14.49s (± 0.27%) 14.49s (± 0.23%) ~ 14.45s 14.53s p=0.808 n=6
material-ui - node (v16.17.1, x64)
Memory used 479,478k (± 0.00%) 479,527k (± 0.00%) +49k (+ 0.01%) 479,505k 479,561k p=0.008 n=6
Parse Time 3.14s (± 0.16%) 3.15s (± 0.24%) ~ 3.14s 3.16s p=0.247 n=6
Bind Time 0.91s (± 0.00%) 0.92s (± 2.66%) ~ 0.91s 0.97s p=0.405 n=6
Check Time 17.80s (± 0.73%) 17.86s (± 0.58%) ~ 17.79s 18.06s p=0.261 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 21.85s (± 0.60%) 21.93s (± 0.55%) ~ 21.85s 22.17s p=0.172 n=6
xstate - node (v16.17.1, x64)
Memory used 542,812k (± 0.00%) 542,857k (± 0.01%) ~ 542,791k 542,943k p=0.173 n=6
Parse Time 3.69s (± 0.28%) 3.68s (± 0.28%) ~ 3.67s 3.70s p=0.134 n=6
Bind Time 1.43s (± 3.32%) 1.45s (± 0.52%) ~ 1.44s 1.46s p=0.867 n=6
Check Time 3.20s (± 1.89%) 3.17s (± 0.39%) ~ 3.16s 3.19s p=0.507 n=6
Emit Time 0.08s (± 6.19%) 0.08s (± 9.83%) ~ 0.08s 0.10s p=0.923 n=6
Total Time 8.41s (± 0.36%) 8.40s (± 0.32%) ~ 8.37s 8.44s p=0.572 n=6
System info unknown
Hosts
  • node (v16.17.1, x64)
Scenarios
  • Angular - node (v16.17.1, x64)
  • Compiler-Unions - node (v16.17.1, x64)
  • Monaco - node (v16.17.1, x64)
  • TFS - node (v16.17.1, x64)
  • material-ui - node (v16.17.1, x64)
  • xstate - node (v16.17.1, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@ahejlsberg Here are the results of running the user test suite comparing main and refs/pull/55638/merge:

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Unknown failure"
  • 2 instances of "Package install failed"

Otherwise...

Everything looks good!

@ssalbdivad
Copy link

This is massive, thanks so much for the quick turnaround 😍

@ahrjarrett
Copy link

This looks great. Did not expect to see this picked up so quickly :)

Thank you!

@typescript-bot
Copy link
Collaborator

@ahejlsberg Here are the results of running the top-repos suite comparing main and refs/pull/55638/merge:

Something interesting changed - please have a look.

Details

microsoft/vscode

4 of 54 projects failed to build with the old tsc and were ignored

src/tsconfig.tsec.json

  • error TS2769: No overload matches this call.
  • error TS2322: Type 'IWorkspaceEditDto' is not assignable to type '{ edits: ({ resource: UriComponents; textEdit: { range: { readonly startLineNumber: number; readonly startColumn: number; readonly endLineNumber: number; readonly endColumn: number; }; text: string; eol?: EndOfLineSequence | undefined; insertAsSnippet?: boolean | undefined; }; versionId: number | undefined; metadata...'.

@typescript-bot
Copy link
Collaborator

Hey @ahejlsberg, the results of running the DT tests are ready.
There were interesting changes:

Branch only errors:

Package: raphael
Error:

Error: /home/vsts/work/1/s/DefinitelyTyped-tools/packages/dtslint-runner/DefinitelyTyped/types/raphael/test/raphael-tests-api.ts:1458:19
ERROR: 1458:19  expect  TypeScript@local compile error: 
Excessive stack depth comparing types 'RaphaelStatic<"SVG" | "VML">' and 'VmlRaphaelStatic'.
ERROR: 1458:19  expect  TypeScript@local compile error: 
Type instantiation is excessively deep and possibly infinite.

    at testTypesVersion (/home/vsts/work/1/s/DefinitelyTyped-tools/packages/dtslint-runner/node_modules/@definitelytyped/dtslint/dist/index.js:194:15)
    at async runTests (/home/vsts/work/1/s/DefinitelyTyped-tools/packages/dtslint-runner/node_modules/@definitelytyped/dtslint/dist/index.js:151:9)

You can check the log here.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels maybe a bit odd that Readonly<Foo>, Partial<Foo>, and WhateverMapper<Foo> all have the same recursion identity, but it's probably better than every Partial having the same recursion identity. Feels like it'd be better to use both the target symbol and the modifiers type symbol as an aggregate identity for mapped types like this, if possible - that way if you have mutually recursive mapped types mapping over the same type (eg, you have a self referential type that is deep mapped in a way that alternates between applying readonly and partial or the like), they all get the full 3-repition depth.

@typescript-bot typescript-bot added For Milestone Bug PRs that fix a bug with a specific milestone and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Sep 6, 2023
@ahejlsberg
Copy link
Member Author

The DT run has new errors in the raphael package. They're caused by deeper exploration of mapped types that now occurs because we no longer cut off recursion after three levels of nesting of any instantiation of a particular mapped type. I did a bit of experimenting, and it looks like the issue can be remedied by placing an in out (i.e. invariant) annotation on the type parameter of the RaphaelStatic interface:

export interface RaphaelStatic<
    in out TTechnology extends RaphaelTechnology = "SVG" | "VML"
    > {
        // ...
}

Also, two new errors in the vscode project. These look like true type mismatches that previously weren't discovered because we only checked three levels of nesting.

@jakebailey
Copy link
Member

jakebailey commented Sep 8, 2023

in out can't really be used on DT without duplicating the entire package to support old versions; DT still works back to TS 4.4.

(Or actually we just mark the package as 4.7+ which is okay though odd that variance is the fix)

@weswigham
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 8, 2023

Heya @weswigham, I've started to run the tarball bundle task on this PR at cd21478. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 8, 2023

Hey @weswigham, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/157523/artifacts?artifactName=tgz&fileId=64993D329541E82B8792E0FE318FBD3AE479AE2FCC3471BCAD4EC954DFC4CE9802&fileName=/typescript-5.3.0-insiders.20230908.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/pr-build@5.3.0-pr-55638-16".;

@DanielRosenwasser
Copy link
Member

One thing from the design meeting notes - include a test case around something like

type Expand<T> = {
  [K in keyof T]: Expand<Expand<T[K]>>
};

@ahejlsberg
Copy link
Member Author

Ok, a better fix for the new raphael errors is to modify the recursive RaphaelPaperPluginRegistry type to:

export type RaphaelPaperPluginRegistry<
    TTechnology extends RaphaelTechnology = "SVG" | "VML",
    T = RaphaelPaper<TTechnology>> = {
        /**
         * Either the paper plugin method or a new namespace with methods.
         */
        [P in keyof T]: RaphaelPaperPluginMethodOrRegistry<TTechnology, T[P]>;
    };

type RaphaelPaperPluginMethodOrRegistry<TTechnology extends RaphaelTechnology, T> =
    T extends (...args: any) => any
        ? RaphaelPaperPluginMethod<TTechnology, Parameters<T>, ReturnType<T>>
        : RaphaelPaperPluginRegistry<TTechnology, T>;

Extracting the conditional type into a separate aliased type allows the compiler to compute variance information for the type arguments, which in turns makes it much cheaper to relate instantiations. This change is backwards compatible and has positive impact on type instantiation counts even before this PR.

@jakebailey
Copy link
Member

Is there anything blocking this? Just, sending the above to DT?

@ahejlsberg ahejlsberg merged commit 4f899a1 into main Sep 11, 2023
@ahejlsberg ahejlsberg deleted the fix55535 branch September 11, 2023 21:03
sandersn added a commit to DefinitelyTyped/DefinitelyTyped that referenced this pull request Sep 12, 2023
Based on microsoft/TypeScript#55638 (comment)

Extracts the conditional type into a separate type alias. Cheaper to
compile and arguably easier to read.
sandersn added a commit to DefinitelyTyped/DefinitelyTyped that referenced this pull request Sep 12, 2023
Based on microsoft/TypeScript#55638 (comment)

Extracts the conditional type into a separate type alias. Cheaper to
compile and arguably easier to read.
sennyeya added a commit to sennyeya/backstage that referenced this pull request Jan 17, 2024
Currently, ThomasAribart/json-schema-to-ts#96 and microsoft/TypeScript#55638 point to the fact that this can't easily be completed at runtime. I'm going to explore moving more to the generated types.

Signed-off-by: Aramis <sennyeyaramis@gmail.com>
sennyeya added a commit to sennyeya/backstage that referenced this pull request Jan 19, 2024
Currently, ThomasAribart/json-schema-to-ts#96 and microsoft/TypeScript#55638 point to the fact that this can't easily be completed at runtime. I'm going to explore moving more to the generated types.

Signed-off-by: Aramis <sennyeyaramis@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Milestone Bug PRs that fix a bug with a specific milestone
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assignability false positive for recursive mapped types, even when fully instantiated
8 participants