fix(mocks): strip nullable annotation from constructor dispatch patterns - #6498
Conversation
The generated mock factory dispatches between same-arity constructor overloads with `is` patterns built from each parameter's fully qualified type. A nullable type is never legal as the type of an `is` pattern (CS8116), so any constructor taking `Uri?`, a nullable delegate, `int?`, etc. produced uncompilable code — mocking the type was impossible. Match against the underlying type instead. A trailing `?` on a value type means `Nullable<T>`, which boxes to either null or a boxed `T`, so those parameters now take the null-accepting branch alongside reference types. Fixes #6492
Greptile SummaryFixes generated constructor dispatch for nullable parameters.
Confidence Score: 5/5The PR appears safe to merge, with no actionable defects identified in the changed constructor-dispatch behavior. The generator now emits legal underlying types in nullable constructor patterns, preserves annotated casts, and adds coverage for the affected reference-type, delegate, interface, value-type, overload, and null scenarios.
|
| Filename | Overview |
|---|---|
| src/TUnit.Mocks.SourceGenerator/Builders/MockFactoryBuilder.cs | Normalizes nullable pattern types and updates null acceptance during same-arity constructor dispatch without changing invocation casts. |
| tests/TUnit.Mocks.Tests/Issue6492Tests.cs | Adds focused regression tests covering compilation, overload dispatch, nullable value boxing, null arguments, and normal mock behavior. |
Reviews (1): Last reviewed commit: "fix(mocks): strip nullable annotation fr..." | Re-trigger Greptile
ReviewVerified the fix by checking out the branch, building, and running the new tests directly ( Root-cause analysis is correct and slightly broader than the PR description suggests. The original code only null-checked non-value-type parameters: p.IsValueType
? $"constructorArgs[{i}] is {p.FullyQualifiedType}"
: $"(constructorArgs[{i}] is null or {p.FullyQualifiedType})"But Test coverage is solid: same-arity overload dispatch, nullable delegate/interface, boxed Minor, non-blocking observations (not requesting changes):
No architectural concerns — this is a narrowly-scoped, well-tested bug fix in the constructor-dispatch codegen. Nice work isolating the actual predicate ( |
Fixes #6492
Problem
T.Mock()failed to compile for any class with a constructor parameter of a nullable type, e.g.Elastic.Clients.Elasticsearch.ElasticsearchClientSettings:MockFactoryBuilder.GenerateConstructorDispatchbuilds anispattern per parameter fromMockParameterModel.FullyQualifiedType, which carries the nullable annotation.x is Uri?/x is int?are never legal C#.Fix
Strip a single trailing
?before emitting the pattern type. A trailing?on a value type meansNullable<T>— which boxes to eithernullor a boxedT— so those parameters now take the null-accepting(arg is null or T)branch alongside reference types, instead of the value-type-onlyarg is Tbranch that would have rejected a legitimatenull.The cast used to invoke the constructor keeps the annotated type, so nullability of the actual argument is unchanged.
Tests
tests/TUnit.Mocks.Tests/Issue6492Tests.cs— a class with same-arity constructor overloads (forcing the type-check dispatch path) takingUri?,string?, a nullable delegate + nullable interface, andint?+ nullable interface. Covers compilation, correct overload dispatch, boxedNullable<T>dispatch, and null arguments.Full
TUnit.Mocks.Testssuite: 1166 passed.TUnit.Mocks.SourceGenerator.Testssnapshots: 80 passed, unchanged.