From 5030b3f0f8f35b0f1f309acb644e802c57f303f8 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 20:57:58 -0700 Subject: [PATCH 1/6] feat(codegen): register static grain-reference factories --- .../docs/implementation/serialization.md | 8 +- .../AnalyzerReleases.Unshipped.md | 1 + .../Diagnostics/DiagnosticRuleId.cs | 1 + .../InvalidGrainReferenceFactoryDiagnostic.cs | 14 ++ .../MetadataGenerator.cs | 19 +++ .../Model/MetadataAggregateModelBuilder.cs | 5 +- .../Model/ProxyInterfaceModel.cs | 3 +- .../Model/ProxyInterfaceModelExtractor.cs | 23 ++- .../Model/ReferenceAssemblyModel.cs | 7 +- .../Model/ReferenceAssemblyModelExtractor.cs | 39 ++++- .../GenerateGrainReferenceAttribute.cs | 25 +++ .../GrainReferenceFactoryOptions.cs | 46 ++++++ .../GrainReferenceActivator.cs | 97 ++++++----- .../Configuration/TypeManifestOptions.cs | 17 ++ .../Orleans.Core.Abstractions.cs | 18 ++ .../Orleans.Serialization.cs | 3 + .../GrainReferenceFactoryCodegenTests.cs | 156 ++++++++++++++++++ ...eGeneratorTests.TestBasicGrain.verified.cs | 1 + ...ateMethodSerializersAnnotation.verified.cs | 1 + ...torTests.TestGrainComplexGrain.verified.cs | 1 + ...AnnotatedWithInvokableBaseType.verified.cs | 1 + ...odAnnotatedWithResponseTimeout.verified.cs | 1 + ...TestGrainWithDifferentKeyTypes.verified.cs | 4 + ...estGrainWithMultipleInterfaces.verified.cs | 2 + .../GrainReferenceActivatorTests.cs | 115 +++++++++++++ .../ReferenceConstructionFixture.cs | 43 ++++- .../Orleans.NativeAotSmoke/GrainReferences.cs | 27 ++- 27 files changed, 623 insertions(+), 55 deletions(-) create mode 100644 src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs create mode 100644 src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs create mode 100644 src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs create mode 100644 test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs diff --git a/docs/site/src/content/docs/implementation/serialization.md b/docs/site/src/content/docs/implementation/serialization.md index 94e2c1a26ee..3dc1f6cf743 100644 --- a/docs/site/src/content/docs/implementation/serialization.md +++ b/docs/site/src/content/docs/implementation/serialization.md @@ -12,7 +12,7 @@ Orleans serialization serves two related pipelines: - value serialization, deep copying, and activation for message and storage payloads; - RPC code generation for grain references, request objects, dispatch, and responses. -Most application code uses generated components. Reflection-based discovery is deliberately not the default architecture: generated manifests make the participating types explicit and keep runtime dispatch compatible with [trimming](https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming) and ahead-of-time compilation. +Most application code uses generated components. Generated manifests make the participating types explicit and keep runtime dispatch compatible with [trimming](https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming) and ahead-of-time compilation. ## Incremental generator pipeline @@ -72,7 +72,11 @@ Statically closed value-type constructor delegates update the caller's `ref` val ## RPC generation -Grain-reference construction resolves the generated proxy from the registered type manifest and invokes its `(GrainReferenceShared, IdSpan)` constructor. The activator caches the constructor delegate and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. JIT runtimes use an emitted constructor delegate; NativeAOT uses a cached reflection constructor invoker. Generated `AddInterfaceProxy` registrations preserve the public proxy constructor during trimming. +Generated manifests register each concrete grain interface with a typed factory which directly constructs its proxy using `(GrainReferenceShared, IdSpan)`. The runtime resolves the factory using the declared interface identity. The activator caches that factory and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. Direct construction preserves proxy constructors through normal static call reachability and propagates constructor exceptions directly. + +For NativeAOT applications, apply to the application assembly for each closed generic grain interface. The generator emits a statically closed factory, including when the interface is declared in a referenced assembly. Register every generic argument combination the application uses. The runtime selects exact closed registrations before resolving open generic families. + +JIT applications also support generic arguments selected at runtime through explicitly registered open generic proxy mappings. Those mappings use reflective generic closure and a cached constructor invoker. Older generated manifests and custom proxies registered by type retain the same constructor-invoker compatibility path; custom proxies can register a direct typed factory through in the manifest's configuration. For each grain interface method, generated code captures arguments in an invokable object. The generated proxy submits that object through its proxy base. On the target, generated dispatch metadata invokes the concrete implementation and encodes the response. diff --git a/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md b/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md index 2d5ffee1aff..1bb4e70950c 100644 --- a/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md +++ b/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md @@ -10,3 +10,4 @@ ORLEANS0110 | Usage | Error | ReferenceAssemblyWithGenerateSerializerDiagnostic ORLEANS0111 | Usage | Error | Invalid invokable base type mapping ORLEANS0112 | Usage | Error | Invalid RPC parameter field identifier ORLEANS0113 | Usage | Warning | CancellationToken parameter is not last +ORLEANS0117 | Usage | Error | Invalid grain-reference factory registration diff --git a/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs b/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs index 3e9c469e31d..39be0ef8911 100644 --- a/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs +++ b/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs @@ -16,4 +16,5 @@ internal static class DiagnosticRuleId public const string InvalidInvokableBaseTypeMapping = "ORLEANS0111"; public const string InvalidRpcParameterId = "ORLEANS0112"; public const string CancellationTokenNotLast = "ORLEANS0113"; + public const string InvalidGrainReferenceFactory = "ORLEANS0117"; } diff --git a/src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs b/src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs new file mode 100644 index 00000000000..3585b95b4d6 --- /dev/null +++ b/src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; + +namespace Orleans.CodeGenerator.Diagnostics; + +internal static class InvalidGrainReferenceFactoryDiagnostic +{ + internal static readonly DiagnosticDescriptor Rule = new( + DiagnosticRuleId.InvalidGrainReferenceFactory, + "Invalid grain-reference factory", + "GenerateGrainReference requires an accessible concrete grain interface with a (GrainReferenceShared, IdSpan) proxy constructor", + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); +} diff --git a/src/Orleans.CodeGenerator/MetadataGenerator.cs b/src/Orleans.CodeGenerator/MetadataGenerator.cs index 57d5a520060..0d6cb498e94 100644 --- a/src/Orleans.CodeGenerator/MetadataGenerator.cs +++ b/src/Orleans.CodeGenerator/MetadataGenerator.cs @@ -79,6 +79,18 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() AddRegistration(body, addProxyMethod, GetGeneratedProxyTypeSyntax(type)); } + foreach (var proxy in orderedProxyInterfaces.Where(static proxy => proxy.ProxyBase.SupportsGrainReferenceFactory)) + { + var interfaceType = GetOpenTypeSyntax(proxy.InterfaceType); + var proxyType = GetGeneratedProxyTypeSyntax(proxy); + AddGrainReferenceFactory(body, interfaceType, proxyType, proxy.TypeParameters.Count == 0); + } + + foreach (var factory in model.ReferenceAssemblyData.GrainReferenceFactories) + { + AddGrainReferenceFactory(body, ParseTypeName(factory.InterfaceType.SyntaxString), ParseTypeName(factory.ProxyType.SyntaxString), isClosed: true); + } + var addInterfaceMethod = configParam.Member("AddInterface"); foreach (var type in orderedProxyInterfaces.Select(static proxy => proxy.InterfaceType).Distinct()) { @@ -146,6 +158,13 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() return CreateMetadataClass(body, providerBody, configParam); } + private static void AddGrainReferenceFactory(List body, TypeSyntax interfaceType, TypeSyntax proxyType, bool isClosed) + { + var options = "config.GetOrCreate()"; + var factory = isClosed ? $", static (shared, key) => new {proxyType}(shared, key)" : string.Empty; + body.Add(ParseStatement($"{options}.Add(typeof({interfaceType}), typeof({proxyType}){factory});")); + } + private ClassDeclarationSyntax CreateMetadataClass( List body, List providerBody, diff --git a/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs b/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs index bdcebde8894..3152cabc47c 100644 --- a/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs +++ b/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs @@ -121,7 +121,10 @@ private static ReferenceAssemblyModel NormalizeReferenceAssemblyData(ReferenceAs ReferencedProxyInterfaces: referencedProxyInterfaces, RegisteredCodecs: registeredCodecs, RegisteredProviders: normalizedRegisteredProviders, - InterfaceImplementations: interfaceImplementations); + InterfaceImplementations: interfaceImplementations) + { + GrainReferenceFactories = referenceData.GrainReferenceFactories, + }; } internal static ImmutableArray MergeSerializableTypes( diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs index 80a424ddc50..c7cb7192f98 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs @@ -7,7 +7,8 @@ internal sealed record class ProxyBaseModel( TypeRef ProxyBaseType, bool IsExtension, string GeneratedClassNameComponent, - TypeMetadataIdentity MetadataIdentity = default); + TypeMetadataIdentity MetadataIdentity = default, + bool SupportsGrainReferenceFactory = false); /// /// Describes a [GenerateMethodSerializers]-annotated interface for incremental proxy/invokable generation. diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs index f0718e31799..7dcfc578a62 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs @@ -94,7 +94,8 @@ internal static class ProxyInterfaceModelExtractor new TypeRef(proxyBaseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), isExtension, generatedClassNameComponent, - TypeMetadataIdentity.Create(proxyBaseType)); + TypeMetadataIdentity.Create(proxyBaseType), + SupportsGrainReferenceFactory(proxyBaseType)); var name = GetProxyInterfaceName(typeSymbol, libraryTypes); var typeParameters = ExtractInterfaceTypeParameters(typeSymbol); @@ -112,6 +113,26 @@ internal static class ProxyInterfaceModelExtractor MetadataIdentity: TypeMetadataIdentity.Create(typeSymbol)); } + private static bool SupportsGrainReferenceFactory(INamedTypeSymbol proxyBaseType) + { + var isGrainReference = false; + for (var type = proxyBaseType; type is not null; type = type.BaseType) + { + if (type.ToDisplayString() == "Orleans.Runtime.GrainReference") + { + isGrainReference = true; + break; + } + } + + return isGrainReference && proxyBaseType.InstanceConstructors.Any(static constructor => + constructor.DeclaredAccessibility != Accessibility.Private + && constructor.Parameters.Length == 2 + && constructor.Parameters.All(static parameter => parameter.RefKind == RefKind.None) + && constructor.Parameters[0].Type.ToDisplayString() == "Orleans.Runtime.GrainReferenceShared" + && constructor.Parameters[1].Type.ToDisplayString() == "Orleans.Runtime.IdSpan"); + } + private static string GetProxyInterfaceName(INamedTypeSymbol typeSymbol, LibraryTypes libraryTypes) { var alias = typeSymbol.GetAttribute(libraryTypes.AliasAttribute); diff --git a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs index 505a1609eac..ae2e765b6bf 100644 --- a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs +++ b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs @@ -61,6 +61,8 @@ public InterfaceImplementationModel(TypeRef implementationType, SourceLocationMo /// internal readonly record struct RegisteredProviderModel(string Target, string Kind, string Name, TypeRef Type); +internal readonly record struct GrainReferenceFactoryModel(TypeRef InterfaceType, TypeRef ProxyType); + /// /// Aggregated data extracted from referenced assemblies via [GenerateCodeForDeclaringAssembly] /// and [ApplicationPart] attributes. This model is produced by a CompilationProvider-based @@ -76,4 +78,7 @@ internal sealed record class ReferenceAssemblyModel( EquatableArray ReferencedProxyInterfaces, EquatableArray RegisteredCodecs, EquatableArray RegisteredProviders, - EquatableArray InterfaceImplementations); + EquatableArray InterfaceImplementations) +{ + public EquatableArray GrainReferenceFactories { get; init; } = EquatableArray.Empty; +} diff --git a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs index ea431f040d9..d5342eebc19 100644 --- a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs +++ b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs @@ -48,6 +48,40 @@ internal static ReferenceAssemblyModel ExtractReferenceAssemblyData( var hasUnrepresentableProviderRegistration = false; var interfaceImplementations = new HashSet(); var diagnosticBuilder = ImmutableArray.CreateBuilder(); + var grainReferenceFactories = new HashSet(); + + foreach (var attribute in compilation.Assembly.GetAttributes()) + { + if (attribute.AttributeClass?.ToDisplayString() != "Orleans.GenerateGrainReferenceAttribute") + { + continue; + } + + if (attribute.ConstructorArguments.Length != 1 + || attribute.ConstructorArguments[0].Value is not INamedTypeSymbol interfaceType + || interfaceType.IsUnboundGenericType + || interfaceType.GetAllTypeArguments().Any(static argument => argument.TypeKind == TypeKind.TypeParameter) + || !compilation.IsSymbolAccessibleWithin(interfaceType, compilation.Assembly) + || ProxyInterfaceModelExtractor.ExtractProxyInterfaceModel(interfaceType.OriginalDefinition, compilation, cancellationToken) is not { } proxy + || !proxy.ProxyBase.SupportsGrainReferenceFactory) + { + diagnosticBuilder.Add(Diagnostic.Create( + InvalidGrainReferenceFactoryDiagnostic.Rule, + attribute.ApplicationSyntaxReference?.GetSyntax(cancellationToken).GetLocation())); + continue; + } + + referencedProxyInterfaces.Add(proxy); + var arguments = interfaceType.GetAllTypeArguments().ToArray(); + var proxyName = $"global::{proxy.GeneratedNamespace}.{ProxyGenerator.GetSimpleClassName(proxy.Name)}"; + if (arguments.Length > 0) + { + proxyName += $"<{string.Join(", ", arguments.Select(static argument => argument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))}>"; + } + + grainReferenceFactories.Add(new GrainReferenceFactoryModel( + new TypeRef(interfaceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), new TypeRef(proxyName))); + } CollectAssemblyAttributes(compilation.Assembly, includeApplicationParts: false); @@ -194,7 +228,10 @@ internal static ReferenceAssemblyModel ExtractReferenceAssemblyData( ReferencedProxyInterfaces: sortedReferencedProxyInterfaces, RegisteredCodecs: sortedRegisteredCodecs, RegisteredProviders: sortedRegisteredProviders, - InterfaceImplementations: sortedInterfaceImplementations); + InterfaceImplementations: sortedInterfaceImplementations) + { + GrainReferenceFactories = grainReferenceFactories.OrderBy(static entry => entry.InterfaceType.SyntaxString, StringComparer.Ordinal).ToImmutableArray(), + }; void AddApplicationPart(string applicationPart) { diff --git a/src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs b/src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs new file mode 100644 index 00000000000..08d45fefe82 --- /dev/null +++ b/src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Orleans; + +/// +/// Generates and registers a statically constructed reference for the specified grain interface. +/// +/// +/// Apply this attribute to an application assembly for each closed generic grain interface used +/// under NativeAOT. The generator also supports interfaces declared in referenced assemblies. +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public sealed class GenerateGrainReferenceAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// The concrete grain interface for which to generate a factory. + public GenerateGrainReferenceAttribute(Type interfaceType) => InterfaceType = interfaceType; + + /// + /// Gets the concrete grain interface for which to generate a factory. + /// + public Type InterfaceType { get; } +} diff --git a/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs b/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs new file mode 100644 index 00000000000..dbb5f4576c1 --- /dev/null +++ b/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Orleans.Runtime; + +namespace Orleans.Configuration; + +/// +/// Registers generated grain-reference factories and their declared grain interfaces. +/// +public sealed class GrainReferenceFactoryOptions +{ + internal Dictionary? Factory)> Factories { get; } = new(); + + /// + /// Registers a factory for a concrete grain interface. + /// + /// The grain interface implemented by the proxy. + /// The generated proxy type. + /// The factory which constructs a reference using shared state and a grain key. + public void Add(Type interfaceType, Type proxyType, Func factory) + { + ArgumentNullException.ThrowIfNull(interfaceType); + ArgumentNullException.ThrowIfNull(proxyType); + ArgumentNullException.ThrowIfNull(factory); + Factories[interfaceType] = (proxyType, factory); + } + + /// + /// Registers an open generic proxy for runtime-selected generic arguments. + /// + /// The open generic grain interface. + /// The open generic generated proxy type. + /// + /// Statically closed registrations added using take precedence. + /// Runtime-selected generic arguments use reflective proxy construction. + /// + public void Add( + Type interfaceType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type proxyType) + { + ArgumentNullException.ThrowIfNull(interfaceType); + ArgumentNullException.ThrowIfNull(proxyType); + Factories[interfaceType] = (proxyType, null); + } +} diff --git a/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs b/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs index cb8cb1554cc..b4540034eae 100644 --- a/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs +++ b/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs @@ -3,12 +3,11 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.CodeGeneration; +using Orleans.Configuration; using Orleans.Metadata; using Orleans.Runtime; using Orleans.Runtime.Versions; @@ -180,12 +179,13 @@ public GrainReference CreateReference(GrainId grainId) } /// - /// Provides functionality for mapping from a to the corresponding generated proxy type. + /// Maps a to its grain-reference factory. /// internal class RpcProvider { private readonly TypeConverter _typeConverter; private readonly Dictionary _mapping; + private readonly Dictionary> _factories = new(); /// /// Initializes a new instance of the class. @@ -201,8 +201,29 @@ public RpcProvider( _typeConverter = typeConverter; var proxyTypes = config.Value.InterfaceProxyTypes; _mapping = new Dictionary(); + var registrations = config.Value.GetOrCreate().Factories; + var registeredProxies = new HashSet(); + foreach (var (interfaceType, registration) in registrations) + { + var id = resolver.GetGrainInterfaceType(interfaceType); + registeredProxies.Add(registration.ProxyType); + if (registration.Factory is { } factory) + { + _factories[id] = factory; + } + else + { + _mapping[id] = registration.ProxyType; + } + } + foreach (var proxyType in proxyTypes) { + if (registeredProxies.Contains(proxyType)) + { + continue; + } + if (!typeof(IAddressable).IsAssignableFrom(proxyType)) { continue; @@ -217,6 +238,7 @@ public RpcProvider( var grainInterface = GetMainInterface(type); var id = resolver.GetGrainInterfaceType(grainInterface); _mapping[id] = type; + _factories.Remove(id); } [UnconditionalSuppressMessage( @@ -251,13 +273,18 @@ static Type GetMainInterface(Type t) } /// - /// Gets the generated proxy object type corresponding to the specified . + /// Gets the factory corresponding to the specified . /// /// The grain interface type. - /// The proxy object type. - /// A value indicating whether a suitable type was found and was able to be constructed. - public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out Type? result) + /// The grain-reference factory. + /// A value indicating whether a suitable factory was found. + public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out Func? result) { + if (_factories.TryGetValue(interfaceType, out result)) + { + return true; + } + GrainInterfaceType lookupId; Type[]? args; if (GenericGrainInterfaceType.TryParse(interfaceType, out var genericId)) @@ -271,18 +298,34 @@ public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out Typ args = default; } - if (!_mapping.TryGetValue(lookupId, out result)) + if (!_mapping.TryGetValue(lookupId, out var proxyType)) { + result = null; return false; } if (args is not null) { - result = result.MakeGenericType(args); + proxyType = proxyType.MakeGenericType(args); } + result = CreateLegacyFactory(proxyType); return true; } + + [UnconditionalSuppressMessage( + "Trimming", + "IL2070", + Justification = "Legacy proxy constructors are preserved by AddInterfaceProxy. Custom non-public constructors require explicit preservation.")] + private static Func CreateLegacyFactory(Type proxyType) + { + var constructor = proxyType.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + [typeof(GrainReferenceShared), typeof(IdSpan)]) + ?? throw new SerializerException("Invalid proxy type: " + proxyType); + var invoker = ConstructorInvoker.Create(constructor); + return (shared, key) => (GrainReference)invoker.Invoke(shared, key); + } } /// @@ -324,13 +367,9 @@ public GrainReferenceActivatorProvider( } /// - [UnconditionalSuppressMessage( - "Trimming", - "IL2067", - Justification = "Generated proxies are registered using TypeManifestOptions.AddInterfaceProxy, which preserves their public constructors before they enter RpcProvider's mapping.")] public bool TryGet(GrainType grainType, GrainInterfaceType interfaceType, [NotNullWhen(true)] out IGrainReferenceActivator? activator) { - if (!_rpcProvider.TryGet(interfaceType, out var proxyType)) + if (!_rpcProvider.TryGet(interfaceType, out var factory)) { activator = default; return false; @@ -357,7 +396,7 @@ public bool TryGet(GrainType grainType, GrainInterfaceType interfaceType, [NotNu _codecProvider, _copyContextPool, _serviceProvider); - activator = new GrainReferenceActivator(proxyType, shared); + activator = new GrainReferenceActivator(factory, shared); return true; } @@ -372,36 +411,14 @@ private sealed class GrainReferenceActivator : IGrainReferenceActivator /// /// Initializes a new instance of the class. /// - /// The generated proxy object type. + /// The grain-reference factory. /// The functionality shared between all grain references for a specified grain type and grain interface type. - [UnconditionalSuppressMessage( - "Trimming", - "IL2070", - Justification = "Generated proxy public constructors are preserved by AddInterfaceProxy. Non-public constructors remain supported for explicitly preserved custom proxies.")] public GrainReferenceActivator( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type referenceType, + Func factory, GrainReferenceShared shared) { _shared = shared; - - var ctor = referenceType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, new[] { typeof(GrainReferenceShared), typeof(IdSpan) }) - ?? throw new SerializerException("Invalid proxy type: " + referenceType); - - if (!RuntimeFeature.IsDynamicCodeSupported) - { - var invoker = ConstructorInvoker.Create(ctor); - _create = (shared, key) => (GrainReference)invoker.Invoke(shared, key); - return; - } - - var method = new DynamicMethod(referenceType.Name, typeof(GrainReference), new[] { typeof(object), typeof(GrainReferenceShared), typeof(IdSpan) }); - var il = method.GetILGenerator(); - // arg0 is unused for better delegate performance (avoids argument shuffling thunk) - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Newobj, ctor); - il.Emit(OpCodes.Ret); - _create = method.CreateDelegate>(); + _create = factory; } public GrainReference CreateReference(GrainId grainId) => _create(_shared, grainId.Key); diff --git a/src/Orleans.Serialization/Configuration/TypeManifestOptions.cs b/src/Orleans.Serialization/Configuration/TypeManifestOptions.cs index 0b2c75ff2fb..3751d83d475 100644 --- a/src/Orleans.Serialization/Configuration/TypeManifestOptions.cs +++ b/src/Orleans.Serialization/Configuration/TypeManifestOptions.cs @@ -31,6 +31,23 @@ public sealed class TypeManifestOptions private readonly HashSet _interfaces = new(); private readonly HashSet _interfaceProxies = new(); private readonly HashSet _interfaceImplementations = new(); + private readonly Dictionary _extensions = new(); + + /// + /// Gets or creates strongly typed configuration associated with this manifest. + /// + /// The configuration type. + /// The configuration instance owned by this manifest. + public T GetOrCreate() where T : class, new() + { + if (!_extensions.TryGetValue(typeof(T), out var value)) + { + value = new T(); + _extensions.Add(typeof(T), value); + } + + return (T)value; + } /// /// Gets or sets a value indicating whether should be enabled. diff --git a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs index 72f59d1cc80..392e97bd7e3 100644 --- a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs +++ b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs @@ -1065,6 +1065,14 @@ public enum ErrorCode OS_InvalidOS = 104601 } + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] + public sealed partial class GenerateGrainReferenceAttribute : System.Attribute + { + public GenerateGrainReferenceAttribute(System.Type interfaceType) { } + + public System.Type InterfaceType { get { throw null; } } + } + public abstract partial class Grain : IGrainBase, Runtime.IAddressable { protected Grain() { } @@ -1503,6 +1511,16 @@ public sealed partial class UnorderedAttribute : System.Attribute } } +namespace Orleans.Configuration +{ + public sealed partial class GrainReferenceFactoryOptions + { + public void Add(System.Type interfaceType, System.Type proxyType, System.Func factory) { } + + public void Add(System.Type interfaceType, System.Type proxyType) { } + } +} + namespace Orleans.Core { public partial interface IStorage diff --git a/src/api/Orleans.Serialization/Orleans.Serialization.cs b/src/api/Orleans.Serialization/Orleans.Serialization.cs index 04e673cb8a2..e2da7ba06b5 100644 --- a/src/api/Orleans.Serialization/Orleans.Serialization.cs +++ b/src/api/Orleans.Serialization/Orleans.Serialization.cs @@ -3403,6 +3403,9 @@ public void AddInterfaceImplementation(System.Type type) { } public void AddInterfaceProxy(System.Type type) { } public void AddSerializer(System.Type type) { } + + public T GetOrCreate() + where T : class, new() { throw null; } } [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] diff --git a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs new file mode 100644 index 00000000000..5b9dff9a505 --- /dev/null +++ b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs @@ -0,0 +1,156 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Orleans.CodeGenerator.Diagnostics; + +namespace Orleans.CodeGenerator.Tests; + +public class GrainReferenceFactoryCodegenTests +{ + [Fact] + public async Task GeneratedFactoriesUseDeclaredInterfaceAndDirectConstruction() + { + const string source = """ + using Orleans; + namespace TestProject; + public interface IBase : IGrainWithStringKey { } + public interface IDerived : IBase { } + public interface IGeneric : IBase { } + """; + + var (result, output) = Generate(await TestCompilationHelper.CreateCompilation(source)); + AssertCompiles(result, output); + var generated = GetSource(result); + Assert.Contains( + ".Add(typeof(global::TestProject.IDerived), typeof(OrleansCodeGen.TestProject.Proxy_IDerived), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IDerived(shared, key))", + generated, StringComparison.Ordinal); + Assert.Contains(".Add(typeof(global::TestProject.IGeneric<>), typeof(OrleansCodeGen.TestProject.Proxy_IGeneric<>))", generated, StringComparison.Ordinal); + Assert.DoesNotContain("GetConstructor", generated, StringComparison.Ordinal); + Assert.DoesNotContain("GetInterfaces", generated, StringComparison.Ordinal); + } + + [Fact] + public async Task ClosedGenericFactoriesSupportConstraintsAndCoalesceDuplicates() + { + const string source = """ + using Orleans; + [assembly: GenerateGrainReference(typeof(TestProject.IGeneric))] + [assembly: GenerateGrainReference(typeof(TestProject.IGeneric))] + [assembly: GenerateGrainReference(typeof(TestProject.Container.INested))] + namespace TestProject; + public interface IGeneric : IGrainWithStringKey where T : struct { } + public class Container where T : class + { + public interface INested : IGrainWithStringKey where U : struct { } + } + """; + + var (result, output) = Generate(await TestCompilationHelper.CreateCompilation(source)); + AssertCompiles(result, output); + var generated = GetSource(result); + Assert.Contains("static (shared, key) => new global::OrleansCodeGen.TestProject.Proxy_IGeneric(shared, key)", generated, StringComparison.Ordinal); + Assert.Contains("static (shared, key) => new global::OrleansCodeGen.TestProject.Container.Proxy_INested(shared, key)", generated, StringComparison.Ordinal); + Assert.Equal(1, generated.Split( + ".Add(typeof(global::TestProject.IGeneric)", StringSplitOptions.None).Length - 1); + } + + [Fact] + public async Task ClosedGenericFactoriesSupportReferencedInterfaces() + { + var contracts = await TestCompilationHelper.CreateCompilation(""" + using Orleans; + namespace Contracts; + public interface IGeneric : IGrainWithStringKey + { + System.Threading.Tasks.Task Echo(T value); + } + """, "Contracts"); + var (contractResult, contractOutput) = Generate(contracts); + AssertCompiles(contractResult, contractOutput); + using var stream = new MemoryStream(); + var emit = contractOutput.Emit(stream, cancellationToken: TestContext.Current.CancellationToken); + Assert.True(emit.Success, string.Join(Environment.NewLine, emit.Diagnostics)); + var consumer = await TestCompilationHelper.CreateCompilation(""" + using Orleans; + [assembly: GenerateGrainReference(typeof(Contracts.IGeneric))] + """, "Consumer", MetadataReference.CreateFromImage(stream.ToArray())); + + var (result, output) = Generate(consumer); + + AssertCompiles(result, output); + var generated = GetSource(result); + Assert.Contains("internal sealed class Proxy_IGeneric", generated, StringComparison.Ordinal); + Assert.Contains( + ".Add(typeof(global::Contracts.IGeneric), typeof(global::OrleansCodeGen.Contracts.Proxy_IGeneric), static (shared, key) => new global::OrleansCodeGen.Contracts.Proxy_IGeneric(shared, key))", + generated, StringComparison.Ordinal); + } + + [Theory] + [InlineData("int")] + [InlineData("TestProject.IGeneric<>")] + [InlineData("TestProject.IPlain")] + public async Task InvalidFactoryRegistrationProducesDiagnostic(string type) + { + var compilation = await TestCompilationHelper.CreateCompilation($$""" + using Orleans; + [assembly: GenerateGrainReference(typeof({{type}}))] + namespace TestProject; + public interface IGeneric : IGrainWithStringKey { } + public interface IPlain { } + """); + + var (result, _) = Generate(compilation); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal(DiagnosticRuleId.InvalidGrainReferenceFactory, diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); + Assert.True(diagnostic.Location.IsInSource); + } + + [Fact] + public async Task NonGrainProxyBasesKeepTypeOnlyRegistration() + { + const string source = """ + using Orleans; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + public class PlainProxy + { + public PlainProxy() { } + public ValueTask InvokeAsync(IInvokable request) => default; + public ValueTask InvokeAsync(IInvokable request) => default; + } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } + """; + + var (result, output) = Generate(await TestCompilationHelper.CreateCompilation(source)); + + AssertCompiles(result, output); + var generated = GetSource(result); + Assert.Contains("AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IPlain))", generated, StringComparison.Ordinal); + Assert.DoesNotContain("GrainReferenceFactoryOptions", generated, StringComparison.Ordinal); + } + + private static (GeneratorRunResult Result, Compilation Output) Generate(CSharpCompilation compilation) + { + GeneratorDriver driver = CSharpGeneratorDriver.Create( + [new OrleansSerializationSourceGenerator().AsSourceGenerator()], + parseOptions: (CSharpParseOptions)compilation.SyntaxTrees.First().Options, + optionsProvider: TestCompilationHelper.CreateOptionsProvider()); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var output, out _, TestContext.Current.CancellationToken); + return (Assert.Single(driver.GetRunResult().Results), output); + } + + private static string GetSource(GeneratorRunResult result) => + string.Join(Environment.NewLine, result.GeneratedSources.Select(static source => source.SourceText.ToString())); + + private static void AssertCompiles(GeneratorRunResult result, Compilation output) + { + Assert.Empty(result.Diagnostics); + Assert.Empty(output.GetDiagnostics(TestContext.Current.CancellationToken).Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); + using var stream = new MemoryStream(); + var emit = output.Emit(stream, cancellationToken: TestContext.Current.CancellationToken); + Assert.True(emit.Success, string.Join(Environment.NewLine, emit.Diagnostics)); + } +} diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs index c15c91d1335..2cf93771d71 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs @@ -254,6 +254,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IBasicGrain_GrainReference_6B0E24A1)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_BasicGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain)); + config.GetOrCreate().Add(typeof(global::TestProject.IBasicGrain), typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IBasicGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IBasicGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.BasicGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_BasicGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs index c6be9c7cf7d..6c0389af771 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs @@ -163,6 +163,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddSerializer(typeof(OrleansCodeGen.Codec_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddCopier(typeof(OrleansCodeGen.Copier_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddInterfaceProxy(typeof(OrleansCodeGen.Proxy_IMyGrain)); + config.GetOrCreate().Add(typeof(global::IMyGrain), typeof(OrleansCodeGen.Proxy_IMyGrain), static (shared, key) => new OrleansCodeGen.Proxy_IMyGrain(shared, key)); config.AddInterface(typeof(global::IMyGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); var n2 = n1.Add(typeof(global::Orleans.Runtime.GrainReference)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs index 171c11aef6e..916a00c92e6 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs @@ -449,6 +449,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IComplexGrain_GrainReference_67FE5808)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_ComplexGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain)); + config.GetOrCreate().Add(typeof(global::TestProject.IComplexGrain), typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IComplexGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IComplexGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ComplexGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_ComplexData)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs index a532a1a5df9..08541d4b0a4 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs @@ -259,6 +259,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IHelloGrain_GrainReference_5336307F)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_HelloGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain)); + config.GetOrCreate().Add(typeof(global::TestProject.IHelloGrain), typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IHelloGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IHelloGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.HelloGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_HelloGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs index 72fd2c84373..c4fc3ba919f 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs @@ -159,6 +159,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddSerializer(typeof(OrleansCodeGen.TestProject.Codec_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain)); + config.GetOrCreate().Add(typeof(global::TestProject.IResponseTimeoutGrain), typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IResponseTimeoutGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ResponseTimeoutGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs index d0428fc3d68..ed17a722c2f 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs @@ -787,6 +787,10 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey)); + config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithGuidKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey(shared, key)); + config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithStringKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey(shared, key)); + config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithGuidCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey(shared, key)); + config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithIntegerCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey(shared, key)); config.AddInterface(typeof(global::TestProject.IMyGrainWithGuidKey)); config.AddInterface(typeof(global::TestProject.IMyGrainWithStringKey)); config.AddInterface(typeof(global::TestProject.IMyGrainWithGuidCompoundKey)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs index 032a288b6e3..0245c63176e 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs @@ -299,6 +299,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IGrainB_GrainReference_6B5D7809)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainA)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainB)); + config.GetOrCreate().Add(typeof(global::TestProject.IGrainA), typeof(OrleansCodeGen.TestProject.Proxy_IGrainA), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainA(shared, key)); + config.GetOrCreate().Add(typeof(global::TestProject.IGrainB), typeof(OrleansCodeGen.TestProject.Proxy_IGrainB), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainB(shared, key)); config.AddInterface(typeof(global::TestProject.IGrainA)); config.AddInterface(typeof(global::TestProject.IGrainB)); config.AddInterfaceImplementation(typeof(global::TestProject.RealGrain)); diff --git a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs index db8bc7302f2..1ae9148574b 100644 --- a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs +++ b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs @@ -2,9 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.CodeGeneration; +using Orleans.Configuration; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Cloning; +using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; using TestExtensions; using Xunit; @@ -91,4 +93,117 @@ public void CreateReference_NonPublicProxyConstructorPreservesGrainId() GrainId.Create(ReferenceConstructionFixture.GrainType, IdSpan.Create("private-constructor")), reference.GrainId); } + + [Fact] + public void CreateReference_StaticallyClosedGenericFactoriesPreserveIdentityAndSharedState() + { + using var fixture = new ReferenceConstructionFixture(); + var first = fixture.CreateReference("int-key"); + var second = fixture.CreateReference("second-key"); + var text = fixture.CreateReference("text-key"); + + Assert.IsAssignableFrom>(first); + Assert.IsAssignableFrom>(text); + Assert.NotEqual(first.GetType(), text.GetType()); + Assert.Equal(fixture.Resolver.GetGrainInterfaceType(typeof(IGenericConstructionGrain)), first.InterfaceType); + Assert.Equal(fixture.Resolver.GetGrainInterfaceType(typeof(IGenericConstructionGrain)), text.InterfaceType); + Assert.Equal(IdSpan.Create("int-key"), first.GrainId.Key); + Assert.Equal(IdSpan.Create("text-key"), text.GrainId.Key); + Assert.Same(first.Shared, second.Shared); + Assert.NotSame(first.Shared, text.Shared); + var factories = fixture.ManifestOptions.GetOrCreate().Factories; + Assert.NotNull(factories[typeof(IGenericConstructionGrain)].Factory); + Assert.NotNull(factories[typeof(IGenericConstructionGrain)].Factory); + } + + [Fact] + public void CreateReference_RuntimeSelectedGenericPreservesJitCompatibility() + { + using var fixture = new ReferenceConstructionFixture(); + + var reference = fixture.CreateReference("generic-key"); + + Assert.IsAssignableFrom>(reference); + Assert.Equal(IdSpan.Create("generic-key"), reference.GrainId.Key); + Assert.Equal(fixture.Resolver.GetGrainInterfaceType(typeof(IGenericConstructionGrain)), reference.InterfaceType); + } + + [Fact] + public void CreateReference_LegacyGeneratedManifestPreservesCompatibility() + { + using var fixture = new ReferenceConstructionFixture(legacy: true); + + var reference = fixture.CreateReference("legacy-key"); + + Assert.IsAssignableFrom(reference); + Assert.Equal(IdSpan.Create("legacy-key"), reference.GrainId.Key); + Assert.Equal(17, reference.InterfaceVersion); + } + + [Fact] + public void CreateReference_RegisteredFactoryUsesDeclaredInterfaceAndPropagatesConstructorExceptions() + { + using var fixture = new ReferenceConstructionFixture( + proxyType: typeof(ThrowingConstructorProxy), factory: static (shared, key) => new ThrowingConstructorProxy(shared, key)); + + var exception = Assert.Throws(() => fixture.CreateReference("key")); + + Assert.Equal("proxy-constructor", exception.Message); + } + + [Fact] + public void CreateReference_RegisteredFactorySupportsPrivateConstructor() + { + using var fixture = new ReferenceConstructionFixture( + proxyType: typeof(NonPublicConstructorProxy), factory: NonPublicConstructorProxy.Create); + + var reference = Assert.IsType(fixture.CreateReference("private-key")); + + Assert.Equal(IdSpan.Create("private-key"), reference.GrainId.Key); + } + + [Fact] + public void CreateReference_LegacyCustomProxyOverridesGeneratedFactory() + { + using var fixture = new ReferenceConstructionFixture( + proxyType: typeof(InspectableConstructionProxy), includeGeneratedFactories: true); + var generated = fixture.ManifestOptions.GetOrCreate().Factories[typeof(IConstructionGrain)]; + Assert.NotNull(generated.Factory); + Assert.NotEqual(typeof(InspectableConstructionProxy), generated.ProxyType); + + var reference = Assert.IsType(fixture.CreateReference("override-key")); + + Assert.Equal(IdSpan.Create("override-key"), reference.GrainId.Key); + Assert.Equal(17, reference.InterfaceVersion); + } + + [Fact] + public void CreateReference_RegisteredFactoryUsesExplicitInterfaceForMultipleInterfaceProxy() + { + using var fixture = new ReferenceConstructionFixture( + proxyType: typeof(MultipleInterfaceConstructionProxy), + factory: static (shared, key) => new MultipleInterfaceConstructionProxy(shared, key)); + + var reference = Assert.IsType(fixture.CreateReference("multiple-key")); + + Assert.IsAssignableFrom(reference); + Assert.Equal(ReferenceConstructionFixture.InterfaceType, reference.InterfaceType); + Assert.Equal(17, reference.InterfaceVersion); + Assert.Equal(IdSpan.Create("multiple-key"), reference.GrainId.Key); + } + + [Fact] + public void ManifestExtensionsPreserveTypedConfigurationPerManifest() + { + var first = new TypeManifestOptions(); + var second = new TypeManifestOptions(); + var factories = first.GetOrCreate(); + factories.Add(typeof(IConstructionGrain), typeof(InspectableConstructionProxy), + static (shared, key) => new InspectableConstructionProxy(shared, key)); + + Assert.Same(factories, first.GetOrCreate()); + Assert.NotSame(factories, second.GetOrCreate()); + Assert.Empty(second.GetOrCreate().Factories); + Assert.Single(first.GetOrCreate().Factories); + } } diff --git a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs index 78c7fdd14ea..8f72ea5d34a 100644 --- a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs +++ b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Options; using Orleans; using Orleans.CodeGeneration; +using Orleans.Configuration; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime; @@ -15,6 +16,9 @@ using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; +[assembly: GenerateGrainReference(typeof(UnitTests.GrainReferences.IGenericConstructionGrain))] +[assembly: GenerateGrainReference(typeof(UnitTests.GrainReferences.IGenericConstructionGrain))] + namespace UnitTests.GrainReferences; internal sealed class ReferenceConstructionFixture : IDisposable @@ -24,7 +28,10 @@ internal sealed class ReferenceConstructionFixture : IDisposable public ReferenceConstructionFixture( bool unordered = false, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] Type? proxyType = null) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] Type? proxyType = null, + Func? factory = null, + bool legacy = false, + bool includeGeneratedFactories = false) { Runtime = new RecordingReferenceRuntime(); Services = new ServiceCollection() @@ -35,20 +42,36 @@ public ReferenceConstructionFixture( .AddSingleton(Runtime) .BuildServiceProvider(); var options = Services.GetRequiredService>(); + ManifestOptions = options.Value; if (proxyType is not null) { - var manifestOptions = new TypeManifestOptions(); + var manifestOptions = includeGeneratedFactories ? ManifestOptions : new TypeManifestOptions(); manifestOptions.AddInterfaceProxy(proxyType); + if (factory is not null) + { + manifestOptions.GetOrCreate().Add(typeof(IConstructionGrain), proxyType, factory); + } + + options = Options.Create(manifestOptions); + } + else if (legacy) + { + var manifestOptions = new TypeManifestOptions(); + foreach (var type in ManifestOptions.InterfaceProxyTypes) + { + manifestOptions.InterfaceProxies.Add(type); + } + options = Options.Create(manifestOptions); } var typeConverter = Services.GetRequiredService(); - var resolver = new GrainInterfaceTypeResolver([new ConstructionInterfaceTypeProvider()], typeConverter); + Resolver = new GrainInterfaceTypeResolver([new ConstructionInterfaceTypeProvider()], typeConverter); var manifestProvider = new ConstructionManifestProvider(unordered); Provider = new GrainReferenceActivatorProvider( Services, new GrainPropertiesResolver(manifestProvider), - new RpcProvider(options, resolver, typeConverter), + new RpcProvider(options, Resolver, typeConverter), Services.GetRequiredService(), Services.GetRequiredService(), new GrainVersionManifest(manifestProvider)); @@ -59,10 +82,15 @@ public ReferenceConstructionFixture( public RecordingReferenceRuntime Runtime { get; } public GrainReferenceActivatorProvider Provider { get; } public GrainReferenceActivator Activator { get; } + public TypeManifestOptions ManifestOptions { get; } + public GrainInterfaceTypeResolver Resolver { get; } public GrainReference CreateReference(string key) => Activator.CreateReference(GrainId.Create(GrainType, IdSpan.Create(key)), InterfaceType); + public GrainReference CreateReference(string key) => + Activator.CreateReference(GrainId.Create(GrainType, IdSpan.Create(key)), Resolver.GetGrainInterfaceType(typeof(IGenericConstructionGrain))); + public void Dispose() => Services.Dispose(); private sealed class ConstructionInterfaceTypeProvider : IGrainInterfaceTypeProvider @@ -100,6 +128,8 @@ public ConstructionManifestProvider(bool unordered) public interface IConstructionBaseGrain : IGrainWithStringKey; public interface IConstructionGrain : IConstructionBaseGrain; +public interface IGenericConstructionGrain : IConstructionBaseGrain; +public interface IOtherConstructionGrain : IGrainWithStringKey; internal sealed class RecordingReferenceRuntime : IGrainReferenceRuntime { @@ -145,6 +175,11 @@ public ThrowingConstructorProxy(GrainReferenceShared shared, IdSpan key) : base( internal sealed class NonPublicConstructorProxy : GrainReference, IConstructionGrain { private NonPublicConstructorProxy(GrainReferenceShared shared, IdSpan key) : base(shared, key) { } + + internal static GrainReference Create(GrainReferenceShared shared, IdSpan key) => new NonPublicConstructorProxy(shared, key); } internal sealed class ConstructionException(string message) : Exception(message); + +internal sealed class MultipleInterfaceConstructionProxy(GrainReferenceShared shared, IdSpan key) + : GrainReference(shared, key), IOtherConstructionGrain, IConstructionGrain; diff --git a/test/Orleans.NativeAotSmoke/GrainReferences.cs b/test/Orleans.NativeAotSmoke/GrainReferences.cs index e1bb54af8e0..2d326976812 100644 --- a/test/Orleans.NativeAotSmoke/GrainReferences.cs +++ b/test/Orleans.NativeAotSmoke/GrainReferences.cs @@ -14,6 +14,7 @@ internal static class Program private static void Main() { ValidateGeneratedProxy(); + ValidateClosedGenericFactories(); ValidateSharedState(unordered: false); ValidateSharedState(unordered: true); ValidateConstructorFailures(); @@ -21,6 +22,22 @@ private static void Main() Console.WriteLine("Production grain-reference provider constructed generated proxies under NativeAOT."); } + private static void ValidateClosedGenericFactories() + { + using var fixture = new ReferenceConstructionFixture(); + var number = fixture.CreateReference("number-key"); + var text = fixture.CreateReference("text-key"); + var repeated = fixture.CreateReference("number-key"); + + Ensure(number is IGenericConstructionGrain, "The statically closed value-type proxy was not constructed."); + Ensure(text is IGenericConstructionGrain, "The statically closed reference-type proxy was not constructed."); + Ensure(number.GetType() != text.GetType(), "Closed proxy types must retain their type arguments."); + Ensure(number.InterfaceType == fixture.Resolver.GetGrainInterfaceType(typeof(IGenericConstructionGrain)), "Closed value-type interface identity changed."); + Ensure(text.InterfaceType == fixture.Resolver.GetGrainInterfaceType(typeof(IGenericConstructionGrain)), "Closed reference-type interface identity changed."); + Ensure(number.GrainId.Key == IdSpan.Create("number-key") && text.GrainId.Key == IdSpan.Create("text-key"), "Closed generic grain keys changed."); + Ensure(!ReferenceEquals(number, repeated) && number.Equals(repeated), "Closed generic reference identity changed."); + } + private static void ValidateGeneratedProxy() { using var fixture = new ReferenceConstructionFixture(); @@ -46,7 +63,8 @@ private static void ValidateGeneratedProxy() private static void ValidateSharedState(bool unordered) { - using var fixture = new ReferenceConstructionFixture(unordered, proxyType: typeof(InspectableConstructionProxy)); + using var fixture = new ReferenceConstructionFixture( + unordered, proxyType: typeof(InspectableConstructionProxy), factory: static (shared, key) => new InspectableConstructionProxy(shared, key)); var first = (InspectableConstructionProxy)fixture.CreateReference("first-key"); var second = (InspectableConstructionProxy)fixture.CreateReference("second-key"); var shared = first.ConstructionShared; @@ -69,14 +87,15 @@ private static void ValidateConstructorFailures() () => invalid.CreateReference("key"), "Invalid proxy type: " + typeof(MissingConstructorProxy)); - using var throwing = new ReferenceConstructionFixture(unordered: false, proxyType: typeof(ThrowingConstructorProxy)); + using var throwing = new ReferenceConstructionFixture( + unordered: false, proxyType: typeof(ThrowingConstructorProxy), factory: static (shared, key) => new ThrowingConstructorProxy(shared, key)); ExpectException(() => throwing.CreateReference("key"), "proxy-constructor"); } - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(NonPublicConstructorProxy))] private static void ValidateNonPublicConstructor() { - using var fixture = new ReferenceConstructionFixture(unordered: false, proxyType: typeof(NonPublicConstructorProxy)); + using var fixture = new ReferenceConstructionFixture( + unordered: false, proxyType: typeof(NonPublicConstructorProxy), factory: NonPublicConstructorProxy.Create); var reference = fixture.CreateReference("private-constructor"); Ensure(reference is NonPublicConstructorProxy, "The preserved non-public proxy constructor was not invoked."); Ensure(reference.GrainId.Key == IdSpan.Create("private-constructor"), "The non-public constructor changed the grain key."); From ca482b9b64fa2a0496e15f7902a52d49c34aae47 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Thu, 1 Oct 2026 21:11:29 -0700 Subject: [PATCH 2/6] fix(codegen): preserve factory registration precedence and generic arity --- .../MetadataGenerator.cs | 7 ++-- .../GrainReferenceFactoryOptions.cs | 5 +++ .../GrainReferenceActivator.cs | 7 ++-- .../GrainReferenceFactoryCodegenTests.cs | 36 +++++++++++++++++++ .../GrainReferenceActivatorTests.cs | 13 +++++++ .../Orleans.NativeAotSmoke/GrainReferences.cs | 6 ++-- 6 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/Orleans.CodeGenerator/MetadataGenerator.cs b/src/Orleans.CodeGenerator/MetadataGenerator.cs index 0d6cb498e94..9bb1f129126 100644 --- a/src/Orleans.CodeGenerator/MetadataGenerator.cs +++ b/src/Orleans.CodeGenerator/MetadataGenerator.cs @@ -83,7 +83,7 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() { var interfaceType = GetOpenTypeSyntax(proxy.InterfaceType); var proxyType = GetGeneratedProxyTypeSyntax(proxy); - AddGrainReferenceFactory(body, interfaceType, proxyType, proxy.TypeParameters.Count == 0); + AddGrainReferenceFactory(body, interfaceType, proxyType, GetProxyGenericArity(proxy) == 0); } foreach (var factory in model.ReferenceAssemblyData.GrainReferenceFactories) @@ -604,10 +604,13 @@ private sealed class OpenGenericTypeSyntaxRewriter : CSharpSyntaxRewriter private static TypeSyntax GetGeneratedProxyTypeSyntax(ProxyInterfaceModel proxy) { - var genericArity = Math.Max(proxy.TypeParameters.Length, CountGenericArguments(proxy.InterfaceType)); + var genericArity = GetProxyGenericArity(proxy); return CreateGeneratedTypeSyntax(proxy.GeneratedNamespace, ProxyGenerator.GetSimpleClassName(proxy.Name), genericArity); } + private static int GetProxyGenericArity(ProxyInterfaceModel proxy) + => Math.Max(proxy.TypeParameters.Length, CountGenericArguments(proxy.InterfaceType)); + private static int CountGenericArguments(TypeRef typeRef) { var typeSyntax = typeRef.ToTypeSyntax(); diff --git a/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs b/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs index dbb5f4576c1..49403efb12d 100644 --- a/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs +++ b/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs @@ -12,6 +12,9 @@ public sealed class GrainReferenceFactoryOptions { internal Dictionary? Factory)> Factories { get; } = new(); + // Superseded registrations remain explicit proxies, so legacy discovery cannot displace their replacements. + internal HashSet ProxyTypes { get; } = new(); + /// /// Registers a factory for a concrete grain interface. /// @@ -23,6 +26,7 @@ public void Add(Type interfaceType, Type proxyType, Func(); - var registrations = config.Value.GetOrCreate().Factories; - var registeredProxies = new HashSet(); - foreach (var (interfaceType, registration) in registrations) + var registrations = config.Value.GetOrCreate(); + var registeredProxies = registrations.ProxyTypes; + foreach (var (interfaceType, registration) in registrations.Factories) { var id = resolver.GetGrainInterfaceType(interfaceType); - registeredProxies.Add(registration.ProxyType); if (registration.Factory is { } factory) { _factories[id] = factory; diff --git a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs index 5b9dff9a505..1223d4fbcd5 100644 --- a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs +++ b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs @@ -53,6 +53,42 @@ public interface INested : IGrainWithStringKey where U : struct { } ".Add(typeof(global::TestProject.IGeneric)", StringSplitOptions.None).Length - 1); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task NestedInterfacesCaptureContainingGenericParameters(bool registerClosedFactory) + { + var registration = registerClosedFactory + ? "[assembly: GenerateGrainReference(typeof(TestProject.Container.INested))]" + : string.Empty; + var source = $$""" + using Orleans; + {{registration}} + namespace TestProject; + public class Container where T : class + { + public interface INested : IGrainWithStringKey + { + System.Threading.Tasks.Task Echo(T value); + } + } + """; + + var (result, output) = Generate(await TestCompilationHelper.CreateCompilation(source)); + + AssertCompiles(result, output); + var generated = GetSource(result); + Assert.Contains( + ".Add(typeof(global::TestProject.Container<>.INested), typeof(OrleansCodeGen.TestProject.Container.Proxy_INested<>))", + generated, StringComparison.Ordinal); + Assert.DoesNotContain("new OrleansCodeGen.TestProject.Container.Proxy_INested<>", generated, StringComparison.Ordinal); + if (registerClosedFactory) + { + Assert.Contains("static (shared, key) => new global::OrleansCodeGen.TestProject.Container.Proxy_INested(shared, key)", + generated, StringComparison.Ordinal); + } + } + [Fact] public async Task ClosedGenericFactoriesSupportReferencedInterfaces() { diff --git a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs index 1ae9148574b..e7ceb4fb8d7 100644 --- a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs +++ b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs @@ -177,6 +177,19 @@ public void CreateReference_LegacyCustomProxyOverridesGeneratedFactory() Assert.Equal(17, reference.InterfaceVersion); } + [Fact] + public void CreateReference_RegisteredCustomFactoryOverridesGeneratedFactory() + { + using var fixture = new ReferenceConstructionFixture( + proxyType: typeof(ThrowingConstructorProxy), + factory: static (shared, key) => new ThrowingConstructorProxy(shared, key), + includeGeneratedFactories: true); + + var exception = Assert.Throws(() => fixture.CreateReference("override-key")); + + Assert.Equal("proxy-constructor", exception.Message); + } + [Fact] public void CreateReference_RegisteredFactoryUsesExplicitInterfaceForMultipleInterfaceProxy() { diff --git a/test/Orleans.NativeAotSmoke/GrainReferences.cs b/test/Orleans.NativeAotSmoke/GrainReferences.cs index 2d326976812..970cf65be5e 100644 --- a/test/Orleans.NativeAotSmoke/GrainReferences.cs +++ b/test/Orleans.NativeAotSmoke/GrainReferences.cs @@ -64,7 +64,8 @@ private static void ValidateGeneratedProxy() private static void ValidateSharedState(bool unordered) { using var fixture = new ReferenceConstructionFixture( - unordered, proxyType: typeof(InspectableConstructionProxy), factory: static (shared, key) => new InspectableConstructionProxy(shared, key)); + unordered, proxyType: typeof(InspectableConstructionProxy), + factory: static (shared, key) => new InspectableConstructionProxy(shared, key), includeGeneratedFactories: true); var first = (InspectableConstructionProxy)fixture.CreateReference("first-key"); var second = (InspectableConstructionProxy)fixture.CreateReference("second-key"); var shared = first.ConstructionShared; @@ -88,7 +89,8 @@ private static void ValidateConstructorFailures() "Invalid proxy type: " + typeof(MissingConstructorProxy)); using var throwing = new ReferenceConstructionFixture( - unordered: false, proxyType: typeof(ThrowingConstructorProxy), factory: static (shared, key) => new ThrowingConstructorProxy(shared, key)); + unordered: false, proxyType: typeof(ThrowingConstructorProxy), + factory: static (shared, key) => new ThrowingConstructorProxy(shared, key), includeGeneratedFactories: true); ExpectException(() => throwing.CreateReference("key"), "proxy-constructor"); } From 8f02bb1f1a3dbb5a9e862555e72db5eb2ee88cc6 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Fri, 2 Oct 2026 11:04:34 -0700 Subject: [PATCH 3/6] refactor(serialization): simplify typed proxy factory registration --- .../docs/implementation/serialization.md | 6 +- .../AnalyzerReleases.Unshipped.md | 1 - .../Diagnostics/DiagnosticRuleId.cs | 1 - .../InvalidGrainReferenceFactoryDiagnostic.cs | 14 --- .../MetadataGenerator.cs | 17 ++-- .../Model/MetadataAggregateModelBuilder.cs | 5 +- .../Model/ReferenceAssemblyModel.cs | 7 +- .../Model/ReferenceAssemblyModelExtractor.cs | 39 +-------- .../GenerateGrainReferenceAttribute.cs | 25 ------ .../GrainReferenceFactoryOptions.cs | 51 ----------- .../GrainReferenceActivator.cs | 3 +- .../InterfaceProxyFactoryOptions.cs | 76 +++++++++++++++++ .../Orleans.Core.Abstractions.cs | 18 ---- .../Orleans.Serialization.cs | 8 ++ .../GrainReferenceFactoryCodegenTests.cs | 85 ++++++++----------- ...eGeneratorTests.TestBasicGrain.verified.cs | 3 +- ...ateMethodSerializersAnnotation.verified.cs | 3 +- ...torTests.TestGrainComplexGrain.verified.cs | 3 +- ...AnnotatedWithInvokableBaseType.verified.cs | 3 +- ...odAnnotatedWithResponseTimeout.verified.cs | 3 +- ...TestGrainWithDifferentKeyTypes.verified.cs | 9 +- ...estGrainWithMultipleInterfaces.verified.cs | 5 +- .../GrainReferenceActivatorTests.cs | 66 ++++++++++++-- .../ReferenceConstructionFixture.cs | 18 ++-- 24 files changed, 227 insertions(+), 242 deletions(-) delete mode 100644 src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs delete mode 100644 src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs delete mode 100644 src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs create mode 100644 src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs diff --git a/docs/site/src/content/docs/implementation/serialization.md b/docs/site/src/content/docs/implementation/serialization.md index 3dc1f6cf743..8aa45139c99 100644 --- a/docs/site/src/content/docs/implementation/serialization.md +++ b/docs/site/src/content/docs/implementation/serialization.md @@ -74,9 +74,11 @@ Statically closed value-type constructor delegates update the caller's `ref` val Generated manifests register each concrete grain interface with a typed factory which directly constructs its proxy using `(GrainReferenceShared, IdSpan)`. The runtime resolves the factory using the declared interface identity. The activator caches that factory and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. Direct construction preserves proxy constructors through normal static call reachability and propagates constructor exceptions directly. -For NativeAOT applications, apply to the application assembly for each closed generic grain interface. The generator emits a statically closed factory, including when the interface is declared in a referenced assembly. Register every generic argument combination the application uses. The runtime selects exact closed registrations before resolving open generic families. +The serialization manifest stores factories in , parameterized by the construction delegate type. This keeps factory registration independent of grain runtime types and supports proxy hierarchies with different construction signatures. Custom proxies can register a direct typed factory in the manifest's configuration. -JIT applications also support generic arguments selected at runtime through explicitly registered open generic proxy mappings. Those mappings use reflective generic closure and a cached constructor invoker. Older generated manifests and custom proxies registered by type retain the same constructor-invoker compatibility path; custom proxies can register a direct typed factory through in the manifest's configuration. +The generator automatically registers factories for non-generic grain interfaces and open proxy mappings for generic interfaces. A statically closed factory registration selects direct construction for that concrete interface before the runtime resolves open generic families. NativeAOT deployments supply a concrete factory for each generic interface they construct; generated proxy names remain assembly-local implementation details. + +JIT applications also support generic arguments selected at runtime through explicitly registered open generic proxy mappings. Those mappings use reflective generic closure and a cached constructor invoker. Older generated manifests and custom proxies registered by type retain the same constructor-invoker compatibility path. For each grain interface method, generated code captures arguments in an invokable object. The generated proxy submits that object through its proxy base. On the target, generated dispatch metadata invokes the concrete implementation and encodes the response. diff --git a/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md b/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md index 1bb4e70950c..2d5ffee1aff 100644 --- a/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md +++ b/src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md @@ -10,4 +10,3 @@ ORLEANS0110 | Usage | Error | ReferenceAssemblyWithGenerateSerializerDiagnostic ORLEANS0111 | Usage | Error | Invalid invokable base type mapping ORLEANS0112 | Usage | Error | Invalid RPC parameter field identifier ORLEANS0113 | Usage | Warning | CancellationToken parameter is not last -ORLEANS0117 | Usage | Error | Invalid grain-reference factory registration diff --git a/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs b/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs index 39be0ef8911..3e9c469e31d 100644 --- a/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs +++ b/src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs @@ -16,5 +16,4 @@ internal static class DiagnosticRuleId public const string InvalidInvokableBaseTypeMapping = "ORLEANS0111"; public const string InvalidRpcParameterId = "ORLEANS0112"; public const string CancellationTokenNotLast = "ORLEANS0113"; - public const string InvalidGrainReferenceFactory = "ORLEANS0117"; } diff --git a/src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs b/src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs deleted file mode 100644 index 3585b95b4d6..00000000000 --- a/src/Orleans.CodeGenerator/Diagnostics/InvalidGrainReferenceFactoryDiagnostic.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Orleans.CodeGenerator.Diagnostics; - -internal static class InvalidGrainReferenceFactoryDiagnostic -{ - internal static readonly DiagnosticDescriptor Rule = new( - DiagnosticRuleId.InvalidGrainReferenceFactory, - "Invalid grain-reference factory", - "GenerateGrainReference requires an accessible concrete grain interface with a (GrainReferenceShared, IdSpan) proxy constructor", - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true); -} diff --git a/src/Orleans.CodeGenerator/MetadataGenerator.cs b/src/Orleans.CodeGenerator/MetadataGenerator.cs index 9bb1f129126..bac03066678 100644 --- a/src/Orleans.CodeGenerator/MetadataGenerator.cs +++ b/src/Orleans.CodeGenerator/MetadataGenerator.cs @@ -79,16 +79,18 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() AddRegistration(body, addProxyMethod, GetGeneratedProxyTypeSyntax(type)); } - foreach (var proxy in orderedProxyInterfaces.Where(static proxy => proxy.ProxyBase.SupportsGrainReferenceFactory)) + var grainReferenceProxies = orderedProxyInterfaces.Where(static proxy => proxy.ProxyBase.SupportsGrainReferenceFactory).ToArray(); + if (grainReferenceProxies.Length > 0) { - var interfaceType = GetOpenTypeSyntax(proxy.InterfaceType); - var proxyType = GetGeneratedProxyTypeSyntax(proxy); - AddGrainReferenceFactory(body, interfaceType, proxyType, GetProxyGenericArity(proxy) == 0); + body.Add(ParseStatement( + "var proxyFactories = config.GetOrCreate>>();")); } - foreach (var factory in model.ReferenceAssemblyData.GrainReferenceFactories) + foreach (var proxy in grainReferenceProxies) { - AddGrainReferenceFactory(body, ParseTypeName(factory.InterfaceType.SyntaxString), ParseTypeName(factory.ProxyType.SyntaxString), isClosed: true); + var interfaceType = GetOpenTypeSyntax(proxy.InterfaceType); + var proxyType = GetGeneratedProxyTypeSyntax(proxy); + AddGrainReferenceFactory(body, interfaceType, proxyType, GetProxyGenericArity(proxy) == 0); } var addInterfaceMethod = configParam.Member("AddInterface"); @@ -160,9 +162,8 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() private static void AddGrainReferenceFactory(List body, TypeSyntax interfaceType, TypeSyntax proxyType, bool isClosed) { - var options = "config.GetOrCreate()"; var factory = isClosed ? $", static (shared, key) => new {proxyType}(shared, key)" : string.Empty; - body.Add(ParseStatement($"{options}.Add(typeof({interfaceType}), typeof({proxyType}){factory});")); + body.Add(ParseStatement($"proxyFactories.Add(typeof({interfaceType}), typeof({proxyType}){factory});")); } private ClassDeclarationSyntax CreateMetadataClass( diff --git a/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs b/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs index 3152cabc47c..bdcebde8894 100644 --- a/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs +++ b/src/Orleans.CodeGenerator/Model/MetadataAggregateModelBuilder.cs @@ -121,10 +121,7 @@ private static ReferenceAssemblyModel NormalizeReferenceAssemblyData(ReferenceAs ReferencedProxyInterfaces: referencedProxyInterfaces, RegisteredCodecs: registeredCodecs, RegisteredProviders: normalizedRegisteredProviders, - InterfaceImplementations: interfaceImplementations) - { - GrainReferenceFactories = referenceData.GrainReferenceFactories, - }; + InterfaceImplementations: interfaceImplementations); } internal static ImmutableArray MergeSerializableTypes( diff --git a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs index ae2e765b6bf..505a1609eac 100644 --- a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs +++ b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModel.cs @@ -61,8 +61,6 @@ public InterfaceImplementationModel(TypeRef implementationType, SourceLocationMo /// internal readonly record struct RegisteredProviderModel(string Target, string Kind, string Name, TypeRef Type); -internal readonly record struct GrainReferenceFactoryModel(TypeRef InterfaceType, TypeRef ProxyType); - /// /// Aggregated data extracted from referenced assemblies via [GenerateCodeForDeclaringAssembly] /// and [ApplicationPart] attributes. This model is produced by a CompilationProvider-based @@ -78,7 +76,4 @@ internal sealed record class ReferenceAssemblyModel( EquatableArray ReferencedProxyInterfaces, EquatableArray RegisteredCodecs, EquatableArray RegisteredProviders, - EquatableArray InterfaceImplementations) -{ - public EquatableArray GrainReferenceFactories { get; init; } = EquatableArray.Empty; -} + EquatableArray InterfaceImplementations); diff --git a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs index d5342eebc19..ea431f040d9 100644 --- a/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs +++ b/src/Orleans.CodeGenerator/Model/ReferenceAssemblyModelExtractor.cs @@ -48,40 +48,6 @@ internal static ReferenceAssemblyModel ExtractReferenceAssemblyData( var hasUnrepresentableProviderRegistration = false; var interfaceImplementations = new HashSet(); var diagnosticBuilder = ImmutableArray.CreateBuilder(); - var grainReferenceFactories = new HashSet(); - - foreach (var attribute in compilation.Assembly.GetAttributes()) - { - if (attribute.AttributeClass?.ToDisplayString() != "Orleans.GenerateGrainReferenceAttribute") - { - continue; - } - - if (attribute.ConstructorArguments.Length != 1 - || attribute.ConstructorArguments[0].Value is not INamedTypeSymbol interfaceType - || interfaceType.IsUnboundGenericType - || interfaceType.GetAllTypeArguments().Any(static argument => argument.TypeKind == TypeKind.TypeParameter) - || !compilation.IsSymbolAccessibleWithin(interfaceType, compilation.Assembly) - || ProxyInterfaceModelExtractor.ExtractProxyInterfaceModel(interfaceType.OriginalDefinition, compilation, cancellationToken) is not { } proxy - || !proxy.ProxyBase.SupportsGrainReferenceFactory) - { - diagnosticBuilder.Add(Diagnostic.Create( - InvalidGrainReferenceFactoryDiagnostic.Rule, - attribute.ApplicationSyntaxReference?.GetSyntax(cancellationToken).GetLocation())); - continue; - } - - referencedProxyInterfaces.Add(proxy); - var arguments = interfaceType.GetAllTypeArguments().ToArray(); - var proxyName = $"global::{proxy.GeneratedNamespace}.{ProxyGenerator.GetSimpleClassName(proxy.Name)}"; - if (arguments.Length > 0) - { - proxyName += $"<{string.Join(", ", arguments.Select(static argument => argument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))}>"; - } - - grainReferenceFactories.Add(new GrainReferenceFactoryModel( - new TypeRef(interfaceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), new TypeRef(proxyName))); - } CollectAssemblyAttributes(compilation.Assembly, includeApplicationParts: false); @@ -228,10 +194,7 @@ internal static ReferenceAssemblyModel ExtractReferenceAssemblyData( ReferencedProxyInterfaces: sortedReferencedProxyInterfaces, RegisteredCodecs: sortedRegisteredCodecs, RegisteredProviders: sortedRegisteredProviders, - InterfaceImplementations: sortedInterfaceImplementations) - { - GrainReferenceFactories = grainReferenceFactories.OrderBy(static entry => entry.InterfaceType.SyntaxString, StringComparer.Ordinal).ToImmutableArray(), - }; + InterfaceImplementations: sortedInterfaceImplementations); void AddApplicationPart(string applicationPart) { diff --git a/src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs b/src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs deleted file mode 100644 index 08d45fefe82..00000000000 --- a/src/Orleans.Core.Abstractions/CodeGeneration/GenerateGrainReferenceAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Orleans; - -/// -/// Generates and registers a statically constructed reference for the specified grain interface. -/// -/// -/// Apply this attribute to an application assembly for each closed generic grain interface used -/// under NativeAOT. The generator also supports interfaces declared in referenced assemblies. -/// -[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] -public sealed class GenerateGrainReferenceAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The concrete grain interface for which to generate a factory. - public GenerateGrainReferenceAttribute(Type interfaceType) => InterfaceType = interfaceType; - - /// - /// Gets the concrete grain interface for which to generate a factory. - /// - public Type InterfaceType { get; } -} diff --git a/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs b/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs deleted file mode 100644 index 49403efb12d..00000000000 --- a/src/Orleans.Core.Abstractions/Configuration/GrainReferenceFactoryOptions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Orleans.Runtime; - -namespace Orleans.Configuration; - -/// -/// Registers generated grain-reference factories and their declared grain interfaces. -/// -public sealed class GrainReferenceFactoryOptions -{ - internal Dictionary? Factory)> Factories { get; } = new(); - - // Superseded registrations remain explicit proxies, so legacy discovery cannot displace their replacements. - internal HashSet ProxyTypes { get; } = new(); - - /// - /// Registers a factory for a concrete grain interface. - /// - /// The grain interface implemented by the proxy. - /// The generated proxy type. - /// The factory which constructs a reference using shared state and a grain key. - public void Add(Type interfaceType, Type proxyType, Func factory) - { - ArgumentNullException.ThrowIfNull(interfaceType); - ArgumentNullException.ThrowIfNull(proxyType); - ArgumentNullException.ThrowIfNull(factory); - ProxyTypes.Add(proxyType); - Factories[interfaceType] = (proxyType, factory); - } - - /// - /// Registers an open generic proxy for runtime-selected generic arguments. - /// - /// The open generic grain interface. - /// The open generic generated proxy type. - /// - /// Statically closed registrations added using take precedence. - /// Runtime-selected generic arguments use reflective proxy construction. - /// - public void Add( - Type interfaceType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type proxyType) - { - ArgumentNullException.ThrowIfNull(interfaceType); - ArgumentNullException.ThrowIfNull(proxyType); - ProxyTypes.Add(proxyType); - Factories[interfaceType] = (proxyType, null); - } -} diff --git a/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs b/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs index 61a47393a72..8cc4180de71 100644 --- a/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs +++ b/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.CodeGeneration; -using Orleans.Configuration; using Orleans.Metadata; using Orleans.Runtime; using Orleans.Runtime.Versions; @@ -201,7 +200,7 @@ public RpcProvider( _typeConverter = typeConverter; var proxyTypes = config.Value.InterfaceProxyTypes; _mapping = new Dictionary(); - var registrations = config.Value.GetOrCreate(); + var registrations = config.Value.GetOrCreate>>(); var registeredProxies = registrations.ProxyTypes; foreach (var (interfaceType, registration) in registrations.Factories) { diff --git a/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs b/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs new file mode 100644 index 00000000000..0191766d881 --- /dev/null +++ b/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +#if NET5_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif + +namespace Orleans.Serialization.Configuration; + +/// +/// Registers interface proxies and strongly typed construction delegates. +/// +/// The delegate type used to construct proxies. +public sealed class InterfaceProxyFactoryOptions where TFactory : Delegate +{ + internal Dictionary Factories { get; } = new(); + + // Superseded registrations remain explicit proxies, so legacy discovery cannot displace their replacements. + internal HashSet ProxyTypes { get; } = new(); + + /// + /// Registers a factory for a concrete interface. + /// + /// The interface implemented by the proxy. + /// The proxy type. + /// The delegate which constructs a proxy. + public void Add(Type interfaceType, Type proxyType, TFactory factory) + { + if (interfaceType is null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + if (proxyType is null) + { + throw new ArgumentNullException(nameof(proxyType)); + } + + if (factory is null) + { + throw new ArgumentNullException(nameof(factory)); + } + + ProxyTypes.Add(proxyType); + Factories[interfaceType] = (proxyType, factory); + } + + /// + /// Registers an open generic proxy for runtime-selected generic arguments. + /// + /// The open generic interface. + /// The open generic generated proxy type. + /// + /// Statically closed registrations added using take precedence. + /// Runtime-selected generic arguments use reflective proxy construction. + /// + public void Add( + Type interfaceType, +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + Type proxyType) + { + if (interfaceType is null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + if (proxyType is null) + { + throw new ArgumentNullException(nameof(proxyType)); + } + + ProxyTypes.Add(proxyType); + Factories[interfaceType] = (proxyType, null); + } +} diff --git a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs index 392e97bd7e3..72f59d1cc80 100644 --- a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs +++ b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs @@ -1065,14 +1065,6 @@ public enum ErrorCode OS_InvalidOS = 104601 } - [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] - public sealed partial class GenerateGrainReferenceAttribute : System.Attribute - { - public GenerateGrainReferenceAttribute(System.Type interfaceType) { } - - public System.Type InterfaceType { get { throw null; } } - } - public abstract partial class Grain : IGrainBase, Runtime.IAddressable { protected Grain() { } @@ -1511,16 +1503,6 @@ public sealed partial class UnorderedAttribute : System.Attribute } } -namespace Orleans.Configuration -{ - public sealed partial class GrainReferenceFactoryOptions - { - public void Add(System.Type interfaceType, System.Type proxyType, System.Func factory) { } - - public void Add(System.Type interfaceType, System.Type proxyType) { } - } -} - namespace Orleans.Core { public partial interface IStorage diff --git a/src/api/Orleans.Serialization/Orleans.Serialization.cs b/src/api/Orleans.Serialization/Orleans.Serialization.cs index e2da7ba06b5..6d7bd892ba1 100644 --- a/src/api/Orleans.Serialization/Orleans.Serialization.cs +++ b/src/api/Orleans.Serialization/Orleans.Serialization.cs @@ -3336,6 +3336,14 @@ public void WriteField(ref Buffers.Writer writer, namespace Orleans.Serialization.Configuration { + public sealed partial class InterfaceProxyFactoryOptions + where TFactory : System.Delegate + { + public void Add(System.Type interfaceType, System.Type proxyType, TFactory factory) { } + + public void Add(System.Type interfaceType, System.Type proxyType) { } + } + public partial interface IProviderMetadataProvider { void ConfigureProviders(System.Collections.Generic.IDictionary<(string Target, string Kind, string Name), System.Type> providers); diff --git a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs index 1223d4fbcd5..bd094c58981 100644 --- a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs +++ b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Orleans.CodeGenerator.Diagnostics; namespace Orleans.CodeGenerator.Tests; @@ -29,41 +28,48 @@ public interface IGeneric : IBase { } } [Fact] - public async Task ClosedGenericFactoriesSupportConstraintsAndCoalesceDuplicates() + public async Task ClosedGenericFactoriesUseOrdinaryTypedRegistration() { const string source = """ using Orleans; - [assembly: GenerateGrainReference(typeof(TestProject.IGeneric))] - [assembly: GenerateGrainReference(typeof(TestProject.IGeneric))] - [assembly: GenerateGrainReference(typeof(TestProject.Container.INested))] + using Orleans.Runtime; + using Orleans.Serialization.Configuration; + using Factories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions< + System.Func>; namespace TestProject; public interface IGeneric : IGrainWithStringKey where T : struct { } public class Container where T : class { public interface INested : IGrainWithStringKey where U : struct { } } + public static class Registration + { + public static void Configure(TypeManifestOptions options) + { + options.GetOrCreate().Add( + typeof(IGeneric), + typeof(OrleansCodeGen.TestProject.Proxy_IGeneric), + static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGeneric(shared, key)); + options.GetOrCreate().Add( + typeof(Container.INested), + typeof(OrleansCodeGen.TestProject.Container.Proxy_INested), + static (shared, key) => new OrleansCodeGen.TestProject.Container.Proxy_INested(shared, key)); + } + } """; var (result, output) = Generate(await TestCompilationHelper.CreateCompilation(source)); AssertCompiles(result, output); var generated = GetSource(result); - Assert.Contains("static (shared, key) => new global::OrleansCodeGen.TestProject.Proxy_IGeneric(shared, key)", generated, StringComparison.Ordinal); - Assert.Contains("static (shared, key) => new global::OrleansCodeGen.TestProject.Container.Proxy_INested(shared, key)", generated, StringComparison.Ordinal); - Assert.Equal(1, generated.Split( - ".Add(typeof(global::TestProject.IGeneric)", StringSplitOptions.None).Length - 1); + Assert.Contains(".Add(typeof(global::TestProject.IGeneric<>), typeof(OrleansCodeGen.TestProject.Proxy_IGeneric<>))", generated, StringComparison.Ordinal); + Assert.Contains("internal sealed class Proxy_INested", generated, StringComparison.Ordinal); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task NestedInterfacesCaptureContainingGenericParameters(bool registerClosedFactory) + [Fact] + public async Task NestedInterfacesCaptureContainingGenericParameters() { - var registration = registerClosedFactory - ? "[assembly: GenerateGrainReference(typeof(TestProject.Container.INested))]" - : string.Empty; - var source = $$""" + const string source = """ using Orleans; - {{registration}} namespace TestProject; public class Container where T : class { @@ -82,11 +88,6 @@ public interface INested : IGrainWithStringKey ".Add(typeof(global::TestProject.Container<>.INested), typeof(OrleansCodeGen.TestProject.Container.Proxy_INested<>))", generated, StringComparison.Ordinal); Assert.DoesNotContain("new OrleansCodeGen.TestProject.Container.Proxy_INested<>", generated, StringComparison.Ordinal); - if (registerClosedFactory) - { - Assert.Contains("static (shared, key) => new global::OrleansCodeGen.TestProject.Container.Proxy_INested(shared, key)", - generated, StringComparison.Ordinal); - } } [Fact] @@ -107,7 +108,17 @@ public interface IGeneric : IGrainWithStringKey Assert.True(emit.Success, string.Join(Environment.NewLine, emit.Diagnostics)); var consumer = await TestCompilationHelper.CreateCompilation(""" using Orleans; - [assembly: GenerateGrainReference(typeof(Contracts.IGeneric))] + using Orleans.Serialization.Configuration; + using Factories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions< + System.Func>; + [assembly: GenerateCodeForDeclaringAssembly(typeof(Contracts.IGeneric<>))] + public static class Registration + { + public static void Configure(TypeManifestOptions options) => options.GetOrCreate().Add( + typeof(Contracts.IGeneric), + typeof(OrleansCodeGen.Contracts.Proxy_IGeneric), + static (shared, key) => new OrleansCodeGen.Contracts.Proxy_IGeneric(shared, key)); + } """, "Consumer", MetadataReference.CreateFromImage(stream.ToArray())); var (result, output) = Generate(consumer); @@ -116,32 +127,10 @@ public interface IGeneric : IGrainWithStringKey var generated = GetSource(result); Assert.Contains("internal sealed class Proxy_IGeneric", generated, StringComparison.Ordinal); Assert.Contains( - ".Add(typeof(global::Contracts.IGeneric), typeof(global::OrleansCodeGen.Contracts.Proxy_IGeneric), static (shared, key) => new global::OrleansCodeGen.Contracts.Proxy_IGeneric(shared, key))", + ".Add(typeof(global::Contracts.IGeneric<>), typeof(OrleansCodeGen.Contracts.Proxy_IGeneric<>))", generated, StringComparison.Ordinal); } - [Theory] - [InlineData("int")] - [InlineData("TestProject.IGeneric<>")] - [InlineData("TestProject.IPlain")] - public async Task InvalidFactoryRegistrationProducesDiagnostic(string type) - { - var compilation = await TestCompilationHelper.CreateCompilation($$""" - using Orleans; - [assembly: GenerateGrainReference(typeof({{type}}))] - namespace TestProject; - public interface IGeneric : IGrainWithStringKey { } - public interface IPlain { } - """); - - var (result, _) = Generate(compilation); - - var diagnostic = Assert.Single(result.Diagnostics); - Assert.Equal(DiagnosticRuleId.InvalidGrainReferenceFactory, diagnostic.Id); - Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); - Assert.True(diagnostic.Location.IsInSource); - } - [Fact] public async Task NonGrainProxyBasesKeepTypeOnlyRegistration() { @@ -165,7 +154,7 @@ public interface IPlain { } AssertCompiles(result, output); var generated = GetSource(result); Assert.Contains("AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IPlain))", generated, StringComparison.Ordinal); - Assert.DoesNotContain("GrainReferenceFactoryOptions", generated, StringComparison.Ordinal); + Assert.DoesNotContain("InterfaceProxyFactoryOptions", generated, StringComparison.Ordinal); } private static (GeneratorRunResult Result, Compilation Output) Generate(CSharpCompilation compilation) diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs index 2cf93771d71..795cef21fdf 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs @@ -254,7 +254,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IBasicGrain_GrainReference_6B0E24A1)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_BasicGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain)); - config.GetOrCreate().Add(typeof(global::TestProject.IBasicGrain), typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IBasicGrain(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::TestProject.IBasicGrain), typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IBasicGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IBasicGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.BasicGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_BasicGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs index 6c0389af771..e5c43025788 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs @@ -163,7 +163,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddSerializer(typeof(OrleansCodeGen.Codec_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddCopier(typeof(OrleansCodeGen.Copier_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddInterfaceProxy(typeof(OrleansCodeGen.Proxy_IMyGrain)); - config.GetOrCreate().Add(typeof(global::IMyGrain), typeof(OrleansCodeGen.Proxy_IMyGrain), static (shared, key) => new OrleansCodeGen.Proxy_IMyGrain(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::IMyGrain), typeof(OrleansCodeGen.Proxy_IMyGrain), static (shared, key) => new OrleansCodeGen.Proxy_IMyGrain(shared, key)); config.AddInterface(typeof(global::IMyGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); var n2 = n1.Add(typeof(global::Orleans.Runtime.GrainReference)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs index 916a00c92e6..488306b1ddf 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs @@ -449,7 +449,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IComplexGrain_GrainReference_67FE5808)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_ComplexGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain)); - config.GetOrCreate().Add(typeof(global::TestProject.IComplexGrain), typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IComplexGrain(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::TestProject.IComplexGrain), typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IComplexGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IComplexGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ComplexGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_ComplexData)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs index 08541d4b0a4..b8c7264871b 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs @@ -259,7 +259,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IHelloGrain_GrainReference_5336307F)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_HelloGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain)); - config.GetOrCreate().Add(typeof(global::TestProject.IHelloGrain), typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IHelloGrain(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::TestProject.IHelloGrain), typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IHelloGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IHelloGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.HelloGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_HelloGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs index c4fc3ba919f..2293110a89e 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs @@ -159,7 +159,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddSerializer(typeof(OrleansCodeGen.TestProject.Codec_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain)); - config.GetOrCreate().Add(typeof(global::TestProject.IResponseTimeoutGrain), typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::TestProject.IResponseTimeoutGrain), typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain(shared, key)); config.AddInterface(typeof(global::TestProject.IResponseTimeoutGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ResponseTimeoutGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs index ed17a722c2f..a7e9dbb7e46 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs @@ -787,10 +787,11 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey)); - config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithGuidKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey(shared, key)); - config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithStringKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey(shared, key)); - config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithGuidCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey(shared, key)); - config.GetOrCreate().Add(typeof(global::TestProject.IMyGrainWithIntegerCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithStringKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithIntegerCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey(shared, key)); config.AddInterface(typeof(global::TestProject.IMyGrainWithGuidKey)); config.AddInterface(typeof(global::TestProject.IMyGrainWithStringKey)); config.AddInterface(typeof(global::TestProject.IMyGrainWithGuidCompoundKey)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs index 0245c63176e..e0fad30a370 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs @@ -299,8 +299,9 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IGrainB_GrainReference_6B5D7809)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainA)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainB)); - config.GetOrCreate().Add(typeof(global::TestProject.IGrainA), typeof(OrleansCodeGen.TestProject.Proxy_IGrainA), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainA(shared, key)); - config.GetOrCreate().Add(typeof(global::TestProject.IGrainB), typeof(OrleansCodeGen.TestProject.Proxy_IGrainB), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainB(shared, key)); + var proxyFactories = config.GetOrCreate>>(); + proxyFactories.Add(typeof(global::TestProject.IGrainA), typeof(OrleansCodeGen.TestProject.Proxy_IGrainA), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainA(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IGrainB), typeof(OrleansCodeGen.TestProject.Proxy_IGrainB), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainB(shared, key)); config.AddInterface(typeof(global::TestProject.IGrainA)); config.AddInterface(typeof(global::TestProject.IGrainB)); config.AddInterfaceImplementation(typeof(global::TestProject.RealGrain)); diff --git a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs index e7ceb4fb8d7..8d7b9a3e681 100644 --- a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs +++ b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.CodeGeneration; -using Orleans.Configuration; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Cloning; @@ -10,6 +9,7 @@ using Orleans.Serialization.Serializers; using TestExtensions; using Xunit; +using ReferenceFactories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions>; namespace UnitTests.GrainReferences; @@ -111,7 +111,7 @@ public void CreateReference_StaticallyClosedGenericFactoriesPreserveIdentityAndS Assert.Equal(IdSpan.Create("text-key"), text.GrainId.Key); Assert.Same(first.Shared, second.Shared); Assert.NotSame(first.Shared, text.Shared); - var factories = fixture.ManifestOptions.GetOrCreate().Factories; + var factories = fixture.ManifestOptions.GetOrCreate().Factories; Assert.NotNull(factories[typeof(IGenericConstructionGrain)].Factory); Assert.NotNull(factories[typeof(IGenericConstructionGrain)].Factory); } @@ -167,7 +167,7 @@ public void CreateReference_LegacyCustomProxyOverridesGeneratedFactory() { using var fixture = new ReferenceConstructionFixture( proxyType: typeof(InspectableConstructionProxy), includeGeneratedFactories: true); - var generated = fixture.ManifestOptions.GetOrCreate().Factories[typeof(IConstructionGrain)]; + var generated = fixture.ManifestOptions.GetOrCreate().Factories[typeof(IConstructionGrain)]; Assert.NotNull(generated.Factory); Assert.NotEqual(typeof(InspectableConstructionProxy), generated.ProxyType); @@ -210,13 +210,63 @@ public void ManifestExtensionsPreserveTypedConfigurationPerManifest() { var first = new TypeManifestOptions(); var second = new TypeManifestOptions(); - var factories = first.GetOrCreate(); + var factories = first.GetOrCreate(); factories.Add(typeof(IConstructionGrain), typeof(InspectableConstructionProxy), static (shared, key) => new InspectableConstructionProxy(shared, key)); - Assert.Same(factories, first.GetOrCreate()); - Assert.NotSame(factories, second.GetOrCreate()); - Assert.Empty(second.GetOrCreate().Factories); - Assert.Single(first.GetOrCreate().Factories); + Assert.Same(factories, first.GetOrCreate()); + Assert.NotSame(factories, second.GetOrCreate()); + Assert.Empty(second.GetOrCreate().Factories); + Assert.Single(first.GetOrCreate().Factories); + } + + [Fact] + public void InterfaceProxyFactoriesSupportIndependentDelegateSignatures() + { + var manifest = new TypeManifestOptions(); + var factories = manifest.GetOrCreate>>(); + factories.Add(typeof(IFormattable), typeof(int), static (prefix, value) => value); + factories.Add(typeof(IFormattable), typeof(int), static (prefix, value) => int.Parse(prefix) + value); + var other = manifest.GetOrCreate>>(); + other.Add(typeof(IComparable), typeof(string), static value => value.ToString()); + + var registration = Assert.Single(factories.Factories); + Assert.Equal(typeof(IFormattable), registration.Key); + Assert.Equal(typeof(int), registration.Value.ProxyType); + Assert.Equal(42, Assert.IsType>(registration.Value.Factory)("40", 2)); + Assert.Equal("42", Assert.IsType>(other.Factories[typeof(IComparable)].Factory)(42)); + Assert.Single(factories.ProxyTypes); + Assert.Single(other.Factories); + Assert.Equal(typeof(Serializer).Assembly, typeof(InterfaceProxyFactoryOptions<>).Assembly); + } + + [Theory] + [InlineData(true, "interfaceType")] + [InlineData(true, "proxyType")] + [InlineData(true, "factory")] + [InlineData(false, "interfaceType")] + [InlineData(false, "proxyType")] + public void InterfaceProxyFactoriesRejectNullArgumentsWithoutChangingRegistrations(bool registerFactory, string parameter) + { + var factories = new InterfaceProxyFactoryOptions>(); + var interfaceType = parameter == "interfaceType" ? null! : typeof(IComparable); + var proxyType = parameter == "proxyType" ? null! : typeof(string); + Func factory = parameter == "factory" ? null! : static () => string.Empty; + + var exception = Assert.Throws(() => + { + if (registerFactory) + { + factories.Add(interfaceType, proxyType, factory); + } + else + { + factories.Add(interfaceType, proxyType); + } + }); + + Assert.Equal(parameter, exception.ParamName); + Assert.Empty(factories.Factories); + Assert.Empty(factories.ProxyTypes); } } diff --git a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs index 8f72ea5d34a..cc74b554400 100644 --- a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs +++ b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Options; using Orleans; using Orleans.CodeGeneration; -using Orleans.Configuration; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime; @@ -15,9 +14,7 @@ using Orleans.Serialization.Invocation; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; - -[assembly: GenerateGrainReference(typeof(UnitTests.GrainReferences.IGenericConstructionGrain))] -[assembly: GenerateGrainReference(typeof(UnitTests.GrainReferences.IGenericConstructionGrain))] +using ReferenceFactories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions>; namespace UnitTests.GrainReferences; @@ -43,13 +40,24 @@ public ReferenceConstructionFixture( .BuildServiceProvider(); var options = Services.GetRequiredService>(); ManifestOptions = options.Value; + // This fixture owns the generated proxies in the same assembly and supplies concrete AOT factory roots. + var factories = ManifestOptions.GetOrCreate(); + factories.Add( + typeof(IGenericConstructionGrain), + typeof(OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain), + static (shared, key) => new OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain(shared, key)); + factories.Add( + typeof(IGenericConstructionGrain), + typeof(OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain), + static (shared, key) => new OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain(shared, key)); + if (proxyType is not null) { var manifestOptions = includeGeneratedFactories ? ManifestOptions : new TypeManifestOptions(); manifestOptions.AddInterfaceProxy(proxyType); if (factory is not null) { - manifestOptions.GetOrCreate().Add(typeof(IConstructionGrain), proxyType, factory); + manifestOptions.GetOrCreate().Add(typeof(IConstructionGrain), proxyType, factory); } options = Options.Create(manifestOptions); From a2e4d00367128798fd96935a653ab1088f6fccee Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Fri, 2 Oct 2026 11:39:29 -0700 Subject: [PATCH 4/6] refactor(codegen): enforce grain proxy factory constructor contract --- .../docs/implementation/serialization.md | 2 + .../MetadataGenerator.cs | 4 +- .../Model/ProxyInterfaceModel.cs | 2 +- .../Model/ProxyInterfaceModelExtractor.cs | 23 +---- src/Orleans.CodeGenerator/ProxyGenerator.cs | 31 +++++++ .../GrainReferenceFactoryCodegenTests.cs | 86 ++++++++++++++++++- ...eGeneratorTests.TestBasicGrain.verified.cs | 3 +- ...ateMethodSerializersAnnotation.verified.cs | 3 +- ...torTests.TestGrainComplexGrain.verified.cs | 3 +- ...AnnotatedWithInvokableBaseType.verified.cs | 3 +- ...odAnnotatedWithResponseTimeout.verified.cs | 3 +- ...TestGrainWithDifferentKeyTypes.verified.cs | 12 ++- ...estGrainWithMultipleInterfaces.verified.cs | 6 +- .../ReferenceConstructionFixture.cs | 4 +- 14 files changed, 144 insertions(+), 41 deletions(-) diff --git a/docs/site/src/content/docs/implementation/serialization.md b/docs/site/src/content/docs/implementation/serialization.md index 8aa45139c99..b38440e91b2 100644 --- a/docs/site/src/content/docs/implementation/serialization.md +++ b/docs/site/src/content/docs/implementation/serialization.md @@ -74,6 +74,8 @@ Statically closed value-type constructor delegates update the caller's `ref` val Generated manifests register each concrete grain interface with a typed factory which directly constructs its proxy using `(GrainReferenceShared, IdSpan)`. The runtime resolves the factory using the declared interface identity. The activator caches that factory and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. Direct construction preserves proxy constructors through normal static call reachability and propagates constructor exceptions directly. +Every generated proxy derived from declares a typed `Create` method with that constructor call. C# compilation checks the construction contract for both non-generic and open generic proxies. Custom grain-reference proxy bases supply an accessible `(GrainReferenceShared, IdSpan)` constructor. Other RPC proxy hierarchies retain their own constructor contracts. + The serialization manifest stores factories in , parameterized by the construction delegate type. This keeps factory registration independent of grain runtime types and supports proxy hierarchies with different construction signatures. Custom proxies can register a direct typed factory in the manifest's configuration. The generator automatically registers factories for non-generic grain interfaces and open proxy mappings for generic interfaces. A statically closed factory registration selects direct construction for that concrete interface before the runtime resolves open generic families. NativeAOT deployments supply a concrete factory for each generic interface they construct; generated proxy names remain assembly-local implementation details. diff --git a/src/Orleans.CodeGenerator/MetadataGenerator.cs b/src/Orleans.CodeGenerator/MetadataGenerator.cs index bac03066678..5cf45f7add1 100644 --- a/src/Orleans.CodeGenerator/MetadataGenerator.cs +++ b/src/Orleans.CodeGenerator/MetadataGenerator.cs @@ -79,7 +79,7 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() AddRegistration(body, addProxyMethod, GetGeneratedProxyTypeSyntax(type)); } - var grainReferenceProxies = orderedProxyInterfaces.Where(static proxy => proxy.ProxyBase.SupportsGrainReferenceFactory).ToArray(); + var grainReferenceProxies = orderedProxyInterfaces.Where(static proxy => proxy.ProxyBase.IsGrainReference).ToArray(); if (grainReferenceProxies.Length > 0) { body.Add(ParseStatement( @@ -162,7 +162,7 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() private static void AddGrainReferenceFactory(List body, TypeSyntax interfaceType, TypeSyntax proxyType, bool isClosed) { - var factory = isClosed ? $", static (shared, key) => new {proxyType}(shared, key)" : string.Empty; + var factory = isClosed ? $", {proxyType}.Create" : string.Empty; body.Add(ParseStatement($"proxyFactories.Add(typeof({interfaceType}), typeof({proxyType}){factory});")); } diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs index c7cb7192f98..6ad5e2d0b06 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs @@ -8,7 +8,7 @@ internal sealed record class ProxyBaseModel( bool IsExtension, string GeneratedClassNameComponent, TypeMetadataIdentity MetadataIdentity = default, - bool SupportsGrainReferenceFactory = false); + bool IsGrainReference = false); /// /// Describes a [GenerateMethodSerializers]-annotated interface for incremental proxy/invokable generation. diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs index 7dcfc578a62..63b46c143bd 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs @@ -95,7 +95,8 @@ internal static class ProxyInterfaceModelExtractor isExtension, generatedClassNameComponent, TypeMetadataIdentity.Create(proxyBaseType), - SupportsGrainReferenceFactory(proxyBaseType)); + compilation.GetTypeByMetadataName("Orleans.Runtime.GrainReference") is { } grainReference + && proxyBaseType.HasBaseType(grainReference)); var name = GetProxyInterfaceName(typeSymbol, libraryTypes); var typeParameters = ExtractInterfaceTypeParameters(typeSymbol); @@ -113,26 +114,6 @@ internal static class ProxyInterfaceModelExtractor MetadataIdentity: TypeMetadataIdentity.Create(typeSymbol)); } - private static bool SupportsGrainReferenceFactory(INamedTypeSymbol proxyBaseType) - { - var isGrainReference = false; - for (var type = proxyBaseType; type is not null; type = type.BaseType) - { - if (type.ToDisplayString() == "Orleans.Runtime.GrainReference") - { - isGrainReference = true; - break; - } - } - - return isGrainReference && proxyBaseType.InstanceConstructors.Any(static constructor => - constructor.DeclaredAccessibility != Accessibility.Private - && constructor.Parameters.Length == 2 - && constructor.Parameters.All(static parameter => parameter.RefKind == RefKind.None) - && constructor.Parameters[0].Type.ToDisplayString() == "Orleans.Runtime.GrainReferenceShared" - && constructor.Parameters[1].Type.ToDisplayString() == "Orleans.Runtime.IdSpan"); - } - private static string GetProxyInterfaceName(INamedTypeSymbol typeSymbol, LibraryTypes libraryTypes) { var alias = typeSymbol.GetAttribute(libraryTypes.AliasAttribute); diff --git a/src/Orleans.CodeGenerator/ProxyGenerator.cs b/src/Orleans.CodeGenerator/ProxyGenerator.cs index 8da7ad250cf..f75f57febb4 100644 --- a/src/Orleans.CodeGenerator/ProxyGenerator.cs +++ b/src/Orleans.CodeGenerator/ProxyGenerator.cs @@ -32,6 +32,7 @@ internal class ProxyGenerator(IGeneratorServices generatorServices, CopierGenera var proxyMethods = CreateProxyMethods(fieldDescriptions, interfaceDescription); var ctors = GenerateConstructors(generatedClassName, fieldDescriptions, interfaceDescription.ProxyBaseType); + var factories = GenerateFactory(generatedClassName, interfaceDescription); var classDeclaration = ClassDeclaration(generatedClassName) .AddBaseListTypes( @@ -41,6 +42,7 @@ internal class ProxyGenerator(IGeneratorServices generatorServices, CopierGenera .AddAttributeLists(GeneratedCodeUtilities.GetGeneratedCodeAttributes()) .AddMembers(fieldDeclarations) .AddMembers(ctors) + .AddMembers(factories) .AddMembers(proxyMethods); var typeParameters = interfaceDescription.TypeParameters; @@ -58,6 +60,35 @@ public static string GetSimpleClassName(ProxyInterfaceDescription interfaceDescr public static string GetSimpleClassName(string name) => $"Proxy_{SyntaxGeneration.Identifier.SanitizeIdentifierName(name)}"; + private MemberDeclarationSyntax[] GenerateFactory(string className, ProxyInterfaceDescription interfaceDescription) + { + if (LibraryTypes.Compilation.GetTypeByMetadataName("Orleans.Runtime.GrainReference") is not { } grainReference + || !interfaceDescription.ProxyBaseType.HasBaseType(grainReference)) + { + return []; + } + + var proxyType = interfaceDescription.TypeParameters.Count == 0 + ? (TypeSyntax)IdentifierName(className) + : GenericName(Identifier(className), TypeArgumentList(SeparatedList( + interfaceDescription.TypeParameters.Select(static parameter => parameter.Name.ToIdentifierName())))); + return + [ + MethodDeclaration(grainReference.ToTypeSyntax(), "Create") + .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) + .AddParameterListParameters( + Parameter(Identifier("shared")).WithType(ParseTypeName("global::Orleans.Runtime.GrainReferenceShared")), + Parameter(Identifier("key")).WithType(ParseTypeName("global::Orleans.Runtime.IdSpan"))) + .WithExpressionBody(ArrowExpressionClause( + ObjectCreationExpression(proxyType).WithArgumentList(ArgumentList(SeparatedList( + [ + Argument(IdentifierName("shared")), + Argument(IdentifierName("key")), + ]))))) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), + ]; + } + private List GetFieldDescriptions( ProxyInterfaceDescription interfaceDescription) { diff --git a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs index bd094c58981..b6c2948b996 100644 --- a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs +++ b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Orleans.CodeGenerator.Tests; @@ -20,7 +21,10 @@ public interface IGeneric : IBase { } AssertCompiles(result, output); var generated = GetSource(result); Assert.Contains( - ".Add(typeof(global::TestProject.IDerived), typeof(OrleansCodeGen.TestProject.Proxy_IDerived), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IDerived(shared, key))", + ".Add(typeof(global::TestProject.IDerived), typeof(OrleansCodeGen.TestProject.Proxy_IDerived), OrleansCodeGen.TestProject.Proxy_IDerived.Create)", + generated, StringComparison.Ordinal); + Assert.Contains( + "public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IDerived(shared, key);", generated, StringComparison.Ordinal); Assert.Contains(".Add(typeof(global::TestProject.IGeneric<>), typeof(OrleansCodeGen.TestProject.Proxy_IGeneric<>))", generated, StringComparison.Ordinal); Assert.DoesNotContain("GetConstructor", generated, StringComparison.Ordinal); @@ -49,11 +53,11 @@ public static void Configure(TypeManifestOptions options) options.GetOrCreate().Add( typeof(IGeneric), typeof(OrleansCodeGen.TestProject.Proxy_IGeneric), - static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGeneric(shared, key)); + OrleansCodeGen.TestProject.Proxy_IGeneric.Create); options.GetOrCreate().Add( typeof(Container.INested), typeof(OrleansCodeGen.TestProject.Container.Proxy_INested), - static (shared, key) => new OrleansCodeGen.TestProject.Container.Proxy_INested(shared, key)); + OrleansCodeGen.TestProject.Container.Proxy_INested.Create); } } """; @@ -117,7 +121,7 @@ public static class Registration public static void Configure(TypeManifestOptions options) => options.GetOrCreate().Add( typeof(Contracts.IGeneric), typeof(OrleansCodeGen.Contracts.Proxy_IGeneric), - static (shared, key) => new OrleansCodeGen.Contracts.Proxy_IGeneric(shared, key)); + OrleansCodeGen.Contracts.Proxy_IGeneric.Create); } """, "Consumer", MetadataReference.CreateFromImage(stream.ToArray())); @@ -155,8 +159,82 @@ public interface IPlain { } var generated = GetSource(result); Assert.Contains("AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IPlain))", generated, StringComparison.Ordinal); Assert.DoesNotContain("InterfaceProxyFactoryOptions", generated, StringComparison.Ordinal); + Assert.DoesNotContain("GrainReference Create", generated, StringComparison.Ordinal); } + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task GrainReferenceProxyFactoryUsesProtectedConstructor(bool generic, bool baseFactory) + { + var compilation = await TestCompilationHelper.CreateCompilation(CreateCustomProxySource( + "protected CustomReference(GrainReferenceShared shared, IdSpan key) : base(shared, key) { }", generic, baseFactory)); + + var (result, output) = Generate(compilation); + + AssertCompiles(result, output); + Assert.DoesNotContain(output.GetDiagnostics(TestContext.Current.CancellationToken), static diagnostic => diagnostic.Id == "CS0108"); + var generated = GetSource(result); + Assert.Contains( + generic ? "=> new Proxy_ICustom(shared, key);" : "=> new Proxy_ICustom(shared, key);", + generated, StringComparison.Ordinal); + Assert.Contains( + generic + ? ".Add(typeof(global::TestProject.ICustom<>), typeof(OrleansCodeGen.TestProject.Proxy_ICustom<>))" + : ".Add(typeof(global::TestProject.ICustom), typeof(OrleansCodeGen.TestProject.Proxy_ICustom), OrleansCodeGen.TestProject.Proxy_ICustom.Create)", + generated, StringComparison.Ordinal); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task GrainReferenceProxyWithoutRequiredConstructorFailsCompilation(bool generic, bool privateConstructor) + { + var constructor = "protected CustomReference(GrainReferenceShared shared) : base(shared, default) { }"; + if (privateConstructor) + { + constructor += "\nprivate CustomReference(GrainReferenceShared shared, IdSpan key) : base(shared, key) { }"; + } + + var compilation = await TestCompilationHelper.CreateCompilation(CreateCustomProxySource(constructor, generic)); + Assert.Empty(compilation.GetDiagnostics(TestContext.Current.CancellationToken).Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); + + var (result, output) = Generate(compilation); + + Assert.Empty(result.Diagnostics); + var diagnostic = Assert.Single(output.GetDiagnostics(TestContext.Current.CancellationToken), + static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error); + Assert.Equal("CS1729", diagnostic.Id); + Assert.Contains("Proxy_ICustom", diagnostic.GetMessage(), StringComparison.Ordinal); + Assert.Contains("proxy", diagnostic.Location.SourceTree!.FilePath, StringComparison.Ordinal); + var construction = diagnostic.Location.SourceTree.GetRoot(TestContext.Current.CancellationToken) + .FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); + var errorText = Assert.IsType(construction).ToString(); + Assert.Contains("Proxy_ICustom", errorText, StringComparison.Ordinal); + Assert.Contains("shared, key", errorText, StringComparison.Ordinal); + } + + private static string CreateCustomProxySource(string constructor, bool generic, bool baseFactory = false) => $$""" + using Orleans; + using Orleans.Runtime; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + public class CustomReference : GrainReference + { + {{constructor}} + public new ValueTask InvokeAsync(IRequest request) => base.InvokeAsync(request); + public new ValueTask InvokeAsync(IRequest request) => base.InvokeAsync(request); + {{(baseFactory ? "public static GrainReference Create(GrainReferenceShared shared, IdSpan key) => throw new System.NotSupportedException(\"base-factory\");" : "")}} + } + [GenerateMethodSerializers(typeof(CustomReference))] + public interface ICustom{{(generic ? "" : "")}} : IGrainWithStringKey { } + """; + private static (GeneratorRunResult Result, Compilation Output) Generate(CSharpCompilation compilation) { GeneratorDriver driver = CSharpGeneratorDriver.Create( diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs index 795cef21fdf..eadf3227adb 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs @@ -65,6 +65,7 @@ public Proxy_IBasicGrain(global::Orleans.Runtime.GrainReferenceShared arg0, glob { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IBasicGrain(shared, key); global::System.Threading.Tasks.Task global::TestProject.IBasicGrain.SayHello(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IBasicGrain_GrainReference_6B0E24A1(); @@ -255,7 +256,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_BasicGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::TestProject.IBasicGrain), typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IBasicGrain(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IBasicGrain), typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain), OrleansCodeGen.TestProject.Proxy_IBasicGrain.Create); config.AddInterface(typeof(global::TestProject.IBasicGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.BasicGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_BasicGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs index e5c43025788..5da7f3827ac 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs @@ -65,6 +65,7 @@ public Proxy_IMyGrain(global::Orleans.Runtime.GrainReferenceShared arg0, global: { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrain(shared, key); global::System.Threading.Tasks.Task global::IMyGrain.SayHello(string arg0) { var request = new OrleansCodeGen.Invokable_IMyGrain_GrainReference_6D39E404(); @@ -164,7 +165,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.Copier_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddInterfaceProxy(typeof(OrleansCodeGen.Proxy_IMyGrain)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::IMyGrain), typeof(OrleansCodeGen.Proxy_IMyGrain), static (shared, key) => new OrleansCodeGen.Proxy_IMyGrain(shared, key)); + proxyFactories.Add(typeof(global::IMyGrain), typeof(OrleansCodeGen.Proxy_IMyGrain), OrleansCodeGen.Proxy_IMyGrain.Create); config.AddInterface(typeof(global::IMyGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); var n2 = n1.Add(typeof(global::Orleans.Runtime.GrainReference)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs index 488306b1ddf..f9337e40abf 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs @@ -110,6 +110,7 @@ public Proxy_IComplexGrain(global::Orleans.Runtime.GrainReferenceShared arg0, gl _copier_ComplexData_765A40ED2309AF35 = OrleansGeneratedCodeHelper.GetService(this, CodecProvider); } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IComplexGrain(shared, key); global::System.Threading.Tasks.Task global::TestProject.IComplexGrain.ProcessData(int arg0, string arg1, global::TestProject.ComplexData arg2, global::System.Threading.CancellationToken arg3) { var request = new OrleansCodeGen.TestProject.Invokable_IComplexGrain_GrainReference_67FE5808(); @@ -450,7 +451,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_ComplexGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::TestProject.IComplexGrain), typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IComplexGrain(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IComplexGrain), typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain), OrleansCodeGen.TestProject.Proxy_IComplexGrain.Create); config.AddInterface(typeof(global::TestProject.IComplexGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ComplexGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_ComplexData)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs index b8c7264871b..fd8a76cc587 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs @@ -70,6 +70,7 @@ public Proxy_IHelloGrain(global::Orleans.Runtime.GrainReferenceShared arg0, glob { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IHelloGrain(shared, key); global::System.Threading.Tasks.Task global::TestProject.IHelloGrain.SayHello(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IHelloGrain_GrainReference_5336307F(); @@ -260,7 +261,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_HelloGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::TestProject.IHelloGrain), typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IHelloGrain(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IHelloGrain), typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain), OrleansCodeGen.TestProject.Proxy_IHelloGrain.Create); config.AddInterface(typeof(global::TestProject.IHelloGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.HelloGrain)); config.AddActivator(typeof(OrleansCodeGen.TestProject.Activator_HelloGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs index 2293110a89e..0211a42e438 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs @@ -67,6 +67,7 @@ public Proxy_IResponseTimeoutGrain(global::Orleans.Runtime.GrainReferenceShared { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IResponseTimeoutGrain(shared, key); global::System.Threading.Tasks.Task global::TestProject.IResponseTimeoutGrain.LongRunningMethod(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8(); @@ -160,7 +161,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::TestProject.IResponseTimeoutGrain), typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IResponseTimeoutGrain), typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain), OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain.Create); config.AddInterface(typeof(global::TestProject.IResponseTimeoutGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ResponseTimeoutGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs index a7e9dbb7e46..88864c52d9f 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs @@ -38,6 +38,7 @@ public Proxy_IMyGrainWithGuidKey(global::Orleans.Runtime.GrainReferenceShared ar { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithGuidKey(shared, key); global::System.Threading.Tasks.Task global::TestProject.IMyGrainWithGuidKey.GetGuidValue() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithGuidKey_GrainReference_8F0FEC0E(); @@ -73,6 +74,7 @@ public Proxy_IMyGrainWithStringKey(global::Orleans.Runtime.GrainReferenceShared { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithStringKey(shared, key); global::System.Threading.Tasks.Task global::TestProject.IMyGrainWithStringKey.GetStringKey() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithStringKey_GrainReference_43570316(); @@ -108,6 +110,7 @@ public Proxy_IMyGrainWithGuidCompoundKey(global::Orleans.Runtime.GrainReferenceS { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithGuidCompoundKey(shared, key); global::System.Threading.Tasks.Task> global::TestProject.IMyGrainWithGuidCompoundKey.GetGuidAndStringKey() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithGuidCompoundKey_GrainReference_A9FEF7AF(); @@ -143,6 +146,7 @@ public Proxy_IMyGrainWithIntegerCompoundKey(global::Orleans.Runtime.GrainReferen { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithIntegerCompoundKey(shared, key); global::System.Threading.Tasks.Task> global::TestProject.IMyGrainWithIntegerCompoundKey.GetIntegerAndStringKey() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithIntegerCompoundKey_GrainReference_9814021A(); @@ -788,10 +792,10 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey(shared, key)); - proxyFactories.Add(typeof(global::TestProject.IMyGrainWithStringKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey(shared, key)); - proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey(shared, key)); - proxyFactories.Add(typeof(global::TestProject.IMyGrainWithIntegerCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey.Create); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithStringKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey.Create); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey.Create); + proxyFactories.Add(typeof(global::TestProject.IMyGrainWithIntegerCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey.Create); config.AddInterface(typeof(global::TestProject.IMyGrainWithGuidKey)); config.AddInterface(typeof(global::TestProject.IMyGrainWithStringKey)); config.AddInterface(typeof(global::TestProject.IMyGrainWithGuidCompoundKey)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs index e0fad30a370..656b7bd63fb 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs @@ -65,6 +65,7 @@ public Proxy_IGrainA(global::Orleans.Runtime.GrainReferenceShared arg0, global:: { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IGrainA(shared, key); global::System.Threading.Tasks.Task global::TestProject.IGrainA.MethodA(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IGrainA_GrainReference_11405B98(); @@ -128,6 +129,7 @@ public Proxy_IGrainB(global::Orleans.Runtime.GrainReferenceShared arg0, global:: { } + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IGrainB(shared, key); global::System.Threading.Tasks.Task global::TestProject.IGrainB.MethodB(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IGrainB_GrainReference_6B5D7809(); @@ -300,8 +302,8 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainA)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainB)); var proxyFactories = config.GetOrCreate>>(); - proxyFactories.Add(typeof(global::TestProject.IGrainA), typeof(OrleansCodeGen.TestProject.Proxy_IGrainA), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainA(shared, key)); - proxyFactories.Add(typeof(global::TestProject.IGrainB), typeof(OrleansCodeGen.TestProject.Proxy_IGrainB), static (shared, key) => new OrleansCodeGen.TestProject.Proxy_IGrainB(shared, key)); + proxyFactories.Add(typeof(global::TestProject.IGrainA), typeof(OrleansCodeGen.TestProject.Proxy_IGrainA), OrleansCodeGen.TestProject.Proxy_IGrainA.Create); + proxyFactories.Add(typeof(global::TestProject.IGrainB), typeof(OrleansCodeGen.TestProject.Proxy_IGrainB), OrleansCodeGen.TestProject.Proxy_IGrainB.Create); config.AddInterface(typeof(global::TestProject.IGrainA)); config.AddInterface(typeof(global::TestProject.IGrainB)); config.AddInterfaceImplementation(typeof(global::TestProject.RealGrain)); diff --git a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs index cc74b554400..b9647280d9d 100644 --- a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs +++ b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs @@ -45,11 +45,11 @@ public ReferenceConstructionFixture( factories.Add( typeof(IGenericConstructionGrain), typeof(OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain), - static (shared, key) => new OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain(shared, key)); + OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain.Create); factories.Add( typeof(IGenericConstructionGrain), typeof(OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain), - static (shared, key) => new OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain(shared, key)); + OrleansCodeGen.UnitTests.GrainReferences.Proxy_IGenericConstructionGrain.Create); if (proxyType is not null) { From fbdee8d483753d4a793927efbdc32270202bb553 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Fri, 2 Oct 2026 12:55:00 -0700 Subject: [PATCH 5/6] feat(codegen): declare typed proxy factories on proxy bases --- .../docs/implementation/serialization.md | 4 +- src/Orleans.CodeGenerator/LibraryTypes.cs | 2 + .../MetadataGenerator.cs | 33 ++- .../Model/ProxyInterfaceDescription.cs | 40 ++++ .../Model/ProxyInterfaceModel.cs | 2 +- .../Model/ProxyInterfaceModelExtractor.cs | 8 +- src/Orleans.CodeGenerator/ProxyGenerator.cs | 23 +- .../Runtime/GrainReference.cs | 1 + .../GenerateProxyFactoryAttribute.cs | 28 +++ .../InterfaceProxyFactoryOptions.cs | 15 +- .../Orleans.Core.Abstractions.cs | 1 + .../Orleans.Serialization.Abstractions.cs | 8 + .../Orleans.Serialization.cs | 2 + .../GrainReferenceFactoryCodegenTests.cs | 218 +++++++++++++++++- ...eGeneratorTests.TestBasicGrain.verified.cs | 2 +- ...ateMethodSerializersAnnotation.verified.cs | 2 +- ...torTests.TestGrainComplexGrain.verified.cs | 2 +- ...AnnotatedWithInvokableBaseType.verified.cs | 2 +- ...odAnnotatedWithResponseTimeout.verified.cs | 2 +- ...TestGrainWithDifferentKeyTypes.verified.cs | 8 +- ...estGrainWithMultipleInterfaces.verified.cs | 4 +- .../Orleans.NativeAotSmoke/GrainReferences.cs | 30 +++ 22 files changed, 390 insertions(+), 47 deletions(-) create mode 100644 src/Orleans.Serialization.Abstractions/GenerateProxyFactoryAttribute.cs diff --git a/docs/site/src/content/docs/implementation/serialization.md b/docs/site/src/content/docs/implementation/serialization.md index b38440e91b2..5c88543dcca 100644 --- a/docs/site/src/content/docs/implementation/serialization.md +++ b/docs/site/src/content/docs/implementation/serialization.md @@ -74,7 +74,9 @@ Statically closed value-type constructor delegates update the caller's `ref` val Generated manifests register each concrete grain interface with a typed factory which directly constructs its proxy using `(GrainReferenceShared, IdSpan)`. The runtime resolves the factory using the declared interface identity. The activator caches that factory and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. Direct construction preserves proxy constructors through normal static call reachability and propagates constructor exceptions directly. -Every generated proxy derived from declares a typed `Create` method with that constructor call. C# compilation checks the construction contract for both non-generic and open generic proxies. Custom grain-reference proxy bases supply an accessible `(GrainReferenceShared, IdSpan)` constructor. Other RPC proxy hierarchies retain their own constructor contracts. +Proxy bases declare their construction delegate using . declares `Func`, while other RPC proxy bases can select their own delegate signatures. The generator emits a static `Create` method matching the declared delegate and forwards its parameters to the proxy constructor. C# compilation checks that constructor call for both non-generic and open generic proxies. + +Derived proxy bases inherit the factory declaration. A declaration on a nearer base overrides the inherited signature. Factory declarations use closed delegate types with a by-value proxy result; attribute arguments follow C#'s rules for concrete type references. The generator validates the delegate and its result conversion at build time, and preserves parameter passing modes such as `ref`. Proxy hierarchies supply accessible constructors matching their declared contract. The serialization manifest stores factories in , parameterized by the construction delegate type. This keeps factory registration independent of grain runtime types and supports proxy hierarchies with different construction signatures. Custom proxies can register a direct typed factory in the manifest's configuration. diff --git a/src/Orleans.CodeGenerator/LibraryTypes.cs b/src/Orleans.CodeGenerator/LibraryTypes.cs index 6f17d26f2fe..d20ab2a97e0 100644 --- a/src/Orleans.CodeGenerator/LibraryTypes.cs +++ b/src/Orleans.CodeGenerator/LibraryTypes.cs @@ -32,6 +32,7 @@ private LibraryTypes(Compilation compilation) MethodInfo = Type("System.Reflection.MethodInfo"); Func_2 = Type("System.Func`2"); GenerateMethodSerializersAttribute = Type("Orleans.GenerateMethodSerializersAttribute"); + GenerateProxyFactoryAttribute = TypeOrDefault("Orleans.GenerateProxyFactoryAttribute"); GenerateSerializerAttribute = Type("Orleans.GenerateSerializerAttribute"); SerializationCallbacksAttribute = Type("Orleans.SerializationCallbacksAttribute"); IActivator_1 = Type("Orleans.Serialization.Activators.IActivator`1"); @@ -217,6 +218,7 @@ INamedTypeSymbol Type(string metadataName) public INamedTypeSymbol Func_2 { get; private set; } public INamedTypeSymbol CompoundTypeAliasAttribute { get; private set; } public INamedTypeSymbol GenerateMethodSerializersAttribute { get; private set; } + public INamedTypeSymbol? GenerateProxyFactoryAttribute { get; } public INamedTypeSymbol GenerateSerializerAttribute { get; private set; } public INamedTypeSymbol IActivator_1 { get; private set; } public INamedTypeSymbol IBufferWriter { get; private set; } diff --git a/src/Orleans.CodeGenerator/MetadataGenerator.cs b/src/Orleans.CodeGenerator/MetadataGenerator.cs index 5cf45f7add1..554721cb9cf 100644 --- a/src/Orleans.CodeGenerator/MetadataGenerator.cs +++ b/src/Orleans.CodeGenerator/MetadataGenerator.cs @@ -79,18 +79,23 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() AddRegistration(body, addProxyMethod, GetGeneratedProxyTypeSyntax(type)); } - var grainReferenceProxies = orderedProxyInterfaces.Where(static proxy => proxy.ProxyBase.IsGrainReference).ToArray(); - if (grainReferenceProxies.Length > 0) - { + var factoryGroups = orderedProxyInterfaces + .Where(static proxy => !proxy.ProxyBase.FactoryType.IsEmpty) + .GroupBy(static proxy => proxy.ProxyBase.FactoryType) + .ToArray(); + for (var index = 0; index < factoryGroups.Length; index++) + { + var group = factoryGroups[index]; + var name = index == 0 ? "proxyFactories" : $"proxyFactories{index}"; body.Add(ParseStatement( - "var proxyFactories = config.GetOrCreate>>();")); - } - - foreach (var proxy in grainReferenceProxies) - { - var interfaceType = GetOpenTypeSyntax(proxy.InterfaceType); - var proxyType = GetGeneratedProxyTypeSyntax(proxy); - AddGrainReferenceFactory(body, interfaceType, proxyType, GetProxyGenericArity(proxy) == 0); + $"var {name} = config.GetOrCreate>();")); + foreach (var proxy in group) + { + var interfaceType = GetOpenTypeSyntax(proxy.InterfaceType); + var proxyType = GetGeneratedProxyTypeSyntax(proxy); + var factory = GetProxyGenericArity(proxy) == 0 ? $", {proxyType}.Create" : string.Empty; + body.Add(ParseStatement($"{name}.Add(typeof({interfaceType}), typeof({proxyType}){factory});")); + } } var addInterfaceMethod = configParam.Member("AddInterface"); @@ -160,12 +165,6 @@ private ClassDeclarationSyntax GenerateIncrementalMetadata() return CreateMetadataClass(body, providerBody, configParam); } - private static void AddGrainReferenceFactory(List body, TypeSyntax interfaceType, TypeSyntax proxyType, bool isClosed) - { - var factory = isClosed ? $", {proxyType}.Create" : string.Empty; - body.Add(ParseStatement($"proxyFactories.Add(typeof({interfaceType}), typeof({proxyType}){factory});")); - } - private ClassDeclarationSyntax CreateMetadataClass( List body, List providerBody, diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs index 8ef0cde9b4b..7b463da1268 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs @@ -17,6 +17,7 @@ public ProxyInterfaceDescription( INamedTypeSymbol interfaceType) { ValidateBaseClass(generationContext.LibraryTypes, proxyBaseType); + FactorySignature = GetFactorySignature(generationContext.LibraryTypes, proxyBaseType, interfaceType); var prop = interfaceType.GetAllMembers().FirstOrDefault(); if (prop is { }) @@ -112,6 +113,45 @@ static IEnumerable GetAllInterfaces(INamedTypeSymbol s) public string GeneratedNamespace { get; } public List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters { get; } public INamedTypeSymbol ProxyBaseType { get; } + public IMethodSymbol? FactorySignature { get; } + + private static IMethodSymbol? GetFactorySignature(LibraryTypes libraryTypes, INamedTypeSymbol proxyBaseType, INamedTypeSymbol interfaceType) + { + var attribute = libraryTypes.GenerateProxyFactoryAttribute is { } attributeType + ? proxyBaseType.GetAttribute(attributeType, inherited: true) + : null; + if (attribute is null) + { + return null; + } + + var location = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? proxyBaseType.Locations.First(); + if (attribute.ConstructorArguments.Length != 1 + || attribute.ConstructorArguments[0].Value is not INamedTypeSymbol { IsUnboundGenericType: false } factoryType + || factoryType.DelegateInvokeMethod is not { ReturnsVoid: false, ReturnsByRef: false, ReturnsByRefReadonly: false } signature) + { + throw new OrleansGeneratorDiagnosticAnalysisException( + IncorrectProxyBaseClassSpecificationDiagnostic.CreateDiagnostic( + proxyBaseType, location, "GenerateProxyFactory requires a closed delegate with a non-void, by-value return type.")); + } + + if (!libraryTypes.Compilation.ClassifyCommonConversion(proxyBaseType, signature.ReturnType).IsImplicit + && !libraryTypes.Compilation.ClassifyCommonConversion(interfaceType, signature.ReturnType).IsImplicit) + { + throw new OrleansGeneratorDiagnosticAnalysisException( + IncorrectProxyBaseClassSpecificationDiagnostic.CreateDiagnostic( + proxyBaseType, location, "GenerateProxyFactory's return type must accept the generated proxy base or interface.")); + } + + if (signature.Parameters.Any(static parameter => parameter.RefKind is not (RefKind.None or RefKind.Ref or RefKind.Out or RefKind.In))) + { + throw new OrleansGeneratorDiagnosticAnalysisException( + IncorrectProxyBaseClassSpecificationDiagnostic.CreateDiagnostic( + proxyBaseType, location, "GenerateProxyFactory supports by-value, ref, in, and out parameters.")); + } + + return signature; + } private static void ValidateBaseClass(LibraryTypes l, INamedTypeSymbol baseClass) { diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs index 6ad5e2d0b06..ec9e18e4ffb 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModel.cs @@ -8,7 +8,7 @@ internal sealed record class ProxyBaseModel( bool IsExtension, string GeneratedClassNameComponent, TypeMetadataIdentity MetadataIdentity = default, - bool IsGrainReference = false); + TypeRef FactoryType = default); /// /// Describes a [GenerateMethodSerializers]-annotated interface for incremental proxy/invokable generation. diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs index 63b46c143bd..9de2ff78f58 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceModelExtractor.cs @@ -90,13 +90,17 @@ internal static class ProxyInterfaceModelExtractor var proxyBaseType = proxyBaseTypeSymbol.OriginalDefinition; var generatedClassNameComponent = isExtension ? $"{proxyBaseType.Name}_Ext" : proxyBaseType.Name; + var factoryType = libraryTypes.GenerateProxyFactoryAttribute is { } factoryAttributeType + && proxyBaseType.GetAttribute(factoryAttributeType, inherited: true) is { ConstructorArguments.Length: 1 } factoryAttribute + && factoryAttribute.ConstructorArguments[0].Value is INamedTypeSymbol factoryDelegate + ? new TypeRef(factoryDelegate.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + : TypeRef.Empty; var proxyBase = new ProxyBaseModel( new TypeRef(proxyBaseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), isExtension, generatedClassNameComponent, TypeMetadataIdentity.Create(proxyBaseType), - compilation.GetTypeByMetadataName("Orleans.Runtime.GrainReference") is { } grainReference - && proxyBaseType.HasBaseType(grainReference)); + factoryType); var name = GetProxyInterfaceName(typeSymbol, libraryTypes); var typeParameters = ExtractInterfaceTypeParameters(typeSymbol); diff --git a/src/Orleans.CodeGenerator/ProxyGenerator.cs b/src/Orleans.CodeGenerator/ProxyGenerator.cs index f75f57febb4..fc3ab10fed5 100644 --- a/src/Orleans.CodeGenerator/ProxyGenerator.cs +++ b/src/Orleans.CodeGenerator/ProxyGenerator.cs @@ -62,8 +62,7 @@ public static string GetSimpleClassName(string name) private MemberDeclarationSyntax[] GenerateFactory(string className, ProxyInterfaceDescription interfaceDescription) { - if (LibraryTypes.Compilation.GetTypeByMetadataName("Orleans.Runtime.GrainReference") is not { } grainReference - || !interfaceDescription.ProxyBaseType.HasBaseType(grainReference)) + if (interfaceDescription.FactorySignature is not { } signature) { return []; } @@ -74,17 +73,23 @@ private MemberDeclarationSyntax[] GenerateFactory(string className, ProxyInterfa interfaceDescription.TypeParameters.Select(static parameter => parameter.Name.ToIdentifierName())))); return [ - MethodDeclaration(grainReference.ToTypeSyntax(), "Create") + MethodDeclaration(signature.ReturnType.ToTypeSyntax(), "Create") .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) .AddParameterListParameters( - Parameter(Identifier("shared")).WithType(ParseTypeName("global::Orleans.Runtime.GrainReferenceShared")), - Parameter(Identifier("key")).WithType(ParseTypeName("global::Orleans.Runtime.IdSpan"))) + [.. signature.Parameters.Select((parameter, index) => GetParameterSyntax(index, parameter, typeParameterSubstitutions: null))]) .WithExpressionBody(ArrowExpressionClause( ObjectCreationExpression(proxyType).WithArgumentList(ArgumentList(SeparatedList( - [ - Argument(IdentifierName("shared")), - Argument(IdentifierName("key")), - ]))))) + signature.Parameters.Select((parameter, index) => + { + var argument = Argument(IdentifierName($"arg{index}")); + return parameter.RefKind switch + { + RefKind.Ref => argument.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), + RefKind.Out => argument.WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword)), + RefKind.In => argument.WithRefOrOutKeyword(Token(SyntaxKind.InKeyword)), + _ => argument, + }; + })))))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), ]; } diff --git a/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs b/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs index 143ee50023c..f8c460f6a96 100644 --- a/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs +++ b/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs @@ -259,6 +259,7 @@ internal struct GrainReferenceSurrogate /// This is the base class for all grain references. /// [Alias("GrainRef")] + [GenerateProxyFactory(typeof(Func))] [DefaultInvokableBaseType(typeof(ValueTask<>), typeof(Request<>))] [DefaultInvokableBaseType(typeof(ValueTask), typeof(Request))] [DefaultInvokableBaseType(typeof(Task<>), typeof(TaskRequest<>))] diff --git a/src/Orleans.Serialization.Abstractions/GenerateProxyFactoryAttribute.cs b/src/Orleans.Serialization.Abstractions/GenerateProxyFactoryAttribute.cs new file mode 100644 index 00000000000..ae33ceda57c --- /dev/null +++ b/src/Orleans.Serialization.Abstractions/GenerateProxyFactoryAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace Orleans; + +/// +/// Specifies the construction delegate for generated proxies derived from this base type. +/// +/// +/// The generator emits a static Create method with the delegate's signature and registers +/// it for each concrete proxy interface. The delegate must be closed and return a proxy by value. +/// Its parameters are forwarded to the generated proxy constructor. +/// Derived proxy bases inherit the declaration and can supply their own declaration to override it. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = true)] +public sealed class GenerateProxyFactoryAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// The delegate type describing proxy construction. + public GenerateProxyFactoryAttribute(Type delegateType) => + DelegateType = delegateType ?? throw new ArgumentNullException(nameof(delegateType)); + + /// + /// Gets the delegate type describing proxy construction. + /// + public Type DelegateType { get; } +} diff --git a/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs b/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs index 0191766d881..1ac918c81ee 100644 --- a/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs +++ b/src/Orleans.Serialization/Configuration/InterfaceProxyFactoryOptions.cs @@ -12,7 +12,16 @@ namespace Orleans.Serialization.Configuration; /// The delegate type used to construct proxies. public sealed class InterfaceProxyFactoryOptions where TFactory : Delegate { - internal Dictionary Factories { get; } = new(); + private readonly Dictionary _factories = new(); + + /// + /// Gets proxy registrations keyed by their declared interfaces. + /// + /// + /// Concrete registrations contain a construction delegate. Generic definitions contain a proxy + /// type mapping for runtime-selected arguments. The latest registration for an interface is used. + /// + public IReadOnlyDictionary Factories => _factories; // Superseded registrations remain explicit proxies, so legacy discovery cannot displace their replacements. internal HashSet ProxyTypes { get; } = new(); @@ -41,7 +50,7 @@ public void Add(Type interfaceType, Type proxyType, TFactory factory) } ProxyTypes.Add(proxyType); - Factories[interfaceType] = (proxyType, factory); + _factories[interfaceType] = (proxyType, factory); } /// @@ -71,6 +80,6 @@ public void Add( } ProxyTypes.Add(proxyType); - Factories[interfaceType] = (proxyType, null); + _factories[interfaceType] = (proxyType, null); } } diff --git a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs index 72f59d1cc80..da0f21c9283 100644 --- a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs +++ b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs @@ -2266,6 +2266,7 @@ public static partial class GrainLifecycleStage } [Alias("GrainRef")] + [GenerateProxyFactory(typeof(System.Func))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.ValueTask<>), typeof(Request<>))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.ValueTask), typeof(Request))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.Task<>), typeof(TaskRequest<>))] diff --git a/src/api/Orleans.Serialization.Abstractions/Orleans.Serialization.Abstractions.cs b/src/api/Orleans.Serialization.Abstractions/Orleans.Serialization.Abstractions.cs index 9c816e4f857..70d5b03ef77 100644 --- a/src/api/Orleans.Serialization.Abstractions/Orleans.Serialization.Abstractions.cs +++ b/src/api/Orleans.Serialization.Abstractions/Orleans.Serialization.Abstractions.cs @@ -83,6 +83,14 @@ public GenerateMethodSerializersAttribute(System.Type proxyBase, bool isExtensio public System.Type ProxyBase { get { throw null; } } } + [System.AttributeUsage(System.AttributeTargets.Class, Inherited = true)] + public sealed partial class GenerateProxyFactoryAttribute : System.Attribute + { + public GenerateProxyFactoryAttribute(System.Type delegateType) { } + + public System.Type DelegateType { get { throw null; } } + } + [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum)] public sealed partial class GenerateSerializerAttribute : System.Attribute { diff --git a/src/api/Orleans.Serialization/Orleans.Serialization.cs b/src/api/Orleans.Serialization/Orleans.Serialization.cs index 6d7bd892ba1..0d1aeb98db8 100644 --- a/src/api/Orleans.Serialization/Orleans.Serialization.cs +++ b/src/api/Orleans.Serialization/Orleans.Serialization.cs @@ -3339,6 +3339,8 @@ namespace Orleans.Serialization.Configuration public sealed partial class InterfaceProxyFactoryOptions where TFactory : System.Delegate { + public System.Collections.Generic.IReadOnlyDictionary Factories { get { throw null; } } + public void Add(System.Type interfaceType, System.Type proxyType, TFactory factory) { } public void Add(System.Type interfaceType, System.Type proxyType) { } diff --git a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs index b6c2948b996..28d3c1ed447 100644 --- a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs +++ b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Orleans.CodeGenerator.Diagnostics; namespace Orleans.CodeGenerator.Tests; @@ -24,7 +25,7 @@ public interface IGeneric : IBase { } ".Add(typeof(global::TestProject.IDerived), typeof(OrleansCodeGen.TestProject.Proxy_IDerived), OrleansCodeGen.TestProject.Proxy_IDerived.Create)", generated, StringComparison.Ordinal); Assert.Contains( - "public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IDerived(shared, key);", + "public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IDerived(arg0, arg1);", generated, StringComparison.Ordinal); Assert.Contains(".Add(typeof(global::TestProject.IGeneric<>), typeof(OrleansCodeGen.TestProject.Proxy_IGeneric<>))", generated, StringComparison.Ordinal); Assert.DoesNotContain("GetConstructor", generated, StringComparison.Ordinal); @@ -151,6 +152,8 @@ public PlainProxy() { } } [GenerateMethodSerializers(typeof(PlainProxy))] public interface IPlain { } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } """; var (result, output) = Generate(await TestCompilationHelper.CreateCompilation(source)); @@ -178,7 +181,7 @@ public async Task GrainReferenceProxyFactoryUsesProtectedConstructor(bool generi Assert.DoesNotContain(output.GetDiagnostics(TestContext.Current.CancellationToken), static diagnostic => diagnostic.Id == "CS0108"); var generated = GetSource(result); Assert.Contains( - generic ? "=> new Proxy_ICustom(shared, key);" : "=> new Proxy_ICustom(shared, key);", + generic ? "=> new Proxy_ICustom(arg0, arg1);" : "=> new Proxy_ICustom(arg0, arg1);", generated, StringComparison.Ordinal); Assert.Contains( generic @@ -215,9 +218,218 @@ public async Task GrainReferenceProxyWithoutRequiredConstructorFailsCompilation( .FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); var errorText = Assert.IsType(construction).ToString(); Assert.Contains("Proxy_ICustom", errorText, StringComparison.Ordinal); - Assert.Contains("shared, key", errorText, StringComparison.Ordinal); + Assert.Contains("arg0, arg1", errorText, StringComparison.Ordinal); + } + + [Fact] + public async Task NonGrainProxyFactoryUsesBaseDeclaredDelegate() + { + const string source = """ + using Orleans; + using Orleans.Serialization.Configuration; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + [GenerateProxyFactory(typeof(System.Func))] + public class PlainProxy + { + public PlainProxy(string label, int number, bool enabled) + { + Label = label; Number = number; Enabled = enabled; + } + public string Label { get; } + public int Number { get; } + public bool Enabled { get; } + public ValueTask InvokeAsync(IInvokable request) => default; + public ValueTask InvokeAsync(IInvokable request) => default; + } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } + public interface IGrainLike : IGrainWithStringKey { } + public static class Verification + { + public static bool Verify() + { + var options = new TypeManifestOptions(); + ((ITypeManifestProvider)new OrleansCodeGen.TestProject.Metadata_TestProject()).Configure(options); + var factories = options.GetOrCreate>>(); + factories.Add(typeof(IPlain), typeof(OrleansCodeGen.TestProject.Proxy_IPlain), + OrleansCodeGen.TestProject.Proxy_IPlain.Create); + var registration = factories.Factories[typeof(IPlain)]; + var proxy = registration.Factory("plain", 42, true); + var generic = factories.Factories[typeof(IPlain)].Factory("generic", 17, false); + return proxy is IPlain && proxy.Label == "plain" && proxy.Number == 42 && proxy.Enabled + && registration.ProxyType == proxy.GetType() + && registration.Factory.Method.DeclaringType == proxy.GetType() + && generic is IPlain && generic.Label == "generic" && generic.Number == 17 && !generic.Enabled + && factories.Factories[typeof(IPlain<>)].ProxyType == typeof(OrleansCodeGen.TestProject.Proxy_IPlain<>) + && factories.Factories[typeof(IPlain<>)].Factory is null + && options.GetOrCreate>>() + .Factories[typeof(IGrainLike)].Factory is not null; + } + } + """; + + var (result, output) = Generate(await CreateConfiguredProxyCompilation(source)); + + AssertCompiles(result, output); + Assert.Contains("public static global::TestProject.PlainProxy Create(string arg0, int arg1, bool arg2) => new Proxy_IPlain(arg0, arg1, arg2);", + GetSource(result), StringComparison.Ordinal); + AssertVerificationSucceeds(output); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ProxyFactoryDeclarationIsInheritedAndNearestBaseCanOverrideIt(bool overrideFactory) + { + var declaration = overrideFactory ? "[GenerateProxyFactory(typeof(System.Func))]" : ""; + var constructor = overrideFactory ? "public ChildProxy(string label) { Label = label; }" : "public ChildProxy() { }"; + var invocation = overrideFactory + ? "var proxy = options.GetOrCreate>>().Factories[typeof(IChild)].Factory(\"child\");" + : "var proxy = options.GetOrCreate>>().Factories[typeof(IChild)].Factory();"; + var source = $$""" + using Orleans; + using Orleans.Serialization.Configuration; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + [GenerateProxyFactory(typeof(System.Func))] + public class ParentProxy + { + public string Label { get; protected set; } = "parent"; + public ValueTask InvokeAsync(IInvokable request) => default; + public ValueTask InvokeAsync(IInvokable request) => default; + } + {{declaration}} + public class ChildProxy : ParentProxy + { + {{constructor}} + public new ValueTask InvokeAsync(IInvokable request) => default; + public new ValueTask InvokeAsync(IInvokable request) => default; + } + [GenerateMethodSerializers(typeof(ChildProxy))] + public interface IChild { } + public static class Verification + { + public static bool Verify() + { + var options = new TypeManifestOptions(); + ((ITypeManifestProvider)new OrleansCodeGen.TestProject.Metadata_TestProject()).Configure(options); + {{invocation}} + return proxy is IChild && proxy is ChildProxy && proxy.Label == "{{(overrideFactory ? "child" : "parent")}}"; + } + } + """; + + var (result, output) = Generate(await CreateConfiguredProxyCompilation(source)); + + AssertCompiles(result, output); + AssertVerificationSucceeds(output); } + [Theory] + [InlineData("ref")] + [InlineData("in")] + [InlineData("out")] + public async Task ProxyFactoryCustomDelegatePreservesReferenceParameters(string mode) + { + var initialization = mode switch + { + "out" => "Number = value = 42;", + "in" => "Number = value + 1;", + _ => "Number = ++value;", + }; + var source = $$""" + using Orleans; + using Orleans.Serialization.Configuration; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + public delegate PlainProxy ProxyFactory({{mode}} int value); + [GenerateProxyFactory(typeof(ProxyFactory))] + public class PlainProxy + { + public PlainProxy({{mode}} int value) { {{initialization}} } + public int Number { get; } + public ValueTask InvokeAsync(IInvokable request) => default; + public ValueTask InvokeAsync(IInvokable request) => default; + } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } + public static class Verification + { + public static bool Verify() + { + var options = new TypeManifestOptions(); + ((ITypeManifestProvider)new OrleansCodeGen.TestProject.Metadata_TestProject()).Configure(options); + var factory = options.GetOrCreate>().Factories[typeof(IPlain)].Factory; + var value = 41; + var proxy = factory({{mode}} value); + return proxy is IPlain && proxy.Number == 42 && value == {{(mode == "in" ? 41 : 42)}}; + } + } + """; + + var (result, output) = Generate(await CreateConfiguredProxyCompilation(source)); + + AssertCompiles(result, output); + Assert.Contains($"Create({mode} int arg0) => new Proxy_IPlain({mode} arg0);", GetSource(result), StringComparison.Ordinal); + AssertVerificationSucceeds(output); + } + + [Theory] + [InlineData("typeof(int)")] + [InlineData("typeof(System.Action)")] + [InlineData("typeof(System.Func<,>)")] + [InlineData("typeof(RefFactory)")] + [InlineData("typeof(System.Func)")] + [InlineData("null")] + public async Task InvalidProxyFactoryDeclarationReportsProxyBaseDiagnostic(string declaration) + { + var source = $$""" + using Orleans; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + public delegate ref PlainProxy RefFactory(); + [GenerateProxyFactory({{declaration}})] + public class PlainProxy + { + public PlainProxy() { } + public ValueTask InvokeAsync(IInvokable request) => default; + public ValueTask InvokeAsync(IInvokable request) => default; + } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } + """; + + var (result, _) = Generate(await TestCompilationHelper.CreateCompilation(source)); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal(DiagnosticRuleId.IncorrectProxyBaseClassSpecification, diagnostic.Id); + Assert.Contains("GenerateProxyFactory", diagnostic.GetMessage(), StringComparison.Ordinal); + Assert.True(diagnostic.Location.IsInSource); + } + + private static void AssertVerificationSucceeds(Compilation output) + { + using var stream = new MemoryStream(); + var emit = output.Emit(stream, cancellationToken: TestContext.Current.CancellationToken); + Assert.True(emit.Success, string.Join(Environment.NewLine, emit.Diagnostics)); + var assembly = System.Reflection.Assembly.Load(stream.ToArray()); + var method = assembly.GetType("TestProject.Verification")!.GetMethod("Verify")!; + Assert.True(Assert.IsType(method.Invoke(null, null))); + } + + private static Task CreateConfiguredProxyCompilation(string source) => + TestCompilationHelper.CreateCompilation(source, additionalReferences: + [ + MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Options.IConfigureOptions<>).Assembly.Location), + ]); + private static string CreateCustomProxySource(string constructor, bool generic, bool baseFactory = false) => $$""" using Orleans; using Orleans.Runtime; diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs index eadf3227adb..781e5f6172e 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs @@ -65,7 +65,7 @@ public Proxy_IBasicGrain(global::Orleans.Runtime.GrainReferenceShared arg0, glob { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IBasicGrain(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IBasicGrain(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IBasicGrain.SayHello(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IBasicGrain_GrainReference_6B0E24A1(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs index 5da7f3827ac..105d41aa2d8 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs @@ -65,7 +65,7 @@ public Proxy_IMyGrain(global::Orleans.Runtime.GrainReferenceShared arg0, global: { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrain(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IMyGrain(arg0, arg1); global::System.Threading.Tasks.Task global::IMyGrain.SayHello(string arg0) { var request = new OrleansCodeGen.Invokable_IMyGrain_GrainReference_6D39E404(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs index f9337e40abf..7cee6243216 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs @@ -110,7 +110,7 @@ public Proxy_IComplexGrain(global::Orleans.Runtime.GrainReferenceShared arg0, gl _copier_ComplexData_765A40ED2309AF35 = OrleansGeneratedCodeHelper.GetService(this, CodecProvider); } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IComplexGrain(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IComplexGrain(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IComplexGrain.ProcessData(int arg0, string arg1, global::TestProject.ComplexData arg2, global::System.Threading.CancellationToken arg3) { var request = new OrleansCodeGen.TestProject.Invokable_IComplexGrain_GrainReference_67FE5808(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs index fd8a76cc587..635b7362813 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs @@ -70,7 +70,7 @@ public Proxy_IHelloGrain(global::Orleans.Runtime.GrainReferenceShared arg0, glob { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IHelloGrain(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IHelloGrain(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IHelloGrain.SayHello(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IHelloGrain_GrainReference_5336307F(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs index 0211a42e438..5e40d3da00c 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs @@ -67,7 +67,7 @@ public Proxy_IResponseTimeoutGrain(global::Orleans.Runtime.GrainReferenceShared { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IResponseTimeoutGrain(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IResponseTimeoutGrain(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IResponseTimeoutGrain.LongRunningMethod(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs index 88864c52d9f..244501545a4 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs @@ -38,7 +38,7 @@ public Proxy_IMyGrainWithGuidKey(global::Orleans.Runtime.GrainReferenceShared ar { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithGuidKey(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IMyGrainWithGuidKey(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IMyGrainWithGuidKey.GetGuidValue() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithGuidKey_GrainReference_8F0FEC0E(); @@ -74,7 +74,7 @@ public Proxy_IMyGrainWithStringKey(global::Orleans.Runtime.GrainReferenceShared { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithStringKey(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IMyGrainWithStringKey(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IMyGrainWithStringKey.GetStringKey() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithStringKey_GrainReference_43570316(); @@ -110,7 +110,7 @@ public Proxy_IMyGrainWithGuidCompoundKey(global::Orleans.Runtime.GrainReferenceS { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithGuidCompoundKey(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IMyGrainWithGuidCompoundKey(arg0, arg1); global::System.Threading.Tasks.Task> global::TestProject.IMyGrainWithGuidCompoundKey.GetGuidAndStringKey() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithGuidCompoundKey_GrainReference_A9FEF7AF(); @@ -146,7 +146,7 @@ public Proxy_IMyGrainWithIntegerCompoundKey(global::Orleans.Runtime.GrainReferen { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IMyGrainWithIntegerCompoundKey(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IMyGrainWithIntegerCompoundKey(arg0, arg1); global::System.Threading.Tasks.Task> global::TestProject.IMyGrainWithIntegerCompoundKey.GetIntegerAndStringKey() { var request = new OrleansCodeGen.TestProject.Invokable_IMyGrainWithIntegerCompoundKey_GrainReference_9814021A(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs index 656b7bd63fb..4fa9dccb808 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs @@ -65,7 +65,7 @@ public Proxy_IGrainA(global::Orleans.Runtime.GrainReferenceShared arg0, global:: { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IGrainA(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IGrainA(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IGrainA.MethodA(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IGrainA_GrainReference_11405B98(); @@ -129,7 +129,7 @@ public Proxy_IGrainB(global::Orleans.Runtime.GrainReferenceShared arg0, global:: { } - public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared shared, global::Orleans.Runtime.IdSpan key) => new Proxy_IGrainB(shared, key); + public static global::Orleans.Runtime.GrainReference Create(global::Orleans.Runtime.GrainReferenceShared arg0, global::Orleans.Runtime.IdSpan arg1) => new Proxy_IGrainB(arg0, arg1); global::System.Threading.Tasks.Task global::TestProject.IGrainB.MethodB(string arg0) { var request = new OrleansCodeGen.TestProject.Invokable_IGrainB_GrainReference_6B5D7809(); diff --git a/test/Orleans.NativeAotSmoke/GrainReferences.cs b/test/Orleans.NativeAotSmoke/GrainReferences.cs index 970cf65be5e..69af50fcfaf 100644 --- a/test/Orleans.NativeAotSmoke/GrainReferences.cs +++ b/test/Orleans.NativeAotSmoke/GrainReferences.cs @@ -1,6 +1,8 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGeneration; +using Orleans.Serialization.Configuration; +using Orleans.Serialization.Invocation; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Cloning; @@ -15,6 +17,7 @@ private static void Main() { ValidateGeneratedProxy(); ValidateClosedGenericFactories(); + ValidateNonGrainFactory(); ValidateSharedState(unordered: false); ValidateSharedState(unordered: true); ValidateConstructorFailures(); @@ -22,6 +25,20 @@ private static void Main() Console.WriteLine("Production grain-reference provider constructed generated proxies under NativeAOT."); } + private static void ValidateNonGrainFactory() + { + using var fixture = new ReferenceConstructionFixture(); + var registration = fixture.ManifestOptions + .GetOrCreate>>() + .Factories[typeof(INonGrainProxy)]; + var factory = registration.Factory ?? throw new InvalidOperationException("The non-grain factory was not registered."); + var proxy = factory("non-grain", 42); + + Ensure(proxy is INonGrainProxy, "The non-grain factory did not construct the generated interface proxy."); + Ensure(proxy.Label == "non-grain" && proxy.Number == 42, "The non-grain factory changed its arguments."); + Ensure(registration.ProxyType == proxy.GetType(), "The non-grain factory changed its registered proxy type."); + } + private static void ValidateClosedGenericFactories() { using var fixture = new ReferenceConstructionFixture(); @@ -38,6 +55,19 @@ private static void ValidateClosedGenericFactories() Ensure(!ReferenceEquals(number, repeated) && number.Equals(repeated), "Closed generic reference identity changed."); } + [GenerateProxyFactory(typeof(Func))] + public class NonGrainProxyBase(string label, int number) + { + public string Label { get; } = label; + public int Number { get; } = number; + + public ValueTask InvokeAsync(IInvokable request) => throw new NotSupportedException(); + public ValueTask InvokeAsync(IInvokable request) => throw new NotSupportedException(); + } + + [GenerateMethodSerializers(typeof(NonGrainProxyBase))] + public interface INonGrainProxy; + private static void ValidateGeneratedProxy() { using var fixture = new ReferenceConstructionFixture(); From 6e03a5d2dcef91c5da7dfa681fb4181a4df6771b Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Fri, 2 Oct 2026 14:47:06 -0700 Subject: [PATCH 6/6] refactor(core): name grain reference construction delegate --- .../docs/implementation/serialization.md | 2 +- .../Model/ProxyInterfaceDescription.cs | 8 ++-- .../Runtime/GrainReference.cs | 2 +- .../Runtime/GrainReferenceFactory.cs | 9 +++++ .../GrainReferenceActivator.cs | 12 +++--- .../Orleans.Core.Abstractions.cs | 3 +- .../GrainReferenceFactoryCodegenTests.cs | 39 +++++++++++++++++-- ...eGeneratorTests.TestBasicGrain.verified.cs | 2 +- ...ateMethodSerializersAnnotation.verified.cs | 2 +- ...torTests.TestGrainComplexGrain.verified.cs | 2 +- ...AnnotatedWithInvokableBaseType.verified.cs | 2 +- ...odAnnotatedWithResponseTimeout.verified.cs | 2 +- ...TestGrainWithDifferentKeyTypes.verified.cs | 2 +- ...estGrainWithMultipleInterfaces.verified.cs | 2 +- .../GrainReferenceActivatorTests.cs | 2 +- .../ReferenceConstructionFixture.cs | 4 +- 16 files changed, 70 insertions(+), 25 deletions(-) create mode 100644 src/Orleans.Core.Abstractions/Runtime/GrainReferenceFactory.cs diff --git a/docs/site/src/content/docs/implementation/serialization.md b/docs/site/src/content/docs/implementation/serialization.md index 5c88543dcca..1ef59da555c 100644 --- a/docs/site/src/content/docs/implementation/serialization.md +++ b/docs/site/src/content/docs/implementation/serialization.md @@ -74,7 +74,7 @@ Statically closed value-type constructor delegates update the caller's `ref` val Generated manifests register each concrete grain interface with a typed factory which directly constructs its proxy using `(GrainReferenceShared, IdSpan)`. The runtime resolves the factory using the declared interface identity. The activator caches that factory and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. Direct construction preserves proxy constructors through normal static call reachability and propagates constructor exceptions directly. -Proxy bases declare their construction delegate using . declares `Func`, while other RPC proxy bases can select their own delegate signatures. The generator emits a static `Create` method matching the declared delegate and forwards its parameters to the proxy constructor. C# compilation checks that constructor call for both non-generic and open generic proxies. +Proxy bases declare their construction delegate using . declares , which constructs a grain reference from shared runtime state and a grain key. Other RPC proxy bases can select their own delegate signatures. The generator emits a static `Create` method matching the declared delegate and forwards its parameters to the proxy constructor. C# compilation checks that constructor call for both non-generic and open generic proxies. Derived proxy bases inherit the factory declaration. A declaration on a nearer base overrides the inherited signature. Factory declarations use closed delegate types with a by-value proxy result; attribute arguments follow C#'s rules for concrete type references. The generator validates the delegate and its result conversion at build time, and preserves parameter passing modes such as `ref`. Proxy hierarchies supply accessible constructors matching their declared contract. diff --git a/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs b/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs index 7b463da1268..31399efa468 100644 --- a/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs +++ b/src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs @@ -135,12 +135,14 @@ static IEnumerable GetAllInterfaces(INamedTypeSymbol s) proxyBaseType, location, "GenerateProxyFactory requires a closed delegate with a non-void, by-value return type.")); } - if (!libraryTypes.Compilation.ClassifyCommonConversion(proxyBaseType, signature.ReturnType).IsImplicit - && !libraryTypes.Compilation.ClassifyCommonConversion(interfaceType, signature.ReturnType).IsImplicit) + var baseConversion = libraryTypes.Compilation.ClassifyCommonConversion(proxyBaseType, signature.ReturnType); + var interfaceConversion = libraryTypes.Compilation.ClassifyCommonConversion(interfaceType, signature.ReturnType); + if (!(baseConversion.IsIdentity || baseConversion.IsImplicit && baseConversion.IsReference) + && !(interfaceConversion.IsIdentity || interfaceConversion.IsImplicit && interfaceConversion.IsReference)) { throw new OrleansGeneratorDiagnosticAnalysisException( IncorrectProxyBaseClassSpecificationDiagnostic.CreateDiagnostic( - proxyBaseType, location, "GenerateProxyFactory's return type must accept the generated proxy base or interface.")); + proxyBaseType, location, "GenerateProxyFactory's return type must accept the generated proxy base or interface through an identity or implicit reference conversion.")); } if (signature.Parameters.Any(static parameter => parameter.RefKind is not (RefKind.None or RefKind.Ref or RefKind.Out or RefKind.In))) diff --git a/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs b/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs index f8c460f6a96..9aa6310c5a2 100644 --- a/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs +++ b/src/Orleans.Core.Abstractions/Runtime/GrainReference.cs @@ -259,7 +259,7 @@ internal struct GrainReferenceSurrogate /// This is the base class for all grain references. /// [Alias("GrainRef")] - [GenerateProxyFactory(typeof(Func))] + [GenerateProxyFactory(typeof(GrainReferenceFactory))] [DefaultInvokableBaseType(typeof(ValueTask<>), typeof(Request<>))] [DefaultInvokableBaseType(typeof(ValueTask), typeof(Request))] [DefaultInvokableBaseType(typeof(Task<>), typeof(TaskRequest<>))] diff --git a/src/Orleans.Core.Abstractions/Runtime/GrainReferenceFactory.cs b/src/Orleans.Core.Abstractions/Runtime/GrainReferenceFactory.cs new file mode 100644 index 00000000000..17f6f730651 --- /dev/null +++ b/src/Orleans.Core.Abstractions/Runtime/GrainReferenceFactory.cs @@ -0,0 +1,9 @@ +namespace Orleans.Runtime; + +/// +/// Constructs a grain reference using shared runtime state and a grain key. +/// +/// The runtime state shared by references for a grain type and interface. +/// The key identifying the target grain. +/// A new grain reference. +public delegate GrainReference GrainReferenceFactory(GrainReferenceShared shared, IdSpan key); diff --git a/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs b/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs index 8cc4180de71..aeb796b509c 100644 --- a/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs +++ b/src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs @@ -184,7 +184,7 @@ internal class RpcProvider { private readonly TypeConverter _typeConverter; private readonly Dictionary _mapping; - private readonly Dictionary> _factories = new(); + private readonly Dictionary _factories = new(); /// /// Initializes a new instance of the class. @@ -200,7 +200,7 @@ public RpcProvider( _typeConverter = typeConverter; var proxyTypes = config.Value.InterfaceProxyTypes; _mapping = new Dictionary(); - var registrations = config.Value.GetOrCreate>>(); + var registrations = config.Value.GetOrCreate>(); var registeredProxies = registrations.ProxyTypes; foreach (var (interfaceType, registration) in registrations.Factories) { @@ -276,7 +276,7 @@ static Type GetMainInterface(Type t) /// The grain interface type. /// The grain-reference factory. /// A value indicating whether a suitable factory was found. - public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out Func? result) + public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out GrainReferenceFactory? result) { if (_factories.TryGetValue(interfaceType, out result)) { @@ -315,7 +315,7 @@ public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out Fun "Trimming", "IL2070", Justification = "Legacy proxy constructors are preserved by AddInterfaceProxy. Custom non-public constructors require explicit preservation.")] - private static Func CreateLegacyFactory(Type proxyType) + private static GrainReferenceFactory CreateLegacyFactory(Type proxyType) { var constructor = proxyType.GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, @@ -404,7 +404,7 @@ public bool TryGet(GrainType grainType, GrainInterfaceType interfaceType, [NotNu private sealed class GrainReferenceActivator : IGrainReferenceActivator { private readonly GrainReferenceShared _shared; - private readonly Func _create; + private readonly GrainReferenceFactory _create; /// /// Initializes a new instance of the class. @@ -412,7 +412,7 @@ private sealed class GrainReferenceActivator : IGrainReferenceActivator /// The grain-reference factory. /// The functionality shared between all grain references for a specified grain type and grain interface type. public GrainReferenceActivator( - Func factory, + GrainReferenceFactory factory, GrainReferenceShared shared) { _shared = shared; diff --git a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs index da0f21c9283..ac668a9a964 100644 --- a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs +++ b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs @@ -2266,7 +2266,7 @@ public static partial class GrainLifecycleStage } [Alias("GrainRef")] - [GenerateProxyFactory(typeof(System.Func))] + [GenerateProxyFactory(typeof(GrainReferenceFactory))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.ValueTask<>), typeof(Request<>))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.ValueTask), typeof(Request))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.Task<>), typeof(TaskRequest<>))] @@ -2319,6 +2319,7 @@ protected void Invoke(IRequest methodDescription) { } public sealed override string ToString() { throw null; } } + public delegate GrainReference GrainReferenceFactory(GrainReferenceShared shared, IdSpan key); [GenerateSerializer] public sealed partial class GrainReferenceNotBoundException : OrleansException { diff --git a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs index 28d3c1ed447..cf113c1ebff 100644 --- a/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs +++ b/test/Orleans.CodeGenerator.Tests/GrainReferenceFactoryCodegenTests.cs @@ -30,6 +30,7 @@ public interface IGeneric : IBase { } Assert.Contains(".Add(typeof(global::TestProject.IGeneric<>), typeof(OrleansCodeGen.TestProject.Proxy_IGeneric<>))", generated, StringComparison.Ordinal); Assert.DoesNotContain("GetConstructor", generated, StringComparison.Ordinal); Assert.DoesNotContain("GetInterfaces", generated, StringComparison.Ordinal); + Assert.Contains("InterfaceProxyFactoryOptions", generated, StringComparison.Ordinal); } [Fact] @@ -40,7 +41,7 @@ public async Task ClosedGenericFactoriesUseOrdinaryTypedRegistration() using Orleans.Runtime; using Orleans.Serialization.Configuration; using Factories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions< - System.Func>; + Orleans.Runtime.GrainReferenceFactory>; namespace TestProject; public interface IGeneric : IGrainWithStringKey where T : struct { } public class Container where T : class @@ -115,7 +116,7 @@ public interface IGeneric : IGrainWithStringKey using Orleans; using Orleans.Serialization.Configuration; using Factories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions< - System.Func>; + Orleans.Runtime.GrainReferenceFactory>; [assembly: GenerateCodeForDeclaringAssembly(typeof(Contracts.IGeneric<>))] public static class Registration { @@ -266,7 +267,7 @@ public static bool Verify() && generic is IPlain && generic.Label == "generic" && generic.Number == 17 && !generic.Enabled && factories.Factories[typeof(IPlain<>)].ProxyType == typeof(OrleansCodeGen.TestProject.Proxy_IPlain<>) && factories.Factories[typeof(IPlain<>)].Factory is null - && options.GetOrCreate>>() + && options.GetOrCreate>() .Factories[typeof(IGrainLike)].Factory is not null; } } @@ -414,6 +415,38 @@ public interface IPlain { } Assert.True(diagnostic.Location.IsInSource); } + [Fact] + public async Task ProxyFactoryRejectsUserDefinedResultConversion() + { + const string source = """ + using Orleans; + using Orleans.Serialization.Invocation; + using System.Threading.Tasks; + namespace TestProject; + public sealed class ConvertedProxy { } + [GenerateProxyFactory(typeof(System.Func))] + public class PlainProxy + { + public static implicit operator ConvertedProxy(PlainProxy proxy) => new ConvertedProxy(); + public ValueTask InvokeAsync(IInvokable request) => default; + public ValueTask InvokeAsync(IInvokable request) => default; + } + [GenerateMethodSerializers(typeof(PlainProxy))] + public interface IPlain { } + """; + + var compilation = await TestCompilationHelper.CreateCompilation(source); + Assert.Empty(compilation.GetDiagnostics(TestContext.Current.CancellationToken) + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); + + var (result, _) = Generate(compilation); + + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal(DiagnosticRuleId.IncorrectProxyBaseClassSpecification, diagnostic.Id); + Assert.Contains("identity or implicit reference conversion", diagnostic.GetMessage(), StringComparison.Ordinal); + Assert.True(diagnostic.Location.IsInSource); + } + private static void AssertVerificationSucceeds(Compilation output) { using var stream = new MemoryStream(); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs index 781e5f6172e..96de3bae8d9 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicGrain.verified.cs @@ -255,7 +255,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IBasicGrain_GrainReference_6B0E24A1)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_BasicGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::TestProject.IBasicGrain), typeof(OrleansCodeGen.TestProject.Proxy_IBasicGrain), OrleansCodeGen.TestProject.Proxy_IBasicGrain.Create); config.AddInterface(typeof(global::TestProject.IBasicGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.BasicGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs index 105d41aa2d8..62de50e5d89 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs @@ -164,7 +164,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddSerializer(typeof(OrleansCodeGen.Codec_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddCopier(typeof(OrleansCodeGen.Copier_Invokable_IMyGrain_GrainReference_6D39E404)); config.AddInterfaceProxy(typeof(OrleansCodeGen.Proxy_IMyGrain)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::IMyGrain), typeof(OrleansCodeGen.Proxy_IMyGrain), OrleansCodeGen.Proxy_IMyGrain.Create); config.AddInterface(typeof(global::IMyGrain)); var n1 = config.CompoundTypeAliases.Add("inv"); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs index 7cee6243216..21fa9605b9f 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs @@ -450,7 +450,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IComplexGrain_GrainReference_67FE5808)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_ComplexGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::TestProject.IComplexGrain), typeof(OrleansCodeGen.TestProject.Proxy_IComplexGrain), OrleansCodeGen.TestProject.Proxy_IComplexGrain.Create); config.AddInterface(typeof(global::TestProject.IComplexGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ComplexGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs index 635b7362813..f868886dfed 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs @@ -260,7 +260,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IHelloGrain_GrainReference_5336307F)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_HelloGrain)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::TestProject.IHelloGrain), typeof(OrleansCodeGen.TestProject.Proxy_IHelloGrain), OrleansCodeGen.TestProject.Proxy_IHelloGrain.Create); config.AddInterface(typeof(global::TestProject.IHelloGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.HelloGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs index 5e40d3da00c..700d9dea605 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs @@ -160,7 +160,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddSerializer(typeof(OrleansCodeGen.TestProject.Codec_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IResponseTimeoutGrain_GrainReference_6BE752C8)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::TestProject.IResponseTimeoutGrain), typeof(OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain), OrleansCodeGen.TestProject.Proxy_IResponseTimeoutGrain.Create); config.AddInterface(typeof(global::TestProject.IResponseTimeoutGrain)); config.AddInterfaceImplementation(typeof(global::TestProject.ResponseTimeoutGrain)); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs index 244501545a4..2e9dc14d692 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs @@ -791,7 +791,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithIntegerCompoundKey)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidKey.Create); proxyFactories.Add(typeof(global::TestProject.IMyGrainWithStringKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithStringKey.Create); proxyFactories.Add(typeof(global::TestProject.IMyGrainWithGuidCompoundKey), typeof(OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey), OrleansCodeGen.TestProject.Proxy_IMyGrainWithGuidCompoundKey.Create); diff --git a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs index 4fa9dccb808..d3834ab2ad3 100644 --- a/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs +++ b/test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs @@ -301,7 +301,7 @@ protected override void ConfigureInner(global::Orleans.Serialization.Configurati config.AddCopier(typeof(OrleansCodeGen.TestProject.Copier_Invokable_IGrainB_GrainReference_6B5D7809)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainA)); config.AddInterfaceProxy(typeof(OrleansCodeGen.TestProject.Proxy_IGrainB)); - var proxyFactories = config.GetOrCreate>>(); + var proxyFactories = config.GetOrCreate>(); proxyFactories.Add(typeof(global::TestProject.IGrainA), typeof(OrleansCodeGen.TestProject.Proxy_IGrainA), OrleansCodeGen.TestProject.Proxy_IGrainA.Create); proxyFactories.Add(typeof(global::TestProject.IGrainB), typeof(OrleansCodeGen.TestProject.Proxy_IGrainB), OrleansCodeGen.TestProject.Proxy_IGrainB.Create); config.AddInterface(typeof(global::TestProject.IGrainA)); diff --git a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs index 8d7b9a3e681..2ea888aa2bf 100644 --- a/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs +++ b/test/Orleans.Core.Tests/GrainReferences/GrainReferenceActivatorTests.cs @@ -9,7 +9,7 @@ using Orleans.Serialization.Serializers; using TestExtensions; using Xunit; -using ReferenceFactories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions>; +using ReferenceFactories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions; namespace UnitTests.GrainReferences; diff --git a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs index b9647280d9d..ec03628e541 100644 --- a/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs +++ b/test/Orleans.Core.Tests/GrainReferences/ReferenceConstructionFixture.cs @@ -14,7 +14,7 @@ using Orleans.Serialization.Invocation; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; -using ReferenceFactories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions>; +using ReferenceFactories = Orleans.Serialization.Configuration.InterfaceProxyFactoryOptions; namespace UnitTests.GrainReferences; @@ -26,7 +26,7 @@ internal sealed class ReferenceConstructionFixture : IDisposable public ReferenceConstructionFixture( bool unordered = false, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] Type? proxyType = null, - Func? factory = null, + GrainReferenceFactory? factory = null, bool legacy = false, bool includeGeneratedFactories = false) {