diff --git a/Riok.Mapperly.sln b/Riok.Mapperly.sln
index 1104608627..4312128ae0 100644
--- a/Riok.Mapperly.sln
+++ b/Riok.Mapperly.sln
@@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riok.Mapperly.Benchmarks",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{961BAABA-0672-48E7-A5B3-30A676146BE3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riok.Mapperly.Templates", "src\Riok.Mapperly.Templates\Riok.Mapperly.Templates.csproj", "{FF31D522-6A62-4466-90F7-6B297F82FCF3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riok.Mapperly.Templates.Tests", "test\Riok.Mapperly.Templates.Tests\Riok.Mapperly.Templates.Tests.csproj", "{1500A843-37E3-4DBA-8BAB-A40CF14678ED}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -59,6 +63,14 @@ Global
{F2214C71-15A7-46EB-A3AA-D02EF4B705EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2214C71-15A7-46EB-A3AA-D02EF4B705EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2214C71-15A7-46EB-A3AA-D02EF4B705EF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FF31D522-6A62-4466-90F7-6B297F82FCF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FF31D522-6A62-4466-90F7-6B297F82FCF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FF31D522-6A62-4466-90F7-6B297F82FCF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FF31D522-6A62-4466-90F7-6B297F82FCF3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1500A843-37E3-4DBA-8BAB-A40CF14678ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1500A843-37E3-4DBA-8BAB-A40CF14678ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1500A843-37E3-4DBA-8BAB-A40CF14678ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1500A843-37E3-4DBA-8BAB-A40CF14678ED}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -71,6 +83,8 @@ Global
{C3C40A0A-168F-4A66-B9F9-FC80D2F26306} = {3598BE50-28D5-4BF4-BEA7-09E5FEA2910C}
{43A2E8E0-5A2C-45E9-84EF-CF934EC946FA} = {0FBD6C81-7E7A-4915-90D2-896F11C89FF0}
{F2214C71-15A7-46EB-A3AA-D02EF4B705EF} = {961BAABA-0672-48E7-A5B3-30A676146BE3}
+ {FF31D522-6A62-4466-90F7-6B297F82FCF3} = {B65AF89A-4A3B-473C-83C8-5F0CB0EED30E}
+ {1500A843-37E3-4DBA-8BAB-A40CF14678ED} = {3598BE50-28D5-4BF4-BEA7-09E5FEA2910C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BAAC5976-BEE6-440E-8DDC-90916A1001A1}
diff --git a/docs/docs/contributing/architecture.md b/docs/docs/contributing/architecture.md
index a21b727619..45c7fe2d56 100644
--- a/docs/docs/contributing/architecture.md
+++ b/docs/docs/contributing/architecture.md
@@ -8,14 +8,19 @@ description: The architecture of Mapperly.
Mapperly is an incremental .NET source generator implementation.
Source generators are explained [here](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md)
and [here](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md).
-However, the incremental source generator of Mapperly is not yet optimal (see [#72](https://github.com/riok/mapperly/issues/72)).
-## Projects
+## Solution
-Mapperly is structured in two projects.
-`Riok.Mapperly.Abstractions` includes abstractions and attributes to be used by the application code,
-this is referenced by the source generator.
-`Riok.Mapperly` includes the implementation of the source generator.
+- `benchmarks` Benchmarks to analyze the performance of the generated code and the source generator itself
+- `build` Build scripts
+- `docs` Documentation of Mapperly
+- `samples` Sample implementations of Mappers using Mapperly
+- `src` Source code of Mapperly
+ - `Riok.Mapperly` The source generator implementation
+ - `Riok.Mapperly.Abstractions` Abstractions and attributes to be used by the application code to configure Mapperly.
+ This is referenced by the source generator but is not needed at runtime.
+ - `Riok.Mapperly.Templates` Templates of code files which are embedded as resources into `Riok.Mapperly` and may be emitted during source generation depending on enabled features.
+- `test` Unit- and integration tests of Mapperly
## Flow
@@ -24,11 +29,12 @@ For each discovered `MapperAttribute` a new `DescriptorBuilder` is created.
The `DescriptorBuilder` is responsible to build a `MapperDescriptor` which holds all the mappings.
The `DescriptorBuilder` does this by following this process:
-1. Extracting the configuration from the attribute
+1. Extracting the configuration from the attributes
2. Extracting user implemented object factories
3. Extracting user implemented and user defined mapping methods.
It instantiates a `User*Mapping` (eg. `UserDefinedNewInstanceMethodMapping`) for each discovered mapping method and adds it to the queue of mappings to work on.
-4. For each mapping in the queue the `DescriptorBuilder` tries to build its implementation bodies.
+4. Extracting external mappings
+5. For each mapping in the queue the `DescriptorBuilder` tries to build its implementation bodies.
This is done by a so called `*MappingBodyBuilder`.
A mapping body builder tries to map each property from the source to the target.
To do this, it asks the `DescriptorBuilder` to create mappings for the according types.
@@ -36,7 +42,7 @@ The `DescriptorBuilder` does this by following this process:
Each of the mapping builders try to create a mapping (an `ITypeMapping` implementation) for the asked type mapping by using
one approach on how to map types (eg. an explicit cast is implemented by the `ExplicitCastMappingBuilder`).
These mappings are queued in the queue of mappings which need the body to be built (currently body builders are only used for object to object (property-based) mappings).
-5. The `SourceEmitter` emits the code described by the `MapperDescriptor` and all its mappings.
+6. The `SourceEmitter` emits the code described by the `MapperDescriptor` and all its mappings.
The syntax objects are created by using `SyntaxFactory` and `SyntaxFactoryHelper`.
The `SyntaxFactoryHelper` tries to simplify creating formatted syntax trees.
If indentation is needed,
diff --git a/docs/docs/contributing/common-tasks.md b/docs/docs/contributing/common-tasks.md
index 9967dc4450..1da4bad0d3 100644
--- a/docs/docs/contributing/common-tasks.md
+++ b/docs/docs/contributing/common-tasks.md
@@ -50,3 +50,4 @@ To support a new roslyn version via multi targeting follow these steps (see also
Mapperly Mappings use Roslyn syntax trees.
[RoslynQuoter](https://roslynquoter.azurewebsites.net/) and [SharpLab](https://sharplab.io/)
are fantastic tools to understand and work with Roslyn syntax trees.
+The `Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper` and `Microsoft.CodeAnalysis.CSharp.SyntaxFactory` classes help building these syntax trees.
diff --git a/docs/docs/getting-started/installation.mdx b/docs/docs/getting-started/installation.mdx
index eedfcf3590..a49f022f85 100644
--- a/docs/docs/getting-started/installation.mdx
+++ b/docs/docs/getting-started/installation.mdx
@@ -12,13 +12,13 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
## Add the NuGet Package to your project
-All you need to do, to install Mapperly is to add a NuGet reference pointing to the package `Riok.Mapperly`.
+All you need to do, to install Mapperly is to add a NuGet reference pointing to the package [`Riok.Mapperly`](https://www.nuget.org/packages/Riok.Mapperly).
- {``}
+ {``}
@@ -26,6 +26,8 @@ All you need to do, to install Mapperly is to add a NuGet reference pointing to
dotnet add package Riok.Mapperly
```
+Make sure to set `PrivateAssets="all" ExcludeAssets="runtime"` on the added `PackageReference`.
+
@@ -33,5 +35,13 @@ dotnet add package Riok.Mapperly
Install-Package Riok.Mapperly
```
+Make sure to set `PrivateAssets="all" ExcludeAssets="runtime"` on the added `PackageReference`.
+
+
+:::info
+`PrivateAssets="all"` ensures projects referencing this project will not also get a reference to `Riok.Mapperly`.
+`ExcludeAssets="runtime"` ensures the `Riok.Mapperly.Abstractions.dll` file is not copied to your build output
+(it is not required at runtime).
+:::
diff --git a/docs/docs/intro.md b/docs/docs/intro.md
index 102b672cd7..66eeb02125 100644
--- a/docs/docs/intro.md
+++ b/docs/docs/intro.md
@@ -39,6 +39,7 @@ For example, Mapperly can report a warning when there is an added property in a
- Mapperly does not use reflection
- Mapperly is trimming and AoT safe
- Mapperly runs at build time
+- Mapperly does not have a runtime dependency
- The generated mappings are amazingly fast with minimal memory overhead
- The generated mapping code is readable and debuggable
- No need to write and maintain boilerplate code by hand
diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
index 71b8efc981..80b1edf4ec 100644
--- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
+++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
@@ -81,10 +81,6 @@ Riok.Mapperly.Abstractions.MappingConversionType.Tuple = 32768 -> Riok.Mapperly.
Riok.Mapperly.Abstractions.MappingConversionType.Queryable = 1024 -> Riok.Mapperly.Abstractions.MappingConversionType
Riok.Mapperly.Abstractions.MapperAttribute.UseReferenceHandling.get -> bool
Riok.Mapperly.Abstractions.MapperAttribute.UseReferenceHandling.set -> void
-Riok.Mapperly.Abstractions.ReferenceHandling.Internal.PreserveReferenceHandler
-Riok.Mapperly.Abstractions.ReferenceHandling.Internal.PreserveReferenceHandler.PreserveReferenceHandler() -> void
-Riok.Mapperly.Abstractions.ReferenceHandling.Internal.PreserveReferenceHandler.SetReference(TSource source, TTarget target) -> void
-Riok.Mapperly.Abstractions.ReferenceHandling.Internal.PreserveReferenceHandler.TryGetReference(TSource source, out TTarget? target) -> bool
Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler
Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler.SetReference(TSource source, TTarget target) -> void
Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler.TryGetReference(TSource source, out TTarget? target) -> bool
diff --git a/src/Riok.Mapperly.Abstractions/ReferenceHandling/Internal/PreserveReferenceHandler.cs b/src/Riok.Mapperly.Abstractions/ReferenceHandling/Internal/PreserveReferenceHandler.cs
deleted file mode 100644
index efa5159eba..0000000000
--- a/src/Riok.Mapperly.Abstractions/ReferenceHandling/Internal/PreserveReferenceHandler.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Riok.Mapperly.Abstractions.ReferenceHandling.Internal;
-
-///
-/// A implementation
-/// which returns the same target object instance if encountered the same source object instance.
-/// Do not use directly. Should only be used by Mapperly generated code.
-/// API surface is not subject to semantic releases and may break in any release.
-///
-public sealed class PreserveReferenceHandler : IReferenceHandler
-{
- private readonly Dictionary<(Type, Type), ReferenceHolder> _referenceHolders = new();
-
- ///
- public bool TryGetReference(TSource source, [NotNullWhen(true)] out TTarget? target)
- where TSource : notnull
- where TTarget : notnull
- {
- var refHolder = GetReferenceHolder();
- return refHolder.TryGetRef(source, out target);
- }
-
- ///
- public void SetReference(TSource source, TTarget target)
- where TSource : notnull
- where TTarget : notnull => GetReferenceHolder().SetRef(source, target);
-
- private ReferenceHolder GetReferenceHolder()
- {
- var mapping = (typeof(TSource), typeof(TTarget));
- if (_referenceHolders.TryGetValue(mapping, out var refHolder))
- return refHolder;
-
- return _referenceHolders[mapping] = new();
- }
-
- private class ReferenceHolder
- {
- private readonly Dictionary