Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generic mappings #1285

Open
trejjam opened this issue May 13, 2024 · 2 comments
Open

Support generic mappings #1285

trejjam opened this issue May 13, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@trejjam
Copy link
Contributor

trejjam commented May 13, 2024

Is your feature request related to a problem? Please describe.

Mapping definition

public partial IReadOnlyCollection<long> Map(IReadOnlyCollection<int> source);

produces nice mapping using an array:

var target = new long[source.Count];
var i = 0;
foreach (var item in source)
{
    target[i] = (long)item;
    i++;
}
return target;

When you change it to generic:

public partial IReadOnlyCollection<T> MapItems<T>(IReadOnlyCollection<A> source) where T : class;
public partial T MapItem<T>(A source) where T : class;
public partial B MapToB(A source);

public record A(string Value);
public record B(string Value);

it refuses to generate array mapping.

Describe the solution you'd like

public partial IReadOnlyCollection<T> MapItems<T>(IReadOnlyCollection<A> source)
{
  var target = new T[source.Count];
  var i = 0;
  foreach (var item in source)
  {
      target[i] = MapItem(item);
      i++;
  }
  return target;
}

Additional context
Failing test

Workaround:
Add another (specific-non-generic) mapping, then generic arm-search finds a match. The downside is that you need to define it for all generic arguments (which makes it ugly)

public partial IReadOnlyCollection<B> MapItems(IReadOnlyCollection<A> source);
@trejjam trejjam added the enhancement New feature or request label May 13, 2024
@trejjam
Copy link
Contributor Author

trejjam commented May 13, 2024

If you can point me to the solution, I can implement it.

@latonz
Copy link
Contributor

latonz commented May 16, 2024

Thanks for opening this. It seems to be somewhat related to #884 and #451.
IMO we should try to implement support for mappings with open generics no matter whether it is a collection or not. However, this is not a trivial task as probably a lot of different parts of the codebase need to be touched.

  • Introduce new generic mappings (in Riok.Mapperly.Descriptors.Mappings)

  • Extract them while discovering user mappings (in Riok.Mapperly.Descriptors.UserMethodMappingExtractor)

  • If a mapping is needed and one cannot be found, try to match one of the discovered generic mappings (make sure the signature matches and no type constraints are validated) (in Riok.Mapperly.Descriptors.MappingCollection and callers)

  • Include the generic mappings in the currently supported generic mappings (probably not in the first iteration as this could get really complicated) (in Riok.Mapperly.Descriptors.MappingBodyBuilders.RuntimeTargetTypeMappingBodyBuilder)

    • Idea 1: resolve all possible types in the generator (as in the sample below); could get really complicated really fast, does not work with Support generic user implemented mappings #451, could result in a LOT of switch arms
    • Idea 2: match against open generic types and type constraints at runtime; would need more reflection at runtime and therefore probably also make things slower at runtime
    • Idea 3: implement both Idea 1 + 2
  • How would this work in Expression mappings?

These are the points that currently come to my mind.

Example

public partial IReadOnlyCollection<S, T> MapItems<S, T>(IReadOnlyCollection<S> source)
{
  var target = new T[source.Count];
  var i = 0;
  foreach (var item in source)
  {
      target[i] = MapItem(item);
      i++;
  }
  return target;
}

public partial T MapItem<S, T>(S source)
{
    return source switch
    {
        A x when typeof(T).IsAssignableFrom(typeof(B)) => MapToB(x),
        IReadOnlyCollection<A> x when typeof(IReadOnlyCollection<B>).IsAssignableFrom(typeof(IReadOnlyCollection<B>)) => MapItems<A, B>(x),
        _ => throw ...,
    }
}

public partial B MapToB(A source)
{
    return new B(source.Value);
}

@latonz latonz changed the title Map generic collections Support generic mappings May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants