Map derived instances and array elements in generated code as the engine does - #103
Conversation
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 26 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Interface-typed members and collection elements still violate the documented runtime-lane behavior.
Review effort: Balanced
Findings: 1
What changed in this PR
Aligns source-generated mappings with runtime behavior for derived instances and array elements.
Changes:
- Dispatches derived source instances to the runtime mapper.
- Copies class elements when converting arrays to lists.
- Adds conformance tests and documents NativeAOT behavior.
| File | Description |
|---|---|
src/Mapsicle.SourceGen/MapperGenerator.cs |
Adds runtime-type guards and element mapping. |
tests/Mapsicle.SourceGen.Tests/LaneConformanceTests.cs |
Adds polymorphism and aliasing coverage. |
README.md |
Documents derived-type fallback behavior. |
changelog.d/sourcegen-runtime-types.fixed.md |
Records the fixes and NativeAOT impact. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Generated code planned every nested member, collection element and extension receiver from its declared type. A derived instance was mapped as the base, where the engine maps by the runtime type.
What changed in the emitter:
source.GetType()and hands anything that is not exactly the declared type toMapper.MapTo<TDest>((object)source). A sealed type or a struct gets no check.MapToextension does the same for the declared source, soAnimal a = new Dog(); a.MapTo<PetDto>()no longer dropsBreed. This fourth case is not in the issue. It showed up while writing the rows.List<T>of the same class maps each element through a nested helper instead of passing the instance through. If that element class cannot be generated, the pair is refused withMSG001.Proof:
LaneConformanceTests. Four fail on main (nested member, list and array elements,Tag[]intoList<Tag>, derived instance through the extension). The other three are controls: the declared type itself, a null member, and a cycle that only a derived type adds, which ends at the same depth in both lanes.TheTableCoversEveryPairTheAssemblyDeclareschecks, andEveryDeclaredPairWasActuallyGeneratedpasses, so the rows compare generated code against the engine and not the engine against itself.dotnet test Mapsicle.sln -c Release: all green on net8.0 and net10.0 (SourceGen 95, core 532, allocation budgets 10).--bandon an M1, benchmark types not sealed so the check is in the measured path: 0.94 before, 0.97 and 0.97 after, 792 B on both sides each time. The before run was noisy (hand written at plus or minus 6 ns).One behaviour to know about: under NativeAOT a derived instance whose own pair is not declared now throws
NotSupportedExceptionfrom the engine, where it used to come back with the base members only. The README says to declare the derived pair.Review ran in the same session as the change, so finder and judge shared a context.
After review: a collection whose destination element is an interface (
IThing[]orThing[]intoList<IThing>) is now refused with MSG001. Generated code put the source instances in the list and the engine leaves each element null.InterfaceElementPairsAreRefusedAndMapAsTheEngineDoesfailed before the change. No emitted code changes for any pair that is still generated, so the band numbers above stand.Closes #73