Skip to content

Commit

Permalink
feat: allow null value (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ne4to authored Oct 18, 2023
1 parent 5055e2e commit 40b740b
Show file tree
Hide file tree
Showing 120 changed files with 2,158 additions and 962 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,21 @@ public partial class FooResult
```
Or you can use generic type.
```csharp
[GenericUnionType]
public partial class OperationDataResult<TResult, TError>
public partial class OperationDataResult<[GenericUnionType] TResult, [GenericUnionType] TError>
{
}

// extend generic type union with additional Int32 type
[UnionType(typeof(int))]
public partial class ExtendedOperationDataResult<TResult, TError>
public partial class ExtendedOperationDataResult<[GenericUnionType] TResult, [GenericUnionType] TError>
{
}
```
Null values are not allowed by default. This behavior can be overriden by `AllowNull = true` parameter.
```csharp
[UnionType(typeof(int?), AllowNull = true)]
[UnionType(typeof(string), AllowNull = true)]
public partial class ResultNullable<[GenericUnionType(AllowNull = true)] T>
{
}
```
Expand Down
4 changes: 0 additions & 4 deletions examples/DataAccess/DataAccessModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

namespace DataAccess;


// TODO - uncomment to negative case
// -->
public record DataAccessModel1;

public record DataAccessModel2(string Message);
Expand All @@ -15,6 +12,5 @@ public partial class DataAccessModel
{

}
// <--

public record DataAccessModel3();
5 changes: 2 additions & 3 deletions examples/N.SourceGenerators.UnionTypes.Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,13 @@ public partial class Result2
{
}

[GenericUnionType]
public partial class OperationDataResult<TResult, TError>
public partial class OperationDataResult<[GenericUnionType] TResult, [GenericUnionType] TError>
{
}

// extend generic type union with additional Int32 type
[UnionType(typeof(int))]
public partial class ExtendedOperationDataResult<TResult, TError>
public partial class ExtendedOperationDataResult<[GenericUnionType] TResult, [GenericUnionType] TError>
{
}

Expand Down
104 changes: 52 additions & 52 deletions src/N.SourceGenerators.UnionTypes.Benchmark/README.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,88 @@
## Ctor
``` ini

BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job-BKNSSQ : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
BenchmarkDotNet=v0.13.4, OS=macOS 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK=7.0.401
[Host] : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD
Job-CPQSKY : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD

Runtime=.NET 7.0 Toolchain=net7.0 IterationCount=3
LaunchCount=1 WarmupCount=3

```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------------------------- |---------:|----------:|----------:|------:|--------:|-------:|----------:|------------:|
| Class | 7.050 ns | 0.3546 ns | 0.0194 ns | 1.00 | 0.00 | 0.0153 | 64 B | 1.00 |
| ClassSealed | 6.769 ns | 2.5824 ns | 0.1416 ns | 0.96 | 0.02 | 0.0153 | 64 B | 1.00 |
| Struct | 3.302 ns | 0.5208 ns | 0.0285 ns | 0.47 | 0.00 | 0.0057 | 24 B | 0.38 |
| StructReadonly | 3.264 ns | 0.6143 ns | 0.0337 ns | 0.46 | 0.00 | 0.0057 | 24 B | 0.38 |
| StructExplicitLayout | 4.123 ns | 0.5004 ns | 0.0274 ns | 0.58 | 0.00 | - | - | 0.00 |
| StructReadonlyExplicitLayout | 4.115 ns | 0.8700 ns | 0.0477 ns | 0.58 | 0.01 | - | - | 0.00 |
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|----------------------------- |----------:|----------:|----------:|------:|-------:|----------:|------------:|
| Class | 6.0429 ns | 0.2688 ns | 0.0147 ns | 1.000 | 0.0086 | 72 B | 1.00 |
| ClassSealed | 6.0265 ns | 0.2679 ns | 0.0147 ns | 0.997 | 0.0086 | 72 B | 1.00 |
| Struct | 2.4038 ns | 0.2048 ns | 0.0112 ns | 0.398 | 0.0029 | 24 B | 0.33 |
| StructReadonly | 2.4161 ns | 0.2119 ns | 0.0116 ns | 0.400 | 0.0029 | 24 B | 0.33 |
| StructExplicitLayout | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.000 | - | - | 0.00 |
| StructReadonlyExplicitLayout | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.000 | - | - | 0.00 |

## GetHashCode
``` ini

BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job-EOFGDV : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
BenchmarkDotNet=v0.13.4, OS=macOS 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK=7.0.401
[Host] : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD
Job-MVTOFC : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD

Runtime=.NET 7.0 Toolchain=net7.0 IterationCount=3
LaunchCount=1 WarmupCount=3

```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|----------------------------- |----------:|----------:|----------:|------:|--------:|----------:|------------:|
| Class | 6.758 ns | 0.5807 ns | 0.0318 ns | 1.00 | 0.00 | - | NA |
| ClassSealed | 6.731 ns | 0.7423 ns | 0.0407 ns | 1.00 | 0.01 | - | NA |
| Struct | 12.645 ns | 2.8756 ns | 0.1576 ns | 1.87 | 0.02 | - | NA |
| StructReadonly | 12.605 ns | 1.5499 ns | 0.0850 ns | 1.87 | 0.01 | - | NA |
| StructExplicitLayout | 2.465 ns | 0.1746 ns | 0.0096 ns | 0.36 | 0.00 | - | NA |
| StructReadonlyExplicitLayout | 2.315 ns | 0.7554 ns | 0.0414 ns | 0.34 | 0.01 | - | NA |
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|----------------------------- |---------:|----------:|----------:|------:|----------:|------------:|
| Class | 4.119 ns | 0.0605 ns | 0.0033 ns | 1.00 | - | NA |
| ClassSealed | 4.133 ns | 0.0484 ns | 0.0027 ns | 1.00 | - | NA |
| Struct | 7.660 ns | 0.1243 ns | 0.0068 ns | 1.86 | - | NA |
| StructReadonly | 7.677 ns | 0.2665 ns | 0.0146 ns | 1.86 | - | NA |
| StructExplicitLayout | 1.467 ns | 0.0200 ns | 0.0011 ns | 0.36 | - | NA |
| StructReadonlyExplicitLayout | 1.470 ns | 0.0193 ns | 0.0011 ns | 0.36 | - | NA |

## ReadValue
``` ini

BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job-RVTMNA : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
BenchmarkDotNet=v0.13.4, OS=macOS 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK=7.0.401
[Host] : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD
Job-NCEFIQ : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD

Runtime=.NET 7.0 Toolchain=net7.0 IterationCount=3
LaunchCount=1 WarmupCount=3

```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|----------------------------- |---------:|----------:|----------:|------:|--------:|----------:|------------:|
| Class | 1.674 ns | 0.2215 ns | 0.0121 ns | 1.00 | 0.00 | - | NA |
| ClassSealed | 1.675 ns | 0.8915 ns | 0.0489 ns | 1.00 | 0.02 | - | NA |
| Struct | 6.576 ns | 1.2893 ns | 0.0707 ns | 3.93 | 0.05 | - | NA |
| StructReadonly | 6.749 ns | 2.4536 ns | 0.1345 ns | 4.03 | 0.07 | - | NA |
| StructExplicitLayout | 1.727 ns | 1.0336 ns | 0.0567 ns | 1.03 | 0.03 | - | NA |
| StructReadonlyExplicitLayout | 1.636 ns | 0.1580 ns | 0.0087 ns | 0.98 | 0.01 | - | NA |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|----------------------------- |----------:|----------:|----------:|------:|--------:|----------:|------------:|
| Class | 0.3540 ns | 0.0891 ns | 0.0049 ns | 1.00 | 0.00 | - | NA |
| ClassSealed | 0.3228 ns | 0.0092 ns | 0.0005 ns | 0.91 | 0.01 | - | NA |
| Struct | 3.2880 ns | 0.2043 ns | 0.0112 ns | 9.29 | 0.15 | - | NA |
| StructReadonly | 3.2877 ns | 0.0631 ns | 0.0035 ns | 9.29 | 0.13 | - | NA |
| StructExplicitLayout | 0.2804 ns | 0.0901 ns | 0.0049 ns | 0.79 | 0.02 | - | NA |
| StructReadonlyExplicitLayout | 0.3270 ns | 1.9317 ns | 0.1059 ns | 0.92 | 0.30 | - | NA |

## ToString
``` ini

BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job-PKSHPT : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
BenchmarkDotNet=v0.13.4, OS=macOS 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK=7.0.401
[Host] : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD
Job-MVCCVK : .NET 7.0.11 (7.0.1123.42427), Arm64 RyuJIT AdvSIMD

Runtime=.NET 7.0 Toolchain=net7.0 IterationCount=3
LaunchCount=1 WarmupCount=3

```
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|----------------------------- |---------:|----------:|---------:|------:|-------:|----------:|------------:|
| Class | 95.39 ns | 10.200 ns | 0.559 ns | 1.00 | 0.1128 | 472 B | 1.00 |
| ClassSealed | 90.89 ns | 7.331 ns | 0.402 ns | 0.95 | 0.1128 | 472 B | 1.00 |
| Struct | 99.57 ns | 10.057 ns | 0.551 ns | 1.04 | 0.1128 | 472 B | 1.00 |
| StructReadonly | 98.63 ns | 17.335 ns | 0.950 ns | 1.03 | 0.1128 | 472 B | 1.00 |
| StructExplicitLayout | 94.54 ns | 5.686 ns | 0.312 ns | 0.99 | 0.1147 | 480 B | 1.02 |
| StructReadonlyExplicitLayout | 95.61 ns | 11.132 ns | 0.610 ns | 1.00 | 0.1147 | 480 B | 1.02 |
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|----------------------------- |---------:|---------:|---------:|------:|-------:|----------:|------------:|
| Class | 72.75 ns | 0.173 ns | 0.009 ns | 1.00 | 0.0564 | 472 B | 1.00 |
| ClassSealed | 72.83 ns | 2.486 ns | 0.136 ns | 1.00 | 0.0564 | 472 B | 1.00 |
| Struct | 77.59 ns | 3.612 ns | 0.198 ns | 1.07 | 0.0564 | 472 B | 1.00 |
| StructReadonly | 76.85 ns | 2.708 ns | 0.148 ns | 1.06 | 0.0564 | 472 B | 1.00 |
| StructExplicitLayout | 70.28 ns | 4.734 ns | 0.260 ns | 0.97 | 0.0573 | 480 B | 1.02 |
| StructReadonlyExplicitLayout | 71.65 ns | 1.785 ns | 0.098 ns | 0.98 | 0.0573 | 480 B | 1.02 |

Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,14 @@ public static ParameterSyntax AddModifiersWhen(
? syntax.AddModifiers(items)
: syntax;
}

public static ParameterSyntax AddAttributeListsWhen(
this ParameterSyntax syntax,
bool condition,
params AttributeListSyntax[] items)
{
return condition
? syntax.AddAttributeLists(items)
: syntax;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ public static bool IsTypeWithAttributes(this SyntaxNode s)
AttributeLists.Count: > 0
};
}

public static bool IsGenericTypeAttribute(this SyntaxNode s)
{
if (s is not TypeParameterSyntax typeParameter)
{
return false;
}

if (typeParameter.Parent is not TypeParameterListSyntax typeParameterList)
{
return false;
}

return typeParameterList.Parent is TypeDeclarationSyntax;
}

public static bool IsPartial(this TypeDeclarationSyntax s)
{
Expand Down
44 changes: 39 additions & 5 deletions src/N.SourceGenerators.UnionTypes/Models/UnionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ public UnionType(INamedTypeSymbol containerType,
IsPartial = syntax.IsPartial();
}

// TODO convert to simple model?
TypeArguments = containerType.TypeArguments;

IsReferenceType = containerType.IsReferenceType;
IsValueType = containerType.IsValueType;
HasToStringMethod = containerType.GetMembers().Any(IsToStringMethod);
Expand Down Expand Up @@ -87,8 +84,6 @@ public UnionType(INamedTypeSymbol containerType,
}
}

public ImmutableArray<ITypeSymbol> TypeArguments { get; set; }

private static bool IsToStringMethod(ISymbol symbol)
{
return symbol is IMethodSymbol
Expand All @@ -107,4 +102,43 @@ public Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, params objec
messageArgs
);
}
}

internal class UnionTypeComparer : IEqualityComparer<UnionType>
{
public static UnionTypeComparer Instance { get; } = new();

private UnionTypeComparer()
{
}

public bool Equals(UnionType x, UnionType y)
{
if (ReferenceEquals(x, y))
{
return true;
}

if (ReferenceEquals(x, null))
{
return false;
}

if (ReferenceEquals(y, null))
{
return false;
}

if (x.GetType() != y.GetType())
{
return false;
}

return x.TypeFullName == y.TypeFullName;
}

public int GetHashCode(UnionType obj)
{
return obj.TypeFullName.GetHashCode();
}
}
4 changes: 3 additions & 1 deletion src/N.SourceGenerators.UnionTypes/Models/UnionTypeVariant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ internal class UnionTypeVariant
public bool IsValueType { get; }
public int IdConstValue { get; internal set; }
public bool IsInterface { get; }
public bool AllowNull { get; }

public UnionTypeVariant(ITypeSymbol typeSymbol, string? alias, int order)
public UnionTypeVariant(ITypeSymbol typeSymbol, string? alias, int order, bool allowNull)
{
Alias = alias ?? GetAlias(typeSymbol);
Order = order;
Expand All @@ -39,6 +40,7 @@ public UnionTypeVariant(ITypeSymbol typeSymbol, string? alias, int order)
IdConstName = $"{Alias}Id";
IsValueType = typeSymbol.IsValueType;
IsInterface = typeSymbol is { IsReferenceType: true, BaseType: null };
AllowNull = allowNull;
}

private static HashSet<string> GetKeywords()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<IsRoslynComponent>true</IsRoslynComponent>
<IsPackable>true</IsPackable>
<PackageOutputPath>./nupkg</PackageOutputPath>
<VersionPrefix>0.24.0</VersionPrefix>
<VersionPrefix>0.25.3</VersionPrefix>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal sealed class UnionTypeAttribute : Attribute
public Type Type { get; }
public string? Alias { get; }
public int Order { get; }
public bool AllowNull { get; set; }
public UnionTypeAttribute(Type type, string? alias = null, [CallerLineNumber] int order = 0)
{
Expand All @@ -45,9 +46,11 @@ public UnionTypeAttribute(Type type, string? alias = null, [CallerLineNumber] in
namespace N.SourceGenerators.UnionTypes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.GenericParameter, Inherited = false, AllowMultiple = false)]
internal sealed class GenericUnionTypeAttribute : Attribute
{
public string? Alias { get; set; }
public bool AllowNull { get; set; }
}
}
""";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ private static MemberDeclarationSyntax GetHashCodeMethod(UnionType unionType)
Token(SyntaxKind.PublicKeyword),
Token(SyntaxKind.OverrideKeyword)
).AddBodyStatements(
VariantsBodyStatements(unionType, v => AliasStatement(unionType, v))
VariantsBodyStatements(unionType, AliasStatement)
);

static StatementSyntax AliasStatement(UnionType unionType, UnionTypeVariant variant)
static StatementSyntax AliasStatement(UnionTypeVariant variant)
{
return IfStatement(
IsPropertyCondition(variant),
Expand Down Expand Up @@ -110,10 +110,6 @@ IEnumerable<StatementSyntax> BodyStatements()
foreach (UnionTypeVariant variant in unionType.Variants)
{
MemberAccessExpressionSyntax memberAccess = MemberAccess("other", variant.FieldName);
if (variant.IsValueType && !unionType.UseStructLayout)
{
memberAccess = MemberAccess(memberAccess, "Value");
}

yield return IfStatement(
IsPropertyCondition(variant),
Expand All @@ -129,7 +125,7 @@ IEnumerable<StatementSyntax> BodyStatements()
IdentifierName("Equals")
)
).AddArgumentListArguments(
Argument(NotNullableArgumentExpression(unionType, variant)),
Argument(NotNullableArgumentExpression(variant)),
Argument(memberAccess)
)
)
Expand Down
Loading

0 comments on commit 40b740b

Please sign in to comment.