Skip to content

Commit

Permalink
Add option for compatibility mode, to generate backwards compatible i…
Browse files Browse the repository at this point in the history
…nvokers as well as the simplified form
  • Loading branch information
ReubenBond committed Nov 29, 2023
1 parent f7b6d93 commit d93091a
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 86 deletions.
83 changes: 39 additions & 44 deletions src/Orleans.CodeGenerator/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class CodeGeneratorOptions
public List<string> ImmutableAttributes { get; } = new() { "Orleans.ImmutableAttribute" };
public List<string> ConstructorAttributes { get; } = new() { "Orleans.OrleansConstructorAttribute", "Microsoft.Extensions.DependencyInjection.ActivatorUtilitiesConstructorAttribute" };
public GenerateFieldIds GenerateFieldIds { get; set; }
public bool GenerateCompatibilityInvokers { get; set; }
}

public class CodeGenerator
Expand Down Expand Up @@ -643,7 +644,7 @@ internal InvokableMethodProxyBase GetProxyBase(INamedTypeSymbol interfaceType)
return result;
}

internal ProxyInterfaceDescription GetInvokableInterfaceDescription(INamedTypeSymbol proxyBaseType, INamedTypeSymbol interfaceType)
private ProxyInterfaceDescription GetInvokableInterfaceDescription(INamedTypeSymbol proxyBaseType, INamedTypeSymbol interfaceType)
{
var originalInterface = interfaceType.OriginalDefinition;
if (MetadataModel.InvokableInterfaces.TryGetValue(originalInterface, out var description))
Expand All @@ -668,62 +669,56 @@ internal ProxyInterfaceDescription GetInvokableInterfaceDescription(INamedTypeSy
return description;
}

internal InvokableMethodDescription GetInvokableMethod(InvokableMethodId invokableId)
internal ProxyMethodDescription GetProxyMethodDescription(INamedTypeSymbol interfaceType, IMethodSymbol method, bool hasCollision)
{
if (!_invokableMethodDescriptions.TryGetValue(invokableId, out var result))
{
result = _invokableMethodDescriptions[invokableId] = InvokableMethodDescription.Create(invokableId);
}

return result;
}
var originalMethod = method.OriginalDefinition;
var proxyBaseInfo = GetProxyBase(interfaceType);
var invokableId = new InvokableMethodId(proxyBaseInfo, originalMethod);
var interfaceDescription = GetInvokableInterfaceDescription(invokableId.ProxyBase.ProxyBaseType, interfaceType);

internal GeneratedInvokableDescription GetGeneratedInvokable(InvokableMethodId invokableId)
{
// Get or generate an invokable for the original method definition.
if (MetadataModel.GeneratedInvokables.TryGetValue(invokableId, out var result))
if (!MetadataModel.GeneratedInvokables.TryGetValue(invokableId, out var generatedInvokable))
{
return result;
}
if (!_invokableMethodDescriptions.TryGetValue(invokableId, out var methodDescription))
{
methodDescription = _invokableMethodDescriptions[invokableId] = InvokableMethodDescription.Create(invokableId);
}

var methodDescription = GetInvokableMethod(invokableId);
result = MetadataModel.GeneratedInvokables[invokableId] = InvokableGenerator.Generate(methodDescription);
generatedInvokable = MetadataModel.GeneratedInvokables[invokableId] = InvokableGenerator.Generate(methodDescription);

if (Compilation.GetTypeByMetadataName(result.MetadataName) == null)
{
// Emit the generated code on-demand.
AddMember(result.GeneratedNamespace, result.ClassDeclarationSyntax);
if (Compilation.GetTypeByMetadataName(generatedInvokable.MetadataName) == null)
{
// Emit the generated code on-demand.
AddMember(generatedInvokable.GeneratedNamespace, generatedInvokable.ClassDeclarationSyntax);

// Ensure the type will have a serializer generated for it.
MetadataModel.SerializableTypes.Add(result);
// Ensure the type will have a serializer generated for it.
MetadataModel.SerializableTypes.Add(generatedInvokable);

foreach (var alias in result.CompoundTypeAliases)
{
MetadataModel.CompoundTypeAliases.Add(alias, result.OpenTypeSyntax);
foreach (var alias in generatedInvokable.CompoundTypeAliases)
{
MetadataModel.CompoundTypeAliases.Add(alias, generatedInvokable.OpenTypeSyntax);
}
}
}

return result;
}

internal ProxyMethodDescription GetProxyMethodDescription(INamedTypeSymbol interfaceType, IMethodSymbol method, bool hasCollision)
{
var invokableId = GetInvokableMethodId(interfaceType, method);
return GetInterfaceMethodDescription(interfaceType, method, invokableId, hasCollision);
}
var proxyMethodDescription = ProxyMethodDescription.Create(interfaceDescription, generatedInvokable, method, hasCollision);

internal ProxyMethodDescription GetInterfaceMethodDescription(INamedTypeSymbol interfaceType, IMethodSymbol method, InvokableMethodId invokableId, bool hasCollision)
{
var interfaceDescription = GetInvokableInterfaceDescription(invokableId.ProxyBase.ProxyBaseType, interfaceType);
var generatedInvokable = GetGeneratedInvokable(invokableId);
return ProxyMethodDescription.Create(interfaceDescription, generatedInvokable, method, hasCollision);
}
// For backwards compatibility, generate invokers for the specific implementation types as well, where they differ.
if (Options.GenerateCompatibilityInvokers && !SymbolEqualityComparer.Default.Equals(method.OriginalDefinition.ContainingType, interfaceType))
{
var compatInvokableId = new InvokableMethodId(proxyBaseInfo, method);
var compatMethodDescription = InvokableMethodDescription.Create(compatInvokableId, interfaceType);
var compatInvokable = InvokableGenerator.Generate(compatMethodDescription);
AddMember(compatInvokable.GeneratedNamespace, compatInvokable.ClassDeclarationSyntax);
var alias =
InvokableGenerator.GetCompoundTypeAliasComponents(
compatInvokableId,
interfaceType,
compatMethodDescription.GeneratedMethodId);
MetadataModel.CompoundTypeAliases.Add(alias, compatInvokable.OpenTypeSyntax);
}

internal InvokableMethodId GetInvokableMethodId(INamedTypeSymbol interfaceType, IMethodSymbol method)
{
var originalMethod = method.OriginalDefinition;
var proxyBaseInfo = GetProxyBase(interfaceType);
return new InvokableMethodId(proxyBaseInfo, originalMethod);
return proxyMethodDescription;
}
}
}
47 changes: 22 additions & 25 deletions src/Orleans.CodeGenerator/InvokableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public GeneratedInvokableDescription Generate(InvokableMethodDescription invokab
var fields = GetFieldDeclarations(invokableMethodInfo, fieldDescriptions);
var (ctor, ctorArgs) = GenerateConstructor(generatedClassName, invokableMethodInfo, baseClassType);
var accessibility = GetAccessibility(method);
var compoundTypeAliases = GetCompoundTypeAliasAttributeArguments(invokableMethodInfo);
var compoundTypeAliases = GetCompoundTypeAliasAttributeArguments(invokableMethodInfo, invokableMethodInfo.Key);

List<INamedTypeSymbol> serializationHooks = new();
if (baseClassType.GetAttributes(LibraryTypes.SerializationCallbacksAttribute, out var hookAttributes))
Expand Down Expand Up @@ -208,38 +208,35 @@ internal AttributeSyntax GetCompoundTypeAliasAttribute(CompoundTypeAliasComponen
return Attribute(LibraryTypes.CompoundTypeAliasAttribute.ToNameSyntax()).AddArgumentListArguments(args);
}

internal static List<CompoundTypeAliasComponent[]> GetCompoundTypeAliasAttributeArguments(InvokableMethodDescription methodDescription)
internal static List<CompoundTypeAliasComponent[]> GetCompoundTypeAliasAttributeArguments(InvokableMethodDescription methodDescription, InvokableMethodId invokableId)
{
var result = new List<CompoundTypeAliasComponent[]>(2);
var proxyBaseComponents = methodDescription.Key.ProxyBase.CompositeAliasComponents;
var containingInterface = methodDescription.ContainingInterface;
if (methodDescription.HasAlias)
{
var alias = new CompoundTypeAliasComponent[1 + proxyBaseComponents.Length + 2];
alias[0] = new("inv");
for (var i = 0; i < proxyBaseComponents.Length; i++)
{
alias[i + 1] = proxyBaseComponents[i];
}

alias[1 + proxyBaseComponents.Length] = new(methodDescription.ContainingInterface);
alias[1 + proxyBaseComponents.Length + 1] = new(methodDescription.MethodId);
result.Add(alias);
result.Add(GetCompoundTypeAliasComponents(invokableId, containingInterface, methodDescription.MethodId));
}

{
var alias = new CompoundTypeAliasComponent[1 + proxyBaseComponents.Length + 2];
alias[0] = new("inv");
for (var i = 0; i < proxyBaseComponents.Length; i++)
{
alias[i + 1] = proxyBaseComponents[i];
}
result.Add(GetCompoundTypeAliasComponents(invokableId, containingInterface, methodDescription.GeneratedMethodId));
return result;
}

alias[1 + proxyBaseComponents.Length] = new(methodDescription.ContainingInterface);
alias[1 + proxyBaseComponents.Length + 1] = new(methodDescription.GeneratedMethodId);
result.Add(alias);
public static CompoundTypeAliasComponent[] GetCompoundTypeAliasComponents(
InvokableMethodId invokableId,
INamedTypeSymbol containingInterface,
string methodId)
{
var proxyBaseComponents = invokableId.ProxyBase.CompositeAliasComponents;
var alias = new CompoundTypeAliasComponent[1 + proxyBaseComponents.Length + 2];
alias[0] = new("inv");
for (var i = 0; i < proxyBaseComponents.Length; i++)
{
alias[i + 1] = proxyBaseComponents[i];
}

return result;
alias[1 + proxyBaseComponents.Length] = new(containingInterface);
alias[1 + proxyBaseComponents.Length + 1] = new(methodId);
return alias;
}

private INamedTypeSymbol GetBaseClassType(InvokableMethodDescription method)
Expand Down Expand Up @@ -582,7 +579,7 @@ public static string GetSimpleClassName(InvokableMethodDescription method)
var genericArity = method.AllTypeParameters.Count;
var typeArgs = genericArity > 0 ? "_" + genericArity : string.Empty;
var proxyKey = method.ProxyBase.Key.GeneratedClassNameComponent;
return $"Invokable_{method.Method.ContainingType.Name}_{proxyKey}_{method.GeneratedMethodId}{typeArgs}";
return $"Invokable_{method.ContainingInterface.Name}_{proxyKey}_{method.GeneratedMethodId}{typeArgs}";
}

private MemberDeclarationSyntax[] GetFieldDeclarations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public GeneratedInvokableDescription(
public bool IsExceptionType => false;
public List<TypeSyntax> ActivatorConstructorParameters { get; }
public bool HasActivatorConstructor => UseActivator;
public List<CompoundTypeAliasComponent[]> CompoundTypeAliases {get;}
public List<CompoundTypeAliasComponent[]> CompoundTypeAliases { get; }
public ClassDeclarationSyntax ClassDeclarationSyntax { get; }
public string ReturnValueInitializerMethod { get; }

Expand Down
9 changes: 5 additions & 4 deletions src/Orleans.CodeGenerator/Model/InvokableMethodDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ namespace Orleans.CodeGenerator
/// </summary>
internal sealed class InvokableMethodDescription : IEquatable<InvokableMethodDescription>
{
public static InvokableMethodDescription Create(InvokableMethodId method) => new(method);
public static InvokableMethodDescription Create(InvokableMethodId method, INamedTypeSymbol containingType = null) => new(method, containingType);

private InvokableMethodDescription(InvokableMethodId invokableId)
private InvokableMethodDescription(InvokableMethodId invokableId, INamedTypeSymbol containingType)
{
Key = invokableId;
ContainingInterface = containingType ?? invokableId.Method.ContainingType;
GeneratedMethodId = CodeGenerator.CreateHashedMethodId(Method);
MethodId = CodeGenerator.GetId(Method)?.ToString(CultureInfo.InvariantCulture) ?? CodeGenerator.GetAlias(Method) ?? GeneratedMethodId;

Expand Down Expand Up @@ -98,7 +99,7 @@ private InvokableMethodDescription(InvokableMethodId invokableId)
MethodTypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>();

var names = new HashSet<string>(StringComparer.Ordinal);
foreach (var typeParameter in Method.ContainingType.GetAllTypeParameters())
foreach (var typeParameter in ContainingInterface.GetAllTypeParameters())
{
var tpName = GetTypeParameterName(names, typeParameter);
AllTypeParameters.Add((tpName, typeParameter));
Expand Down Expand Up @@ -203,7 +204,7 @@ static bool TryGetNamedArgument(ImmutableArray<KeyValuePair<string, TypedConstan
/// <summary>
/// Gets the interface which this type is contained in.
/// </summary>
public INamedTypeSymbol ContainingInterface => Method.ContainingType;
public INamedTypeSymbol ContainingInterface { get; }

public bool Equals(InvokableMethodDescription other) => Key.Equals(other.Key);
public override bool Equals(object obj) => obj is InvokableMethodDescription imd && Equals(imd);
Expand Down
13 changes: 3 additions & 10 deletions src/Orleans.CodeGenerator/Model/InvokableMethodId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ namespace Orleans.CodeGenerator
{
public InvokableMethodId(InvokableMethodProxyBase proxyBaseInfo, IMethodSymbol method)
{
if (!SymbolEqualityComparer.Default.Equals(method, method.OriginalDefinition))
{
throw new ArgumentException("Method must be an original definition", nameof(method));
}

ProxyBase = proxyBaseInfo;
Method = method;
}
Expand All @@ -29,11 +24,9 @@ public InvokableMethodId(InvokableMethodProxyBase proxyBaseInfo, IMethodSymbol m
/// </summary>
public IMethodSymbol Method { get; }

public bool Equals(InvokableMethodId other)
{
return ProxyBase.Equals(other.ProxyBase)
&& SymbolEqualityComparer.Default.Equals(Method, other.Method);
}
public bool Equals(InvokableMethodId other) =>
ProxyBase.Equals(other.ProxyBase)
&& SymbolEqualityComparer.Default.Equals(Method, other.Method);

public override bool Equals(object obj) => obj is InvokableMethodId imd && Equals(imd);
public override int GetHashCode() => ProxyBase.GetHashCode() * 17 ^ SymbolEqualityComparer.Default.GetHashCode(Method);
Expand Down
2 changes: 0 additions & 2 deletions src/Orleans.CodeGenerator/Model/ProxyMethodDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ public ConstructedGeneratedInvokableDescription(GeneratedInvokableDescription in
public bool IsExceptionType => _invokableDescription.IsExceptionType;
public List<TypeSyntax> ActivatorConstructorParameters => _invokableDescription.ActivatorConstructorParameters;
public bool HasActivatorConstructor => UseActivator;
public List<CompoundTypeAliasComponent[]> CompoundTypeAliases => _invokableDescription.CompoundTypeAliases;
public ClassDeclarationSyntax ClassDeclarationSyntax => _invokableDescription.ClassDeclarationSyntax;
public string ReturnValueInitializerMethod => _invokableDescription.ReturnValueInitializerMethod;

public InvokableMethodDescription MethodDescription => _invokableDescription.MethodDescription;
Expand Down
8 changes: 8 additions & 0 deletions src/Orleans.CodeGenerator/OrleansSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,15 @@ public void Execute(GeneratorExecutionContext context)
if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.orleans_generatefieldids", out var generateFieldIds) && generateFieldIds is { Length: > 0 })
{
if (Enum.TryParse(generateFieldIds, out GenerateFieldIds fieldIdOption))
{
options.GenerateFieldIds = fieldIdOption;
}
}

if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.orleansgeneratecompatibilityinvokers", out var generateCompatInvokersValue)
&& bool.TryParse(generateCompatInvokersValue, out var genCompatInvokers))
{
options.GenerateCompatibilityInvokers = genCompatInvokers;
}

var codeGenerator = new CodeGenerator(context.Compilation, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<CompilerVisibleProperty Include="Orleans_AliasAttributes" />
<CompilerVisibleProperty Include="Orleans_GenerateSerializerAttributes" />
<CompilerVisibleProperty Include="Orleans_ConstructorAttributes" />
<CompilerVisibleProperty Include="OrleansGenerateCompatibilityInvokers" />
</ItemGroup>

<PropertyGroup>
Expand Down

0 comments on commit d93091a

Please sign in to comment.