Skip to content

fix: Hot reload no longer warns about callers in other assemblies that it already patched - #3033

Merged
hatayama merged 4 commits into
mainfrom
fix/hot-reload-cross-assembly-covered-callers
Sep 30, 2026
Merged

hatayama merged 4 commits into
mainfrom
fix/hot-reload-cross-assembly-covered-callers

Conversation

@hatayama

@hatayama hatayama commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • After a method is renamed, gets new parameters, or is deleted, hot reload warns about compiled callers that still call the old signature. The warning no longer lists a caller in another assembly once that caller's own patch is active, whether this run or an earlier one patched it.

User Impact

  • Before: a caller in another assembly was always listed as an unpatched compiled call site, even after hot reload had patched it and it no longer ran the compiled body that calls the old method. Following the warning led to reloads or compiles that changed nothing.
  • After: a caller whose patch is active when the reload ends is left out. Callers that still run compiled code are listed as before, with the same text.
  • A caller in another assembly still blocks a return-type change even after it is patched: its patch is compiled against the compiled assembly, where the old signature still exists. An end-to-end test now covers this, and the docs say so.
  • Known limits, now documented:
    • The patched body is not inspected, so a patch that still calls the removed method is not detected.
    • A copy the JIT inlined, or a delegate created before the patch, can still reach the old method.
    • A call inside a lambda or local function stays listed under its compiler-generated name.

Changes

  • The signature-change gate now records the call sites it cannot cover in structured form for the whole run, instead of formatting them into text right away. When the run ends, callers whose (assembly, method label) patch is active are dropped, and the rest are de-duplicated for display. The filter waits for the end of the run because groups run one assembly at a time, so a group cannot see what a later group patches.
  • The active-patch description now carries the declaring assembly name, so methods with the same label in two assemblies are not confused.
  • Docs: the skill summary and the scope-and-limits reference. The generated skill copies are regenerated.

This pull request is stacked on #3031. It matches callers by method label, and #3031 changes how labels spell parameter types.

Verification

  • New tests: 14 unit tests for the run-scoped filter, 1 coverage test that the collection keeps one hit per assembly-qualified caller, and 7 end-to-end cases:
    • the caller is patched in an earlier group, and in a later group;
    • a host-only rerun with an active caller, and with an unpatched caller;
    • the caller is peeled back later in the same run;
    • an active caller still calls the removed method;
    • a return-type change stays gated.
  • Related test classes pass: 37, 189, 30 and 5 tests.
  • Each of these mutations fails the expected tests: turning the filter off, ignoring the assembly, de-duplicating before filtering, and de-duplicating the collection by wire key.
  • File length, complexity, check-skill-size and sync-tool-docs --check pass.
  • An independent review with no prior context found no issues.

Closes #2896

Review in cubic

Labels built from a resolved MethodBase used Type.ToString for parameter
types (List`1[System.Int32], System.Int32[,]), while worker rows and
call-site hits carry Cecil's spelling (List`1<System.Int32>,
System.Int32[0...,0...]). The ledgers compare labels by ordinal equality,
so methods with constructed generic or multidimensional-array parameters
silently missed their matches: the skipped-writer warning claimed the
property keeps its default value although an earlier patch still assigns
it, pause points found no unapplied row, and the stale, superseded, and
signature-change lookups could miss (#2903).

- FormatMethodLabel walks the parameter type structurally (byref, pointer,
  array rank, generic parameter, generic type from its definition's
  FullName plus argument names, otherwise FullName) and keeps reflection's
  '+' for nested types; FormatMethodLabelParts and the worker are unchanged.
- A Cecil parity test compares the MethodBase label with the label built
  from the same method's metadata spelling for 23 parameter shapes, and
  two end-to-end tests repeat the skipped-writer-with-earlier-patch case
  with List<int> and int[,] writers; restoring ToString makes both fail
  with the default-value wording.
- The pause-point domain test and the patcher label test expect the
  metadata spelling, outdated comments about the mismatch are updated, and
  the skill output reference says how Method spells parameter types.
Each group's signature-change gate covers only its own entries, so a
compiled caller in another assembly was always named in the warning, even
when this run (in an earlier or a later group) or an earlier run had
patched it and it no longer runs the compiled body that calls the old
signature. Groups run one assembly at a time, so no check at gate time can
see what a later group patches.

- The gate now records the uncovered call-site hits in structured form in a
  run-scoped HotReloadRunStaleSignatureWarnings instead of formatting text.
  BuildResult drops every caller whose (assembly, label) is active when the
  run ends, before de-duplicating the display by wire key, and keeps the
  warning text unchanged. Reading the state at the end also keeps a caller
  that a later group peeled back to its compiled body.
- HotReloadActivePatchInfo carries the declaring assembly name, so a method
  with the same label in another assembly is not taken for the caller.
- Known limits stay on the safe or documented side: a patched body that
  still calls the removed method is not detected (shims are not scanned),
  and callers inside lambdas or local functions keep compiler-generated
  names and stay listed. The return-type gate is unchanged and now pinned:
  a caller in another assembly still gates the change even when patched.

Tests: 14 unit tests for the run-scoped filter, a coverage test that the
collection keeps one hit per assembly-qualified caller, and 7 end-to-end
cases (caller patched in an earlier or later group, host-only rerun with an
active or unpatched caller, caller peeled later in the same run, active
caller still calling the removed method, return-type change stays gated).
Turning the filter off, ignoring the assembly, de-duplicating before
filtering, or de-duplicating the collection by wire key each fails the
expected tests.
The hot reload docs said a return-type change applies once this or an
earlier reload patched every compiled caller. A caller in another assembly
still gates the change even after being patched, because its patch is
compiled against the compiled assembly, where the old signature still
exists; an end-to-end test now pins that. The SKILL.md summary and the gate
paragraph now limit the "already patched" case to the same assembly.

The paragraph on the warning for renamed, re-parameterized, or deleted
methods now says a caller whose patch is active when the reload ends is
left out, and names the limits: what the patched body calls is not checked,
a copy the JIT inlined or a delegate created before the patch can still
reach the old method, and a call inside a lambda or local function stays
listed under its compiler-generated name.

SKILL.md stays at 7,996 of 8,000 bytes. The generated copies under .claude
and .agents are regenerated and match the source byte for byte.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: hatayama/unity-cli-loop/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 8853752c-8512-471f-ab90-d65f030cd823

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2611f and 676f209.

⛔ Files ignored due to path filters (6)
  • Assets/Tests/Editor/HotReload/HotReloadCrossAssemblyStaleSignatureE2ETests.cs.meta is excluded by none and included by none
  • Assets/Tests/Editor/HotReload/HotReloadCrossAssemblyStaleSignatureHost.cs.meta is excluded by none and included by none
  • Assets/Tests/Editor/HotReload/HotReloadRunStaleSignatureWarningsTests.cs.meta is excluded by none and included by none
  • Assets/Tests/Editor/HotReloadCallSiteCrossAssembly/HotReloadCrossAssemblyStaleSignatureCaller.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadRunStaleSignatureWarnings.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadStaleSignatureCallSites.cs.meta is excluded by none and included by none
📒 Files selected for processing (17)
  • .agents/skills/uloop-hot-reload/references/scope-and-limits.md
  • .claude/skills/uloop-hot-reload/references/scope-and-limits.md
  • Assets/Tests/Editor/HotReload/HotReloadGroupProcessorTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeOutcomeSinkTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadOrchestratorTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadRetainedTypePatchReverterTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSignatureChangeCoverageTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadUnchangedPatchPeelTests.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadFileSinks.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupProcessor.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadInputFileResolver.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadRunAccumulator.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSiblingRebindReporter.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeCoverage.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeGate.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Patching/HotReloadDomain.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Skill/references/scope-and-limits.md

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


📝 Walkthrough

Walkthrough

The signature gate now retains stale call-site records for run-level processing. At the end of a reload, warning generation filters callers against active patches, using assembly and method labels. Documentation and tests describe and check the gate and warning behavior.

Changes

Stale signature call-site capture

Layer / File(s) Summary
Capture call-site records and apply the signature gate
Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeCoverage.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeGate.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadStaleSignatureCallSites.cs, Assets/Tests/Editor/HotReload/HotReloadSignatureChangeCoverageTests.cs, Assets/Tests/Editor/HotReload/HotReloadGroupProcessorTests.cs
The gate passes stale call-site records instead of preformatted warning strings. The coverage tests check distinct callers across assemblies and omit removed signatures without uncovered callers. The gate guidance states that cross-assembly callers block return-type changes.

Run-level warning filtering

Layer / File(s) Summary
Generate warnings using active patches at run end
Packages/src/Editor/FirstPartyTools/HotReload/HotReloadRunStaleSignatureWarnings.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadRunAccumulator.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadFileSinks.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupProcessor.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadInputFileResolver.cs, Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSiblingRebindReporter.cs, Packages/src/Editor/FirstPartyTools/HotReload/Patching/*
Call-site records are collected across groups and formatted after groups finish. The warning collector filters callers using active patch assembly and method labels, then de-duplicates remaining caller keys.
Exercise cross-assembly caller and warning cases
Assets/Tests/Editor/HotReload/HotReloadCrossAssemblyStaleSignatureE2ETests.cs, Assets/Tests/Editor/HotReload/HotReloadCrossAssemblyStaleSignatureHost.cs, Assets/Tests/Editor/HotReloadCallSiteCrossAssembly/HotReloadCrossAssemblyStaleSignatureCaller.cs, Assets/Tests/Editor/HotReload/HotReloadRunStaleSignatureWarningsTests.cs, Assets/Tests/Editor/HotReload/*Tests.cs
Tests cover caller and host group order, earlier active patches, unpatched or peeled callers, and a cross-assembly return-type change. Unit tests check filtering and warning formatting. The skill references describe the corresponding gate and warning rules.

Priority: ⬇️ Low

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

Change: Bug fix · Severity of issue fixed: Low

Possibly related PRs

Merge Risk: ⚪ Minimal · up to 676f2

Hot reload's stale-signature warning now omits callers already patched, including callers in other assemblies, and still gates return-type changes. The change is well covered by unit and end-to-end tests, and no merge-blocking risk was identified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 676f2

The change affects warning selection, not permission to patch code. Assembly-qualified caller matching and return-type compatibility checks are preserved. Behavior during overlapping or interrupted reloads is not fully established, so the assessment retains some uncertainty.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected change affects advisory reporting within the existing Unity Editor patch domain. It does not demonstrate added code-execution authority or exposure to additional tenants, services, or data stores.

Trust Boundaries and Controls

  • observed — Signature compatibility enforcement precedes warning collection and is independent of final filtering. An active cross-assembly caller patch can suppress a stale-caller warning without permitting a return-type replacement that the gate rejects; an end-to-end test asserts that distinction.

Resilience and Maintainability Implications

  • observed — Each invocation creates its own accumulator but uses shared patch services. The inspected apply entrypoint and orchestrator do not establish single-flight serialization. Upstream scheduling remains unverified, so the consistency of warning snapshots during overlapping apply or revert operations is unresolved.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 86 functions across 21 files. (3 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: hot reload no longer warns about callers in other assemblies after their patches are active.
Description check ✅ Passed The description directly explains the stale-signature warning fix, return-type gate behavior, implementation changes, tests, and documented limits.
Linked Issues check ✅ Passed The PR satisfies the coding requirements in #2896. HotReloadRunStaleSignatureWarnings records stale call sites for the full run, matches active patches by assembly and method label, removes active c…
Out of Scope Changes check ✅ Passed The changes stay within #2896. Production changes implement run-scoped warning collection, active-patch data propagation, caller filtering, and de-duplication. Tests verify the warning and gate behavi…
Full details: Docstring Coverage

Explanation

Docstring coverage is 41.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 86 functions across 21 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

Comment @coderabbitai help to get the list of available commands.

Base automatically changed from fix/hot-reload-generic-parameter-labels to main September 29, 2026 23:51
@hatayama
hatayama merged commit adc73c8 into main Sep 30, 2026
16 checks passed
@hatayama
hatayama deleted the fix/hot-reload-cross-assembly-covered-callers branch September 30, 2026 00:01
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.

Hot reload keeps listing a caller in another assembly as an unpatched compiled call site after that caller was patched

1 participant