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
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>
121 changes: 97 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,70 @@ 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.Attribute = attribute;

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 +851,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 +882,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