diff --git a/src/Riok.Mapperly.Abstractions/AggressiveInliningMode.cs b/src/Riok.Mapperly.Abstractions/AggressiveInliningMode.cs new file mode 100644 index 00000000000..b551ec1a6f9 --- /dev/null +++ b/src/Riok.Mapperly.Abstractions/AggressiveInliningMode.cs @@ -0,0 +1,27 @@ +namespace Riok.Mapperly.Abstractions; + +/// +/// Indicates how AggressiveInlining should be applied to methods. +/// +public enum AggressiveInliningMode +{ + /// + /// Does not apply AggressiveInlining + /// + None, + + /// + /// Applies AggressiveInlining to value types + /// + ValueTypes, + + /// + /// Applies AggressiveInlining to reference types + /// + ReferenceTypes, + + /// + /// Applies AggressiveInlining to all types + /// + All, +} diff --git a/src/Riok.Mapperly.Abstractions/MapperAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperAttribute.cs index 3a129163bc3..8f2293c457f 100644 --- a/src/Riok.Mapperly.Abstractions/MapperAttribute.cs +++ b/src/Riok.Mapperly.Abstractions/MapperAttribute.cs @@ -120,4 +120,10 @@ public class MapperAttribute : Attribute /// partial methods are discovered. /// public bool AutoUserMappings { get; set; } = true; + + /// + /// Gets or sets the aggressive inlining mode. + /// Defaults to . + /// + public AggressiveInliningMode AggressiveInliningMode { get; set; } = AggressiveInliningMode.ValueTypes; } diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt index 3b12015a40a..8e2fc040865 100644 --- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt +++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt @@ -186,3 +186,10 @@ Riok.Mapperly.Abstractions.MappingTargetAttribute Riok.Mapperly.Abstractions.MappingTargetAttribute.MappingTargetAttribute() -> void Riok.Mapperly.Abstractions.MapPropertyAttribute.MapPropertyAttribute(string! source, string![]! target) -> void Riok.Mapperly.Abstractions.MapPropertyAttribute.MapPropertyAttribute(string![]! source, string! target) -> void +Riok.Mapperly.Abstractions.AggressiveInliningMode +Riok.Mapperly.Abstractions.AggressiveInliningMode.None = 0 -> Riok.Mapperly.Abstractions.AggressiveInliningMode +Riok.Mapperly.Abstractions.AggressiveInliningMode.ValueTypes = 1 -> Riok.Mapperly.Abstractions.AggressiveInliningMode +Riok.Mapperly.Abstractions.AggressiveInliningMode.ReferenceTypes = 2 -> Riok.Mapperly.Abstractions.AggressiveInliningMode +Riok.Mapperly.Abstractions.AggressiveInliningMode.All = 3 -> Riok.Mapperly.Abstractions.AggressiveInliningMode +Riok.Mapperly.Abstractions.MapperAttribute.AggressiveInliningMode.get -> Riok.Mapperly.Abstractions.AggressiveInliningMode +Riok.Mapperly.Abstractions.MapperAttribute.AggressiveInliningMode.set -> void diff --git a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs index a82b9a80dc6..e31678bace2 100644 --- a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs +++ b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs @@ -115,4 +115,10 @@ public record MapperConfiguration /// Whether to consider non-partial methods in a mapper as user implemented mapping methods. /// public bool? AutoUserMappings { get; init; } + + /// + /// Gets or sets the aggressive inlining mode. + /// Defaults to . + /// + public AggressiveInliningMode? AggressiveInliningMode { get; set; } } diff --git a/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs b/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs index d85911f0163..64298864a17 100644 --- a/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs +++ b/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs @@ -62,6 +62,11 @@ public static MapperAttribute Merge(MapperConfiguration mapperConfiguration, Map mapper.AutoUserMappings = mapperConfiguration.AutoUserMappings ?? defaultMapperConfiguration.AutoUserMappings ?? mapper.AutoUserMappings; + mapper.AggressiveInliningMode = + mapperConfiguration.AggressiveInliningMode + ?? defaultMapperConfiguration.AggressiveInliningMode + ?? mapper.AggressiveInliningMode; + return mapper; } } diff --git a/src/Riok.Mapperly/Descriptors/Mappings/MethodMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/MethodMapping.cs index 2dae8a315f2..6afade6b885 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/MethodMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/MethodMapping.cs @@ -1,7 +1,9 @@ using System.Diagnostics; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Riok.Mapperly.Abstractions; using Riok.Mapperly.Emit; using Riok.Mapperly.Emit.Syntax; using Riok.Mapperly.Helpers; @@ -96,10 +98,37 @@ public virtual MethodDeclarationSyntax BuildMethod(SourceEmitterContext ctx) return MethodDeclaration(returnType.AddTrailingSpace(), Identifier(MethodName)) .WithModifiers(TokenList(BuildModifiers(ctx.IsStatic))) .WithParameterList(parameters) - .WithAttributeLists(ctx.SyntaxFactory.GeneratedCodeAttributeList()) + .WithAttributeLists(ctx.SyntaxFactory.GeneratedCodeAttributeList().AddRange(GenerateMethodImplAttributeListIfNeeded(ctx))) .WithBody(ctx.SyntaxFactory.Block(BuildBody(typeMappingBuildContext))); } + private SyntaxList GenerateMethodImplAttributeListIfNeeded(SourceEmitterContext ctx) + { + var hasMethodImplAttribute = MethodDeclarationSyntax.HasAttribute(); + + //Skip if method already has [MethodImple] attribute + if (hasMethodImplAttribute) + return []; + + var aggressiveInliningMode = GetAggressiveInliningMode(); + + return aggressiveInliningMode switch + { + AggressiveInliningMode.ValueTypes when SourceType.IsValueType => ctx.SyntaxFactory.MethodImplAttributeList(), + AggressiveInliningMode.ReferenceTypes when SourceType.IsReferenceType => ctx.SyntaxFactory.MethodImplAttributeList(), + AggressiveInliningMode.All => ctx.SyntaxFactory.MethodImplAttributeList(), + AggressiveInliningMode.None => [], + _ => [], + }; + } + + private AggressiveInliningMode GetAggressiveInliningMode() + { + //Read value from AggressiveInliningMode property of [Mapper] attribute annotated on containing class + //I guess from SimpleMappingBuilderContext.Configuration.Mapper.AggressiveInliningMode + return AggressiveInliningMode.ReferenceTypes; + } + public abstract IEnumerable BuildBody(TypeMappingBuildContext ctx); internal void SetMethodNameIfNeeded(Func methodNameBuilder) diff --git a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Literal.cs b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Literal.cs index 6e748d85184..f86f961efda 100644 --- a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Literal.cs +++ b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.Literal.cs @@ -17,4 +17,11 @@ public static LiteralExpressionSyntax BooleanLiteral(bool b) => public static LiteralExpressionSyntax StringLiteral(string content) => LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(content)); + + public static MemberAccessExpressionSyntax EnumLiteral(Enum @enum) => + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName($"global::{@enum.GetType().FullName}"), + IdentifierName(@enum.ToString()) + ); } diff --git a/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.MethodImpl.cs b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.MethodImpl.cs new file mode 100644 index 00000000000..345e563ce0c --- /dev/null +++ b/src/Riok.Mapperly/Emit/Syntax/SyntaxFactoryHelper.MethodImpl.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Riok.Mapperly.Emit.Syntax; + +public partial struct SyntaxFactoryHelper +{ + private const string MethodImplAttributeName = "global::System.Runtime.CompilerServices.MethodImpl"; + + public SyntaxList MethodImplAttributeList() + { + return AttributeList(MethodImplAttributeName, EnumLiteral(MethodImplOptions.AggressiveInlining)); + } +} diff --git a/src/Riok.Mapperly/Helpers/MethodDeclarationSyntaxExtensions.cs b/src/Riok.Mapperly/Helpers/MethodDeclarationSyntaxExtensions.cs new file mode 100644 index 00000000000..4bf81c372fd --- /dev/null +++ b/src/Riok.Mapperly/Helpers/MethodDeclarationSyntaxExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Riok.Mapperly.Helpers; + +public static class MethodDeclarationSyntaxExtensions +{ + public static bool HasAttribute(this MethodDeclarationSyntax methodDeclarationSyntax) + where TAttribute : Attribute + { + var csharpCompilation = CSharpCompilation.Create( + assemblyName: null, + syntaxTrees: [methodDeclarationSyntax!.SyntaxTree], + references: [MetadataReference.CreateFromFile(typeof(TAttribute).Assembly.Location)] + ); + + var semanticModel = csharpCompilation.GetSemanticModel(methodDeclarationSyntax.SyntaxTree); + + var attributes = methodDeclarationSyntax.AttributeLists.SelectMany(p => p.Attributes); + + return attributes.Any(attributeSyntax => + { + var symbols = semanticModel.GetSymbolInfo(attributeSyntax).CandidateSymbols.OfType(); + return symbols.Any(p => string.Equals(p.ContainingType.ToString(), typeof(TAttribute).FullName, StringComparison.Ordinal)); + }); + } +} diff --git a/test/Riok.Mapperly.Tests/TestSourceBuilder.cs b/test/Riok.Mapperly.Tests/TestSourceBuilder.cs index 9d393a31c62..82c677ccc82 100644 --- a/test/Riok.Mapperly.Tests/TestSourceBuilder.cs +++ b/test/Riok.Mapperly.Tests/TestSourceBuilder.cs @@ -97,6 +97,7 @@ private static string BuildAttribute(TestSourceBuilderOptions options) Attribute(options.IncludedMembers), Attribute(options.PreferParameterlessConstructors), Attribute(options.AutoUserMappings), + Attribute(options.AggressiveInliningMode), }.WhereNotNull(); return $"[Mapper({string.Join(", ", attrs)})]"; diff --git a/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs b/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs index 43f6431b67d..3ac019cfce8 100644 --- a/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs +++ b/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs @@ -20,7 +20,8 @@ public record TestSourceBuilderOptions( MemberVisibility? IncludedMembers = null, bool Static = false, bool PreferParameterlessConstructors = true, - bool AutoUserMappings = true + bool AutoUserMappings = true, + AggressiveInliningMode? AggressiveInliningMode = null ) { public const string DefaultMapperClassName = "Mapper"; @@ -44,6 +45,9 @@ public static TestSourceBuilderOptions WithRequiredMappingStrategy(RequiredMappi public static TestSourceBuilderOptions WithMemberVisibility(MemberVisibility memberVisibility) => new(IncludedMembers: memberVisibility); + public static TestSourceBuilderOptions WithAggressiveInliningMode(AggressiveInliningMode aggressiveInliningMode) => + new(AggressiveInliningMode: aggressiveInliningMode); + public static TestSourceBuilderOptions WithDisabledMappingConversion(params MappingConversionType[] conversionTypes) { var enabled = MappingConversionType.All;