Skip to content

fix: Multiple bug fixes and enhancements to source generator #287

Merged
glennawatson merged 10 commits intomainfrom
glennawatson/fix-tests
Jan 21, 2026
Merged

fix: Multiple bug fixes and enhancements to source generator #287
glennawatson merged 10 commits intomainfrom
glennawatson/fix-tests

Conversation

@glennawatson
Copy link
Copy Markdown
Contributor

@glennawatson glennawatson commented Jan 21, 2026

This PR addresses four major bugs and adds comprehensive test coverage for the Splat.DI.SourceGenerator. All changes maintain compatibility with the incremental generator pipeline and follow AOT-first design principles.

Fixes #282 - Analyzer false positives on non-Splat Register methods
Problem: ConstructorAnalyzer triggered SPLATDI001 warnings on ALL classes with multiple constructors, including non-Splat code like DialogManager.Register<TView, TContext>().

Root Cause: Used RegisterSymbolAction(SymbolKind.NamedType) which analyzed every class/struct in the compilation, regardless of whether it was used with Splat DI.

Solution: Changed to RegisterOperationAction(OperationKind.Invocation) to only analyze types actually registered via SplatRegistrations.Register/RegisterLazySingleton. Added IsSplatRegistrationsMethod() helper to filter invocations.

Impact: Eliminates false positives while maintaining detection of real Splat DI issues.

Tests: 5 new tests added, 114 analyzer tests passing.

Fixes #120 - Missing dependencies silently return null
Problem: Generated code used resolver.GetService(typeof(T)) which returns null when dependencies are missing, causing runtime NullReferenceException instead of clear error messages.

Solution:

  • Switched to generic GetService() API (no cast needed, better for AOT)
  • Added null-coalescing operator with descriptive InvalidOperationException
  • Error messages include dependency type name and contract (if applicable)

Generated Code Change:
Before: (IFoo)resolver.GetService(typeof(IFoo))
After: resolver.GetService() ?? throw new InvalidOperationException("Dependency 'IFoo' not registered...")

Impact: Early failure detection at registration time with clear error messages. All 342 snapshot tests updated.

Tests: All existing tests updated and verified.

Fixes #72 - IEnumerable dependency injection support
Problem: Constructor parameters of type IEnumerable were treated like regular dependencies, calling GetService(typeof(IEnumerable)) which returns null. Should call GetServices() to retrieve all registered implementations.

Solution:

  • Extended ConstructorParameter model with IsCollection and CollectionItemType fields
  • Added IEnumerable detection in ExtractConstructorParameters (similar to Lazy)
  • Generate GetServices() calls for collection parameters (returns IEnumerable, never null)

Generated Code:
resolver.GetServices() for IEnumerable parameters resolver.GetServices("contract") when using contracts

Impact: Enables injection of multiple implementations, enables common DI patterns.

Tests: 9 new tests added covering contracts and scenarios.

Validates and closes #137 - Generic type registration support
Status: Already working correctly via ToDisplayString(FullyQualifiedFormat). Added comprehensive test coverage to prevent regressions.

Tests Added:

  • Single type parameter: Register<IAppUpdateService, AppUpdateService>()
  • Multiple type parameters: Register<ICache, Cache<string, int>>()
  • Nested generics: Register<IRepository, Repository<List>>()

Impact: 27 new tests (3 scenarios × 3 contracts × 3 frameworks) confirm generic types work correctly with fully-qualified names.

Additional Improvements

  • All generated code now uses generic APIs (GetService, Register, GetServices) instead of Type-based APIs for better AOT compatibility and performance
  • Test infrastructure updated to include SplatRegistrations class in test compilations
  • All tests follow TUnit framework patterns with proper async/await usage

Breaking Changes

  1. Generated GetService calls now throw exceptions if dependencies are missing
  2. ConstructorParameter record has 2 new fields (IsCollection, CollectionItemType) - internal API
  3. Generated code uses generic GetService() and GetServices() instead of typeof() variants

Test Results

  • Total: 633 tests (36 new tests added)
  • Failed: 0
  • Succeeded: 633
  • New coverage: 5 analyzer + 4 IEnumerable + 27 generic type tests

All changes follow ReactiveUI contribution guidelines with proper formatting, XML documentation, and no LINQ in hot paths.

…yzer

This commit addresses four major bugs and adds comprehensive test coverage
for the Splat.DI.SourceGenerator. All changes maintain compatibility with
the incremental generator pipeline and follow AOT-first design principles.

Fixes #282 - Analyzer false positives on non-Splat Register methods
============================================================
**Problem**: ConstructorAnalyzer triggered SPLATDI001 warnings on ALL classes
with multiple constructors, including non-Splat code like DialogManager.Register<TView, TContext>().

**Root Cause**: Used RegisterSymbolAction(SymbolKind.NamedType) which analyzed
every class/struct in the compilation, regardless of whether it was used with Splat DI.

**Solution**: Changed to RegisterOperationAction(OperationKind.Invocation) to only
analyze types actually registered via SplatRegistrations.Register/RegisterLazySingleton.
Added IsSplatRegistrationsMethod() helper to filter invocations.

**Impact**: Eliminates false positives while maintaining detection of real Splat DI issues.

**Tests**: 5 new tests added, 114 analyzer tests passing.

Fixes #120 - Missing dependencies silently return null
=====================================================
**Problem**: Generated code used resolver.GetService(typeof(T)) which returns null
when dependencies are missing, causing runtime NullReferenceException instead of
clear error messages.

**Solution**:
- Switched to generic GetService<T>() API (no cast needed, better for AOT)
- Added null-coalescing operator with descriptive InvalidOperationException
- Error messages include dependency type name and contract (if applicable)

**Generated Code Change**:
Before: (IFoo)resolver.GetService(typeof(IFoo))
After:  resolver.GetService<IFoo>() ?? throw new InvalidOperationException("Dependency 'IFoo' not registered...")

**Impact**: Early failure detection at registration time with clear error messages.
All 342 snapshot tests updated.

**Tests**: All existing tests updated and verified.

Fixes #72 - IEnumerable<T> dependency injection support
=======================================================
**Problem**: Constructor parameters of type IEnumerable<T> were treated like
regular dependencies, calling GetService(typeof(IEnumerable<T>)) which returns null.
Should call GetServices<T>() to retrieve all registered implementations.

**Solution**:
- Extended ConstructorParameter model with IsCollection and CollectionItemType fields
- Added IEnumerable<T> detection in ExtractConstructorParameters (similar to Lazy<T>)
- Generate GetServices<T>() calls for collection parameters (returns IEnumerable<T>, never null)

**Generated Code**:
resolver.GetServices<IService>() for IEnumerable<IService> parameters
resolver.GetServices<IService>("contract") when using contracts

**Impact**: Enables injection of multiple implementations, enables common DI patterns.

**Tests**: 9 new tests added covering contracts and scenarios.

Validates #137 - Generic type registration support
==================================================
**Status**: Already working correctly via ToDisplayString(FullyQualifiedFormat).
Added comprehensive test coverage to prevent regressions.

**Tests Added**:
- Single type parameter: Register<IAppUpdateService, AppUpdateService<string>>()
- Multiple type parameters: Register<ICache, Cache<string, int>>()
- Nested generics: Register<IRepository, Repository<List<string>>>()

**Impact**: 27 new tests (3 scenarios × 3 contracts × 3 frameworks) confirm generic
types work correctly with fully-qualified names.

Additional Improvements
======================
- All generated code now uses generic APIs (GetService<T>, Register<T>, GetServices<T>)
  instead of Type-based APIs for better AOT compatibility and performance
- Test infrastructure updated to include SplatRegistrations class in test compilations
- All tests follow TUnit framework patterns with proper async/await usage

Breaking Changes
===============
1. Generated GetService calls now throw exceptions if dependencies are missing
2. ConstructorParameter record has 2 new fields (IsCollection, CollectionItemType) - internal API
3. Generated code uses generic GetService<T>() and GetServices<T>() instead of typeof() variants

Test Results
===========
- Total: 633 tests (36 new tests added)
- Failed: 0
- Succeeded: 633
- New coverage: 5 analyzer + 4 IEnumerable + 27 generic type tests

All changes follow ReactiveUI contribution guidelines with proper formatting,
XML documentation, and no LINQ in hot paths.
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 21, 2026

Codecov Report

❌ Patch coverage is 91.57895% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.03%. Comparing base (269560b) to head (6abed78).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...ncyInjection.Analyzer/Analyzers/AnalyzerHelpers.cs 87.87% 1 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #287      +/-   ##
==========================================
+ Coverage   87.85%   94.03%   +6.18%     
==========================================
  Files          14       17       +3     
  Lines         815      838      +23     
  Branches      145      145              
==========================================
+ Hits          716      788      +72     
+ Misses         58       19      -39     
+ Partials       41       31      -10     

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

- Created Microsoft.CodeAnalysis.EmbeddedAttribute.g.cs for test cases.
- Added Splat.DI.Reg.g.cs and Splat.DI.g.cs files for dependency registration in multiple test scenarios.
- Implemented service registration logic in SplatRegistrations for various test contracts.
- Included support for lazy initialization and contract-based service resolution.
- Ensured proper handling of dependencies with contract parameters in the generated code.
Copy link
Copy Markdown

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 implements multiple critical bug fixes and feature enhancements for the Splat.DI.SourceGenerator, addressing analyzer false positives, missing dependency handling, and IEnumerable injection support.

Changes:

  • Switches from reflection-based typeof() API to generic GetService<T>() for better AOT compatibility
  • Adds null-coalescing with descriptive InvalidOperationException for missing dependencies
  • Implements IEnumerable<T> parameter detection using GetServices<T>()
  • Adds comprehensive test coverage (36 new tests across 4 bug fixes)
  • Updates ConstructorParameter model with IsCollection and CollectionItemType fields

Reviewed changes

Copilot reviewed 263 out of 263 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Models/ConstructorParameter.cs Extended record with IsCollection and CollectionItemType fields for IEnumerable support
Generator.cs Refactored dependency resolution to use generic APIs, added null checks with exceptions, implemented IEnumerable detection
Constants.cs Fixed indentation of attribute class definitions (formatting only)
RegisterTests.cs Added 4 new test methods for IEnumerable, generic types, and property injection scenarios
RegisterLazySingletonTests.cs Added 2 new test methods for lazy singleton with IEnumerable and contracts
Models/*Tests.cs Updated unit tests to match new ConstructorParameter constructor signature
*.verified.cs (342 files) Updated snapshot tests reflecting new generic API usage and error handling

Comment thread src/Splat.DependencyInjection.SourceGenerator/Generator.cs Outdated
Comment thread src/Splat.DependencyInjection.SourceGenerator/Generator.cs Outdated
Comment thread src/Splat.DependencyInjection.SourceGenerator/Generator.cs Outdated
Comment thread src/Splat.DependencyInjection.SourceGenerator/Generator.cs Outdated
Comment thread src/Splat.DependencyInjection.SourceGenerator/Generator.cs Outdated
Comment thread src/Splat.DependencyInjection.SourceGenerator/Generator.cs Outdated
@glennawatson glennawatson merged commit 77bd4e9 into main Jan 21, 2026
6 checks passed
@glennawatson glennawatson deleted the glennawatson/fix-tests branch January 21, 2026 07:57
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 5, 2026

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.

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

Labels

None yet

Projects

None yet

3 participants