| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.Text; |
| | | 6 | | using Microsoft.CodeAnalysis; |
| | | 7 | | using Microsoft.CodeAnalysis.CSharp; |
| | | 8 | | using Microsoft.CodeAnalysis.Text; |
| | | 9 | | |
| | | 10 | | namespace CoreWCF.BuildTools; |
| | | 11 | | |
| | | 12 | | public sealed partial class OperationInvokerGenerator |
| | | 13 | | { |
| | | 14 | | private sealed class Emitter |
| | | 15 | | { |
| | | 16 | | private readonly StringBuilder _builder; |
| | | 17 | | private readonly OperationInvokerSourceGenerationContext _sourceGenerationContext; |
| | | 18 | | private readonly SourceGenerationSpec _generationSpec; |
| | | 19 | | |
| | | 20 | | /// <summary> |
| | | 21 | | /// SymbolDisplayFormat that excludes nullable annotations to match reflection-based key generation. |
| | | 22 | | /// Reflection-based MethodInfo does not expose nullable reference type annotations, so we need to |
| | | 23 | | /// exclude them from the generated key to ensure the source generator key matches the runtime key. |
| | | 24 | | /// This prevents PlatformNotSupportedException when UseGeneratedOperationInvokers is enabled. |
| | | 25 | | /// </summary> |
| | 1 | 26 | | private static readonly SymbolDisplayFormat s_methodDisplayFormat = new SymbolDisplayFormat( |
| | 1 | 27 | | globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, |
| | 1 | 28 | | typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, |
| | 1 | 29 | | genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, |
| | 1 | 30 | | memberOptions: SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeContainingTy |
| | 1 | 31 | | parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeParamsRef |
| | 1 | 32 | | miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); |
| | | 33 | | |
| | | 34 | | /// <summary> |
| | | 35 | | /// SymbolDisplayFormat for emitting type references in generated code. Mirrors the default |
| | | 36 | | /// CSharpErrorMessageFormat but strips nullable reference type annotations because the generated |
| | | 37 | | /// file is wrapped in a '#nullable disable' context. Emitting NRT annotations like 'string?' under |
| | | 38 | | /// '#nullable disable' produces CS8669 warnings (see issue #1712). Nullable value types such as |
| | | 39 | | /// 'int?' are unaffected because they are System.Nullable<T> rather than NRT annotations. |
| | | 40 | | /// </summary> |
| | 1 | 41 | | private static readonly SymbolDisplayFormat s_typeDisplayFormat = SymbolDisplayFormat.CSharpErrorMessageFormat |
| | 1 | 42 | | .RemoveMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); |
| | | 43 | | |
| | 52 | 44 | | public Emitter(in OperationInvokerSourceGenerationContext sourceGenerationContext, in SourceGenerationSpec gener |
| | | 45 | | { |
| | 52 | 46 | | _sourceGenerationContext = sourceGenerationContext; |
| | 52 | 47 | | _generationSpec = generationSpec; |
| | 52 | 48 | | _builder = new StringBuilder(); |
| | 52 | 49 | | } |
| | | 50 | | |
| | | 51 | | public void Emit() |
| | | 52 | | { |
| | 52 | 53 | | if (_generationSpec.OperationContractSpecs.Length == 0) |
| | | 54 | | { |
| | 0 | 55 | | return; |
| | | 56 | | } |
| | 52 | 57 | | _builder.Clear(); |
| | 52 | 58 | | _builder.AppendLine($$""" |
| | 52 | 59 | | // <auto-generated> |
| | 52 | 60 | | // Generated by the CoreWCF.BuildTools.OperationInvokerGenerator source generator. DO |
| | 52 | 61 | | // </auto-generated> |
| | 52 | 62 | | #nullable disable |
| | 52 | 63 | | using System; |
| | 52 | 64 | | using System.Threading.Tasks; |
| | 52 | 65 | | namespace System.Runtime.CompilerServices |
| | 52 | 66 | | { |
| | 52 | 67 | | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] |
| | 52 | 68 | | file sealed class ModuleInitializerAttribute : Attribute { } |
| | 52 | 69 | | } |
| | 52 | 70 | | """); |
| | 52 | 71 | | int i = 0; |
| | 224 | 72 | | foreach (var operationContractSpec in _generationSpec.OperationContractSpecs) |
| | | 73 | | { |
| | 60 | 74 | | EmitOperationContract(operationContractSpec, i); |
| | 60 | 75 | | i++; |
| | | 76 | | } |
| | | 77 | | |
| | 52 | 78 | | var indentor = new Indentor(); |
| | 52 | 79 | | _builder.AppendLine($$""" |
| | 52 | 80 | | namespace CoreWCF.Dispatcher |
| | 52 | 81 | | { |
| | 52 | 82 | | file sealed class OperationInvokerModuleInitializer |
| | 52 | 83 | | { |
| | 52 | 84 | | """); |
| | 52 | 85 | | indentor.Increment(); |
| | 52 | 86 | | indentor.Increment(); |
| | | 87 | | |
| | 52 | 88 | | _builder.AppendLine($"{indentor}[System.Runtime.CompilerServices.ModuleInitializer]"); |
| | 52 | 89 | | _builder.AppendLine($"{indentor}internal static void RegisterOperationInvokers()"); |
| | 52 | 90 | | _builder.AppendLine($"{indentor}{{"); |
| | 52 | 91 | | indentor.Increment(); |
| | 224 | 92 | | for (int j = 0; j < i; j++) |
| | | 93 | | { |
| | 60 | 94 | | _builder.AppendLine($"{indentor}{GetOperationInvokerTypeName(j)}.RegisterOperationInvoker();"); |
| | | 95 | | } |
| | 52 | 96 | | indentor.Decrement(); |
| | 52 | 97 | | _builder.AppendLine($"{indentor}}}"); |
| | 52 | 98 | | indentor.Decrement(); |
| | 52 | 99 | | _builder.AppendLine($"{indentor}}}"); |
| | 52 | 100 | | indentor.Decrement(); |
| | 52 | 101 | | _builder.AppendLine($"{indentor}}}"); |
| | 52 | 102 | | _builder.AppendLine("#nullable restore"); |
| | | 103 | | |
| | 52 | 104 | | string sourceText = _builder.ToString(); |
| | 52 | 105 | | _sourceGenerationContext.AddSource("OperationInvoker.g.cs", SourceText.From(sourceText, Encoding.UTF8, Sourc |
| | 52 | 106 | | } |
| | | 107 | | |
| | | 108 | | private void EmitOperationContract(OperationContractSpec operationContractSpec, int index) |
| | | 109 | | { |
| | 60 | 110 | | var indentor = new Indentor(); |
| | 60 | 111 | | string operationInvokerTypeName = GetOperationInvokerTypeName(index); |
| | 60 | 112 | | string escapedMethodName = EscapeIdentifier(operationContractSpec.Method!.Name); |
| | 60 | 113 | | _builder.AppendLine($$""" |
| | 60 | 114 | | namespace CoreWCF.Dispatcher |
| | 60 | 115 | | { |
| | 60 | 116 | | // This class is used to invoke the method {{operationContractSpec.Method.ToDispla |
| | 60 | 117 | | file sealed class {{operationInvokerTypeName}} : CoreWCF.Dispatcher.IOperationInvo |
| | 60 | 118 | | { |
| | 60 | 119 | | """); |
| | 60 | 120 | | indentor.Increment(); |
| | 60 | 121 | | indentor.Increment(); |
| | | 122 | | |
| | 60 | 123 | | INamedTypeSymbol? returnTypeSymbol = operationContractSpec.Method!.ReturnType as INamedTypeSymbol; |
| | 60 | 124 | | bool isGenericTaskReturnType = returnTypeSymbol != null && |
| | 60 | 125 | | returnTypeSymbol.IsGenericType && |
| | 60 | 126 | | returnTypeSymbol.ConstructUnboundGenericType().ToDisplayString() == "System.T |
| | 60 | 127 | | bool isTaskReturnType = operationContractSpec.Method.ReturnType.ToDisplayString() == "System.Threading.Tasks |
| | 60 | 128 | | bool isAsync = isGenericTaskReturnType || isTaskReturnType; |
| | | 129 | | |
| | 60 | 130 | | string asyncString = isAsync ? "async " : string.Empty; |
| | 60 | 131 | | _builder.AppendLine($"{indentor}public { asyncString }ValueTask<(object returnValue, object[] outputs)> Invo |
| | 60 | 132 | | _builder.AppendLine($"{indentor}{{"); |
| | 60 | 133 | | indentor.Increment(); |
| | | 134 | | |
| | 60 | 135 | | int inputParameterCount = 0; |
| | 60 | 136 | | int outputParameterCount = 0; |
| | | 137 | | |
| | 60 | 138 | | List<(int, int, IParameterSymbol)> outputParams = new(); |
| | 60 | 139 | | int i = 0; |
| | 60 | 140 | | List<string> invocationParams = new(); |
| | 336 | 141 | | foreach (var parameter in operationContractSpec.Method.Parameters) |
| | | 142 | | { |
| | 108 | 143 | | _builder.AppendLine($"{indentor}{parameter.Type.ToDisplayString(s_typeDisplayFormat)} p{i};"); |
| | 108 | 144 | | if (FlowsIn(parameter)) |
| | | 145 | | { |
| | 88 | 146 | | _builder.AppendLine($"{indentor}p{i} = inputs[{inputParameterCount}] == null ? default({parameter.Ty |
| | 88 | 147 | | inputParameterCount++; |
| | | 148 | | } |
| | | 149 | | |
| | 108 | 150 | | if (FlowOut(parameter)) |
| | | 151 | | { |
| | 40 | 152 | | outputParams.Add((outputParameterCount, i, parameter)); |
| | 40 | 153 | | outputParameterCount++; |
| | | 154 | | } |
| | | 155 | | |
| | 108 | 156 | | invocationParams.Add($"{GetRefKind(parameter)}p{i}"); |
| | 108 | 157 | | i++; |
| | | 158 | | } |
| | | 159 | | |
| | 60 | 160 | | if (isAsync) |
| | | 161 | | { |
| | 8 | 162 | | if (isTaskReturnType) |
| | | 163 | | { |
| | 4 | 164 | | _builder.AppendLine($"{indentor}await (({operationContractSpec.Method.ContainingType.ToDisplayString |
| | | 165 | | } |
| | | 166 | | else |
| | | 167 | | { |
| | 4 | 168 | | _builder.AppendLine($"{indentor}var result = await (({operationContractSpec.Method.ContainingType.To |
| | | 169 | | } |
| | | 170 | | } |
| | | 171 | | else |
| | | 172 | | { |
| | 52 | 173 | | if (operationContractSpec.Method.ReturnsVoid) |
| | | 174 | | { |
| | 4 | 175 | | _builder.AppendLine($"{indentor}(({operationContractSpec.Method.ContainingType.ToDisplayString(s_typ |
| | | 176 | | } |
| | | 177 | | else |
| | | 178 | | { |
| | 48 | 179 | | _builder.AppendLine($"{indentor}var result = (({operationContractSpec.Method.ContainingType.ToDispla |
| | | 180 | | } |
| | | 181 | | } |
| | | 182 | | |
| | 60 | 183 | | _builder.AppendLine($"{indentor}var outputs = AllocateOutputs();"); |
| | | 184 | | |
| | 200 | 185 | | foreach (var (ouputIndex, parameterIndex, parameter) in outputParams) |
| | | 186 | | { |
| | 40 | 187 | | _builder.AppendLine($"{indentor}outputs[{ouputIndex}] = p{parameterIndex};"); |
| | | 188 | | } |
| | | 189 | | |
| | 60 | 190 | | if (isAsync) |
| | | 191 | | { |
| | 8 | 192 | | if (isTaskReturnType) |
| | | 193 | | { |
| | 4 | 194 | | _builder.AppendLine($"{indentor}return (null, outputs);"); |
| | | 195 | | } |
| | | 196 | | else |
| | | 197 | | { |
| | 4 | 198 | | _builder.AppendLine($"{indentor}return (result, outputs);"); |
| | | 199 | | } |
| | | 200 | | } |
| | | 201 | | else |
| | | 202 | | { |
| | 52 | 203 | | if (operationContractSpec.Method.ReturnsVoid) |
| | | 204 | | { |
| | 4 | 205 | | _builder.AppendLine($"{indentor}return new ValueTask<(object, object[])>((null, outputs));"); |
| | | 206 | | } |
| | | 207 | | else |
| | | 208 | | { |
| | 48 | 209 | | _builder.AppendLine($"{indentor}return new ValueTask<(object, object[])>((result, outputs));"); |
| | | 210 | | } |
| | | 211 | | } |
| | | 212 | | |
| | 60 | 213 | | indentor.Decrement(); |
| | 60 | 214 | | _builder.AppendLine($"{indentor}}}"); |
| | 60 | 215 | | _builder.AppendLine(); |
| | 60 | 216 | | _builder.Append($"{indentor}public object[] AllocateInputs() => "); |
| | 60 | 217 | | if (inputParameterCount == 0) |
| | | 218 | | { |
| | 0 | 219 | | _builder.AppendLine("Array.Empty<object>();"); |
| | | 220 | | } |
| | | 221 | | else |
| | | 222 | | { |
| | 60 | 223 | | _builder.AppendLine($"new object[{inputParameterCount}];"); |
| | | 224 | | } |
| | 60 | 225 | | _builder.AppendLine(); |
| | 60 | 226 | | _builder.Append($"{indentor}private object[] AllocateOutputs() => "); |
| | 60 | 227 | | if (outputParameterCount == 0) |
| | | 228 | | { |
| | 40 | 229 | | _builder.AppendLine("Array.Empty<object>();"); |
| | | 230 | | } |
| | | 231 | | else |
| | | 232 | | { |
| | 20 | 233 | | _builder.AppendLine($"new object[{outputParameterCount}];"); |
| | | 234 | | } |
| | 60 | 235 | | _builder.AppendLine(); |
| | | 236 | | |
| | 60 | 237 | | _builder.Append($"{indentor}internal static void RegisterOperationInvoker() => "); |
| | 60 | 238 | | _builder.AppendLine($"CoreWCF.Dispatcher.DispatchOperationRuntimeHelpers.RegisterOperationInvoker(\"{operati |
| | 60 | 239 | | indentor.Decrement(); |
| | 60 | 240 | | _builder.AppendLine($"{indentor}}}"); |
| | | 241 | | |
| | 60 | 242 | | indentor.Decrement(); |
| | 60 | 243 | | _builder.AppendLine($"{indentor}}}"); |
| | | 244 | | |
| | | 245 | | |
| | 60 | 246 | | } |
| | | 247 | | |
| | 120 | 248 | | private static string GetOperationInvokerTypeName(int index) => $"OperationInvoker{index}"; |
| | | 249 | | |
| | | 250 | | private static string EscapeIdentifier(string identifier) |
| | | 251 | | { |
| | 60 | 252 | | return SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None || |
| | 60 | 253 | | SyntaxFacts.GetContextualKeywordKind(identifier) != SyntaxKind.None |
| | 60 | 254 | | ? "@" + identifier |
| | 60 | 255 | | : identifier; |
| | | 256 | | } |
| | | 257 | | |
| | | 258 | | private static bool FlowsIn(IParameterSymbol parameterSymbol) |
| | | 259 | | { |
| | 108 | 260 | | return parameterSymbol.RefKind == RefKind.In || parameterSymbol.RefKind == RefKind.Ref || parameterSymbol.Re |
| | | 261 | | } |
| | | 262 | | |
| | | 263 | | private static bool FlowOut(IParameterSymbol parameterSymbol) |
| | | 264 | | { |
| | 108 | 265 | | return parameterSymbol.RefKind == RefKind.Out || parameterSymbol.RefKind == RefKind.Ref; |
| | | 266 | | } |
| | | 267 | | |
| | | 268 | | private static string GetRefKind(IParameterSymbol parameterSymbol) |
| | | 269 | | { |
| | 108 | 270 | | return parameterSymbol.RefKind switch |
| | 108 | 271 | | { |
| | 20 | 272 | | RefKind.Ref => "ref ", |
| | 20 | 273 | | RefKind.Out => "out ", |
| | 68 | 274 | | _ => string.Empty, |
| | 108 | 275 | | }; |
| | | 276 | | } |
| | | 277 | | } |
| | | 278 | | } |