public async Task <SyntaxList <MemberDeclarationSyntax> > GenerateAsync(TransformationContext context, IProgress <Diagnostic> progress, CancellationToken cancellationToken) { await Task.CompletedTask; FixPdb(context.Compilation); if (!(context.ProcessingNode is MethodDeclarationSyntax methodDeclarationSyntax)) { return(new SyntaxList <MemberDeclarationSyntax>()); } var awaitNodes = methodDeclarationSyntax.DescendantNodes() .Where(n => n is AwaitExpressionSyntax) .Cast <AwaitExpressionSyntax>() .ToList(); List <AwaitInfo> awaitInfos = new List <AwaitInfo>(); string log = string.Empty; var methodString = methodDeclarationSyntax.ChildNodes() .FirstOrDefault(x => x is BlockSyntax) .ToString(); Dictionary <string, int> hashTempVariables = new Dictionary <string, int>(); foreach (var awaitNode in awaitNodes) { var awaitExpressionType = context.SemanticModel.GetTypeInfo(awaitNode.Expression); var symbolInfo = context.SemanticModel.GetSymbolInfo(awaitNode.Expression); var isStatic = symbolInfo.Symbol.IsStatic; if (awaitExpressionType.ConvertedType is INamedTypeSymbol namedType) { var getAwaiterSymbol = namedType.GetMembers().FirstOrDefault(m => m.Name == "GetAwaiter"); if (getAwaiterSymbol is IMethodSymbol awaiterMethodSymbol) { if (awaiterMethodSymbol.ReturnType is INamedTypeSymbol returnNamedSymbol) { string awaiterType = returnNamedSymbol.Name + "<" + string.Join(",", returnNamedSymbol.TypeArguments.Select(t => t.ToString())) + ">"; var dontNeedThis = (awaitNode.Expression is MemberAccessExpressionSyntax) || isStatic; var tempVariableName = awaitNode .ToString() .ToLower() .Replace(".", "") .Replace(" ", "") .Replace("(", "") .Replace(")", "") .Replace(";", ""); if (hashTempVariables.ContainsKey(tempVariableName)) { hashTempVariables[tempVariableName]++; tempVariableName += hashTempVariables[tempVariableName]; } else { hashTempVariables.Add(tempVariableName, 0); tempVariableName += "0"; } // Guid.NewGuid().ToString().Replace("-", ""); //tempVariableName = Regex.Replace(tempVariableName, "[0-9]", ""); var beforeBlock = FindBeforeParentBlockSyntax(awaitNode); var needVariable = NeedVariable(awaitNode, beforeBlock); var awaitInfo = new AwaitInfo() { Await = awaitNode.ToString(), AwaiterType = awaiterType, NeedThis = !dontNeedThis, GenericType = returnNamedSymbol.TypeArguments.FirstOrDefault()?.ToString(), AssignmentVariable = needVariable ? tempVariableName : "" }; awaitInfos.Add(awaitInfo); if (needVariable) { methodString = methodString.Replace(beforeBlock.ToString(), $"var {tempVariableName};{Environment.NewLine}" + beforeBlock.ToString()); methodString = ReplaceFirst(methodString, awaitNode.ToString(), tempVariableName); //var fullNewAwait = $"var {tempVariableName}={awaitNode};{Environment.NewLine}"; //methodString = methodString.Replace($"var {tempVariableName};{Environment.NewLine}", fullNewAwait); //awaitInfo.Await = fullNewAwait; } log += Environment.NewLine + returnNamedSymbol.Name + "<" + string.Join(",", returnNamedSymbol.TypeArguments.Select(t => t.ToString())) + ">"; } } } } //теперь надо заменить переменные на вызов методов, т.к. теперь мы точно можем идентифицировать нужные места foreach (var awaitInfo in awaitInfos) { if (string.IsNullOrEmpty(awaitInfo.AssignmentVariable)) { awaitInfo.AwaitNew = awaitInfo.Await; } else { awaitInfo.AwaitNew = $"var {awaitInfo.AssignmentVariable}={awaitInfo.Await};"; methodString = methodString.Replace($"var {awaitInfo.AssignmentVariable};", $"var {awaitInfo.AssignmentVariable}={awaitInfo.Await};"); } } var clearMethodString = methodString.Trim(new char[] { '{', '}' }); string TempAwaiters = string.Empty; List <string> tempAwaitersHash = new List <string>(); foreach (var awaitInfo in awaitInfos) { var tempClassName = awaitInfo.AwaiterType .ToLowerInvariant() .Replace(".", "_") .Replace("<", "_") .Replace(">", ""); var tempName = $"tempAwaiter__oftype__{tempClassName}"; var typeAwaiter = $"{awaitInfo.AwaiterType} {tempName};"; var boolTypeAwaiter = typeAwaiter.Replace(";", "").Replace(awaitInfo.AwaiterType, "bool") + "_completed;"; awaitInfo.TempAwaiterName = tempName; awaitInfo.TempAwaiterNameCompleted = tempName.Replace(";", "") + "_completed"; if (!tempAwaitersHash.Contains(typeAwaiter)) { TempAwaiters += Environment.NewLine + "// пары переменных временных значений awaiter'ов: первая переменная сам awaiter, вторая информация о том установлен он или нет, т.к. не каждую struct можно проверить на default"; TempAwaiters += Environment.NewLine + typeAwaiter; TempAwaiters += Environment.NewLine + boolTypeAwaiter; tempAwaitersHash.Add(typeAwaiter); } } methodString += Environment.NewLine + TempAwaiters; string TempVariables = Environment.NewLine + "// переменные в методе выведенные в состояние которое может сохраняться"; foreach (var awaitInfo in awaitInfos) { if (!string.IsNullOrEmpty(awaitInfo.AssignmentVariable)) { TempVariables += $"{Environment.NewLine}{awaitInfo.GenericType} {awaitInfo.AssignmentVariable};"; } } TempVariables += Environment.NewLine + "// локальные переменные в оригинальном методе"; var allLocalVariables = methodDeclarationSyntax.DescendantNodes() .Where(n => n is VariableDeclarationSyntax) .Cast <VariableDeclarationSyntax>(); List <string> postprocessVars = new List <string>(); foreach (var localVariable in allLocalVariables) { var symbolInfo = context.SemanticModel.GetSymbolInfo(localVariable.Type); var typeSymbol = symbolInfo.Symbol; var t = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var variableName = localVariable.DescendantNodes() .Where(n => n is VariableDeclaratorSyntax) .Cast <VariableDeclaratorSyntax>() .FirstOrDefault(); TempVariables += $"{Environment.NewLine}{t} {variableName.Identifier};"; postprocessVars.Add(variableName.Identifier.ToString()); } methodString += Environment.NewLine + TempVariables; string LocalAwaiterVariables = "// локальные переменные метода"; int awaitCount = 1; foreach (var awaitInfo in awaitInfos) { var awaiterVariable = $"awaiter{awaitCount}"; var awaiterName = $"{awaitInfo.AwaiterType} {awaiterVariable}"; awaitInfo.LocalAwaiter = awaiterVariable; LocalAwaiterVariables += Environment.NewLine + awaiterName + ";"; awaitCount++; } methodString += Environment.NewLine + LocalAwaiterVariables; string AsyncMethod = "// изменённое тело метода"; int caseNum = -1; foreach (var awaitInfo in awaitInfos) { var next = awaitInfos.ElementAtOrDefault(awaitInfos.IndexOf(awaitInfo) + 1); AsyncMethod += Environment.NewLine + CaseTemplate(clearMethodString, awaitInfo, next, awaitInfos.First() == awaitInfo, awaitInfos.Last() == awaitInfo, caseNum, methodDeclarationSyntax.Identifier.ToString()); caseNum++; } methodString += Environment.NewLine + AsyncMethod; var stateMachine = AsyncMachineTemplate( methodDeclarationSyntax.FirstAncestorOrSelf <NamespaceDeclarationSyntax>().Name.ToString(), methodDeclarationSyntax.Identifier.ToString(), (methodDeclarationSyntax.Parent as ClassDeclarationSyntax).Identifier.ToString(), TempAwaiters, TempVariables, LocalAwaiterVariables, AsyncMethod); foreach (var @var in postprocessVars) { stateMachine = stateMachine.Replace($"var {@var}", @var); } File.AppendAllText($"S:\\GenerateRichAsync.txt", stateMachine); return(new SyntaxList <MemberDeclarationSyntax>(SyntaxFactory.ParseMemberDeclaration(stateMachine))); }
public string CaseTemplate(string method, AwaitInfo awaitInfo, AwaitInfo next, bool start, bool end, int caseNum, string methodName) { var template = @"case [Case]: completed = [TempAwaiterCompleted]; if (!completed) { // код вначале метода в первом case [CodeBefore] //получение awaiter [AwaiterN] = [AwaiterCode].GetAwaiter(); if (![AwaiterN].IsCompleted) { state++; [TempAwaiter] = [AwaiterN]; //запоминаем темповую переменную ___CustomAsyncStateMachine___[MethodName] stateMachine = this; //машине присваиваем себя builder.Await[Unsafe]OnCompleted(ref [AwaiterN], ref stateMachine); return; } [TempAwaiter] = [AwaiterN]; completed = true; } if (completed) { [AwaiterN] = [TempAwaiter]; [TempAwaiter] = default; [TempAwaiterCompletedVariable] = false; //код после await [VariableAwaiterBindResult][AwaiterN].GetResult(); [CodeAfter] // до следующего await, либо присваиваем результат [EndComplete] } [EndCase]" ; awaitInfo.Await = awaitInfo.Await.Replace(";", ""); template = template.Replace("[Unsafe]", awaitInfo.AwaiterType.Contains("StepResultAwaiter") ? "" : "Unsafe"); template = template.Replace("[MethodName]", methodName); template = template.Replace("[Case]", caseNum.ToString()); template = template.Replace("[TempAwaiterCompleted]", (start ? "" : "!switched || ") + awaitInfo.TempAwaiterNameCompleted); template = template.Replace("[TempAwaiterCompletedVariable]", awaitInfo.TempAwaiterNameCompleted); template = template.Replace("[TempAwaiter]", awaitInfo.TempAwaiterName); template = template.Replace("[AwaiterN]", awaitInfo.LocalAwaiter); template = template.Replace("[AwaiterCode]", (awaitInfo.NeedThis ? "@this." : "") + awaitInfo.Await.Replace("await", "").Trim()); template = template.Replace("[EndComplete]", !end ? $"switched = true; goto case {caseNum + 1};" : ""); template = template.Replace("[EndCase]", !end ? $"return;" : "break;"); // определяем awaiter будет писать данные в переменную или нет if (string.IsNullOrEmpty(awaitInfo.AssignmentVariable)) { template = template.Replace("[VariableAwaiterBindResult]", ""); } else { template = template.Replace("[VariableAwaiterBindResult]", $"{awaitInfo.AssignmentVariable}="); } var splitted = new string[0]; if (start) { splitted = method.Split(new string[] { awaitInfo.AwaitNew }, StringSplitOptions.RemoveEmptyEntries); template = template.Replace("[CodeBefore]", splitted[0]); } else { template = template.Replace("[CodeBefore]", ""); } if (!end) { splitted = method.Split(new string[] { awaitInfo.AwaitNew }, StringSplitOptions.RemoveEmptyEntries); splitted = splitted[1].Split(new string[] { next.AwaitNew }, StringSplitOptions.RemoveEmptyEntries); template = template.Replace("[CodeAfter]", splitted[0]); } else { splitted = method.Split(new string[] { awaitInfo.AwaitNew }, StringSplitOptions.RemoveEmptyEntries); var endOfMethod = splitted[1]; var indexOfReturn = endOfMethod.LastIndexOf("return"); var startOfEndMethod = endOfMethod.Substring(0, indexOfReturn); var returnPartFromMethod = endOfMethod.Replace(startOfEndMethod, "").Replace("return", "result = "); template = template.Replace("[CodeAfter]", startOfEndMethod + returnPartFromMethod); } return(template); }