Skip to content

fix(health): suppress defer_in_loop when the next statement exits the loop - #2942

Merged
RaghavChamadiya merged 1 commit into
repowise-dev:mainfrom
Pushpak731:fix/go-defer-in-loop-exit
Oct 3, 2026
Merged

RaghavChamadiya merged 1 commit into
repowise-dev:mainfrom
Pushpak731:fix/go-defer-in-loop-exit

Conversation

@Pushpak731

Copy link
Copy Markdown
Contributor

Summary

  • loop_stmt_marker returns None for a defer_statement whose next statement in the same block is a return, or a break that exits the loop: such a defer can only be reached once per entry, so the deferred call cannot pile up per iteration (Go defer that runs once because the loop exits right after it is reported as defer in a loop #2938's hugo transform.go shape).
  • Unlabeled break resolves to the innermost breakable statement and only counts when that is the for AND no further loop wraps it up to the function scope (otherwise each outer iteration re-enters and registers the defer again). A labeled break counts only when the label names a for statement, again with no loop above it. switch/select-case breaks, labels naming inner statements, continue, and any other statement between the defer and the exit keep firing. Kept to the syntactic next-statement form per the issue; comments and the ; separator of single-line bodies are skipped.
  • The existing fixture case deferInLoop keeps its count of 1.

Related Issues

Closes #2938

Test Plan

  • Tests pass (pytest)
  • Lint passes (ruff check .)

New tests/unit/health/test_perf_go_defer_exit.py: 33 tests covering all four loop forms, bare/value/multi-value returns, the hugo if { defer; break } shape, comment and ; skipping, labeled breaks naming the loop vs. naming an inner switch/if, labeled break out of a nested loop, switch/select/type-switch case breaks, inner-loop-only breaks in a nest, continue, goto, break-before-defer, per-defer coverage in one body, and the five repro shapes from the issue. tests/unit/health/ in full: 2858 passed, 1 skipped; ruff check clean.

Checklist

  • My code follows the project's code style
  • I have added tests for new functionality
  • All existing tests still pass
  • I have updated documentation if needed

… loop

A defer immediately followed by a return, or by a break that exits the
for loop (not a switch/select case, and not only an inner loop of a
nest), is reached at most once per entry, so the deferred call cannot
pile up per iteration. Unlabeled break resolution walks to the innermost
breakable statement; a labeled break counts only when the label names a
for statement with no further loop above it up to the function scope.
Everything else - a call between the defer and the exit, continue,
switch/select-case breaks, labels naming inner statements, nested-loop
breaks - keeps firing, and the existing fixture count is unchanged.

Closes repowise-dev#2938
Copilot AI balanced review requested due to automatic review settings October 2, 2026 13:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@repowise-bot

repowise-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Repowise checks

Check Result
Coverage Waiting for this PR's CI to upload a report (last report from this repo 2 minutes ago)
Tests 0 of 1 changed file reached by a test (inferred) · 1 reached by no test
Security No new findings on changed lines
Doc drift No drift in docs this PR edits
Tests: which tests reach the changed files (inferred)
  • packages/core/src/repowise/core/analysis/health/perf/dialects/go.py: No test reaches this file (inferred)

Inferred from the code graph of the last indexed commit (60bacdc), nearest tests first; a test reaching a file does not mean it covers the changed lines.

Configure checks: bot settings

@RaghavChamadiya RaghavChamadiya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @Pushpak731, this is careful work. Requiring the exit to leave the outermost loop is the part that makes it right: in for { for { defer f(); break } } the outer iteration re-enters and registers the defer again, and the test file pins that along with the func-literal return and labelled break cases. Merging.

@RaghavChamadiya
RaghavChamadiya merged commit 3fffeb6 into repowise-dev:main Oct 3, 2026
10 checks passed
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.

Go defer that runs once because the loop exits right after it is reported as defer in a loop

3 participants