uncheckedtypeassertion: recognize safe comma-ok form in var init and parenthesized assertions#39774
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the uncheckedtypeassertion Go analyzer to recognize additional safe comma-ok type assertion contexts by unwrapping parenthesized assertions and treating var v, ok = x.(T) as safe, reducing false positives without relaxing unsafe single-value assertion reporting.
Changes:
- Expand “safe two-value assertion” detection to unwrap
*ast.ParenExprparents and accept*ast.ValueSpec(var v, ok = ...) in addition to*ast.AssignStmt. - Update analyzer fixtures to cover safe
var v, ok = ...and parenthesized comma-ok assignment, and keep coverage for unsafe single-valuevar v = .... - Includes workflow
.lock.ymlschema changes forpull_request_numbervalidation across multiple generated workflows.
Show a summary per file
| File | Description |
|---|---|
| pkg/linters/uncheckedtypeassertion/uncheckedtypeassertion.go | Broadens safe-context detection for comma-ok assertions (paren unwrapping + ValueSpec). |
| pkg/linters/uncheckedtypeassertion/testdata/src/uncheckedtypeassertion/uncheckedtypeassertion.go | Adds/adjusts fixtures for new safe and still-unsafe assertion forms. |
| .github/workflows/test-quality-sentinel.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/smoke-copilot.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/smoke-copilot-arm.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/smoke-copilot-aoai-entra.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/smoke-copilot-aoai-apikey.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/smoke-claude.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/security-review.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/refiner.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/pr-triage-agent.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/pr-nitpick-reviewer.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/pr-code-quality-reviewer.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/mattpocock-skills-reviewer.lock.yml | Generated lockfile update affecting pull_request_number validation. |
| .github/workflows/grumpy-reviewer.lock.yml | Generated lockfile update affecting pull_request_number validation. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 15/15 changed files
- Comments generated: 13
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
| }, | ||
| "pull_request_number": { | ||
| "optionalPositiveInteger": true | ||
| "issueOrPRNumber": true |
|
@copilot run pr-finisher skill |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Test Quality Sentinel completed test quality analysis. No _test.go files were added or modified in this PR. Test Quality Sentinel skipped. Note: the PR does add three new annotated scenarios to the analysistest testdata file (GoodTwoValueVarDecl, GoodTwoValueParen, BadSingleValueVarDecl), which extend the behavioral coverage of the existing TestAnalyzer test — but testdata files are not scored by this sentinel. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed for PR #39774: it does not have the 'implementation' label (has_implementation_label=false) and has only 42 new lines of code in business logic directories (≤100 threshold, no custom .design-gate.yml config). |
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /tdd and /diagnose — approving with two minor test-coverage suggestions.
📋 Key Themes & Highlights
Positive Highlights
- ✅ Root cause correctly identified: the old
parents[typeAssert].(*ast.AssignStmt)lookup missed both*ast.ParenExprwrappers and*ast.ValueSpecparents. - ✅ ParenExpr unwrap loop is clean: iterates until a non-paren ancestor is found; handles the nil-parent edge case by falling through to
default: false— correct and safe. - ✅
ValueSpeccheck mirrors theAssignStmtcheck:len(p.Names) == 2 && len(p.Values) == 1precisely matches the comma-ok grammar for var declarations. - ✅ No weakening of unsafe reporting: single-value var declarations (
var s = x.(T)) correctly remain flagged, confirmed byBadSingleValueVarDecl. - ✅ Lock-file changes are auto-generated (schema rename
optionalPositiveInteger → issueOrPRNumber): no manual review needed.
Minor Test Gaps (non-blocking)
Two combinations of the new code paths aren't exercised by any fixture:
- Parenthesized var-declaration:
var s, ok = (v.(string))— exercises the loop and theValueSpecbranch together (see inline comment at line 52). - Double-parens:
s, ok := ((v.(string)))— confirms the loop iterates more than once (see inline comment at line 48).
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer
| if ok { | ||
| fmt.Println(s) | ||
| } | ||
| } |
There was a problem hiding this comment.
[/tdd] Missing combined test: var s, ok = (v.(string)) — parenthesized var-declaration is not exercised, even though it threads through both new code paths (ParenExpr unwrapping and the ValueSpec case). If a future refactor accidentally broke the interaction, no test would catch it.
💡 Suggested fixture
// Good: parenthesized two-value var declaration is safe.
func GoodTwoValueParenVarDecl(v interface{}) {
var s, ok = (v.(string))
if ok {
fmt.Println(s)
}
}This is the intersection of GoodTwoValueVarDecl and GoodTwoValueParen and is the only path that exercises the ParenExpr-unwrap loop together with the *ast.ValueSpec branch.
|
|
||
| // Good: parenthesized two-value assignment is safe. | ||
| func GoodTwoValueParen(v interface{}) { | ||
| s, ok := (v.(string)) |
There was a problem hiding this comment.
[/tdd] Consider adding a multi-level parens test: s, ok := ((v.(string))). The loop at isSafeTwoValueAssertion is designed to handle any depth of nesting, but no fixture confirms this works for more than one level.
💡 Suggested fixture
// Good: double-parenthesized two-value assignment is safe.
func GoodTwoValueDoubleParen(v interface{}) {
s, ok := ((v.(string)))
if ok {
fmt.Println(s)
}
}Without this, if the loop were accidentally changed to only strip a single ParenExpr, double-paren cases would silently become false positives again.
There was a problem hiding this comment.
Non-blocking: logic is correct, two test coverage gaps
The core change is sound: paren-unwrapping and ValueSpec detection are both correctly implemented and preserve all pre-existing unsafe-detection semantics.
Gaps found (non-blocking)
Medium — paren + ValueSpec combo untested (see inline comment at GoodTwoValueVarDecl)
The paren-unwrapping loop is exercised for AssignStmt (via GoodTwoValueParen), but not for ValueSpec. The pattern var s, ok = (v.(string)) should be added as a fixture.
Low — regular re-assignment with parens untested (see inline comment at GoodTwoValueParen)
v, ok = (x.(T)) (using = instead of :=) is structurally identical in the AST but is not exercised by any fixture.
Note: existing comments on the 13 .lock.yml changes already flag those as unrelated; no additional comment added here.
🔎 Code quality review by PR Code Quality Reviewer
| if ok { | ||
| fmt.Println(s) | ||
| } | ||
| } |
There was a problem hiding this comment.
Paren + ValueSpec combination is not tested, so the paren-unwrapping path for the new ValueSpec branch has zero coverage.
💡 Suggested addition
GoodTwoValueVarDecl tests var s, ok = v.(string) (ValueSpec branch) and GoodTwoValueParen tests s, ok := (v.(string)) (paren→AssignStmt). Neither tests the cross-product: var s, ok = (v.(string)), which goes through the paren-unwrapping loop and then lands on ValueSpec.
If the loop were ever changed to stop early (e.g. breaking on nil before reaching the real parent), this combination would silently produce a false positive and no existing test would catch it.
Add:
// Good: parenthesized two-value var declaration is safe.
func GoodTwoValueVarDeclParen(v interface{}) {
var s, ok = (v.(string))
if ok {
fmt.Println(s)
}
}|
|
||
| // Good: parenthesized two-value assignment is safe. | ||
| func GoodTwoValueParen(v interface{}) { | ||
| s, ok := (v.(string)) |
There was a problem hiding this comment.
v, ok = (x.(T)) (paren + regular re-assignment) is not covered. The only paren fixture uses := (short var decl), leaving the equivalent re-assignment path untested.
💡 Suggested addition
Both := and = produce an AssignStmt, so both are handled by the same case *ast.AssignStmt branch. But a regression that accidentally checked assign.Tok == token.DEFINE would only be caught if the = form is also exercised.
Add:
// Good: parenthesized two-value re-assignment is safe.
func GoodTwoValueParenAssign(v interface{}) {
var s string
var ok bool
s, ok = (v.(string))
if ok {
fmt.Println(s)
}
}Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Completed a pr-finisher pass. Addressed the scope issue by removing unrelated workflow lockfile churn and added the missing uncheckedtypeassertion fixture coverage requested by bot review. Latest commit: |
uncheckedtypeassertiononly treatedx.(T)as safe when its direct parent was anAssignStmt, which produced false positives for valid comma-ok initializations and parenthesized assertions. This PR broadens safe-form detection to match Go’s assignment/initialization semantics without weakening existing unsafe checks.Safe-form parent resolution
*ast.ParenExprancestors before evaluating assertion context.Expanded safe contexts
v, ok := x.(T)v, ok = x.(T)var v, ok = x.(T)v, ok := (x.(T))Preserved unsafe reporting
var v = x.(T),_ = x.(T),f(x.(T))).Analyzer fixture updates
var v, ok = ...and parenthesized comma-ok assignment.