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); + } +}