diff --git a/System.ServiceModel.sln b/System.ServiceModel.sln
index 64a9fe87fdd..57e9baa3609 100644
--- a/System.ServiceModel.sln
+++ b/System.ServiceModel.sln
@@ -89,6 +89,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ServiceModel.UnixDom
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Binding.UDS.IntegrationTests", "src\System.Private.ServiceModel\tests\Scenarios\Binding\UDS\Binding.UDS.IntegrationTests.csproj", "{B7C7D4F1-DE4D-421B-9CE9-C7320A503D58}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceModel.BuildTools.Roslyn4.0", "src\System.ServiceModel.BuildTools\src\System.ServiceModel.BuildTools.Roslyn4.0.csproj", "{C03919A5-503D-4FC1-A9C2-F534EE25D84B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceModel.BuildTools.Roslyn3.11", "src\System.ServiceModel.BuildTools\src\System.ServiceModel.BuildTools.Roslyn3.11.csproj", "{83BB41BB-CB6E-4D32-9CD1-384FEAE88949}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceModel.BuildTools.Roslyn3.11.Tests", "src\System.ServiceModel.BuildTools\tests\System.ServiceModel.BuildTools.Roslyn3.11.Tests.csproj", "{5D1B7FB8-3A9B-487F-96C0-76ACC43D98A2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceModel.BuildTools.Roslyn4.0.Tests", "src\System.ServiceModel.BuildTools\tests\System.ServiceModel.BuildTools.Roslyn4.0.Tests.csproj", "{6B670970-CAD2-446E-AC83-2BF097E4CD5E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contract.Service.SourceGeneration.IntegrationTests", "src\System.Private.ServiceModel\tests\Scenarios\Contract\Service\Contract.Service.SourceGeneration.IntegrationTests.csproj", "{A82E5300-B869-45A1-8379-040D47B28103}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -259,6 +269,26 @@ Global
{B7C7D4F1-DE4D-421B-9CE9-C7320A503D58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7C7D4F1-DE4D-421B-9CE9-C7320A503D58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7C7D4F1-DE4D-421B-9CE9-C7320A503D58}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C03919A5-503D-4FC1-A9C2-F534EE25D84B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C03919A5-503D-4FC1-A9C2-F534EE25D84B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C03919A5-503D-4FC1-A9C2-F534EE25D84B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C03919A5-503D-4FC1-A9C2-F534EE25D84B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83BB41BB-CB6E-4D32-9CD1-384FEAE88949}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83BB41BB-CB6E-4D32-9CD1-384FEAE88949}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83BB41BB-CB6E-4D32-9CD1-384FEAE88949}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83BB41BB-CB6E-4D32-9CD1-384FEAE88949}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D1B7FB8-3A9B-487F-96C0-76ACC43D98A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D1B7FB8-3A9B-487F-96C0-76ACC43D98A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D1B7FB8-3A9B-487F-96C0-76ACC43D98A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D1B7FB8-3A9B-487F-96C0-76ACC43D98A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B670970-CAD2-446E-AC83-2BF097E4CD5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B670970-CAD2-446E-AC83-2BF097E4CD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B670970-CAD2-446E-AC83-2BF097E4CD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B670970-CAD2-446E-AC83-2BF097E4CD5E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A82E5300-B869-45A1-8379-040D47B28103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A82E5300-B869-45A1-8379-040D47B28103}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A82E5300-B869-45A1-8379-040D47B28103}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A82E5300-B869-45A1-8379-040D47B28103}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -296,6 +326,9 @@ Global
{E8E40B62-E737-4768-82C2-039E90ED9A39} = {D6302510-AB10-4775-BCE9-98FA96FDEB76}
{88918456-A2B2-431F-BB95-BAAD2818BFC7} = {DFDC71CF-6E65-481D-99D7-C35ED7EF6D4E}
{B7C7D4F1-DE4D-421B-9CE9-C7320A503D58} = {D6302510-AB10-4775-BCE9-98FA96FDEB76}
+ {5D1B7FB8-3A9B-487F-96C0-76ACC43D98A2} = {DFDC71CF-6E65-481D-99D7-C35ED7EF6D4E}
+ {6B670970-CAD2-446E-AC83-2BF097E4CD5E} = {DFDC71CF-6E65-481D-99D7-C35ED7EF6D4E}
+ {A82E5300-B869-45A1-8379-040D47B28103} = {D6302510-AB10-4775-BCE9-98FA96FDEB76}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E0638FAC-BA6B-4E18-BAE6-468C3191BE58}
diff --git a/azure-pipelines-arcade.yml b/azure-pipelines-arcade.yml
index 3ff43fbb9ac..9db55b1bfee 100644
--- a/azure-pipelines-arcade.yml
+++ b/azure-pipelines-arcade.yml
@@ -226,9 +226,10 @@ stages:
- ${{ if eq(variables._RunAsPublic, True) }}:
- job: MacOS
timeoutInMinutes: 90
+ container: ubuntu_2004_20211215
pool:
name: NetCore-Public
- demands: ImageOverride -equals windows.vs2022.amd64.open
+ demands: ImageOverride -equals build.Ubuntu.1804.amd64.open
variables:
- _TestArgs: /p:ServiceUri=$(_serviceUri) /p:Root_Certificate_Installed=true /p:Client_Certificate_Installed=true /p:SSL_Available=true
- _serviceUri: wcfcoresrv23.westus3.cloudapp.azure.com/WcfService$(_WcfPRServiceId)
@@ -257,13 +258,13 @@ stages:
- template: /eng/UpdatePRService.yml
parameters:
wcfPRServiceId: $(_WcfPRServiceId)
- - script: eng\common\cibuild.cmd
+ - script: eng/common/cibuild.sh
-configuration $(_BuildConfig)
-preparemachine
$(_TestArgs)
/p:Test=false
displayName: MacOS Build
- - powershell: eng\common\build.ps1
+ - powershell: eng/common/build.sh
-configuration $(_BuildConfig)
-prepareMachine
-ci
diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Contract/Service/Contract.Service.SourceGeneration.IntegrationTests.csproj b/src/System.Private.ServiceModel/tests/Scenarios/Contract/Service/Contract.Service.SourceGeneration.IntegrationTests.csproj
new file mode 100644
index 00000000000..076dd5f9e95
--- /dev/null
+++ b/src/System.Private.ServiceModel/tests/Scenarios/Contract/Service/Contract.Service.SourceGeneration.IntegrationTests.csproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+ 11.0
+ true
+
+
+
+
+
+
+
+
+ <_Parameter1>$(EnableSystemServiceModelOperationInvokerGenerator)
+
+
+
+
+
+
+
+
diff --git a/src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Shipped.md b/src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Shipped.md
new file mode 100644
index 00000000000..5f282702bb0
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Shipped.md
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Unshipped.md b/src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Unshipped.md
new file mode 100644
index 00000000000..e02abfc9b0e
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Unshipped.md
@@ -0,0 +1 @@
+
diff --git a/src/System.ServiceModel.BuildTools/src/Extensions.cs b/src/System.ServiceModel.BuildTools/src/Extensions.cs
new file mode 100644
index 00000000000..43edf29f254
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/Extensions.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace System.ServiceModel.BuildTools
+{
+ internal static class SymbolExtensions
+ {
+ public static AttributeData? HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeTypeSymbol)
+ {
+ foreach (var attribute in symbol.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeTypeSymbol))
+ {
+ return attribute;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/Indentor.cs b/src/System.ServiceModel.BuildTools/src/Indentor.cs
new file mode 100644
index 00000000000..051ef071fb9
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/Indentor.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.ServiceModel.BuildTools
+{
+ internal sealed class Indentor
+ {
+ const string ____ = " ";
+ const string ________ = " ";
+ const string ____________ = " ";
+ const string ________________ = " ";
+ const string ____________________ = " ";
+ const string ________________________ = " ";
+ const string ____________________________ = " ";
+ const string ________________________________ = " ";
+ public int Level { get; private set; } = 0;
+ public void Increment()
+ {
+ Level++;
+ }
+
+ public void Decrement()
+ {
+ Level--;
+ }
+
+ public override string ToString() => Level switch
+ {
+ 0 => string.Empty,
+ 1 => ____,
+ 2 => ________,
+ 3 => ____________,
+ 4 => ________________,
+ 5 => ____________________,
+ 6 => ________________________,
+ 7 => ____________________________,
+ 8 => ________________________________,
+ _ => throw new InvalidOperationException()
+ };
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs
new file mode 100644
index 00000000000..764320d1c6c
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs
@@ -0,0 +1,403 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace System.ServiceModel.BuildTools
+{
+ public sealed partial class OperationInvokerGenerator
+ {
+ private sealed class Emitter
+ {
+ private readonly StringBuilder _builder;
+ private readonly OperationInvokerSourceGenerationContext _sourceGenerationContext;
+ private readonly SourceGenerationSpec _generationSpec;
+
+ public Emitter(in OperationInvokerSourceGenerationContext sourceGenerationContext, in SourceGenerationSpec generationSpec)
+ {
+ _sourceGenerationContext = sourceGenerationContext;
+ _generationSpec = generationSpec;
+ _builder = new StringBuilder();
+ }
+
+ public void Emit()
+ {
+ if (_generationSpec.OperationContractSpecs.Length == 0)
+ {
+ return;
+ }
+ _builder.Clear();
+ _builder.AppendLine($$"""
+//
+// Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+//
+#nullable disable
+using System;
+using System.Runtime;
+using System.Threading.Tasks;
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ file sealed class ModuleInitializerAttribute : Attribute { }
+}
+""");
+ int i = 0;
+ foreach (var operationContractSpec in _generationSpec.OperationContractSpecs)
+ {
+ EmitOperationContract(operationContractSpec, i);
+ i++;
+ }
+
+ var indentor = new Indentor();
+ _builder.AppendLine($$"""
+namespace System.ServiceModel.Dispatcher
+{
+ file sealed class OperationInvokerModuleInitializer
+ {
+""");
+ indentor.Increment();
+ indentor.Increment();
+
+ _builder.AppendLine($"{indentor}[System.Runtime.CompilerServices.ModuleInitializer]");
+ _builder.AppendLine($"{indentor}internal static void RegisterOperationInvokers()");
+ _builder.AppendLine($"{indentor}{{");
+ indentor.Increment();
+ for (int j = 0; j < i; j++)
+ {
+ _builder.AppendLine($"{indentor}{GetOperationInvokerTypeName(j)}.RegisterOperationInvoker();");
+ }
+ indentor.Decrement();
+ _builder.AppendLine($"{indentor}}}");
+ indentor.Decrement();
+ _builder.AppendLine($"{indentor}}}");
+ indentor.Decrement();
+ _builder.AppendLine($"{indentor}}}");
+ _builder.AppendLine($$"""
+ namespace System.ServiceModel.Dispatcher
+ {
+ file static class TaskHelpers
+ {
+ // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+ // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+ // return MethodAsync(params).ToApm(callback, state);
+ // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+ // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+ // state object
+ public static Task ToApm(this Task task, AsyncCallback callback, object state)
+ {
+ // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+ // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+ // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+ // This is a performance optimization for this special case.
+ if (task.AsyncState == state)
+ {
+ if (callback != null)
+ {
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var callbackObj = obj as AsyncCallback;
+ callbackObj(antecedent);
+ }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ }
+ return task;
+ }
+
+ // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+ var tcs = new TaskCompletionSource(state);
+ var continuationState = Tuple.Create(tcs, callback);
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var tuple = obj as Tuple, AsyncCallback>;
+ var tcsObj = tuple.Item1;
+ var callbackObj = tuple.Item2;
+ if (antecedent.IsFaulted)
+ {
+ tcsObj.TrySetException(antecedent.Exception.InnerException);
+ }
+ else if (antecedent.IsCanceled)
+ {
+ tcsObj.TrySetCanceled();
+ }
+ else
+ {
+ tcsObj.TrySetResult(antecedent.Result);
+ }
+
+ if (callbackObj != null)
+ {
+ callbackObj(tcsObj.Task);
+ }
+ }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ return tcs.Task;
+ }
+
+ // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+ // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+ // return MethodAsync(params).ToApm(callback, state);
+ // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+ // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+ // state object
+ public static Task ToApm(this Task task, AsyncCallback callback, object state)
+ {
+ // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+ // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+ // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+ // This is a performance optimization for this special case.
+ if (task.AsyncState == state)
+ {
+ if (callback != null)
+ {
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var callbackObj = obj as AsyncCallback;
+ callbackObj(antecedent);
+ }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ }
+ return task;
+ }
+
+ // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+ // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+ // won't be using it. As Task derives from Task, the returned Task is compatible.
+ var tcs = new TaskCompletionSource(state);
+ var continuationState = Tuple.Create(tcs, callback);
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var tuple = obj as Tuple, AsyncCallback>;
+ var tcsObj = tuple.Item1;
+ var callbackObj = tuple.Item2;
+ if (antecedent.IsFaulted)
+ {
+ tcsObj.TrySetException(antecedent.Exception.InnerException);
+ }
+ else if (antecedent.IsCanceled)
+ {
+ tcsObj.TrySetCanceled();
+ }
+ else
+ {
+ tcsObj.TrySetResult(null);
+ }
+
+ if (callbackObj != null)
+ {
+ callbackObj(tcsObj.Task);
+ }
+ }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ return tcs.Task;
+ }
+
+ // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+ // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+ // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+ // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+ // cancelled.
+ public static TResult ToApmEnd(this IAsyncResult iar)
+ {
+ Task task = iar as Task;
+ System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+ return task.GetAwaiter().GetResult();
+ }
+
+ // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+ // async method when the Task does not return result.
+ public static void ToApmEnd(this IAsyncResult iar)
+ {
+ Task task = iar as Task;
+ System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+ task.GetAwaiter().GetResult();
+ }
+ }
+ }
+ """);
+
+
+ _builder.AppendLine("#nullable restore");
+
+ string sourceText = _builder.ToString();
+ _sourceGenerationContext.AddSource("OperationInvoker.g.cs", SourceText.From(sourceText, Encoding.UTF8, SourceHashAlgorithm.Sha256));
+ }
+
+ private void EmitOperationContract(OperationContractSpec operationContractSpec, int index)
+ {
+ var indentor = new Indentor();
+ string operationInvokerTypeName = GetOperationInvokerTypeName(index);
+ _builder.AppendLine($$"""
+namespace System.ServiceModel.Dispatcher
+{
+ // This class is used to invoke the method {{operationContractSpec.Method!.ToDisplayString()}}.
+ file sealed class {{operationInvokerTypeName}} : System.ServiceModel.Dispatcher.IOperationInvoker
+ {
+""");
+ indentor.Increment();
+ indentor.Increment();
+
+ INamedTypeSymbol? returnTypeSymbol = operationContractSpec.Method!.ReturnType as INamedTypeSymbol;
+ bool isGenericTaskReturnType = returnTypeSymbol != null &&
+ returnTypeSymbol.IsGenericType &&
+ returnTypeSymbol.ConstructUnboundGenericType().ToDisplayString() == "System.Threading.Tasks.Task<>";
+ bool isTaskReturnType = operationContractSpec.Method.ReturnType.ToDisplayString() == "System.Threading.Tasks.Task";
+ bool isAsync = isGenericTaskReturnType || isTaskReturnType;
+
+ _builder.AppendLine($$"""
+{{indentor}}public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
+{{indentor}}{
+{{indentor}} return InvokeAsync(instance, inputs).ToApm(callback, state);
+{{indentor}}}
+
+{{indentor}}public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
+{{indentor}}{
+{{indentor}} var (returnValue, outputsValue) = result.ToApmEnd<(object, object[])>();
+{{indentor}} outputs = outputsValue;
+{{indentor}} return returnValue;
+{{indentor}}}
+
+""");
+
+
+ string asyncString = isAsync ? "async " : string.Empty;
+ _builder.AppendLine($"{indentor}private { asyncString }Task<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)");
+ _builder.AppendLine($"{indentor}{{");
+ indentor.Increment();
+
+ int inputParameterCount = 0;
+ int outputParameterCount = 0;
+
+ List<(int, int, IParameterSymbol)> outputParams = new();
+ int i = 0;
+ List invocationParams = new();
+ foreach (var parameter in operationContractSpec.Method.Parameters)
+ {
+ _builder.AppendLine($"{indentor}{parameter.Type.ToDisplayString()} p{i};");
+ if (FlowsIn(parameter))
+ {
+ _builder.AppendLine($"{indentor}p{i} = ({parameter.Type.ToDisplayString()})inputs[{inputParameterCount}];");
+ inputParameterCount++;
+ }
+
+ if (FlowOut(parameter))
+ {
+
+ outputParams.Add((outputParameterCount, i, parameter));
+ outputParameterCount++;
+ }
+
+ invocationParams.Add($"{GetRefKind(parameter)}p{i}");
+ i++;
+ }
+
+ if (isAsync)
+ {
+ if (isTaskReturnType)
+ {
+ _builder.AppendLine($"{indentor}await (({operationContractSpec.Method.ContainingType.ToDisplayString()})instance).{operationContractSpec.Method.Name}({string.Join(", ", invocationParams)});");
+ }
+ else
+ {
+ _builder.AppendLine($"{indentor}var result = await (({operationContractSpec.Method.ContainingType.ToDisplayString()})instance).{operationContractSpec.Method.Name}({string.Join(", ", invocationParams)});");
+ }
+ }
+ else
+ {
+ if (operationContractSpec.Method.ReturnsVoid)
+ {
+ _builder.AppendLine($"{indentor}(({operationContractSpec.Method.ContainingType.ToDisplayString()})instance).{operationContractSpec.Method.Name}({string.Join(", ", invocationParams)});");
+ }
+ else
+ {
+ _builder.AppendLine($"{indentor}var result = (({operationContractSpec.Method.ContainingType.ToDisplayString()})instance).{operationContractSpec.Method.Name}({string.Join(", ", invocationParams)});");
+ }
+ }
+
+ _builder.AppendLine($"{indentor}var outputs = AllocateOutputs();");
+
+ foreach (var (ouputIndex, parameterIndex, parameter) in outputParams)
+ {
+ _builder.AppendLine($"{indentor}outputs[{ouputIndex}] = p{parameterIndex};");
+ }
+
+ if (isAsync)
+ {
+ if (isTaskReturnType)
+ {
+ _builder.AppendLine($"{indentor}return (null, outputs);");
+ }
+ else
+ {
+ _builder.AppendLine($"{indentor}return (result, outputs);");
+ }
+ }
+ else
+ {
+ if (operationContractSpec.Method.ReturnsVoid)
+ {
+ _builder.AppendLine($"{indentor}return Task.FromResult<(object, object[])>((null, outputs));");
+ }
+ else
+ {
+ _builder.AppendLine($"{indentor}return Task.FromResult<(object, object[])>((result, outputs));");
+ }
+ }
+
+ indentor.Decrement();
+ _builder.AppendLine($"{indentor}}}");
+ _builder.AppendLine();
+ _builder.Append($"{indentor}public object[] AllocateInputs() => ");
+ if (inputParameterCount == 0)
+ {
+ _builder.AppendLine("Array.Empty();");
+ }
+ else
+ {
+ _builder.AppendLine($"new object[{inputParameterCount}];");
+ }
+ _builder.AppendLine();
+ _builder.Append($"{indentor}private object[] AllocateOutputs() => ");
+ if (outputParameterCount == 0)
+ {
+ _builder.AppendLine("Array.Empty();");
+ }
+ else
+ {
+ _builder.AppendLine($"new object[{outputParameterCount}];");
+ }
+ _builder.AppendLine();
+
+ _builder.Append($"{indentor}internal static void RegisterOperationInvoker() => ");
+ _builder.AppendLine($"System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker(\"{operationContractSpec.Method.ToDisplayString()}\", new {operationInvokerTypeName}());");
+ indentor.Decrement();
+ _builder.AppendLine($"{indentor}}}");
+
+ indentor.Decrement();
+ _builder.AppendLine($"{indentor}}}");
+
+
+ }
+
+ private static string GetOperationInvokerTypeName(int index) => $"OperationInvoker{index}";
+
+ private static bool FlowsIn(IParameterSymbol parameterSymbol)
+ {
+ return parameterSymbol.RefKind == RefKind.In || parameterSymbol.RefKind == RefKind.Ref || parameterSymbol.RefKind == RefKind.None;
+ }
+
+ private static bool FlowOut(IParameterSymbol parameterSymbol)
+ {
+ return parameterSymbol.RefKind == RefKind.Out || parameterSymbol.RefKind == RefKind.Ref;
+ }
+
+ private static string GetRefKind(IParameterSymbol parameterSymbol)
+ {
+ return parameterSymbol.RefKind switch
+ {
+ RefKind.Ref => "ref ",
+ RefKind.Out => "out ",
+ _ => string.Empty,
+ };
+ }
+ }
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.OperationContractSpec.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.OperationContractSpec.cs
new file mode 100644
index 00000000000..b1c6f89c4e2
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.OperationContractSpec.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace System.ServiceModel.BuildTools;
+
+public sealed partial class OperationInvokerGenerator
+{
+ internal readonly record struct OperationContractSpec(IMethodSymbol? Method)
+ {
+ public IMethodSymbol? Method { get; } = Method;
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs
new file mode 100644
index 00000000000..466f946caff
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs
@@ -0,0 +1,106 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace System.ServiceModel.BuildTools
+{
+ public sealed partial class OperationInvokerGenerator
+ {
+ private sealed class Parser
+ {
+ private readonly Compilation _compilation;
+ private readonly OperationInvokerSourceGenerationContext _context;
+ private readonly INamedTypeSymbol _sSMOperationContractSymbol;
+ private readonly INamedTypeSymbol _sSMServiceContractSymbol;
+
+ public Parser(Compilation compilation, in OperationInvokerSourceGenerationContext context)
+ {
+ _compilation = compilation;
+ _context = context;
+ _sSMOperationContractSymbol = _compilation.GetTypeByMetadataName("System.ServiceModel.OperationContractAttribute")!;
+ _sSMServiceContractSymbol = _compilation.GetTypeByMetadataName("System.ServiceModel.ServiceContractAttribute")!;
+ }
+
+ public SourceGenerationSpec GetGenerationSpec(ImmutableArray methodDeclarationSyntaxes)
+ {
+ var methodSymbols = (from methodDeclarationSyntax in methodDeclarationSyntaxes
+ let semanticModel = _compilation.GetSemanticModel(methodDeclarationSyntax.SyntaxTree)
+ let symbol = semanticModel.GetDeclaredSymbol(methodDeclarationSyntax)
+ where symbol is not null
+ let methodSymbol = symbol
+ select methodSymbol).ToImmutableArray();
+
+ var methods = (from method in methodSymbols
+ where method.HasAttribute(_sSMOperationContractSymbol) is not null
+ let @interface = method.ContainingSymbol
+ where @interface.HasAttribute(_sSMServiceContractSymbol) is not null
+ select method).ToImmutableArray();
+
+ var builder = ImmutableArray.CreateBuilder();
+
+ foreach (var value in methods)
+ {
+ builder.Add(new OperationContractSpec(value));
+ }
+
+ ImmutableArray operationContractSpecs = builder.ToImmutable();
+
+ if (operationContractSpecs.IsEmpty)
+ {
+ return SourceGenerationSpec.None;
+ }
+
+ return new SourceGenerationSpec(operationContractSpecs);
+ }
+
+ internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is MethodDeclarationSyntax methodDeclarationSyntax
+ && methodDeclarationSyntax.AttributeLists.Count > 0
+ && methodDeclarationSyntax.Ancestors().Any(static ancestor => ancestor.IsKind(SyntaxKind.InterfaceDeclaration) && ((InterfaceDeclarationSyntax)ancestor).AttributeLists.Count > 0);
+
+ internal static MethodDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
+ {
+ var methodDeclarationSyntax = (MethodDeclarationSyntax)context.Node;
+ foreach (var attributeList in methodDeclarationSyntax.AttributeLists)
+ {
+ foreach (var attribute in attributeList.Attributes)
+ {
+ var symbolInfo = context.SemanticModel.GetSymbolInfo(attribute);
+ ISymbol? attributeSymbol = symbolInfo.Symbol as IMethodSymbol;
+ // If the symbol is null, let's try to get it from the CandidateSymbols property
+ // NOTE: we do not need this fallback towards CandidateSymbols in the CoreWCF similar implementation
+ if (attributeSymbol == null)
+ {
+ foreach (var symbol in symbolInfo.CandidateSymbols)
+ {
+ if (symbol is IMethodSymbol methodSymbol)
+ {
+ attributeSymbol = methodSymbol;
+ break;
+ }
+ }
+ }
+ if (attributeSymbol == null)
+ {
+ continue;
+ }
+
+ var attributeContainingTypeSymbol = attributeSymbol.ContainingType;
+ var fullName = attributeContainingTypeSymbol.ToDisplayString();
+
+ if (fullName == "System.ServiceModel.OperationContractAttribute")
+ {
+ return methodDeclarationSyntax;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs
new file mode 100644
index 00000000000..e235abec0b8
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace System.ServiceModel.BuildTools
+{
+ [Generator]
+ public sealed partial class OperationInvokerGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(static () => new SyntaxContextReceiver());
+ }
+
+ public void Execute(GeneratorExecutionContext executionContext)
+ {
+ bool enableOperationInvokerGenerator = executionContext.AnalyzerConfigOptions.GlobalOptions
+ .TryGetValue("build_property.EnableSystemServiceModelOperationInvokerGenerator", out string? enableSourceGenerator)
+ && enableSourceGenerator == "true";
+
+ if (!enableOperationInvokerGenerator)
+ {
+ return;
+ }
+
+ if (executionContext.SyntaxContextReceiver is not SyntaxContextReceiver receiver || receiver.MethodDeclarationSyntaxList == null)
+ {
+ // nothing to do yet
+ return;
+ }
+
+ OperationInvokerSourceGenerationContext context = new(executionContext);
+ Parser parser = new(executionContext.Compilation, context);
+ SourceGenerationSpec spec = parser.GetGenerationSpec(receiver.MethodDeclarationSyntaxList.ToImmutableArray());
+ if (spec != SourceGenerationSpec.None)
+ {
+ Emitter emitter = new(context, spec);
+ emitter.Emit();
+ }
+ }
+
+ private sealed class SyntaxContextReceiver : ISyntaxContextReceiver
+ {
+ public List? MethodDeclarationSyntaxList { get; private set; }
+
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ if (Parser.IsSyntaxTargetForGeneration(context.Node))
+ {
+ MethodDeclarationSyntax? methodDeclarationSyntax = Parser.GetSemanticTargetForGeneration(context);
+ if (methodDeclarationSyntax != null)
+ {
+ (MethodDeclarationSyntaxList ??= new List()).Add(methodDeclarationSyntax);
+ }
+ }
+ }
+ }
+
+ internal readonly struct OperationInvokerSourceGenerationContext
+ {
+ private readonly GeneratorExecutionContext _context;
+
+ public OperationInvokerSourceGenerationContext(GeneratorExecutionContext context)
+ {
+ _context = context;
+ }
+
+ public void ReportDiagnostic(Diagnostic diagnostic)
+ {
+ _context.ReportDiagnostic(diagnostic);
+ }
+
+ public void AddSource(string hintName, SourceText sourceText)
+ {
+ _context.AddSource(hintName, sourceText);
+ }
+ }
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs
new file mode 100644
index 00000000000..1ae94f44c37
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+
+namespace System.ServiceModel.BuildTools
+{
+ [Generator]
+ public sealed partial class OperationInvokerGenerator : IIncrementalGenerator
+ {
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ IncrementalValuesProvider methodDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (token, _) => Parser.IsSyntaxTargetForGeneration(token),
+ transform: static (s, _) => Parser.GetSemanticTargetForGeneration(s))
+ .Where(static c => c is not null);
+
+ IncrementalValueProvider<(AnalyzerConfigOptionsProvider ConfigOptions, (Compilation Compilation, ImmutableArray Methods) CompilationAndMethods)> compilationAndMethods =
+ context.AnalyzerConfigOptionsProvider.Combine(context.CompilationProvider.Combine(methodDeclarations.Collect()));
+
+ context.RegisterSourceOutput(compilationAndMethods, (spc, source)
+ => Execute(source.ConfigOptions.GlobalOptions, source.CompilationAndMethods.Compilation, source.CompilationAndMethods.Methods!, spc));
+ }
+
+ private void Execute(AnalyzerConfigOptions analyzerConfigOptions, Compilation compilation, ImmutableArray contextMethods, SourceProductionContext sourceProductionContext)
+ {
+ bool enableOperationInvokerGenerator =
+ analyzerConfigOptions.TryGetValue("build_property.EnableSystemServiceModelOperationInvokerGenerator",
+ out string? enableSourceGenerator) && enableSourceGenerator == "true";
+
+ if (!enableOperationInvokerGenerator)
+ {
+ return;
+ }
+
+ if (contextMethods.IsDefaultOrEmpty)
+ {
+ return;
+ }
+
+ OperationInvokerSourceGenerationContext context = new(sourceProductionContext);
+ Parser parser = new(compilation, context);
+ SourceGenerationSpec spec = parser.GetGenerationSpec(contextMethods);
+ if (spec != SourceGenerationSpec.None)
+ {
+ Emitter emitter = new(context, spec);
+ emitter.Emit();
+ }
+ }
+
+ internal readonly struct OperationInvokerSourceGenerationContext
+ {
+ private readonly SourceProductionContext _context;
+
+ public OperationInvokerSourceGenerationContext(SourceProductionContext context)
+ {
+ _context = context;
+ }
+
+ public void ReportDiagnostic(Diagnostic diagnostic)
+ {
+ _context.ReportDiagnostic(diagnostic);
+ }
+
+ public void AddSource(string hintName, SourceText sourceText)
+ {
+ _context.AddSource(hintName, sourceText);
+ }
+ }
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.SourceGenerationSpec.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.SourceGenerationSpec.cs
new file mode 100644
index 00000000000..712f2039b4d
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.SourceGenerationSpec.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace System.ServiceModel.BuildTools;
+
+public sealed partial class OperationInvokerGenerator
+{
+ internal readonly record struct SourceGenerationSpec(in ImmutableArray OperationContractSpecs)
+ {
+ public ImmutableArray OperationContractSpecs { get; } = OperationContractSpecs;
+
+ public static readonly SourceGenerationSpec None = new();
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn3.11.csproj b/src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn3.11.csproj
new file mode 100644
index 00000000000..5c65bce6549
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn3.11.csproj
@@ -0,0 +1,24 @@
+
+
+ netstandard2.0
+ True
+ 11.0
+ enable
+ false
+ true
+ Microsoft.ServiceModel.BuildTools
+ $(WcfAssemblyVersion)
+ Microsoft
+ System.ServiceModel.BuildTools.Roslyn3.11
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn4.0.csproj b/src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn4.0.csproj
new file mode 100644
index 00000000000..1b75be7a417
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn4.0.csproj
@@ -0,0 +1,24 @@
+
+
+ netstandard2.0
+ True
+ 11.0
+ enable
+ false
+ true
+ Microsoft.ServiceModel.BuildTools
+ $(WcfAssemblyVersion)
+ Microsoft
+ System.ServiceModel.BuildTools.Roslyn4.0
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/src/System.ServiceModel.BuildTools/tests/CSharpAnalyzerVerifier.cs b/src/System.ServiceModel.BuildTools/tests/CSharpAnalyzerVerifier.cs
new file mode 100644
index 00000000000..1c92005a0e0
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/tests/CSharpAnalyzerVerifier.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Immutable;
+using System.ServiceModel.BuildTools.Tests;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+public static class CSharpAnalyzerVerifier where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ public class Test : CSharpAnalyzerTest
+ {
+ public Test()
+ {
+ ReferenceAssemblies = ReferenceAssembliesHelper.Default.Value;
+ TestState.AdditionalReferences.Add(typeof(System.ServiceModel.ServiceContractAttribute).Assembly);
+ }
+
+ protected override CompilationOptions CreateCompilationOptions()
+ {
+ var compilationOptions = base.CreateCompilationOptions();
+ return compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler()));
+ }
+
+ public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default;
+
+ private static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = { "/warnaserror:nullable" };
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
+ var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+
+ return nullableWarnings;
+ }
+
+ protected override ParseOptions CreateParseOptions()
+ => ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
+
+ protected override bool IsCompilerDiagnosticIncluded(Diagnostic diagnostic, CompilerDiagnostics compilerDiagnostics) => false;
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs b/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs
new file mode 100644
index 00000000000..5c85c641de0
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+
+public static class CSharpGeneratorVerifier
+#if ROSLYN4_0_OR_GREATER
+ where TSourceGenerator : IIncrementalGenerator, new()
+#else
+ where TSourceGenerator : ISourceGenerator, new()
+#endif
+{
+ public class Test : CSharpSourceGeneratorTest
+ {
+ public Test()
+ {
+ ReferenceAssemblies = System.ServiceModel.BuildTools.Tests.ReferenceAssembliesHelper.Default.Value;
+ TestState.AdditionalReferences.Add(typeof(System.ServiceModel.ServiceContractAttribute).Assembly);
+ }
+
+ protected override CompilationOptions CreateCompilationOptions()
+ {
+ var compilationOptions = base.CreateCompilationOptions();
+ return compilationOptions.WithSpecificDiagnosticOptions(
+ compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler()));
+ }
+
+ public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default;
+
+ private static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = { "/warnaserror:nullable" };
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);
+ var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+
+ return nullableWarnings;
+ }
+
+ protected override ParseOptions CreateParseOptions()
+ => ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);
+
+#if ROSLYN4_0_OR_GREATER
+ protected override IEnumerable GetSourceGenerators()
+ {
+ yield return new TSourceGenerator().AsSourceGenerator();
+ }
+#else
+ protected override IEnumerable GetSourceGenerators()
+ {
+ yield return new TSourceGenerator();
+ }
+#endif
+
+ protected override bool IsCompilerDiagnosticIncluded(Diagnostic diagnostic, CompilerDiagnostics compilerDiagnostics) => false;
+ }
+}
+
diff --git a/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs b/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs
new file mode 100644
index 00000000000..312af28e841
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs
@@ -0,0 +1,1943 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Infrastructure.Common;
+using Microsoft.CodeAnalysis.Text;
+using Xunit;
+using VerifyGenerator = CSharpGeneratorVerifier;
+
+namespace System.ServiceModel.BuildTools.Tests
+{
+ public class OperationInvokerGeneratorTests
+ {
+ [WcfFact]
+ public async Task DefaultNonOptInTest()
+ {
+ var test = new VerifyGenerator.Test
+ {
+ TestState =
+ {
+ Sources =
+ {
+@$"
+namespace MyProject
+{{
+ [System.ServiceModel.ServiceContract]
+ public interface IIdentityService
+ {{
+ [System.ServiceModel.OperationContract]
+ string Echo(string input, ref bool b, out int i);
+ }}
+
+ public partial class IdentityService : IIdentityService
+ {{
+ public string Echo(string input, ref bool b, out int i)
+ {{
+ i = 10;
+ return input;
+ }}
+ }}
+}}
+"
+ }
+ }
+ };
+
+ await test.RunAsync();
+ }
+
+ [WcfFact]
+ public async Task SimpleTest()
+ {
+ var test = new VerifyGenerator.Test
+ {
+ TestState =
+ {
+ Sources =
+ {
+@$"
+namespace MyProject
+{{
+ [System.ServiceModel.ServiceContract]
+ public interface IIdentityService
+ {{
+ [System.ServiceModel.OperationContract]
+ string Echo(string input, ref bool b, out int i);
+ }}
+
+ public partial class IdentityService : IIdentityService
+ {{
+ public string Echo(string input, ref bool b, out int i)
+ {{
+ i = 10;
+ return input;
+ }}
+ }}
+}}
+"
+ },
+ AnalyzerConfigFiles =
+ {
+ (typeof(OperationInvokerGenerator),"/.globalconfig", """
+is_global = true
+build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+""")
+ },
+ GeneratedSources =
+ {
+ (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+//
+// Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+//
+#nullable disable
+using System;
+using System.Runtime;
+using System.Threading.Tasks;
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ file sealed class ModuleInitializerAttribute : Attribute { }
+}
+namespace System.ServiceModel.Dispatcher
+{
+ // This class is used to invoke the method MyProject.IIdentityService.Echo(string, ref bool, out int).
+ file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+ {
+ public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
+ {
+ return InvokeAsync(instance, inputs).ToApm(callback, state);
+ }
+
+ public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
+ {
+ var (returnValue, outputsValue) = result.ToApmEnd<(object, object[])>();
+ outputs = outputsValue;
+ return returnValue;
+ }
+
+ private Task<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+ {
+ string p0;
+ p0 = (string)inputs[0];
+ bool p1;
+ p1 = (bool)inputs[1];
+ int p2;
+ var result = ((MyProject.IIdentityService)instance).Echo(p0, ref p1, out p2);
+ var outputs = AllocateOutputs();
+ outputs[0] = p1;
+ outputs[1] = p2;
+ return Task.FromResult<(object, object[])>((result, outputs));
+ }
+
+ public object[] AllocateInputs() => new object[2];
+
+ private object[] AllocateOutputs() => new object[2];
+
+ internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(string, ref bool, out int)", new OperationInvoker0());
+ }
+}
+namespace System.ServiceModel.Dispatcher
+{
+ file sealed class OperationInvokerModuleInitializer
+ {
+ [System.Runtime.CompilerServices.ModuleInitializer]
+ internal static void RegisterOperationInvokers()
+ {
+ OperationInvoker0.RegisterOperationInvoker();
+ }
+ }
+}
+namespace System.ServiceModel.Dispatcher
+{
+ file static class TaskHelpers
+ {
+ // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+ // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+ // return MethodAsync(params).ToApm(callback, state);
+ // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+ // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+ // state object
+ public static Task ToApm(this Task task, AsyncCallback callback, object state)
+ {
+ // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+ // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+ // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+ // This is a performance optimization for this special case.
+ if (task.AsyncState == state)
+ {
+ if (callback != null)
+ {
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var callbackObj = obj as AsyncCallback;
+ callbackObj(antecedent);
+ }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ }
+ return task;
+ }
+
+ // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+ var tcs = new TaskCompletionSource(state);
+ var continuationState = Tuple.Create(tcs, callback);
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var tuple = obj as Tuple, AsyncCallback>;
+ var tcsObj = tuple.Item1;
+ var callbackObj = tuple.Item2;
+ if (antecedent.IsFaulted)
+ {
+ tcsObj.TrySetException(antecedent.Exception.InnerException);
+ }
+ else if (antecedent.IsCanceled)
+ {
+ tcsObj.TrySetCanceled();
+ }
+ else
+ {
+ tcsObj.TrySetResult(antecedent.Result);
+ }
+
+ if (callbackObj != null)
+ {
+ callbackObj(tcsObj.Task);
+ }
+ }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ return tcs.Task;
+ }
+
+ // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+ // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+ // return MethodAsync(params).ToApm(callback, state);
+ // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+ // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+ // state object
+ public static Task ToApm(this Task task, AsyncCallback callback, object state)
+ {
+ // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+ // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+ // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+ // This is a performance optimization for this special case.
+ if (task.AsyncState == state)
+ {
+ if (callback != null)
+ {
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var callbackObj = obj as AsyncCallback;
+ callbackObj(antecedent);
+ }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ }
+ return task;
+ }
+
+ // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+ // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+ // won't be using it. As Task derives from Task, the returned Task is compatible.
+ var tcs = new TaskCompletionSource(state);
+ var continuationState = Tuple.Create(tcs, callback);
+ task.ContinueWith((antecedent, obj) =>
+ {
+ var tuple = obj as Tuple, AsyncCallback>;
+ var tcsObj = tuple.Item1;
+ var callbackObj = tuple.Item2;
+ if (antecedent.IsFaulted)
+ {
+ tcsObj.TrySetException(antecedent.Exception.InnerException);
+ }
+ else if (antecedent.IsCanceled)
+ {
+ tcsObj.TrySetCanceled();
+ }
+ else
+ {
+ tcsObj.TrySetResult(null);
+ }
+
+ if (callbackObj != null)
+ {
+ callbackObj(tcsObj.Task);
+ }
+ }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+ return tcs.Task;
+ }
+
+ // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+ // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+ // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+ // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+ // cancelled.
+ public static TResult ToApmEnd(this IAsyncResult iar)
+ {
+ Task task = iar as Task;
+ System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+ return task.GetAwaiter().GetResult();
+ }
+
+ // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+ // async method when the Task does not return result.
+ public static void ToApmEnd(this IAsyncResult iar)
+ {
+ Task task = iar as Task;
+ System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+ task.GetAwaiter().GetResult();
+ }
+ }
+}
+#nullable restore
+
+""", Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+ },
+ },
+ };
+
+ await test.RunAsync();
+ }
+
+// [WcfFact]
+// public async Task MultipleOperationTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// string Echo(string input, ref bool b, out int i);
+// [System.ServiceModel.OperationContract]
+// string Echo2(string input, ref bool b, out int i);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public string Echo(string input, ref bool b, out int i)
+// {{
+// i = 10;
+// return input;
+// }}
+// public string Echo2(string input, ref bool b, out int i)
+// {{
+// i = 10;
+// return input;
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(string, ref bool, out int).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string p0;
+// p0 = (string)inputs[0];
+// bool p1;
+// p1 = (bool)inputs[1];
+// int p2;
+// var result = ((MyProject.IIdentityService)instance).Echo(p0, ref p1, out p2);
+// var outputs = AllocateOutputs();
+// outputs[0] = p1;
+// outputs[1] = p2;
+// return new ValueTask<(object, object[])>((result, outputs));
+// }
+//
+// public object[] AllocateInputs() => new object[2];
+//
+// private object[] AllocateOutputs() => new object[2];
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(string, ref bool, out int)", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo2(string, ref bool, out int).
+// file sealed class OperationInvoker1 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string p0;
+// p0 = (string)inputs[0];
+// bool p1;
+// p1 = (bool)inputs[1];
+// int p2;
+// var result = ((MyProject.IIdentityService)instance).Echo2(p0, ref p1, out p2);
+// var outputs = AllocateOutputs();
+// outputs[0] = p1;
+// outputs[1] = p2;
+// return new ValueTask<(object, object[])>((result, outputs));
+// }
+//
+// public object[] AllocateInputs() => new object[2];
+//
+// private object[] AllocateOutputs() => new object[2];
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo2(string, ref bool, out int)", new OperationInvoker1());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// OperationInvoker1.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+//
+// [WcfFact]
+// public async Task IntegratedTypesAsDotNetTypesTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// System.String Echo(System.String input, ref System.Boolean b, out System.Int32 i);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public System.String Echo(System.String input, ref System.Boolean b, out System.Int32 i)
+// {{
+// i = 10;
+// return input;
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(string, ref bool, out int).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string p0;
+// p0 = (string)inputs[0];
+// bool p1;
+// p1 = (bool)inputs[1];
+// int p2;
+// var result = ((MyProject.IIdentityService)instance).Echo(p0, ref p1, out p2);
+// var outputs = AllocateOutputs();
+// outputs[0] = p1;
+// outputs[1] = p2;
+// return new ValueTask<(object, object[])>((result, outputs));
+// }
+//
+// public object[] AllocateInputs() => new object[2];
+//
+// private object[] AllocateOutputs() => new object[2];
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(string, ref bool, out int)", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+//
+// [WcfFact]
+// public async Task GenericsTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// string Echo(System.Collections.Generic.List inputs);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public string Echo(System.Collections.Generic.List inputs)
+// {{
+// return input[0];
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(System.Collections.Generic.List).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// System.Collections.Generic.List p0;
+// p0 = (System.Collections.Generic.List)inputs[0];
+// var result = ((MyProject.IIdentityService)instance).Echo(p0);
+// var outputs = AllocateOutputs();
+// return new ValueTask<(object, object[])>((result, outputs));
+// }
+//
+// public object[] AllocateInputs() => new object[1];
+//
+// private object[] AllocateOutputs() => Array.Empty();
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(System.Collections.Generic.List)", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+//
+// [WcfFact]
+// public async Task ParamsArrayTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// string Echo(params string[] inputs);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public string Echo(params string[] inputs)
+// {{
+// return input[0];
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(params string[]).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string[] p0;
+// p0 = (string[])inputs[0];
+// var result = ((MyProject.IIdentityService)instance).Echo(p0);
+// var outputs = AllocateOutputs();
+// return new ValueTask<(object, object[])>((result, outputs));
+// }
+//
+// public object[] AllocateInputs() => new object[1];
+//
+// private object[] AllocateOutputs() => Array.Empty();
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(params string[])", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+//
+// [WcfFact]
+// public async Task ReturnsVoidTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// void Echo(string input);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public void Echo(string input)
+// {{
+//
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(string).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string p0;
+// p0 = (string)inputs[0];
+// ((MyProject.IIdentityService)instance).Echo(p0);
+// var outputs = AllocateOutputs();
+// return new ValueTask<(object, object[])>((null, outputs));
+// }
+//
+// public object[] AllocateInputs() => new object[1];
+//
+// private object[] AllocateOutputs() => Array.Empty();
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(string)", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+//
+// [WcfFact]
+// public async Task ReturnsTaskTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// System.Threading.Tasks.Task Echo(string input);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public System.Threading.Tasks.Task Echo(string input)
+// {{
+// return System.Threading.Tasks.Task.CompletedTask;
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(string).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public async ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string p0;
+// p0 = (string)inputs[0];
+// await ((MyProject.IIdentityService)instance).Echo(p0);
+// var outputs = AllocateOutputs();
+// return (null, outputs);
+// }
+//
+// public object[] AllocateInputs() => new object[1];
+//
+// private object[] AllocateOutputs() => Array.Empty();
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(string)", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+//
+// [WcfFact]
+// public async Task ReturnsGenericTaskTest()
+// {
+// var test = new VerifyGenerator.Test
+// {
+// TestState =
+// {
+// Sources =
+// {
+// @$"
+// namespace MyProject
+// {{
+// [System.ServiceModel.ServiceContract]
+// public interface IIdentityService
+// {{
+// [System.ServiceModel.OperationContract]
+// System.Threading.Tasks.Task Echo(string input);
+// }}
+//
+// public partial class IdentityService : IIdentityService
+// {{
+// public System.Threading.Tasks.Task Echo(string input)
+// {{
+// return System.Threading.Tasks.Task.FromResult(input);
+// }}
+// }}
+// }}
+// "
+// },
+// AnalyzerConfigFiles =
+// {
+// (typeof(OperationInvokerGenerator),"/.globalconfig", """
+// is_global = true
+// build_property.EnableSystemServiceModelOperationInvokerGenerator = true
+// """)
+// },
+// GeneratedSources =
+// {
+// (typeof(OperationInvokerGenerator), "OperationInvoker.g.cs", SourceText.From($$"""
+// //
+// // Generated by the System.ServiceModel.BuildTools.OperationInvokerGenerator source generator. DO NOT EDIT!
+// //
+// #nullable disable
+// using System;
+// using System.Threading.Tasks;
+// namespace System.Runtime.CompilerServices
+// {
+// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+// file sealed class ModuleInitializerAttribute : Attribute { }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// // This class is used to invoke the method MyProject.IIdentityService.Echo(string).
+// file sealed class OperationInvoker0 : System.ServiceModel.Dispatcher.IOperationInvoker
+// {
+// public async ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
+// {
+// string p0;
+// p0 = (string)inputs[0];
+// var result = await ((MyProject.IIdentityService)instance).Echo(p0);
+// var outputs = AllocateOutputs();
+// return (result, outputs);
+// }
+//
+// public object[] AllocateInputs() => new object[1];
+//
+// private object[] AllocateOutputs() => Array.Empty();
+//
+// internal static void RegisterOperationInvoker() => System.ServiceModel.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker("MyProject.IIdentityService.Echo(string)", new OperationInvoker0());
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file sealed class OperationInvokerModuleInitializer
+// {
+// [System.Runtime.CompilerServices.ModuleInitializer]
+// internal static void RegisterOperationInvokers()
+// {
+// OperationInvoker0.RegisterOperationInvoker();
+// }
+// }
+// }
+// namespace System.ServiceModel.Dispatcher
+// {
+// file static class TaskHelpers
+// {
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(antecedent.Result);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method when implementing an APM wrapper around a Task based async method which returns a result.
+// // In the BeginMethod method, you would call use ToApm to wrap a call to MethodAsync:
+// // return MethodAsync(params).ToApm(callback, state);
+// // In the EndMethod, you would use ToApmEnd to ensure the correct exception handling
+// // This will handle throwing exceptions in the correct place and ensure the IAsyncResult contains the provided
+// // state object
+// public static Task ToApm(this Task task, AsyncCallback callback, object state)
+// {
+// // When using APM, the returned IAsyncResult must have the passed in state object stored in AsyncState. This
+// // is so the callback can regain state. If the incoming task already holds the state object, there's no need
+// // to create a TaskCompletionSource to ensure the returned (IAsyncResult)Task has the right state object.
+// // This is a performance optimization for this special case.
+// if (task.AsyncState == state)
+// {
+// if (callback != null)
+// {
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var callbackObj = obj as AsyncCallback;
+// callbackObj(antecedent);
+// }, callback, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// }
+// return task;
+// }
+//
+// // Need to create a TaskCompletionSource so that the returned Task object has the correct AsyncState value.
+// // As we intend to create a task with no Result value, we don't care what result type the TCS holds as we
+// // won't be using it. As Task derives from Task, the returned Task is compatible.
+// var tcs = new TaskCompletionSource(state);
+// var continuationState = Tuple.Create(tcs, callback);
+// task.ContinueWith((antecedent, obj) =>
+// {
+// var tuple = obj as Tuple, AsyncCallback>;
+// var tcsObj = tuple.Item1;
+// var callbackObj = tuple.Item2;
+// if (antecedent.IsFaulted)
+// {
+// tcsObj.TrySetException(antecedent.Exception.InnerException);
+// }
+// else if (antecedent.IsCanceled)
+// {
+// tcsObj.TrySetCanceled();
+// }
+// else
+// {
+// tcsObj.TrySetResult(null);
+// }
+//
+// if (callbackObj != null)
+// {
+// callbackObj(tcsObj.Task);
+// }
+// }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default);
+// return tcs.Task;
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task returns a result. By using task.GetAwaiter.GetResult(), the exception
+// // handling conventions are the same as when await'ing a task, i.e. this throws the first exception
+// // and doesn't wrap it in an AggregateException. It also throws the right exception if the task was
+// // cancelled.
+// public static TResult ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// return task.GetAwaiter().GetResult();
+// }
+//
+// // Helper method to implement the End method of an APM method pair which is wrapping a Task based
+// // async method when the Task does not return result.
+// public static void ToApmEnd(this IAsyncResult iar)
+// {
+// Task task = iar as Task;
+// System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task");
+// task.GetAwaiter().GetResult();
+// }
+// }
+// }
+// #nullable restore
+//
+// """, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
+// },
+// },
+// };
+//
+// await test.RunAsync();
+// }
+ }
+}
diff --git a/src/System.ServiceModel.BuildTools/tests/ReferenceAssembliesHelper.cs b/src/System.ServiceModel.BuildTools/tests/ReferenceAssembliesHelper.cs
new file mode 100644
index 00000000000..99d84d5007b
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/tests/ReferenceAssembliesHelper.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace System.ServiceModel.BuildTools.Tests;
+
+internal static class ReferenceAssembliesHelper
+{
+ public static readonly Lazy Default = new(() =>
+ {
+ var packages = new[]
+ {
+ new PackageIdentity("System.Security.Cryptography.Xml", "6.0.1"),
+ new PackageIdentity("Microsoft.Extensions.ObjectPool", "6.0.16"),
+ }.ToImmutableArray();
+ return ReferenceAssemblies.Default.AddPackages(packages);
+ });
+}
diff --git a/src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn3.11.Tests.csproj b/src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn3.11.Tests.csproj
new file mode 100644
index 00000000000..6850ba4113e
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn3.11.Tests.csproj
@@ -0,0 +1,39 @@
+
+
+ $(UnitTestTargetFrameworks)
+ false
+ true
+ false
+ System.ServiceModel.BuildTools.Tests
+ 11.0
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn4.0.Tests.csproj b/src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn4.0.Tests.csproj
new file mode 100644
index 00000000000..3c151ece01c
--- /dev/null
+++ b/src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn4.0.Tests.csproj
@@ -0,0 +1,36 @@
+
+
+ $(UnitTestTargetFrameworks)
+ false
+ true
+ false
+ System.ServiceModel.BuildTools.Tests
+ 11.0
+ true
+ false
+ false
+ false
+ $(DefineConstants);ROSLYN4_0_OR_GREATER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/System.ServiceModel.Primitives/src/EnableSystemServiceModelOperationInvokerGeneratorAttribute.cs b/src/System.ServiceModel.Primitives/src/EnableSystemServiceModelOperationInvokerGeneratorAttribute.cs
new file mode 100644
index 00000000000..526b1bf2284
--- /dev/null
+++ b/src/System.ServiceModel.Primitives/src/EnableSystemServiceModelOperationInvokerGeneratorAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.ServiceModel
+{
+ [AttributeUsage(AttributeTargets.Assembly)]
+ public sealed class EnableSystemServiceModelOperationInvokerGeneratorAttribute : Attribute
+ {
+ public string Value { get; }
+
+ public EnableSystemServiceModelOperationInvokerGeneratorAttribute(string value)
+ {
+ Value = value;
+ }
+ }
+}
diff --git a/src/System.ServiceModel.Primitives/src/System.ServiceModel.Primitives.csproj b/src/System.ServiceModel.Primitives/src/System.ServiceModel.Primitives.csproj
index 2a13dae411f..b26dc802991 100644
--- a/src/System.ServiceModel.Primitives/src/System.ServiceModel.Primitives.csproj
+++ b/src/System.ServiceModel.Primitives/src/System.ServiceModel.Primitives.csproj
@@ -49,6 +49,22 @@
+
+
+
+
+
+ true
+ analyzers/dotnet/roslyn4.0/cs
+ false
+
+
+ true
+ analyzers/dotnet/roslyn3.11/cs
+ false
+
+
+
diff --git a/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs b/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs
new file mode 100644
index 00000000000..5ff5ec05064
--- /dev/null
+++ b/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System.ServiceModel.Dispatcher
+{
+ public class DispatchOperationRuntimeHelpers
+ {
+ internal static Dictionary OperationInvokers { get; } = new();
+
+ public static void RegisterOperationInvoker(string key, IOperationInvoker invoker)
+ {
+ OperationInvokers[key] = invoker;
+ }
+
+ internal static string GetKey(MethodInfo method)
+ {
+ StringBuilder stringBuilder = new($"{method.DeclaringType.FullName}.{method.Name}(");
+ stringBuilder.Append(string.Join(", ", method.GetParameters().Select(GetParameterString)));
+ stringBuilder.Replace("+", ".");
+ stringBuilder.Append(")");
+ string result = stringBuilder.ToString();
+ return result;
+ }
+
+ private static string GetParameterString(ParameterInfo p)
+ {
+ StringBuilder sb = new();
+ Type parameterType = p.ParameterType;
+ if (p.IsOut)
+ {
+ sb.Append("out ");
+ parameterType = p.ParameterType.GetElementType();
+ }
+ else if (p.ParameterType.IsByRef)
+ {
+ sb.Append("ref ");
+ parameterType = p.ParameterType.GetElementType();
+ }
+
+ if (p.IsDefined(typeof(ParamArrayAttribute)))
+ {
+ sb.Append("params ");
+ }
+
+ string parameterName = GetParameterFullName(parameterType);
+ sb.Append(parameterName);
+ return sb.ToString();
+ }
+
+ private static string GetParameterFullName(Type type)
+ {
+ if (type.IsGenericType)
+ {
+ StringBuilder sb = new();
+ sb.Append(type.FullName.Substring(0, type.FullName.IndexOf('`')));
+ sb.Append("<");
+ sb.Append(string.Join(", ", type.GetGenericArguments().Select(GetParameterFullName)));
+ sb.Append(">");
+ return sb.ToString();
+ }
+
+ if (type.IsArray)
+ {
+ return GetParameterFullName(type.GetElementType()) + "[]";
+ }
+
+ string result;
+ if (RuntimeFeature.IsDynamicCodeSupported)
+ {
+ result = s_runtimeIntegratedTypesMap.TryGetValue(type.TypeHandle.Value, out result)
+ ? result
+ : type.FullName;
+ }
+ else
+ {
+ result = s_integratedTypesMap.TryGetValue(type, out result)
+ ? result
+ : type.FullName;
+ }
+
+ return result;
+ }
+
+ private static readonly Dictionary s_integratedTypesMap = new()
+ {
+ { typeof(bool), "bool" },
+ { typeof(byte), "byte" },
+ { typeof(sbyte), "sbyte" },
+ { typeof(char), "char" },
+ { typeof(decimal), "decimal" },
+ { typeof(double), "double" },
+ { typeof(float), "float" },
+ { typeof(int), "int" },
+ { typeof(uint), "uint" },
+ { typeof(nint), "nint" },
+ { typeof(nuint), "nuint" },
+ { typeof(long), "long" },
+ { typeof(ulong), "ulong" },
+ { typeof(short), "short" },
+ { typeof(ushort), "ushort" },
+ { typeof(object), "object" },
+ { typeof(string), "string" }
+ };
+
+ private static readonly Dictionary s_runtimeIntegratedTypesMap = s_integratedTypesMap
+ .ToDictionary(static kvp => kvp.Key.TypeHandle.Value, kvp => kvp.Value);
+ }
+}