diff --git a/sandbox/FileGenerate/SimplePrimitive.cs b/sandbox/FileGenerate/SimplePrimitive.cs index b98deef..a17c135 100644 --- a/sandbox/FileGenerate/SimplePrimitive.cs +++ b/sandbox/FileGenerate/SimplePrimitive.cs @@ -12,7 +12,7 @@ public readonly partial struct B { } - [UnitOf(typeof(int), UnitGenerateOptions.Comparable | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator)] + [UnitOf(typeof(int), UnitGenerateOptions.Comparable | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.ParseMethod)] public readonly partial struct C { } diff --git a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.A.g.cs b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.A.g.cs index b547431..1097a85 100644 --- a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.A.g.cs +++ b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.A.g.cs @@ -12,9 +12,13 @@ namespace FileGenerate [System.ComponentModel.TypeConverter(typeof(ATypeConverter))] readonly partial struct A : IEquatable -#if NET7_0_OR_GREATER + , IFormattable +#if NET6_0_OR_GREATER + , ISpanFormattable +#endif +#if NET8_0_OR_GREATER , IEqualityOperators -#endif +#endif { readonly int value; @@ -73,6 +77,12 @@ public override int GetHashCode() public override string ToString() => value.ToString(); + public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); + +#if NET6_0_OR_GREATER + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); +#endif // Default private class ATypeConverter : System.ComponentModel.TypeConverter diff --git a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Aa.g.cs b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Aa.g.cs index cbff044..f92ae9d 100644 --- a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Aa.g.cs +++ b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Aa.g.cs @@ -15,6 +15,7 @@ readonly partial struct Aa #if NET7_0_OR_GREATER , IEqualityOperators #endif + , IFormattable { readonly int value; @@ -73,6 +74,8 @@ public override int GetHashCode() public override string ToString() => value.ToString(); + public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); + // Default private class AaTypeConverter : System.ComponentModel.TypeConverter diff --git a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.B.g.cs b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.B.g.cs index d58b9b0..fccbdd2 100644 --- a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.B.g.cs +++ b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.B.g.cs @@ -12,9 +12,9 @@ namespace FileGenerate [System.ComponentModel.TypeConverter(typeof(BTypeConverter))] readonly partial struct B : IEquatable -#if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER , IEqualityOperators -#endif +#endif { readonly string value; diff --git a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.C.g.cs b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.C.g.cs index 5807c9c..82b70d9 100644 --- a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.C.g.cs +++ b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.C.g.cs @@ -12,14 +12,15 @@ namespace FileGenerate [System.ComponentModel.TypeConverter(typeof(CTypeConverter))] readonly partial struct C : IEquatable -#if NET7_0_OR_GREATER - , IEqualityOperators -#endif , IComparable -#if NET7_0_OR_GREATER - , IComparisonOperators + , IFormattable +#if NET6_0_OR_GREATER + , ISpanFormattable #endif #if NET7_0_OR_GREATER + , IComparisonOperators + , IParsable + , ISpanParsable , IAdditionOperators , ISubtractionOperators , IMultiplyOperators @@ -28,6 +29,10 @@ readonly partial struct C , IUnaryNegationOperators , IIncrementOperators , IDecrementOperators +#endif +#if NET8_0_OR_GREATER + , IEqualityOperators + , IUtf8SpanParsable #endif { readonly int value; @@ -87,6 +92,96 @@ public override int GetHashCode() public override string ToString() => value.ToString(); + public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); + +#if NET6_0_OR_GREATER + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); +#endif + // UnitGenerateOptions.ParseMethod + + public static C Parse(string s) + { + return new C(int.Parse(s)); + } + + public static bool TryParse(string s, out C result) + { + if (int.TryParse(s, out var r)) + { + result = new C(r); + return true; + } + else + { + result = default(C); + return false; + } + } + +#if NET7_0_OR_GREATER + public static C Parse(string s, IFormatProvider? provider) + { + return new C(int.Parse(s, provider)); + } + + public static bool TryParse(string s, IFormatProvider? provider, out C result) + { + if (int.TryParse(s, provider, out var r)) + { + result = new C(r); + return true; + } + else + { + result = default(C); + return false; + } + } +#endif + +#if NET7_0_OR_GREATER + public static C Parse(ReadOnlySpan s, IFormatProvider? provider) + { + return new C(int.Parse(s, provider)); + } + + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out C result) + { + if (int.TryParse(s, provider, out var r)) + { + result = new C(r); + return true; + } + else + { + result = default(C); + return false; + } + } +#endif + +#if NET8_0_OR_GREATER + public static C Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) + { + return new C(int.Parse(utf8Text, provider)); + } + + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out C result) + { + if (int.TryParse(utf8Text, provider, out var r)) + { + result = new C(r); + return true; + } + else + { + result = default(C); + return false; + } + } +#endif + // UnitGenerateOptions.ArithmeticOperator public static C operator +(C x, C y) diff --git a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Cc.g.cs b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Cc.g.cs index f77081e..4d4a367 100644 --- a/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Cc.g.cs +++ b/sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Cc.g.cs @@ -19,6 +19,7 @@ readonly partial struct Cc #if NET7_0_OR_GREATER , IComparisonOperators #endif + , IFormattable #if NET7_0_OR_GREATER , IAdditionOperators , ISubtractionOperators @@ -87,6 +88,8 @@ public override int GetHashCode() public override string ToString() => value.ToString(); + public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); + // UnitGenerateOptions.ArithmeticOperator public static Cc operator +(Cc x, Cc y) diff --git a/src/UnitGenerator/ReferenceSymbols.cs b/src/UnitGenerator/ReferenceSymbols.cs new file mode 100644 index 0000000..fd417ca --- /dev/null +++ b/src/UnitGenerator/ReferenceSymbols.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace UnitGenerator; + +class ReferenceSymbols +{ + public static ReferenceSymbols Create(Compilation compilation) + { + return new ReferenceSymbols + { + GuidType = compilation.GetTypeByMetadataName("System.Guid")!, + UlidType = compilation.GetTypeByMetadataName("System.Ulid"), + FormattableInterface = compilation.GetTypeByMetadataName("System.IFormattable")!, + ParsableInterface = compilation.GetTypeByMetadataName("System.IParsable`1"), + SpanFormattableInterface = compilation.GetTypeByMetadataName("System.ISpanFormattable"), + SpanParsableInterface = compilation.GetTypeByMetadataName("System.ISpanParsable`1"), + Utf8SpanFormattableInterface = compilation.GetTypeByMetadataName("System.IUtf8SpanFormattable"), + Utf8SpanParsableInterface = compilation.GetTypeByMetadataName("System.IUtf8SpanParsable`1"), + }; + } + + public INamedTypeSymbol GuidType { get; private set; } = default!; + public INamedTypeSymbol? UlidType { get; private set; } + public INamedTypeSymbol FormattableInterface { get; private set; } = default!; + public INamedTypeSymbol? ParsableInterface { get; private set; } + public INamedTypeSymbol? SpanFormattableInterface { get; private set; } + public INamedTypeSymbol? SpanParsableInterface { get; private set; } + public INamedTypeSymbol? Utf8SpanFormattableInterface { get; private set; } + public INamedTypeSymbol? Utf8SpanParsableInterface { get; private set; } +} \ No newline at end of file diff --git a/src/UnitGenerator/SourceGenerator.cs b/src/UnitGenerator/SourceGenerator.cs index a9a74c1..ffa0a13 100644 --- a/src/UnitGenerator/SourceGenerator.cs +++ b/src/UnitGenerator/SourceGenerator.cs @@ -25,13 +25,18 @@ public void Execute(GeneratorExecutionContext context) var receiver = context.SyntaxReceiver as SyntaxReceiver; if (receiver == null) return; + var symbols = ReferenceSymbols.Create(context.Compilation); var list = new List<(StructDeclarationSyntax, UnitOfAttributeProperty)>(); foreach (var (type, attr, targetType) in receiver.Targets) { var model = context.Compilation.GetSemanticModel(type.SyntaxTree); // retrieve attribute parameter - var prop = new UnitOfAttributeProperty { ArithmeticOperators = UnitArithmeticOperators.All }; + var prop = new UnitOfAttributeProperty + { + ReferenceSymbols = symbols, + ArithmeticOperators = UnitArithmeticOperators.All + }; if(targetType is not null) { @@ -258,71 +263,127 @@ namespace {{ns}} """); } + var anyPlatformInterfaces = new List(); + var net6Interfaces = new List(); + var net7Interfaces = new List(); + var net8Interfaces = new List + { + $"IEqualityOperators<{unitTypeName}, {unitTypeName}, bool>" + }; + sb.AppendLine($$""" [System.ComponentModel.TypeConverter(typeof({{unitTypeName}}TypeConverter))] readonly partial struct {{unitTypeName}} : IEquatable<{{unitTypeName}}> -#if NET7_0_OR_GREATER - , IEqualityOperators<{{unitTypeName}}, {{unitTypeName}}, bool> -#endif """); if (prop.HasFlag(UnitGenerateOptions.Comparable) && !prop.HasFlag(UnitGenerateOptions.WithoutComparisonOperator)) { - sb.AppendLine($$""" - , IComparable<{{unitTypeName}}> -#if NET7_0_OR_GREATER - , IComparisonOperators<{{unitTypeName}}, {{unitTypeName}}, bool> -#endif -"""); + anyPlatformInterfaces.Add($"IComparable<{unitTypeName}>"); + net7Interfaces.Add($"IComparisonOperators<{unitTypeName}, {unitTypeName}, bool>"); } + if (prop.HasFormattableInterface()) + { + anyPlatformInterfaces.Add("IFormattable"); + } + if (prop.HasSpanFormattableInterface()) + { + net6Interfaces.Add($"ISpanFormattable"); + } + if (prop.HasUtf8SpanFormattableInterface()) + { + net8Interfaces.Add($"IUtf8SpanFormattable"); + } + if (prop.HasFlag(UnitGenerateOptions.ParseMethod)) + { + if (prop.HasParsableInterface()) + { + net7Interfaces.Add($"IParsable<{unitTypeName}>"); + } + if (prop.HasSpanParsableInterface()) + { + net7Interfaces.Add($"ISpanParsable<{unitTypeName}>"); + } + if (prop.HasUtf8SpanParsableInterface()) + { + net8Interfaces.Add($"IUtf8SpanParsable<{unitTypeName}>"); + } + } + if (prop.HasFlag(UnitGenerateOptions.ArithmeticOperator)) { - sb.AppendLine("#if NET7_0_OR_GREATER"); if (prop.HasArithmeticOperator(UnitArithmeticOperators.Addition)) { - sb.AppendLine($$""" - , IAdditionOperators<{{unitTypeName}}, {{unitTypeName}}, {{unitTypeName}}> -"""); + net7Interfaces.Add($"IAdditionOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); } if (prop.HasArithmeticOperator(UnitArithmeticOperators.Subtraction)) { - sb.AppendLine($$""" - , ISubtractionOperators<{{unitTypeName}}, {{unitTypeName}}, {{unitTypeName}}> -"""); + net7Interfaces.Add($"ISubtractionOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); } if (prop.HasArithmeticOperator(UnitArithmeticOperators.Multiply)) { - sb.AppendLine($$""" - , IMultiplyOperators<{{unitTypeName}}, {{unitTypeName}}, {{unitTypeName}}> -"""); + net7Interfaces.Add($"IMultiplyOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); } if (prop.HasArithmeticOperator(UnitArithmeticOperators.Division)) { - sb.AppendLine($$""" - , IDivisionOperators<{{unitTypeName}}, {{unitTypeName}}, {{unitTypeName}}> -"""); + net7Interfaces.Add($"IDivisionOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); } if (prop.HasArithmeticOperator(UnitArithmeticOperators.Multiply) || prop.HasArithmeticOperator(UnitArithmeticOperators.Division)) { - sb.AppendLine($$""" - , IUnaryPlusOperators<{{unitTypeName}}, {{unitTypeName}}> - , IUnaryNegationOperators<{{unitTypeName}}, {{unitTypeName}}> -"""); + net7Interfaces.Add($"IUnaryPlusOperators<{unitTypeName}, {unitTypeName}>"); + net7Interfaces.Add($"IUnaryNegationOperators<{unitTypeName}, {unitTypeName}>"); } if (prop.HasArithmeticOperator(UnitArithmeticOperators.Increment)) { - sb.AppendLine($$""" - , IIncrementOperators<{{unitTypeName}}> -"""); + net7Interfaces.Add($"IIncrementOperators<{unitTypeName}>"); } if (prop.HasArithmeticOperator(UnitArithmeticOperators.Decrement)) { - sb.AppendLine($$""" - , IDecrementOperators<{{unitTypeName}}> -"""); + net7Interfaces.Add($"IDecrementOperators<{unitTypeName}>"); } + } + + foreach (var interfaceName in anyPlatformInterfaces) + { + sb.AppendLine($" , {interfaceName}"); + } + if (net6Interfaces.Count > 0) + { + sb.AppendLine("#if NET6_0_OR_GREATER"); + } + foreach (var interfaceName in net6Interfaces) + { + sb.AppendLine($" , {interfaceName}"); + } + if (net6Interfaces.Count > 0) + { + sb.AppendLine("#endif"); + } + + if (net7Interfaces.Count > 0) + { + sb.AppendLine("#if NET7_0_OR_GREATER"); + } + foreach (var interfaceName in net7Interfaces) + { + sb.AppendLine($" , {interfaceName}"); + } + if (net7Interfaces.Count > 0) + { + sb.AppendLine("#endif"); + } + + if (net8Interfaces.Count > 0) + { + sb.AppendLine("#if NET8_0_OR_GREATER"); + } + foreach (var interfaceName in net8Interfaces) + { + sb.AppendLine($" , {interfaceName}"); + } + if (net8Interfaces.Count > 0) + { sb.AppendLine("#endif"); } @@ -453,6 +514,33 @@ public override int GetHashCode() } } + if (prop.HasFormattableInterface()) + { + sb.AppendLine(""" + public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); + +"""); + } + if (prop.HasSpanFormattableInterface()) + { + sb.AppendLine(""" +#if NET6_0_OR_GREATER + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); +#endif +"""); + } + if (prop.HasUtf8SpanFormattableInterface()) + { + sb.AppendLine(""" +#if NET8_0_OR_GREATER + public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => + ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); +#endif + +"""); + } + if (prop.IsGuid()) { sb.AppendLine($$""" @@ -543,7 +631,88 @@ public static bool TryParse(string s, out {{unitTypeName}} result) """); } + + if (prop.HasParsableInterface()) + { + sb.AppendLine($$""" +#if NET7_0_OR_GREATER + public static {{unitTypeName}} Parse(string s, IFormatProvider? provider) + { + return new {{unitTypeName}}({{innerTypeName}}.Parse(s, provider)); + } + + public static bool TryParse(string s, IFormatProvider? provider, out {{unitTypeName}} result) + { + if ({{innerTypeName}}.TryParse(s, provider, out var r)) + { + result = new {{unitTypeName}}(r); + return true; + } + else + { + result = default({{unitTypeName}}); + return false; + } + } +#endif + +"""); + } + if (prop.HasSpanParsableInterface()) + { + sb.AppendLine($$""" +#if NET7_0_OR_GREATER + public static {{unitTypeName}} Parse(ReadOnlySpan s, IFormatProvider? provider) + { + return new {{unitTypeName}}({{innerTypeName}}.Parse(s, provider)); + } + + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out {{unitTypeName}} result) + { + if ({{innerTypeName}}.TryParse(s, provider, out var r)) + { + result = new {{unitTypeName}}(r); + return true; + } + else + { + result = default({{unitTypeName}}); + return false; + } + } +#endif + +"""); + } + + if (prop.HasUtf8SpanParsableInterface()) + { + sb.AppendLine($$""" +#if NET8_0_OR_GREATER + public static {{unitTypeName}} Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) + { + return new {{unitTypeName}}({{innerTypeName}}.Parse(utf8Text, provider)); + } + + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out {{unitTypeName}} result) + { + if ({{innerTypeName}}.TryParse(utf8Text, provider, out var r)) + { + result = new {{unitTypeName}}(r); + return true; + } + else + { + result = default({{unitTypeName}}); + return false; } + } +#endif + +"""); + } + } + if (prop.HasFlag(UnitGenerateOptions.MinMaxMethod)) { sb.AppendLine($$""" @@ -1049,6 +1218,7 @@ public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext co struct UnitOfAttributeProperty { + public ReferenceSymbols ReferenceSymbols { get; set; } public ITypeSymbol Type { get; set; } public UnitGenerateOptions Options { get; set; } public UnitArithmeticOperators ArithmeticOperators { get; set; } @@ -1057,8 +1227,8 @@ struct UnitOfAttributeProperty public bool IsString() => TypeName is "string"; public bool IsBool() => TypeName is "bool"; - public bool IsUlid() => TypeName is "Ulid" or "System.Ulid"; - public bool IsGuid() => TypeName is "Guid" or "System.Guid"; + public bool IsUlid() => SymbolEqualityComparer.Default.Equals(Type, ReferenceSymbols.UlidType); + public bool IsGuid() => SymbolEqualityComparer.Default.Equals(Type, ReferenceSymbols.GuidType); public bool HasFlag(UnitGenerateOptions options) => Options.HasFlag(options); @@ -1072,6 +1242,13 @@ public bool HasValueArithmeticOperator(UnitArithmeticOperators op) return HasFlag(UnitGenerateOptions.ValueArithmeticOperator) && ArithmeticOperators.HasFlag(op); } + public bool HasFormattableInterface() => IsImplemented(ReferenceSymbols.FormattableInterface); + public bool HasParsableInterface() => ReferenceSymbols.ParsableInterface != null && IsImplementedGenericSelfType(ReferenceSymbols.ParsableInterface); + public bool HasSpanFormattableInterface() => ReferenceSymbols.SpanFormattableInterface != null && IsImplemented(ReferenceSymbols.SpanFormattableInterface); + public bool HasSpanParsableInterface() => ReferenceSymbols.SpanParsableInterface != null && IsImplementedGenericSelfType(ReferenceSymbols.SpanParsableInterface); + public bool HasUtf8SpanFormattableInterface() => ReferenceSymbols.Utf8SpanFormattableInterface != null && IsImplemented(ReferenceSymbols.Utf8SpanFormattableInterface); + public bool HasUtf8SpanParsableInterface() => ReferenceSymbols.ParsableInterface != null && IsImplementedGenericSelfType(ReferenceSymbols.ParsableInterface); + public DbType GetDbType() { return TypeName switch @@ -1120,6 +1297,32 @@ public bool IsSupportUtf8Formatter() _ => false }; } + + bool IsImplemented(INamedTypeSymbol interfaceSymbol) + { + foreach (var x in Type.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(x, interfaceSymbol)) + { + return true; + } + } + return false; + } + + bool IsImplementedGenericSelfType(INamedTypeSymbol interfaceSymbol) + { + foreach (var x in Type.AllInterfaces) + { + if (x.IsGenericType && + SymbolEqualityComparer.Default.Equals(x.ConstructedFrom, interfaceSymbol) && + SymbolEqualityComparer.Default.Equals(x.TypeArguments[0], Type)) + { + return true; + } + } + return false; + } } class SyntaxReceiver : ISyntaxReceiver diff --git a/src/UnitGenerator/UnitGenerator.csproj b/src/UnitGenerator/UnitGenerator.csproj index f78bdf1..3c85e2c 100644 --- a/src/UnitGenerator/UnitGenerator.csproj +++ b/src/UnitGenerator/UnitGenerator.csproj @@ -2,7 +2,7 @@ netstandard2.0 - preview + 12 enable $(NoWarn);CS1591 true