Skip to content

fix(generator): declare observation helpers for every dispatch file - #57

Merged
glennawatson merged 2 commits into
mainfrom
fix/issue-48-kvo-helper-emission
Aug 2, 2026
Merged

fix(generator): declare observation helpers for every dispatch file#57
glennawatson merged 2 commits into
mainfrom
fix/issue-48-kvo-helper-emission

Conversation

@glennawatson

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Bug fix (source generator emission and packaging).

What is the new behavior?

  • The observation helper types a KVO or WinUI source needs (__KVOObserver, __KVOObservable<T> and the WinUI equivalents) are declared once, in a dedicated ObservationHelpers.g.cs, for any compilation that detects a type requiring them. Every dispatch file can now use them, so binding a KVO type through BindTwoWay, BindOneWay, Bind, OneWayBind, BindCommand, BindInteraction, WhenAny or WhenAnyObservable compiles.
  • Declaration is driven by the detected type set rather than by call sites. A reference can only be emitted for a type the detection pipeline matched, so the declarations are provably a superset of the references and a future binding API cannot reintroduce the gap by forgetting to register itself.
  • The runtime packages ReactiveUI.Binding and ReactiveUI.Binding.Reactive now carry the generator, so referencing the runtime package alone is enough. This is what the documentation already promised.

What is the current behavior?

Closes #48

  • A compilation that used a KVO type only through a binding API, with no WhenChanged, WhenChanging or WhenAnyValue call anywhere, generated dispatch code referencing __KVOObservable<T> while nothing declared it. The build failed with CS0246: The type or namespace name '__KVOObservable<>' could not be found. Only the observation code generator declared the helpers.
  • Separately, a compilation using two of WhenChanged, WhenChanging and WhenAnyValue on a KVO type declared the helpers more than once, because the observation generator runs once per API. That produced CS0102. This was latent and is fixed by the same change.
  • Neither runtime package referenced the generator, so consumers had to add ReactiveUI.Binding.SourceGenerators by hand despite the documentation stating the generator is included automatically.

What might this PR break?

  • ReactiveUI.Binding.SourceGenerators no longer carries the generator assemblies; it keeps only its props file and is now a compatibility package. This is deliberate: shipping the generator in both runtime packages while also shipping it here caused a consumer referencing more than one of them to load the generator twice, which failed the build with CS0111, CS0101 and CS0579. Existing consumers that reference the SourceGenerators package continue to build, because the generator arrives through the runtime package they already reference. Consider deprecating or unlisting that package separately.
  • PlatformDetectionSnapshotTests.NSObject_Detected now uses the shared Apple platform stub and asserts the generated output compiles. Its previous minimal stub could snapshot helper code that does not compile.

Checklist

  • I have read the Contribute guide
  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

Verification:

  • Generator tests cover an NSObject source used through BindOneWay and BindTwoWay. Both fail on main with the reporter's exact error and pass here.
  • All three packages were packed and inspected, and three consumer projects were built against a local feed: runtime package alone (generator runs), both runtime packages together (single generation, clean build), and the reported shape of ReactiveUI.Binding.Reactive plus an NSObject source with BindTwoWay (clean build, helpers emitted).
  • Build is clean with 0 warnings and 0 errors; the full suite passes 10764/10764.

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.63%. Comparing base (c37f8ca) to head (f727861).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #57      +/-   ##
==========================================
+ Coverage   97.61%   97.63%   +0.01%     
==========================================
  Files         227      228       +1     
  Lines        7752     7772      +20     
  Branches     1062     1066       +4     
==========================================
+ Hits         7567     7588      +21     
  Misses        143      143              
+ Partials       42       41       -1     

☔ 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.

- Move the KVO and WinUI helper class declarations out of the WhenChanged
  dispatch file into ObservationHelpers.g.cs, so binding dispatch files that
  reference them compile (fixes #48)
- Key the declarations to the detected types rather than the call sites, which
  keeps them a superset of the references and stops a second observation file
  declaring them twice
- Ship the generator and analyzer in ReactiveUI.Binding and
  ReactiveUI.Binding.Reactive, matching the documented install story
- Stop packing the same assemblies in ReactiveUI.Binding.SourceGenerators: a
  second copy loads as a second generator and emits every dispatch file twice
- Cover NSObject sources and targets through BindOneWay and BindTwoWay
- Extract the declaration loop into a static AppendHelperDeclarations so it can
  be exercised without a source production context.
- Cover both paths, including a kind no plugin answers to: the pipeline cannot
  produce one, but a generator that threw on an unexpected kind would fail the
  consumer's build rather than merely generate less.
- Pin the invariant that makes that case impossible - every kind the selection
  yields resolves back to a plugin, so a new plugin whose kind did not
  round-trip would be caught rather than silently declaring nothing.
@glennawatson
glennawatson force-pushed the fix/issue-48-kvo-helper-emission branch from 804b4ad to f727861 Compare August 2, 2026 03:26
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

@glennawatson
glennawatson merged commit 244c2f2 into main Aug 2, 2026
12 checks passed
@glennawatson
glennawatson deleted the fix/issue-48-kvo-helper-emission branch August 2, 2026 04:07
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.

[Bug]: __KVOObserver not emitted

1 participant