diff --git a/src/Vogen/BuildWorkItems.cs b/src/Vogen/BuildWorkItems.cs index e674e28669..61335065f7 100644 --- a/src/Vogen/BuildWorkItems.cs +++ b/src/Vogen/BuildWorkItems.cs @@ -75,6 +75,12 @@ internal static class BuildWorkItems globalConfig, funcForDefaultUnderlyingType: () => vogenKnownSymbols.Int32); + if (DuplicateCastOperatorsSpecified(config)) + { + context.ReportDiagnostic(DiagnosticsCatalogue.BothImplicitAndExplicitCastsSpecified(target.VoSymbolInformation)); + return null; + } + ReportErrorIfNestedType(context, voSymbolInformation, target.NestingInfo); if (config.UnderlyingType is null) @@ -157,6 +163,18 @@ RecordDeclarationSyntax rds when rds.IsKind(SyntaxKind.RecordStructDeclaration) }; } + private static bool DuplicateCastOperatorsSpecified(VogenConfiguration config) + { + var sag = config.StaticAbstractsGeneration; + var explicitFromPrimitive = config.FromPrimitiveCasting == CastOperator.Explicit || sag.HasFlag(StaticAbstractsGeneration.ExplicitCastFromPrimitive); + var implicitFromPrimitive = config.FromPrimitiveCasting == CastOperator.Implicit || sag.HasFlag(StaticAbstractsGeneration.ImplicitCastFromPrimitive); + + var explicitToPrimitive = config.ToPrimitiveCasting == CastOperator.Explicit || sag.HasFlag(StaticAbstractsGeneration.ExplicitCastToPrimitive); + var implicitToPrimitive = config.ToPrimitiveCasting == CastOperator.Implicit || sag.HasFlag(StaticAbstractsGeneration.ImplicitCastToPrimitive); + + return (explicitFromPrimitive && implicitFromPrimitive) || (explicitToPrimitive && implicitToPrimitive); + } + private static bool ShouldShowNullAnnotations(Compilation compilation, VoTarget target) { SemanticModel sm = compilation.GetSemanticModel(target.VoSyntaxInformation.SyntaxTree); diff --git a/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs b/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs index d6b09a0096..c47306f71b 100644 --- a/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs +++ b/src/Vogen/Diagnostics/DiagnosticsCatalogue.cs @@ -114,6 +114,11 @@ internal static class DiagnosticsCatalogue "Invalid custom exception", "{0} must have at least 1 public constructor with 1 parameter of type System.String"); + private static readonly DiagnosticDescriptor _bothImplicitAndExplicitCastsSpecified = CreateDescriptor( + RuleIdentifiers.BothImplicitAndExplicitCastsSpecified, + "Both implicit and explicit casts specified", + "'{0}' should have either an explicit or implicit cast for casting to or from the wrapper or primitive, but not both. Check that the global config isn't specifying a conflicting casting operator. Check 'toPrimitiveCasting', 'fromPrimitiveCasting', and 'staticAbstractsGeneration'. 'staticAbstractGeneration' defaults to explicit casting, so if you change the default, you need to change it here too. See issue 720 (https://github.com/SteveDunn/Vogen/issues/720) for more information."); + private static readonly DiagnosticDescriptor _voReferencedInAConversionMarkerMustExplicitlySpecifyPrimitive = CreateDescriptor( RuleIdentifiers.VoReferencedInAConversionMarkerMustExplicitlySpecifyPrimitive, "Value objects that are referenced in a conversion marker attribute must explicitly specify the primitive type", @@ -194,6 +199,9 @@ public static Diagnostic VoReferencedInAConversionMarkerMustExplicitlySpecifyPri voSymbol.Name, markerClassSymbol.Name); + public static Diagnostic BothImplicitAndExplicitCastsSpecified(INamedTypeSymbol voSymbol) => + Create(_bothImplicitAndExplicitCastsSpecified, voSymbol.Locations, voSymbol.Name); + public static Diagnostic TypesReferencedInAConversionMarkerMustBeaValueObjects(INamedTypeSymbol markerClassSymbol, INamedTypeSymbol voSymbol) => Create(_typesReferencedInAConversionMarkerMustBeaValueObjects, voSymbol.Locations, markerClassSymbol.Name, voSymbol.Name); diff --git a/src/Vogen/Diagnostics/RuleIdentifiers.cs b/src/Vogen/Diagnostics/RuleIdentifiers.cs index 652f65791c..32f2f4e430 100644 --- a/src/Vogen/Diagnostics/RuleIdentifiers.cs +++ b/src/Vogen/Diagnostics/RuleIdentifiers.cs @@ -42,4 +42,5 @@ public static class RuleIdentifiers public const string UseReadonlyStructInsteadOfStruct = "VOG033"; public const string DoNotCompareWithPrimitivesInEfCore = "VOG034"; public const string VoReferencedInAConversionMarkerMustExplicitlySpecifyPrimitive = "VOG035"; + public const string BothImplicitAndExplicitCastsSpecified = "VOG036"; } \ No newline at end of file diff --git a/src/Vogen/GenerateCodeForCastingOperators.cs b/src/Vogen/GenerateCodeForCastingOperators.cs index d65e80b888..2eb1ee4b98 100644 --- a/src/Vogen/GenerateCodeForCastingOperators.cs +++ b/src/Vogen/GenerateCodeForCastingOperators.cs @@ -1,44 +1,49 @@ using System.Text; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Vogen.Generators; namespace Vogen; public static class GenerateCodeForCastingOperators { - public static string GenerateImplementations(VoWorkItem item, TypeDeclarationSyntax tds) + public static string GenerateImplementations(GenerationParameters p, TypeDeclarationSyntax tds) { - var className = tds.Identifier; - var itemUnderlyingType = item.UnderlyingTypeFullName; + var item = p.WorkItem; + var wrapper = tds.Identifier; + var primitive = item.UnderlyingTypeFullName; string primitiveBang = item.Nullable.BangForUnderlying; StringBuilder sb = new(); - if (item.Config.FromPrimitiveCasting == CastOperator.Explicit) + var config = item.Config; + var sag = config.StaticAbstractsGeneration; + + if (config.FromPrimitiveCasting == CastOperator.Explicit || sag.HasFlag(StaticAbstractsGeneration.ExplicitCastFromPrimitive)) { - sb.AppendLine($"public static explicit operator {className}({itemUnderlyingType} value) => From(value);"); + sb.AppendLine($"public static explicit operator {wrapper}({primitive} value) => From(value);"); } // Generate the call to the Value property so that it throws if uninitialized. - if (item.Config.ToPrimitiveCasting == CastOperator.Explicit) + if (config.ToPrimitiveCasting == CastOperator.Explicit || sag.HasFlag(StaticAbstractsGeneration.ExplicitCastToPrimitive)) { - sb.AppendLine($"public static explicit operator {itemUnderlyingType}({className} value) => value.Value;"); + sb.AppendLine($"public static explicit operator {primitive}({wrapper} value) => value.Value;"); } // Generate the call to the _value field so that it doesn't throw if uninitialized. - if (item.Config.ToPrimitiveCasting == CastOperator.Implicit) + if (config.ToPrimitiveCasting == CastOperator.Implicit || sag.HasFlag(StaticAbstractsGeneration.ImplicitCastToPrimitive)) { - sb.AppendLine($"public static implicit operator {itemUnderlyingType}({className} vo) => vo._value{primitiveBang};"); + sb.AppendLine($"public static implicit operator {primitive}({wrapper} vo) => vo._value{primitiveBang};"); } - if (item.Config.FromPrimitiveCasting == CastOperator.Implicit) + if (config.FromPrimitiveCasting == CastOperator.Implicit || sag.HasFlag(StaticAbstractsGeneration.ImplicitCastFromPrimitive)) { if (item.NormalizeInputMethod is not null) { sb.AppendLine($$""" - public static implicit operator {{className}}({{itemUnderlyingType}} value) + public static implicit operator {{wrapper}}({{primitive}} value) { - return new {{className}}({{className}}.NormalizeInput(value)); + return new {{wrapper}}({{wrapper}}.NormalizeInput(value)); } """); @@ -46,9 +51,9 @@ public static string GenerateImplementations(VoWorkItem item, TypeDeclarationSyn else { sb.AppendLine($$""" - public static implicit operator {{className}}({{itemUnderlyingType}} value) + public static implicit operator {{wrapper}}({{primitive}} value) { - return new {{className}}(value); + return new {{wrapper}}(value); } """); } diff --git a/src/Vogen/Generators/ClassGenerator.cs b/src/Vogen/Generators/ClassGenerator.cs index ddd9b97144..b5dcf3382f 100644 --- a/src/Vogen/Generators/ClassGenerator.cs +++ b/src/Vogen/Generators/ClassGenerator.cs @@ -123,7 +123,7 @@ string GenerateCode() => $@" public static global::System.Boolean operator !=({className}{wrapperQ} left, {className}{wrapperQ} right) => !Equals(left, right); {GenerateCodeForEqualsMethodsAndOperators.GenerateEqualsOperatorsForPrimitivesIfNeeded(itemUnderlyingType, className, item)} - {GenerateCodeForCastingOperators.GenerateImplementations(item,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} + {GenerateCodeForCastingOperators.GenerateImplementations(parameters,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} {GenerateCodeForComparables.GenerateIComparableImplementationIfNeeded(item, tds)} {GenerateCodeForTryParse.GenerateAnyHoistedTryParseMethods(item)}{GenerateCodeForParse.GenerateAnyHoistedParseMethods(item)} diff --git a/src/Vogen/Generators/RecordClassGenerator.cs b/src/Vogen/Generators/RecordClassGenerator.cs index 96e6e2ed47..6398dd287c 100644 --- a/src/Vogen/Generators/RecordClassGenerator.cs +++ b/src/Vogen/Generators/RecordClassGenerator.cs @@ -127,7 +127,7 @@ string GenerateCode() => $@" {GenerateCodeForEqualsMethodsAndOperators.GenerateEqualsMethodsForAClass(item, tds)} {GenerateCodeForEqualsMethodsAndOperators.GenerateEqualsOperatorsForPrimitivesIfNeeded(itemUnderlyingType, wrapperName, item)} -{GenerateCodeForCastingOperators.GenerateImplementations(item,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} +{GenerateCodeForCastingOperators.GenerateImplementations(parameters,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} {GenerateCodeForComparables.GenerateIComparableImplementationIfNeeded(item, tds)} {GenerateCodeForTryParse.GenerateAnyHoistedTryParseMethods(item)}{GenerateCodeForParse.GenerateAnyHoistedParseMethods(item)} diff --git a/src/Vogen/Generators/RecordStructGenerator.cs b/src/Vogen/Generators/RecordStructGenerator.cs index 52ebeb5af6..69023abb2a 100644 --- a/src/Vogen/Generators/RecordStructGenerator.cs +++ b/src/Vogen/Generators/RecordStructGenerator.cs @@ -113,7 +113,7 @@ public readonly {itemUnderlyingType} Value {Util.GenerateIsInitializedMethod(true, item)} {GenerateCodeForStringComparers.GenerateIfNeeded(item, tds)} -{GenerateCodeForCastingOperators.GenerateImplementations(item,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} +{GenerateCodeForCastingOperators.GenerateImplementations(parameters,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} // only called internally when something has been deserialized into // its primitive type. private static {wrapperName} __Deserialize({itemUnderlyingType} value) diff --git a/src/Vogen/Generators/StructGenerator.cs b/src/Vogen/Generators/StructGenerator.cs index 3419ec2c85..95081c8d8e 100644 --- a/src/Vogen/Generators/StructGenerator.cs +++ b/src/Vogen/Generators/StructGenerator.cs @@ -105,7 +105,7 @@ public readonly {itemUnderlyingType} Value {GenerateCodeForStringComparers.GenerateIfNeeded(item, tds)} -{GenerateCodeForCastingOperators.GenerateImplementations(item,tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} +{GenerateCodeForCastingOperators.GenerateImplementations(parameters, tds)}{Util.GenerateGuidFactoryMethodIfNeeded(item)} // only called internally when something has been deserialized into // its primitive type. private static {structName} __Deserialize({itemUnderlyingType} value) diff --git a/tests/AnalyzerTests/GlobalConfig/SadTests.cs b/tests/AnalyzerTests/GlobalConfig/SadTests.cs index 3a680c0134..03c3d60f9c 100644 --- a/tests/AnalyzerTests/GlobalConfig/SadTests.cs +++ b/tests/AnalyzerTests/GlobalConfig/SadTests.cs @@ -9,6 +9,40 @@ namespace AnalyzerTests.GlobalConfig; public class SadTests { + [Fact] + public async Task Conflicting_casts() + { + var source = """ + using Vogen; + + [assembly: VogenDefaults( + toPrimitiveCasting: CastOperator.Implicit, + staticAbstractsGeneration: StaticAbstractsGeneration.MostCommon)] + + namespace MyApp; + + [ValueObject] + public readonly partial record struct ToDoItemId; + """; + + await new TestRunner() + .WithSource(source) + .ValidateWith(Validate) + .RunOnAllFrameworks(); + return; + + static void Validate(ImmutableArray diagnostics) + { + diagnostics.Should().HaveCount(1); + + Diagnostic diagnostic = diagnostics.Single(); + + diagnostic.Id.Should().Be("VOG036"); + diagnostic.ToString().Should().Be( + "(10,39): error VOG036: 'ToDoItemId' should have either an explicit or implicit cast for casting to or from the wrapper or primitive, but not both. Check that the global config isn't specifying a conflicting casting operator. Check 'toPrimitiveCasting', 'fromPrimitiveCasting', and 'staticAbstractsGeneration'. 'staticAbstractGeneration' defaults to explicit casting, so if you change the default, you need to change it here too. See issue 720 (https://github.com/SteveDunn/Vogen/issues/720) for more information."); + } + } + [Fact] public async Task Missing_any_constructors() { diff --git a/tests/SnapshotTests/BugFixes/Bug720_Inconsistent_casting_mixed_with_IVogen_generation.cs b/tests/SnapshotTests/BugFixes/Bug720_Inconsistent_casting_mixed_with_IVogen_generation.cs new file mode 100644 index 0000000000..1ff9d0ae65 --- /dev/null +++ b/tests/SnapshotTests/BugFixes/Bug720_Inconsistent_casting_mixed_with_IVogen_generation.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Shared; +using Vogen; + +namespace SnapshotTests.BugFixes; + +// See https://github.com/SteveDunn/Vogen/issues/720 +public class Bug720_Inconsistent_casting_mixed_with_IVogen_generation +{ + [Fact] + public async Task Works_when_the_static_abstracts_and_implementation_have_same_casting() + { + // we say that to primitive is implicit and we say that the static abstract interface matches. + var source = """ + using Vogen; + using static Vogen.StaticAbstractsGeneration; + + [assembly: VogenDefaults( + toPrimitiveCasting: CastOperator.Implicit, + staticAbstractsGeneration: ValueObjectsDeriveFromTheInterface | + EqualsOperators | + ExplicitCastFromPrimitive | + ImplicitCastToPrimitive | + FactoryMethods)] + + namespace MyApp; + + [ValueObject] + public readonly partial record struct ToDoItemId; + """; + + await new SnapshotRunner() + .WithSource(source) + .IgnoreInitialCompilationErrors() + .RunOn(TargetFramework.AspNetCore8_0); + } +} \ No newline at end of file diff --git a/tests/SnapshotTests/BugFixes/snapshots/snap-vAspNetCore8.0/Bug720_Inconsistent_casting_mixed_with_IVogen_generation.Works_when_the_static_abstracts_and_implementation_have_same_casting.verified.txt b/tests/SnapshotTests/BugFixes/snapshots/snap-vAspNetCore8.0/Bug720_Inconsistent_casting_mixed_with_IVogen_generation.Works_when_the_static_abstracts_and_implementation_have_same_casting.verified.txt new file mode 100644 index 0000000000..e5ac797404 --- /dev/null +++ b/tests/SnapshotTests/BugFixes/snapshots/snap-vAspNetCore8.0/Bug720_Inconsistent_casting_mixed_with_IVogen_generation.Works_when_the_static_abstracts_and_implementation_have_same_casting.verified.txt @@ -0,0 +1,845 @@ +[ +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +using System.Reflection; + +public class VogenSchemaFilterIngenerator : global::Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter +{ + private const BindingFlags _flags = BindingFlags.Public | BindingFlags.Instance; + public void Apply(global::Microsoft.OpenApi.Models.OpenApiSchema schema, global::Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context) + { + if (context.Type.GetCustomAttribute()is not { } attribute) + return; + var type = attribute.GetType(); + if (!type.IsGenericType || type.GenericTypeArguments.Length != 1) + { + return; + } + + var schemaValueObject = context.SchemaGenerator.GenerateSchema(type.GenericTypeArguments[0], context.SchemaRepository, context.MemberInfo, context.ParameterInfo); + TryCopyPublicProperties(schemaValueObject, schema); + } + + private static void TryCopyPublicProperties(T oldObject, T newObject) + where T : class + { + if (ReferenceEquals(oldObject, newObject)) + { + return; + } + + var type = typeof(T); + var propertyList = type.GetProperties(_flags); + if (propertyList.Length <= 0) + { + return; + } + + foreach (var newObjProp in propertyList) + { + var oldProp = type.GetProperty(newObjProp.Name, _flags)!; + if (!oldProp.CanRead || !newObjProp.CanWrite) + { + continue; + } + + var value = oldProp.GetValue(oldObject); + newObjProp.SetValue(newObject, value); + } + } +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 + +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 + +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 + +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 + +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 + +public static class VogenSwashbuckleExtensions +{ + public static global::Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions MapVogenTypesIngenerator(this global::Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions o) + { + global::Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.MapType(o, () => new global::Microsoft.OpenApi.Models.OpenApiSchema { Type = "integer", Format = "int32", Nullable = false }); +global::Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.MapType>(o, () => new global::Microsoft.OpenApi.Models.OpenApiSchema { Type = "integer", Format = "int32", Nullable = true }); + + + return o; + } +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +public interface IVogen + where TSelf : IVogen +{ + static abstract explicit operator TSelf(TPrimitive value); + static abstract implicit operator TPrimitive(TSelf value); + static abstract bool operator ==(TSelf left, TSelf right); + static abstract bool operator !=(TSelf left, TSelf right); + static abstract bool operator ==(TSelf left, TPrimitive right); + static abstract bool operator !=(TSelf left, TPrimitive right); + static abstract bool operator ==(TPrimitive left, TSelf right); + static abstract bool operator !=(TPrimitive left, TSelf right); + static abstract TSelf From(TPrimitive value); + static abstract bool TryFrom(TPrimitive value, out TSelf vo); +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace generator; +public class VogenTypesFactory : global::System.Text.Json.Serialization.JsonConverterFactory +{ + public VogenTypesFactory() + { + } + + private static readonly global::System.Collections.Generic.Dictionary> _lookup = new global::System.Collections.Generic.Dictionary> + { + { + typeof(global::MyApp.ToDoItemId), + new global::System.Lazy(() => new global::MyApp.ToDoItemId.ToDoItemIdSystemTextJsonConverter()) + } + }; + public override bool CanConvert(global::System.Type typeToConvert) => _lookup.ContainsKey(typeToConvert); + public override global::System.Text.Json.Serialization.JsonConverter CreateConverter(global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) => _lookup[typeToConvert].Value; +} + +// ------------------------------------------------------------------------------ +// +// This code was generated by a source generator named Vogen (https://github.com/SteveDunn/Vogen) +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +// Suppress warnings about [Obsolete] member usage in generated code. +#pragma warning disable CS0618 +// Suppress warnings for 'Override methods on comparable types'. +#pragma warning disable CA1036 +// Suppress Error MA0097 : A class that implements IComparable or IComparable should override comparison operators +#pragma warning disable MA0097 +// Suppress warning for 'The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.' +// The generator copies signatures from the BCL, e.g. for `TryParse`, and some of those have nullable annotations. +#pragma warning disable CS8669, CS8632 +// Suppress warnings about CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' +#pragma warning disable CS1591 +namespace MyApp +{ + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")] + [global::System.Text.Json.Serialization.JsonConverter(typeof(ToDoItemIdSystemTextJsonConverter))] + [global::System.ComponentModel.TypeConverter(typeof(ToDoItemIdTypeConverter))] + [global::System.Diagnostics.DebuggerTypeProxyAttribute(typeof(ToDoItemIdDebugView))] + [global::System.Diagnostics.DebuggerDisplayAttribute("Underlying type: System.Int32, Value = { _value }")] + public readonly partial record struct ToDoItemId : global::System.IEquatable, global::System.IEquatable, global::System.IComparable, global::System.IComparable, global::System.IParsable, global::System.ISpanParsable, global::System.IUtf8SpanParsable, global::System.IFormattable, global::System.ISpanFormattable, global::System.IUtf8SpanFormattable, IVogen + { +#if DEBUG +private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; +#endif +#if !VOGEN_NO_VALIDATION + private readonly global::System.Boolean _isInitialized; +#endif + private readonly System.Int32 _value; + /// + /// Gets the underlying value if set, otherwise a is thrown. + /// + public readonly System.Int32 Value + { + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [global::System.Diagnostics.DebuggerStepThroughAttribute] + get + { + EnsureInitialized(); + return _value; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + init + { + _value = value; + } + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public ToDoItemId() + { +#if DEBUG + _stackTrace = new global::System.Diagnostics.StackTrace(); +#endif +#if !VOGEN_NO_VALIDATION + _isInitialized = false; +#endif + _value = default; + } + + [global::System.Diagnostics.DebuggerStepThroughAttribute] + private ToDoItemId(System.Int32 value) + { + _value = value; +#if !VOGEN_NO_VALIDATION + _isInitialized = true; +#endif + } + + /// + /// Builds an instance from the provided underlying type. + /// + /// The underlying type. + /// An instance of this type. + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static ToDoItemId From(System.Int32 value) + { + return new ToDoItemId(value); + } + + /// + /// Tries to build an instance from the provided underlying type. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, false will be returned. + /// + /// The underlying type. + /// An instance of the value object. + /// True if the value object can be built, otherwise false. + +#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + public static bool TryFrom( +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + System.Int32 value, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] +#endif + out ToDoItemId vo) +#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member because of nullability attributes. + + { + vo = new ToDoItemId(value); + return true; + } + + /// + /// Tries to build an instance from the provided underlying value. + /// If a normalization method is provided, it will be called. + /// If validation is provided, and it fails, an error will be returned. + /// + /// The primitive value. + /// A containing either the value object, or an error. + public static Vogen.ValueObjectOrError TryFrom(System.Int32 value) + { + return new Vogen.ValueObjectOrError(new ToDoItemId(value)); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] +#if VOGEN_NO_VALIDATION +#pragma warning disable CS8775 + public readonly bool IsInitialized() => true; +#pragma warning restore CS8775 +#else + public readonly bool IsInitialized() => _isInitialized; +#endif + public static explicit operator ToDoItemId(System.Int32 value) => From(value); + public static implicit operator System.Int32(ToDoItemId vo) => vo._value; + // only called internally when something has been deserialized into + // its primitive type. + private static ToDoItemId __Deserialize(System.Int32 value) + { + return new ToDoItemId(value); + } + + public readonly global::System.Boolean Equals(ToDoItemId other) + { + // It's possible to create uninitialized instances via converters such as EfCore (HasDefaultValue), which call Equals. + // We treat anything uninitialized as not equal to anything, even other uninitialized instances of this type. + if (!IsInitialized() || !other.IsInitialized()) + return false; + return global::System.Collections.Generic.EqualityComparer.Default.Equals(Value, other.Value); + } + + public global::System.Boolean Equals(ToDoItemId other, global::System.Collections.Generic.IEqualityComparer comparer) + { + return comparer.Equals(this, other); + } + + public readonly global::System.Boolean Equals(System.Int32 primitive) + { + return Value.Equals(primitive); + } + + public static global::System.Boolean operator ==(ToDoItemId left, System.Int32 right) => left.Value.Equals(right); + public static global::System.Boolean operator ==(System.Int32 left, ToDoItemId right) => right.Value.Equals(left); + public static global::System.Boolean operator !=(System.Int32 left, ToDoItemId right) => !(left == right); + public static global::System.Boolean operator !=(ToDoItemId left, System.Int32 right) => !(left == right); + public int CompareTo(ToDoItemId other) => Value.CompareTo(other.Value); + public int CompareTo(object other) + { + if (other is null) + return 1; + if (other is ToDoItemId x) + return CompareTo(x); + ThrowHelper.ThrowArgumentException("Cannot compare to object as it is not of type ToDoItemId", nameof(other)); + return 0; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan utf8Text, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(utf8Text, style, provider, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan utf8Text, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(utf8Text, provider, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan utf8Text, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(utf8Text, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(s, style, provider, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(s, provider, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(global::System.ReadOnlySpan s, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(s, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(s, style, provider, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, global::System.IFormatProvider provider, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(s, provider, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// True if the value could a) be parsed by the underlying type, and b) passes any validation (after running any optional normalization). + /// + public static global::System.Boolean TryParse(string s, +#if NETCOREAPP3_0_OR_GREATER +[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + out ToDoItemId result) + { + if (System.Int32.TryParse(s, out var __v)) + { + result = new ToDoItemId(__v); + return true; + } + + result = default; + return false; + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(global::System.ReadOnlySpan utf8Text, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider) + { + var r = System.Int32.Parse(utf8Text, style, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(global::System.ReadOnlySpan utf8Text, global::System.IFormatProvider provider) + { + var r = System.Int32.Parse(utf8Text, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(global::System.ReadOnlySpan s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider) + { + var r = System.Int32.Parse(s, style, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(global::System.ReadOnlySpan s, global::System.IFormatProvider provider) + { + var r = System.Int32.Parse(s, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(string s) + { + var r = System.Int32.Parse(s); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(string s, global::System.Globalization.NumberStyles style) + { + var r = System.Int32.Parse(s, style); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(string s, global::System.Globalization.NumberStyles style, global::System.IFormatProvider provider) + { + var r = System.Int32.Parse(s, style, provider); + return From(r); + } + + /// + /// + /// + /// + /// The value created by calling the Parse method on the primitive. + /// + /// Thrown when the value can be parsed, but is not valid. + public static ToDoItemId Parse(string s, global::System.IFormatProvider provider) + { + var r = System.Int32.Parse(s, provider); + return From(r); + } + +#nullable disable + /// + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string format, global::System.IFormatProvider provider) + { + return IsInitialized() ? Value.ToString(format, provider) : "[UNINITIALIZED]"; + } + + /// + public bool TryFormat(global::System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] global::System.ReadOnlySpan format, global::System.IFormatProvider provider) + { + charsWritten = default; + return IsInitialized() ? Value.TryFormat(destination, out charsWritten, format, provider) : true; + } + + /// + public bool TryFormat(global::System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] global::System.ReadOnlySpan format, global::System.IFormatProvider provider) + { + bytesWritten = default; + return IsInitialized() ? Value.TryFormat(utf8Destination, out bytesWritten, format, provider) : true; + } + +#nullable restore + public readonly override global::System.Int32 GetHashCode() + { + return global::System.Collections.Generic.EqualityComparer.Default.GetHashCode(Value); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private readonly void EnsureInitialized() + { + if (!IsInitialized()) + { +#if DEBUG + ThrowHelper.ThrowWhenNotInitialized(_stackTrace); +#else + ThrowHelper.ThrowWhenNotInitialized(); +#endif + } + } + + // record enumerates fields - we just want our Value and to throw if it's not initialized. + /// + public override global::System.String ToString() => IsInitialized() ? Value.ToString() ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString(global::System.IFormatProvider provider) => IsInitialized() ? Value.ToString(provider) ?? "" : "[UNINITIALIZED]"; + /// + public global::System.String ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string format) => IsInitialized() ? Value.ToString(format) ?? "" : "[UNINITIALIZED]"; +#nullable disable + /// + /// Converts a ToDoItemId to or from JSON. + /// + public class ToDoItemIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter + { + public override ToDoItemId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { +#if NET5_0_OR_GREATER + return ToDoItemId.__Deserialize(global::System.Text.Json.JsonSerializer.Deserialize(ref reader, (global::System.Text.Json.Serialization.Metadata.JsonTypeInfo)options.GetTypeInfo(typeof(global::System.Int32)))); +#else + return ToDoItemId.__Deserialize(reader.GetInt32()); +#endif + } + + public override void Write(System.Text.Json.Utf8JsonWriter writer, ToDoItemId value, global::System.Text.Json.JsonSerializerOptions options) + { +#if NET5_0_OR_GREATER + global::System.Text.Json.JsonSerializer.Serialize(writer, value.Value, options); +#else + writer.WriteNumberValue(value.Value); +#endif + } +#if NET6_0_OR_GREATER + public override ToDoItemId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) + { + return ToDoItemId.__Deserialize(global::System.Int32.Parse(reader.GetString(), global::System.Globalization.NumberStyles.Any, global::System.Globalization.CultureInfo.InvariantCulture)); + } + + public override void WriteAsPropertyName(System.Text.Json.Utf8JsonWriter writer, ToDoItemId value, global::System.Text.Json.JsonSerializerOptions options) + { + writer.WritePropertyName(value.Value.ToString(global::System.Globalization.CultureInfo.InvariantCulture)); + } +#endif + } + +#nullable restore +#nullable disable + class ToDoItemIdTypeConverter : global::System.ComponentModel.TypeConverter + { + public override global::System.Boolean CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Int32) || sourceType == typeof(global::System.String) || base.CanConvertFrom(context, sourceType); + } + + public override global::System.Object ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value) + { + return value switch + { + global::System.Int32 intValue => ToDoItemId.__Deserialize(intValue), + global::System.String stringValue when !global::System.String.IsNullOrEmpty(stringValue) && global::System.Int32.TryParse(stringValue, out var result) => ToDoItemId.__Deserialize(result), + _ => base.ConvertFrom(context, culture, value), + }; + } + + public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Type sourceType) + { + return sourceType == typeof(global::System.Int32) || sourceType == typeof(global::System.String) || base.CanConvertTo(context, sourceType); + } + + public override object ConvertTo(global::System.ComponentModel.ITypeDescriptorContext context, global::System.Globalization.CultureInfo culture, global::System.Object value, global::System.Type destinationType) + { + if (value is ToDoItemId idValue) + { + if (destinationType == typeof(global::System.Int32)) + { + return idValue.Value; + } + + if (destinationType == typeof(global::System.String)) + { + return idValue.Value.ToString(); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + +#nullable restore +#nullable disable + internal sealed class ToDoItemIdDebugView + { + private readonly ToDoItemId _t; + ToDoItemIdDebugView(ToDoItemId t) + { + _t = t; + } + + public global::System.Boolean IsInitialized => _t.IsInitialized(); + public global::System.String UnderlyingType => "System.Int32"; + public global::System.String Value => _t.IsInitialized() ? _t._value.ToString() : "[not initialized]"; +#if DEBUG + public global::System.String CreatedWith => _t._stackTrace.ToString() ?? "the From method"; +#endif + public global::System.String Conversions => @"Default"; + } + +#nullable restore + static class ThrowHelper + { +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowInvalidOperationException(string message) => throw new global::System.InvalidOperationException(message); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowArgumentException(string message, string arg) => throw new global::System.ArgumentException(message, arg); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenCreatedWithNull() => throw new global::Vogen.ValueObjectValidationException("Cannot create a value object with null."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized() => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object."); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenNotInitialized(global::System.Diagnostics.StackTrace stackTrace) => throw new global::Vogen.ValueObjectValidationException("Use of uninitialized Value Object at: " + stackTrace ?? ""); +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute] +#endif + internal static void ThrowWhenValidationFails(Vogen.Validation validation) + { + var ex = new global::Vogen.ValueObjectValidationException(validation.ErrorMessage); + if (validation.Data is not null) + { + foreach (var kvp in validation.Data) + { + ex.Data[kvp.Key] = kvp.Value; + } + } + + throw ex; + } + } + } +} +] \ No newline at end of file diff --git a/tests/SnapshotTests/StaticAbstracts/StaticAbstractTests.cs b/tests/SnapshotTests/StaticAbstracts/StaticAbstractTests.cs index 12b711ef61..0024b7be09 100644 --- a/tests/SnapshotTests/StaticAbstracts/StaticAbstractTests.cs +++ b/tests/SnapshotTests/StaticAbstracts/StaticAbstractTests.cs @@ -99,6 +99,8 @@ public async Task Omits_equality_operators_to_primitives_if_specified_in_global_ [assembly: VogenDefaults( primitiveEqualityGeneration: PrimitiveEqualityGeneration.Omit, conversions: Conversions.None, + toPrimitiveCasting: CastOperator.Implicit, + fromPrimitiveCasting: CastOperator.Implicit, staticAbstractsGeneration: StaticAbstractsGeneration.FactoryMethods | StaticAbstractsGeneration.EqualsOperators | StaticAbstractsGeneration.ImplicitCastFromPrimitive | StaticAbstractsGeneration.ImplicitCastToPrimitive)] [ValueObject] @@ -119,8 +121,13 @@ public async Task Emits_equality_operators_to_primitives_if_specified_in_global_ [assembly: VogenDefaults( primitiveEqualityGeneration: PrimitiveEqualityGeneration.GenerateOperatorsAndMethods, - conversions: Conversions.None, - staticAbstractsGeneration: StaticAbstractsGeneration.FactoryMethods | StaticAbstractsGeneration.EqualsOperators | StaticAbstractsGeneration.ImplicitCastFromPrimitive | StaticAbstractsGeneration.ImplicitCastToPrimitive)] + conversions: Conversions.None, + toPrimitiveCasting: CastOperator.Implicit, + fromPrimitiveCasting: CastOperator.Implicit, + staticAbstractsGeneration: StaticAbstractsGeneration.FactoryMethods | + StaticAbstractsGeneration.EqualsOperators | + StaticAbstractsGeneration.ImplicitCastFromPrimitive | + StaticAbstractsGeneration.ImplicitCastToPrimitive)] [ValueObject] public partial class MyVo { } diff --git a/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Emits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt b/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Emits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt index c171aef84b..919c98c50e 100644 --- a/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Emits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt +++ b/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Emits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt @@ -239,8 +239,12 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public static global::System.Boolean operator ==(System.Guid left, MyVo right) => right.Value.Equals(left); public static global::System.Boolean operator !=(System.Guid left, MyVo right) => !(left == right); public static global::System.Boolean operator !=(MyVo left, System.Guid right) => !(left == right); - public static explicit operator MyVo(System.Guid value) => From(value); - public static explicit operator System.Guid(MyVo value) => value.Value; + public static implicit operator System.Guid(MyVo vo) => vo._value; + public static implicit operator MyVo(System.Guid value) + { + return new MyVo(value); + } + public int CompareTo(MyVo other) { if (other is null) diff --git a/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Omits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt b/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Omits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt index 65848a24ef..df7ce01ba6 100644 --- a/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Omits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt +++ b/tests/SnapshotTests/StaticAbstracts/snapshots/snap-v8.0/StaticAbstractTests.Omits_equality_operators_to_primitives_if_specified_in_global_config.verified.txt @@ -226,8 +226,12 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public static global::System.Boolean operator ==(MyVo left, MyVo right) => Equals(left, right); public static global::System.Boolean operator !=(MyVo left, MyVo right) => !Equals(left, right); - public static explicit operator MyVo(System.Guid value) => From(value); - public static explicit operator System.Guid(MyVo value) => value.Value; + public static implicit operator System.Guid(MyVo vo) => vo._value; + public static implicit operator MyVo(System.Guid value) + { + return new MyVo(value); + } + public int CompareTo(MyVo other) { if (other is null) diff --git a/tests/Testbench/Program.cs b/tests/Testbench/Program.cs index f39dcd30f1..8151d0bd20 100644 --- a/tests/Testbench/Program.cs +++ b/tests/Testbench/Program.cs @@ -2,9 +2,22 @@ // ReSharper disable UnusedVariable - +using Vogen; using iformattable_infinite_loop; +using static Vogen.StaticAbstractsGeneration; + +[assembly: VogenDefaults( + toPrimitiveCasting: CastOperator.Implicit, + staticAbstractsGeneration: ValueObjectsDeriveFromTheInterface | + EqualsOperators | + ImplicitCastFromPrimitive | + ImplicitCastToPrimitive | + FactoryMethods)] + InfiniteLoopRunner.Run(); +[ValueObject] +public readonly partial record struct ToDoItemId; +