Skip to content

Commit 6b8ae0d

Browse files
committed
.NET 8+ only, IIdentity implements IWrapperValueObject, bug fixes, trimming, warnings.
1 parent d1cd82b commit 6b8ae0d

16 files changed

+117
-51
lines changed

DomainModeling.Example/DomainModeling.Example.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net6.0</TargetFramework>
5+
<TargetFramework>net8.0</TargetFramework>
66
<AssemblyName>Architect.DomainModeling.Example</AssemblyName>
77
<RootNamespace>Architect.DomainModeling.Example</RootNamespace>
88
<Nullable>Enable</Nullable>

DomainModeling.Generator/DummyBuilderGenerator.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
4949

5050
var result = new Builder()
5151
{
52-
TypeFullyQualifiedName = type.ToString(),
53-
ModelTypeFullyQualifiedName = modelType.ToString(),
52+
TypeFullMetadataName = type.GetFullMetadataName(),
53+
ModelTypeFullMetadataName = modelType is INamedTypeSymbol namedModelType ? namedModelType.GetFullMetadataName() : modelType.ToString(),
5454
IsPartial = tds.Modifiers.Any(SyntaxKind.PartialKeyword),
5555
IsRecord = type.IsRecord,
5656
IsClass = type.TypeKind == TypeKind.Class,
@@ -91,15 +91,15 @@ private static void GenerateSource(SourceProductionContext context, (ImmutableAr
9191

9292
var concreteBuilderTypesByModel = builders
9393
.Where(builder => !builder.IsAbstract && !builder.IsGeneric) // Concrete only
94-
.GroupBy(builder => builder.ModelTypeFullyQualifiedName) // Deduplicate
95-
.Select(group => new KeyValuePair<ITypeSymbol?, string>(compilation.GetTypeByMetadataName(group.Key), group.First().TypeFullyQualifiedName))
96-
.Where(pair => pair.Key is not null)
94+
.GroupBy(builder => builder.ModelTypeFullMetadataName) // Deduplicate
95+
.Select(group => new KeyValuePair<ITypeSymbol?, string>(compilation.GetTypeByMetadataName(group.Key), compilation.GetTypeByMetadataName(group.First().TypeFullMetadataName)?.ToString()!))
96+
.Where(pair => pair.Key is not null && pair.Value is not null)
9797
.ToDictionary<KeyValuePair<ITypeSymbol?, string>, ITypeSymbol, string>(pair => pair.Key!, pair => pair.Value, SymbolEqualityComparer.Default);
9898

9999
// Remove models for which multiple builders exist
100100
{
101101
var buildersWithDuplicateModel = builders
102-
.GroupBy(builder => builder.ModelTypeFullyQualifiedName)
102+
.GroupBy(builder => builder.ModelTypeFullMetadataName)
103103
.Where(group => group.Count() > 1)
104104
.ToList();
105105

@@ -110,15 +110,15 @@ private static void GenerateSource(SourceProductionContext context, (ImmutableAr
110110
builders.Remove(type);
111111

112112
context.ReportDiagnostic("DummyBuilderGeneratorDuplicateBuilders", "Duplicate builders",
113-
$"Multiple dummy builders exist for {group.Key}. Source generation for these builders was skipped.", DiagnosticSeverity.Warning, compilation.GetTypeByMetadataName(group.Last().TypeFullyQualifiedName));
113+
$"Multiple dummy builders exist for {group.Key}. Source generation for these builders was skipped.", DiagnosticSeverity.Warning, compilation.GetTypeByMetadataName(group.Last().TypeFullMetadataName));
114114
}
115115
}
116116

117117
foreach (var builder in builders)
118118
{
119119
context.CancellationToken.ThrowIfCancellationRequested();
120120

121-
var type = compilation.GetTypeByMetadataName(builder.TypeFullyQualifiedName);
121+
var type = compilation.GetTypeByMetadataName(builder.TypeFullMetadataName);
122122
var modelType = type?.GetAttribute("DummyBuilderAttribute", Constants.DomainModelingNamespace, arity: 1) is AttributeData { AttributeClass: not null } attribute
123123
? attribute.AttributeClass.TypeArguments[0]
124124
: null;
@@ -135,15 +135,15 @@ private static void GenerateSource(SourceProductionContext context, (ImmutableAr
135135
if (type is null)
136136
{
137137
context.ReportDiagnostic("DummyBuilderGeneratorUnexpectedType", "Unexpected type",
138-
$"Type marked as dummy builder has unexpected type '{builder.TypeFullyQualifiedName}'.", DiagnosticSeverity.Warning, type);
138+
$"Type marked as dummy builder has unexpected type '{builder.TypeFullMetadataName}'.", DiagnosticSeverity.Warning, type);
139139
continue;
140140
}
141141

142142
// Require being able to find the model type
143143
if (modelType is null)
144144
{
145145
context.ReportDiagnostic("DummyBuilderGeneratorUnexpectedModelType", "Unexpected model type",
146-
$"Type marked as dummy builder has unexpected model type '{builder.ModelTypeFullyQualifiedName}'.", DiagnosticSeverity.Warning, type);
146+
$"Type marked as dummy builder has unexpected model type '{builder.ModelTypeFullMetadataName}'.", DiagnosticSeverity.Warning, type);
147147
continue;
148148
}
149149

@@ -218,7 +218,7 @@ private static void GenerateSource(SourceProductionContext context, (ImmutableAr
218218
componentBuilder.Append("// ");
219219
componentBuilder.AppendLine($" private {param.Type.WithNullableAnnotation(NullableAnnotation.None)} {memberName} {{ get; set; }} = {param.Type.CreateDummyInstantiationExpression(param.Name == "value" ? param.ContainingType.Name : param.Name, concreteBuilderTypesByModel.Keys, type => $"new {concreteBuilderTypesByModel[type]}().Build()")};");
220220

221-
concreteBuilderTypesByModel.Add(modelType, builder.TypeFullyQualifiedName);
221+
concreteBuilderTypesByModel.Add(modelType, builder.TypeFullMetadataName);
222222
}
223223

224224
if (membersByName[$"With{memberName}"].Any(member => member is IMethodSymbol method && method.Parameters.Length == 1 && method.Parameters[0].Type.Equals(param.Type, SymbolEqualityComparer.Default)))
@@ -336,8 +336,8 @@ namespace {containingNamespace}
336336

337337
private sealed record Builder : IGeneratable
338338
{
339-
public string TypeFullyQualifiedName { get; set; } = null!;
340-
public string ModelTypeFullyQualifiedName { get; set; } = null!;
339+
public string TypeFullMetadataName { get; set; } = null!;
340+
public string ModelTypeFullMetadataName { get; set; } = null!;
341341
public bool IsPartial { get; set; }
342342
public bool IsRecord { get; set; }
343343
public bool IsClass { get; set; }

DomainModeling.Generator/IdentityGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,21 +124,21 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
124124
!ctor.IsStatic && ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.Equals(underlyingType, SymbolEqualityComparer.Default)));
125125

126126
// Records override this, but our implementation is superior
127-
existingComponents |= IdTypeComponents.ToStringOverride.If(!result.IsRecord && members.Any(member =>
128-
member.Name == nameof(ToString) && member is IMethodSymbol method && method.Arity == 0 && method.Parameters.Length == 0));
127+
existingComponents |= IdTypeComponents.ToStringOverride.If(members.Any(member =>
128+
member.Name == nameof(ToString) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Arity == 0 && method.Parameters.Length == 0));
129129

130130
// Records override this, but our implementation is superior
131-
existingComponents |= IdTypeComponents.GetHashCodeOverride.If(!result.IsRecord && members.Any(member =>
132-
member.Name == nameof(GetHashCode) && member is IMethodSymbol method && method.Arity == 0 && method.Parameters.Length == 0));
131+
existingComponents |= IdTypeComponents.GetHashCodeOverride.If(members.Any(member =>
132+
member.Name == nameof(GetHashCode) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Arity == 0 && method.Parameters.Length == 0));
133133

134134
// Records irrevocably and correctly override this, checking the type and delegating to IEquatable<T>.Equals(T)
135135
existingComponents |= IdTypeComponents.EqualsOverride.If(members.Any(member =>
136136
member.Name == nameof(Equals) && member is IMethodSymbol method && method.Arity == 0 && method.Parameters.Length == 1 &&
137137
method.Parameters[0].Type.IsType<object>()));
138138

139139
// Records override this, but our implementation is superior
140-
existingComponents |= IdTypeComponents.EqualsMethod.If(!result.IsRecord && members.Any(member =>
141-
member.Name == nameof(Equals) && member is IMethodSymbol method && method.Arity == 0 && method.Parameters.Length == 1 &&
140+
existingComponents |= IdTypeComponents.EqualsMethod.If(members.Any(member =>
141+
member.Name == nameof(Equals) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Arity == 0 && method.Parameters.Length == 1 &&
142142
method.Parameters[0].Type.Equals(type, SymbolEqualityComparer.Default)));
143143

144144
existingComponents |= IdTypeComponents.CompareToMethod.If(members.Any(member =>

DomainModeling.Generator/TypeSymbolExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ internal static class TypeSymbolExtensions
1212

1313
private static IReadOnlyCollection<string> ConversionOperatorNames { get; } = ["op_Implicit", "op_Explicit",];
1414

15+
/// <summary>
16+
/// Returns the full CLR metadata name of the <see cref="INamedTypeSymbol"/>, e.g. "Namespace.Type+NestedGenericType`1".
17+
/// </summary>
18+
public static string GetFullMetadataName(this INamedTypeSymbol namedTypeSymbol)
19+
{
20+
// Recurse until we have a non-nested type
21+
if (namedTypeSymbol.IsNested())
22+
return $"{GetFullMetadataName(namedTypeSymbol.ContainingType)}+{namedTypeSymbol.MetadataName}";
23+
24+
// Beware that types may exist in the global namespace
25+
return namedTypeSymbol.ContainingNamespace is INamespaceSymbol ns && !ns.IsGlobalNamespace
26+
? $"{ns.ToDisplayString()}.{namedTypeSymbol.MetadataName}"
27+
: namedTypeSymbol.MetadataName;
28+
}
29+
1530
/// <summary>
1631
/// Returns whether the <see cref="ITypeSymbol"/> is of type <typeparamref name="T"/>.
1732
/// </summary>

DomainModeling.Generator/ValueObjectGenerator.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
5252
result.IsGeneric = type.IsGenericType;
5353
result.IsNested = type.IsNested();
5454

55+
result.FullMetadataName = type.GetFullMetadataName();
5556
result.TypeName = type.Name; // Will be non-generic if we pass the conditions to proceed with generation
5657
result.ContainingNamespace = type.ContainingNamespace.ToString();
5758

@@ -63,21 +64,21 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
6364
!ctor.IsStatic && ctor.Parameters.Length == 0 /*&& ctor.DeclaringSyntaxReferences.Length > 0*/));
6465

6566
// Records override this, but our implementation is superior
66-
existingComponents |= ValueObjectTypeComponents.ToStringOverride.If(!result.IsRecord && members.Any(member =>
67-
member.Name == nameof(ToString) && member is IMethodSymbol method && method.Parameters.Length == 0));
67+
existingComponents |= ValueObjectTypeComponents.ToStringOverride.If(members.Any(member =>
68+
member.Name == nameof(ToString) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Parameters.Length == 0));
6869

6970
// Records override this, but our implementation is superior
70-
existingComponents |= ValueObjectTypeComponents.GetHashCodeOverride.If(!result.IsRecord && members.Any(member =>
71-
member.Name == nameof(GetHashCode) && member is IMethodSymbol method && method.Parameters.Length == 0));
71+
existingComponents |= ValueObjectTypeComponents.GetHashCodeOverride.If(members.Any(member =>
72+
member.Name == nameof(GetHashCode) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Parameters.Length == 0));
7273

7374
// Records irrevocably and correctly override this, checking the type and delegating to IEquatable<T>.Equals(T)
7475
existingComponents |= ValueObjectTypeComponents.EqualsOverride.If(members.Any(member =>
7576
member.Name == nameof(Equals) && member is IMethodSymbol method && method.Parameters.Length == 1 &&
7677
method.Parameters[0].Type.IsType<object>()));
7778

7879
// Records override this, but our implementation is superior
79-
existingComponents |= ValueObjectTypeComponents.EqualsMethod.If(!result.IsRecord && members.Any(member =>
80-
member.Name == nameof(Equals) && member is IMethodSymbol method && method.Parameters.Length == 1 &&
80+
existingComponents |= ValueObjectTypeComponents.EqualsMethod.If(members.Any(member =>
81+
member.Name == nameof(Equals) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Parameters.Length == 1 &&
8182
method.Parameters[0].Type.Equals(type, SymbolEqualityComparer.Default)));
8283

8384
existingComponents |= ValueObjectTypeComponents.CompareToMethod.If(members.Any(member =>
@@ -152,7 +153,7 @@ private static void GenerateSource(SourceProductionContext context, (Generatable
152153
var generatable = input.Generatable;
153154
var compilation = input.Compilation;
154155

155-
var type = compilation.GetTypeByMetadataName($"{generatable.ContainingNamespace}.{generatable.TypeName}");
156+
var type = compilation.GetTypeByMetadataName(generatable.FullMetadataName);
156157

157158
// Require being able to find the type and attribute
158159
if (type is null)
@@ -429,6 +430,7 @@ private sealed record Generatable : IGeneratable
429430
public bool IsGeneric { get; set; }
430431
public bool IsNested { get; set; }
431432
public bool IsComparable { get; set; }
433+
public string FullMetadataName { get; set; } = null!;
432434
public string TypeName { get; set; } = null!;
433435
public string ContainingNamespace { get; set; } = null!;
434436
public ValueObjectTypeComponents ExistingComponents { get; set; }

DomainModeling.Generator/WrapperValueObjectGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,21 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella
9797
!ctor.IsStatic && ctor.Parameters.Length == 0 && ctor.DeclaringSyntaxReferences.Length > 0));
9898

9999
// Records override this, but our implementation is superior
100-
existingComponents |= WrapperValueObjectTypeComponents.ToStringOverride.If(!result.IsRecord && members.Any(member =>
101-
member.Name == nameof(ToString) && member is IMethodSymbol method && method.Parameters.Length == 0));
100+
existingComponents |= WrapperValueObjectTypeComponents.ToStringOverride.If(members.Any(member =>
101+
member.Name == nameof(ToString) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Parameters.Length == 0));
102102

103103
// Records override this, but our implementation is superior
104-
existingComponents |= WrapperValueObjectTypeComponents.GetHashCodeOverride.If(!result.IsRecord && members.Any(member =>
105-
member.Name == nameof(GetHashCode) && member is IMethodSymbol method && method.Parameters.Length == 0));
104+
existingComponents |= WrapperValueObjectTypeComponents.GetHashCodeOverride.If(members.Any(member =>
105+
member.Name == nameof(GetHashCode) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Parameters.Length == 0));
106106

107107
// Records irrevocably and correctly override this, checking the type and delegating to IEquatable<T>.Equals(T)
108108
existingComponents |= WrapperValueObjectTypeComponents.EqualsOverride.If(members.Any(member =>
109109
member.Name == nameof(Equals) && member is IMethodSymbol method && method.Parameters.Length == 1 &&
110110
method.Parameters[0].Type.IsType<object>()));
111111

112112
// Records override this, but our implementation is superior
113-
existingComponents |= WrapperValueObjectTypeComponents.EqualsMethod.If(!result.IsRecord && members.Any(member =>
114-
member.Name == nameof(Equals) && member is IMethodSymbol method && method.Parameters.Length == 1 &&
113+
existingComponents |= WrapperValueObjectTypeComponents.EqualsMethod.If(members.Any(member =>
114+
member.Name == nameof(Equals) && member is IMethodSymbol { IsImplicitlyDeclared: false } method && method.Parameters.Length == 1 &&
115115
method.Parameters[0].Type.Equals(type, SymbolEqualityComparer.Default)));
116116

117117
existingComponents |= WrapperValueObjectTypeComponents.CompareToMethod.If(members.Any(member =>

DomainModeling.Tests/DomainModeling.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<AssemblyName>Architect.DomainModeling.Tests</AssemblyName>
66
<RootNamespace>Architect.DomainModeling.Tests</RootNamespace>
77
<Nullable>Enable</Nullable>

DomainModeling/Configuration/IDomainEventConfigurator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ void ConfigureDomainEvent<
1616
in Args args)
1717
where TDomainEvent : IDomainObject;
1818

19+
#pragma warning disable IDE0040 // Remove accessibility modifiers -- We always want explicit accessibility for types
1920
public readonly struct Args
21+
#pragma warning restore IDE0040 // Remove accessibility modifiers
2022
{
2123
public readonly bool HasDefaultConstructor { get; init; }
2224
}

DomainModeling/Configuration/IEntityConfigurator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ void ConfigureEntity<
1616
in Args args)
1717
where TEntity : IEntity;
1818

19+
#pragma warning disable IDE0040 // Remove accessibility modifiers -- We always want explicit accessibility for types
1920
public readonly struct Args
21+
#pragma warning restore IDE0040 // Remove accessibility modifiers
2022
{
2123
public bool HasDefaultConstructor { get; init; }
2224
}

DomainModeling/Configuration/IIdentityConfigurator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ void ConfigureIdentity<
1818
where TIdentity : IIdentity<TUnderlying>, ISerializableDomainObject<TIdentity, TUnderlying>
1919
where TUnderlying : notnull, IEquatable<TUnderlying>, IComparable<TUnderlying>;
2020

21+
#pragma warning disable IDE0040 // Remove accessibility modifiers -- We always want explicit accessibility for types
2122
public readonly struct Args
23+
#pragma warning restore IDE0040 // Remove accessibility modifiers
2224
{
2325
}
2426
}

0 commit comments

Comments
 (0)