Don't reuse emit resolvers cross-file - #63986
Conversation
There was a problem hiding this comment.
Pull request overview
Prevents cross-file declaration-emit state leakage by replacing checker-wide resolver reuse with operation-scoped resolvers.
Changes:
- Creates a fresh emit resolver per emit host.
- Threads resolver instances through node building and symbol-accessibility checks.
- Updates language-service and test callers.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tsc/internal/compiler/emitHost.go |
Creates per-file emit resolvers. |
tsc/internal/checker/checker.go |
Removes cached resolver state. |
tsc/internal/checker/emitresolver.go |
Shares the active resolver with node builders. |
tsc/internal/checker/symbolaccessibility.go |
Accepts explicit resolver instances. |
tsc/internal/checker/nodebuilder.go |
Adds resolver-aware constructors. |
tsc/internal/checker/nodebuilderimpl.go |
Uses the builder’s resolver consistently. |
tsc/internal/checker/nodecopy.go |
Routes accessibility checks through the builder. |
tsc/internal/checker/exports.go |
Uses a fresh resolver for exported checker behavior. |
tsc/internal/ls/findallreferences.go |
Isolates resolver state during reference lookup. |
tsc/internal/transformers/tstransforms/importelision_test.go |
Updates test setup for the new API. |
| return &emitHost{ | ||
| program: program, | ||
| emitResolver: checker.GetEmitResolver(), | ||
| emitResolver: checker.NewEmitResolver(), |
There was a problem hiding this comment.
we have that, it's called "the flaky test I already mentioned"
|
I noticed that type EmitResolver struct {
checker *Checker
checkerMu *sync.Mutex
isValueAliasDeclaration func(node *ast.Node) bool
aliasMarkingVisitor func(node *ast.Node) bool
referenceResolver binder.ReferenceResolver
jsxLinks core.LinkStore[*ast.Node, JSXLinks]
declarationLinks core.LinkStore[*ast.Node, DeclarationLinks]
declarationFileLinks core.LinkStore[*ast.Node, DeclarationFileLinks]
}
func newEmitResolver(checker *Checker) *EmitResolver {
e := &EmitResolver{checker: checker}
e.isValueAliasDeclaration = e.isValueAliasDeclarationWorker
e.aliasMarkingVisitor = e.aliasMarkingVisitorWorker
e.checkerMu = &checker.mu
return e
}Does this allocate two closures per call?
|
|
Also, is it worth running a perf test on here just to be safe? |
|
Another hit on main: https://github.com/microsoft/TypeScript/actions/runs/32760252400/job/97537150724 |
It does, but we only make one of these per file to emit, so it's not a hotspot. All of this could use a good refactor, of course. |
Reusing resolvers appears to cause races, like:
As far as I can tell, there's no perf impact for flipping this around to just make a new one and refactor things a bit.