Skip to content

feat: add AOT-safe ViewLocator with source-generated view dispatch#15

Merged
glennawatson merged 2 commits intomainfrom
feature/iviewforbinding
Mar 9, 2026
Merged

feat: add AOT-safe ViewLocator with source-generated view dispatch#15
glennawatson merged 2 commits intomainfrom
feature/iviewforbinding

Conversation

@glennawatson
Copy link
Contributor

@glennawatson glennawatson commented Mar 9, 2026

Summary

This PR ports ReactiveUI's ViewLocator system into ReactiveUI.Binding, replacing the reflection-based MakeGenericType approach with a fully AOT-safe, source-generated alternative.

In ReactiveUI, DefaultViewLocator.ResolveView(object) uses typeof(IViewFor<>).MakeGenericType(vmType) to resolve views at runtime. This breaks under AOT compilation and trimming — a problem for RoutedViewHost and ViewModelViewHost across all platforms (WPF, MAUI, WinForms, Apple/iOS) where the view model type is only known at runtime (e.g., navigation stacks with IRoutableViewModel).

The source generator now scans for all IViewFor<T> implementations at compile time and generates a type-switch dispatch function. At runtime, DefaultViewLocator.ResolveView(object) calls this generated dispatch first — no reflection, no MakeGenericType, fully AOT and trimming safe. Views are resolved through a three-tier strategy:

  1. Generated dispatch — the compile-time type-switch tries the service locator for each known IViewFor<T>, then falls back to direct construction if the view has an accessible parameterless constructor.
  2. Explicit mappings — runtime Map<TVM, TView>() registrations via the fluent ConfigureViewLocator builder API.
  3. Service locator fallback — the existing Splat-based resolution as a last resort.

The builder integration follows the same pattern as the rest of ReactiveUI.Binding — ConfigureViewLocator is available on both IReactiveUIBindingBuilder and as an IAppBuilder extension via BuilderMixins, supporting fluent configuration like:

builder
    .WithCoreServices()
    .ConfigureViewLocator(mappings => mappings.Map<MyViewModel, MyView>())
    .BuildApp();

Tests

  • Add 7 snapshot tests for ViewLocatorDispatchGenerator covering single/multiple views, abstract exclusion, private/missing constructors, deduplication, and non-IViewFor classes.
  • Add 31 runtime tests for DefaultViewLocator, ViewLocator, ViewMappingBuilder, ViewLocatorNotFoundException, and builder integration.
  • Add TestExecutor infrastructure following ReactiveUI's executor pattern for clean test isolation.

## New Features
- Add IViewLocator, DefaultViewLocator, ViewLocator, and ViewMappingBuilder runtime types supporting three-tier view resolution: generated dispatch → explicit mappings → service locator fallback.
- Add source generator that scans IViewFor<T> implementations at compile time and generates a type-switch dispatch function for AOT-safe view resolution.
- Add ViewRegistrationExtractor and ViewRegistrationInfo model for extracting view/view-model pairs from the compilation.
- Integrate ConfigureViewLocator into the builder pattern via IReactiveUIBindingBuilder, ReactiveUIBindingBuilder, and BuilderMixins.

## Tests
- Add 7 snapshot tests for ViewLocatorDispatchGenerator covering single/multiple views, abstract exclusion, private/missing constructors, deduplication, and non-IViewFor classes.
- Add 31 runtime tests for DefaultViewLocator, ViewLocator, ViewMappingBuilder, ViewLocatorNotFoundException, and builder integration.
- Add TestExecutor infrastructure following ReactiveUI's executor pattern for clean test isolation.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an AOT-safe ViewLocator system to ReactiveUI.Binding, replacing the reflection-based MakeGenericType approach with a source-generated type-switch dispatch. The implementation follows a three-tier resolution strategy: compile-time generated dispatch, explicit runtime Map<TVM, TView>() mappings, and Splat service locator fallback.

Changes:

  • Adds DefaultViewLocator, ViewLocator, IViewLocator, ViewMappingBuilder, and ViewLocatorNotFoundException to the runtime library, with builder integration via ConfigureViewLocator on both IReactiveUIBindingBuilder and IAppBuilder extension mixins.
  • Adds a new source generator pipeline (Pipeline C) in ViewLocatorDispatchGenerator that scans IViewFor<T> implementations at compile time and generates a type-switch dispatch method in ViewDispatch.g.cs.
  • Adds comprehensive tests: 7 snapshot tests for the generator and 31+ runtime tests for DefaultViewLocator, ViewLocator, ViewMappingBuilder, exception handling, and builder integration, using the project's TestExecutor infrastructure.

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/ReactiveUI.Binding/View/DefaultViewLocator.cs Core three-tier view resolution implementation
src/ReactiveUI.Binding/View/ViewLocator.cs Static accessor for the current IViewLocator
src/ReactiveUI.Binding/View/ViewLocatorNotFoundException.cs Custom exception for missing IViewLocator
src/ReactiveUI.Binding/View/ViewMappingBuilder.cs Fluent builder for registering view-to-VM mappings
src/ReactiveUI.Binding/Interfaces/IViewLocator.cs IViewLocator interface with generic and non-generic overloads
src/ReactiveUI.Binding/Builder/IReactiveUIBindingBuilder.cs Adds ConfigureViewLocator to the builder interface
src/ReactiveUI.Binding/Builder/ReactiveUIBindingBuilder.cs Implements ConfigureViewLocator and registers default IViewLocator in WithCoreServices
src/ReactiveUI.Binding/Mixins/BuilderMixins.cs Adds ConfigureViewLocator extension for IAppBuilder
src/ReactiveUI.Binding.SourceGenerators/Generators/ViewLocatorDispatchGenerator.cs Source generator that produces ViewDispatch.g.cs
src/ReactiveUI.Binding.SourceGenerators/Helpers/ViewRegistrationExtractor.cs Extracts IViewFor implementations from syntax
src/ReactiveUI.Binding.SourceGenerators/Models/ViewRegistrationInfo.cs POCO model for detected view registrations
src/ReactiveUI.Binding.SourceGenerators/Constants.cs Adds IViewForGenericMetadataName constant
src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs Registers Pipeline C in the generator
src/tests/.../ViewLocatorDispatchGeneratorTests.cs 7 snapshot tests for the generator
src/tests/.../DefaultViewLocatorTests.cs Runtime tests for DefaultViewLocator
src/tests/.../ViewLocatorTests.cs Tests for ViewLocator static accessor
src/tests/.../ViewLocatorNotFoundExceptionTests.cs Tests for exception constructors
src/tests/.../ViewMappingBuilderTests.cs Tests for ViewMappingBuilder
src/tests/.../Builder/ReactiveUIBindingBuilderTests.cs Tests for ConfigureViewLocator builder integration
src/tests/.../Builder/BuilderMixinsTests.cs Tests for ConfigureViewLocator mixin
src/tests/.../TestExecutors/* Test executor infrastructure for builder state isolation
src/tests/.../Helpers/TestHelper.cs Adds VDG abbreviation for snapshot tests
src/tests/.../*.verified.cs Verified snapshot files for generator output

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Update BindingGenerator XML doc to mention all three pipelines (A, B, C)
- Make DefaultViewLocator _lock instance-scoped instead of static since it only guards the per-instance _mappings dictionary
@glennawatson glennawatson enabled auto-merge (squash) March 9, 2026 17:12
@codecov
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (0281c27) to head (e0995ea).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main       #15    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          121       125     +4     
  Lines         2014      2116   +102     
  Branches       375       395    +20     
==========================================
+ Hits          2014      2116   +102     

☔ View full report in Codecov by Sentry.
📢 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.

@glennawatson glennawatson merged commit bab552d into main Mar 9, 2026
6 checks passed
@glennawatson glennawatson deleted the feature/iviewforbinding branch March 9, 2026 19:06
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.

3 participants