feat: add view registration attributes and attribute-aware dispatch#17
Conversation
## View Registration Attributes - Add ExcludeFromViewRegistration, SingleInstanceView, and ViewContract attributes to ReactiveUI.Binding - Source generator reads attributes to skip excluded views, emit singleton caching, and generate contract-aware dispatch - Add CreateMappingBuilder() factory method on DefaultViewLocator ## Generator Improvements - Rewrite ViewLocatorDispatchGenerator to use raw string literals ($$""" pattern) - Deduplicate by (ViewModel FQN, Contract) pair instead of ViewModel FQN alone - Remove redundant IEquatable<Self> from all sealed record model types ## Test Coverage - 100% line and branch coverage maintained
eadfa83 to
a0741ec
Compare
There was a problem hiding this comment.
Pull request overview
Adds compile-time attributes and generator support to control view auto-registration/resolution (exclude, singleton caching, and contract-based dispatch), plus a fluent runtime mapping builder on DefaultViewLocator.
Changes:
- Introduces
ExcludeFromViewRegistrationAttribute,SingleInstanceViewAttribute, andViewContractAttributeinReactiveUI.Binding. - Updates the source generator pipeline to extract these attributes and emit contract-aware dispatch + optional singleton caching fields.
- Adds
DefaultViewLocator.CreateMappingBuilder()and expands unit/snapshot coverage; removes redundantIEquatable<T>from model records.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ReactiveUI.Binding/View/ViewContractAttribute.cs | Adds contract attribute for view resolution specificity. |
| src/ReactiveUI.Binding/View/SingleInstanceViewAttribute.cs | Adds marker attribute for singleton view caching generation. |
| src/ReactiveUI.Binding/View/ExcludeFromViewRegistrationAttribute.cs | Adds marker attribute to skip view auto-registration. |
| src/ReactiveUI.Binding/View/DefaultViewLocator.cs | Exposes CreateMappingBuilder() factory for fluent runtime mappings. |
| src/ReactiveUI.Binding.SourceGenerators/Constants.cs | Adds metadata name constants for new attributes. |
| src/ReactiveUI.Binding.SourceGenerators/Helpers/ViewRegistrationExtractor.cs | Extracts contract/singleton/exclusion metadata into ViewRegistrationInfo. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs | Emits contract-aware dispatch branches and singleton cache fields. |
| src/ReactiveUI.Binding.SourceGenerators/Models/ViewRegistrationInfo.cs | Extends model with Contract and IsSingleInstance. |
| src/ReactiveUI.Binding.SourceGenerators/Models/WhenAnyObservableInvocationInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/PropertyPathSegment.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/ObservableTypeInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/ObservablePropertyInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/InvocationInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/ClassBindingInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/BindingInvocationInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/BindInteractionInvocationInfo.cs | Removes redundant IEquatable<> from record. |
| src/ReactiveUI.Binding.SourceGenerators/Models/BindCommandInvocationInfo.cs | Removes redundant IEquatable<> from record. |
| src/tests/ReactiveUI.Binding.Tests/View/ViewAttributeTests.cs | Adds basic tests for new attribute types. |
| src/tests/ReactiveUI.Binding.Tests/View/DefaultViewLocatorTests.cs | Tests CreateMappingBuilder() registers mappings on the locator instance. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/ViewLocatorDispatchGeneratorTests.cs | Adds snapshot tests for exclusion, singleton, and contract dispatch generation. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithoutParameterlessConstructor#ViewDispatch.g.verified.cs | Updates snapshots for new generator output formatting/behavior. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewWithPrivateConstructor#ViewDispatch.g.verified.cs | Updates snapshots for new generator output formatting/behavior. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#ViewDispatch.g.verified.cs | Adds snapshot for contract-aware dispatch output. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#GeneratedBindingsAttributes.g.verified.cs | Snapshot for generated bindings attributes file. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ViewContractGeneratesContractDispatch#GeneratedBinderRegistration.g.verified.cs | Snapshot for generated binder registration file. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleViewForImplementation#ViewDispatch.g.verified.cs | Updates snapshot for generator formatting changes. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#ViewDispatch.g.verified.cs | Adds snapshot for singleton attribute without direct construction case. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#GeneratedBindingsAttributes.g.verified.cs | Snapshot for generated bindings attributes file. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewWithoutParameterlessCtor#GeneratedBinderRegistration.g.verified.cs | Snapshot for generated binder registration file. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#ViewDispatch.g.verified.cs | Adds snapshot for singleton-cache output. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#GeneratedBindingsAttributes.g.verified.cs | Snapshot for generated bindings attributes file. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.SingleInstanceViewGeneratesSingletonCache#GeneratedBinderRegistration.g.verified.cs | Snapshot for generated binder registration file. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.MultipleViewForImplementations#ViewDispatch.g.verified.cs | Updates snapshot for generator formatting changes. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ExclAttr#GeneratedBindingsAttributes.g.verified.cs | Snapshot for exclusion-only generation case. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.ExclAttr#GeneratedBinderRegistration.g.verified.cs | Snapshot for exclusion-only generation case. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/VDG.DuplicateViewModelsAreDeduplicated#ViewDispatch.g.verified.cs | Updates snapshot for new deduplication key and formatting changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #17 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 125 126 +1
Lines 2116 2119 +3
Branches 395 395
=========================================
+ Hits 2116 2119 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ive improvements - Group dispatch branches by ViewModel type so contract-specific checks are emitted before the default branch (prevents shadowing) - Escape contract strings with SymbolDisplay.FormatLiteral for safe codegen - Use Interlocked.CompareExchange for thread-safe singleton view caching - Fix strategyDoc for SingleInstanceView without parameterless constructor - Move [ExcludeFromViewRegistration] check inside IViewFor<T> guard to prevent EnsureNotNull crash in compilations without ReactiveUI.Binding - Add tests for grouped dispatch and contract-only dispatch (100% coverage)
…DE.md - Add View Locator section with usage examples, attribute table, and resolution strategy to README - Add BindCommand and BindInteraction to supported APIs table - Document Pipeline C (View Dispatch) in CLAUDE.md - Update project structure with View/, ViewLocatorDispatchGenerator, ViewRegistrationExtractor, and ViewRegistrationInfo
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
What's New
Three new attributes give you compile-time control over how views are registered and resolved — no runtime reflection needed.
Exclude a view from auto-registration
The source generator skips this class entirely. Useful for design-time views, test doubles, or platform-specific views you register manually.
Cache a view as a singleton
The generator emits a static field and lazy-initialization pattern so the same instance is returned on every resolution:
Register a view under a contract
The generated dispatch only matches when the caller requests that exact contract:
This lets you have multiple views for the same ViewModel, selected at resolution time:
Fluent mapping builder
DefaultViewLocatornow exposes aCreateMappingBuilder()factory for fluent runtime registration:Maintainer Notes
Attributes
ExcludeFromViewRegistrationAttribute,SingleInstanceViewAttribute,ViewContractAttributeadded toReactiveUI.Binding/View/ReactiveUI.Binding(notReactiveUI) — ReactiveUI will delete its copies and consume these viaglobal usingConstants.csSource Generator Changes
ViewRegistrationExtractorreads all three attributes duringIViewFor<T>extraction; excluded views returnnullbefore reaching the pipelineViewRegistrationInforecord gainsContract(string?) andIsSingleInstance(bool) propertiesViewLocatorDispatchGeneratordeduplicates by(ViewModel FQN, Contract)pair, emits contract-guarded dispatch branches, and emits singleton cache fields + lazy init for[SingleInstanceView]views$$"""raw string literals matching project conventionsCleanup
: IEquatable<Self>from all 10 sealed record model types (records implement this automatically)InvalidOperationExceptionHelper.EnsureNotNull(attribute types co-exist withIViewFor<T>in the same assembly, so null is unreachable)Coverage