From fd8ec8870c840f624603497c525ae9069b7bcc42 Mon Sep 17 00:00:00 2001 From: g7ed6e Date: Sat, 28 Oct 2023 10:50:51 +0200 Subject: [PATCH 1/5] Add OperationInvoker source generator --- System.ServiceModel.sln | 26 + .../src/AnalyzerReleases.Shipped.md | 1 + .../src/AnalyzerReleases.Unshipped.md | 1 + .../src/Extensions.cs | 126 +++ .../src/Indentor.cs | 41 + .../src/OperationInvokerGenerator.Emitter.cs | 246 +++++ ...nInvokerGenerator.OperationContractSpec.cs | 14 + .../src/OperationInvokerGenerator.Parser.cs | 107 +++ .../OperationInvokerGenerator.Roslyn3.11.cs | 87 ++ .../OperationInvokerGenerator.Roslyn4.0.cs | 79 ++ ...onInvokerGenerator.SourceGenerationSpec.cs | 16 + ....ServiceModel.BuildTools.Roslyn3.11.csproj | 24 + ...m.ServiceModel.BuildTools.Roslyn4.0.csproj | 24 + .../tests/CSharpAnalyzerVerifier.cs | 46 + .../tests/CSharpGeneratorVerifier.cs | 54 ++ .../tests/CSharpIncrementalGeneratorTest`2.cs | 42 + .../tests/IncrementalGeneratorTest`1.cs | 151 ++++ .../tests/OperationInvokerGeneratorTests.cs | 842 ++++++++++++++++++ .../tests/ReferenceAssembliesHelper.cs | 20 + ...ceModel.BuildTools.Roslyn3.11.Tests.csproj | 39 + ...iceModel.BuildTools.Roslyn4.0.Tests.csproj | 36 + ...ModelOperationInvokerGeneratorAttribute.cs | 16 + .../src/System.ServiceModel.Primitives.csproj | 16 + .../DispatchOperationRuntimeHelpers.cs | 24 + 24 files changed, 2078 insertions(+) create mode 100644 src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Shipped.md create mode 100644 src/System.ServiceModel.BuildTools/src/AnalyzerReleases.Unshipped.md create mode 100644 src/System.ServiceModel.BuildTools/src/Extensions.cs create mode 100644 src/System.ServiceModel.BuildTools/src/Indentor.cs create mode 100644 src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs create mode 100644 src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.OperationContractSpec.cs create mode 100644 src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs create mode 100644 src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs create mode 100644 src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs create mode 100644 src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.SourceGenerationSpec.cs create mode 100644 src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn3.11.csproj create mode 100644 src/System.ServiceModel.BuildTools/src/System.ServiceModel.BuildTools.Roslyn4.0.csproj create mode 100644 src/System.ServiceModel.BuildTools/tests/CSharpAnalyzerVerifier.cs create mode 100644 src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs create mode 100644 src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs create mode 100644 src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs create mode 100644 src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs create mode 100644 src/System.ServiceModel.BuildTools/tests/ReferenceAssembliesHelper.cs create mode 100644 src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn3.11.Tests.csproj create mode 100644 src/System.ServiceModel.BuildTools/tests/System.ServiceModel.BuildTools.Roslyn4.0.Tests.csproj create mode 100644 src/System.ServiceModel.Primitives/src/EnableSystemServiceModelOperationInvokerGeneratorAttribute.cs create mode 100644 src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs diff --git a/System.ServiceModel.sln b/System.ServiceModel.sln index 64a9fe87fdd..ccf920eea2b 100644 --- a/System.ServiceModel.sln +++ b/System.ServiceModel.sln @@ -89,6 +89,14 @@ 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -259,6 +267,22 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -296,6 +320,8 @@ 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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E0638FAC-BA6B-4E18-BAE6-468C3191BE58} 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..2366009b255 --- /dev/null +++ b/src/System.ServiceModel.BuildTools/src/Extensions.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.ServiceModel.BuildTools +{ + internal static class MethodSymbolExtensions + { + public static bool? IsGeneratedCode(this IMethodSymbol methodSymbol) + => methodSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath.EndsWith(".g.cs"); + + public static bool IsMatchingUserProvidedMethod(this IMethodSymbol methodSymbol, IMethodSymbol userProvidedMethodSymbol, INamedTypeSymbol? coreWCFInjectedAttribute, INamedTypeSymbol? fromServicesAttribute) + { + int parameterFound = 0; + if (methodSymbol.Name != userProvidedMethodSymbol.Name) + { + return false; + } + + var parameters = methodSymbol.Parameters; + + for (int i = 0,j = 0; i < userProvidedMethodSymbol.Parameters.Length; i++) + { + IParameterSymbol parameterSymbol = userProvidedMethodSymbol.Parameters[i]; + if (parameterSymbol.GetOneAttributeOf(coreWCFInjectedAttribute, fromServicesAttribute) is not null) + { + continue; + } + + if (parameterSymbol.IsMatchingParameter(parameters[j])) + { + j++; + parameterFound++; + } + } + + return parameterFound == parameters.Length; + } + } + + internal static class ParameterSymbolExtensions + { + public static bool IsMatchingParameter(this IParameterSymbol symbol, IParameterSymbol parameterSymbol) + => SymbolEqualityComparer.Default.Equals(symbol.Type, parameterSymbol.Type); + } + + internal static class SymbolExtensions + { + public static AttributeData? GetOneAttributeOf(this ISymbol symbol, params INamedTypeSymbol?[] attributeTypeSymbols) + { + if (attributeTypeSymbols.Length == 0) + { + return null; + } + + foreach (var attribute in symbol.GetAttributes()) + { + foreach (var namedTypeSymbol in attributeTypeSymbols.Where(static s => s is not null)) + { + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, namedTypeSymbol)) + { + return attribute; + } + } + } + + return null; + } + + public static bool HasOneAttributeInheritFrom(this ISymbol symbol, params INamedTypeSymbol?[] attributeTypeSymbols) + { + if (attributeTypeSymbols.Length == 0) + { + return false; + } + + foreach (var attribute in symbol.GetAttributes()) + { + foreach (var @interface in attribute.AttributeClass!.AllInterfaces) + { + foreach (var namedTypeSymbol in attributeTypeSymbols.Where(static s => s is not null)) + { + if (SymbolEqualityComparer.Default.Equals(@interface, namedTypeSymbol)) + { + return true; + } + } + } + } + + return false; + } + } + + internal static class NamedTypeSymbolExtensions + { + public static bool IsPartial(this INamedTypeSymbol namedTypeSymbol, out INamedTypeSymbol parentType) + { + bool result = namedTypeSymbol.DeclaringSyntaxReferences.Select(static s => s.GetSyntax()).OfType().All(static c => c.Modifiers.Any(static m => m.IsKind(SyntaxKind.PartialKeyword))); + if (result && namedTypeSymbol.ContainingType != null) + { + return namedTypeSymbol.ContainingType.IsPartial(out parentType); + } + parentType = namedTypeSymbol; + return result; + } + } + + internal static class TypedConstantExtensions + { + public static string ToSafeCSharpString(this TypedConstant typedConstant) + { + if (typedConstant.Kind == TypedConstantKind.Array) + { + return $"new [] {typedConstant.ToCSharpString()}"; + } + + return typedConstant.ToCSharpString(); + + } + } +} 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..e54bde86694 --- /dev/null +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs @@ -0,0 +1,246 @@ +// 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.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("#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; + + string asyncString = isAsync ? "async " : string.Empty; + _builder.AppendLine($"{indentor}public { asyncString }ValueTask<(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 new ValueTask<(object, object[])>((null, outputs));"); + } + else + { + _builder.AppendLine($"{indentor}return new ValueTask<(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..b783b2d526f --- /dev/null +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs @@ -0,0 +1,107 @@ +// 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.GetOneAttributeOf(_sSMOperationContractSymbol) is not null + let @interface = method.ContainingSymbol + where @interface.GetOneAttributeOf(_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..e3ad23f4f98 --- /dev/null +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs @@ -0,0 +1,87 @@ +// 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 System.Text; +using System.Threading; +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 : 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..cf8d191f209 --- /dev/null +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs @@ -0,0 +1,79 @@ +// 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 System.Text; +using System.Xml.Linq; +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..1411770432a --- /dev/null +++ b/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs @@ -0,0 +1,54 @@ +// 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 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 +{ +#if ROSLYN4_0_OR_GREATER + public class Test : CSharpIncrementalGeneratorTest +#else + public class Test : CSharpSourceGeneratorTest +#endif + { + 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); + + protected override bool IsCompilerDiagnosticIncluded(Diagnostic diagnostic, CompilerDiagnostics compilerDiagnostics) => false; + } +} + diff --git a/src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs b/src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs new file mode 100644 index 00000000000..bcf9851b100 --- /dev/null +++ b/src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs @@ -0,0 +1,42 @@ +// 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 System.Linq; + +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpIncrementalGeneratorTest : IncrementalGeneratorTest + where TSourceGenerator : IIncrementalGenerator, new() + where TVerifier : IVerifier, new() + { + private static readonly LanguageVersion DefaultLanguageVersion = + Enum.TryParse("Default", out LanguageVersion version) ? version : LanguageVersion.CSharp6; + + protected override IEnumerable GetSourceGenerators() + => new IIncrementalGenerator[] { new TSourceGenerator() }; + + protected override string DefaultFileExt => "cs"; + + public override string Language => LanguageNames.CSharp; + + protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators) + { + return CSharpGeneratorDriver.Create( + sourceGenerators.Select(s => s.AsSourceGenerator()), + project.AnalyzerOptions.AdditionalFiles, + (CSharpParseOptions)project.ParseOptions!, + project.AnalyzerOptions.AnalyzerConfigOptionsProvider); + } + + protected override CompilationOptions CreateCompilationOptions() + => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override ParseOptions CreateParseOptions() + => new CSharpParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose); + } +} diff --git a/src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs b/src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs new file mode 100644 index 00000000000..e3d964153b7 --- /dev/null +++ b/src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs @@ -0,0 +1,151 @@ +// 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 System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Model; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public abstract class IncrementalGeneratorTest : AnalyzerTest + where TVerifier : IVerifier, new() + { + protected override IEnumerable GetDiagnosticAnalyzers() + => Enumerable.Empty(); + + /// + /// Returns the source generators being tested - to be implemented in non-abstract class. + /// + /// The to be used. + protected abstract IEnumerable GetSourceGenerators(); + + protected abstract GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators); + + protected override async Task RunImplAsync(CancellationToken cancellationToken) + { + var analyzers = GetDiagnosticAnalyzers().ToArray(); + var defaultDiagnostic = GetDefaultDiagnostic(analyzers); + var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); + var fixableDiagnostics = ImmutableArray.Empty; + var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + + var diagnostics = await VerifySourceGeneratorAsync(testState, Verify, cancellationToken).ConfigureAwait(false); + await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies).WithAdditionalDiagnostics(diagnostics), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); + } + + protected override async Task GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var (finalProject, diagnostics) = await ApplySourceGeneratorAsync(GetSourceGenerators().ToImmutableArray(), project, verifier, cancellationToken).ConfigureAwait(false); + return (await finalProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!; + } + + /// + /// Called to test a C# source generator when applied on the input source as a string. + /// + /// The effective input test state. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A representing the asynchronous operation. + protected async Task> VerifySourceGeneratorAsync(SolutionState testState, IVerifier verifier, CancellationToken cancellationToken) + { + return await VerifySourceGeneratorAsync(Language, GetSourceGenerators().ToImmutableArray(), testState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken); + } + + private async Task> VerifySourceGeneratorAsync( + string language, + ImmutableArray sourceGenerators, + SolutionState testState, + Func, Project, IVerifier, CancellationToken, Task<(Project project, ImmutableArray diagnostics)>> getFixedProject, + IVerifier verifier, + CancellationToken cancellationToken) + { + var project = await CreateProjectAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); + _ = await GetCompilerDiagnosticsAsync(project, verifier, cancellationToken).ConfigureAwait(false); + + ImmutableArray diagnostics; + (project, diagnostics) = await getFixedProject(sourceGenerators, project, verifier, cancellationToken).ConfigureAwait(false); + + // After applying the source generator, compare the resulting string to the inputted one + if (!TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck)) + { + var updatedDocuments = project.Documents.ToArray(); + var expectedSources = testState.Sources.Concat(testState.GeneratedSources).ToArray(); + + verifier.Equal(expectedSources.Length, updatedDocuments.Length, $"expected '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' to match '{nameof(updatedDocuments)}', but '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' contains '{expectedSources.Length}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); + + for (var i = 0; i < updatedDocuments.Length; i++) + { + var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(expectedSources[i].content.ToString(), actual.ToString(), $"content of '{expectedSources[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(expectedSources[i].content.Encoding, actual.Encoding, $"encoding of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(expectedSources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(expectedSources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{expectedSources[i].filename}' but was '{updatedDocuments[i].Name}'"); + } + } + + return diagnostics; + } + + private async Task<(Project project, ImmutableArray diagnostics)> ApplySourceGeneratorAsync(ImmutableArray sourceGenerators, Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + verifier.True(compilation is { }); + + var driver = CreateGeneratorDriver(project, sourceGenerators).RunGenerators(compilation, cancellationToken); + var result = driver.GetRunResult(); + + var updatedProject = project; + foreach (var tree in result.GeneratedTrees) + { + updatedProject = updatedProject.AddDocument(tree.FilePath, await tree.GetTextAsync(cancellationToken).ConfigureAwait(false), filePath: tree.FilePath).Project; + } + + return (updatedProject, result.Diagnostics); + } + + /// + /// Get the existing compiler diagnostics on the input document. + /// + /// The to run the compiler diagnostic analyzers on. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// The compiler diagnostics that were found in the code. + private static async Task> GetCompilerDiagnosticsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var allDiagnostics = ImmutableArray.Create(); + + foreach (var document in project.Documents) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + verifier.True(semanticModel is { }); + + allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken)); + } + + return allDiagnostics; + } + + /// + /// Given a document, turn it into a string based on the syntax root. + /// + /// The to be converted to a string. + /// The that the task will observe. + /// A containing the syntax of the after formatting. + private static async Task GetSourceTextFromDocumentAsync(Document document, CancellationToken cancellationToken) + { + var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + return await formatted.GetTextAsync(cancellationToken).ConfigureAwait(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..346aac1537a --- /dev/null +++ b/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs @@ -0,0 +1,842 @@ +// 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.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(); + } + } +} +#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(); + } + } +} +#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(); + } + } +} +#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(); + } + } +} +#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(); + } + } +} +#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(); + } + } +} +#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(); + } + } +} +#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(); + } + } +} +#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..bc89f6ccef4 --- /dev/null +++ b/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs @@ -0,0 +1,24 @@ +// 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.Reflection; + +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) + { + // TODO implement GetKey and wire it OperationInvokerBehavior + throw new NotImplementedException(); + } + } +} From d7aa90588f110a5dac4f1f650b427ed4402d33d2 Mon Sep 17 00:00:00 2001 From: g7ed6e Date: Thu, 2 Nov 2023 16:50:22 +0100 Subject: [PATCH 2/5] Unify CSharpSourceGeneratorTest according to https://github.com/dotnet/roslyn-sdk/issues/941 --- .../tests/CSharpGeneratorVerifier.cs | 19 ++- .../tests/CSharpIncrementalGeneratorTest`2.cs | 42 ----- .../tests/IncrementalGeneratorTest`1.cs | 151 ------------------ 3 files changed, 14 insertions(+), 198 deletions(-) delete mode 100644 src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs delete mode 100644 src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs diff --git a/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs b/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs index 1411770432a..5c85c641de0 100644 --- a/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs +++ b/src/System.ServiceModel.BuildTools/tests/CSharpGeneratorVerifier.cs @@ -2,6 +2,7 @@ // 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; @@ -15,11 +16,7 @@ public static class CSharpGeneratorVerifier where TSourceGenerator : ISourceGenerator, new() #endif { -#if ROSLYN4_0_OR_GREATER - public class Test : CSharpIncrementalGeneratorTest -#else - public class Test : CSharpSourceGeneratorTest -#endif + public class Test : CSharpSourceGeneratorTest { public Test() { @@ -48,6 +45,18 @@ private static ImmutableDictionary GetNullableWarnings 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/CSharpIncrementalGeneratorTest`2.cs b/src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs deleted file mode 100644 index bcf9851b100..00000000000 --- a/src/System.ServiceModel.BuildTools/tests/CSharpIncrementalGeneratorTest`2.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 System.Linq; - -using Microsoft.CodeAnalysis.Testing; - -namespace Microsoft.CodeAnalysis.CSharp.Testing -{ - public class CSharpIncrementalGeneratorTest : IncrementalGeneratorTest - where TSourceGenerator : IIncrementalGenerator, new() - where TVerifier : IVerifier, new() - { - private static readonly LanguageVersion DefaultLanguageVersion = - Enum.TryParse("Default", out LanguageVersion version) ? version : LanguageVersion.CSharp6; - - protected override IEnumerable GetSourceGenerators() - => new IIncrementalGenerator[] { new TSourceGenerator() }; - - protected override string DefaultFileExt => "cs"; - - public override string Language => LanguageNames.CSharp; - - protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators) - { - return CSharpGeneratorDriver.Create( - sourceGenerators.Select(s => s.AsSourceGenerator()), - project.AnalyzerOptions.AdditionalFiles, - (CSharpParseOptions)project.ParseOptions!, - project.AnalyzerOptions.AnalyzerConfigOptionsProvider); - } - - protected override CompilationOptions CreateCompilationOptions() - => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); - - protected override ParseOptions CreateParseOptions() - => new CSharpParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose); - } -} diff --git a/src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs b/src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs deleted file mode 100644 index e3d964153b7..00000000000 --- a/src/System.ServiceModel.BuildTools/tests/IncrementalGeneratorTest`1.cs +++ /dev/null @@ -1,151 +0,0 @@ -// 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 System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Testing.Model; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.CSharp.Testing -{ - public abstract class IncrementalGeneratorTest : AnalyzerTest - where TVerifier : IVerifier, new() - { - protected override IEnumerable GetDiagnosticAnalyzers() - => Enumerable.Empty(); - - /// - /// Returns the source generators being tested - to be implemented in non-abstract class. - /// - /// The to be used. - protected abstract IEnumerable GetSourceGenerators(); - - protected abstract GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators); - - protected override async Task RunImplAsync(CancellationToken cancellationToken) - { - var analyzers = GetDiagnosticAnalyzers().ToArray(); - var defaultDiagnostic = GetDefaultDiagnostic(analyzers); - var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); - var fixableDiagnostics = ImmutableArray.Empty; - var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); - - var diagnostics = await VerifySourceGeneratorAsync(testState, Verify, cancellationToken).ConfigureAwait(false); - await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies).WithAdditionalDiagnostics(diagnostics), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); - } - - protected override async Task GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) - { - var (finalProject, diagnostics) = await ApplySourceGeneratorAsync(GetSourceGenerators().ToImmutableArray(), project, verifier, cancellationToken).ConfigureAwait(false); - return (await finalProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!; - } - - /// - /// Called to test a C# source generator when applied on the input source as a string. - /// - /// The effective input test state. - /// The verifier to use for test assertions. - /// The that the task will observe. - /// A representing the asynchronous operation. - protected async Task> VerifySourceGeneratorAsync(SolutionState testState, IVerifier verifier, CancellationToken cancellationToken) - { - return await VerifySourceGeneratorAsync(Language, GetSourceGenerators().ToImmutableArray(), testState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken); - } - - private async Task> VerifySourceGeneratorAsync( - string language, - ImmutableArray sourceGenerators, - SolutionState testState, - Func, Project, IVerifier, CancellationToken, Task<(Project project, ImmutableArray diagnostics)>> getFixedProject, - IVerifier verifier, - CancellationToken cancellationToken) - { - var project = await CreateProjectAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); - _ = await GetCompilerDiagnosticsAsync(project, verifier, cancellationToken).ConfigureAwait(false); - - ImmutableArray diagnostics; - (project, diagnostics) = await getFixedProject(sourceGenerators, project, verifier, cancellationToken).ConfigureAwait(false); - - // After applying the source generator, compare the resulting string to the inputted one - if (!TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck)) - { - var updatedDocuments = project.Documents.ToArray(); - var expectedSources = testState.Sources.Concat(testState.GeneratedSources).ToArray(); - - verifier.Equal(expectedSources.Length, updatedDocuments.Length, $"expected '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' to match '{nameof(updatedDocuments)}', but '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' contains '{expectedSources.Length}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); - - for (var i = 0; i < updatedDocuments.Length; i++) - { - var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); - verifier.EqualOrDiff(expectedSources[i].content.ToString(), actual.ToString(), $"content of '{expectedSources[i].filename}' did not match. Diff shown with expected as baseline:"); - verifier.Equal(expectedSources[i].content.Encoding, actual.Encoding, $"encoding of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); - verifier.Equal(expectedSources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); - verifier.Equal(expectedSources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{expectedSources[i].filename}' but was '{updatedDocuments[i].Name}'"); - } - } - - return diagnostics; - } - - private async Task<(Project project, ImmutableArray diagnostics)> ApplySourceGeneratorAsync(ImmutableArray sourceGenerators, Project project, IVerifier verifier, CancellationToken cancellationToken) - { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - verifier.True(compilation is { }); - - var driver = CreateGeneratorDriver(project, sourceGenerators).RunGenerators(compilation, cancellationToken); - var result = driver.GetRunResult(); - - var updatedProject = project; - foreach (var tree in result.GeneratedTrees) - { - updatedProject = updatedProject.AddDocument(tree.FilePath, await tree.GetTextAsync(cancellationToken).ConfigureAwait(false), filePath: tree.FilePath).Project; - } - - return (updatedProject, result.Diagnostics); - } - - /// - /// Get the existing compiler diagnostics on the input document. - /// - /// The to run the compiler diagnostic analyzers on. - /// The verifier to use for test assertions. - /// The that the task will observe. - /// The compiler diagnostics that were found in the code. - private static async Task> GetCompilerDiagnosticsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) - { - var allDiagnostics = ImmutableArray.Create(); - - foreach (var document in project.Documents) - { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - verifier.True(semanticModel is { }); - - allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken)); - } - - return allDiagnostics; - } - - /// - /// Given a document, turn it into a string based on the syntax root. - /// - /// The to be converted to a string. - /// The that the task will observe. - /// A containing the syntax of the after formatting. - private static async Task GetSourceTextFromDocumentAsync(Document document, CancellationToken cancellationToken) - { - var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - return await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false); - } - } -} From aebf8183d3d1defbed361a8ff54ce077f405c878 Mon Sep 17 00:00:00 2001 From: g7ed6e Date: Fri, 3 Nov 2023 10:52:13 +0100 Subject: [PATCH 3/5] use an ubuntu agent to build binaries for MacOSX unit tests --- azure-pipelines-arcade.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 From 51c19faff5aa02075bf0a09d3c6ad3a5f6de6f11 Mon Sep 17 00:00:00 2001 From: g7ed6e Date: Fri, 3 Nov 2023 15:20:41 +0100 Subject: [PATCH 4/5] Code cleanup --- .../src/Extensions.cs | 109 +----------------- .../src/OperationInvokerGenerator.Parser.cs | 13 +-- .../OperationInvokerGenerator.Roslyn3.11.cs | 3 - .../OperationInvokerGenerator.Roslyn4.0.cs | 4 - 4 files changed, 9 insertions(+), 120 deletions(-) diff --git a/src/System.ServiceModel.BuildTools/src/Extensions.cs b/src/System.ServiceModel.BuildTools/src/Extensions.cs index 2366009b255..43edf29f254 100644 --- a/src/System.ServiceModel.BuildTools/src/Extensions.cs +++ b/src/System.ServiceModel.BuildTools/src/Extensions.cs @@ -1,126 +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 System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace System.ServiceModel.BuildTools { - internal static class MethodSymbolExtensions - { - public static bool? IsGeneratedCode(this IMethodSymbol methodSymbol) - => methodSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath.EndsWith(".g.cs"); - - public static bool IsMatchingUserProvidedMethod(this IMethodSymbol methodSymbol, IMethodSymbol userProvidedMethodSymbol, INamedTypeSymbol? coreWCFInjectedAttribute, INamedTypeSymbol? fromServicesAttribute) - { - int parameterFound = 0; - if (methodSymbol.Name != userProvidedMethodSymbol.Name) - { - return false; - } - - var parameters = methodSymbol.Parameters; - - for (int i = 0,j = 0; i < userProvidedMethodSymbol.Parameters.Length; i++) - { - IParameterSymbol parameterSymbol = userProvidedMethodSymbol.Parameters[i]; - if (parameterSymbol.GetOneAttributeOf(coreWCFInjectedAttribute, fromServicesAttribute) is not null) - { - continue; - } - - if (parameterSymbol.IsMatchingParameter(parameters[j])) - { - j++; - parameterFound++; - } - } - - return parameterFound == parameters.Length; - } - } - - internal static class ParameterSymbolExtensions - { - public static bool IsMatchingParameter(this IParameterSymbol symbol, IParameterSymbol parameterSymbol) - => SymbolEqualityComparer.Default.Equals(symbol.Type, parameterSymbol.Type); - } - internal static class SymbolExtensions { - public static AttributeData? GetOneAttributeOf(this ISymbol symbol, params INamedTypeSymbol?[] attributeTypeSymbols) + public static AttributeData? HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeTypeSymbol) { - if (attributeTypeSymbols.Length == 0) - { - return null; - } - foreach (var attribute in symbol.GetAttributes()) { - foreach (var namedTypeSymbol in attributeTypeSymbols.Where(static s => s is not null)) + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeTypeSymbol)) { - if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, namedTypeSymbol)) - { - return attribute; - } + return attribute; } } return null; } - - public static bool HasOneAttributeInheritFrom(this ISymbol symbol, params INamedTypeSymbol?[] attributeTypeSymbols) - { - if (attributeTypeSymbols.Length == 0) - { - return false; - } - - foreach (var attribute in symbol.GetAttributes()) - { - foreach (var @interface in attribute.AttributeClass!.AllInterfaces) - { - foreach (var namedTypeSymbol in attributeTypeSymbols.Where(static s => s is not null)) - { - if (SymbolEqualityComparer.Default.Equals(@interface, namedTypeSymbol)) - { - return true; - } - } - } - } - - return false; - } - } - - internal static class NamedTypeSymbolExtensions - { - public static bool IsPartial(this INamedTypeSymbol namedTypeSymbol, out INamedTypeSymbol parentType) - { - bool result = namedTypeSymbol.DeclaringSyntaxReferences.Select(static s => s.GetSyntax()).OfType().All(static c => c.Modifiers.Any(static m => m.IsKind(SyntaxKind.PartialKeyword))); - if (result && namedTypeSymbol.ContainingType != null) - { - return namedTypeSymbol.ContainingType.IsPartial(out parentType); - } - parentType = namedTypeSymbol; - return result; - } - } - - internal static class TypedConstantExtensions - { - public static string ToSafeCSharpString(this TypedConstant typedConstant) - { - if (typedConstant.Kind == TypedConstantKind.Array) - { - return $"new [] {typedConstant.ToCSharpString()}"; - } - - return typedConstant.ToCSharpString(); - - } } } diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs index b783b2d526f..466f946caff 100644 --- a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Parser.cs @@ -15,16 +15,15 @@ private sealed class Parser { private readonly Compilation _compilation; private readonly OperationInvokerSourceGenerationContext _context; - private readonly INamedTypeSymbol? _sSMOperationContractSymbol; - private readonly INamedTypeSymbol? _sSMServiceContractSymbol; + 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"); + _sSMOperationContractSymbol = _compilation.GetTypeByMetadataName("System.ServiceModel.OperationContractAttribute")!; + _sSMServiceContractSymbol = _compilation.GetTypeByMetadataName("System.ServiceModel.ServiceContractAttribute")!; } public SourceGenerationSpec GetGenerationSpec(ImmutableArray methodDeclarationSyntaxes) @@ -37,9 +36,9 @@ where symbol is not null select methodSymbol).ToImmutableArray(); var methods = (from method in methodSymbols - where method.GetOneAttributeOf(_sSMOperationContractSymbol) is not null + where method.HasAttribute(_sSMOperationContractSymbol) is not null let @interface = method.ContainingSymbol - where @interface.GetOneAttributeOf(_sSMServiceContractSymbol) is not null + where @interface.HasAttribute(_sSMServiceContractSymbol) is not null select method).ToImmutableArray(); var builder = ImmutableArray.CreateBuilder(); diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs index e3ad23f4f98..e235abec0b8 100644 --- a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn3.11.cs @@ -3,11 +3,8 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; -using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace System.ServiceModel.BuildTools diff --git a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs index cf8d191f209..1ae94f44c37 100644 --- a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Roslyn4.0.cs @@ -1,11 +1,7 @@ // 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 System.Text; -using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; From 8356a84181a82c24e7b020b06f15c309da3c5234 Mon Sep 17 00:00:00 2001 From: g7ed6e Date: Fri, 10 Nov 2023 11:04:35 +0100 Subject: [PATCH 5/5] Add integration tests --- System.ServiceModel.sln | 7 + ...e.SourceGeneration.IntegrationTests.csproj | 24 + .../src/OperationInvokerGenerator.Emitter.cs | 163 +- .../tests/OperationInvokerGeneratorTests.cs | 2439 ++++++++++++----- .../DispatchOperationRuntimeHelpers.cs | 94 +- 5 files changed, 2053 insertions(+), 674 deletions(-) create mode 100644 src/System.Private.ServiceModel/tests/Scenarios/Contract/Service/Contract.Service.SourceGeneration.IntegrationTests.csproj diff --git a/System.ServiceModel.sln b/System.ServiceModel.sln index ccf920eea2b..57e9baa3609 100644 --- a/System.ServiceModel.sln +++ b/System.ServiceModel.sln @@ -97,6 +97,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.ServiceModel.BuildTo 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 @@ -283,6 +285,10 @@ Global {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 @@ -322,6 +328,7 @@ Global {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/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/OperationInvokerGenerator.Emitter.cs b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs index e54bde86694..764320d1c6c 100644 --- a/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs +++ b/src/System.ServiceModel.BuildTools/src/OperationInvokerGenerator.Emitter.cs @@ -36,6 +36,7 @@ public void Emit() // #nullable disable using System; +using System.Runtime; using System.Threading.Tasks; namespace System.Runtime.CompilerServices { @@ -74,6 +75,146 @@ file sealed class OperationInvokerModuleInitializer _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(); @@ -101,8 +242,24 @@ namespace System.ServiceModel.Dispatcher 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}public { asyncString }ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)"); + _builder.AppendLine($"{indentor}private { asyncString }Task<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)"); _builder.AppendLine($"{indentor}{{"); indentor.Increment(); @@ -177,11 +334,11 @@ namespace System.ServiceModel.Dispatcher { if (operationContractSpec.Method.ReturnsVoid) { - _builder.AppendLine($"{indentor}return new ValueTask<(object, object[])>((null, outputs));"); + _builder.AppendLine($"{indentor}return Task.FromResult<(object, object[])>((null, outputs));"); } else { - _builder.AppendLine($"{indentor}return new ValueTask<(object, object[])>((result, outputs));"); + _builder.AppendLine($"{indentor}return Task.FromResult<(object, object[])>((result, outputs));"); } } diff --git a/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs b/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs index 346aac1537a..312af28e841 100644 --- a/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs +++ b/src/System.ServiceModel.BuildTools/tests/OperationInvokerGeneratorTests.cs @@ -96,6 +96,7 @@ public string Echo(string input, ref bool b, out int i) // #nullable disable using System; +using System.Runtime; using System.Threading.Tasks; namespace System.Runtime.CompilerServices { @@ -107,112 +108,19 @@ 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) + public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) { - 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)); + return InvokeAsync(instance, inputs).ToApm(callback, state); } - 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() + public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) { - OperationInvoker0.RegisterOperationInvoker(); - } - } -} -#nullable restore - -""", Encoding.UTF8, SourceHashAlgorithm.Sha256)), - }, - }, - }; - - await test.RunAsync(); + var (returnValue, outputsValue) = result.ToApmEnd<(object, object[])>(); + outputs = outputsValue; + return returnValue; } - [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) + private Task<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs) { string p0; p0 = (string)inputs[0]; @@ -223,7 +131,7 @@ namespace System.ServiceModel.Dispatcher var outputs = AllocateOutputs(); outputs[0] = p1; outputs[1] = p2; - return new ValueTask<(object, object[])>((result, outputs)); + return Task.FromResult<(object, object[])>((result, outputs)); } public object[] AllocateInputs() => new object[2]; @@ -234,32 +142,6 @@ namespace System.ServiceModel.Dispatcher } } 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 { @@ -267,105 +149,142 @@ file sealed class OperationInvokerModuleInitializer internal static void RegisterOperationInvokers() { OperationInvoker0.RegisterOperationInvoker(); - OperationInvoker1.RegisterOperationInvoker(); } } } -#nullable restore +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; + } -""", Encoding.UTF8, SourceHashAlgorithm.Sha256)), - }, - }, - }; + // 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); + } - await test.RunAsync(); + if (callbackObj != null) + { + callbackObj(tcsObj.Task); + } + }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default); + return tcs.Task; } - [WcfFact] - public async Task IntegratedTypesAsDotNetTypesTest() + // 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) { - var test = new VerifyGenerator.Test + // 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) { - TestState = + if (callback != null) { - Sources = + task.ContinueWith((antecedent, obj) => { -@$" -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); - }} + 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); + } - 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)); + if (callbackObj != null) + { + callbackObj(tcsObj.Task); + } + }, continuationState, CancellationToken.None, TaskContinuationOptions.HideScheduler, TaskScheduler.Default); + return tcs.Task; } - public object[] AllocateInputs() => new object[2]; - - private object[] AllocateOutputs() => new object[2]; + // 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(); + } - 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() + // 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) { - OperationInvoker0.RegisterOperationInvoker(); + Task task = iar as Task; + System.Diagnostics.Debug.Assert(task != null, "IAsyncResult must be an instance of Task"); + task.GetAwaiter().GetResult(); } } } @@ -379,464 +298,1646 @@ internal static void RegisterOperationInvokers() 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(); - } - } -} -#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(); - } - } -} -#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(); - } - } -} -#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(); - } - } -} -#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(); - } - } -} -#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.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs b/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs index bc89f6ccef4..5ff5ec05064 100644 --- a/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs +++ b/src/System.ServiceModel.Primitives/src/System/ServiceModel/Dispatcher/DispatchOperationRuntimeHelpers.cs @@ -2,7 +2,10 @@ // 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 { @@ -17,8 +20,95 @@ public static void RegisterOperationInvoker(string key, IOperationInvoker invoke internal static string GetKey(MethodInfo method) { - // TODO implement GetKey and wire it OperationInvokerBehavior - throw new NotImplementedException(); + 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); } }