Skip to content

fix: prevent panic on type switch bindings in anonymous functions - #534

Open
Thanatos9404 wants to merge 4 commits into
uber-go:mainfrom
Thanatos9404:fix/anonymousfunc-type-switch-panic
Open

Thanatos9404 wants to merge 4 commits into
uber-go:mainfrom
Thanatos9404:fix/anonymousfunc-type-switch-panic

Conversation

@Thanatos9404

@Thanatos9404 Thanatos9404 commented Oct 19, 2026 •

Copy link
Copy Markdown

Fixes #531

With -experimental-anonymous-function, a type switch binding inside a function literal (switch n := v.(type)) makes the closure collection panic, which hides every other diagnostic in the package. The header n has a nil Defs entry, and each case clause declares its own implicit variable.

Changes:

  • collectClosure skips the header identifier, next to the blank identifier check from fix: prevent panic with blank identifiers in anonymous functions (cleaned-up replacement PR) #510.
  • A variable now belongs to the literal when its scope chain reaches the literal's scope (isDeclaredIn). The old scope.Lookup check treated the case clause variables as captured, which caused a second panic.
  • A second commit fixes the 7 self-check findings (make nilaway-lint) that this fix exposes, without changing behaviour.

Behaviour change: variables declared in nested blocks of a literal (if, for) no longer become fake parameters. On 16 standard library packages and x/tools nilness, only the wording of crypto/x509 diagnostics changes, plus html/template_test, where a dependency that used to panic is now analyzed.

Testing: new cases in the assertion/anonymousfunc testdata and in testdata/src/anonymousfunction. TestClosureCollection and TestAnonymousFunction fail on main and pass with this change.

Still open: a binding declared outside a literal and used inside it panics on a separate path (GetDeclaringIdent); I can follow up.

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Explain Change →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fb9840da-e5eb-4d53-8a2e-25a0d17f44a8
Commits

Reviewing files that changed from the base of the PR and between ab2f3cf and 9cb1edf.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Summary by CodeRabbit

  • Bug Fixes
    • Improved analysis of variables captured by anonymous functions, including declarations in nested scopes and type-switch bindings. Diagnostics involving these variables are now handled more accurately, including cases with shadowing and nested function literals.
    • Avoided generating comparisons when the actual expression is unavailable.
  • Tests
    • Expanded coverage for anonymous functions, variadic functions, nested local variables, type switches, and nil-flow scenarios, including diagnostic handling for captured variables and nested scopes.

Walkthrough

Closure analysis now treats declarations in nested scopes as local to a function literal and skips type-switch header identifiers without definition entries. Added tests cover closure collection and anonymous-function diagnostics. Other changes centralize affiliation trigger appending, use preorder traversal in two function analyses, skip comparator generation when the actual expression is missing, and use slices.Reverse for field-chain segments.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: Minimal · previous review ab2f3

The change fixes a panic in anonymous-function analysis of type-switch bindings and adds tests. No actionable merge-blocking risk was found in the supplied context.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: a panic fix for type switch bindings inside anonymous functions. The raw summary and objectives confirm closure collection changes that skip the type-switch header ide…
Description check Passed The description states the problem (a panic from a type switch binding in a function literal under -experimental-anonymous-function), links issue #531, lists the code changes (skipping the header iden…
Linked Issues check Passed The only active direct target is #531. Its coding requirement is that -experimental-anonymous-function must not panic on a type-switch binding inside a function literal. The reviewed change addresse…
Out of Scope Changes check Passed The closure changes, the traversal updates in structfieldeffects (ast.Preorder), the nil guard in hook/split_blocks_on.go, the slices.Reverse change in util/asthelper, the `TestSplitFieldCha…
Full details: Title check

Explanation

The title names the main change: a panic fix for type switch bindings inside anonymous functions. The raw summary and objectives confirm closure collection changes that skip the type-switch header identifier and use scope-chain checks for declarations, which matches the title's subject.

Full details: Description check

Explanation

The description states the problem (a panic from a type switch binding in a function literal under -experimental-anonymous-function), links issue #531, lists the code changes (skipping the header identifier, isDeclaredIn scope check, nilaway self-check fixes), names the testdata added, and notes a known open panic path. It directly describes the changeset.

Full details: Linked Issues check

Explanation

The only active direct target is #531. Its coding requirement is that -experimental-anonymous-function must not panic on a type-switch binding inside a function literal. The reviewed change addresses this in assertion/anonymousfunc/closure.go. collectClosure skips the type-switch header identifier when its TypesInfo.Defs entry is nil, so the failing .(*types.Var) assertion is no longer reached. isDeclaredIn checks the scope chain instead of scope.Lookup, which removes the second panic on case-clause variables. The new fixture testdata/src/anonymousfunction/type_switch.go covers the issue reproducer. TestClosureCollection now asserts that no recovered panic is returned. The second commit contains self-check fixes and does not change the #531 behaviour. The separate outer-binding panic (GetDeclaringIdent) and the diagnostic-position problem in #533 are not #531 requirements. The evidence is the PR summary and the change summary, not a source diff read, so the result depends on those summaries being accurate.

Full details: Out of Scope Changes check

Explanation

The closure changes, the traversal updates in structfieldeffects (ast.Preorder), the nil guard in hook/split_blocks_on.go, the slices.Reverse change in util/asthelper, the TestSplitFieldChain assertion, and the affiliation.go helper refactor are described as self-check fixes. The PR states that these fixes come from make nilaway-lint findings exposed by this change, and that they do not change behaviour. The affiliation.go refactor and the structfieldeffects traversal changes have a weaker link to closure collection. The evidence does not show that they were required for #531, but it also does not show that they are unrelated. The change summary reports no public API changes. No unrelated feature is shown. The behaviour change for variables in nested blocks of a literal is a direct consequence of the isDeclaredIn rule and is covered by the new testNestedScopes and variadicFuncLitWithNestedLocal tests.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f010161b-95ba-4f2a-baad-912e277449bd
Commits

Reviewing files that changed from the base of the PR and between b960d0d and ab2f3cf.

Files selected for processing: 1

  • hook/split_blocks_on.go

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@ccuetoh ccuetoh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Thanatos9404! Thank you very much for your contribution and for taking this fix. I have only one small nit, everything else looks all right!

Please note that you need to sign the CLA before the changes can be merged. See the CLAassistant bot comment.

Comment thread hook/split_blocks_on.go Resolved
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Golden Test

Note

✅ NilAway errors reported on standard libraries are identical.

2409 errors on base branch (main, ccd0056)
2409 errors on test branch (94179da)

@codecov

codecov Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.49%. Comparing base (ccd0056) to head (9cb1edf).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #534      +/-   ##
==========================================
- Coverage   92.91%   90.49%   -2.43%     
==========================================
  Files          82       82              
  Lines        9938     9942       +4     
==========================================
- Hits         9234     8997     -237     
- Misses        694      699       +5     
- Partials       10      246     +236     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ccuetoh

ccuetoh commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

Hi @Thanatos9404! The problem now is that since the 'if' is split you're going to be missing coverage. Could you keep it where it was before and just add a comment?

@Thanatos9404

Copy link
Copy Markdown
Author

Makes sense, done in ab2f3cf: the check is back in the single if, and the comment above it now explains that the actualExpr == nil part is only there for the self-check.

@codspeed

codspeed Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 25.19%

3 warnings

⚠️ Walltime results may be unreliable

Some benchmarks used the Walltime instrument outside CodSpeed Macro Runners, which can make the results inconsistent.

Learn more about Macro Runners

See affected benchmarks

⚠️ 9 benchmarks with a significant change ran on different hardware

Their base and head results were measured on machines with a different CPU, core count or memory, so the changes might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

⚠️ 9 benchmarks with a significant change ran in a different software environment

Their base and head results were measured with a different OS or toolchain, such as compiler, runtime or library versions, so the changes might come from this difference rather than from the code. Open an affected benchmark to see what changed.

See affected benchmarks

❌ 9 regressed benchmarks
✅ 4 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ decode/100-functions 126.1 µs 175.9 µs -28.33%
❌ encode/100-functions 116 µs 161.7 µs -28.25%
❌ decode/10-functions 24.2 µs 33.4 µs -27.64%
❌ encode/10-functions 15 µs 20.3 µs -26.25%
❌ encode/1-functions 5 µs 6.8 µs -25.41%
❌ decode/1-functions 14.3 µs 19 µs -24.48%
❌ go/types 686.1 ms 891.9 ms -23.08%
❌ encoding/json 321 ms 414.1 ms -22.49%
❌ regexp 278.8 ms 350 ms -20.33%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing Thanatos9404:fix/anonymousfunc-type-switch-panic (9cb1edf) with main (ccd0056)

Open in CodSpeed

With -experimental-anonymous-function, a type switch with a binding
inside a function literal (`switch n := v.(type)`) made the closure
collection panic, which also hid every other diagnostic in the package.

The binding in the switch header does not denote a variable by itself:
go/types records a nil object for it in Defs and declares a separate
implicit variable in each case clause instead. Skip the header
identifier, and decide whether a variable belongs to the function
literal by walking its scope chain instead of looking it up in the
function scope only. Otherwise the implicit variables of the case
clauses, which live in nested scopes, are collected as closure
variables and passed as extra arguments at the call site, which then
panics in the function analysis.

As a result, variables declared in other nested scopes of a function
literal (for example in an `if` or `for` statement) are no longer
collected as closure variables either. This changes some results for
the better: a nil flow into such a variable is now reported from the
variable itself instead of through a fake parameter, and a variadic
function literal with such a variable no longer hits the panic from
issue uber-go#532.

Fixes uber-go#531
The type switch panic fixed in the previous commit also hit several
NilAway packages in `make nilaway-lint`, and the resulting INTERNAL
ERROR was filtered out. Now that those packages are analyzed again, the
self-check reports 7 findings. None of the changes below alter NilAway's
behaviour:

* Call `record` and `markUnstable` from `ast.Preorder` loops instead of
  `ast.Inspect` callbacks (the callbacks always returned true), and turn
  `appendTypeToTypeTriggers` into a method. The anonymous function
  analysis does not pass the variables captured by a function literal
  through another function literal that calls it, so it reported them
  as unassigned.
* Use `slices.Reverse` in `SplitFieldChain`.
* Return early in `requireComparators` if `actualExpr` is nil, which
  cannot happen once `expectedExprValue` is known.
* Check that the base is not nil in `TestSplitFieldChain` before using
  it.
Split the actualExpr nil check from the unsupported-case check and add a
comment: actualExpr is always set together with expectedExprValue, so the
check only exists for NilAway's self-check, which does not relate the two
variables.
@ccuetoh
ccuetoh force-pushed the fix/anonymousfunc-type-switch-panic branch from ab2f3cf to 9cb1edf Compare November 12, 2026 14:04
@ccuetoh

ccuetoh commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks, approved!

Please note that you need to sign the CLA before the changes can be merged. See the CLAassistant bot comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anonymous-function analysis panics on a type switch binding inside a function literal

3 participants