Skip to content

Tweak logic that chooses co- vs. contra-variant inferences when covariant inference is an empty object type #59772

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

Closed
wants to merge 3 commits into from

Conversation

Andarist
Copy link
Contributor

fixes #59765
cc @weswigham as the reviewer of #57909 by which this got affected

@@ -26989,7 +26989,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it
// and it would spoil the overall inference.
const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
!(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) &&
!(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any) || isEmptyAnonymousObjectType(inferredCovariantType) && isWeakType(inferredContravariantType)) &&
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 is a conservative change that aims to address #59765 .

I could imagine this might need some further tweaking to produce good error locations when both of the inferred types are weak or something. However, so far so good. This already produces good results and good error locations as far as I can tell - perhaps part of that can be attributed to the further checks like some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)).

Copy link
Contributor Author

@Andarist Andarist Aug 27, 2024

Choose a reason for hiding this comment

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

Actually, there is an open question here as to what the inferred type here should be (what should be the result's type):

declare function call<P>(tag: (props: P) => void, attributes: P): P;

declare function consume(p: { a?: string; b?: number; c?: boolean }): void;
declare const obj: { a?: string };

const result = call(consume, obj);

There is no clear answer to this - both satisfy the constraint and both of the arguments. I think that, in general, a covariant inference is safer in a situation like this. It's less likely to introduce problems like this one:

declare function call<P>(tag: (props: P) => void, attributes: P): P;

declare function consume(p: { a?: string; b?: number; c?: boolean }): void;
const obj: { a?: string; } = { a: 'foo', b: 'bar' }

const result = call(consume, obj);
obj.b // it's `number` in 5.5 but at runtime it could easily be a `string`

Nowadays the user can use NoInfer to guide what should be used as the inference source if they don't get the desired inferences for their use case - it's all a little bit situational after all.

And from this perspective... this whole PR could just be closed as the current behavior (5.6-rc) isn't necessarily wrong. Duh, it could even be seen as desirable. All in all, this requires a ruling on what's the better default and all from the team.

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 also experimented with an alternative patch:

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 954e45cdeb..f5e3d12778 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -26989,11 +26989,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
                     // Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it
                     // and it would spoil the overall inference.
                     const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
-                        !(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any) || isEmptyAnonymousObjectType(inferredCovariantType) && isWeakType(inferredContravariantType)) &&
+                        !(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) &&
                             some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) &&
                             every(context.inferences, other =>
                                 other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
-                                every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType))));
+                                every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType))) &&
+                            (some(inference.candidates, t => !isObjectOrArrayLiteralType(t)) || isTypeAssignableTo(inferredCovariantType, inferredContravariantType) && !isTypeAssignableTo(inferredContravariantType, inferredCovariantType)));
                     inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType;
                     fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType;
                 }

All tests pass with it with no changes whatsoever but then it makes those 2 to behave differently:

declare function call<P>(tag: (props: P) => void, attributes: P): P;
declare function consume(p: { a?: string; b?: number; c?: boolean }): void;

declare const obj1: { a?: string };
const result1 = call(consume, obj1);
const result4 = call(consume, { ...obj1 });

And all in all, I'm just not sure what's right here. It all boils down to choosing some tradeoffs.

@Andarist Andarist force-pushed the fix/co-contra-empty-co branch from fed8b34 to 993dfb4 Compare August 27, 2024 09:52
@jakebailey
Copy link
Member

@typescript-bot test it

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 28, 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.

Everything looks the same!

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/59772/merge:

Everything looks good!

@typescript-bot
Copy link
Collaborator

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

Everything looks good!

@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,153 ~ ~ ~ p=1.000 n=6
Types 50,242 50,242 ~ ~ ~ p=1.000 n=6
Memory used 193,033k (± 0.73%) 193,580k (± 0.93%) ~ 192,398k 195,914k p=0.298 n=6
Parse Time 1.30s (± 0.64%) 1.30s (± 0.80%) ~ 1.29s 1.31s p=0.181 n=6
Bind Time 0.71s 0.71s ~ ~ ~ p=1.000 n=6
Check Time 9.58s (± 0.22%) 9.59s (± 0.57%) ~ 9.50s 9.65s p=0.419 n=6
Emit Time 2.72s (± 0.44%) 2.71s (± 0.72%) ~ 2.68s 2.74s p=0.410 n=6
Total Time 14.31s (± 0.16%) 14.31s (± 0.37%) ~ 14.22s 14.38s p=0.935 n=6
angular-1 - node (v18.15.0, x64)
Errors 7 7 ~ ~ ~ p=1.000 n=6
Symbols 945,753 945,753 ~ ~ ~ p=1.000 n=6
Types 410,067 410,067 ~ ~ ~ p=1.000 n=6
Memory used 1,222,695k (± 0.00%) 1,222,709k (± 0.00%) ~ 1,222,687k 1,222,743k p=0.336 n=6
Parse Time 6.64s (± 0.58%) 6.65s (± 0.57%) ~ 6.60s 6.71s p=1.000 n=6
Bind Time 1.86s 1.86s (± 0.34%) ~ 1.85s 1.87s p=1.000 n=6
Check Time 31.18s (± 0.33%) 31.18s (± 0.62%) ~ 30.90s 31.42s p=0.936 n=6
Emit Time 14.94s (± 0.47%) 14.98s (± 0.66%) ~ 14.79s 15.07s p=0.090 n=6
Total Time 54.63s (± 0.23%) 54.67s (± 0.47%) ~ 54.36s 55.04s p=0.810 n=6
mui-docs - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 2,532,841 2,532,841 ~ ~ ~ p=1.000 n=6
Types 996,405 996,405 ~ ~ ~ p=1.000 n=6
Memory used 2,460,295k (± 0.00%) 2,460,345k (± 0.00%) ~ 2,460,255k 2,460,459k p=0.378 n=6
Parse Time 9.45s (± 0.20%) 9.44s (± 0.22%) ~ 9.41s 9.46s p=0.935 n=6
Bind Time 2.21s (± 0.74%) 2.22s (± 0.47%) ~ 2.20s 2.23s p=1.000 n=6
Check Time 75.08s (± 0.57%) 75.17s (± 0.42%) ~ 74.76s 75.53s p=0.575 n=6
Emit Time 0.29s (± 4.29%) 0.28s ~ ~ ~ p=0.598 n=6
Total Time 87.03s (± 0.50%) 87.11s (± 0.39%) ~ 86.65s 87.48s p=0.575 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,232,180 1,232,180 ~ ~ ~ p=1.000 n=6
Types 264,629 264,629 ~ ~ ~ p=1.000 n=6
Memory used 2,532,437k (± 7.69%) 2,532,360k (± 7.70%) ~ 2,353,828k 2,710,797k p=0.936 n=6
Parse Time 6.05s (± 0.57%) 6.05s (± 1.02%) ~ 5.94s 6.13s p=0.687 n=6
Bind Time 2.26s (± 1.23%) 2.25s (± 0.96%) ~ 2.22s 2.28s p=0.748 n=6
Check Time 40.89s (± 0.69%) 40.88s (± 0.97%) ~ 40.45s 41.45s p=0.810 n=6
Emit Time 4.00s (± 0.69%) 4.05s (± 1.19%) ~ 4.00s 4.12s p=0.128 n=6
Total Time 53.24s (± 0.51%) 53.25s (± 0.67%) ~ 52.93s 53.85s p=0.873 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,232,180 1,232,180 ~ ~ ~ p=1.000 n=6
Types 264,629 264,629 ~ ~ ~ p=1.000 n=6
Memory used 2,429,058k (± 0.03%) 2,429,538k (± 0.04%) ~ 2,428,360k 2,431,221k p=0.298 n=6
Parse Time 6.22s (± 0.30%) 6.23s (± 0.87%) ~ 6.13s 6.28s p=0.687 n=6
Bind Time 2.04s (± 0.81%) 2.04s (± 0.91%) ~ 2.02s 2.07s p=1.000 n=6
Check Time 41.59s (± 0.57%) 41.42s (± 0.47%) ~ 41.20s 41.77s p=0.298 n=6
Emit Time 4.05s (± 3.01%) 4.08s (± 5.20%) ~ 3.87s 4.35s p=0.936 n=6
Total Time 53.90s (± 0.43%) 53.80s (± 0.50%) ~ 53.52s 54.11s p=0.689 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 257,016 257,016 ~ ~ ~ p=1.000 n=6
Types 105,789 105,789 ~ ~ ~ p=1.000 n=6
Memory used 429,701k (± 0.02%) 429,752k (± 0.02%) ~ 429,663k 429,869k p=0.173 n=6
Parse Time 4.16s (± 0.36%) 4.17s (± 0.35%) ~ 4.15s 4.19s p=0.119 n=6
Bind Time 1.60s (± 0.34%) 1.60s (± 0.94%) ~ 1.58s 1.62s p=0.863 n=6
Check Time 22.41s (± 0.23%) 22.38s (± 0.29%) ~ 22.26s 22.46s p=0.687 n=6
Emit Time 2.03s (± 1.18%) 2.03s (± 0.80%) ~ 2.01s 2.05s p=1.000 n=6
Total Time 30.20s (± 0.23%) 30.19s (± 0.30%) ~ 30.02s 30.26s p=1.000 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 68 68 ~ ~ ~ p=1.000 n=6
Symbols 225,018 225,018 ~ ~ ~ p=1.000 n=6
Types 94,249 94,249 ~ ~ ~ p=1.000 n=6
Memory used 370,206k (± 0.02%) 370,214k (± 0.02%) ~ 370,122k 370,325k p=0.936 n=6
Parse Time 2.29s (± 0.71%) 2.29s (± 0.36%) ~ 2.28s 2.30s p=0.458 n=6
Bind Time 1.33s (± 2.23%) 1.32s (± 1.17%) ~ 1.31s 1.35s p=0.677 n=6
Check Time 13.40s (± 0.41%) 13.40s (± 0.46%) ~ 13.29s 13.46s p=0.936 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 17.03s (± 0.37%) 17.00s (± 0.43%) ~ 16.88s 17.10s p=0.574 n=6
vscode - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 3,022,415 3,022,415 ~ ~ ~ p=1.000 n=6
Types 1,039,909 1,039,909 ~ ~ ~ p=1.000 n=6
Memory used 3,143,179k (± 0.00%) 3,143,157k (± 0.00%) ~ 3,143,070k 3,143,304k p=0.810 n=6
Parse Time 14.00s (± 0.57%) 14.06s (± 0.82%) ~ 13.94s 14.27s p=0.378 n=6
Bind Time 4.41s (± 2.89%) 4.32s (± 2.24%) ~ 4.27s 4.52s p=0.075 n=6
Check Time 80.36s (± 0.17%) 80.47s (± 0.33%) ~ 80.06s 80.83s p=0.470 n=6
Emit Time 20.62s (± 0.80%) 20.60s (± 0.58%) ~ 20.48s 20.73s p=0.872 n=6
Total Time 119.39s (± 0.18%) 119.45s (± 0.33%) ~ 118.76s 119.83s p=0.521 n=6
webpack - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 275,318 275,318 ~ ~ ~ p=1.000 n=6
Types 112,432 112,432 ~ ~ ~ p=1.000 n=6
Memory used 424,236k (± 0.02%) 424,199k (± 0.02%) ~ 424,098k 424,287k p=0.575 n=6
Parse Time 3.98s (± 0.65%) 3.98s (± 0.60%) ~ 3.94s 4.00s p=0.807 n=6
Bind Time 1.73s (± 0.67%) 1.72s (± 1.13%) ~ 1.70s 1.75s p=0.291 n=6
Check Time 17.59s (± 0.30%) 17.59s (± 0.63%) ~ 17.37s 17.66s p=0.292 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 23.29s (± 0.11%) 23.29s (± 0.58%) ~ 23.03s 23.39s p=0.335 n=6
xstate-main - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 536,424 536,424 ~ ~ ~ p=1.000 n=6
Types 177,440 177,440 ~ ~ ~ p=1.000 n=6
Memory used 481,666k (± 0.10%) 481,393k (± 0.05%) ~ 481,222k 481,920k p=0.809 n=6
Parse Time 4.25s (± 0.71%) 4.25s (± 0.89%) ~ 4.21s 4.31s p=0.809 n=6
Bind Time 1.54s (± 1.06%) 1.54s (± 0.53%) ~ 1.53s 1.55s p=0.616 n=6
Check Time 22.52s (± 0.38%) 22.50s (± 0.30%) ~ 22.41s 22.59s p=0.629 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 28.31s (± 0.33%) 28.29s (± 0.36%) ~ 28.17s 28.46s p=0.689 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

@RyanCavanaugh
Copy link
Member

I don't feel like this change is well-motivated enough to merge as-is. It seems like it's just detecting the repro in the linked issue and doing something else when it sees that, rather than a principled approach of what to do if all inference sites for a type parameter are intersections. Given the call on the linked issue it sounds like we're in agreement to not take this, but I'm definitely willing to have a discussion from first principles (just a log a new bug and we can take it from there).

@Andarist
Copy link
Contributor Author

Andarist commented Sep 3, 2024

I agree. I think that maybe a patch closer to this one could improve some scenarios but I'm also not sure. It's hard for me to know which tradeoffs should be preferred by the overall TS design in cases like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5.6 regression: Inference with mixed in type intersection no longer compiles
4 participants