-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Add nested call and new expressions as potential intra expression inference sites #54183
base: main
Are you sure you want to change the base?
Add nested call and new expressions as potential intra expression inference sites #54183
Conversation
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
@typescript-bot test this |
Heya @weswigham, I've started to run the perf test suite on this PR at bcb0da5. You can monitor the build here. Update: The results are in! |
Heya @weswigham, I've started to run the diff-based top-repos suite on this PR at bcb0da5. You can monitor the build here. Update: The results are in! |
Heya @weswigham, I've started to run the parallelized Definitely Typed test suite on this PR at bcb0da5. You can monitor the build here. Update: The results are in! |
Heya @weswigham, I've started to run the extended test suite on this PR at bcb0da5. You can monitor the build here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems fine - iirc, needing to do a deeper traversal for context-sensitivity checking was something we've known for awhile, we've just been avoiding doing it in most of our other fixes, since we've been worried about the perf cost of a deeper tree traversal. Assuming perf comes back fine, only change I think I'd want is a deprecated isContextSensitive
in the public API (which can just call the new function), so this isn't a hard API break.
Heya @weswigham, I've run the RWC suite on this PR - assuming you're on the TS core team, you can view the resulting diff here. |
@weswigham Here are the results of running the top-repos suite comparing Everything looks good! |
Hey @weswigham, the results of running the DT tests are ready. |
…inference-with-resolved-calls # Conflicts: # src/compiler/checker.ts # tests/baselines/reference/intraExpressionInferences.errors.txt # tests/baselines/reference/intraExpressionInferences.js # tests/baselines/reference/intraExpressionInferences.symbols # tests/baselines/reference/intraExpressionInferences.types # tests/cases/conformance/types/typeRelationships/typeInference/intraExpressionInferences.ts
is this relevant for a function that is annotated with |
@weswigham Here they are:
CompilerComparison Report - main..54183
System
Hosts
Scenarios
TSServerComparison Report - main..54183
System
Hosts
Scenarios
StartupComparison Report - main..54183
System
Hosts
Scenarios
Developer Information: |
…inference-with-resolved-calls # Conflicts: # src/compiler/checker.ts
…inference-with-resolved-calls # Conflicts: # src/compiler/checker.ts # tests/baselines/reference/intraExpressionInferences.errors.txt # tests/baselines/reference/intraExpressionInferences.js # tests/baselines/reference/intraExpressionInferences.symbols # tests/baselines/reference/intraExpressionInferences.types # tests/cases/conformance/types/typeRelationships/typeInference/intraExpressionInferences.ts
…inference-with-resolved-calls
68e3c49
to
bfdaa86
Compare
@typescript-bot perf test public |
Heya @weswigham, I've started to run the public perf test suite on this PR at bfdaa86. You can monitor the build here. Update: The results are in! |
@weswigham Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
@weswigham Here are the results of running the user test suite comparing There were infrastructure failures potentially unrelated to your change:
Otherwise... Something interesting changed - please have a look. Details
|
Hey @weswigham, the results of running the DT tests are ready. |
@weswigham Here are the results of running the top-repos suite comparing Something interesting changed - please have a look. Details
|
@typescript-bot test top800 |
@typescript-bot perf test this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm probably not the best one to review the code specifics, but what happens if we consider all calls and new
expressions to be context-sensitive?
src/compiler/checker.ts
Outdated
} | ||
|
||
function containsContextSensitiveOrCallOrNewExpression(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { | ||
return containsContextRelatedNode(node, n => isContextSensitiveFunctionOrObjectLiteralMethod(n) || isCallOrNewExpression(n)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like you should consider the same of tagged template expressions (unless we currently don't handle those as direct arguments either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless we currently don't handle those as direct arguments either.
I might be mistaken but I think the above is true today:
function test<T extends TemplateStringsArray>(a: T) {}
test`foo`
// ^? function test<TemplateStringsArray>(a: TemplateStringsArray): void
src/compiler/checker.ts
Outdated
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression | ||
// that is subject to contextual typing. | ||
function containsContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I wonder what the technical reasons are around why this is this still needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the question why this is just not replaced by containsContextSensitiveOrCallOrNewExpression
everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh, I don't know ;p it has been almost a year since I opened this PR. I'm not sure if there was any specific reason behind this choice or if I just tried to minimize the surface area of this change. I can run an experiment on this later to learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I ran this experiment and here is the diff
git diff
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 9b37dedb32..ede8bd4bc2 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -20180,7 +20180,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
// that is subject to contextual typing.
function containsContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
- return containsContextRelatedNode(node, isContextSensitiveFunctionOrObjectLiteralMethod);
+ return containsContextSensitiveOrCallOrNewExpression(node);
}
function containsContextSensitiveOrCallOrNewExpression(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
diff --git a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnType.types b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnType.types
index bf756a3ad4..d9cfa0ba27 100644
--- a/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnType.types
+++ b/tests/baselines/reference/contextuallyTypeAsyncFunctionReturnType.types
@@ -133,7 +133,7 @@ test("windows-process-tree", async () => {
>test("windows-process-tree", async () => { return new Promise((resolve, reject) => { getProcessTree(123, (tree) => { if (tree) { resolve(); } else { reject(new Error("windows-process-tree")); } }); });}) : void
>test : TestFunction
>"windows-process-tree" : "windows-process-tree"
->async () => { return new Promise((resolve, reject) => { getProcessTree(123, (tree) => { if (tree) { resolve(); } else { reject(new Error("windows-process-tree")); } }); });} : () => Promise<void>
+>async () => { return new Promise((resolve, reject) => { getProcessTree(123, (tree) => { if (tree) { resolve(); } else { reject(new Error("windows-process-tree")); } }); });} : (this: Context) => Promise<void>
return new Promise((resolve, reject) => {
>new Promise((resolve, reject) => { getProcessTree(123, (tree) => { if (tree) { resolve(); } else { reject(new Error("windows-process-tree")); } }); }) : Promise<void>
diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.types b/tests/baselines/reference/deeplyNestedMappedTypes.types
index 4b4cd3cea9..d5f9434801 100644
--- a/tests/baselines/reference/deeplyNestedMappedTypes.types
+++ b/tests/baselines/reference/deeplyNestedMappedTypes.types
@@ -1,7 +1,7 @@
//// [tests/cases/compiler/deeplyNestedMappedTypes.ts] ////
=== Performance Stats ===
-Assignability cache: 500 / 600 (nearest 100)
+Assignability cache: 600 / 600 (nearest 100)
Type Count: 1,500 / 1,500 (nearest 100)
Instantiation count: 15,500 / 15,500 (nearest 500)
Symbol count: 26,000 / 26,000 (nearest 500)
diff --git a/tests/baselines/reference/promiseTypeStrictNull.types b/tests/baselines/reference/promiseTypeStrictNull.types
index 4b9375dd6f..0afffb9aaa 100644
--- a/tests/baselines/reference/promiseTypeStrictNull.types
+++ b/tests/baselines/reference/promiseTypeStrictNull.types
@@ -1,9 +1,9 @@
//// [tests/cases/compiler/promiseTypeStrictNull.ts] ////
=== Performance Stats ===
-Identity cache: 200 / 200 (nearest 100)
+Identity cache: 300 / 300 (nearest 100)
Assignability cache: 200 / 200 (nearest 100)
-Type Count: 700 / 700 (nearest 100)
+Type Count: 700 / 800 (nearest 100)
Instantiation count: 2,000 / 2,000 (nearest 500)
Symbol count: 27,500 / 27,500 (nearest 500)
diff --git a/tests/baselines/reference/reverseMappedTypeContextualTypeNotCircular.types b/tests/baselines/reference/reverseMappedTypeContextualTypeNotCircular.types
index 65a4677dcd..67b08b8aee 100644
--- a/tests/baselines/reference/reverseMappedTypeContextualTypeNotCircular.types
+++ b/tests/baselines/reference/reverseMappedTypeContextualTypeNotCircular.types
@@ -20,8 +20,8 @@ const editable = () => ({});
>{} : {}
const mapStateToProps = createStructuredSelector({
->mapStateToProps : Selector<unknown, { editable: {}; }>
->createStructuredSelector({ editable: (state: any, props: any) => editable(), // expect "Type '(state: any, props: any) => {}' is not assignable to type 'Selector<unknown, {}>'", _not_ a circularity error}) : Selector<unknown, { editable: {}; }>
+>mapStateToProps : Selector<unknown, unknown>
+>createStructuredSelector({ editable: (state: any, props: any) => editable(), // expect "Type '(state: any, props: any) => {}' is not assignable to type 'Selector<unknown, {}>'", _not_ a circularity error}) : Selector<unknown, unknown>
>createStructuredSelector : <S, T>(selectors: { [K in keyof T]: Selector<S, T[K]>; }) => Selector<S, T>
>{ editable: (state: any, props: any) => editable(), // expect "Type '(state: any, props: any) => {}' is not assignable to type 'Selector<unknown, {}>'", _not_ a circularity error} : { editable: (state: any, props: any) => {}; }
So nothing spectacular has changed here. However, it shows that couple of caches get bigger after this change. The change in the inferred type in reverseMappedTypeContextualTypeNotCircular
is slightly worrying but did some extra experiments with the code like this that doesn't error (this test case is meant to error):
type Selector<S, R> = (state: S) => R;
declare function createStructuredSelector<S, T>(selectors: {
[K in keyof T]: Selector<S, T[K]>;
}): Selector<S, T>;
const editable = () => "";
// current:
// const mapStateToProps: Selector<unknown, { editable: string; }>
// with the change:
// const mapStateToProps: Selector<any, { editable: string; }>
const mapStateToProps = createStructuredSelector({
editable: (state: any) => editable(),
});
declare function createStructuredSelector2<S, T>(
state: S,
selectors: { [K in keyof T]: Selector<S, T[K]> },
): Selector<S, T>;
const editable2 = () => "";
// current:
// const mapStateToProps21: Selector<{ editable: { foo: string; }; }, { editable: string; }>
// with the change:
// const mapStateToProps21: Selector<any, { editable: string; }>
const mapStateToProps21 = createStructuredSelector2(
{ editable: { foo: "" } },
{
editable: (state: any) => editable(),
},
);
// current:
// const mapStateToProps22: Selector<{ editable: { foo: string; }; }, { editable: string; }>
// with the change:
// const mapStateToProps22: Selector<{ editable: { foo: string; }; }, { editable: string; }>
const mapStateToProps22 = createStructuredSelector2(
{ editable: { foo: "" } },
{
editable: (state) => editable(),
},
);
declare function createStructuredSelector3<S, T>(
state: S,
selectors: { [K in keyof T]: Selector<S, T[K]> },
): Selector<S, T>;
const editable3 = (s: { editable: { foo: string } }) => s.editable.foo;
// current:
// const mapStateToProps3: Selector<{ editable: { foo: string; }; }, { editable: string; }>
// with the change:
// const mapStateToProps3: Selector<{ editable: { foo: string; }; }, { editable: string; }>
const mapStateToProps3 = createStructuredSelector2(
{ editable: { foo: "" } },
{
editable: (state) => editable3(state),
},
);
Even though some of those seemingly regressed (any
inferred in some of them)... I'm not that sure if that would be the correct assessment of what happens here.
At the moment, inference sources for type variables used within reverse-mapped types are often "ignored". See the corresponding issue and some of my attempts to address the situation here. You can also see some extra instances of how it behaves today weirdly and somewhat inconsistently in my recent comment here.
So to me, it seems that this change just somehow makes such functions with calls/news at the return positions viable inference sources for other type variables
@DanielRosenwasser Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
tsserverComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
startupComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
Extracted repro (still depending on 3rd party libs) from the reported failure above: TS playground EDIT: Somewhat reduced repro of the above: TS playground |
…inference-with-resolved-calls
It would definitely impact some inferences negatively. Context-sensitive functions are replaced with |
@Andarist maybe there's been a miscommunication - I was asking about all call/construct invocation expressions like
In #54183 (comment), it seems you're talking about all function expressions like
|
Ah, sorry - I misread it. But isn't this what this PR is effectively doing? function containsContextSensitiveOrCallOrNewExpression(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
return containsContextRelatedNode(node, n => isContextSensitiveFunctionOrObjectLiteralMethod(n) || isCallOrNewExpression(n));
} This check doesn't have any extra checks combined with |
…inference-with-resolved-calls
#57909 was merged so the issue above is resolved now. @jakebailey could you re-run top800 and other important tests here? @DanielRosenwasser @weswigham could I ask for another review? :) |
@typescript-bot perf test this |
Hey @jakebailey, the results of running the DT tests are ready. Everything looks the same! |
@jakebailey Here are the results of running the user tests with tsc comparing Everything looks good! |
@jakebailey Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
tsserverComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
startupComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
@jakebailey Here are the results of running the top 800 repos with tsc comparing Everything looks good! |
Sometimes nested generic functions are skipped during inference in the first pass:
https://github.dev/microsoft/TypeScript/blob/04d4580f4eedc036b014ef4329cffe9979da3af9/src/compiler/checker.ts#L33619-L33634
However, those calls are resolved from within
checkExpressionWithContextualType
and by the time we have a chance to calladdIntraExpressionInferenceSite
they are already resolved. So it, imho, makes sense to actually use them as intra expression inference sites.fixes #54184