Skip to content

Commit 3873c15

Browse files
authored
Merge pull request #1303 from microsoft/dev/andarno/proxyWithVarianceModifiers
Fix source generated proxies compile errors
2 parents 2cd77e3 + ecb5cee commit 3873c15

File tree

80 files changed

+490
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+490
-3
lines changed

src/StreamJsonRpc.Analyzers/GeneratorModels/FullModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ private void GenerateOptionalInterfaceExtensionMethods(SourceProductionContext c
6464
6565
#nullable enable
6666
#pragma warning disable CS0436 // prefer local types to imported ones
67+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
6768
6869
using StreamJsonRpc;
6970

src/StreamJsonRpc.Analyzers/GeneratorModels/InterfaceModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace StreamJsonRpc.Analyzers.GeneratorModels;
1818
/// <param name="Methods">The methods in the interface.</param>
1919
/// <param name="Events">The events in the interface.</param>
2020
/// <param name="HasUnsupportedMemberTypes">Indicates whether the interface has additional members that are not supported.</param>
21-
internal record InterfaceModel(string FullName, string Name, ImmutableEquatableArray<string> TypeParameters, Container? Container, ImmutableEquatableArray<MethodModel> Methods, ImmutableEquatableArray<EventModel> Events, bool HasUnsupportedMemberTypes)
21+
internal record InterfaceModel(string FullName, string Name, ImmutableEquatableArray<(VarianceKind Variance, string Identifier)> TypeParameters, Container? Container, ImmutableEquatableArray<MethodModel> Methods, ImmutableEquatableArray<EventModel> Events, bool HasUnsupportedMemberTypes)
2222
{
2323
internal required bool IsPartial { get; init; }
2424

@@ -77,7 +77,7 @@ internal static InterfaceModel Create(INamedTypeSymbol iface, KnownSymbols symbo
7777
return new InterfaceModel(
7878
iface.ToDisplayString(ProxyGenerator.FullyQualifiedNoGlobalWithNullableFormat),
7979
iface.Name,
80-
[.. iface.TypeParameters.Select(tp => tp.Name)],
80+
[.. iface.TypeParameters.Select(tp => (tp.Variance, tp.Name))],
8181
Container.CreateFor((INamespaceOrTypeSymbol?)iface.ContainingType ?? iface.ContainingNamespace, cancellationToken),
8282
methods.ToImmutableEquatableArray(),
8383
events.ToImmutableEquatableArray(),

src/StreamJsonRpc.Analyzers/GeneratorModels/ProxyModel.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ internal ProxyModel(ImmutableEquatableSet<InterfaceModel> interfaces, string? ex
7474
internal void WriteInterfaceMapping(SourceWriter writer, InterfaceModel iface)
7575
{
7676
string genericTypeParameters = iface.TypeParameters.Length > 0
77-
? $"<{string.Join(", ", iface.TypeParameters)}>"
77+
? $"<{string.Join(", ", iface.TypeParameters.Select(WriteTypeParameter))}>"
7878
: string.Empty;
7979
writer.WriteLine($$"""
8080
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof({{ProxyGenerator.GenerationNamespace}}.{{this.Name}}{{this.GenericTypeDefinitionSuffix}}))]
@@ -111,6 +111,7 @@ internal void GenerateSource(SourceProductionContext context, bool isPublic)
111111
112112
#nullable enable
113113
#pragma warning disable CS0436 // prefer local types to imported ones
114+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
114115
115116
""");
116117

@@ -291,4 +292,12 @@ private static string CreateProxyName(ImmutableEquatableSet<InterfaceModel> inte
291292
string additionalInterfaceHashString = Convert.ToBase64String(additionalInterfaceHash).TrimEnd('=').Replace('+', '_').Replace('/', '_');
292293
return $"{sorted[0]}{additionalInterfaceHashString[..8]}";
293294
}
295+
296+
private static string WriteTypeParameter((VarianceKind Variance, string Identifier) typeParameter) => typeParameter.Variance switch
297+
{
298+
VarianceKind.None => typeParameter.Identifier,
299+
VarianceKind.In => $"in {typeParameter.Identifier}",
300+
VarianceKind.Out => $"out {typeParameter.Identifier}",
301+
_ => throw new InvalidOperationException($"Unknown variance kind: {typeParameter.Variance}."),
302+
};
294303
}

test/StreamJsonRpc.Analyzer.Tests/ProxyGeneratorTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,30 @@ public partial interface IGenericMarshalable<T>
389389
""");
390390
}
391391

392+
[Fact]
393+
public async Task RpcMarshalable_Generic_WithInModifier()
394+
{
395+
await VerifyCS.RunDefaultAsync("""
396+
[RpcMarshalable]
397+
public partial interface IGenericMarshalable<in T>
398+
{
399+
Task DoSomethingWithParameterAsync(T parameter);
400+
}
401+
""");
402+
}
403+
404+
[Fact]
405+
public async Task RpcMarshalable_Generic_WithOutModifier()
406+
{
407+
await VerifyCS.RunDefaultAsync("""
408+
[RpcMarshalable]
409+
public partial interface IGenericMarshalable<out T>
410+
{
411+
Task DoSomethingWithParameterAsync();
412+
}
413+
""");
414+
}
415+
392416
[Fact]
393417
public async Task RpcMarshalable_GenericWithClosedPrescriptions()
394418
{
@@ -415,6 +439,34 @@ public partial interface IGenericMarshalable<T1, T2>
415439
""");
416440
}
417441

442+
#if NET
443+
[Fact]
444+
public async Task ExperimentalApis()
445+
{
446+
await VerifyCS.RunDefaultAsync("""
447+
using System.Diagnostics.CodeAnalysis;
448+
449+
[Experimental("MYEXPERIMENT1")]
450+
public struct CustomType { }
451+
452+
[RpcMarshalable]
453+
[RpcMarshalableOptionalInterfaceAttribute(1, typeof(SomeExperimentalInterface2))]
454+
[Experimental("MYEXPERIMENT2")]
455+
public partial interface SomeExperimentalInterface
456+
{
457+
Task<int> AddAsync(int a, CustomType t, CancellationToken token);
458+
}
459+
460+
[RpcMarshalable(IsOptional = true)]
461+
[Experimental("MYEXPERIMENT2")]
462+
public partial interface SomeExperimentalInterface2 : IDisposable
463+
{
464+
Task<int> AddAsync(int a, CustomType t, CancellationToken token);
465+
}
466+
""");
467+
}
468+
#endif
469+
418470
/// <summary>
419471
/// Verifies that an RpcMarshalable attribute on an interface with both valid and invalid members does not break the build (but it will report a diagnostic, as tested elsewhere).
420472
/// </summary>

test/StreamJsonRpc.Analyzer.Tests/Resources/EmptyInterface/IFoo.g.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#nullable enable
44
#pragma warning disable CS0436 // prefer local types to imported ones
5+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
56

67
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof(StreamJsonRpc.Generated.IFoo_Proxy))]
78
partial interface IFoo

test/StreamJsonRpc.Analyzer.Tests/Resources/Events/IFoo.g.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#nullable enable
44
#pragma warning disable CS0436 // prefer local types to imported ones
5+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
56

67
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof(StreamJsonRpc.Generated.IFoo_Proxy))]
78
partial interface IFoo
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// <auto-generated/>
2+
3+
#nullable enable
4+
#pragma warning disable CS0436 // prefer local types to imported ones
5+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
6+
7+
using StreamJsonRpc;
8+
9+
/// <summary>Extension methods for interfaces acting as optional interfaces on proxies.</summary>
10+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("StreamJsonRpc.Analyzers", "x.x.x.x")]
11+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
12+
public static partial class StreamJsonRpcOptionalInterfaceAccessors
13+
{
14+
/// <inheritdoc cref="global::StreamJsonRpc.IClientProxy.Is(global::System.Type)"/>
15+
public static bool Is(this SomeExperimentalInterface self, global::System.Type type) => self is global::StreamJsonRpc.IClientProxy proxy ? proxy.Is(type) : type.IsAssignableFrom(self.GetType());
16+
/// <inheritdoc cref="global::StreamJsonRpc.JsonRpcExtensions.As{T}(global::StreamJsonRpc.IClientProxy)"/>
17+
public static T? As<T>(this SomeExperimentalInterface self) where T : class => self is global::StreamJsonRpc.IClientProxy proxy ? proxy.As<T>() : self as T;
18+
19+
/// <inheritdoc cref="global::StreamJsonRpc.IClientProxy.Is(global::System.Type)"/>
20+
public static bool Is(this SomeExperimentalInterface2 self, global::System.Type type) => self is global::StreamJsonRpc.IClientProxy proxy ? proxy.Is(type) : type.IsAssignableFrom(self.GetType());
21+
/// <inheritdoc cref="global::StreamJsonRpc.JsonRpcExtensions.As{T}(global::StreamJsonRpc.IClientProxy)"/>
22+
public static T? As<T>(this SomeExperimentalInterface2 self) where T : class => self is global::StreamJsonRpc.IClientProxy proxy ? proxy.As<T>() : self as T;
23+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// <auto-generated/>
2+
3+
#nullable enable
4+
#pragma warning disable CS0436 // prefer local types to imported ones
5+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
6+
7+
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof(StreamJsonRpc.Generated.SomeExperimentalInterface_Proxy))]
8+
partial interface SomeExperimentalInterface
9+
{
10+
}
11+
12+
namespace StreamJsonRpc.Generated
13+
{
14+
15+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("StreamJsonRpc.Analyzers", "x.x.x.x")]
16+
internal class SomeExperimentalInterface_Proxy : global::StreamJsonRpc.Reflection.ProxyBase
17+
, global::SomeExperimentalInterface
18+
{
19+
20+
private static readonly global::System.Collections.Generic.IReadOnlyDictionary<string, global::System.Type> AddAsyncNamedArgumentDeclaredTypes1 = new global::System.Collections.Generic.Dictionary<string, global::System.Type>
21+
{
22+
["a"] = typeof(int),
23+
["t"] = typeof(global::CustomType),
24+
};
25+
26+
private static readonly global::System.Collections.Generic.IReadOnlyList<global::System.Type> AddAsyncPositionalArgumentDeclaredTypes1 = new global::System.Collections.Generic.List<global::System.Type>
27+
{
28+
typeof(int),
29+
typeof(global::CustomType),
30+
};
31+
32+
private string? transformedAddAsync1;
33+
34+
public SomeExperimentalInterface_Proxy(global::StreamJsonRpc.JsonRpc client, global::StreamJsonRpc.Reflection.ProxyInputs inputs)
35+
: base(client, inputs)
36+
{
37+
}
38+
39+
global::System.Threading.Tasks.Task<int> global::SomeExperimentalInterface.AddAsync(int a, global::CustomType t, global::System.Threading.CancellationToken token)
40+
{
41+
if (this.IsDisposed) throw new global::System.ObjectDisposedException(this.GetType().FullName);
42+
43+
this.OnCallingMethod("AddAsync");
44+
string rpcMethodName = this.transformedAddAsync1 ??= this.TransformMethodName("AddAsync", typeof(global::SomeExperimentalInterface));
45+
global::System.Threading.Tasks.Task<int> result = this.Options.ServerRequiresNamedArguments ?
46+
this.JsonRpc.InvokeWithParameterObjectAsync<int>(rpcMethodName, ConstructNamedArgs(), AddAsyncNamedArgumentDeclaredTypes1, token) :
47+
this.JsonRpc.InvokeWithCancellationAsync<int>(rpcMethodName, [a, t], AddAsyncPositionalArgumentDeclaredTypes1, token);
48+
this.OnCalledMethod("AddAsync");
49+
50+
return result;
51+
52+
global::System.Collections.Generic.Dictionary<string, object?> ConstructNamedArgs()
53+
=> new()
54+
{
55+
["a"] = a,
56+
["t"] = t,
57+
};
58+
}
59+
}
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// <auto-generated/>
2+
3+
#nullable enable
4+
#pragma warning disable CS0436 // prefer local types to imported ones
5+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
6+
7+
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof(StreamJsonRpc.Generated.SomeExperimentalInterface2_Proxy))]
8+
partial interface SomeExperimentalInterface2
9+
{
10+
}
11+
12+
namespace StreamJsonRpc.Generated
13+
{
14+
15+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("StreamJsonRpc.Analyzers", "x.x.x.x")]
16+
internal class SomeExperimentalInterface2_Proxy : global::StreamJsonRpc.Reflection.ProxyBase
17+
, global::SomeExperimentalInterface2
18+
{
19+
20+
private static readonly global::System.Collections.Generic.IReadOnlyDictionary<string, global::System.Type> AddAsyncNamedArgumentDeclaredTypes1 = new global::System.Collections.Generic.Dictionary<string, global::System.Type>
21+
{
22+
["a"] = typeof(int),
23+
["t"] = typeof(global::CustomType),
24+
};
25+
26+
private static readonly global::System.Collections.Generic.IReadOnlyList<global::System.Type> AddAsyncPositionalArgumentDeclaredTypes1 = new global::System.Collections.Generic.List<global::System.Type>
27+
{
28+
typeof(int),
29+
typeof(global::CustomType),
30+
};
31+
32+
private string? transformedAddAsync1;
33+
34+
public SomeExperimentalInterface2_Proxy(global::StreamJsonRpc.JsonRpc client, global::StreamJsonRpc.Reflection.ProxyInputs inputs)
35+
: base(client, inputs)
36+
{
37+
}
38+
39+
global::System.Threading.Tasks.Task<int> global::SomeExperimentalInterface2.AddAsync(int a, global::CustomType t, global::System.Threading.CancellationToken token)
40+
{
41+
if (this.IsDisposed) throw new global::System.ObjectDisposedException(this.GetType().FullName);
42+
43+
this.OnCallingMethod("AddAsync");
44+
string rpcMethodName = this.transformedAddAsync1 ??= this.TransformMethodName("AddAsync", typeof(global::SomeExperimentalInterface2));
45+
global::System.Threading.Tasks.Task<int> result = this.Options.ServerRequiresNamedArguments ?
46+
this.JsonRpc.InvokeWithParameterObjectAsync<int>(rpcMethodName, ConstructNamedArgs(), AddAsyncNamedArgumentDeclaredTypes1, token) :
47+
this.JsonRpc.InvokeWithCancellationAsync<int>(rpcMethodName, [a, t], AddAsyncPositionalArgumentDeclaredTypes1, token);
48+
this.OnCalledMethod("AddAsync");
49+
50+
return result;
51+
52+
global::System.Collections.Generic.Dictionary<string, object?> ConstructNamedArgs()
53+
=> new()
54+
{
55+
["a"] = a,
56+
["t"] = t,
57+
};
58+
}
59+
}
60+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// <auto-generated/>
2+
3+
#nullable enable
4+
#pragma warning disable CS0436 // prefer local types to imported ones
5+
#pragma warning disable // Disable all warnings so that [Experimental] APIs don't flag anything.
6+
7+
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof(StreamJsonRpc.Generated.SomeExperimentalInterfaceMDGoEVkn_Proxy))]
8+
partial interface SomeExperimentalInterface
9+
{
10+
}
11+
12+
[global::StreamJsonRpc.Reflection.JsonRpcProxyMappingAttribute(typeof(StreamJsonRpc.Generated.SomeExperimentalInterfaceMDGoEVkn_Proxy))]
13+
partial interface SomeExperimentalInterface2
14+
{
15+
}
16+
17+
namespace StreamJsonRpc.Generated
18+
{
19+
20+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("StreamJsonRpc.Analyzers", "x.x.x.x")]
21+
internal class SomeExperimentalInterfaceMDGoEVkn_Proxy : global::StreamJsonRpc.Reflection.ProxyBase
22+
, global::SomeExperimentalInterface
23+
, global::SomeExperimentalInterface2
24+
{
25+
26+
private static readonly global::System.Collections.Generic.IReadOnlyDictionary<string, global::System.Type> AddAsyncNamedArgumentDeclaredTypes1 = new global::System.Collections.Generic.Dictionary<string, global::System.Type>
27+
{
28+
["a"] = typeof(int),
29+
["t"] = typeof(global::CustomType),
30+
};
31+
32+
private static readonly global::System.Collections.Generic.IReadOnlyList<global::System.Type> AddAsyncPositionalArgumentDeclaredTypes1 = new global::System.Collections.Generic.List<global::System.Type>
33+
{
34+
typeof(int),
35+
typeof(global::CustomType),
36+
};
37+
38+
private string? transformedAddAsync1;
39+
40+
private static readonly global::System.Collections.Generic.IReadOnlyDictionary<string, global::System.Type> AddAsyncNamedArgumentDeclaredTypes2 = new global::System.Collections.Generic.Dictionary<string, global::System.Type>
41+
{
42+
["a"] = typeof(int),
43+
["t"] = typeof(global::CustomType),
44+
};
45+
46+
private static readonly global::System.Collections.Generic.IReadOnlyList<global::System.Type> AddAsyncPositionalArgumentDeclaredTypes2 = new global::System.Collections.Generic.List<global::System.Type>
47+
{
48+
typeof(int),
49+
typeof(global::CustomType),
50+
};
51+
52+
private string? transformedAddAsync2;
53+
54+
public SomeExperimentalInterfaceMDGoEVkn_Proxy(global::StreamJsonRpc.JsonRpc client, global::StreamJsonRpc.Reflection.ProxyInputs inputs)
55+
: base(client, inputs)
56+
{
57+
}
58+
59+
global::System.Threading.Tasks.Task<int> global::SomeExperimentalInterface.AddAsync(int a, global::CustomType t, global::System.Threading.CancellationToken token)
60+
{
61+
if (this.IsDisposed) throw new global::System.ObjectDisposedException(this.GetType().FullName);
62+
63+
this.OnCallingMethod("AddAsync");
64+
string rpcMethodName = this.transformedAddAsync1 ??= this.TransformMethodName("AddAsync", typeof(global::SomeExperimentalInterface));
65+
global::System.Threading.Tasks.Task<int> result = this.Options.ServerRequiresNamedArguments ?
66+
this.JsonRpc.InvokeWithParameterObjectAsync<int>(rpcMethodName, ConstructNamedArgs(), AddAsyncNamedArgumentDeclaredTypes1, token) :
67+
this.JsonRpc.InvokeWithCancellationAsync<int>(rpcMethodName, [a, t], AddAsyncPositionalArgumentDeclaredTypes1, token);
68+
this.OnCalledMethod("AddAsync");
69+
70+
return result;
71+
72+
global::System.Collections.Generic.Dictionary<string, object?> ConstructNamedArgs()
73+
=> new()
74+
{
75+
["a"] = a,
76+
["t"] = t,
77+
};
78+
}
79+
80+
global::System.Threading.Tasks.Task<int> global::SomeExperimentalInterface2.AddAsync(int a, global::CustomType t, global::System.Threading.CancellationToken token)
81+
{
82+
if (this.IsDisposed) throw new global::System.ObjectDisposedException(this.GetType().FullName);
83+
84+
this.OnCallingMethod("AddAsync");
85+
string rpcMethodName = this.transformedAddAsync2 ??= this.TransformMethodName("AddAsync", typeof(global::SomeExperimentalInterface2));
86+
global::System.Threading.Tasks.Task<int> result = this.Options.ServerRequiresNamedArguments ?
87+
this.JsonRpc.InvokeWithParameterObjectAsync<int>(rpcMethodName, ConstructNamedArgs(), AddAsyncNamedArgumentDeclaredTypes2, token) :
88+
this.JsonRpc.InvokeWithCancellationAsync<int>(rpcMethodName, [a, t], AddAsyncPositionalArgumentDeclaredTypes2, token);
89+
this.OnCalledMethod("AddAsync");
90+
91+
return result;
92+
93+
global::System.Collections.Generic.Dictionary<string, object?> ConstructNamedArgs()
94+
=> new()
95+
{
96+
["a"] = a,
97+
["t"] = t,
98+
};
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)