-
Notifications
You must be signed in to change notification settings - Fork 749
Revert "Make Program diagnostic API clearer (#2197)" #2250
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
Conversation
This reverts commit c86b860.
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.
Pull request overview
This PR reverts commit c86b860 which attempted to make the Program diagnostic API clearer. The revert is being done to address issue #2249, as the maintainer needs to reconsider the approach.
Key changes being reverted:
- Re-introduces
context.Contextparameter toForEachCheckerParallelmethods - Renames
GetSemanticDiagnosticsWithoutNoEmitFilteringback toGetSemanticDiagnosticsNoFilter - Restores previous diagnostic collection logic with helper methods
- Re-adds
CheckSourceFilesmethod - Reverts changes to diagnostic comparison optimizations
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/testrunner/compiler_runner.go | Adds context parameter to ForEachCheckerParallel call in union ordering test |
| internal/project/checkerpool.go | Re-adds ForEachCheckerParallel method with context parameter and request ID validation |
| internal/execute/incremental/program.go | Reverts method name from GetSemanticDiagnosticsWithoutNoEmitFiltering to GetSemanticDiagnosticsNoFilter |
| internal/compiler/program.go | Restores previous diagnostic collection architecture with helper methods, re-adds CheckSourceFiles, and changes back to using slices.Concat |
| internal/compiler/checkerpool.go | Adds context parameter to ForEachCheckerParallel interface and implementation |
| internal/checker/checker_test.go | Adds test for checking TypeScript compiler source files |
| internal/ast/diagnostic.go | Removes pointer equality optimization checks from diagnostic comparison functions |
| func (p *Program) InstantiationCount() int { | ||
| var val atomic.Uint32 | ||
| p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { | ||
| p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) { |
Copilot
AI
Dec 6, 2025
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.
Calling ForEachCheckerParallel with context.Background() will cause a panic in the project CheckerPool implementation. The project CheckerPool's ForEachCheckerParallel method (line 106-108 in internal/project/checkerpool.go) panics when the context has no request ID.
The InstantiationCount() method doesn't receive a context parameter, so it uses context.Background(). This will work fine with the compiler's checkerPool but will panic when used with a project CheckerPool.
Consider either:
- Adding a context parameter to this method
- Creating a variant of
ForEachCheckerParallelthat doesn't require a request ID for read-only operations like counting - Using the compiler's checkerPool directly instead of the interface when the implementation is known
| fileChecker, done = p.checkerPool.GetCheckerForFile(ctx, sourceFile) | ||
| defer done() | ||
| } | ||
| diags := slices.Clip(sourceFile.BindDiagnostics()) |
Copilot
AI
Dec 6, 2025
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 function will panic if sourceFile is nil. The code on lines 1047-1052 attempts to handle the nil case by conditionally getting a file checker, but then line 1053 unconditionally accesses sourceFile.BindDiagnostics(), which will cause a nil pointer dereference.
Either remove the nil handling code (lines 1047-1052) if this function should never be called with nil, or add a nil check before line 1053 to handle the nil case properly.
| diags := slices.Clip(sourceFile.BindDiagnostics()) | |
| var diags []*ast.Diagnostic | |
| if sourceFile != nil { | |
| diags = slices.Clip(sourceFile.BindDiagnostics()) | |
| } else { | |
| diags = nil | |
| } |
| } | ||
|
|
||
| // Getting a checker will force a bind, so this will be populated. | ||
| diags := slices.Clip(sourceFile.BindSuggestionDiagnostics) |
Copilot
AI
Dec 6, 2025
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 function will panic if sourceFile is nil. The code on lines 1150-1155 attempts to handle the nil case by conditionally getting a file checker, but then line 1157 unconditionally accesses sourceFile.BindSuggestionDiagnostics, which will cause a nil pointer dereference.
Either remove the nil handling code (lines 1150-1155) if this function should never be called with nil, or add a nil check before line 1157 to handle the nil case properly.
| diags := slices.Clip(sourceFile.BindSuggestionDiagnostics) | |
| var diags []*ast.Diagnostic | |
| if sourceFile != nil { | |
| diags = slices.Clip(sourceFile.BindSuggestionDiagnostics) | |
| } else { | |
| diags = []*ast.Diagnostic{} | |
| } |
| // TODO: Does this ever work without deadlocking? `p.GetChecker` also tries to lock this mutex. | ||
| // Should this just be a panic? | ||
| c, release := p.GetChecker(ctx) | ||
| defer release() | ||
| cb(0, c) |
Copilot
AI
Dec 6, 2025
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.
The call to p.GetChecker(ctx) on line 119 will deadlock because the mutex p.mu is already locked on line 102. The GetChecker method (line 86-91) attempts to acquire the same mutex with p.mu.Lock(), which will cause a deadlock.
This code path is only reached when there is no existing request checker, which means this implementation is fundamentally broken. Consider either:
- Implementing a
getCheckerLockedvariant that assumes the lock is already held - Panicking instead, as suggested by the TODO comment on line 117-118
| // TODO: Does this ever work without deadlocking? `p.GetChecker` also tries to lock this mutex. | |
| // Should this just be a panic? | |
| c, release := p.GetChecker(ctx) | |
| defer release() | |
| cb(0, c) | |
| // Use getCheckerLocked to avoid deadlock; GetChecker would try to lock p.mu again. | |
| c, idx := p.getCheckerLocked(requestID) | |
| release := func() { | |
| p.inUse[c] = false | |
| if requestID != "" { | |
| delete(p.requestAssociations, requestID) | |
| } | |
| } | |
| defer release() | |
| cb(idx, c) |
| var val atomic.Uint32 | ||
| val.Store(uint32(count)) | ||
| p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { | ||
| p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) { |
Copilot
AI
Dec 6, 2025
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.
Calling ForEachCheckerParallel with context.Background() will cause a panic in the project CheckerPool implementation. The project CheckerPool's ForEachCheckerParallel method (line 106-108 in internal/project/checkerpool.go) panics when the context has no request ID.
The counting methods SymbolCount(), TypeCount(), and InstantiationCount() don't receive a context parameter, so they use context.Background(). This will work fine with the compiler's checkerPool but will panic when used with a project CheckerPool.
Consider either:
- Adding a context parameter to these counting methods
- Creating a variant of
ForEachCheckerParallelthat doesn't require a request ID for read-only operations like counting - Using the compiler's checkerPool directly instead of the interface when the implementation is known
| func (p *Program) TypeCount() int { | ||
| var val atomic.Uint32 | ||
| p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { | ||
| p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) { |
Copilot
AI
Dec 6, 2025
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.
Calling ForEachCheckerParallel with context.Background() will cause a panic in the project CheckerPool implementation. The project CheckerPool's ForEachCheckerParallel method (line 106-108 in internal/project/checkerpool.go) panics when the context has no request ID.
The TypeCount() method doesn't receive a context parameter, so it uses context.Background(). This will work fine with the compiler's checkerPool but will panic when used with a project CheckerPool.
Consider either:
- Adding a context parameter to this method
- Creating a variant of
ForEachCheckerParallelthat doesn't require a request ID for read-only operations like counting - Using the compiler's checkerPool directly instead of the interface when the implementation is known
This reverts commit c86b860.
Fixes #2249
I need to think about this harder. Best to revert.