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

Fixes reverse mapped type members limiting constraint #56911

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

Conversation

Andarist
Copy link
Contributor

fixes #56910

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Dec 31, 2023
@Andarist
Copy link
Contributor Author

Andarist commented Jan 4, 2024

@jakebailey would you mind preparing a TS playground for this one? :)

@jakebailey
Copy link
Member

@typescript-bot test top200
@typescript-bot user test this
@typescript-bot run dt

@typescript-bot perf test this
@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 4, 2024

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 4, 2024

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

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 4, 2024

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 4, 2024

Heya @jakebailey, I've started to run the regular perf test suite on this PR at 03848cf. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 4, 2024

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

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 4, 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/159251/artifacts?artifactName=tgz&fileId=98004E881020791F8B6A55883177852A7E97B3448E77B0028F7227721FB42D5F02&fileName=/typescript-5.4.0-insiders.20240104.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/[email protected]".;

@typescript-bot
Copy link
Collaborator

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

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Package install failed"

Otherwise...

Something interesting changed - please have a look.

Details

puppeteer

packages/browsers/test/src/tsconfig.json

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.
Everything looks the same!
You can check the log here.

@typescript-bot
Copy link
Collaborator

@jakebailey
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 (v18.15.0, x64)
Memory used 295,475k (± 0.01%) 295,485k (± 0.01%) ~ 295,444k 295,530k p=0.689 n=6
Parse Time 2.64s (± 0.20%) 2.64s (± 0.20%) ~ 2.64s 2.65s p=1.000 n=6
Bind Time 0.82s (± 1.01%) 0.82s (± 0.92%) ~ 0.81s 0.83s p=0.652 n=6
Check Time 8.14s (± 0.18%) 8.15s (± 0.42%) ~ 8.10s 8.19s p=0.870 n=6
Emit Time 7.09s (± 0.23%) 7.09s (± 0.21%) ~ 7.07s 7.11s p=0.624 n=6
Total Time 18.70s (± 0.13%) 18.70s (± 0.21%) ~ 18.66s 18.75s p=0.936 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used 193,472k (± 1.54%) 192,500k (± 1.29%) ~ 191,432k 197,560k p=0.109 n=6
Parse Time 1.35s (± 0.56%) 1.35s (± 0.73%) ~ 1.33s 1.36s p=0.652 n=6
Bind Time 0.72s (± 0.00%) 0.72s (± 0.00%) ~ 0.72s 0.72s p=1.000 n=6
Check Time 9.28s (± 0.46%) 9.26s (± 0.29%) ~ 9.23s 9.30s p=0.687 n=6
Emit Time 2.62s (± 1.17%) 2.60s (± 0.32%) ~ 2.59s 2.61s p=0.438 n=6
Total Time 13.97s (± 0.42%) 13.94s (± 0.15%) ~ 13.91s 13.96s p=0.293 n=6
Monaco - node (v18.15.0, x64)
Memory used 347,418k (± 0.01%) 347,406k (± 0.00%) ~ 347,384k 347,418k p=0.335 n=6
Parse Time 2.46s (± 0.47%) 2.45s (± 0.52%) ~ 2.43s 2.46s p=0.210 n=6
Bind Time 0.93s (± 0.44%) 0.93s (± 0.88%) ~ 0.92s 0.94s p=0.584 n=6
Check Time 6.89s (± 0.29%) 6.86s (± 0.11%) -0.02s (- 0.36%) 6.85s 6.87s p=0.048 n=6
Emit Time 4.05s (± 0.29%) 4.05s (± 0.25%) ~ 4.04s 4.07s p=0.615 n=6
Total Time 14.32s (± 0.19%) 14.29s (± 0.18%) ~ 14.25s 14.33s p=0.070 n=6
TFS - node (v18.15.0, x64)
Memory used 302,740k (± 0.00%) 302,729k (± 0.00%) ~ 302,722k 302,733k p=0.332 n=6
Parse Time 2.00s (± 0.63%) 2.01s (± 0.58%) ~ 1.99s 2.02s p=0.281 n=6
Bind Time 1.00s (± 1.04%) 1.00s (± 0.63%) ~ 0.99s 1.01s p=0.388 n=6
Check Time 6.29s (± 0.42%) 6.30s (± 0.38%) ~ 6.27s 6.34s p=0.571 n=6
Emit Time 3.59s (± 0.55%) 3.58s (± 0.50%) ~ 3.56s 3.61s p=0.564 n=6
Total Time 12.88s (± 0.23%) 12.89s (± 0.24%) ~ 12.84s 12.93s p=0.627 n=6
material-ui - node (v18.15.0, x64)
Memory used 506,814k (± 0.00%) 506,817k (± 0.00%) ~ 506,784k 506,836k p=0.748 n=6
Parse Time 2.59s (± 0.66%) 2.59s (± 0.57%) ~ 2.57s 2.61s p=0.805 n=6
Bind Time 0.99s (± 0.76%) 0.98s (± 1.06%) ~ 0.97s 1.00s p=0.611 n=6
Check Time 16.93s (± 0.14%) 16.88s (± 0.47%) ~ 16.80s 17.01s p=0.226 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 20.51s (± 0.22%) 20.46s (± 0.39%) ~ 20.37s 20.58s p=0.229 n=6
xstate - node (v18.15.0, x64)
Memory used 512,855k (± 0.01%) 512,897k (± 0.02%) ~ 512,730k 512,989k p=0.230 n=6
Parse Time 3.27s (± 0.19%) 3.28s (± 0.26%) ~ 3.27s 3.29s p=0.340 n=6
Bind Time 1.53s (± 0.34%) 1.54s (± 0.27%) ~ 1.53s 1.54s p=0.112 n=6
Check Time 2.83s (± 0.19%) 2.83s (± 0.14%) ~ 2.82s 2.83s p=0.282 n=6
Emit Time 0.07s (± 0.00%) 0.07s (± 5.69%) ~ 0.07s 0.08s p=0.405 n=6
Total Time 7.70s (± 0.07%) 7.72s (± 0.21%) ~ 7.69s 7.74s p=0.086 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Angular - node (v18.15.0, x64)
  • Compiler-Unions - node (v18.15.0, x64)
  • Monaco - node (v18.15.0, x64)
  • TFS - node (v18.15.0, x64)
  • material-ui - node (v18.15.0, x64)
  • xstate - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

tsserver

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-UnionsTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,354ms (± 0.57%) 2,349ms (± 0.79%) ~ 2,326ms 2,377ms p=0.470 n=6
Req 2 - geterr 5,431ms (± 1.80%) 5,442ms (± 1.51%) ~ 5,347ms 5,538ms p=1.000 n=6
Req 3 - references 325ms (± 0.81%) 324ms (± 0.46%) ~ 321ms 325ms p=0.222 n=6
Req 4 - navto 276ms (± 0.99%) 274ms (± 1.40%) ~ 269ms 278ms p=0.411 n=6
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) ~ 1,356 1,356 p=1.000 n=6
Req 5 - completionInfo 91ms (± 4.48%) 93ms (± 4.57%) ~ 84ms 95ms p=0.370 n=6
CompilerTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,468ms (± 1.06%) 2,485ms (± 1.04%) ~ 2,450ms 2,524ms p=0.298 n=6
Req 2 - geterr 4,163ms (± 2.16%) 4,169ms (± 1.84%) ~ 4,086ms 4,241ms p=0.936 n=6
Req 3 - references 338ms (± 0.84%) 339ms (± 1.14%) ~ 334ms 343ms p=0.625 n=6
Req 4 - navto 288ms (± 1.16%) 285ms (± 1.13%) ~ 282ms 291ms p=0.167 n=6
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) ~ 1,518 1,518 p=1.000 n=6
Req 5 - completionInfo 84ms (± 5.81%) 85ms (± 8.10%) ~ 76ms 91ms p=0.373 n=6
xstateTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,601ms (± 0.53%) 2,600ms (± 0.40%) ~ 2,584ms 2,611ms p=0.936 n=6
Req 2 - geterr 1,718ms (± 2.50%) 1,708ms (± 2.11%) ~ 1,652ms 1,752ms p=0.630 n=6
Req 3 - references 118ms (± 6.94%) 109ms (± 9.96%) ~ 101ms 123ms p=0.366 n=6
Req 4 - navto 367ms (± 1.24%) 366ms (± 0.38%) ~ 364ms 367ms p=0.934 n=6
Req 5 - completionInfo count 2,073 (± 0.00%) 2,073 (± 0.00%) ~ 2,073 2,073 p=1.000 n=6
Req 5 - completionInfo 307ms (± 2.41%) 305ms (± 2.43%) ~ 293ms 314ms p=0.628 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • CompilerTSServer - node (v18.15.0, x64)
  • Compiler-UnionsTSServer - node (v18.15.0, x64)
  • xstateTSServer - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Startup

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
tsc-startup - node (v18.15.0, x64)
Execution time 153.16ms (± 0.20%) 153.17ms (± 0.20%) ~ 152.08ms 156.75ms p=0.621 n=600
tsserver-startup - node (v18.15.0, x64)
Execution time 229.15ms (± 0.16%) 229.01ms (± 0.15%) -0.14ms (- 0.06%) 227.66ms 232.81ms p=0.000 n=600
tsserverlibrary-startup - node (v18.15.0, x64)
Execution time 230.19ms (± 0.18%) 230.28ms (± 0.19%) ~ 228.51ms 235.81ms p=0.089 n=600
typescript-startup - node (v18.15.0, x64)
Execution time 230.70ms (± 0.20%) 230.60ms (± 0.21%) -0.09ms (- 0.04%) 228.94ms 237.00ms p=0.017 n=600
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • tsc-startup - node (v18.15.0, x64)
  • tsserver-startup - node (v18.15.0, x64)
  • tsserverlibrary-startup - node (v18.15.0, x64)
  • typescript-startup - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

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

Everything looks good!

@typescript-bot typescript-bot added For Backlog Bug PRs that fix a backlog bug and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Jan 4, 2024
return;
}
const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType));
return limitedConstraint !== neverType ? limitedConstraint : undefined;
const mapper = appendTypeMapping(type.mappedType.mapper, type.constraintType.type, keyofConstraintObjectType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@weswigham would you mind taking a look at this PR? It's a fix to the recently-ish merged #55811

I have a concern that using keyofConstraintObjectType here is not that great since it might change the meaning of T[K]. In reality, I'd like to just map keyof T... but type mappings only work on type parameters.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, swapping keyof Whatever out for string | number | symbol and getting the resulting type is the goal here, right? That definitely aughta result in the useful bound. It's definitely possible T[K] if it were used in a constraint could return weird stuff here by doing an instantiation in this way (which could be actually done because of some conditional type weirdness).... hmmm... Maybe instantiate with T with T & keyofConstraintObjectType so specific-key indexing still turns up the specific member types, but keyof returns string | number | symbol.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah yes! great idea with that intersection :) I pushed out this change

@jakebailey jakebailey requested a review from weswigham January 10, 2024 18:26
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.

Also, why was a test removed? I can't obviously identify the old test in the new test content.

const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType));
return limitedConstraint !== neverType ? limitedConstraint : undefined;
const mapper = appendTypeMapping(type.mappedType.mapper, type.constraintType.type, keyofConstraintObjectType);
return getBaseConstraintOrType(instantiateType(constraint, mapper));
Copy link
Member

Choose a reason for hiding this comment

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

Not a fan of getBaseConstraintOrType - it is almost never the right operation for the job.... why is it even needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Diff without getBaseConstraintOrType it:

git diff
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index bb3203b745..1752d5e162 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -13677,7 +13677,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
             return;
         }
         const mapper = appendTypeMapping(type.mappedType.mapper, type.constraintType.type, keyofConstraintObjectType);
-        return getBaseConstraintOrType(instantiateType(constraint, mapper));
+        return instantiateType(constraint, mapper);
     }
 
     function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.js b/tests/baselines/reference/isomorphicMappedTypeInference.js
index ee60e3717f..2971c8eb1e 100644
--- a/tests/baselines/reference/isomorphicMappedTypeInference.js
+++ b/tests/baselines/reference/isomorphicMappedTypeInference.js
@@ -372,26 +372,11 @@ declare function f21<T, K extends keyof T>(obj: Pick<T, K>): K;
 declare function f22<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): T;
 declare function f23<T, U extends keyof T, K extends U>(obj: Pick<T, K>): T;
 declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): T & U;
-declare let x0: {
-    foo: number;
-    bar: string;
-};
-declare let x1: "foo" | "bar";
-declare let x2: {
-    foo: number;
-    bar: string;
-};
-declare let x3: {
-    foo: number;
-    bar: string;
-};
-declare let x4: {
-    foo: number;
-    bar: string;
-} & {
-    foo: number;
-    bar: string;
-};
+declare let x0: {};
+declare let x1: never;
+declare let x2: {};
+declare let x3: {};
+declare let x4: {};
 declare function getProps<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K>;
 declare const myAny: any;
 declare const o1: Pick<any, "foo" | "bar">;
diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.types b/tests/baselines/reference/isomorphicMappedTypeInference.types
index 32b74150e5..919a72f9bf 100644
--- a/tests/baselines/reference/isomorphicMappedTypeInference.types
+++ b/tests/baselines/reference/isomorphicMappedTypeInference.types
@@ -533,8 +533,8 @@ declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): T
 >obj : Pick<T & U, K>
 
 let x0 = f20({ foo: 42, bar: "hello" });
->x0 : { foo: number; bar: string; }
->f20({ foo: 42, bar: "hello" }) : { foo: number; bar: string; }
+>x0 : {}
+>f20({ foo: 42, bar: "hello" }) : {}
 >f20 : <T, K extends keyof T>(obj: Pick<T, K>) => T
 >{ foo: 42, bar: "hello" } : { foo: number; bar: string; }
 >foo : number
@@ -543,8 +543,8 @@ let x0 = f20({ foo: 42, bar: "hello" });
 >"hello" : "hello"
 
 let x1 = f21({ foo: 42, bar: "hello" });
->x1 : "foo" | "bar"
->f21({ foo: 42, bar: "hello" }) : "foo" | "bar"
+>x1 : never
+>f21({ foo: 42, bar: "hello" }) : never
 >f21 : <T, K extends keyof T>(obj: Pick<T, K>) => K
 >{ foo: 42, bar: "hello" } : { foo: number; bar: string; }
 >foo : number
@@ -553,8 +553,8 @@ let x1 = f21({ foo: 42, bar: "hello" });
 >"hello" : "hello"
 
 let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } });
->x2 : { foo: number; bar: string; }
->f22({ foo: { value: 42} , bar: { value: "hello" } }) : { foo: number; bar: string; }
+>x2 : {}
+>f22({ foo: { value: 42} , bar: { value: "hello" } }) : {}
 >f22 : <T, K extends keyof T>(obj: Boxified<Pick<T, K>>) => T
 >{ foo: { value: 42} , bar: { value: "hello" } } : { foo: { value: number; }; bar: { value: string; }; }
 >foo : { value: number; }
@@ -567,8 +567,8 @@ let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } });
 >"hello" : "hello"
 
 let x3 = f23({ foo: 42, bar: "hello" });
->x3 : { foo: number; bar: string; }
->f23({ foo: 42, bar: "hello" }) : { foo: number; bar: string; }
+>x3 : {}
+>f23({ foo: 42, bar: "hello" }) : {}
 >f23 : <T, U extends keyof T, K extends U>(obj: Pick<T, K>) => T
 >{ foo: 42, bar: "hello" } : { foo: number; bar: string; }
 >foo : number
@@ -577,8 +577,8 @@ let x3 = f23({ foo: 42, bar: "hello" });
 >"hello" : "hello"
 
 let x4 = f24({ foo: 42, bar: "hello" });
->x4 : { foo: number; bar: string; } & { foo: number; bar: string; }
->f24({ foo: 42, bar: "hello" }) : { foo: number; bar: string; } & { foo: number; bar: string; }
+>x4 : {}
+>f24({ foo: 42, bar: "hello" }) : {}
 >f24 : <T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>) => T & U
 >{ foo: 42, bar: "hello" } : { foo: number; bar: string; }
 >foo : number
@@ -615,10 +615,10 @@ const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']);
 >o2 : { foo: any; bar: any; }
 >foo : any
 >bar : any
->getProps(myAny, ['foo', 'bar']) : Pick<any, "foo" | "bar">
+>getProps(myAny, ['foo', 'bar']) : Pick<any, string>
 >getProps : <T, K extends keyof T>(obj: T, list: K[]) => Pick<T, K>
 >myAny : any
->['foo', 'bar'] : ("foo" | "bar")[]
+>['foo', 'bar'] : string[]
 >'foo' : "foo"
 >'bar' : "bar"
 
diff --git a/tests/baselines/reference/reverseMappedTypeLimitedConstraintWithIntersection1.types b/tests/baselines/reference/reverseMappedTypeLimitedConstraintWithIntersection1.types
index e66efc74dd..215caee211 100644
--- a/tests/baselines/reference/reverseMappedTypeLimitedConstraintWithIntersection1.types
+++ b/tests/baselines/reference/reverseMappedTypeLimitedConstraintWithIntersection1.types
@@ -516,10 +516,10 @@ declare function fn3<T1, T2>(obj: {
 }): [T1, T2];
 
 const result3 = fn3({
->result3 : [{ a: string; b: boolean; }, { a: number; b: null; }]
->fn3({  a: {    v1: "foo",    v2: 100,  },  b: {    v1: true,    v2: null,  },}) : [{ a: string; b: boolean; }, { a: number; b: null; }]
+>result3 : [{}, {}]
+>fn3({  a: {    v1: "foo",    v2: 100,  },  b: {    v1: true,    v2: null,  },}) : [{}, {}]
 >fn3 : <T1, T2>(obj: { [K in keyof T1 & keyof T2]: { v1: T1[K]; v2: T2[K]; }; }) => [T1, T2]
->{  a: {    v1: "foo",    v2: 100,  },  b: {    v1: true,    v2: null,  },} : { a: { v1: string; v2: number; }; b: { v1: true; v2: null; }; }
+>{  a: {    v1: "foo",    v2: 100,  },  b: {    v1: true,    v2: null,  },} : { a: { v1: string; v2: number; }; b: { v1: boolean; v2: null; }; }
 
   a: {
 >a : { v1: string; v2: number; }
@@ -535,11 +535,11 @@ const result3 = fn3({
 
   },
   b: {
->b : { v1: true; v2: null; }
->{    v1: true,    v2: null,  } : { v1: true; v2: null; }
+>b : { v1: boolean; v2: null; }
+>{    v1: true,    v2: null,  } : { v1: boolean; v2: null; }
 
     v1: true,
->v1 : true
+>v1 : boolean
 >true : true
 
     v2: null,
@@ -571,14 +571,14 @@ declare function fn4<T, E extends Record<string, number>>(arg: {
 }): [T, E];
 
 const result4 = fn4({
->result4 : [{ a: string; b: boolean; }, { a: 404; b: 500; }]
->fn4({  a: {    data: "foo",    onSuccess: (dataArg) => {      dataArg;    },    error: 404,    onError: (errorArg) => {      errorArg;    },  },  b: {    data: true,    onSuccess: (dataArg) => {      dataArg;    },    error: 500,    onError: (errorArg) => {      errorArg;    },  },}) : [{ a: string; b: boolean; }, { a: 404; b: 500; }]
+>result4 : [{ a: string; b: boolean; }, {}]
+>fn4({  a: {    data: "foo",    onSuccess: (dataArg) => {      dataArg;    },    error: 404,    onError: (errorArg) => {      errorArg;    },  },  b: {    data: true,    onSuccess: (dataArg) => {      dataArg;    },    error: 500,    onError: (errorArg) => {      errorArg;    },  },}) : [{ a: string; b: boolean; }, {}]
 >fn4 : <T, E extends Record<string, number>>(arg: { [K in keyof T & keyof E]: { data: T[K]; onSuccess: (data: T[K]) => void; error: E[K]; onError: (data: E[K]) => void; }; }) => [T, E]
->{  a: {    data: "foo",    onSuccess: (dataArg) => {      dataArg;    },    error: 404,    onError: (errorArg) => {      errorArg;    },  },  b: {    data: true,    onSuccess: (dataArg) => {      dataArg;    },    error: 500,    onError: (errorArg) => {      errorArg;    },  },} : { a: { data: string; onSuccess: (dataArg: string) => void; error: 404; onError: (errorArg: 404) => void; }; b: { data: true; onSuccess: (dataArg: boolean) => void; error: 500; onError: (errorArg: 500) => void; }; }
+>{  a: {    data: "foo",    onSuccess: (dataArg) => {      dataArg;    },    error: 404,    onError: (errorArg) => {      errorArg;    },  },  b: {    data: true,    onSuccess: (dataArg) => {      dataArg;    },    error: 500,    onError: (errorArg) => {      errorArg;    },  },} : { a: { data: string; onSuccess: (dataArg: string) => void; error: number; onError: (errorArg: unknown) => void; }; b: { data: boolean; onSuccess: (dataArg: boolean) => void; error: number; onError: (errorArg: unknown) => void; }; }
 
   a: {
->a : { data: string; onSuccess: (dataArg: string) => void; error: 404; onError: (errorArg: 404) => void; }
->{    data: "foo",    onSuccess: (dataArg) => {      dataArg;    },    error: 404,    onError: (errorArg) => {      errorArg;    },  } : { data: string; onSuccess: (dataArg: string) => void; error: 404; onError: (errorArg: 404) => void; }
+>a : { data: string; onSuccess: (dataArg: string) => void; error: number; onError: (errorArg: unknown) => void; }
+>{    data: "foo",    onSuccess: (dataArg) => {      dataArg;    },    error: 404,    onError: (errorArg) => {      errorArg;    },  } : { data: string; onSuccess: (dataArg: string) => void; error: number; onError: (errorArg: unknown) => void; }
 
     data: "foo",
 >data : string
@@ -594,25 +594,25 @@ const result4 = fn4({
 
     },
     error: 404,
->error : 404
+>error : number
 >404 : 404
 
     onError: (errorArg) => {
->onError : (errorArg: 404) => void
->(errorArg) => {      errorArg;    } : (errorArg: 404) => void
->errorArg : 404
+>onError : (errorArg: unknown) => void
+>(errorArg) => {      errorArg;    } : (errorArg: unknown) => void
+>errorArg : unknown
 
       errorArg;
->errorArg : 404
+>errorArg : unknown
 
     },
   },
   b: {
->b : { data: true; onSuccess: (dataArg: boolean) => void; error: 500; onError: (errorArg: 500) => void; }
->{    data: true,    onSuccess: (dataArg) => {      dataArg;    },    error: 500,    onError: (errorArg) => {      errorArg;    },  } : { data: true; onSuccess: (dataArg: boolean) => void; error: 500; onError: (errorArg: 500) => void; }
+>b : { data: boolean; onSuccess: (dataArg: boolean) => void; error: number; onError: (errorArg: unknown) => void; }
+>{    data: true,    onSuccess: (dataArg) => {      dataArg;    },    error: 500,    onError: (errorArg) => {      errorArg;    },  } : { data: boolean; onSuccess: (dataArg: boolean) => void; error: number; onError: (errorArg: unknown) => void; }
 
     data: true,
->data : true
+>data : boolean
 >true : true
 
     onSuccess: (dataArg) => {
@@ -625,16 +625,16 @@ const result4 = fn4({
 
     },
     error: 500,
->error : 500
+>error : number
 >500 : 500
 
     onError: (errorArg) => {
->onError : (errorArg: 500) => void
->(errorArg) => {      errorArg;    } : (errorArg: 500) => void
->errorArg : 500
+>onError : (errorArg: unknown) => void
+>(errorArg) => {      errorArg;    } : (errorArg: unknown) => void
+>errorArg : unknown
 
       errorArg;
->errorArg : 500
+>errorArg : unknown
 
     },
   },

Dissecting one of the failures:

declare function fn3<T1, T2>(obj: {
  [K in keyof T1 & keyof T2]: {
    v1: T1[K];
    v2: T2[K];
  };
}): [T1, T2];

const result3 = fn3({
  a: {
    v1: "foo",
    v2: 100,
  },
  b: {
    v1: true,
    v2: null,
  },
});

When creating this limiting constraint for T1 I currently instantiate T1 in the constraint with keyofConstraintObjectType. That leaves us with the other part of this constraint since it's keyof T1 & keyof T2. So after instantiation we are left with (string | number | symbol) & keyof T2. This getBaseConstraintOrType tries to reduce it further to string | number | symbol.

Thinking about it now - what we want is to do the same thing for all reverse mapped types created based on the type.source object. In the example above, T2 is not a reverse mapped type though (it's a type parameter). And on top of that, even keyof T2 doesn't exactly have to end up being iterating over a reverse mapped type. If T2 appears somewhere naked or something then the reverse mapped type won't be the best inference candidate.

So I'm not sure how to properly deal with this here - since even keeping tabs on all potential reverse mapped types created from type.source could not work here (as we don't have a guarantee that other type params will actually end up being inferred as reverse mapped types). A failing test case for this would be something like this:

declare function fn5<T1, T2>(
  obj1: {
    [K in keyof T1 & keyof T2]: T1[K];
  },
  obj2: T2,
): [T1, T2];

const result5 = fn5(
  {
    a: "foo",
    b: 100,
  },
  {
    a: true,
  },
);

It works right now, in a sense that we get EPC on b but inferred T1 includes b property while it shouldn't. By including it we end up in square zero (it could lead to a different manifestation of #56910 ).

I think that perhaps it becomes even more crucial to make those reversed mapped types aware of the original inference context etc. To get the best results we should have access to the inferred types of other type params involved in all of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It works right now, in a sense that we get EPC on b but inferred T1 includes b property while it shouldn't. By including it we end up in square zero (it could lead to a different manifestation of #56910 ).

To complete this with a test case:

declare function fn6<T1 extends Record<string, string>, T2>(
  obj1: {
    [K in keyof T1 & keyof T2]: T1[K];
  },
  obj2: T2,
): [T1, T2];

const obj1_6 = {
  a: "foo",
  b: 100,
};

const result6 = fn6(obj1_6, {
  a: true,
});

result6;
// ^? [Record<string, string>, { a: boolean }]

return;
}
const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType));
return limitedConstraint !== neverType ? limitedConstraint : undefined;
const mapper = appendTypeMapping(type.mappedType.mapper, type.constraintType.type, keyofConstraintObjectType);
Copy link
Member

Choose a reason for hiding this comment

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

Hm, swapping keyof Whatever out for string | number | symbol and getting the resulting type is the goal here, right? That definitely aughta result in the useful bound. It's definitely possible T[K] if it were used in a constraint could return weird stuff here by doing an instantiation in this way (which could be actually done because of some conditional type weirdness).... hmmm... Maybe instantiate with T with T & keyofConstraintObjectType so specific-key indexing still turns up the specific member types, but keyof returns string | number | symbol.

@Andarist
Copy link
Contributor Author

Also, why was a test removed? I can't obviously identify the old test in the new test content.

Those tests are not in the new test content but rather in the old content of that test file that I touched. I just rechecked it and both of them are there. They are almost identical - variable names involved don't include _ though.

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 reviewers
Development

Successfully merging this pull request may close these issues.

Reverse mapped type fails to be inferred with constraint that is a union containing an intersection
4 participants