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

Infer type parameters from indexes on those parameters #53017

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

closes #51612

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Feb 28, 2023
@typescript-bot
Copy link
Collaborator

The TypeScript team hasn't accepted the linked issue #51612. If you can get it accepted, this PR will have a better chance of being reviewed.

const inferences = [createInferenceInfo(typeParameter)];
inferTypes(inferences, sourceType, templateType);
const wholeInferredType = getTypeFromInference(inferences[0]);
if (!wholeInferredType && inferences.length > 1) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If any direct inference was made for T[K] then we will ignore inferences made for its properties. I think it's the right thing to do here to preserve backward compatibility. This means that, In a sense, the property-based inferences simply~ have a lower inference priority.

const wholeInferredType = getTypeFromInference(inferences[0]);
if (!wholeInferredType && inferences.length > 1) {
const members = createSymbolTable();
for (let i = 1; i < inferences.length; i++) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This assumes that we only collect inferences for direct properties of the reverse mapped type. I think that this is enough for a lot of cases but we could also try to infer deeper ones. This would complicate the algorithm a little bit though. Right now I can just "merge" all of those extra inferences into a single object. If we'd infer into nested properties then:

  • this merging would have to be smart enough to handle properties at arbitrary levels when those inferences are kept in a flat array and they don't have any particular order that would help the merging algorithm. Maybe the layout of those extra inferences could be improved to aid this merging algorithm but then that would complicate the inferences.push(inference) call site
  • we should decide what to do when we find inferences for both T[K]['foo'] and T[K]['foo']['a']. That is pretty much the same situation as T[K] and T[K]['foo'], so I think that we simply should always "prefer" the less nested one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is also an open question if this whole feature is a good idea. One could argue that this is inconsistent with other parts of TS since TS never collects such "partial"/prop-based inferences elsewhere... OTOH, it actually kinda does this with T[K] in the reverse mapped type.

So I treat this feature as an extension of the existing mechanism for reverse mapped types. I think that this would enable some great new capabilities for library authors because this feature would allow inferring multiple unique things per tuple element/object property. Right now it's only possible to infer a single unique thing per tuple element/object property and that is a little bit limiting when it comes to expressing complex types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This assumes that we only collect inferences for direct properties of the reverse mapped type.

while the implementation has completely changed (basically I pulled #20126 here and just tweaked it a little bit) - this is still a "limitation" but I still believe that it's fine. So this doesn't work with the current state of this PR:

declare function test3<
  T extends { p1: { p1p1: unknown; p1p2: unknown }; p2: unknown }
>(o: { a: T["p1"]["p1p1"]; b: T["p2"]; c: T["p1"]["p1p2"] }): T;

test3({
  a: "foo",
  b: 42,
  c: true,
});

There is also an open question if this whole feature is a good idea. One could argue that this is inconsistent with other parts of TS since TS never collects such "partial"/prop-based inferences elsewhere...

This is no longer the case with this PR since this infers now:

declare function test2<T extends { p1: unknown; p2: unknown }>(o: {
  a: T["p1"];
  b: T["p2"];
}): T;

// `T` inferred as `{ p1: string; } & { p2: number; }`
test2({
  a: "foo",
  b: 42,
});

@sandersn sandersn requested review from ahejlsberg and weswigham March 9, 2023 18:07
@typescript-bot typescript-bot removed the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Mar 9, 2023
@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Mar 9, 2023
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.

I had #20126 which was pretty functional a long time ago, which we basically only didn't take because nobody could be bothered to care about it without a backing issue (myself included), but that seems to be an even more general version of this (or at least relied on a similar partial inferencing mechanism), since these kinds of partial inferences that you combine later are applicable beyond reverse mapped types.

(target as IndexedAccessType).indexType.flags & TypeFlags.StringLiteral &&
((target as IndexedAccessType).objectType.flags & TypeFlags.IndexedAccess)
) {
inferences.push(inference = createInferenceInfo(target));
Copy link
Member

Choose a reason for hiding this comment

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

inference lists are stored on inference contexts and used to created cached type mappers, so mutating the list like this is a no-go, since it could muck with the mapper state when the context is cloned and mappers get remade. You may want to take a page from #52944 and adjust inferTypes to take a full InferenceContext and store the sidecar data there, otherwise storing it on the existing inference for the root object could work reasonably well as in #20126.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to learn from #52944 but that PR is quite a lot to take in for me 😅 So I decided to reuse ur work from #20126 as it was much simpler to pull off.

After some tweaks I managed to make it work for the reverse mapped types case that this PR originally was meant to fix. There are currently some issues with the current state of this PR:

  • some of the tests that you originally added were changed, you can see the commit that updates those baselines here (some are just display changes, some are not though)
  • self-check fails

I intend to investigate both problems but perhaps you'd be able to peek here in the meantime and validate the used approach. It would be great to know if I'm heading into the right direction here.

@Andarist Andarist requested a review from weswigham April 18, 2023 11:52
@@ -25141,6 +25160,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let inferredType: Type | undefined;
const signature = context.signature;
if (signature) {
if (inference.indexes) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the time of this comment this aggregated inference is pushed as an extra candidate (that's the behavior preserved from #20126 ).

That creates problems with cases like this:

interface Expression extends Node {
    _expressionBrand: any;
}

declare function setParent<T extends Node>(child: T, parent: T["parent"] | undefined): T;

interface Node {
    readonly kind: number;
    readonly parent: Node;
}

declare const expr: Expression
declare const node: Node

const res = setParent(expr, node)
//    ^? actual: Node, expected: Expression

export {}

It incorrectly widens the inferred type because it's able to eliminate the inferred subtype (Expression) through subtype reduction~.

I was afraid of cases like this in my initial implementation and in there I decided to just use the gathered indexes~ when there were no candidates for the "naked" type variable. Should I do that here and avoid making the intersected indexes the "same priority" candidate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To clarify, this is what happens right now:

  1. we end up with 2 candidates (because the aggregateInference is appended as an extra candidate): Expression and { parent: Node; }
  2. we compute a common super type (getCommonSupertype(baseCandidates)) which is { parent: Node; }
  3. that is not assignable to the constraint, it fails the compareTypes check and thus we assign the instantiated constraint as the inferred type (inference.inferredType = inferredType = instantiatedConstraint)
  4. that in turn is just OK further down the road since the constraint (Node) satisfies everything here

I tried to just always require the aggregateInference to be comparable to the constraint but that broke the "discriminating logic" added for this aggregateInference stuff. So I reverted that change and only added this requirement for non-union constraints.

At this point, the logic related to pushing aggregateInference into the inference.candidates is a little bit hairy 🤷‍♂️ I guess the main question here is if every bit of the included enhancement is good at the end of the day, how it fits the rest of the compiler etc. That's something on what you'd have to chime in with @ahejlsberg .

I still have some of those change to investigate but at least the self-check is working now so perhaps we could create a build using this PR for toying purposes.

@Andarist Andarist changed the title Infer reverse mapped types based on inferences made for its concrete properties Infer type parameters from indexes on those parameters Apr 19, 2023
@Andarist
Copy link
Contributor Author

Status of this PR:

  • it's pretty much done and I await the review and feedback
  • I still think that this feature is especially nice for inferring indexes on reverse mapped types. This PR also enables inferring regular type parameters using those indexes. This is a nice improvement and I don't see many drawbacks of allowing this. However, I feel that a dedicated syntax for declaring/using "generic bags" would be much better. There are things that a dedicated syntax could do that this just can't do. I mentioned all of that in my feature proposal here: Named/keyed type parameters #54254 .
  • the original PR by @weswigham included the "aggregate inference" as an extra candidate. That suffers from some subtle issues as it might impact how supertypes/subtypes are chosen from a set of candidates. I added tests to cover those cases (they were found by the self-check). That's why I only attempt to use the aggregate inference if there are no other candidates in sight. I think it's the best thing to do here.
  • the original PR by @weswigham had a logic for discriminating unions based on the observed usage of the index (a related test case can be seen here). This kind of code is fundamentally unsound since there is no true relation between the argument and the return value. It's impossible to write the implementation of such a function without casting and so on. It could work if a generic would be constrained using an imaginary oneof operator~ but - as it is - I don't think this is OK. For the time being, I retained this logic but I'd like to drop this branch of the code and the related test cases.

@Andarist
Copy link
Contributor Author

Andarist commented Jun 5, 2023

@weswigham friendly 🏓 I'm really curious about your thoughts on this one 😉

Andarist added 2 commits June 13, 2023 11:08
…ties-of-reverse-mapped-types

# Conflicts:
#	src/lib/es5.d.ts
@Andarist Andarist force-pushed the infer-concrete-properties-of-reverse-mapped-types branch from ded12e2 to 375c127 Compare August 22, 2023 15:49
@Andarist
Copy link
Contributor Author

Andarist commented Mar 2, 2024

@weswigham friendly 🏓

@jakebailey
Copy link
Member

@typescript-bot test it

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 16, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
test top400 ✅ Started 👀 Results
user test this ✅ Started 👀 Results
run dt ✅ Started 👀 Results
perf test this faster ✅ Started ❌ Results

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.

There were interesting changes:

Branch only errors:

Package: node/v16
Error:

Error: 
/mnt/vss/_work/1/DefinitelyTyped/types/node/v16/test/events.ts
  29:49  error  TypeScript@local tsconfig.dom.json, local tsconfig.non-dom.json compile error: 
Type instantiation is excessively deep and possibly infinite  @definitelytyped/expect

✖ 1 problem (1 error, 0 warnings)

    at combineErrorsAndWarnings (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.23_typescript@5.7.0-dev.20240916/node_modules/@definitelytyped/dtslint/dist/index.js:194:28)
    at runTests (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.23_typescript@5.7.0-dev.20240916/node_modules/@definitelytyped/dtslint/dist/index.js:186:20)

Package: node/v18
Error:

Error: 
/mnt/vss/_work/1/DefinitelyTyped/types/node/v18/test/events.ts
  29:49  error  TypeScript@local tsconfig.dom.json, local tsconfig.non-dom.json compile error: 
Type instantiation is excessively deep and possibly infinite  @definitelytyped/expect

✖ 1 problem (1 error, 0 warnings)

    at combineErrorsAndWarnings (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.23_typescript@5.7.0-dev.20240916/node_modules/@definitelytyped/dtslint/dist/index.js:194:28)
    at runTests (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.23_typescript@5.7.0-dev.20240916/node_modules/@definitelytyped/dtslint/dist/index.js:186:20)

Package: rdfjs__data-model
Error:

Error: 
/mnt/vss/_work/1/DefinitelyTyped/types/rdfjs__data-model/rdfjs__data-model-tests.ts
  76:5  error  TypeScript@local compile error: 
Unused '@ts-expect-error' directive  @definitelytyped/expect

✖ 1 problem (1 error, 0 warnings)

    at combineErrorsAndWarnings (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.23_typescript@5.7.0-dev.20240916/node_modules/@definitelytyped/dtslint/dist/index.js:194:28)
    at runTests (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.23_typescript@5.7.0-dev.20240916/node_modules/@definitelytyped/dtslint/dist/index.js:186:20)

You can check the log here.

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user tests with tsc comparing main and refs/pull/53017/merge:

Something interesting changed - please have a look.

Details

webpack

tsconfig.types.json

  • [NEW] error TS2345: Argument of type 'Comparator<PartialInference<number, keyof PartialInference<number, keyof T>>>' is not assignable to parameter of type '(a: { connection: ModuleGraphConnection; sourceOrder: number; rangeStart: number; }, b: { connection: ModuleGraphConnection; sourceOrder: number; rangeStart: number; }) => number'.

tsconfig.json

  • [NEW] error TS2345: Argument of type 'Comparator<PartialInference<number, keyof PartialInference<number, keyof T>>>' is not assignable to parameter of type '(a: { connection: ModuleGraphConnection; sourceOrder: number; rangeStart: number; }, b: { connection: ModuleGraphConnection; sourceOrder: number; rangeStart: number; }) => number'.

@typescript-bot
Copy link
Collaborator

@jakebailey, the perf run you requested failed. You can check the log here.

@jakebailey
Copy link
Member

@typescript-bot perf test this faster

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 16, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
perf test this faster ✅ Started 👀 Results

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top 400 repos with tsc comparing main and refs/pull/53017/merge:

Something interesting changed - please have a look.

Details

Eugeny/tabby

11 of 29 projects failed to build with the old tsc and were ignored

tabby-web-demo/tsconfig.json

tabby-web/tsconfig.json

tabby-terminal/tsconfig.json

tabby-telnet/tsconfig.json

tabby-ssh/tsconfig.json

tabby-settings/tsconfig.json

tabby-serial/tsconfig.json

tabby-plugin-manager/tsconfig.json

tabby-local/tsconfig.json

tabby-linkifier/tsconfig.json

tabby-electron/tsconfig.json

tabby-core/tsconfig.typings.json

tabby-core/tsconfig.json

tabby-community-color-schemes/tsconfig.json

honojs/hono

5 of 6 projects failed to build with the old tsc and were ignored

tsconfig.json

  • error TS2345: Argument of type 'Context<{ Bindings: E["Bindings"] & { eventContext: EventContext<E["Bindings"], any, Record<string, unknown>>; ASSETS: { fetch: { (input: RequestInfo | URL, init?: RequestInit | undefined): Promise<...>; (input: string | ... 1 more ... | URL, init?: RequestInit | undefined): Promise<...>; }; }; }; }, any, {}>' is not assignable to parameter of type 'Context<E & { Bindings: { eventContext: EventContext<{}, any, Record<string, unknown>>; }; }, P, I>'.

mapbox/mapbox-gl-js

1 of 2 projects failed to build with the old tsc and were ignored

tsconfig.json

  • error TS2345: Argument of type 'T & string' is not assignable to parameter of type 'keyof PartialInference<R[T] extends void ? [] : [R[T]], keyof PartialInference<R[T] extends void ? [] : [R[T]], T>>'.
  • error TS2345: Argument of type 'T | keyof PartialInference<R[T] extends void ? [] : [R[T]], T>' is not assignable to parameter of type '(string & {}) | keyof R'.
  • error TS2536: Type 'T | keyof PartialInference<R[T] extends void ? [] : [R[T]], T>' cannot be used to index type 'Listeners<R, unknown>'.
  • error TS2345: Argument of type 'T | keyof PartialInference<R[T] extends void ? [] : [R[T]], T>' is not assignable to parameter of type 'keyof R'.

mendableai/firecrawl

apps/api/tsconfig.json

react-hook-form/react-hook-form

2 of 3 projects failed to build with the old tsc and were ignored

tsconfig.json

@typescript-bot
Copy link
Collaborator

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

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 30 30 ~ ~ ~ p=1.000 n=6
Symbols 62,153 62,190 +37 (+ 0.06%) ~ ~ p=0.001 n=6
Types 50,242 50,312 +70 (+ 0.14%) ~ ~ p=0.001 n=6
Memory used 192,970k (± 0.60%) 193,842k (± 0.90%) ~ 192,525k 196,089k p=0.066 n=6
Parse Time 1.31s (± 0.79%) 1.31s (± 0.57%) ~ 1.30s 1.32s p=0.931 n=6
Bind Time 0.71s 0.71s ~ ~ ~ p=1.000 n=6
Check Time 9.58s (± 0.45%) 9.61s (± 0.38%) ~ 9.56s 9.66s p=0.257 n=6
Emit Time 2.71s (± 1.75%) 2.72s (± 0.43%) ~ 2.70s 2.73s p=0.560 n=6
Total Time 14.31s (± 0.40%) 14.34s (± 0.27%) ~ 14.29s 14.39s p=0.334 n=6
angular-1 - node (v18.15.0, x64)
Errors 7 7 ~ ~ ~ p=1.000 n=6
Symbols 945,753 942,931 -2,822 (- 0.30%) ~ ~ p=0.001 n=6
Types 410,067 409,363 -704 (- 0.17%) ~ ~ p=0.001 n=6
Memory used 1,222,750k (± 0.00%) 1,219,385k (± 0.00%) -3,365k (- 0.28%) 1,219,344k 1,219,410k p=0.005 n=6
Parse Time 6.65s (± 0.19%) 6.67s (± 0.35%) ~ 6.65s 6.71s p=0.088 n=6
Bind Time 1.87s (± 0.28%) 1.87s (± 0.40%) ~ 1.86s 1.88s p=0.784 n=6
Check Time 31.24s (± 0.12%) 31.14s (± 0.47%) ~ 30.94s 31.34s p=0.172 n=6
Emit Time 15.01s (± 0.62%) 15.03s (± 0.53%) ~ 14.90s 15.12s p=0.686 n=6
Total Time 54.76s (± 0.18%) 54.72s (± 0.30%) ~ 54.53s 54.93s p=0.748 n=6
mui-docs - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 2,520,527 2,518,728 -1,799 (- 0.07%) ~ ~ p=0.001 n=6
Types 935,974 934,799 -1,175 (- 0.13%) ~ ~ p=0.001 n=6
Memory used 2,350,373k (± 0.00%) 2,347,084k (± 0.00%) -3,289k (- 0.14%) 2,346,982k 2,347,212k p=0.005 n=6
Parse Time 9.28s (± 0.15%) 9.28s (± 0.38%) ~ 9.25s 9.34s p=0.518 n=6
Bind Time 2.15s (± 0.35%) 2.14s (± 0.76%) ~ 2.11s 2.15s p=0.230 n=6
Check Time 73.29s (± 0.38%) 72.38s (± 0.43%) -0.91s (- 1.25%) 72.15s 72.97s p=0.005 n=6
Emit Time 0.28s (± 1.86%) 0.28s (± 2.95%) ~ 0.26s 0.28s p=0.752 n=6
Total Time 85.00s (± 0.33%) 84.07s (± 0.38%) -0.93s (- 1.09%) 83.86s 84.69s p=0.005 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,245,769 1,246,141 +372 (+ 0.03%) ~ ~ p=0.001 n=6
Types 264,202 265,123 +921 (+ 0.35%) ~ ~ p=0.001 n=6
Memory used 2,397,199k (± 0.02%) 2,398,103k (± 0.02%) +904k (+ 0.04%) 2,397,252k 2,398,735k p=0.020 n=6
Parse Time 5.11s (± 0.96%) 5.11s (± 0.65%) ~ 5.08s 5.17s p=0.810 n=6
Bind Time 1.92s (± 0.42%) 1.90s (± 0.54%) -0.02s (- 1.04%) 1.89s 1.92s p=0.014 n=6
Check Time 34.86s (± 0.59%) 34.91s (± 0.82%) ~ 34.53s 35.20s p=0.936 n=6
Emit Time 3.09s (± 4.76%) 3.07s (± 3.89%) ~ 2.96s 3.27s p=0.810 n=6
Total Time 44.98s (± 0.65%) 45.02s (± 0.62%) ~ 44.51s 45.28s p=0.936 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,245,769 1,246,141 +372 (+ 0.03%) ~ ~ p=0.001 n=6
Types 264,202 265,123 +921 (+ 0.35%) ~ ~ p=0.001 n=6
Memory used 2,471,498k (± 0.04%) 2,473,095k (± 0.04%) +1,597k (+ 0.06%) 2,471,800k 2,474,271k p=0.031 n=6
Parse Time 6.34s (± 0.54%) 6.34s (± 0.40%) ~ 6.30s 6.36s p=0.936 n=6
Bind Time 2.05s (± 0.40%) 2.05s (± 0.91%) ~ 2.02s 2.07s p=1.000 n=6
Check Time 41.45s (± 0.67%) 41.73s (± 0.89%) ~ 41.28s 42.10s p=0.298 n=6
Emit Time 3.69s (± 4.99%) 3.63s (± 4.54%) ~ 3.46s 3.95s p=0.689 n=6
Total Time 53.52s (± 0.37%) 53.76s (± 0.64%) ~ 53.15s 54.06s p=0.173 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 259,878 259,991 +113 (+ 0.04%) ~ ~ p=0.001 n=6
Types 106,139 106,532 +393 (+ 0.37%) ~ ~ p=0.001 n=6
Memory used 435,624k (± 0.01%) 435,914k (± 0.03%) +290k (+ 0.07%) 435,792k 436,149k p=0.005 n=6
Parse Time 3.44s (± 0.77%) 3.43s (± 0.29%) ~ 3.42s 3.44s p=0.870 n=6
Bind Time 1.29s (± 1.06%) 1.30s (± 0.63%) ~ 1.29s 1.31s p=0.181 n=6
Check Time 18.16s (± 0.39%) 18.16s (± 0.18%) ~ 18.11s 18.20s p=0.871 n=6
Emit Time 1.51s (± 0.69%) 1.54s (± 1.67%) ~ 1.50s 1.57s p=0.063 n=6
Total Time 24.41s (± 0.24%) 24.43s (± 0.17%) ~ 24.38s 24.49s p=0.574 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 68 68 ~ ~ ~ p=1.000 n=6
Symbols 225,018 225,119 +101 (+ 0.04%) ~ ~ p=0.001 n=6
Types 94,249 94,612 +363 (+ 0.39%) ~ ~ p=0.001 n=6
Memory used 370,233k (± 0.02%) 370,561k (± 0.03%) +329k (+ 0.09%) 370,445k 370,707k p=0.005 n=6
Parse Time 2.77s (± 0.90%) 2.77s (± 0.78%) ~ 2.74s 2.80s p=0.936 n=6
Bind Time 1.57s (± 2.11%) 1.57s (± 1.19%) ~ 1.53s 1.58s p=1.000 n=6
Check Time 15.78s (± 0.36%) 15.82s (± 0.19%) ~ 15.77s 15.85s p=0.199 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 20.11s (± 0.33%) 20.15s (± 0.17%) ~ 20.11s 20.19s p=0.147 n=6
vscode - node (v18.15.0, x64)
Errors 1 1 ~ ~ ~ p=1.000 n=6
Symbols 3,065,376 3,064,897 -479 (- 0.02%) ~ ~ p=0.001 n=6
Types 1,058,725 1,058,375 -350 (- 0.03%) ~ ~ p=0.001 n=6
Memory used 3,171,032k (± 0.00%) 3,170,119k (± 0.00%) -913k (- 0.03%) 3,170,048k 3,170,167k p=0.005 n=6
Parse Time 13.85s (± 0.36%) 13.84s (± 0.42%) ~ 13.78s 13.94s p=0.747 n=6
Bind Time 4.46s (± 2.69%) 4.49s (± 1.98%) ~ 4.31s 4.54s p=1.000 n=6
Check Time 81.01s (± 0.17%) 81.00s (± 0.45%) ~ 80.71s 81.69s p=0.521 n=6
Emit Time 22.32s (± 0.67%) 22.04s (± 0.34%) -0.29s (- 1.28%) 21.94s 22.13s p=0.008 n=6
Total Time 121.65s (± 0.26%) 121.37s (± 0.37%) ~ 121.02s 122.20s p=0.228 n=6
webpack - node (v18.15.0, x64)
Errors 0 1 🔻+1 (+ ∞%) ~ ~ p=0.001 n=6
Symbols 277,156 275,983 -1,173 (- 0.42%) ~ ~ p=0.001 n=6
Types 112,946 111,976 -970 (- 0.86%) ~ ~ p=0.001 n=6
Memory used 426,926k (± 0.01%) 426,053k (± 0.01%) -874k (- 0.20%) 425,978k 426,117k p=0.005 n=6
Parse Time 3.95s (± 0.41%) 3.94s (± 0.30%) ~ 3.93s 3.96s p=0.216 n=6
Bind Time 1.72s (± 0.30%) 1.70s (± 1.47%) ~ 1.66s 1.73s p=0.352 n=6
Check Time 17.58s (± 0.33%) 17.70s (± 0.37%) +0.12s (+ 0.70%) 17.60s 17.77s p=0.020 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 23.25s (± 0.27%) 23.34s (± 0.36%) ~ 23.20s 23.42s p=0.107 n=6
xstate-main - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 531,582 531,541 -41 (- 0.01%) ~ ~ p=0.001 n=6
Types 181,676 181,726 +50 (+ 0.03%) ~ ~ p=0.001 n=6
Memory used 463,705k (± 0.01%) 463,658k (± 0.01%) ~ 463,569k 463,749k p=0.230 n=6
Parse Time 3.87s (± 0.30%) 3.87s (± 0.38%) ~ 3.85s 3.89s p=1.000 n=6
Bind Time 1.38s (± 0.59%) 1.38s (± 0.54%) ~ 1.37s 1.39s p=0.340 n=6
Check Time 22.61s (± 0.18%) 22.53s (± 0.35%) ~ 22.46s 22.68s p=0.065 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 27.86s (± 0.12%) 27.77s (± 0.30%) ~ 27.68s 27.93s p=0.065 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@jakebailey
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 16, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started ✅ Results

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 16, 2024

Hey @jakebailey, 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/163583/artifacts?artifactName=tgz&fileId=382726B29C1E4F0DB6DA262450365794DC24B4866B4420BB65B73E1F4BDB587B02&fileName=/typescript-5.7.0-insiders.20240916.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.7.0-pr-53017-15".;

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.

I still think that this feature is especially nice for inferring indexes on reverse mapped types. This PR also enables inferring regular type parameters using those indexes. This is a nice improvement and I don't see many drawbacks of allowing this. However, I feel that a dedicated syntax for declaring/using "generic bags" would be much better. There are things that a dedicated syntax could do that this just can't do. I mentioned all of that in my feature proposal here: #54254 .

I think this change is a pretty
good improvement, but I'm biased. Probably need to look into the top repo changes, though.

the original PR by @weswigham included the "aggregate inference" as an extra candidate. That suffers from some subtle issues as it might impact how supertypes/subtypes are chosen from a set of candidates. I added tests to cover those cases (they were found by the self-check). That's why I only attempt to use the aggregate inference if there are no other candidates in sight. I think it's the best thing to do here.

Yeah, it being a lower priority inference target is probably fine.

the original PR by @weswigham had a logic for discriminating unions based on the observed usage of the index (a related test case can be seen here). This kind of code is fundamentally unsound since there is no true relation between the argument and the return value. It's impossible to write the implementation of such a function without casting and so on. It could work if a generic would be constrained using an imaginary oneof operator~ but - as it is - I don't think this is OK. For the time being, I retained this logic but I'd like to drop this branch of the code and the related test cases.

Inference isn't about picking the one choice to end them all, it's about picking a choice for instantiation that works for the arguments provided and that roughly matches user expectations. Otherwise we'd just always instantiate as never/unknown/any, since they'll trivially make arguments typecheck. When I write

function createNode<T extends Node>(kind: T["kind"]): T {
  return nodeConstructors.get(kind)?.()! as T;
}
// assume `Node` is a `kind`-ed union type and `nodeConstructors` is a `Map<SyntaxKind, () => Node>`

I'd assume, broadly speaking, that users are usually passing in a single kind, and, when they are, they'd like a single kind of node out the back end. I was always going to have to cast in the implementation though, and this choosier inference doesn't really have anything to do with that. 🤷

Anyways, broadly speaking, again, I like this, but we probably need to look at those extended test suites we just got, and, for the code itself, since I'm the originator of a big chunk of it now, I'm probably not best suited to review it. @jakebailey are you interested in this?

@jakebailey
Copy link
Member

I can't say I'm an expert here but I can certainly try to review it if/when things are settled; I'm just always one to run extended tests and make playgrounds 😄

@jcalz
Copy link
Contributor

jcalz commented Dec 12, 2024

cross-linking #20126 here (it's mentioned above but hard to find)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Status: Waiting on author
Development

Successfully merging this pull request may close these issues.

Improve reverse mapped types inference by creating candidates from concrete index types
6 participants