feat(extractor): silence multi-arg self-WhenAnyValue (precision dig + CI sample)#47
Conversation
…sion) Investigate the "residual self-WhenAnyValue FP" caveat on the WalletWasabi oracle run by checking the real target source. Finding: most own-only nested findings (x => x.Settings.Foo) are REAL leaks — Settings is a constructor-injected, long-lived ApplicationSettings that keeps the transient view-model alive, result ignored — so the classifier's conservatism is correct and the differentiation is stronger than the caveat implied. The genuine residual FP is the narrow multi-arg single-hop own case (x => x.A, x => x.B), which the classifier misses only because it requires Arguments.Count == 1. Records the exact fix (generalise IsSelfRootedWhenAny to >=1 single-hop self selectors, no System.Linq) and the coverage gap: no WhenAnyValue sample exists, so the classifier is CI-untested and the fix must land with a wpf-extractor sample (no local dotnet to validate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
Implements the fix from docs/notes/self-whenany-precision.md. The ignored- .Subscribe() source classifier silenced only a single-arg single-hop self selector (this.WhenAnyValue(p => p.Member)); a multi-arg self chain (this.WhenAnyValue(x => x.A, x => x.B)) over the component's OWN single-hop properties is the SAME collectable self-cycle but was wrongly flagged — a residual false positive confirmed on the WalletWasabi oracle run. - Program.cs: generalise IsSelfRootedWhenAny from one single-hop self selector to one-or-more (IsSelfMemberSelector + a loop; no System.Linq, which isn't imported). A nested path (p => p.A.B — can observe an injected object) and a result-combiner overload (..., (a, b) => ...) stay flagged. - Add frontend/roslyn/samples/WhenAnyValueViewModel.cs and wire it into the wpf-extractor CI job. It is the first WhenAnyValue sample, so the classifier finally gets a CI regression pin: single-arg AND multi-arg self silenced; a nested injected path and an external combinator flagged (exactly 2 OWN001). Bridge-side validated locally (the facts the extractor emits -> 2 OWN001, the three self chains silent, "x => x.B" never surfaces); the C# classifier change is validated by the wpf-extractor job (no local dotnet). ci.yml parses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughExtends ChangesWhenAnyValue Self-Cycle Precision
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Digs into the "residual self-
WhenAnyValueFP" caveat from the WalletWasabi oracle run, then fixes the one genuine FP it found.The dig (docs/notes/self-whenany-precision.md)
The own-only WhenAnyValue findings looked like they might include self-cycle FPs. Checking the real WalletWasabi source flips that:
BitcoinTabSettingsViewModel.Settingsis a constructor-injected, long-livedApplicationSettings, sothis.WhenAnyValue(x => x.Settings.Foo).Subscribe(result ignored) attaches a handler to the long-lived object and keeps the transient VM alive — a real leak. So most own-only nested findings are real leaks, and the classifier's conservatism (not silencing nested paths, which can run through an injected object) is correct. The differentiation is stronger than the caveat implied.The one genuine residual FP is narrow: multi-arg, single-hop own —
this.WhenAnyValue(x => x.A, x => x.B).Subscribe, where both selectors are the component's own properties. That observes onlythis→ a collectable self-cycle, but the classifier missed it solely because it requiredArguments.Count == 1.The fix
Program.cs: generaliseIsSelfRootedWhenAnyfrom one single-hop self selector to one-or-more (IsSelfMemberSelector+ a loop; noSystem.Linq, which isn't imported). A nested path (p => p.A.B) and a result-combiner overload (…, (a, b) => …) stay flagged (conservative — they can observe an injected object).frontend/roslyn/samples/WhenAnyValueViewModel.csand wire it into thewpf-extractorCI job — the firstWhenAnyValuesample, so this classifier finally gets a CI regression pin: single-arg and multi-arg self silenced; a nested injected path and an external combinator flagged (exactly 2 OWN001).Validation
The bridge half + the assertion logic are validated locally (the facts the extractor emits → 2 OWN001, the three self chains silent,
x => x.Bnever surfaces). The C# classifier change has no local .NET SDK, so it is validated by thewpf-extractorjob — which is exactly why the sample lands with it.ci.ymlparses.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
Generated by Claude Code
Summary by CodeRabbit
Refactor
Documentation
Tests