diff --git a/DomainModeling.Generator/NamespaceSymbolExtensions.cs b/DomainModeling.Generator/NamespaceSymbolExtensions.cs index dbf7c66..8ec5969 100644 --- a/DomainModeling.Generator/NamespaceSymbolExtensions.cs +++ b/DomainModeling.Generator/NamespaceSymbolExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; namespace Architect.DomainModeling.Generator; @@ -12,16 +12,16 @@ internal static class NamespaceSymbolExtensions /// public static bool IsInSystemNamespace(this INamespaceSymbol namespaceSymbol) { - while (namespaceSymbol.ContainingNamespace is not null) + while (namespaceSymbol?.ContainingNamespace is not null) namespaceSymbol = namespaceSymbol.ContainingNamespace; - return namespaceSymbol.Name == "System"; + return namespaceSymbol?.Name == "System"; } /// /// Returns whether the given has the given . /// - public static bool HasFullName(this INamespaceSymbol namespaceSymbol, string fullName) + public static bool HasFullName(this INamespaceSymbol? namespaceSymbol, string fullName) { return namespaceSymbol.HasFullName(fullName.AsSpan()); } @@ -29,8 +29,11 @@ public static bool HasFullName(this INamespaceSymbol namespaceSymbol, string ful /// /// Returns whether the given has the given . /// - public static bool HasFullName(this INamespaceSymbol namespaceSymbol, ReadOnlySpan fullName) + public static bool HasFullName(this INamespaceSymbol? namespaceSymbol, ReadOnlySpan fullName) { + if (namespaceSymbol is null) + return false; + do { var length = namespaceSymbol.Name.Length; diff --git a/DomainModeling.Generator/TypeSymbolExtensions.cs b/DomainModeling.Generator/TypeSymbolExtensions.cs index 04fcca2..6b2008f 100644 --- a/DomainModeling.Generator/TypeSymbolExtensions.cs +++ b/DomainModeling.Generator/TypeSymbolExtensions.cs @@ -43,7 +43,7 @@ public static bool IsType(this ITypeSymbol typeSymbol, ITypeSymbol comparand) chars = chars.Slice(0, chars.Length - freeBuffer.Length); if (containingNamespace?.IsGlobalNamespace != false) - chars = typeSymbol.ContainingNamespace.ToString().AsSpan(); + chars = (typeSymbol.ContainingNamespace?.ToString() ?? "").AsSpan(); if (!typeSymbol.IsType(typeSymbol.Name.AsSpan(), chars)) return false; @@ -333,6 +333,11 @@ public static bool IsEnumerable(this ITypeSymbol typeSymbol, out INamedTypeSymbo if (!typeSymbol.IsOrImplementsInterface(type => type.IsType("IEnumerable", "System.Collections", generic: false), out var nonGenericEnumerableInterface)) return false; + if (typeSymbol.Kind == SymbolKind.ArrayType) + { + elementType = ((IArrayTypeSymbol)typeSymbol).ElementType as INamedTypeSymbol; + return true; + } if (typeSymbol.IsOrImplementsInterface(type => type.IsType("IList", "System.Collections.Generic", generic: true), out var interf)) { elementType = interf.TypeArguments[0] as INamedTypeSymbol; diff --git a/DomainModeling.Generator/ValueObjectGenerator.cs b/DomainModeling.Generator/ValueObjectGenerator.cs index f27b031..35c3eb1 100644 --- a/DomainModeling.Generator/ValueObjectGenerator.cs +++ b/DomainModeling.Generator/ValueObjectGenerator.cs @@ -148,7 +148,7 @@ private static bool FilterSyntaxNode(SyntaxNode node, CancellationToken cancella { dataMemberHashCode = tuple.Member.Name.GetStableHashCode64(dataMemberHashCode); dataMemberHashCode = ":".GetStableHashCode64(dataMemberHashCode); - dataMemberHashCode = tuple.Type.ContainingNamespace.ToString().GetStableHashCode64(dataMemberHashCode); + dataMemberHashCode = (tuple.Type.ContainingNamespace?.ToString() ?? "").GetStableHashCode64(dataMemberHashCode); // Arrays have no namespace dataMemberHashCode = ".".GetStableHashCode64(dataMemberHashCode); dataMemberHashCode = tuple.Type.Name.GetStableHashCode64(dataMemberHashCode); dataMemberHashCode = "&".GetStableHashCode64(dataMemberHashCode); @@ -288,7 +288,7 @@ public bool Equals({typeName}? other) {(existingComponents.HasFlags(ValueObjectTypeComponents.EqualsMethod) ? " */" : "")} /// - /// Provides type inference when comparing types that are completed source-generated. The current code's source generator does not know the appropriate namespace, because the type is being generated at the same time, thus necessitating type inference. + /// Provides type inference when comparing types that are entirely source-generated. The current code's source generator does not know the appropriate namespace, because the type is being generated at the same time, thus necessitating type inference. /// [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] private static bool Equals(T left, T right) @@ -307,7 +307,7 @@ public int CompareTo({typeName}? other) }} /// - /// Provides type inference when comparing types that are completed source-generated. The current code's source generator does not know the appropriate namespace, because the type is being generated at the same time, thus necessitating type inference. + /// Provides type inference when comparing types that are entirely source-generated. The current code's source generator does not know the appropriate namespace, because the type is being generated at the same time, thus necessitating type inference. /// [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] private static int Compare(T left, T right) diff --git a/DomainModeling.Tests/ValueObjectTests.cs b/DomainModeling.Tests/ValueObjectTests.cs index a9d8b28..7f660f5 100644 --- a/DomainModeling.Tests/ValueObjectTests.cs +++ b/DomainModeling.Tests/ValueObjectTests.cs @@ -1086,6 +1086,25 @@ public ImmutableArrayValueObject(IEnumerable values) } } + /// + /// Should merely compile. + /// + [Obsolete("Should merely compile.", error: true)] + [SourceGenerated] + public sealed partial class ArrayValueObject : ValueObject + { + protected override StringComparison StringComparison => StringComparison.OrdinalIgnoreCase; + + public string?[]? StringValues { get; } + public int?[] IntValues { get; } + + public ArrayValueObject(string?[]? stringValues, int?[] intValues) + { + this.StringValues = stringValues; + this.IntValues = intValues; + } + } + [SourceGenerated] public sealed partial class CustomCollectionValueObject : ValueObject { diff --git a/DomainModeling.Tests/WrapperValueObjectTests.cs b/DomainModeling.Tests/WrapperValueObjectTests.cs index 877369a..5c6084a 100644 --- a/DomainModeling.Tests/WrapperValueObjectTests.cs +++ b/DomainModeling.Tests/WrapperValueObjectTests.cs @@ -508,6 +508,18 @@ public CustomCollection(string value) } } + [SourceGenerated] + [Obsolete("Should merely compile.", error: true)] + public sealed partial class StringArrayValue : WrapperValueObject + { + } + + [SourceGenerated] + [Obsolete("Should merely compile.", error: true)] + public sealed partial class DecimalArrayValue : WrapperValueObject + { + } + /// /// Should merely compile. /// diff --git a/DomainModeling/DomainModeling.csproj b/DomainModeling/DomainModeling.csproj index 5db77ed..20ca875 100644 --- a/DomainModeling/DomainModeling.csproj +++ b/DomainModeling/DomainModeling.csproj @@ -13,7 +13,7 @@ - 2.0.0 + 2.0.1 A complete Domain-Driven Design (DDD) toolset for implementing domain models, including base types and source generators. @@ -21,6 +21,9 @@ https://github.com/TheArchitectDev/Architect.DomainModeling Release notes: +2.0.1: +- Fixed a bug where arrays in (Wrapper)ValueObjects would trip the generator. + 2.0.0: - BREAKING: Generated DummyBuilders now use UTC datetimes for generated defaults and for interpreting datetime strings. - Semi-breaking: Generated types no longer add [Serializable] attribute, since there would be no way to remove it.