Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<PackageVersion Include="Nerdbank.MessagePack" Version="0.8.82-rc" />
<PackageVersion Include="Nerdbank.Streams" Version="2.12.87" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="PolyType" Version="0.52.1" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
Expand Down
2 changes: 1 addition & 1 deletion src/StreamJsonRpc/ProxyGeneration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ internal static TypeInfo Get(Type contractInterface, ReadOnlySpan<Type> addition
{
foreach (RpcTargetMetadata.TargetMethodMetadata methodMetadata in overloads)
{
MethodInfo method = methodMetadata.Method;
MethodInfo method = methodMetadata.MethodInfo;
if (!implementedMethods.Add(method))
{
continue;
Expand Down
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), target, attribute: null, synchronizationContext);
MethodSignatureAndTarget methodTarget = new(RpcTargetMetadata.TargetMethodMetadata.From(handler, methodRpcSettings, shape: null), target, attribute: null, synchronizationContext);
this.TraceLocalMethodAdded(rpcMethodName, methodTarget);
if (this.targetRequestMethodToClrMethodMap.TryGetValue(rpcMethodName, out List<MethodSignatureAndTarget>? existingList))
{
Expand Down
8 changes: 4 additions & 4 deletions src/StreamJsonRpc/Reflection/TargetMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal TargetMethod(
/// <summary>
/// Gets the <see cref="MethodInfo"/> that will be invoked to handle the request, if one was found.
/// </summary>
public MethodInfo? TargetMethodInfo => this.signature?.Method;
public MethodInfo? TargetMethodInfo => this.signature?.MethodInfo;

/// <summary>
/// Gets all the exceptions thrown while trying to deserialize arguments to candidate parameter types.
Expand All @@ -107,12 +107,12 @@ internal string LookupErrorMessage
}
}

internal Type? ReturnType => this.signature?.Method.ReturnType;
internal Type? ReturnType => this.signature?.ReturnType;

/// <inheritdoc/>
public override string ToString()
{
return this.signature is not null ? $"{this.signature.Method.DeclaringType!.FullName}.{this.signature.Name}({this.GetParameterSignature()})" : "<no method>";
return this.signature is not null ? $"{this.signature.MethodInfo.DeclaringType?.FullName}.{this.signature.Name}({this.GetParameterSignature()})" : "<no method>";
}

internal async Task<object?> InvokeAsync(CancellationToken cancellationToken)
Expand All @@ -130,7 +130,7 @@ public override string ToString()

Assumes.NotNull(this.synchronizationContext);
await this.synchronizationContext;
return this.signature.Method.Invoke(!this.signature.Method.IsStatic ? this.target : null, this.arguments);
return this.signature.MethodInfo.Invoke(!this.signature.MethodInfo.IsStatic ? this.target : null, this.arguments);
}

private string? GetParameterSignature() => this.signature is not null ? string.Join(", ", this.signature.Parameters.Select(p => p.ParameterType.Name)) : null;
Expand Down
3 changes: 3 additions & 0 deletions src/StreamJsonRpc/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,7 @@
<data name="UsableOnceOnly" xml:space="preserve">
<value>This operation can only be performed once on this object.</value>
</data>
<data name="AttributeProviderRequired" xml:space="preserve">
<value>The shape for {member} must include a value for AttributeProvider.</value>
</data>
</root>
120 changes: 96 additions & 24 deletions src/StreamJsonRpc/RpcTargetMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
using System.Globalization;
using System.Reflection;
using Microsoft.VisualStudio.Threading;
using PolyType;
using PolyType.Abstractions;
using PolyType.Utilities;
using StreamJsonRpc.Protocol;

namespace StreamJsonRpc;

Expand Down Expand Up @@ -277,6 +281,46 @@ public static RpcTargetMetadata FromClassNonPublic([DynamicallyAccessedMembers(D
return NonPublicClass.TryAdd(classType, result) ? result : NonPublicClass[classType];
}

#if NET
/// <summary>
/// Creates an <see cref="RpcTargetMetadata"/> instance from the specified shape.
/// </summary>
/// <typeparam name="T">The type for which a shape should be obtained and <see cref="RpcTargetMetadata"/> generated for.</typeparam>
/// <returns>An <see cref="RpcTargetMetadata"/> instance initialized from the shape of the <typeparamref name="T"/>.</returns>
public static RpcTargetMetadata FromShape<T>()
where T : IShapeable<T> => FromShape(T.GetShape());

/// <summary>
/// Creates an <see cref="RpcTargetMetadata"/> instance from the specified shape.
/// </summary>
/// <typeparam name="T">The type for which a shape should be obtained and <see cref="RpcTargetMetadata"/> generated for.</typeparam>
/// <typeparam name="TProvider">The provider of type shapes from which to obtain the shape.</typeparam>
/// <returns>An <see cref="RpcTargetMetadata"/> instance initialized from the shape of the <typeparamref name="T"/>.</returns>
public static RpcTargetMetadata FromShape<T, TProvider>()
where TProvider : IShapeable<T> => FromShape(TProvider.GetShape());
#endif

/// <inheritdoc cref="FromShape(ITypeShape)" path="/summary"/>
/// <typeparam name="T">The type for which a shape should be obtained and <see cref="RpcTargetMetadata"/> generated for.</typeparam>
/// <param name="provider">The provider of type shapes from which to obtain the shape.</param>
/// <returns>An <see cref="RpcTargetMetadata"/> instance initialized from the shape of the <typeparamref name="T"/>.</returns>
public static RpcTargetMetadata FromShape<T>(ITypeShapeProvider provider) => FromShape(provider.Resolve<T>());

/// <summary>
/// Creates an <see cref="RpcTargetMetadata"/> instance from the specified shape.
/// </summary>
/// <param name="shape">The shape to create the metadata from.</param>
/// <returns>An <see cref="RpcTargetMetadata"/> instance initialized from the <paramref name="shape"/>.</returns>
public static RpcTargetMetadata FromShape(ITypeShape shape)
{
Requires.NotNull(shape);

Builder builder = new(shape);
AddMethods(builder, shape.Methods);

return builder.ToImmutable();
}

/// <summary>
/// Creates an event handler factory that supports <see cref="EventHandler{TEventArgs}"/> for a given <typeparamref name="TEventArgs"/>.
/// </summary>
Expand All @@ -287,15 +331,23 @@ public static RpcTargetMetadata FromClassNonPublic([DynamicallyAccessedMembers(D
public static void RegisterEventArgs<TEventArgs>()
where TEventArgs : struct => EventHandlerFactories.TryAdd(typeof(TEventArgs), new EventHandlerFactory<TEventArgs>());

private static void AddMethods(Builder builder, IReadOnlyList<IMethodShape> methods)
{
foreach (IMethodShape shape in methods)
{
TryAddCandidateMethod(builder, GetMethodInfo(shape), shape);
}
}

private static void AddMethods(Builder builder, IEnumerable<MethodInfo> methods)
{
foreach (MethodInfo method in methods)
{
TryAddCandidateMethod(builder, method);
TryAddCandidateMethod(builder, method, shape: null);
}
}

private static bool TryAddCandidateMethod(Builder builder, MethodInfo method)
private static bool TryAddCandidateMethod(Builder builder, MethodInfo method, IMethodShape? shape)
{
if (method.IsSpecialName || method.IsConstructor || method.DeclaringType == typeof(object))
{
Expand All @@ -315,14 +367,9 @@ private static bool TryAddCandidateMethod(Builder builder, MethodInfo method)
return false;
}

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

if (!builder.Methods.TryGetValue(methodMetadata.Name, out List<TargetMethodMetadata>? methodList))
{
builder.Methods[methodMetadata.Name] = methodList = [];
}

methodList.Add(methodMetadata);
builder.AddMethod(methodMetadata);
return true;
}

Expand Down Expand Up @@ -452,6 +499,8 @@ private static IReadOnlyList<Type> GetMissingInterfacesFromSet([DynamicallyAcces
return missing ?? [];
}

private static MethodInfo GetMethodInfo(IMethodShape shape) => (MethodInfo)(shape.AttributeProvider ?? throw new ArgumentException(Resources.FormatAttributeProviderRequired($"{shape.DeclaringType.Type.FullName}.{shape.Name}"), nameof(shape)));
Comment thread
AArnott marked this conversation as resolved.

internal struct RpcTargetInterface([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicEvents)] Type iface)
{
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicEvents)]
Expand Down Expand Up @@ -683,61 +732,69 @@ public class TargetMethodMetadata
{
private ParameterInfo[]? parameters;

internal TargetMethodMetadata(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape)
{
this.IsPublic = method.IsPublic;
Comment thread
AArnott marked this conversation as resolved.
this.Name = attribute?.Name ?? shape?.Name ?? method.Name;
this.MethodInfo = method;

this.ReturnType = method.ReturnType;
ParameterInfo[] parameters = method.GetParameters();
this.RequiredParamCount = parameters.Count(pi => !pi.IsOptional && pi.ParameterType != typeof(CancellationToken));
this.HasCancellationTokenParameter = parameters is [.., { ParameterType: { } type }] && type == typeof(CancellationToken);
}

/// <summary>
/// Gets the <see cref="MethodInfo"/> for the RPC target method.
/// </summary>
public required MethodInfo Method { get; init; }
public MethodInfo MethodInfo { get; }

/// <summary>
/// Gets the RPC target name that should invoke this method.
/// </summary>
public required string Name { get; init; }
public string Name { get; }

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

/// <summary>
/// Gets the parameters on the method.
/// </summary>
/// <remarks>
/// This is equivalent to <see cref="MethodBase.GetParameters"/>, but cached for performance.
/// </remarks>
internal IReadOnlyList<ParameterInfo> Parameters => this.parameters ??= this.Method.GetParameters() ?? [];
internal IReadOnlyList<ParameterInfo> Parameters => this.parameters ??= this.MethodInfo.GetParameters() ?? [];

/// <summary>
/// Gets a <see cref="ReadOnlyMemory{T}"/> view of the parameters on the method.
/// </summary>
/// <seealso cref="Parameters"/>
internal ReadOnlyMemory<ParameterInfo> ParametersMemory => (ParameterInfo[])this.Parameters;

internal Type ReturnType { get; }

/// <summary>
/// Gets a value indicating whether the method is declared as public.
/// </summary>
internal bool IsPublic => this.Method.IsPublic;
internal bool IsPublic { get; }

internal int RequiredParamCount => this.Parameters.Count(pi => !pi.IsOptional && pi.ParameterType != typeof(CancellationToken));
internal int RequiredParamCount { get; }

internal int TotalParamCountExcludingCancellationToken => this.HasCancellationTokenParameter ? this.Parameters.Count - 1 : this.Parameters.Count;

internal bool HasCancellationTokenParameter => this.Parameters is [.., { ParameterType: { } type }] && type == typeof(CancellationToken);
internal bool HasCancellationTokenParameter { get; }

internal bool HasOutOrRefParameters => this.Parameters.Any(pi => pi.IsOut || pi.ParameterType.IsByRef);

[ExcludeFromCodeCoverage]
private string DebuggerDisplay => $"{this.Method.DeclaringType}.{this.Name}({string.Join(", ", this.Parameters.Select(p => p.ParameterType.Name))})";
private string DebuggerDisplay => $"{this.MethodInfo.DeclaringType}.{this.Name}({string.Join(", ", this.Parameters.Select(p => p.ParameterType.Name))})";

/// <inheritdoc/>
public override string ToString() => this.DebuggerDisplay;

internal static TargetMethodMetadata From(MethodInfo method, JsonRpcMethodAttribute? attribute)
=> new()
{
Method = method,
Name = attribute?.Name ?? method.Name,
Attribute = attribute,
};
internal static TargetMethodMetadata From(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape) => new(method, attribute, shape);

internal bool EqualSignature(TargetMethodMetadata other)
{
Expand Down Expand Up @@ -793,6 +850,11 @@ public Delegate CreateEventHandler(JsonRpc rpc, string eventName, Type delegateT

private class Builder
{
internal Builder(ITypeShape shape)
{
this.TargetType = shape.Type;
}

internal Builder(InterfaceCollection interfaces)
{
this.TargetType = interfaces.PrimaryInterface;
Expand All @@ -819,6 +881,16 @@ internal Builder(ClassAndInterfaces classAndInterfaces)

internal List<EventMetadata> Events { get; } = [];

internal void AddMethod(TargetMethodMetadata methodMetadata)
{
if (!this.Methods.TryGetValue(methodMetadata.Name, out List<TargetMethodMetadata>? methodList))
{
this.Methods[methodMetadata.Name] = methodList = [];
}

methodList.Add(methodMetadata);
}

internal RpcTargetMetadata ToImmutable()
{
this.GenerateAliases();
Expand Down
2 changes: 1 addition & 1 deletion src/StreamJsonRpc/StreamJsonRpc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx" />
<EmbeddedResource Update="Resources.resx" EmitFormatMethods="true" />
</ItemGroup>
<ItemGroup>
<Compile Update="NerdbankMessagePackFormatter.*.cs" DependentUpon="NerdbankMessagePackFormatter.cs" />
Expand Down
10 changes: 5 additions & 5 deletions src/StreamJsonRpc/net8.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType
static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType, StreamJsonRpc.RpcTargetMetadata.ClassAndInterfaces! metadata) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromInterface(StreamJsonRpc.RpcTargetMetadata.InterfaceCollection! interfaces) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromInterface(System.Type! rpcContract) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.Abstractions.ITypeShape! shape) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromShape<T, TProvider>() -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromShape<T>() -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromShape<T>(PolyType.ITypeShapeProvider! provider) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.RegisterEventArgs<TEventArgs>() -> void
StreamJsonRpc.ExportRpcContractProxiesAttribute
StreamJsonRpc.ExportRpcContractProxiesAttribute.ExportRpcContractProxiesAttribute() -> void
Expand Down Expand Up @@ -117,12 +121,8 @@ StreamJsonRpc.RpcTargetMetadata.Methods.init -> void
StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.init -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.init -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.init -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.TargetMethodMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type!
StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void
virtual StreamJsonRpc.RpcTargetMetadata.CreateEventHandlerDelegate.Invoke(StreamJsonRpc.JsonRpc! rpc, string! eventName) -> System.Delegate!
Expand Down
8 changes: 3 additions & 5 deletions src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType
static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType, StreamJsonRpc.RpcTargetMetadata.ClassAndInterfaces! metadata) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromInterface(StreamJsonRpc.RpcTargetMetadata.InterfaceCollection! interfaces) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromInterface(System.Type! rpcContract) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.Abstractions.ITypeShape! shape) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.FromShape<T>(PolyType.ITypeShapeProvider! provider) -> StreamJsonRpc.RpcTargetMetadata!
static StreamJsonRpc.RpcTargetMetadata.RegisterEventArgs<TEventArgs>() -> void
StreamJsonRpc.ExportRpcContractProxiesAttribute
StreamJsonRpc.ExportRpcContractProxiesAttribute.ExportRpcContractProxiesAttribute() -> void
Expand Down Expand Up @@ -117,12 +119,8 @@ StreamJsonRpc.RpcTargetMetadata.Methods.init -> void
StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute?
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.init -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.init -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string!
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.init -> void
StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.TargetMethodMetadata() -> void
StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type!
StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void
virtual StreamJsonRpc.RpcTargetMetadata.CreateEventHandlerDelegate.Invoke(StreamJsonRpc.JsonRpc! rpc, string! eventName) -> System.Delegate!
Expand Down
Loading
Loading