diff --git a/src/Orleans.CodeGenerator/CodeGeneratorOptions.cs b/src/Orleans.CodeGenerator/CodeGeneratorOptions.cs index 905e2ce3c68..939e02b4e37 100644 --- a/src/Orleans.CodeGenerator/CodeGeneratorOptions.cs +++ b/src/Orleans.CodeGenerator/CodeGeneratorOptions.cs @@ -41,4 +41,8 @@ public class CodeGeneratorOptions /// Gets or sets a value indicating whether generated serializers and copiers support adding members using .NET Hot Reload. /// public bool HotReloadSafe { get; set; } + + internal bool SupportsUnsafeAccessors { get; set; } + internal bool SupportsGenericUnsafeAccessors { get; set; } + internal bool SupportsVolatileUnsafeAccessors { get; set; } } diff --git a/src/Orleans.CodeGenerator/CopierGenerator.cs b/src/Orleans.CodeGenerator/CopierGenerator.cs index d746d88430e..28bf33b9b8e 100644 --- a/src/Orleans.CodeGenerator/CopierGenerator.cs +++ b/src/Orleans.CodeGenerator/CopierGenerator.cs @@ -131,33 +131,7 @@ static MemberDeclarationSyntax GetFieldDeclaration(GeneratedFieldDescription des SingletonSeparatedList(VariableDeclarator(accessor.FieldName).WithInitializer(EqualsValueClause(accessor.InitializationSyntax))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)); case FieldAccessorDescription accessor when accessor.InitializationSyntax == null: - //[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_Amount")] - //extern static void SetAmount(External instance, int value); - return - MethodDeclaration( - PredefinedType(Token(SyntaxKind.VoidKeyword)), - accessor.AccessorName) - .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ExternKeyword), Token(SyntaxKind.StaticKeyword)) - .AddAttributeLists(AttributeList(SingletonSeparatedList( - Attribute(IdentifierName("System.Runtime.CompilerServices.UnsafeAccessor")) - .AddArgumentListArguments( - AttributeArgument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName("System.Runtime.CompilerServices.UnsafeAccessorKind"), - IdentifierName("Method"))), - AttributeArgument( - LiteralExpression( - SyntaxKind.StringLiteralExpression, - Literal($"set_{accessor.FieldName}"))) - .WithNameEquals(NameEquals("Name")))))) - .WithParameterList( - ParameterList(SeparatedList( - [ - Parameter(Identifier("instance")).WithType(accessor.ContainingType), - Parameter(Identifier("value")).WithType(description.FieldType) - ]))) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + return GetUnsafeAccessorDeclaration(accessor); default: var declaration = FieldDeclaration(VariableDeclaration(description.FieldType, SingletonSeparatedList(VariableDeclarator(description.FieldName)))) .AddModifiers(Token(SyntaxKind.PrivateKeyword)); @@ -264,15 +238,7 @@ private List GetFieldDescriptions( { if (onlyDeepFields && member.IsShallowCopyable) continue; - if (member.GetGetterFieldDescription() is { } getterFieldDescription) - { - fields.Add(getterFieldDescription); - } - - if (member.GetSetterFieldDescription() is { } setterFieldDescription) - { - fields.Add(setterFieldDescription); - } + AddFieldAccessorDescriptions(fields, member); } for (var hookIndex = 0; hookIndex < serializationHooks.Count; ++hookIndex) diff --git a/src/Orleans.CodeGenerator/LibraryTypes.cs b/src/Orleans.CodeGenerator/LibraryTypes.cs index 7a21f207325..6f17d26f2fe 100644 --- a/src/Orleans.CodeGenerator/LibraryTypes.cs +++ b/src/Orleans.CodeGenerator/LibraryTypes.cs @@ -42,6 +42,7 @@ private LibraryTypes(Compilation compilation) IInvokable = Type("Orleans.Serialization.Invocation.IInvokable"); InvokeMethodNameAttribute = Type("Orleans.InvokeMethodNameAttribute"); RuntimeHelpers = Type("System.Runtime.CompilerServices.RuntimeHelpers"); + HasUnsafeAccessorAttribute = TypeOrDefault("System.Runtime.CompilerServices.UnsafeAccessorAttribute") is not null; InvokableCustomInitializerAttribute = Type("Orleans.InvokableCustomInitializerAttribute"); DefaultInvokableBaseTypeAttribute = Type("Orleans.DefaultInvokableBaseTypeAttribute"); GenerateCodeForDeclaringAssemblyAttribute = Type("Orleans.GenerateCodeForDeclaringAssemblyAttribute"); @@ -206,6 +207,7 @@ INamedTypeSymbol Type(string metadataName) } public INamedTypeSymbol Action_2 { get; private set; } + public bool HasUnsafeAccessorAttribute { get; } public INamedTypeSymbol TypeManifestProviderBase { get; private set; } public INamedTypeSymbol Field { get; private set; } public INamedTypeSymbol DeepCopier_1 { get; private set; } diff --git a/src/Orleans.CodeGenerator/SerializerGenerator.cs b/src/Orleans.CodeGenerator/SerializerGenerator.cs index 0e1337f2659..d4b35e44987 100644 --- a/src/Orleans.CodeGenerator/SerializerGenerator.cs +++ b/src/Orleans.CodeGenerator/SerializerGenerator.cs @@ -167,33 +167,7 @@ static MemberDeclarationSyntax GetFieldDeclaration(GeneratedFieldDescription des SingletonSeparatedList(VariableDeclarator(accessor.FieldName).WithInitializer(EqualsValueClause(accessor.InitializationSyntax))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)); case FieldAccessorDescription accessor when accessor.InitializationSyntax == null: - //[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_Amount")] - //extern static void SetAmount(External instance, int value); - return - MethodDeclaration( - PredefinedType(Token(SyntaxKind.VoidKeyword)), - accessor.AccessorName) - .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ExternKeyword), Token(SyntaxKind.StaticKeyword)) - .AddAttributeLists(AttributeList(SingletonSeparatedList( - Attribute(IdentifierName("System.Runtime.CompilerServices.UnsafeAccessor")) - .AddArgumentListArguments( - AttributeArgument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName("System.Runtime.CompilerServices.UnsafeAccessorKind"), - IdentifierName("Method"))), - AttributeArgument( - LiteralExpression( - SyntaxKind.StringLiteralExpression, - Literal($"set_{accessor.FieldName}"))) - .WithNameEquals(NameEquals("Name")))))) - .WithParameterList( - ParameterList(SeparatedList( - [ - Parameter(Identifier("instance")).WithType(accessor.ContainingType), - Parameter(Identifier("value")).WithType(description.FieldType) - ]))) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + return GetUnsafeAccessorDeclaration(accessor); default: var declaration = FieldDeclaration(VariableDeclaration(description.FieldType, SingletonSeparatedList(VariableDeclarator(description.FieldName)))) .AddModifiers(Token(SyntaxKind.PrivateKeyword)); @@ -202,6 +176,39 @@ static MemberDeclarationSyntax GetFieldDeclaration(GeneratedFieldDescription des } } + internal static MethodDeclarationSyntax GetUnsafeAccessorDeclaration(FieldAccessorDescription accessor) + { + var instance = Parameter(Identifier("instance")).WithType(accessor.ContainingType); + if (accessor.ContainingTypeIsValueType) + { + instance = instance.AddModifiers(Token(SyntaxKind.RefKeyword)); + } + + var parameters = new List { instance }; + TypeSyntax returnType; + if (accessor.IsUnsafeFieldAccessor) + { + returnType = RefType(accessor.FieldType); + } + else + { + returnType = PredefinedType(Token(SyntaxKind.VoidKeyword)); + parameters.Add(Parameter(Identifier("value")).WithType(accessor.FieldType)); + } + + return MethodDeclaration(returnType, accessor.AccessorName) + .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ExternKeyword), Token(SyntaxKind.StaticKeyword)) + .AddAttributeLists(AttributeList(SingletonSeparatedList( + Attribute(ParseName("global::System.Runtime.CompilerServices.UnsafeAccessorAttribute")) + .AddArgumentListArguments( + AttributeArgument(ParseName("global::System.Runtime.CompilerServices.UnsafeAccessorKind") + .Member(accessor.IsUnsafeFieldAccessor ? "Field" : "Method")), + AttributeArgument((accessor.IsUnsafeFieldAccessor ? accessor.FieldName : $"set_{accessor.FieldName}").GetLiteralExpression()) + .WithNameEquals(NameEquals("Name")))))) + .WithParameterList(ParameterList(SeparatedList(parameters))) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + } + private ConstructorDeclarationSyntax? GenerateConstructor(string simpleClassName, List fieldDescriptions) { var codecProviderAdded = false; @@ -311,15 +318,7 @@ private List GetFieldDescriptions( foreach (var member in members) { - if (member.GetGetterFieldDescription() is { } getterFieldDescription) - { - fields.Add(getterFieldDescription); - } - - if (member.GetSetterFieldDescription() is { } setterFieldDescription) - { - fields.Add(setterFieldDescription); - } + AddFieldAccessorDescriptions(fields, member); } for (var hookIndex = 0; hookIndex < serializableTypeDescription.SerializationHooks.Count; ++hookIndex) @@ -375,6 +374,21 @@ CodecFieldDescription GetCodecDescription(IMemberDescription member, string fiel } } + internal static void AddFieldAccessorDescriptions(List fields, ISerializableMember member) + { + var getter = member.GetGetterFieldDescription(); + if (getter is not null) + { + fields.Add(getter); + } + + if (member.GetSetterFieldDescription() is { } setter + && (!setter.IsUnsafeFieldAccessor || setter.AccessorName != getter?.AccessorName)) + { + fields.Add(setter); + } + } + private BaseCodecFieldDescription GetBaseTypeField(ISerializableTypeDescription serializableTypeDescription) { var baseType = serializableTypeDescription.BaseType; @@ -1127,13 +1141,15 @@ internal sealed class CodecFieldTypeFieldDescription(TypeSyntax fieldType, strin public override bool IsInjected => false; } - internal sealed class FieldAccessorDescription(TypeSyntax containingType, TypeSyntax fieldType, string fieldName, string accessorName, ExpressionSyntax? initializationSyntax = null, bool lazyInitialization = false) : GeneratedFieldDescription(fieldType, fieldName) + internal sealed class FieldAccessorDescription(TypeSyntax containingType, TypeSyntax fieldType, string fieldName, string accessorName, ExpressionSyntax? initializationSyntax = null, bool lazyInitialization = false, bool isUnsafeFieldAccessor = false, bool containingTypeIsValueType = false) : GeneratedFieldDescription(fieldType, fieldName) { public override bool IsInjected => false; public override bool LazyInitialization { get; } = lazyInitialization; public readonly string AccessorName = accessorName; public readonly TypeSyntax ContainingType = containingType; public readonly ExpressionSyntax? InitializationSyntax = initializationSyntax; + public bool IsUnsafeFieldAccessor { get; } = isUnsafeFieldAccessor; + public bool ContainingTypeIsValueType { get; } = containingTypeIsValueType; } internal sealed class SerializationHookFieldDescription(TypeSyntax fieldType, string fieldName) : GeneratedFieldDescription(fieldType, fieldName) @@ -1247,14 +1263,14 @@ internal class SerializableMember(IGeneratorServices generatorServices, IMemberD public string MemberName => Field?.Name ?? Property?.Name ?? Member.Symbol.Name; /// - /// Gets the name of the getter field. + /// Gets the name of the accessor used to read the field. /// - private string GetterFieldName => GeneratedFieldNames.Accessor("getField", Member); + private string GetterFieldName => GeneratedFieldNames.Accessor(UseUnsafeFieldAccessor ? "accessField" : "getField", Member); /// - /// Gets the name of the setter field. + /// Gets the name of the accessor used to write the field. /// - private string SetterFieldName => GeneratedFieldNames.Accessor("setField", Member); + private string SetterFieldName => GeneratedFieldNames.Accessor(UseUnsafeFieldAccessor ? "accessField" : "setField", Member); /// /// Gets a value indicating if the member is a property. @@ -1382,9 +1398,11 @@ public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax va instanceArg = instanceArg.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } - return - InvocationExpression(GetAccessorExpression(GetSetterFieldDescription()!)) - .AddArgumentListArguments(instanceArg, Argument(value)); + var accessor = GetSetterFieldDescription()!; + var invocation = InvocationExpression(GetAccessorExpression(accessor)).AddArgumentListArguments(instanceArg); + return accessor.IsUnsafeFieldAccessor + ? AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, invocation, value) + : invocation.AddArgumentListArguments(Argument(value)); } private static ExpressionSyntax GetAccessorExpression(FieldAccessorDescription accessor) @@ -1396,22 +1414,30 @@ private static ExpressionSyntax GetAccessorExpression(FieldAccessorDescription a { if (IsGettableField || IsGettableProperty) return null; return GetFieldAccessor(ContainingType, TypeSyntax, MemberName, GetterFieldName, LibraryTypes, false, - IsPrimaryConstructorParameter && IsProperty, _generatorServices.Options.HotReloadSafe); + IsPrimaryConstructorParameter && IsProperty, _generatorServices.Options.HotReloadSafe, UseUnsafeFieldAccessor); } public FieldAccessorDescription? GetSetterFieldDescription() { if (IsSettableField || IsSettableProperty) return null; return GetFieldAccessor(ContainingType, TypeSyntax, MemberName, SetterFieldName, LibraryTypes, true, - IsPrimaryConstructorParameter && IsProperty, _generatorServices.Options.HotReloadSafe); + IsPrimaryConstructorParameter && IsProperty, _generatorServices.Options.HotReloadSafe, UseUnsafeFieldAccessor); } - public static FieldAccessorDescription GetFieldAccessor(INamedTypeSymbol containingType, TypeSyntax fieldType, string fieldName, string accessorName, LibraryTypes library, bool setter, bool useUnsafeAccessor = false, bool lazyInitialization = false) + private bool UseUnsafeFieldAccessor => Field is not null + && !_generatorServices.Options.HotReloadSafe + && _generatorServices.Options.SupportsUnsafeAccessors + && LibraryTypes.HasUnsafeAccessorAttribute + && (!Field.IsVolatile || _generatorServices.Options.SupportsVolatileUnsafeAccessors) + && (!ContainingType.IsGenericType || _generatorServices.Options.SupportsGenericUnsafeAccessors); + + public static FieldAccessorDescription GetFieldAccessor(INamedTypeSymbol containingType, TypeSyntax fieldType, string fieldName, string accessorName, LibraryTypes library, bool setter, bool useUnsafeAccessor = false, bool lazyInitialization = false, bool useUnsafeFieldAccessor = false) { var containingTypeSyntax = containingType.ToTypeSyntax(); - if (useUnsafeAccessor) - return new(containingTypeSyntax, fieldType, fieldName, accessorName); + if (useUnsafeFieldAccessor || useUnsafeAccessor) + return new(containingTypeSyntax, fieldType, fieldName, accessorName, + isUnsafeFieldAccessor: useUnsafeFieldAccessor, containingTypeIsValueType: containingType.IsValueType); var valueType = containingType.IsValueType; diff --git a/src/Orleans.CodeGenerator/SourceGeneratorOptionsParser.cs b/src/Orleans.CodeGenerator/SourceGeneratorOptionsParser.cs index 262c58338e9..908a5437fb0 100644 --- a/src/Orleans.CodeGenerator/SourceGeneratorOptionsParser.cs +++ b/src/Orleans.CodeGenerator/SourceGeneratorOptionsParser.cs @@ -15,6 +15,9 @@ internal static CodeGeneratorOptions CreateCodeGeneratorOptions(SourceGeneratorO GenerateFieldIds = options.GenerateFieldIds, GenerateCompatibilityInvokers = options.GenerateCompatibilityInvokers, HotReloadSafe = options.HotReload ?? false, + SupportsUnsafeAccessors = options.SupportsUnsafeAccessors, + SupportsGenericUnsafeAccessors = options.SupportsGenericUnsafeAccessors, + SupportsVolatileUnsafeAccessors = options.SupportsVolatileUnsafeAccessors, }; } @@ -59,6 +62,22 @@ internal static SourceGeneratorOptions ParseOptions(AnalyzerConfigOptions global result.HotReload = hotReload; } + if (globalOptions.TryGetValue("build_property.TargetFrameworkIdentifier", out var frameworkIdentifier) + && string.Equals(frameworkIdentifier, ".NETCoreApp", StringComparison.OrdinalIgnoreCase) + && globalOptions.TryGetValue("build_property.TargetFrameworkVersion", out var frameworkVersion)) + { + var versionText = frameworkVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase) + ? frameworkVersion.Substring(1) + : frameworkVersion; + if (Version.TryParse(versionText, out var version)) + { + result.SupportsUnsafeAccessors = version.Major >= 8; + result.SupportsGenericUnsafeAccessors = version.Major >= 9; + // Older targets can run on .NET 9.0.0, whose field matching rejects volatile modifiers. + result.SupportsVolatileUnsafeAccessors = version.Major >= 10; + } + } + return result; } @@ -74,12 +93,18 @@ internal struct SourceGeneratorOptions : IEquatable /// Enables hot-reload-safe code generation. /// public bool? HotReload { get; set; } + public bool SupportsUnsafeAccessors { get; set; } + public bool SupportsGenericUnsafeAccessors { get; set; } + public bool SupportsVolatileUnsafeAccessors { get; set; } public readonly bool Equals(SourceGeneratorOptions other) => GenerateFieldIds == other.GenerateFieldIds && GenerateCompatibilityInvokers == other.GenerateCompatibilityInvokers && AttachDebugger == other.AttachDebugger - && HotReload == other.HotReload; + && HotReload == other.HotReload + && SupportsUnsafeAccessors == other.SupportsUnsafeAccessors + && SupportsGenericUnsafeAccessors == other.SupportsGenericUnsafeAccessors + && SupportsVolatileUnsafeAccessors == other.SupportsVolatileUnsafeAccessors; public override readonly bool Equals(object obj) => obj is SourceGeneratorOptions other && Equals(other); @@ -91,6 +116,9 @@ public override readonly int GetHashCode() hash = hash * 31 + (GenerateCompatibilityInvokers ? 1 : 0); hash = hash * 31 + (AttachDebugger ? 1 : 0); hash = hash * 31 + (HotReload switch { true => 1, false => 2, null => 0 }); + hash = hash * 31 + (SupportsUnsafeAccessors ? 1 : 0); + hash = hash * 31 + (SupportsGenericUnsafeAccessors ? 1 : 0); + hash = hash * 31 + (SupportsVolatileUnsafeAccessors ? 1 : 0); return hash; } } diff --git a/src/Orleans.CodeGenerator/build/Microsoft.Orleans.CodeGenerator.props b/src/Orleans.CodeGenerator/build/Microsoft.Orleans.CodeGenerator.props index 0fa6f629446..1761a0dd25d 100644 --- a/src/Orleans.CodeGenerator/build/Microsoft.Orleans.CodeGenerator.props +++ b/src/Orleans.CodeGenerator/build/Microsoft.Orleans.CodeGenerator.props @@ -7,6 +7,8 @@ + + diff --git a/src/Orleans.Serialization/README.md b/src/Orleans.Serialization/README.md index 4e5fcf4e48b..f10cbfe3058 100644 --- a/src/Orleans.Serialization/README.md +++ b/src/Orleans.Serialization/README.md @@ -46,6 +46,37 @@ public class MyClass When fail-closed type validation is enabled, additional types can be allowed by configuring `TypeManifestOptions.AddAllowedType` or `TypeManifestOptions.AddAllowedAssembly`. +Generated serializers and copiers for C# payloads use ref-returning +`UnsafeAccessor` methods to read and restore private fields, readonly fields, +and auto-property backing fields on supported targets. Non-generic C# payloads +use this access on .NET 8 and later; generic C# payloads use it on .NET 9 and +later, with the payload's generic constraints preserved. Struct receivers are +passed by reference. This allows NativeAOT-compiled generated code for C# +payloads to restore get-only and init-only properties and to deep-copy values +stored in readonly fields. + +Generated C# codecs and copiers emit one ref-returning accessor per field for +both reading and writing on supported targets. + +The generator selects these capabilities from the SDK's target-framework +identifier and version and verifies that `UnsafeAccessorAttribute` is available +in the compilation references. + +For C# payloads, legacy targets and .NET 8 generic payloads use generated +field-access delegates. Setting `OrleansHotReload=true` retains lazily +initialized delegates for fields, so existing serializer and copier instances +can access members added by hot reload. + +Volatile fields use ref-returning accessors on .NET 10 and later. Earlier +targets restore private volatile fields through generated delegates, preserving +compatibility with runtimes affected by volatile-field signature matching. + +F# record and union field restoration uses the existing generated-delegate +strategy on JIT-enabled runtimes. + +NativeAOT applications also need statically available codecs, copiers, and +activators for their closed payload types. + ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://dotnet.github.io/orleans/docs/) diff --git a/test/Orleans.CodeGenerator.Tests/FieldAccessCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/FieldAccessCodegenTests.cs new file mode 100644 index 00000000000..39a21f39aaf --- /dev/null +++ b/test/Orleans.CodeGenerator.Tests/FieldAccessCodegenTests.cs @@ -0,0 +1,264 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Xunit; + +namespace Orleans.CodeGenerator.Tests; + +public class FieldAccessCodegenTests +{ + private const string Source = """ + using Orleans; + namespace TestProject; + + [GenerateSerializer] + public sealed class Fields + { + [Id(0)] private readonly int _value; + [Id(1)] public string ReadOnly { get; } = ""; + [Id(2)] public string InitOnly { get; init; } = ""; + [Id(3)] private string Private { get; set; } = ""; + public int Value => _value; + } + + [GenerateSerializer] + public struct StructFields + { + [Id(0)] private readonly int _value; + [Id(1)] private byte[] _bytes; + public int Value => _value; + } + + [GenerateSerializer] + public sealed class GenericFields where T : class, System.IEquatable, new() + { + [Id(0)] private readonly T _value; + [Id(1)] public T ReadOnly { get; } + public T Value => _value; + } + """; + + private const string VolatileSource = """ + [GenerateSerializer] + public sealed class VolatileFields + { + [Id(0)] private volatile int _number; + [Id(1)] private volatile string _text; + [Id(2)] private volatile byte[] _bytes; + [Id(3)] private int _ordinary; + [Id(4)] public volatile int Direct; + public int Number => _number; + public string Text => _text; + public byte[] Bytes => _bytes; + public int Ordinary => _ordinary; + } + + [GenerateSerializer] + public struct VolatileStructFields + { + [Id(0)] private volatile int _number; + [Id(1)] private volatile byte[] _bytes; + public int Number => _number; + public byte[] Bytes => _bytes; + } + + [GenerateSerializer] + public sealed class GenericVolatileFields where T : class + { + [Id(0)] private volatile T _value; + public T Value => _value; + } + """; + + [Fact] + public async Task PrivateAndBackingFieldsUseRefReturningUnsafeAccessors() + { + var generated = await Generate(hotReload: false, frameworkVersion: "v10.0"); + Assert.Contains("UnsafeAccessorKind.Field, Name = \"_value\"", generated); + Assert.Contains("UnsafeAccessorKind.Field, Name = \"k__BackingField\"", generated); + Assert.Contains("UnsafeAccessorKind.Field, Name = \"k__BackingField\"", generated); + Assert.Contains("UnsafeAccessorKind.Field, Name = \"k__BackingField\"", generated); + Assert.Contains("private extern static ref int accessField_0(global::TestProject.Fields instance);", generated); + Assert.Contains("accessField_0(result) =", generated); + Assert.Contains("accessField_0(instance) =", generated); + Assert.Contains("private extern static ref int accessField_0(ref global::TestProject.StructFields instance);", generated); + } + + [Theory] + [InlineData(".NETCoreApp", "v7.0", false, false)] + [InlineData(".NETCoreApp", "v8.0", true, false)] + [InlineData(".NETCoreApp", "v9.0", true, true)] + [InlineData(".NETCoreApp", "v10.0", true, true)] + [InlineData(".NETCoreApp", "v11.0", true, true)] + [InlineData(".NETStandard", "v2.1", false, false)] + [InlineData(".NETStandard", "v9.0", false, false)] + [InlineData(".NETFramework", "v4.8", false, false)] + [InlineData("", "", false, false)] + public async Task GenericAccessorsMatchTargetRuntimeSupportAndConstraints( + string frameworkIdentifier, string frameworkVersion, bool expectFieldAccessors, bool expectGenericAccessors) + { + var generated = await Generate(hotReload: false, frameworkIdentifier, frameworkVersion); + Assert.Contains("where T : class, global::System.IEquatable, new()", generated); + Assert.Equal(expectFieldAccessors, generated.Contains( + "private extern static ref int accessField_0(global::TestProject.Fields instance);", StringComparison.Ordinal)); + Assert.Equal(expectFieldAccessors, generated.Contains( + "private extern static ref int accessField_0(ref global::TestProject.StructFields instance);", StringComparison.Ordinal)); + if (expectGenericAccessors) + { + Assert.Contains("private extern static ref T accessField_0(global::TestProject.GenericFields instance);", generated); + Assert.DoesNotContain("Utilities.FieldAccessor", generated); + } + else + { + Assert.Contains("Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.GenericFields)", generated); + Assert.DoesNotContain("extern static ref T", generated); + } + } + + [Theory] + [InlineData("Codec_Fields", 4)] + [InlineData("Copier_Fields", 4)] + [InlineData("Codec_StructFields", 2)] + [InlineData("Copier_StructFields", 1)] + [InlineData("Codec_GenericFields", 2)] + [InlineData("Copier_GenericFields", 2)] + public async Task SingleUnsafeAccessorServesFieldReadsAndWrites(string className, int expectedAccessors) + { + var generated = await Generate(hotReload: false); + var root = CSharpSyntaxTree.ParseText(generated, cancellationToken: TestContext.Current.CancellationToken) + .GetRoot(TestContext.Current.CancellationToken); + var type = Assert.Single(root.DescendantNodes().OfType(), c => c.Identifier.ValueText == className); + var accessors = type.Members.OfType() + .Where(m => m.Modifiers.Any(SyntaxKind.ExternKeyword)).ToList(); + + Assert.Equal(expectedAccessors, accessors.Count); + Assert.Equal(expectedAccessors, accessors.Select(m => m.Identifier.ValueText).Distinct(StringComparer.Ordinal).Count()); + Assert.All(accessors, accessor => + { + Assert.StartsWith("accessField_", accessor.Identifier.ValueText, StringComparison.Ordinal); + Assert.IsType(accessor.ReturnType); + var attribute = Assert.Single(accessor.AttributeLists.SelectMany(a => a.Attributes)); + Assert.Contains("UnsafeAccessorKind.Field", attribute.ToString(), StringComparison.Ordinal); + }); + + var target = className.Contains("StructFields", StringComparison.Ordinal) ? "accessField_1" : "accessField_0"; + var calls = type.DescendantNodes().OfType() + .Where(i => i.Expression.ToString() == target).ToList(); + Assert.Contains(calls, call => call.Parent is AssignmentExpressionSyntax assignment && assignment.Left == call); + Assert.Contains(calls, call => call.Parent is not AssignmentExpressionSyntax assignment || assignment.Right == call); + Assert.DoesNotContain("getField_", type.ToString(), StringComparison.Ordinal); + Assert.DoesNotContain("setField_", type.ToString(), StringComparison.Ordinal); + } + + [Theory] + [InlineData("v8.0")] + [InlineData("v9.0")] + [InlineData("v10.0")] + public async Task HotReloadRetainsLazyDelegateFieldAccess(string frameworkVersion) + { + var generated = await Generate(hotReload: true, frameworkVersion: frameworkVersion); + Assert.DoesNotContain("UnsafeAccessorKind.Field", generated); + Assert.Contains("Utilities.FieldAccessor.GetGetter", generated); + Assert.Contains("Utilities.FieldAccessor.GetValueGetter", generated); + Assert.Contains("Utilities.FieldAccessor.GetReferenceSetter", generated); + Assert.Contains("Utilities.FieldAccessor.GetValueSetter", generated); + Assert.Contains("(setField_0 ??=", generated); + } + + [Theory] + [InlineData("v8.0", false, false)] + [InlineData("v9.0", false, false)] + [InlineData("v10.0", false, true)] + [InlineData("v11.0", false, true)] + [InlineData("v10.0", true, false)] + public async Task VolatileFieldsFollowTargetRuntimeCapability(string frameworkVersion, bool hotReload, bool expectUnsafeAccessors) + { + var generated = await Generate(hotReload, frameworkVersion: frameworkVersion, source: Source + VolatileSource); + var root = CSharpSyntaxTree.ParseText(generated, cancellationToken: TestContext.Current.CancellationToken) + .GetRoot(TestContext.Current.CancellationToken); + foreach (var className in new[] { "Codec_VolatileFields", "Copier_VolatileFields", "Codec_VolatileStructFields", "Copier_VolatileStructFields", "Codec_GenericVolatileFields", "Copier_GenericVolatileFields" }) + { + var type = Assert.Single(root.DescendantNodes().OfType(), c => c.Identifier.ValueText == className); + var source = type.ToString(); + if (expectUnsafeAccessors) + { + Assert.Contains("accessField_", source, StringComparison.Ordinal); + Assert.DoesNotContain("Utilities.FieldAccessor", source, StringComparison.Ordinal); + var accessors = type.Members.OfType().Where(m => m.Modifiers.Any(SyntaxKind.ExternKeyword)).ToList(); + Assert.Equal(accessors.Count, accessors.Select(m => m.Identifier.ValueText).Distinct(StringComparer.Ordinal).Count()); + } + else + { + Assert.Contains("Utilities.FieldAccessor", source, StringComparison.Ordinal); + Assert.Contains("getField_", source, StringComparison.Ordinal); + Assert.Contains("setField_", source, StringComparison.Ordinal); + Assert.DoesNotContain("UnsafeAccessorKind.Field, Name = \"_number\"", source, StringComparison.Ordinal); + Assert.DoesNotContain("UnsafeAccessorKind.Field, Name = \"_bytes\"", source, StringComparison.Ordinal); + Assert.DoesNotContain("UnsafeAccessorKind.Field, Name = \"_value\"", source, StringComparison.Ordinal); + } + } + + var codec = Assert.Single(root.DescendantNodes().OfType(), c => c.Identifier.ValueText == "Codec_VolatileFields").ToString(); + Assert.Equal(!hotReload, codec.Contains("accessField_3", StringComparison.Ordinal)); + Assert.Contains("instance.Direct", codec, StringComparison.Ordinal); + Assert.Contains("instance.Direct =", codec, StringComparison.Ordinal); + } + + [Theory] + [InlineData("v7.0", "v8.0", true)] + [InlineData("v8.0", "v9.0", true)] + [InlineData("v9.0", "v10.0", true)] + [InlineData("v10.0", "v11.0", false)] + public async Task TargetFrameworkChangesInvalidateOnlyChangedAccessorCapabilities( + string beforeVersion, string afterVersion, bool expectChanges) + { + var compilation = await TestCompilationHelper.CreateCompilation(Source + VolatileSource); + GeneratorDriver driver = CSharpGeneratorDriver.Create( + generators: [new OrleansSerializationSourceGenerator().AsSourceGenerator()], + optionsProvider: CreateOptions(hotReload: false, ".NETCoreApp", beforeVersion), + driverOptions: new GeneratorDriverOptions(default, trackIncrementalGeneratorSteps: true)); + driver = driver.RunGenerators(compilation, TestContext.Current.CancellationToken); + var before = driver.GetRunResult().Results.Single(); + + driver = driver.WithUpdatedAnalyzerConfigOptions(CreateOptions(hotReload: false, ".NETCoreApp", afterVersion)); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var output, out var diagnostics, TestContext.Current.CancellationToken); + var after = driver.GetRunResult().Results.Single(); + Assert.Empty(diagnostics); + Assert.Empty(output.GetDiagnostics(TestContext.Current.CancellationToken).Where(d => d.Severity == DiagnosticSeverity.Error)); + Assert.Equal(expectChanges, GetSource(before) != GetSource(after)); + var optionsStep = Assert.Single(after.TrackedSteps[OrleansSerializationSourceGenerator.GeneratorOptionsTrackingName]); + Assert.Equal( + expectChanges ? IncrementalStepRunReason.Modified : IncrementalStepRunReason.Unchanged, + Assert.Single(optionsStep.Outputs).Reason); + if (!expectChanges) + { + var serializerStep = Assert.Single(after.TrackedSteps[OrleansSerializationSourceGenerator.SerializerOutputsTrackingName]); + Assert.Equal(IncrementalStepRunReason.Cached, Assert.Single(serializerStep.Outputs).Reason); + } + } + + private static async Task Generate(bool hotReload, string frameworkIdentifier = ".NETCoreApp", string frameworkVersion = "v10.0", string source = Source) + { + var compilation = await TestCompilationHelper.CreateCompilation(source); + GeneratorDriver driver = CSharpGeneratorDriver.Create( + generators: [new OrleansSerializationSourceGenerator().AsSourceGenerator()], + optionsProvider: CreateOptions(hotReload, frameworkIdentifier, frameworkVersion)); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var output, out var diagnostics, TestContext.Current.CancellationToken); + Assert.Empty(diagnostics); + Assert.Empty(output.GetDiagnostics(TestContext.Current.CancellationToken).Where(d => d.Severity == DiagnosticSeverity.Error)); + return GetSource(driver.GetRunResult().Results.Single()); + } + + private static string GetSource(GeneratorRunResult result) + => string.Join(Environment.NewLine, result.GeneratedSources.Select(s => s.SourceText.ToString())); + + private static AnalyzerConfigOptionsProvider CreateOptions( + bool hotReload, string frameworkIdentifier, string frameworkVersion) + => TestCompilationHelper.CreateOptionsProvider(new Dictionary + { + ["build_property.orleanshotreload"] = hotReload ? "true" : "false", + ["build_property.TargetFrameworkIdentifier"] = frameworkIdentifier, + ["build_property.TargetFrameworkVersion"] = frameworkVersion, + }); +} diff --git a/test/Orleans.CodeGenerator.Tests/FieldAccessRuntimeTests.cs b/test/Orleans.CodeGenerator.Tests/FieldAccessRuntimeTests.cs new file mode 100644 index 00000000000..809d847ff95 --- /dev/null +++ b/test/Orleans.CodeGenerator.Tests/FieldAccessRuntimeTests.cs @@ -0,0 +1,89 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Orleans.Serialization.NativeAotFieldAccessSmoke; +using Xunit; + +namespace Orleans.CodeGenerator.Tests; + +public class FieldAccessRuntimeTests +{ + [Fact] + public void GeneratedGenericAccessorShapeMatchesTargetFramework() + { + var codecType = typeof(OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Codec_ConstrainedFields); + const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; +#if NET8_0 + Assert.Equal(typeof(Func, string>), codecType.GetField("getField_0", flags)!.FieldType); + Assert.Equal(typeof(Action, string>), codecType.GetField("setField_0", flags)!.FieldType); + Assert.Null(codecType.GetMethod("setField_0", flags)); + Assert.Null(codecType.GetMethod("accessField_0", flags)); +#else + Assert.Null(codecType.GetField("setField_0", flags)); + Assert.Null(codecType.GetMethod("getField_0", flags)); + Assert.Null(codecType.GetMethod("setField_0", flags)); + var method = codecType.GetMethod("accessField_0", flags)!; + Assert.Equal(typeof(string).MakeByRefType(), method.ReturnType); + var accessor = method.GetCustomAttribute()!; + Assert.Equal(UnsafeAccessorKind.Field, accessor.Kind); + Assert.Equal("_value", accessor.Name); +#endif + } + + [Fact] + public void GeneratedPrivateFieldAccessorsAreDeclaredOncePerField() + { + const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; + foreach (var type in new[] + { + typeof(OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Codec_PrivateFields), + typeof(OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Copier_PrivateFields), + }) + { + var accessors = type.GetMethods(flags) + .Select(method => method.GetCustomAttribute()) + .Where(attribute => attribute is not null) + .ToList(); + Assert.Equal(6, accessors.Count); + Assert.Equal(6, accessors.Select(attribute => attribute!.Name).Distinct(StringComparer.Ordinal).Count()); + Assert.All(accessors, attribute => Assert.Equal(UnsafeAccessorKind.Field, attribute!.Kind)); + } + } + + [Fact] + public void GeneratedVolatileAccessorShapeMatchesTargetFramework() + { + var codecType = typeof(OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Codec_VolatileFields); + const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; +#if NET8_0 + Assert.Equal(typeof(Func), codecType.GetField("getField_0", flags)!.FieldType); + Assert.Equal(typeof(Action), codecType.GetField("setField_0", flags)!.FieldType); + Assert.Null(codecType.GetMethod("accessField_0", flags)); +#else + Assert.Null(codecType.GetField("getField_0", flags)); + Assert.Null(codecType.GetField("setField_0", flags)); + Assert.Equal(typeof(int).MakeByRefType(), codecType.GetMethod("accessField_0", flags)!.ReturnType); +#endif + Assert.Equal(typeof(int).MakeByRefType(), codecType.GetMethod("accessField_3", flags)!.ReturnType); + } + + [Fact] + public void PrivateAndBackingFieldsRoundTripAndCopy() => FieldAccessChecks.PrivateAndBackingFieldsRoundTripAndCopy(); + + [Fact] + public void ConstrainedGenericFieldsRoundTripAndCopy() => FieldAccessChecks.ConstrainedGenericFieldsRoundTripAndCopy(); + + [Fact] + public void GenericStructFieldsRoundTripAndCopy() => FieldAccessChecks.GenericStructFieldsRoundTripAndCopy(); + + [Fact] + public void NestedGenericFieldsRoundTripAndCopy() => FieldAccessChecks.NestedGenericFieldsRoundTripAndCopy(); + + [Fact] + public void VolatileFieldsRoundTripAndCopy() => FieldAccessChecks.VolatileFieldsRoundTripAndCopy(); + + [Fact] + public void VolatileValueFieldsRoundTripAndCopy() => FieldAccessChecks.VolatileValueFieldsRoundTripAndCopy(); + + [Fact] + public void GenericVolatileFieldsRoundTripAndCopy() => FieldAccessChecks.GenericVolatileFieldsRoundTripAndCopy(); +} diff --git a/test/Orleans.CodeGenerator.Tests/HotReloadCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/HotReloadCodegenTests.cs index 1dffcf2a747..1a3d55f094c 100644 --- a/test/Orleans.CodeGenerator.Tests/HotReloadCodegenTests.cs +++ b/test/Orleans.CodeGenerator.Tests/HotReloadCodegenTests.cs @@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; @@ -102,12 +101,13 @@ public async Task DebugBuildsInitializeGeneratedFieldsLazily() } [Fact] - public async Task ReleaseBuildsKeepEagerInitialization() + public async Task ReleaseBuildsUseStaticFieldAccessorsAndEagerCodecs() { var generated = await Generate(GreetingV2, OptimizationLevel.Release); var codec = GetClass(generated, "Codec_Greeting").NormalizeWhitespace().ToFullString(); - Assert.Contains("private static readonly global::System.Action setField_0 = ", codec); + Assert.Contains("private extern static ref string accessField_0(global::TestProject.Greeting instance);", codec); + Assert.DoesNotContain("Utilities.FieldAccessor", codec); Assert.Contains("private readonly global::Orleans.Serialization.Codecs.ListCodec _codec_List_Int32_", codec); Assert.Contains("private readonly global::System.Type _type_List_Int32_", codec); Assert.Contains("= typeof(global::System.Collections.Generic.List);", codec); @@ -189,9 +189,9 @@ public async Task AccessorFieldsAreNamedByFieldId() { var codec = GetClass(await Generate(GreetingV2, OptimizationLevel.Release), "Codec_Greeting").NormalizeWhitespace().ToFullString(); - Assert.Contains("setField_0 = ", codec); - Assert.Contains("setField_3 = ", codec); - Assert.Contains("setField_4 = ", codec); + Assert.Contains("accessField_0(global::TestProject.Greeting instance)", codec); + Assert.Contains("accessField_3(global::TestProject.Greeting instance)", codec); + Assert.Contains("accessField_4(global::TestProject.Greeting instance)", codec); Assert.DoesNotContain("setField0", codec); Assert.DoesNotContain("setField1", codec); } @@ -398,7 +398,7 @@ private static async Task Generate(string code, OptimizationLevel level, var generator = new OrleansSerializationSourceGenerator().AsSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create( generators: [generator], - optionsProvider: globalOptions is null ? null : new TestAnalyzerConfigOptionsProvider(globalOptions), + optionsProvider: TestCompilationHelper.CreateOptionsProvider(globalOptions), driverOptions: new GeneratorDriverOptions(default)); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics, TestContext.Current.CancellationToken); Assert.Empty(diagnostics); @@ -420,7 +420,7 @@ private static async Task GenerateCompilation( var generator = new OrleansSerializationSourceGenerator().AsSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create( generators: [generator], - optionsProvider: new TestAnalyzerConfigOptionsProvider(globalOptions), + optionsProvider: TestCompilationHelper.CreateOptionsProvider(globalOptions), driverOptions: new GeneratorDriverOptions(default)); driver.RunGeneratorsAndUpdateCompilation( compilation, @@ -504,19 +504,4 @@ private static string FormatDiagnostics(IEnumerable diagnostics) } } } - - private sealed class TestAnalyzerConfigOptionsProvider(IReadOnlyDictionary globalOptions) : AnalyzerConfigOptionsProvider - { - private static readonly AnalyzerConfigOptions EmptyOptions = new TestAnalyzerConfigOptions(new Dictionary()); - private readonly AnalyzerConfigOptions _globalOptions = new TestAnalyzerConfigOptions(globalOptions); - - public override AnalyzerConfigOptions GlobalOptions => _globalOptions; - public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => EmptyOptions; - public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => EmptyOptions; - } - - private sealed class TestAnalyzerConfigOptions(IReadOnlyDictionary options) : AnalyzerConfigOptions - { - public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value!); - } } diff --git a/test/Orleans.CodeGenerator.Tests/Orleans.CodeGenerator.Tests.csproj b/test/Orleans.CodeGenerator.Tests/Orleans.CodeGenerator.Tests.csproj index 23bb751f97c..a19ce278f58 100644 --- a/test/Orleans.CodeGenerator.Tests/Orleans.CodeGenerator.Tests.csproj +++ b/test/Orleans.CodeGenerator.Tests/Orleans.CodeGenerator.Tests.csproj @@ -26,6 +26,8 @@ + + diff --git a/test/Orleans.CodeGenerator.Tests/OrleansSourceGeneratorTests.cs b/test/Orleans.CodeGenerator.Tests/OrleansSourceGeneratorTests.cs index 1e73004b8b9..2825aa60a1c 100644 --- a/test/Orleans.CodeGenerator.Tests/OrleansSourceGeneratorTests.cs +++ b/test/Orleans.CodeGenerator.Tests/OrleansSourceGeneratorTests.cs @@ -1,7 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGenerator.Diagnostics; using Orleans.Serialization; @@ -588,7 +587,7 @@ public class UsesGenericWithCtor { [Id(0)] public GenericWithCtor StringGen { get; set; } -}"); +}", snapshotName: nameof(TestGenericClassWithConstructorParameters)); [Fact] public Task TestClassWithNoPublicConstructors() => AssertSuccessfulSourceGeneration( @@ -2213,6 +2212,7 @@ public sealed record PrimaryCtorRecord( var generator = new OrleansSerializationSourceGenerator().AsSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create( generators: [generator], + optionsProvider: TestCompilationHelper.CreateOptionsProvider(), driverOptions: new GeneratorDriverOptions(default)); driver = driver.RunGeneratorsAndUpdateCompilation( compilation, @@ -2245,14 +2245,10 @@ private static GeneratorRunResult RunSourceGenerator( CSharpCompilation compilation, IReadOnlyDictionary? globalOptions = null) { - AnalyzerConfigOptionsProvider? optionsProvider = globalOptions is null - ? null - : new TestAnalyzerConfigOptionsProvider(globalOptions); - var generator = new OrleansSerializationSourceGenerator().AsSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create( generators: [generator], - optionsProvider: optionsProvider, + optionsProvider: TestCompilationHelper.CreateOptionsProvider(globalOptions), driverOptions: new GeneratorDriverOptions(default)); driver = driver.RunGenerators(compilation); return driver.GetRunResult().Results.Single(); @@ -2263,7 +2259,7 @@ private static GeneratorRunResult RunSourceGenerator( /// and verifies successful generation without errors. /// Uses snapshot testing to verify the generated code matches expectations. /// - private static async Task AssertSuccessfulSourceGeneration(string code) + private static async Task AssertSuccessfulSourceGeneration(string code, string? snapshotName = null) { var projectName = "TestProject"; var compilation = await CreateCompilation(code, projectName); @@ -2276,7 +2272,14 @@ private static async Task AssertSuccessfulSourceGeneration(string code) Assert.StartsWith($"{projectName}.orleans.", generated.HintName, StringComparison.Ordinal)); var generatedSource = ConcatenateGeneratedSources(result); - await Verify(generatedSource, extension: "cs").UseDirectory("snapshots"); + var snapshot = Verify(generatedSource, extension: "cs").UseDirectory("snapshots"); + if (snapshotName is not null) + { + var supportsGenericAccessors = SourceGeneratorOptionsParser.ParseOptions(TestCompilationHelper.CreateOptionsProvider().GlobalOptions).SupportsGenericUnsafeAccessors; + snapshot = snapshot.UseFileName($"{nameof(OrleansSourceGeneratorTests)}.{snapshotName}.{(supportsGenericAccessors ? "UnsafeAccessor" : "FieldAccessor")}"); + } + + await snapshot; } private static string ConcatenateGeneratedSources(GeneratorRunResult result) @@ -2538,35 +2541,6 @@ invocation.Expression is MemberAccessExpressionSyntax .OrderBy(static name => name, StringComparer.Ordinal) .ToArray(); - private sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider - { - private static readonly AnalyzerConfigOptions EmptyOptions = new TestAnalyzerConfigOptions(new Dictionary()); - private readonly AnalyzerConfigOptions _globalOptions; - - public TestAnalyzerConfigOptionsProvider(IReadOnlyDictionary globalOptions) - { - _globalOptions = new TestAnalyzerConfigOptions(globalOptions); - } - - public override AnalyzerConfigOptions GlobalOptions => _globalOptions; - - public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => EmptyOptions; - - public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => EmptyOptions; - } - - private sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions - { - private readonly IReadOnlyDictionary _options; - - public TestAnalyzerConfigOptions(IReadOnlyDictionary options) - { - _options = options; - } - - public override bool TryGetValue(string key, out string value) => _options.TryGetValue(key, out value!); - } - private sealed class NamespaceMembers(SyntaxList usings) { public SyntaxList Usings { get; } = usings; diff --git a/test/Orleans.CodeGenerator.Tests/SourceGeneratorOptionsParserTests.cs b/test/Orleans.CodeGenerator.Tests/SourceGeneratorOptionsParserTests.cs new file mode 100644 index 00000000000..1a1d497a651 --- /dev/null +++ b/test/Orleans.CodeGenerator.Tests/SourceGeneratorOptionsParserTests.cs @@ -0,0 +1,95 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Xunit; + +namespace Orleans.CodeGenerator.Tests; + +public class SourceGeneratorOptionsParserTests +{ + [Theory] + [InlineData(".NETCoreApp", "v8.0", true, false, false)] + [InlineData(".NETCoreApp", "v9.0", true, true, false)] + [InlineData(".NETCoreApp", "v10.0", true, true, true)] + [InlineData(".NETCoreApp", "v11.0", true, true, true)] + [InlineData(".netcoreapp", "V10.0", true, true, true)] + [InlineData(".NETCoreApp", "10.0", true, true, true)] + [InlineData(".NETCoreApp", "v7.0", false, false, false)] + [InlineData(".NETStandard", "v2.1", false, false, false)] + [InlineData(".NETStandard", "v10.0", false, false, false)] + [InlineData(".NETFramework", "v4.8", false, false, false)] + [InlineData(null, "v10.0", false, false, false)] + [InlineData(".NETCoreApp", null, false, false, false)] + [InlineData(null, null, false, false, false)] + [InlineData(".NETCoreApp", "", false, false, false)] + [InlineData(".NETCoreApp", "invalid", false, false, false)] + [InlineData(".NETCoreApp", "net10.0-windows", false, false, false)] + public void TargetFrameworkMetadataControlsAccessorCapabilities( + string? identifier, string? version, bool expectFieldAccessors, bool expectGenericAccessors, bool expectVolatileAccessors) + { + var options = Parse(identifier, version); + Assert.Equal(expectFieldAccessors, options.SupportsUnsafeAccessors); + Assert.Equal(expectGenericAccessors, options.SupportsGenericUnsafeAccessors); + Assert.Equal(expectVolatileAccessors, options.SupportsVolatileUnsafeAccessors); + + var generatorOptions = SourceGeneratorOptionsParser.CreateCodeGeneratorOptions(options); + Assert.Equal(expectFieldAccessors, generatorOptions.SupportsUnsafeAccessors); + Assert.Equal(expectGenericAccessors, generatorOptions.SupportsGenericUnsafeAccessors); + Assert.Equal(expectVolatileAccessors, generatorOptions.SupportsVolatileUnsafeAccessors); + } + + [Theory] + [InlineData("v7.0", "v8.0", false)] + [InlineData("v8.0", "v9.0", false)] + [InlineData("v9.0", "v10.0", false)] + [InlineData("v10.0", "v11.0", true)] + public void TargetFrameworkCapabilitiesParticipateInOptionsEquality(string beforeVersion, string afterVersion, bool expectEqual) + { + var before = Parse(".NETCoreApp", beforeVersion); + var after = Parse(".NETCoreApp", afterVersion); + Assert.Equal(expectEqual, before.Equals(after)); + Assert.Equal(expectEqual, before.Equals((object)after)); + if (expectEqual) + { + Assert.Equal(before.GetHashCode(), after.GetHashCode()); + } + } + + [Theory] + [InlineData("net10.0")] + [InlineData("net10.0-windows10.0.19041.0")] + [InlineData("net10.0-android")] + public void PlatformTargetSuffixPreservesRuntimeCapabilities(string targetFramework) + { + var provider = TestCompilationHelper.CreateOptionsProvider(new Dictionary + { + ["build_property.TargetFramework"] = targetFramework, + ["build_property.TargetFrameworkIdentifier"] = ".NETCoreApp", + ["build_property.TargetFrameworkVersion"] = "v10.0", + }); + + var options = SourceGeneratorOptionsParser.ParseOptions(provider.GlobalOptions); + Assert.True(options.SupportsUnsafeAccessors); + Assert.True(options.SupportsGenericUnsafeAccessors); + Assert.True(options.SupportsVolatileUnsafeAccessors); + } + + private static SourceGeneratorOptions Parse(string? identifier, string? version) + { + var options = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (identifier is not null) + { + options.Add("build_property.TargetFrameworkIdentifier", identifier); + } + + if (version is not null) + { + options.Add("build_property.TargetFrameworkVersion", version); + } + + return SourceGeneratorOptionsParser.ParseOptions(new TestOptions(options)); + } + + private sealed class TestOptions(IReadOnlyDictionary options) : AnalyzerConfigOptions + { + public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value!); + } +} diff --git a/test/Orleans.CodeGenerator.Tests/TestCompilationHelper.cs b/test/Orleans.CodeGenerator.Tests/TestCompilationHelper.cs index 589e829147d..f4f5ec89fa4 100644 --- a/test/Orleans.CodeGenerator.Tests/TestCompilationHelper.cs +++ b/test/Orleans.CodeGenerator.Tests/TestCompilationHelper.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; @@ -21,6 +22,29 @@ internal static class TestCompilationHelper : StringComparer.Ordinal; private static readonly ImmutableArray FrameworkReferences = CreateFrameworkReferences(); + public static AnalyzerConfigOptionsProvider CreateOptionsProvider(IReadOnlyDictionary? globalOptions = null) + { + var options = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["build_property.TargetFrameworkIdentifier"] = ".NETCoreApp", +#if NET8_0 + ["build_property.TargetFrameworkVersion"] = "v8.0", +#else + ["build_property.TargetFrameworkVersion"] = "v10.0", +#endif + }; + + if (globalOptions is not null) + { + foreach (var (key, value) in globalOptions) + { + options[key] = value; + } + } + + return new TestOptionsProvider(options); + } + /// /// Creates a with the .NET framework and Orleans assembly references. /// @@ -101,4 +125,17 @@ internal static ImmutableArray GetFrameworkAssemblyPaths( .Concat(runtimeAssemblies.Values) .ToImmutableArray(); } + + private sealed class TestOptionsProvider(IReadOnlyDictionary options) : AnalyzerConfigOptionsProvider + { + private static readonly AnalyzerConfigOptions EmptyOptions = new TestOptions(new Dictionary()); + public override AnalyzerConfigOptions GlobalOptions { get; } = new TestOptions(options); + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => EmptyOptions; + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => EmptyOptions; + } + + private sealed class TestOptions(IReadOnlyDictionary options) : AnalyzerConfigOptions + { + public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value!); + } } diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicClassWithAnnotatedFields.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicClassWithAnnotatedFields.verified.cs index 1a98496a48f..1d663c3db85 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicClassWithAnnotatedFields.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicClassWithAnnotatedFields.verified.cs @@ -15,10 +15,10 @@ public sealed class Codec_DemoDataWithFields : global::Orleans.Serialization.Cod { private readonly global::System.Type _codecFieldType = typeof(global::TestProject.DemoDataWithFields); private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Func getField_0 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.DemoDataWithFields), "_intValue"); - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataWithFields), "_intValue"); - private static readonly global::System.Func getField_1 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.DemoDataWithFields), "_stringValue"); - private static readonly global::System.Action setField_1 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataWithFields), "_stringValue"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_intValue")] + private extern static ref int accessField_0(global::TestProject.DemoDataWithFields instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_stringValue")] + private extern static ref string accessField_1(global::TestProject.DemoDataWithFields instance); public Codec_DemoDataWithFields(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -28,8 +28,8 @@ public Codec_DemoDataWithFields(global::Orleans.Serialization.Activators.IActiva public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::TestProject.DemoDataWithFields instance) where TBufferWriter : global::System.Buffers.IBufferWriter { - global::Orleans.Serialization.Codecs.Int32Codec.WriteField(ref writer, 0U, getField_0(instance)); - global::Orleans.Serialization.Codecs.StringCodec.WriteField(ref writer, 1U, getField_1(instance)); + global::Orleans.Serialization.Codecs.Int32Codec.WriteField(ref writer, 0U, accessField_0(instance)); + global::Orleans.Serialization.Codecs.StringCodec.WriteField(ref writer, 1U, accessField_1(instance)); } [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] @@ -45,7 +45,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 0U) { - setField_0(instance, global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header)); + accessField_0(instance) = global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -54,7 +54,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. if (id == 1U) { - setField_1(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_1(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } @@ -102,10 +102,10 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_DemoDataWithFields : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Func getField_0 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.DemoDataWithFields), "_intValue"); - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataWithFields), "_intValue"); - private static readonly global::System.Func getField_1 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.DemoDataWithFields), "_stringValue"); - private static readonly global::System.Action setField_1 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataWithFields), "_stringValue"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_intValue")] + private extern static ref int accessField_0(global::TestProject.DemoDataWithFields instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_stringValue")] + private extern static ref string accessField_1(global::TestProject.DemoDataWithFields instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.DemoDataWithFields DeepCopy(global::TestProject.DemoDataWithFields original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -127,8 +127,8 @@ public Copier_DemoDataWithFields(global::Orleans.Serialization.Activators.IActiv [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DeepCopy(global::TestProject.DemoDataWithFields input, global::TestProject.DemoDataWithFields output, global::Orleans.Serialization.Cloning.CopyContext context) { - setField_0(output, getField_0(input)); - setField_1(output, getField_1(input)); + accessField_0(output) = accessField_0(input); + accessField_1(output) = accessField_1(input); } } diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassReferenceProperties.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassReferenceProperties.verified.cs index 57bbaf50a4e..6b2ca1caf1d 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassReferenceProperties.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassReferenceProperties.verified.cs @@ -15,7 +15,8 @@ public sealed class Codec_DemoData : global::Orleans.Serialization.Codecs.IField { private readonly global::System.Type _codecFieldType = typeof(global::TestProject.DemoData); private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_3 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoData), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_3(global::TestProject.DemoData instance); public Codec_DemoData(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -71,7 +72,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. if (id == 3U) { - setField_3(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_3(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } @@ -119,7 +120,8 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_DemoData : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_3 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoData), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_3(global::TestProject.DemoData instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.DemoData DeepCopy(global::TestProject.DemoData original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -144,7 +146,7 @@ public void DeepCopy(global::TestProject.DemoData input, global::TestProject.Dem output.NullableStringProp = input.NullableStringProp; output.StringProp = input.StringProp; output.RequiredStringProp = input.RequiredStringProp; - setField_3(output, input.RequiredStringPropInitOnly); + accessField_3(output) = input.RequiredStringPropInitOnly; } } diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithInterfaceConstructorParameter.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithInterfaceConstructorParameter.verified.cs index 5f9aab2843f..ec96aa263f1 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithInterfaceConstructorParameter.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithInterfaceConstructorParameter.verified.cs @@ -17,8 +17,8 @@ public sealed class Codec_InterfaceCtorParam : global::Orleans.Serialization.Cod private readonly global::Orleans.Serialization.Activators.IActivator _activator; private readonly global::System.Type _type_IMyInterface_B86E71B69F6C2271 = typeof(global::TestProject.IMyInterface); private readonly global::Orleans.Serialization.Codecs.IFieldCodec _codec_IMyInterface_B86E71B69F6C2271; - private static readonly global::System.Func getField_0 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.InterfaceCtorParam), "_iface"); - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.InterfaceCtorParam), "_iface"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_iface")] + private extern static ref global::TestProject.IMyInterface accessField_0(global::TestProject.InterfaceCtorParam instance); public Codec_InterfaceCtorParam(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -29,7 +29,7 @@ public Codec_InterfaceCtorParam(global::Orleans.Serialization.Activators.IActiva public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::TestProject.InterfaceCtorParam instance) where TBufferWriter : global::System.Buffers.IBufferWriter { - _codec_IMyInterface_B86E71B69F6C2271.WriteField(ref writer, 0U, _type_IMyInterface_B86E71B69F6C2271, getField_0(instance)); + _codec_IMyInterface_B86E71B69F6C2271.WriteField(ref writer, 0U, _type_IMyInterface_B86E71B69F6C2271, accessField_0(instance)); } [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] @@ -45,7 +45,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 0U) { - setField_0(instance, _codec_IMyInterface_B86E71B69F6C2271.ReadValue(ref reader, header)); + accessField_0(instance) = _codec_IMyInterface_B86E71B69F6C2271.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } @@ -94,8 +94,8 @@ public sealed class Copier_InterfaceCtorParam : global::Orleans.Serialization.Cl { private readonly global::Orleans.Serialization.Activators.IActivator _activator; private readonly global::Orleans.Serialization.Cloning.IDeepCopier _copier_IMyInterface_B86E71B69F6C2271; - private static readonly global::System.Func getField_0 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.InterfaceCtorParam), "_iface"); - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.InterfaceCtorParam), "_iface"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_iface")] + private extern static ref global::TestProject.IMyInterface accessField_0(global::TestProject.InterfaceCtorParam instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.InterfaceCtorParam DeepCopy(global::TestProject.InterfaceCtorParam original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -118,7 +118,7 @@ public Copier_InterfaceCtorParam(global::Orleans.Serialization.Activators.IActiv [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DeepCopy(global::TestProject.InterfaceCtorParam input, global::TestProject.InterfaceCtorParam output, global::Orleans.Serialization.Cloning.CopyContext context) { - setField_0(output, _copier_IMyInterface_B86E71B69F6C2271.DeepCopy(getField_0(input), context)); + accessField_0(output) = _copier_IMyInterface_B86E71B69F6C2271.DeepCopy(accessField_0(input), context); } } diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithOptionalConstructorParameters.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithOptionalConstructorParameters.verified.cs index 32fa46b7a2b..83455a84c07 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithOptionalConstructorParameters.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithOptionalConstructorParameters.verified.cs @@ -15,10 +15,10 @@ public sealed class Codec_OptionalCtorParams : global::Orleans.Serialization.Cod { private readonly global::System.Type _codecFieldType = typeof(global::TestProject.OptionalCtorParams); private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Func getField_0 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.OptionalCtorParams), "_x"); - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.OptionalCtorParams), "_x"); - private static readonly global::System.Func getField_1 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.OptionalCtorParams), "_y"); - private static readonly global::System.Action setField_1 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.OptionalCtorParams), "_y"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_x")] + private extern static ref int accessField_0(global::TestProject.OptionalCtorParams instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_y")] + private extern static ref string accessField_1(global::TestProject.OptionalCtorParams instance); public Codec_OptionalCtorParams(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -28,8 +28,8 @@ public Codec_OptionalCtorParams(global::Orleans.Serialization.Activators.IActiva public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::TestProject.OptionalCtorParams instance) where TBufferWriter : global::System.Buffers.IBufferWriter { - global::Orleans.Serialization.Codecs.Int32Codec.WriteField(ref writer, 0U, getField_0(instance)); - global::Orleans.Serialization.Codecs.StringCodec.WriteField(ref writer, 1U, getField_1(instance)); + global::Orleans.Serialization.Codecs.Int32Codec.WriteField(ref writer, 0U, accessField_0(instance)); + global::Orleans.Serialization.Codecs.StringCodec.WriteField(ref writer, 1U, accessField_1(instance)); } [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] @@ -45,7 +45,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 0U) { - setField_0(instance, global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header)); + accessField_0(instance) = global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -54,7 +54,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. if (id == 1U) { - setField_1(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_1(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } @@ -102,10 +102,10 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_OptionalCtorParams : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Func getField_0 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.OptionalCtorParams), "_x"); - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.OptionalCtorParams), "_x"); - private static readonly global::System.Func getField_1 = (global::System.Func)global::Orleans.Serialization.Utilities.FieldAccessor.GetGetter(typeof(global::TestProject.OptionalCtorParams), "_y"); - private static readonly global::System.Action setField_1 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.OptionalCtorParams), "_y"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_x")] + private extern static ref int accessField_0(global::TestProject.OptionalCtorParams instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_y")] + private extern static ref string accessField_1(global::TestProject.OptionalCtorParams instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.OptionalCtorParams DeepCopy(global::TestProject.OptionalCtorParams original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -127,8 +127,8 @@ public Copier_OptionalCtorParams(global::Orleans.Serialization.Activators.IActiv [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DeepCopy(global::TestProject.OptionalCtorParams input, global::TestProject.OptionalCtorParams output, global::Orleans.Serialization.Cloning.CopyContext context) { - setField_0(output, getField_0(input)); - setField_1(output, getField_1(input)); + accessField_0(output) = accessField_0(input); + accessField_1(output) = accessField_1(input); } } diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.FieldAccessor.verified.cs similarity index 100% rename from test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.verified.cs rename to test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.FieldAccessor.verified.cs diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.UnsafeAccessor.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.UnsafeAccessor.verified.cs new file mode 100644 index 00000000000..e2be5e23d28 --- /dev/null +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.UnsafeAccessor.verified.cs @@ -0,0 +1,263 @@ +#pragma warning disable +[assembly: global::Orleans.ApplicationPartAttribute("TestProject")] +[assembly: global::Orleans.ApplicationPartAttribute("Orleans.Core.Abstractions")] +[assembly: global::Orleans.ApplicationPartAttribute("Orleans.Serialization")] +[assembly: global::Orleans.ApplicationPartAttribute("Orleans.Core")] +[assembly: global::Orleans.ApplicationPartAttribute("Orleans.Runtime")] +[assembly: global::Orleans.Serialization.Configuration.TypeManifestProviderAttribute(typeof(OrleansCodeGen.TestProject.Metadata_TestProject))] +namespace OrleansCodeGen.TestProject +{ + using global::Orleans.Serialization.Codecs; + using global::Orleans.Serialization.GeneratedCodeHelpers; + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "10.0.0.0"), global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] + public sealed class Codec_GenericWithCtor : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Serializers.IBaseCodec> + { + private readonly global::System.Type _codecFieldType = typeof(global::TestProject.GenericWithCtor); + private readonly global::Orleans.Serialization.Activators.IActivator> _activator; + private readonly global::System.Type _type_T_0CA466BDFA032082 = typeof(T); + private readonly global::Orleans.Serialization.Codecs.IFieldCodec _codec_T_0CA466BDFA032082; + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_id")] + private extern static ref int accessField_1(global::TestProject.GenericWithCtor instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_value")] + private extern static ref T accessField_0(global::TestProject.GenericWithCtor instance); + public Codec_GenericWithCtor(global::Orleans.Serialization.Activators.IActivator> _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) + { + this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); + _codec_T_0CA466BDFA032082 = OrleansGeneratedCodeHelper.GetService>(this, codecProvider); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::TestProject.GenericWithCtor instance) + where TBufferWriter : global::System.Buffers.IBufferWriter + { + _codec_T_0CA466BDFA032082.WriteField(ref writer, 0U, _type_T_0CA466BDFA032082, accessField_0(instance)); + global::Orleans.Serialization.Codecs.Int32Codec.WriteField(ref writer, 1U, accessField_1(instance)); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::TestProject.GenericWithCtor instance) + { + uint id = 0U; + global::Orleans.Serialization.WireProtocol.Field header = default; + while (true) + { + reader.ReadFieldHeader(ref header); + if (header.IsEndBaseOrEndObject) + break; + id += header.FieldIdDelta; + if (id == 0U) + { + accessField_0(instance) = _codec_T_0CA466BDFA032082.ReadValue(ref reader, header); + reader.ReadFieldHeader(ref header); + if (header.IsEndBaseOrEndObject) + break; + id += header.FieldIdDelta; + } + + if (id == 1U) + { + accessField_1(instance) = global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header); + reader.ReadFieldHeader(ref header); + } + + reader.ConsumeEndBaseOrEndObject(ref header); + break; + } + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, global::System.Type expectedType, global::TestProject.GenericWithCtor @value) + where TBufferWriter : global::System.Buffers.IBufferWriter + { + if (@value is null || @value.GetType() == typeof(global::TestProject.GenericWithCtor)) + { + if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, @value)) + return; + writer.WriteStartObject(fieldIdDelta, expectedType, _codecFieldType); + Serialize(ref writer, @value); + writer.WriteEndObject(); + } + else + writer.SerializeUnexpectedType(fieldIdDelta, expectedType, @value); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public global::TestProject.GenericWithCtor ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) + { + if (field.IsReference) + return ReferenceCodec.ReadReference, TReaderInput>(ref reader, field); + field.EnsureWireTypeTagDelimited(); + global::System.Type valueType = field.FieldType; + if (valueType is null || valueType == _codecFieldType) + { + var result = _activator.Create(); + ReferenceCodec.RecordObject(reader.Session, result); + Deserialize(ref reader, result); + return result; + } + + return reader.DeserializeUnexpectedType>(ref field); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "10.0.0.0"), global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] + public sealed class Copier_GenericWithCtor : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IBaseCopier> + { + private readonly global::Orleans.Serialization.Activators.IActivator> _activator; + private readonly global::Orleans.Serialization.Cloning.IDeepCopier _copier_T_0CA466BDFA032082; + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_id")] + private extern static ref int accessField_1(global::TestProject.GenericWithCtor instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "_value")] + private extern static ref T accessField_0(global::TestProject.GenericWithCtor instance); + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public global::TestProject.GenericWithCtor DeepCopy(global::TestProject.GenericWithCtor original, global::Orleans.Serialization.Cloning.CopyContext context) + { + if (context.TryGetCopy(original, out global::TestProject.GenericWithCtor existing)) + return existing; + if (original.GetType() != typeof(global::TestProject.GenericWithCtor)) + return context.DeepCopy(original); + var result = _activator.Create(); + context.RecordCopy(original, result); + DeepCopy(original, result, context); + return result; + } + + public Copier_GenericWithCtor(global::Orleans.Serialization.Activators.IActivator> _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) + { + this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); + _copier_T_0CA466BDFA032082 = OrleansGeneratedCodeHelper.GetService>(this, codecProvider); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void DeepCopy(global::TestProject.GenericWithCtor input, global::TestProject.GenericWithCtor output, global::Orleans.Serialization.Cloning.CopyContext context) + { + accessField_0(output) = _copier_T_0CA466BDFA032082.DeepCopy(accessField_0(input), context); + accessField_1(output) = accessField_1(input); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "10.0.0.0"), global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] + public sealed class Codec_UsesGenericWithCtor : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec + { + private readonly global::System.Type _codecFieldType = typeof(global::TestProject.UsesGenericWithCtor); + private readonly global::System.Type _type_GenericWithCtor_String_C8ECFC0964C2C2C3 = typeof(global::TestProject.GenericWithCtor); + private readonly OrleansCodeGen.TestProject.Codec_GenericWithCtor _codec_GenericWithCtor_String_C8ECFC0964C2C2C3; + public Codec_UsesGenericWithCtor(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) + { + _codec_GenericWithCtor_String_C8ECFC0964C2C2C3 = OrleansGeneratedCodeHelper.GetService>(this, codecProvider); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::TestProject.UsesGenericWithCtor instance) + where TBufferWriter : global::System.Buffers.IBufferWriter + { + _codec_GenericWithCtor_String_C8ECFC0964C2C2C3.WriteField(ref writer, 0U, _type_GenericWithCtor_String_C8ECFC0964C2C2C3, instance.StringGen); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::TestProject.UsesGenericWithCtor instance) + { + uint id = 0U; + global::Orleans.Serialization.WireProtocol.Field header = default; + while (true) + { + reader.ReadFieldHeader(ref header); + if (header.IsEndBaseOrEndObject) + break; + id += header.FieldIdDelta; + if (id == 0U) + { + instance.StringGen = _codec_GenericWithCtor_String_C8ECFC0964C2C2C3.ReadValue(ref reader, header); + reader.ReadFieldHeader(ref header); + } + + reader.ConsumeEndBaseOrEndObject(ref header); + break; + } + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, global::System.Type expectedType, global::TestProject.UsesGenericWithCtor @value) + where TBufferWriter : global::System.Buffers.IBufferWriter + { + if (@value is null || @value.GetType() == typeof(global::TestProject.UsesGenericWithCtor)) + { + if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, @value)) + return; + writer.WriteStartObject(fieldIdDelta, expectedType, _codecFieldType); + Serialize(ref writer, @value); + writer.WriteEndObject(); + } + else + writer.SerializeUnexpectedType(fieldIdDelta, expectedType, @value); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public global::TestProject.UsesGenericWithCtor ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) + { + if (field.IsReference) + return ReferenceCodec.ReadReference(ref reader, field); + field.EnsureWireTypeTagDelimited(); + global::System.Type valueType = field.FieldType; + if (valueType is null || valueType == _codecFieldType) + { + var result = new global::TestProject.UsesGenericWithCtor(); + ReferenceCodec.RecordObject(reader.Session, result); + Deserialize(ref reader, result); + return result; + } + + return reader.DeserializeUnexpectedType(ref field); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "10.0.0.0"), global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] + public sealed class Copier_UsesGenericWithCtor : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier + { + private readonly OrleansCodeGen.TestProject.Copier_GenericWithCtor _copier_GenericWithCtor_String_C8ECFC0964C2C2C3; + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public global::TestProject.UsesGenericWithCtor DeepCopy(global::TestProject.UsesGenericWithCtor original, global::Orleans.Serialization.Cloning.CopyContext context) + { + if (context.TryGetCopy(original, out global::TestProject.UsesGenericWithCtor existing)) + return existing; + if (original.GetType() != typeof(global::TestProject.UsesGenericWithCtor)) + return context.DeepCopy(original); + var result = new global::TestProject.UsesGenericWithCtor(); + context.RecordCopy(original, result); + DeepCopy(original, result, context); + return result; + } + + public Copier_UsesGenericWithCtor(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) + { + _copier_GenericWithCtor_String_C8ECFC0964C2C2C3 = OrleansGeneratedCodeHelper.GetService>(this, codecProvider); + } + + [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void DeepCopy(global::TestProject.UsesGenericWithCtor input, global::TestProject.UsesGenericWithCtor output, global::Orleans.Serialization.Cloning.CopyContext context) + { + output.StringGen = _copier_GenericWithCtor_String_C8ECFC0964C2C2C3.DeepCopy(input.StringGen, context); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "10.0.0.0"), global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] + internal sealed class Activator_UsesGenericWithCtor : global::Orleans.Serialization.Activators.IActivator + { + public global::TestProject.UsesGenericWithCtor Create() => new global::TestProject.UsesGenericWithCtor(); + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "10.0.0.0"), global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute] + internal sealed class Metadata_TestProject : global::Orleans.Serialization.Configuration.TypeManifestProviderBase + { + protected override void ConfigureInner(global::Orleans.Serialization.Configuration.TypeManifestOptions config) + { + config.AddSerializer(typeof(OrleansCodeGen.TestProject.Codec_GenericWithCtor<>)); + config.AddSerializer(typeof(OrleansCodeGen.TestProject.Codec_UsesGenericWithCtor)); + config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_GenericWithCtor<>)); + config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_UsesGenericWithCtor)); + config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_UsesGenericWithCtor)); + } + } +} +#pragma warning restore \ No newline at end of file diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecords.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecords.verified.cs index 01b1ef5abf0..e8087e71f75 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecords.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecords.verified.cs @@ -77,7 +77,8 @@ public sealed class Codec_DemoDataRecordClass : global::Orleans.Serialization.Co { private readonly global::System.Type _codecFieldType = typeof(global::TestProject.DemoDataRecordClass); private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataRecordClass), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_0(global::TestProject.DemoDataRecordClass instance); public Codec_DemoDataRecordClass(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -111,7 +112,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 0U) { - setField_0(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_0(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } @@ -159,7 +160,8 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_DemoDataRecordClass : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataRecordClass), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_0(global::TestProject.DemoDataRecordClass instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.DemoDataRecordClass DeepCopy(global::TestProject.DemoDataRecordClass original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -181,7 +183,7 @@ public Copier_DemoDataRecordClass(global::Orleans.Serialization.Activators.IActi [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DeepCopy(global::TestProject.DemoDataRecordClass input, global::TestProject.DemoDataRecordClass output, global::Orleans.Serialization.Cloning.CopyContext context) { - setField_0(output, input.Value); + accessField_0(output) = input.Value; } } @@ -190,7 +192,8 @@ public sealed class Codec_DemoDataRecord : global::Orleans.Serialization.Codecs. { private readonly global::System.Type _codecFieldType = typeof(global::TestProject.DemoDataRecord); private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataRecord), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_0(global::TestProject.DemoDataRecord instance); public Codec_DemoDataRecord(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -224,7 +227,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 0U) { - setField_0(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_0(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } @@ -272,7 +275,8 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_DemoDataRecord : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_0 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.DemoDataRecord), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_0(global::TestProject.DemoDataRecord instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.DemoDataRecord DeepCopy(global::TestProject.DemoDataRecord original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -294,7 +298,7 @@ public Copier_DemoDataRecord(global::Orleans.Serialization.Activators.IActivator [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DeepCopy(global::TestProject.DemoDataRecord input, global::TestProject.DemoDataRecord output, global::Orleans.Serialization.Cloning.CopyContext context) { - setField_0(output, input.Value); + accessField_0(output) = input.Value; } } diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecordsWithParameterIdAttributes.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecordsWithParameterIdAttributes.verified.cs index 945501db8af..7b7670b4b4e 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecordsWithParameterIdAttributes.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecordsWithParameterIdAttributes.verified.cs @@ -15,8 +15,10 @@ public sealed class Codec_SimpleRecord : global::Orleans.Serialization.Codecs.IF { private readonly global::System.Type _codecFieldType = typeof(global::TestProject.SimpleRecord); private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_20_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.SimpleRecord), "k__BackingField"); - private static readonly global::System.Action setField_10_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.SimpleRecord), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_20_ctor(global::TestProject.SimpleRecord instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref int accessField_10_ctor(global::TestProject.SimpleRecord instance); public Codec_SimpleRecord(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -44,7 +46,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 10U) { - setField_10_ctor(instance, global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header)); + accessField_10_ctor(instance) = global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -53,7 +55,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. if (id == 20U) { - setField_20_ctor(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_20_ctor(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -110,8 +112,10 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_SimpleRecord : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_20_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.SimpleRecord), "k__BackingField"); - private static readonly global::System.Action setField_10_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.SimpleRecord), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_20_ctor(global::TestProject.SimpleRecord instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref int accessField_10_ctor(global::TestProject.SimpleRecord instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.SimpleRecord DeepCopy(global::TestProject.SimpleRecord original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -133,8 +137,8 @@ public Copier_SimpleRecord(global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_50 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.RecordWithExtraProperty), "k__BackingField"); - private static readonly global::System.Action setField_30_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.RecordWithExtraProperty), "k__BackingField"); - private static readonly global::System.Action setField_40_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.RecordWithExtraProperty), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_50(global::TestProject.RecordWithExtraProperty instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref int accessField_30_ctor(global::TestProject.RecordWithExtraProperty instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_40_ctor(global::TestProject.RecordWithExtraProperty instance); public Codec_RecordWithExtraProperty(global::Orleans.Serialization.Activators.IActivator _activator) { this._activator = OrleansGeneratedCodeHelper.UnwrapService(this, _activator); @@ -174,7 +181,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 30U) { - setField_30_ctor(instance, global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header)); + accessField_30_ctor(instance) = global::Orleans.Serialization.Codecs.Int32Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -183,7 +190,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. if (id == 40U) { - setField_40_ctor(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_40_ctor(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -203,7 +210,7 @@ public void Deserialize(ref global::Orleans.Serialization.Buffers. id += header.FieldIdDelta; if (id == 50U) { - setField_50(instance, global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header)); + accessField_50(instance) = global::Orleans.Serialization.Codecs.StringCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.IsEndBaseOrEndObject) break; @@ -253,9 +260,12 @@ public void WriteField(ref global::Orleans.Serialization.Buffers. public sealed class Copier_RecordWithExtraProperty : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier { private readonly global::Orleans.Serialization.Activators.IActivator _activator; - private static readonly global::System.Action setField_50 = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.RecordWithExtraProperty), "k__BackingField"); - private static readonly global::System.Action setField_30_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.RecordWithExtraProperty), "k__BackingField"); - private static readonly global::System.Action setField_40_ctor = (global::System.Action)global::Orleans.Serialization.Utilities.FieldAccessor.GetReferenceSetter(typeof(global::TestProject.RecordWithExtraProperty), "k__BackingField"); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_50(global::TestProject.RecordWithExtraProperty instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref int accessField_30_ctor(global::TestProject.RecordWithExtraProperty instance); + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Field, Name = "k__BackingField")] + private extern static ref string accessField_40_ctor(global::TestProject.RecordWithExtraProperty instance); [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public global::TestProject.RecordWithExtraProperty DeepCopy(global::TestProject.RecordWithExtraProperty original, global::Orleans.Serialization.Cloning.CopyContext context) { @@ -277,9 +287,9 @@ public Copier_RecordWithExtraProperty(global::Orleans.Serialization.Activators.I [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void DeepCopy(global::TestProject.RecordWithExtraProperty input, global::TestProject.RecordWithExtraProperty output, global::Orleans.Serialization.Cloning.CopyContext context) { - setField_30_ctor(output, input.Id); - setField_40_ctor(output, input.Name); - setField_50(output, input.Description); + accessField_30_ctor(output) = input.Id; + accessField_40_ctor(output) = input.Name; + accessField_50(output) = input.Description; } } diff --git a/test/Orleans.NativeAotSmoke/Fields.cs b/test/Orleans.NativeAotSmoke/Fields.cs new file mode 100644 index 00000000000..80c3bca47ea --- /dev/null +++ b/test/Orleans.NativeAotSmoke/Fields.cs @@ -0,0 +1,22 @@ +using Orleans.Serialization.NativeAotFieldAccessSmoke; + +if (args.Length == 0) +{ + NativeFieldAccessChecks.Run(); + Console.WriteLine("NativeAOT generated codecs and copiers restored private and backing fields."); +} +else if (args is ["--full-pipeline"]) +{ + FieldAccessChecks.PrivateAndBackingFieldsRoundTripAndCopy(); + FieldAccessChecks.ConstrainedGenericFieldsRoundTripAndCopy(); + FieldAccessChecks.GenericStructFieldsRoundTripAndCopy(); + FieldAccessChecks.NestedGenericFieldsRoundTripAndCopy(); + FieldAccessChecks.VolatileFieldsRoundTripAndCopy(); + FieldAccessChecks.VolatileValueFieldsRoundTripAndCopy(); + FieldAccessChecks.GenericVolatileFieldsRoundTripAndCopy(); + Console.WriteLine("NativeAOT AddSerializer private and backing field serialization and copying passed."); +} +else +{ + throw new ArgumentException("Use no arguments for generated component checks or --full-pipeline for AddSerializer checks."); +} diff --git a/test/Orleans.NativeAotSmoke/Fields.smoke.json b/test/Orleans.NativeAotSmoke/Fields.smoke.json new file mode 100644 index 00000000000..53e45e3e7af --- /dev/null +++ b/test/Orleans.NativeAotSmoke/Fields.smoke.json @@ -0,0 +1,7 @@ +{ + "scenario": "Fields", + "diagnostics": "legacy-visible", + "rejectDiagnostics": [ + "OrleansCodeGen|FieldAccessor|UnsafeAccessor" + ] +} diff --git a/test/Orleans.NativeAotSmoke/FieldsChecks.cs b/test/Orleans.NativeAotSmoke/FieldsChecks.cs new file mode 100644 index 00000000000..bb32a80ae2c --- /dev/null +++ b/test/Orleans.NativeAotSmoke/FieldsChecks.cs @@ -0,0 +1,188 @@ +using Microsoft.Extensions.DependencyInjection; +using Orleans.Serialization.Cloning; +using Orleans.Serialization.Codecs; +using OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke; + +namespace Orleans.Serialization.NativeAotFieldAccessSmoke; + +internal static class FieldAccessChecks +{ + public static ServiceProvider CreateServices() + { + var services = new ServiceCollection().AddSerializer(); + // Root the closed codecs and copiers so this smoke isolates generated member access. + services.AddSingleton, Codec_PrivateFields>(); + services.AddSingleton, Copier_PrivateFields>(); + services.AddSingleton>, Codec_ConstrainedFields>(); + services.AddSingleton>, Copier_ConstrainedFields>(); + services.AddSingleton>, Codec_ValueFields>(); + services.AddSingleton>, Copier_ValueFields>(); + services.AddSingleton.Nested>, OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Outer.Codec_Nested>(); + services.AddSingleton.Nested>, OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Outer.Copier_Nested>(); + services.AddSingleton, Codec_VolatileFields>(); + services.AddSingleton, Copier_VolatileFields>(); + services.AddSingleton, Codec_VolatileValueFields>(); + services.AddSingleton, Copier_VolatileValueFields>(); + services.AddSingleton>, Codec_GenericVolatileFields>(); + services.AddSingleton>, Copier_GenericVolatileFields>(); + return services.BuildServiceProvider(); + } + + public static void PrivateAndBackingFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new PrivateFields(137, [2, 4, 8]); + var serializer = services.GetRequiredService>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)) + ?? throw new InvalidOperationException("Deserialization must return the private-field payload."); + var copy = services.GetRequiredService().Copy(input); + ValidatePrivateFields(input, result, copy); + } + + public static void ValidatePrivateFields(PrivateFields input, PrivateFields result, PrivateFields copy) + { + Validate(result); + Validate(copy); + Ensure(!ReferenceEquals(input, copy), "The class copier must create a new instance."); + Ensure(!ReferenceEquals(input.Bytes, copy.Bytes), "The readonly array field must be deeply copied."); + copy.Bytes[0] = 99; + Ensure(input.Bytes[0] == 2, "Mutating the copied array must preserve the original."); + + static void Validate(PrivateFields value) + { + Ensure(value.Number == 137, "The private field must be restored."); + Ensure(value.Bytes.AsSpan().SequenceEqual(new byte[] { 2, 4, 8 }), "The readonly field must be restored."); + Ensure(value.ReadOnly == "readonly", "The get-only backing field must be restored."); + Ensure(value.InitOnly == "init-only", "The init-only backing field must be restored."); + Ensure(value.PrivateSetter == "private setter", "The private-setter backing field must be restored."); + Ensure(value.PrivateValue == "private property", "The private property's backing field must be restored."); + } + } + + public static void ConstrainedGenericFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new ConstrainedFields("generic fields"); + var serializer = services.GetRequiredService>>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)) + ?? throw new InvalidOperationException("Deserialization must return the constrained generic payload."); + var copy = services.GetRequiredService().Copy(input); + ValidateConstrainedFields(input, result, copy); + } + + public static void ValidateConstrainedFields(ConstrainedFields input, ConstrainedFields result, ConstrainedFields copy) + { + Ensure(result.Value == input.Value && result.ReadOnly == input.Value && result.InitOnly == input.Value, + "The constrained generic serializer must restore private and backing fields."); + Ensure(copy.Value == input.Value && copy.ReadOnly == input.Value && copy.InitOnly == input.Value, + "The constrained generic copier must restore private and backing fields."); + Ensure(!ReferenceEquals(input, copy), "The generic class copier must create a new instance."); + } + + public static void GenericStructFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new ValueFields(211, [3, 6, 9]); + var serializer = services.GetRequiredService>>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)); + var copy = services.GetRequiredService().Copy(input); + ValidateValueFields(input, result, copy); + } + + public static void ValidateValueFields(ValueFields input, ValueFields result, ValueFields copy) + { + Ensure(result.Value == 211 && result.ReadOnly == 211 && result.Bytes.AsSpan().SequenceEqual(input.Bytes), + "The generic struct serializer must restore readonly fields through a ref receiver."); + Ensure(copy.Value == 211 && copy.ReadOnly == 211 && copy.Bytes.AsSpan().SequenceEqual(input.Bytes), + "The generic struct copier must restore readonly fields through a ref receiver."); + Ensure(!ReferenceEquals(input.Bytes, copy.Bytes), "The struct copier must deeply copy the array field."); + } + + public static void NestedGenericFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new Outer.Nested("nested fields", 307); + var serializer = services.GetRequiredService.Nested>>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)) + ?? throw new InvalidOperationException("Deserialization must return the nested generic payload."); + var copy = services.GetRequiredService().Copy(input); + ValidateNestedFields(input, result, copy); + } + + public static void ValidateNestedFields(Outer.Nested input, Outer.Nested result, Outer.Nested copy) + { + Ensure(result.Value == input.Value && result.Number == input.Number, + "The nested generic serializer must preserve both declaring-type parameters."); + Ensure(copy.Value == input.Value && copy.Number == input.Number, + "The nested generic copier must preserve both declaring-type parameters."); + Ensure(!ReferenceEquals(input, copy), "The nested generic copier must create a new instance."); + } + + public static void VolatileFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new VolatileFields(419, "volatile", [5, 10, 15], 23); + var serializer = services.GetRequiredService>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)) + ?? throw new InvalidOperationException("Deserialization must return the volatile-field payload."); + var copy = services.GetRequiredService().Copy(input); + ValidateVolatileFields(input, result, copy); + } + + public static void ValidateVolatileFields(VolatileFields input, VolatileFields result, VolatileFields copy) + { + Ensure(result.Number == input.Number && result.Text == input.Text && result.Ordinary == input.Ordinary + && result.Bytes.AsSpan().SequenceEqual(input.Bytes), "The serializer must restore volatile and ordinary fields."); + Ensure(copy.Number == input.Number && copy.Text == input.Text && copy.Ordinary == input.Ordinary + && copy.Bytes.AsSpan().SequenceEqual(input.Bytes), "The copier must restore volatile and ordinary fields."); + Ensure(!ReferenceEquals(input, copy), "The volatile-field copier must create a new instance."); + Ensure(!ReferenceEquals(input.Bytes, copy.Bytes), "The volatile array field must be deeply copied."); + copy.Bytes[0] = 99; + Ensure(input.Bytes[0] == 5, "Mutating the copied volatile array must preserve the original."); + } + + public static void VolatileValueFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new VolatileValueFields(421, [6, 12, 18]); + var serializer = services.GetRequiredService>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)); + var copy = services.GetRequiredService().Copy(input); + ValidateVolatileValueFields(input, result, copy); + } + + public static void ValidateVolatileValueFields(VolatileValueFields input, VolatileValueFields result, VolatileValueFields copy) + { + Ensure(result.Number == input.Number && result.Bytes.AsSpan().SequenceEqual(input.Bytes), + "The struct serializer must restore volatile fields through a ref receiver."); + Ensure(copy.Number == input.Number && copy.Bytes.AsSpan().SequenceEqual(input.Bytes), + "The struct copier must restore volatile fields through a ref receiver."); + Ensure(!ReferenceEquals(input.Bytes, copy.Bytes), "The volatile struct array field must be deeply copied."); + } + + public static void GenericVolatileFieldsRoundTripAndCopy() + { + using var services = CreateServices(); + var input = new GenericVolatileFields("generic volatile"); + var serializer = services.GetRequiredService>>(); + var result = serializer.Deserialize(serializer.SerializeToArray(input)) + ?? throw new InvalidOperationException("Deserialization must return the generic volatile payload."); + var copy = services.GetRequiredService().Copy(input); + ValidateGenericVolatileFields(input, result, copy); + } + + public static void ValidateGenericVolatileFields(GenericVolatileFields input, GenericVolatileFields result, GenericVolatileFields copy) + { + Ensure(result.Value == input.Value, "The generic serializer must restore the volatile field."); + Ensure(copy.Value == input.Value, "The generic copier must restore the volatile field."); + Ensure(!ReferenceEquals(input, copy), "The generic volatile copier must create a new instance."); + } + + private static void Ensure(bool condition, string message) + { + if (!condition) + { + throw new InvalidOperationException(message); + } + } +} diff --git a/test/Orleans.NativeAotSmoke/FieldsNativeChecks.cs b/test/Orleans.NativeAotSmoke/FieldsNativeChecks.cs new file mode 100644 index 00000000000..50729d77142 --- /dev/null +++ b/test/Orleans.NativeAotSmoke/FieldsNativeChecks.cs @@ -0,0 +1,88 @@ +using System.Buffers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Orleans.Serialization.Buffers; +using Orleans.Serialization.Cloning; +using Orleans.Serialization.Codecs; +using Orleans.Serialization.Configuration; +using Orleans.Serialization.Serializers; +using Orleans.Serialization.Session; +using Orleans.Serialization.TypeSystem; +using OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke; + +namespace Orleans.Serialization.NativeAotFieldAccessSmoke; + +internal static class NativeFieldAccessChecks +{ + public static void Run() + { + using var services = new ServiceCollection() + .AddSingleton>(new StringCodec()) + .AddSingleton>(new Int32Codec()) + .AddSingleton>(new ShallowCopier()) + .AddSingleton>(new ShallowCopier()) + .BuildServiceProvider(); + + // Use a bounded manifest to exercise generated codecs independently of assembly discovery. + var options = Options.Create(new TypeManifestOptions()); + var provider = new CodecProvider(services, options); + var typeCodec = new TypeCodec(new TypeConverter([], [], [], options, new CachedTypeResolver())); + var wellKnownTypes = new WellKnownTypeCollection(options); + using var context = new CopyContext(provider, static _ => { }); + + var fields = new PrivateFields(137, [2, 4, 8]); + var fieldsResult = RoundTrip(new Codec_PrivateFields(), fields, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidatePrivateFields(fields, fieldsResult, new Copier_PrivateFields().DeepCopy(fields, context)); + context.Reset(); + + var generic = new ConstrainedFields("generic fields"); + var genericResult = RoundTrip(new Codec_ConstrainedFields(provider), generic, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidateConstrainedFields(generic, genericResult, new Copier_ConstrainedFields(provider).DeepCopy(generic, context)); + context.Reset(); + + var value = new ValueFields(211, [3, 6, 9]); + var valueResult = RoundTrip(new Codec_ValueFields(provider), value, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidateValueFields(value, valueResult, new Copier_ValueFields(provider).DeepCopy(value, context)); + context.Reset(); + + var nested = new Outer.Nested("nested fields", 307); + var nestedResult = RoundTrip( + new OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Outer.Codec_Nested(provider), + nested, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidateNestedFields( + nested, nestedResult, + new OrleansCodeGen.Orleans.Serialization.NativeAotFieldAccessSmoke.Outer.Copier_Nested(provider).DeepCopy(nested, context)); + context.Reset(); + + var volatileFields = new VolatileFields(419, "volatile", [5, 10, 15], 23); + var volatileResult = RoundTrip(new Codec_VolatileFields(), volatileFields, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidateVolatileFields(volatileFields, volatileResult, new Copier_VolatileFields().DeepCopy(volatileFields, context)); + context.Reset(); + + var volatileValue = new VolatileValueFields(421, [6, 12, 18]); + var volatileValueResult = RoundTrip(new Codec_VolatileValueFields(), volatileValue, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidateVolatileValueFields(volatileValue, volatileValueResult, new Copier_VolatileValueFields().DeepCopy(volatileValue, context)); + context.Reset(); + + var genericVolatile = new GenericVolatileFields("generic volatile"); + var genericVolatileResult = RoundTrip(new Codec_GenericVolatileFields(provider), genericVolatile, provider, typeCodec, wellKnownTypes); + FieldAccessChecks.ValidateGenericVolatileFields( + genericVolatile, genericVolatileResult, new Copier_GenericVolatileFields(provider).DeepCopy(genericVolatile, context)); + } + + private static T RoundTrip(IFieldCodec codec, T input, CodecProvider provider, TypeCodec typeCodec, WellKnownTypeCollection wellKnownTypes) + { + var buffer = new ArrayBufferWriter(); + using (var session = new SerializerSession(typeCodec, wellKnownTypes, provider)) + { + var writer = Writer.Create(buffer, session); + codec.WriteField(ref writer, 0, typeof(T), input); + writer.Commit(); + } + + using var readSession = new SerializerSession(typeCodec, wellKnownTypes, provider); + var reader = Reader.Create(buffer.WrittenMemory, readSession); + return codec.ReadValue(ref reader, reader.ReadFieldHeader()) + ?? throw new InvalidOperationException("The generated codec must restore a non-null payload."); + } +} diff --git a/test/Orleans.NativeAotSmoke/FieldsPayloads.cs b/test/Orleans.NativeAotSmoke/FieldsPayloads.cs new file mode 100644 index 00000000000..1fb571808a6 --- /dev/null +++ b/test/Orleans.NativeAotSmoke/FieldsPayloads.cs @@ -0,0 +1,148 @@ +namespace Orleans.Serialization.NativeAotFieldAccessSmoke; + +[GenerateSerializer] +internal sealed class PrivateFields +{ + [Id(0)] private int _number; + [Id(1)] private readonly byte[] _bytes = []; + [Id(2)] public string ReadOnly { get; } = ""; + [Id(3)] public string InitOnly { get; init; } = ""; + [Id(4)] public string PrivateSetter { get; private set; } = ""; + [Id(5)] private string PrivateProperty { get; set; } = ""; + + public PrivateFields() + { + } + + public PrivateFields(int number, byte[] bytes) + { + _number = number; + _bytes = bytes; + ReadOnly = "readonly"; + InitOnly = "init-only"; + PrivateSetter = "private setter"; + PrivateProperty = "private property"; + } + + public int Number => _number; + public byte[] Bytes => _bytes; + public string PrivateValue => PrivateProperty; +} + +[GenerateSerializer] +internal sealed class ConstrainedFields where T : class, IComparable, IEquatable +{ + [Id(0)] private readonly T _value = default!; + [Id(1)] public T ReadOnly { get; } = default!; + [Id(2)] public T InitOnly { get; init; } = default!; + + public ConstrainedFields() + { + } + + public ConstrainedFields(T value) + { + _value = value; + ReadOnly = value; + InitOnly = value; + } + + public T Value => _value; +} + +[GenerateSerializer] +internal struct ValueFields where T : unmanaged +{ + [Id(0)] private readonly T _value; + [Id(1)] private byte[] _bytes; + [Id(2)] public T ReadOnly { get; } + + public ValueFields(T value, byte[] bytes) + { + _value = value; + _bytes = bytes; + ReadOnly = value; + } + + public T Value => _value; + public byte[] Bytes => _bytes; +} + +internal static class Outer where T : class +{ + [GenerateSerializer] + internal sealed class Nested where U : struct, IEquatable + { + [Id(0)] private readonly T _value = default!; + [Id(1)] private readonly U _number; + + public Nested() + { + } + + public Nested(T value, U number) + { + _value = value; + _number = number; + } + + public T Value => _value; + public U Number => _number; + } +} + +[GenerateSerializer] +internal sealed class VolatileFields +{ + [Id(0)] private volatile int _number; + [Id(1)] private volatile string _text = ""; + [Id(2)] private volatile byte[] _bytes = []; + [Id(3)] private int _ordinary; + + public VolatileFields() + { + } + + public VolatileFields(int number, string text, byte[] bytes, int ordinary) + { + _number = number; + _text = text; + _bytes = bytes; + _ordinary = ordinary; + } + + public int Number => _number; + public string Text => _text; + public byte[] Bytes => _bytes; + public int Ordinary => _ordinary; +} + +[GenerateSerializer] +internal struct VolatileValueFields +{ + [Id(0)] private volatile int _number; + [Id(1)] private volatile byte[] _bytes; + + public VolatileValueFields(int number, byte[] bytes) + { + _number = number; + _bytes = bytes; + } + + public int Number => _number; + public byte[] Bytes => _bytes; +} + +[GenerateSerializer] +internal sealed class GenericVolatileFields where T : class +{ + [Id(0)] private volatile T _value = default!; + + public GenericVolatileFields() + { + } + + public GenericVolatileFields(T value) => _value = value; + + public T Value => _value; +}