Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/StreamJsonRpc.Analyzers/GeneratorModels/MethodModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ internal override void WriteMethods(SourceWriter writer)
internal static MethodModel Create(IMethodSymbol method, KnownSymbols symbols)
{
string rpcMethodName = method.Name;
if (method.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, symbols.MethodShapeAttribute)) is { } methodShapeAttribute)
{
// If the method has a MethodShape attribute, use its name.
if (methodShapeAttribute.NamedArguments.FirstOrDefault(a => a.Key == Types.MethodShapeAttribute.NameProperty).Value.Value is string name)
{
rpcMethodName = name;
}
}

if (method.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, symbols.JsonRpcMethodAttribute)) is { } rpcMethodAttribute)
Comment thread
AArnott marked this conversation as resolved.
{
// If the method has a JsonRpcMethod attribute, use its name.
Expand Down
4 changes: 3 additions & 1 deletion src/StreamJsonRpc.Analyzers/KnownSymbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal record KnownSymbols(
INamedTypeSymbol ExportRpcContractProxiesAttribute,
INamedTypeSymbol JsonRpcProxyMappingAttribute,
INamedTypeSymbol JsonRpcMethodAttribute,
INamedTypeSymbol? MethodShapeAttribute,
INamedTypeSymbol SystemType,
INamedTypeSymbol Stream)
{
Expand All @@ -40,6 +41,7 @@ internal static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out
INamedTypeSymbol? exportRpcContractProxiesAttribute = compilation.GetTypeByMetadataName(Types.ExportRpcContractProxiesAttribute.FullName);
INamedTypeSymbol? rpcProxyMappingAttribute = compilation.GetTypeByMetadataName(Types.JsonRpcProxyMappingAttribute.FullName);
INamedTypeSymbol? jsonRpcMethodAttribute = compilation.GetTypeByMetadataName(Types.JsonRpcMethodAttribute.FullName);
INamedTypeSymbol? methodShapeAttribute = compilation.GetTypeByMetadataName(Types.MethodShapeAttribute.FullName);
INamedTypeSymbol? systemType = compilation.GetTypeByMetadataName("System.Type");
INamedTypeSymbol? systemIOStream = compilation.GetTypeByMetadataName("System.IO.Stream");

Expand All @@ -59,7 +61,7 @@ systemIOStream is null ||
return false;
}

symbols = new KnownSymbols(task, taskOfT, valueTask, valueTaskOfT, asyncEnumerableOfT, cancellationToken, idisposable, rpcMarshalableAttribute, rpcMarshalableOptionalInterface, rpcContractAttribute, jsonRpcProxyInterfaceGroupAttribute, exportRpcContractProxiesAttribute, rpcProxyMappingAttribute, jsonRpcMethodAttribute, systemType, systemIOStream);
symbols = new KnownSymbols(task, taskOfT, valueTask, valueTaskOfT, asyncEnumerableOfT, cancellationToken, idisposable, rpcMarshalableAttribute, rpcMarshalableOptionalInterface, rpcContractAttribute, jsonRpcProxyInterfaceGroupAttribute, exportRpcContractProxiesAttribute, rpcProxyMappingAttribute, jsonRpcMethodAttribute, methodShapeAttribute, systemType, systemIOStream);
return true;
}
}
7 changes: 7 additions & 0 deletions src/StreamJsonRpc.Analyzers/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ internal static class JsonRpcMethodAttribute
{
internal const string FullName = "StreamJsonRpc.JsonRpcMethodAttribute";
}

internal static class MethodShapeAttribute
{
internal const string FullName = "PolyType.MethodShapeAttribute";

internal const string NameProperty = "Name";
}
}
2 changes: 1 addition & 1 deletion src/StreamJsonRpc/Reflection/RpcTargetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ internal void AddLocalRpcMethod(MethodInfo handler, object? target, JsonRpcMetho
string rpcMethodName = methodRpcSettings?.Name ?? handler.Name;
lock (this.SyncObject)
{
MethodSignatureAndTarget methodTarget = new(RpcTargetMetadata.TargetMethodMetadata.From(handler, methodRpcSettings, shape: null), target, attribute: null, synchronizationContext);
MethodSignatureAndTarget methodTarget = new(RpcTargetMetadata.TargetMethodMetadata.From(handler, methodRpcSettings, shape: null, methodShapeAttribute: null), target, attribute: null, synchronizationContext);
this.TraceLocalMethodAdded(rpcMethodName, methodTarget);
if (this.targetRequestMethodToClrMethodMap.TryGetValue(rpcMethodName, out List<MethodSignatureAndTarget>? existingList))
{
Expand Down
24 changes: 18 additions & 6 deletions src/StreamJsonRpc/RpcTargetMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ private static bool TryAddCandidateMethod(Builder builder, MethodInfo method, IM

JsonRpcIgnoreAttribute? ignoreAttribute = FindMethodAttribute<JsonRpcIgnoreAttribute>(builder, method);
JsonRpcMethodAttribute? methodAttribute = FindMethodAttribute<JsonRpcMethodAttribute>(builder, method);
MethodShapeAttribute? methodShapeAttribute = FindMethodAttribute<MethodShapeAttribute>(builder, method);

if (ignoreAttribute is not null)
{
Expand All @@ -371,7 +372,7 @@ private static bool TryAddCandidateMethod(Builder builder, MethodInfo method, IM
return false;
}

var methodMetadata = TargetMethodMetadata.From(method, methodAttribute, shape);
TargetMethodMetadata methodMetadata = TargetMethodMetadata.From(method, methodAttribute, shape, methodShapeAttribute);

builder.AddMethod(methodMetadata);
return true;
Expand Down Expand Up @@ -736,12 +737,13 @@ public class TargetMethodMetadata
{
private ParameterInfo[]? parameters;

internal TargetMethodMetadata(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape)
internal TargetMethodMetadata(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape, MethodShapeAttribute? methodShapeAttribute)
{
this.IsPublic = method.IsPublic;
this.Name = attribute?.Name ?? shape?.Name ?? method.Name;
this.Name = attribute?.Name ?? shape?.Name ?? methodShapeAttribute?.Name ?? method.Name;
this.MethodInfo = method;
this.Attribute = attribute;
this.MethodShapeAttribute = methodShapeAttribute;

// Avoid inspecting the method signature here, as that triggers assembly loads that we might not ever need.
// We'll do it lazily in our property getters instead.
Expand All @@ -762,6 +764,16 @@ internal TargetMethodMetadata(MethodInfo method, JsonRpcMethodAttribute? attribu
/// </summary>
public JsonRpcMethodAttribute? Attribute { get; }

/// <summary>
/// Gets the <see cref="MethodShapeAttribute"/> that applies to this method, if any.
/// </summary>
public MethodShapeAttribute? MethodShapeAttribute { get; }

/// <summary>
/// Gets a value indicating whether this method has a name explicitly given by either a <see cref="JsonRpcMethodAttribute"/> or a <see cref="MethodShapeAttribute"/>.
/// </summary>
internal bool HasExplicitlySpecifiedName => (this.Attribute?.Name ?? this.MethodShapeAttribute?.Name) is not null;

/// <summary>
/// Gets the parameters on the method.
/// </summary>
Expand Down Expand Up @@ -797,7 +809,7 @@ internal TargetMethodMetadata(MethodInfo method, JsonRpcMethodAttribute? attribu
/// <inheritdoc/>
public override string ToString() => this.DebuggerDisplay;

internal static TargetMethodMetadata From(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape) => new(method, attribute, shape);
internal static TargetMethodMetadata From(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape, MethodShapeAttribute? methodShapeAttribute) => new(method, attribute, shape, methodShapeAttribute);

internal bool EqualSignature(TargetMethodMetadata other)
{
Expand Down Expand Up @@ -919,10 +931,10 @@ private ImmutableDictionary<string, IReadOnlyList<TargetMethodMetadata>> Generat
string alias = name[..^ImpliedMethodNameAsyncSuffix.Length];
if (!this.Methods.ContainsKey(alias))
{
List<TargetMethodMetadata> implicitlyNamed = [.. overloads.Where(o => o.Attribute?.Name is null)];
List<TargetMethodMetadata> implicitlyNamed = [.. overloads.Where(o => !o.HasExplicitlySpecifiedName)];
if (implicitlyNamed.Count > 0)
{
aliasedMethods.Add(alias, [.. overloads.Where(o => o.Attribute?.Name is null)]);
aliasedMethods.Add(alias, [.. overloads.Where(o => !o.HasExplicitlySpecifiedName)]);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/StreamJsonRpc/net8.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodShapeAttribute.get -> PolyType.MethodShapeAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string!
StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type!
StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void
Expand Down
1 change: 1 addition & 0 deletions src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodShapeAttribute.get -> PolyType.MethodShapeAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string!
StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type!
StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void
Expand Down
1 change: 1 addition & 0 deletions src/StreamJsonRpc/netstandard2.1/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodShapeAttribute.get -> PolyType.MethodShapeAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string!
StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type!
StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void
Expand Down
16 changes: 16 additions & 0 deletions test/StreamJsonRpc.Tests/JsonRpcProxyGenerationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#endif
using Microsoft.VisualStudio.Threading;
using Nerdbank;
using PolyType;
using StreamJsonRpc.Reflection;
using StreamJsonRpc.Tests;
using ExAssembly = StreamJsonRpc.Tests.ExternalAssembly;
Expand Down Expand Up @@ -91,6 +92,9 @@ public partial interface IServerDerived : IServer
public partial interface IRpcWithAsyncSuffixedMethod
{
Task DoSomethingAsync();

[MethodShape(Name = "RenamedByShape")]
Task DoShapeThingAsync();
}

////[JsonRpcContract] Defining this attribute would produce a compile error, but we're testing runtime handling of the invalid case.
Expand Down Expand Up @@ -909,6 +913,18 @@ public async Task ClientProxiesDoNotUseAliasedNames()
await proxy.DoSomethingAsync().WithCancellation(this.TimeoutToken);
}

[Fact]
public async Task ShapeRenamedMethod()
{
this.serverRpc.AllowModificationWhileListening = true;

// Very deliberately add just a lone method with an explicit name.
// This verifies that proxies invoke methods without mangling their name (e.g. trimming the "Async" suffix).
this.serverRpc.AddLocalRpcMethod("RenamedByShape", new Action(() => { }));
var proxy = this.clientJsonRpc.Attach<IRpcWithAsyncSuffixedMethod>(this.DefaultProxyOptions);
await proxy.DoShapeThingAsync().WithCancellation(this.TimeoutToken);
}

/// <summary>
/// Validates that similar proxies are generated in the same dynamic assembly.
/// </summary>
Expand Down