Skip to content

fix: generic and runtime target type mappings with derived types#2230

Merged
latonz merged 1 commit into
riok:mainfrom
justi7n:fix/polymorphic-mappings
Jun 19, 2026
Merged

fix: generic and runtime target type mappings with derived types#2230
latonz merged 1 commit into
riok:mainfrom
justi7n:fix/polymorphic-mappings

Conversation

@justi7n

@justi7n justi7n commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Fix generic and runtime target type mappings with derived types

Description

If a user defined generic type or runtime target type mapping has a child mapping method which uses the [MapDerivedType] attribute, the parent method switch arm when clause was created wrongly (but only in that specific case). Fixes #1989

This got fixed in this PR

Fixes # (issue)

Checklist

  • I did not use AI tools to generate this PR, or I have manually verified that the code is correct, optimal, and follows the project guidelines and architecture
  • I understand that low-quality, AI-generated PRs will be closed immediately without further explanation
  • The existing code style is followed
  • The commit message follows our guidelines
  • Performed a self-review of my code
  • Hard-to-understand areas of my code are commented
  • The documentation is updated (as applicable)
  • Unit tests are added/updated
  • Integration tests are added/updated (as applicable, especially if feature/bug depends on roslyn or framework version in use)

@justi7n justi7n added the bug Something isn't working label Apr 13, 2026
@justi7n
justi7n requested a review from latonz April 13, 2026 09:59
@justi7n
justi7n force-pushed the fix/polymorphic-mappings branch from a7e108c to 32ee630 Compare April 13, 2026 11:53
Comment thread test/Riok.Mapperly.Tests/Mapping/RuntimeTargetTypeMappingTest.cs
Comment thread test/Riok.Mapperly.Tests/Mapping/RuntimeTargetTypeMappingTest.cs
Comment thread src/Riok.Mapperly/Descriptors/UserMethodMappingExtractor.cs
@justi7n

justi7n commented Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

Example for clarification:

Lets assume we have BananaDto : FruitDto, and FruitDto : OrganicDto. The previously released code in v4.3.1 would generate a code like

    public partial class DerivedTypeMapper
    {
        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.3.1.0")]
        public partial T MapGeneric<T>(object source)
        {
            return source switch
            {
                // --- Relevant Part ---
                global::MapperlyRepro.Fruit x when typeof(T).IsAssignableFrom(typeof(global::MapperlyRepro.FruitDto)) => (T)(object)MapDerivedTypes(x),
                _ => throw new global::System.ArgumentException($"Cannot map {source.GetType()} to {typeof(T)} as there is no known type mapping", nameof(source)),
            };
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.3.1.0")]
        public partial global::MapperlyRepro.FruitDto MapDerivedTypes(global::MapperlyRepro.Fruit source)
        {
            return source switch
            {
                global::MapperlyRepro.Banana x => MapToBananaDto(x),
                _ => throw new global::System.ArgumentException($"Cannot map {source.GetType()} to MapperlyRepro.FruitDto as there is no known derived type mapping", nameof(source)),
            };
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.3.1.0")]
        private global::MapperlyRepro.BananaDto MapToBananaDto(global::MapperlyRepro.Banana source)
        {
               ...
        }
    }

by

[Mapper]
public partial class DerivedTypeMapper
{
    public partial T MapGeneric<T>(object source);

    [MapDerivedType<Banana, BananaDto>]
    public partial FruitDto MapDerivedTypes(Fruit source);
}

So previously, the mapping would work for:

  • Banana => BananaDto.
  • Banana => FruitDto,
  • Banana => OrganicDto

If we reverse the AssignableFrom argument order (like currently in this PR), we would reverse the order of what would work:

  • Banana => BananaDto
  • Banana => FruitDto
  • Banana => OrganicDto

So previously, if someone called derivedTypeMapper.MapGeneric<BananaDto>(new Banana()) he would get an exception. After this PR this works and someone gets an exception after calling derivedTypeMapper.MapGeneric<OrganicDto>(new Banana()) (because the underlying mapping method is for FruitDto, if it should work for OrganicDto it has to be defined as public partial OrganicDto MapDerivedTypes(Organic source)

@justi7n
justi7n force-pushed the fix/polymorphic-mappings branch 2 times, most recently from 890f09f to 73028da Compare June 12, 2026 11:01
@justi7n
justi7n force-pushed the fix/polymorphic-mappings branch from 73028da to adbacf4 Compare June 12, 2026 11:10
@justi7n
justi7n requested a review from latonz June 12, 2026 11:44
@latonz
latonz merged commit 47d75a5 into riok:main Jun 19, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

polymorphic mappings via [MapDerivedType] on child [UseMapper] classes fail

2 participants