-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Cutoff reverse mapped type recursion #53595
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
base: main
Are you sure you want to change the base?
Cutoff reverse mapped type recursion #53595
Conversation
|
@typescript-bot perf test this |
|
Heya @weswigham, I've started to run the perf test suite on this PR at c472b67. You can monitor the build here. Update: The results are in! |
| const newTypeParam = (constraint.type as IndexedAccessType).objectType; | ||
| const newMappedType = replaceIndexedAccess(target, constraint.type as ReplaceableIndexedAccessType, newTypeParam); | ||
| target = newMappedType as MappedType; | ||
| constraint = getIndexType(newTypeParam) as IndexType; |
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.
By moving this logic from reverse mapped type property construction forward into reverse mapped type construction, we can cut down on the number of reverse mapped type identities (and property symbols) we make - this is critical to actually being able to block the recursion, since it makes a generative recursion into a self recursion.
| // Since `inferReverseMappedType` avoids pulling on structure in its inference result, we *shouldn't* have a recursive | ||
| // type resolution request, but if we somehow do, we should error. | ||
| if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { | ||
| reportCircularityError(symbol); |
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.
In the current version of this PR, this error is never reported, since I've avoided pulling on the structure elsewhere, but this is what's needed to prevent another stack depth error from happening with any other incidental recursion in reverse mapped type resolution. Plus, basically every other branch of getTypeOfSymbol has a guard like this. Cue "how did we even manage to get by without circularity guards here?" response.
| inferTypes([inference], sourceType, templateType); | ||
| return getTypeFromInference(inference) || unknownType; | ||
| // Skip subtype reducing the inference result (this can eagerly resolve structure, which we'd rather avoid while doing this inference) | ||
| return getTypeFromInference(inference, UnionReduction.Literal) || unknownType; |
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.
By only doing Literal reductions on this inference result, we save some time, and prevent pulling on the structure of a recursive reverse mapped type while it's still being constructed. What we lose is, conceivably, reducing a reverse mapped type property that resolves to {x} | {x,y} to {x,y} (Or, perhaps more relevant, Function | RecursiveReverseMappingThatResolvesToASpecificFunction becoming Function, which is closer to what mongoose has); but I'd argue that most reverse mapped types only involve a single type, maybe union'd with undefined anyway.
The alternative is just issuing an error on the recursion, and that code is in-place, but that error feels bad because it only appears on instantiation and usage of the reverse mapped type, and not when it's made, even though the error appears at its' declaration site.
(note: as an aside, the command-line compiler does not like this and won't display the error, since it's generated for a file that already had direct diagnostics cached, resulting in a very silent, sneaky any type popping out of inference without an error reported. I suspect any deferred structure resolution that has a circularity can trigger this poor error reporting behavior. I can dodge this by using currentNode as the error node for the circularity error instead of the symbol's declaration site, but then you loose where the circularity occurs, so it probably at least needs a related span... better would be fixing up the program to actually pick up diagnostics added in files which it already collected diagnostics in once.)
|
@weswigham Here they are:
CompilerComparison Report - main..53595
System
Hosts
Scenarios
TSServerComparison Report - main..53595
System
Hosts
Scenarios
StartupComparison Report - main..53595
System
Hosts
Scenarios
Developer Information: |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Eh, a 0.75% improvement in node 16, but not 14 or 18? Weird. At least it's definitely not perf negative. |
|
@typescript-bot run dt |
|
Heya @weswigham, I've started to run the parallelized Definitely Typed test suite on this PR at c472b67. You can monitor the build here. Update: The results are in! |
|
Hey @weswigham, the results of running the DT tests are ready. Branch only errors:Package: convict |
| @@ -24005,6 +23995,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |||
| if (inInferTypeForHomomorphicMappedType) { | |||
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.
tangent q: is there any reason why this is a "global" flag and not something based on source/target identities? 🤔
Fixes #53179
I'm still working on minimizing the repro, since it pulls in all of mongoose and mongodb, but I know this fixes the problem, and want to queue up a perf run to see if this change ends up being perf positive (I think it might be).