Skip to content

Conversation

@thomhurst
Copy link
Owner

Summary

  • Fixes MissingFieldException when using [ClassDataSource] on properties in generic base classes where the property type is the type parameter itself (e.g., public T Provider { get; init; })
  • Enables AOT-compatible property injection for generic base class patterns

Problem

When using patterns like:

public abstract class GenericBase<T> where T : class
{
    [ClassDataSource]
    public T Provider { get; init; } = default!;
}

public class ConcreteTest : GenericBase<MyProvider> { }

The generated UnsafeAccessor was incorrectly using the concrete type substitution (MyProvider) as the return type instead of the type parameter name (T), causing a MissingFieldException at runtime.

Solution

  • Added PropertyTypeAsTypeParameter field to track when a property type is a type parameter in the original definition
  • Updated the UnsafeAccessor generation to use the type parameter name (T) instead of the concrete type when the property type is a type parameter
  • Also fixed TestMetadataGenerator to use the closed generic type for backing field lookup

Test plan

  • Added CompositionPatternTests.cs with comprehensive test cases for the generic base class pattern
  • All existing source generator tests pass (424 tests)
  • All engine tests pass (286 tests)
  • Issue [Bug]: TUnit.AspNetCore execution order #4431 tests pass: 47 passed, 1 expected failure (for the composition pattern that doesn't work by design)

Fixes #4431

🤖 Generated with Claude Code

…eter types (#4431)

When a property in a generic base class has a type that is the type parameter
itself (e.g., `T Provider { get; }` in `GenericBase<T>`), the UnsafeAccessor
return type needs to be the type parameter name `T`, not the concrete type
substitution.

Changes:
- Add `PropertyTypeAsTypeParameter` field to `PropertyDataSourceModel` to track
  when a property type is a type parameter
- Detect type parameters in `ExtractPropertyModel` by checking the original
  property definition type
- Use the type parameter name in UnsafeAccessor generation for generic types
- Fix TestMetadataGenerator to use closed generic type for backing field lookup

This enables AOT-compatible property injection for patterns like:
```csharp
public abstract class GenericBase<T> where T : class
{
    [ClassDataSource]
    public T Provider { get; init; }
}

public class ConcreteTest : GenericBase<MyProvider> { }
```

Fixes #4431

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@thomhurst
Copy link
Owner Author

Summary

This PR fixes MissingFieldException when using [ClassDataSource] on init-only properties in generic base classes where the property type is a type parameter (e.g., public T Provider { get; init; }).

Critical Issues

None found ✅

The PR correctly addresses the issue by:

  1. Tracking type parameters: Added PropertyTypeAsTypeParameter to detect when a property type is a type parameter (T) vs. a concrete type
  2. Dual-mode support: Updated both source generator (PropertyInjectionSourceGenerator.cs) AND reflection mode (PropertySourceRegistry.cs, PropertySetterFactory.cs) - adheres to TUnit's critical dual-mode rule
  3. AOT-compatible approach:
    • For .NET 9+: Uses generic UnsafeAccessor classes with type parameters
    • For .NET 8: Uses reflection with closed generic types (AOT-compatible because types are known at compile time)
    • Falls back gracefully for older runtimes
  4. Snapshot tests: Only .verified.txt files committed (no .received.txt) ✅
  5. Proper incremental caching: All new fields added to PropertyDataSourceModel.Equals() and GetHashCode()

The fix in TestMetadataGenerator.cs correctly uses currentType (the closed generic type from inheritance chain) instead of property.ContainingType (the open generic definition) for backing field lookup, which ensures the reflection call works with concrete types.

Suggestions

None - the implementation is well-designed and handles the nuances of generic types correctly.

Verdict

APPROVE - No critical issues. The PR properly implements dual-mode support, maintains AOT compatibility, and follows all TUnit critical rules.

@thomhurst thomhurst merged commit 6b39087 into main Jan 16, 2026
12 of 13 checks passed
@thomhurst thomhurst deleted the fix/4431-generic-base-class-datasource branch January 16, 2026 21:04
This was referenced Jan 18, 2026
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]: TUnit.AspNetCore execution order

2 participants