Skip to content

Commit cc719e9

Browse files
authored
Add support for the new StreamJsonRpc formatter that supports Nerdbank.MessagePack (#369)
* Add support for the new StreamJsonRpc formatter that supports Nerdbank.MessagePack This is quite an intense undertaking, because Nerdbank.MessagePack introduces our first NativeAOT-safe serializer, which brings in new requirements and usage restrictions. But it is also a great opportunity. Here's all what's in this PR: * Source generated local proxies. Dynamic proxies still exist, but the new PolyType descriptor only supports activating source generated proxies. Source generated proxies will start faster (no Ref.Emit or JIT) and are NativeAOT safe. * Better runtime type checking for what optional interfaces a brokered service implements. We share a common interface with StreamJsonRpc now (which also recently added source generated proxy support) so clients can perform service interface testing in one consistent way whether the proxy is a remote or local one. * This library now advertises NativeAOT safety. A great deal of APIs are in fact *not* NativeAOT safe though and these now are annotated as requiring dynamic or unreferenced code. At the moment, so many APIs are annotated that ultimately nothing in the library is useful in a NativeAOT environment. This is a point-in-time state. Once we get a NativeAOT-safe JSON formatter for our Framework service protocols to use, most of these attributes will be removed, unblocking use in a NativeAOT app. This change required a whole new descriptor type that does *not* derive from `ServiceJsonRpcDescriptor` because `ITypeShapeProvider` is a required property on a descriptor now and because the pre-existing descriptor class offered functionality that is not NativeAOT-safe. But the new class derives from the same abstract base class as the old one did, so it's still compatible with all other APIs. * Touch-up in response to copilot feedback * Accept a binary breaking change of converting a virtual method to abstract This is acceptable because we don't expect anyone to be implementing their own descriptors outside this assembly, and we need to move dynamic runtime behavior to a derived type. * Fix events, and add more tests * Update LKG test output * Fix more tests * Fix support for multiple service interfaces * Fix line ending test failures on linux * Skip line ending format checks which fail on linux * CR feedback * Add delegating variant of the polytype descriptor
1 parent d693250 commit cc719e9

File tree

86 files changed

+4542
-265
lines changed

Some content is hidden

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

86 files changed

+4542
-265
lines changed

Directory.Packages.Analyzers.props

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
<Project>
22
<PropertyGroup>
3-
<CodeAnalysisVersionForAnalyzers>3.11.0</CodeAnalysisVersionForAnalyzers>
3+
<CodeAnalysisVersionForAnalyzers>4.14.0</CodeAnalysisVersionForAnalyzers>
4+
<CodefixTestingVersion>1.1.2</CodefixTestingVersion>
45
</PropertyGroup>
56
<ItemGroup>
6-
<!-- These versions carefully chosen to support VS 2019 Update 11. -->
7-
<PackageVersion Update="Microsoft.CodeAnalysis" Version="$(CodeAnalysisVersionForAnalyzers)" />
8-
<PackageVersion Update="Microsoft.CodeAnalysis.Common" Version="$(CodeAnalysisVersionForAnalyzers)" />
9-
<PackageVersion Update="Microsoft.CodeAnalysis.CSharp" Version="$(CodeAnalysisVersionForAnalyzers)" />
10-
<PackageVersion Update="Microsoft.CodeAnalysis.CSharp.Workspaces" version="$(CodeAnalysisVersionForAnalyzers)" />
11-
<PackageVersion Update="Microsoft.CodeAnalysis.VisualBasic" Version="$(CodeAnalysisVersionForAnalyzers)" />
12-
<PackageVersion Update="Microsoft.CodeAnalysis.VisualBasic.Workspaces" version="$(CodeAnalysisVersionForAnalyzers)" />
13-
<PackageVersion Update="System.Collections.Immutable" Version="5.0.0" />
14-
<PackageVersion Update="System.Memory" Version="4.5.4" />
15-
<PackageVersion Update="System.Reflection.Metadata" Version="5.0.0" />
16-
<PackageVersion Update="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
17-
<PackageVersion Update="System.Text.Json" Version="4.7.2" />
7+
<!-- These versions carefully chosen to support VS 2022 Update 14. -->
8+
<PackageVersion Include="Microsoft.CodeAnalysis" Version="$(CodeAnalysisVersion)" />
9+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(CodeAnalysisVersion)" />
10+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(CodeAnalysisVersion)" />
11+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(CodefixTestingVersion)" />
12+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="$(CodefixTestingVersion)" />
13+
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="$(MicrosoftCodeAnalysisAnalyzersVersion)" />
14+
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="$(CodeAnalysisVersion)" />
15+
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit" Version="$(CodefixTestingVersion)" />
16+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(CodeAnalysisVersionForAnalyzers)" />
17+
<PackageVersion Update="System.Collections.Immutable" Version="9.0.0" />
18+
<PackageVersion Update="System.Memory" Version="4.5.5" />
19+
<PackageVersion Update="System.Reflection.Metadata" Version="9.0.0" />
20+
<PackageVersion Update="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
21+
<PackageVersion Update="System.Text.Json" Version="9.0.0" />
1822
</ItemGroup>
1923
</Project>

Directory.Packages.props

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,32 @@
66
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
77

88
<MicroBuildVersion>2.0.201</MicroBuildVersion>
9-
<CodeAnalysisVersion>4.12.0</CodeAnalysisVersion>
10-
<MicrosoftCodeAnalysisAnalyzersVersion>3.11.0-beta1.24324.1</MicrosoftCodeAnalysisAnalyzersVersion>
11-
<CodefixTestingVersion>1.1.2</CodefixTestingVersion>
9+
<CodeAnalysisVersion>4.14.0</CodeAnalysisVersion>
10+
<MicrosoftCodeAnalysisAnalyzersVersion>4.14.0</MicrosoftCodeAnalysisAnalyzersVersion>
1211
<VSThreadingVersion>17.13.2</VSThreadingVersion>
1312
</PropertyGroup>
1413
<ItemGroup>
15-
<PackageVersion Include="Microsoft.CodeAnalysis" Version="$(CodeAnalysisVersion)" />
16-
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(CodeAnalysisVersion)" />
17-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(CodeAnalysisVersion)" />
18-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(CodefixTestingVersion)" />
19-
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="$(MicrosoftCodeAnalysisAnalyzersVersion)" />
20-
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="$(CodeAnalysisVersion)" />
21-
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit" Version="$(CodefixTestingVersion)" />
2214
<PackageVersion Include="Microsoft.IO.Redist" Version="6.1.0" />
2315
<PackageVersion Include="microsoft.testing.platform.msbuild" Version="1.9.0" />
16+
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="$(MicrosoftCodeAnalysisAnalyzersVersion)" />
2417
<PackageVersion Include="Microsoft.VisualStudio.Composition" Version="17.12.20" />
2518
<PackageVersion Include="Microsoft.VisualStudio.Interop" Version="17.13.40008" />
2619
<PackageVersion Include="Microsoft.VisualStudio.RpcContracts" Version="17.13.7" />
2720
<PackageVersion Include="Microsoft.VisualStudio.Utilities" Version="17.13.40008" />
2821
<PackageVersion Include="Microsoft.VisualStudio.Utilities.Testing" Version="17.5.33428.366" />
2922
<PackageVersion Include="Microsoft.VisualStudio.Sdk.TestFramework.Xunit.v3" Version="17.11.66" />
3023
<PackageVersion Include="Microsoft.VisualStudio.Shell.15.0" Version="17.13.40008" />
31-
<PackageVersion Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
24+
<PackageVersion Include="Microsoft.VisualStudio.Validation" Version="17.13.22" />
3225
<PackageVersion Include="Microsoft.VisualStudioEng.MicroBuild.Core" Version="1.0.0" />
3326
<PackageVersion Include="Microsoft.VisualStudio.Threading" Version="$(VSThreadingVersion)" />
3427
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="$(VSThreadingVersion)" />
3528
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.228" />
36-
<PackageVersion Include="Nerdbank.Streams" Version="2.11.86" />
29+
<PackageVersion Include="Nerdbank.Streams" Version="2.13.16" />
3730
<PackageVersion Include="NuGet.Protocol" Version="6.13.2" />
38-
<PackageVersion Include="StreamJsonRpc" Version="2.21.10" />
31+
<PackageVersion Include="StreamJsonRpc" Version="2.24.45-preview" />
3932
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
4033
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
41-
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
34+
<PackageVersion Include="System.Text.Json" Version="8.0.6" />
4235
<PackageVersion Include="TraceSource.ActivityTracing" Version="0.1.201-beta" />
4336
<PackageVersion Include="Xunit.Combinatorial" Version="2.0.24" />
4437
<PackageVersion Include="xunit.v3.core" Version="3.1.0" />
@@ -50,7 +43,7 @@
5043
<PackageVersion Include="xunit.v3" Version="3.1.0" />
5144
</ItemGroup>
5245
<ItemGroup>
53-
<GlobalPackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="$(MicrosoftCodeAnalysisAnalyzersVersion)" />
46+
<GlobalPackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="3.12.0-beta1.25218.8" />
5447
</ItemGroup>
5548
<ItemGroup Label="Library.Template">
5649
<GlobalPackageReference Include="CSharpIsNullAnalyzer" Version="0.1.593" />

Microsoft.ServiceBroker.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<File Path=".github/renovate.json" />
66
<File Path="Directory.Build.props" />
77
<File Path="Directory.Build.targets" />
8+
<File Path="Directory.Packages.Analyzers.props" />
89
<File Path="Directory.Packages.props" />
910
<File Path="global.json" />
1011
<File Path="nuget.config" />
@@ -32,6 +33,7 @@
3233
<Project Path="src/Microsoft.ServiceHub.Framework/Microsoft.ServiceHub.Framework.csproj" />
3334
</Folder>
3435
<Folder Name="/test/">
36+
<File Path="test/AnalyzerUser.targets" />
3537
<Project Path="test/ExternalTestAssembly/ExternalTestAssembly.csproj" />
3638
<Project Path="test/Microsoft.ServiceHub.Analyzers.Tests/Microsoft.ServiceHub.Analyzers.Tests.csproj" />
3739
<Project Path="test/Microsoft.ServiceHub.Framework.Testing.Tests/Microsoft.ServiceHub.Framework.Testing.Tests.csproj" />

azure-pipelines/build.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ jobs:
203203
IsOptProf: ${{ parameters.IsOptProf }}
204204

205205
- ${{ if and(parameters.EnableDotNetFormatCheck, not(parameters.EnableLinuxBuild)) }}:
206-
- script: dotnet format --verify-no-changes
206+
- script: |
207+
dotnet format style --verify-no-changes
208+
dotnet format analyzers --verify-no-changes --no-restore
207209
displayName: 💅 Verify formatted code
208210
env:
209211
dotnetformat: true # part of a workaround for https://github.com/dotnet/sdk/issues/44951
@@ -263,7 +265,9 @@ jobs:
263265
RunTests: ${{ parameters.RunTests }}
264266
IsOptProf: ${{ parameters.IsOptProf }}
265267
- ${{ if parameters.EnableDotNetFormatCheck }}:
266-
- script: dotnet format --verify-no-changes
268+
- script: |
269+
dotnet format style --verify-no-changes
270+
dotnet format analyzers --verify-no-changes --no-restore
267271
displayName: 💅 Verify formatted code
268272
env:
269273
dotnetformat: true # part of a workaround for https://github.com/dotnet/sdk/issues/44951

nuget.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<packageSources>
77
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
88
<clear />
9-
<add key="msft_consumption_public" value="https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption_public/nuget/v3/index.json" />
9+
<add key="msft_consumption" value="https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json" />
1010
</packageSources>
1111
<disabledPackageSources>
1212
<!-- Defend against user or machine level disabling of sources that we list in this file. -->
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Our source generator tests want identical results regardless of platform.
2+
*.cs text eol=crlf
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.ServiceHub.Analyzers.GeneratorModels;
5+
6+
/// <summary>
7+
/// Helper methods and properties for a source generator.
8+
/// </summary>
9+
internal static class GenerationHelpers
10+
{
11+
/// <summary>
12+
/// Gets a string providing a fully qualified name, without the global:: prefix.
13+
/// </summary>
14+
internal static SymbolDisplayFormat QualifiedNameOnlyFormat { get; } =
15+
new SymbolDisplayFormat(
16+
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
17+
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
18+
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType);
19+
20+
internal static IEnumerable<TResult> Select<T, TResult>(this ReadOnlyMemory<T> memory, Func<T, TResult> selector)
21+
{
22+
for (int i = 0; i < memory.Length; i++)
23+
{
24+
yield return selector(memory.Span[i]);
25+
}
26+
}
27+
28+
internal static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol symbol)
29+
{
30+
if (symbol.BaseType is not null)
31+
{
32+
foreach (ISymbol baseMember in symbol.BaseType.GetAllMembers())
33+
{
34+
yield return baseMember;
35+
}
36+
}
37+
38+
foreach (INamedTypeSymbol iface in symbol.AllInterfaces)
39+
{
40+
foreach (ISymbol member in iface.GetMembers())
41+
{
42+
yield return member;
43+
}
44+
}
45+
46+
foreach (ISymbol member in symbol.GetMembers())
47+
{
48+
yield return member;
49+
}
50+
}
51+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.CodeAnalysis.CSharp;
5+
6+
namespace Microsoft.ServiceHub.Analyzers.GeneratorModels;
7+
8+
/// <summary>
9+
/// A namespace or nesting type that must be reproduced in generated code in order to declare a potentially nested partial type.
10+
/// </summary>
11+
/// <param name="Name">The leaf name of the namespace or the name of the nesting type.</param>
12+
/// <param name="Parent">The parent namespace or nesting type.</param>
13+
internal abstract record Container(string Name, Container? Parent = null)
14+
{
15+
internal abstract bool IsPartial { get; }
16+
17+
internal bool IsFullyPartial => this.IsPartial && (this.Parent is null || this.Parent.IsFullyPartial);
18+
19+
internal abstract string Keywords { get; }
20+
21+
internal string FullName => this.Parent is null ? this.Name : $"{this.Parent.FullName}.{this.Name}";
22+
23+
/// <summary>
24+
/// Gets the first namespace in the chain of containers, starting with this one.
25+
/// </summary>
26+
internal Namespace? ThisOrContainingNamespace => this is Namespace ns ? ns : this.Parent?.ThisOrContainingNamespace;
27+
28+
internal static Container? CreateFor(INamespaceOrTypeSymbol? symbol, CancellationToken cancellationToken)
29+
{
30+
if (symbol is null or INamespaceSymbol { IsGlobalNamespace: true })
31+
{
32+
return null;
33+
}
34+
35+
Container? parent = CreateFor((INamespaceOrTypeSymbol)symbol.ContainingType ?? symbol.ContainingNamespace, cancellationToken);
36+
37+
return symbol switch
38+
{
39+
ITypeSymbol type => new Type(type.Name, IsPartial(), GetTypeKeyword(type), parent),
40+
INamespaceSymbol ns => new Namespace(ns.Name, parent),
41+
_ => throw new ArgumentException($"Unsupported symbol type: {symbol.GetType().Name}.", nameof(symbol)),
42+
};
43+
44+
bool IsPartial() => (symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken) as MemberDeclarationSyntax)?.Modifiers.Any(SyntaxKind.PartialKeyword) is true;
45+
46+
static string GetTypeKeyword(ITypeSymbol symbol)
47+
{
48+
return symbol switch
49+
{
50+
{ TypeKind: TypeKind.Class, IsRecord: false } => "class",
51+
{ TypeKind: TypeKind.Class, IsRecord: true } => "record",
52+
{ TypeKind: TypeKind.Struct, IsRecord: false } => "struct",
53+
{ TypeKind: TypeKind.Struct, IsRecord: true } => "record struct",
54+
{ TypeKind: TypeKind.Interface } => "interface",
55+
_ => throw new ArgumentException($"Unsupported type kind: {symbol.TypeKind}.", nameof(symbol)),
56+
};
57+
}
58+
}
59+
60+
internal void WriteWithin(SourceWriter writer, Action<SourceWriter> writeContent)
61+
{
62+
this.VerifyPartiality();
63+
if (this.Parent is null)
64+
{
65+
this.WriteWithinCore(writer, writeContent);
66+
}
67+
else
68+
{
69+
this.Parent.WriteWithin(writer, writer => this.WriteWithinCore(writer, writeContent));
70+
}
71+
}
72+
73+
private void VerifyPartiality()
74+
{
75+
if (!this.IsPartial)
76+
{
77+
throw new InvalidOperationException($"The {this.FullName} container must be declared as partial.");
78+
}
79+
80+
this.Parent?.VerifyPartiality();
81+
}
82+
83+
private void WriteWithinCore(SourceWriter writer, Action<SourceWriter> writeContent)
84+
{
85+
writer.WriteLine($"{this.Keywords} {this.Name}");
86+
writer.WriteLine("{");
87+
writer.Indentation++;
88+
writeContent(writer);
89+
writer.Indentation--;
90+
writer.WriteLine("}");
91+
}
92+
93+
internal record Namespace(string Name, Container? Parent = null) : Container(Name, Parent)
94+
{
95+
internal override bool IsPartial => true;
96+
97+
internal override string Keywords => "namespace";
98+
}
99+
100+
internal record Type : Container
101+
{
102+
private readonly bool isPartial;
103+
private readonly string typeKeyword;
104+
105+
internal Type(string name, bool isPartial, string typeKeyword, Container? parent = null)
106+
: base(name, parent)
107+
{
108+
this.isPartial = isPartial;
109+
this.typeKeyword = typeKeyword;
110+
}
111+
112+
internal override bool IsPartial => this.isPartial;
113+
114+
internal override string Keywords => this.IsPartial ? $"partial {this.typeKeyword}" : this.typeKeyword;
115+
}
116+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.ServiceHub.Analyzers.GeneratorModels;
5+
6+
internal record EventModel(string DeclaringType, string Name, string DelegateType, string EventArgsType) : FormattableModel
7+
{
8+
internal override void WriteEvents(SourceWriter writer)
9+
{
10+
writer.WriteLine($$"""
11+
12+
public event {{this.DelegateType}}? {{this.Name}}
13+
{
14+
add
15+
{
16+
if (this.TargetOrNull is {{this.DeclaringType}} target)
17+
{
18+
target.{{this.Name}} += value;
19+
}
20+
}
21+
22+
remove
23+
{
24+
if (this.TargetOrNull is {{this.DeclaringType}} target)
25+
{
26+
target.{{this.Name}} -= value;
27+
}
28+
}
29+
}
30+
""");
31+
}
32+
33+
internal static EventModel? Create(IEventSymbol evt, KnownSymbols symbols)
34+
{
35+
if (evt.Type is not INamedTypeSymbol { DelegateInvokeMethod: { } invokeMethod })
36+
{
37+
return null;
38+
}
39+
40+
return new EventModel(evt.ContainingType.ToDisplayString(ProxyGenerator.FullyQualifiedWithNullableFormat), evt.Name, evt.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), invokeMethod.Parameters[1].Type.ToDisplayString(ProxyGenerator.FullyQualifiedWithNullableFormat));
41+
}
42+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.ServiceHub.Analyzers.GeneratorModels;
5+
6+
internal abstract record FormattableModel
7+
{
8+
internal virtual void WriteFields(SourceWriter writer)
9+
{
10+
}
11+
12+
internal virtual void WriteProperties(SourceWriter writer)
13+
{
14+
}
15+
16+
internal virtual void WriteHookupStatements(SourceWriter writer)
17+
{
18+
}
19+
20+
internal virtual void WriteEvents(SourceWriter writer)
21+
{
22+
}
23+
24+
internal virtual void WriteMethods(SourceWriter writer)
25+
{
26+
}
27+
28+
internal virtual void WriteNestedTypes(SourceWriter writer)
29+
{
30+
}
31+
}

0 commit comments

Comments
 (0)