feat: generate bindings for members ReactiveUI.SourceGenerators writes by glennawatson · Pull Request #146 · reactiveui/ReactiveUI.Binding.SourceGenerators · GitHub
Skip to content

feat: generate bindings for members ReactiveUI.SourceGenerators writes - #146

Merged
glennawatson merged 1 commit into
mainfrom
feat/sourcegenerators-contract
Sep 26, 2026
Merged

glennawatson merged 1 commit into
mainfrom
feat/sourcegenerators-contract

Conversation

@glennawatson

Copy link
Copy Markdown
Contributor

Summary

Bindings over the members ReactiveUI.SourceGenerators writes are now generated, and a binding call that nothing was generated for is reported when you build.

  • Members ReactiveUI.SourceGenerators writes bind like any other. The generator reads [Reactive] (field and partial property), [ReactiveCollection], [BindableDerivedList], [ReactiveCommand] and [IReactiveObject] by metadata name, and follows that package's rules for the name, type and accessibility of what each writes. WhenAnyValue, WhenAny, Bind, OneWayBind, BindCommand, ToProperty and the rest now dispatch for those members in both runtime flavours, where they used to throw at run time.
  • RXUIBIND021 (Warning) reports a binding call with no generated binding. It fires on any call that still resolves to a throwing runtime stub and that no interceptor claims, whatever the cause: a member another source generator adds, a stored lambda, an unreachable type. The call throws when it runs, so the build now says so.
  • New docs page sourcegenerators observes, binds and runs the members a [Reactive] field, [IReactiveObject] and [ReactiveCommand] produce.
  • UnsafeSetupExamples.PlainCallWithStoredPathThrows is now PlainCallNeedsAPathWrittenInTheCall. It called a stub on purpose to show the exception, which RXUIBIND021 now reports; it names the warning and shows the path the generator reads instead.

Why

No generator sees another generator's output. A call such as vm.WhenAnyValue(x => x.Nickname), where Nickname comes from a [Reactive] field, named a member that did not exist in the compilation this generator reads, so nothing was generated, no diagnostic was reported, and the call threw at run time. A class marked [IReactiveObject] looked as if it raised no notification. ReactiveUI.Core now depends on ReactiveUI.SourceGenerators (reactiveui/ReactiveUI#4516), so this is the common case rather than an edge.

Breaking changes

None. Calls that used to throw at run time now either run or are reported by RXUIBIND021.

How this was verified

  • ReactiveUI.SourceGenerators' own generators run beside this one in the tests, against 4.0.0 in one test project and 4.0.1 in the other, and a scenario covering every attribute is compiled, loaded and executed for both runtime flavours, with and without interceptors. Declaration tests pin each naming and typing rule, including every setter and access modifier and each command shape.
  • Analyzer tests cover a member added by an unknown generator, stored lambdas, ToProperty, extension-block stubs, and the methods that do their own work. The full solution builds with the new rule and reports nothing outside the one rewritten example.
  • Ran the Linux examples suite and the Windows examples on a real Windows machine.

Notes for the reviewer

  • Start with Generators/SourceGeneratorsCompilation.cs and Helpers/SourceGeneratorsMemberExtractor.cs. The pipeline declares the members that attributes produce in one never-emitted file, adds it to a private copy of the compilation, and BindingGenerator.Detect reads call sites from that copy. A compilation that uses none of the attributes gets no copy and reads call sites exactly as before.
  • Mechanical: the extractors take CallSiteContext instead of GeneratorSyntaxContext; both expose Node and SemanticModel, and the bodies are unchanged.
  • The rules mirror ReactiveUI.SourceGenerators 4.0.0. That package's maintainers confirmed 4.0.1 changes none of them. SourceGeneratorsDeclarationTests is what to update if a rule changes.
  • RXUIBIND021 on older compilers: below Roslyn 4.13 there are no interceptors, so a call resolving to the stub is the whole answer. With generated overloads (no interceptors), a call whose types match another generated call but whose lambda does not still binds to the generated overload and is not reported.

Checklist

  • I have read the Contribute guide
  • The PR title follows Conventional Commits
  • Tests cover this change, or the summary says why they do not
  • New or changed public API has XML documentation

- Read [Reactive], [ReactiveCollection], [BindableDerivedList], [ReactiveCommand] and [IReactiveObject] by metadata name.
- Declare what each writes in a private copy of the compilation that call sites are read from.
- Add RXUIBIND021: a binding call with no generated binding throws at run time.
- Add a sourcegenerators docs page, and rewrite the example that called a stub to show its exception.
@codecov

codecov Bot commented Sep 26, 2026

Copy link
Copy Markdown

@sonarqubecloud

Copy link
Copy Markdown

@glennawatson
glennawatson merged commit c2e06c3 into main Sep 26, 2026
22 checks passed
@glennawatson
glennawatson deleted the feat/sourcegenerators-contract branch September 26, 2026 14:41
@github-actions

Copy link
Copy Markdown

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Oct 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant