internal string GenerateMethodParamsStruct() { var code = new CodeBuilder(); code.AddHeaderLine($"\tprivate struct {_methodInfo.MethodParamsStructName} {_methodInfo.GenericParams}"); if (!string.IsNullOrEmpty(_methodInfo.ConstraintClauses)) { code.AddHeaderLine($"\t\t{_methodInfo.ConstraintClauses}"); } code.AddBlockHeader("\t"); var paramsSymbols = _methodInfo.MethodSyntax.ParameterList.Parameters .Select(parameterSyntax => (IParameterSymbol)_methodInfo.MethodModel.GetDeclaredSymbol(parameterSyntax)) .ToList(); // fields code.AddHeaderLine(string.Join("\n", paramsSymbols.Select(paramSymbol => $"\t\tinternal readonly {paramSymbol.Type.ToDisplayString()} {paramSymbol.Name};"))); code.AddHeaderLine(); // constructor header code.AddBlockHeader($"\t\tinternal {_methodInfo.MethodParamsStructName}(" + string.Join(", ", paramsSymbols.Select(paramSymbol => $"{paramSymbol.Type.ToDisplayString()} {paramSymbol.Name}")) + ")"); // constructor body code.AddHeaderLine(string.Join("\n", paramsSymbols.Select(paramSymbol => $"\t\t\tthis.{paramSymbol.Name} = {paramSymbol.Name};"))); return(code.BuildString()); }
private void GenerateProxyMethodBody(CodeBuilder code) { // method body code.AddBlockHeader("\t"); // create the initial CallParams struct code.AddHeader($"\t\tvar {ParamsVarName} = new {_methodInfo.MethodParamsStructName}{_methodInfo.GenericParams} (") .AddHeader(string.Join(", ", _methodInfo.MethodSyntax.ParameterList.Parameters.Select(parameterSyntax => _methodInfo.MethodModel.GetDeclaredSymbol(parameterSyntax).Name))) .AddHeaderLine(");"); code.AddHeaderLine($"\t\tvar runner = new {_calculatorClassInfo.CalculatorClassName}{_calculatorClassInfo.GenericParams}();"); code.AddHeaderLine($"\t\trunner.RunRecursion({ParamsVarName});"); if (_methodInfo.ShouldGenerateReturnField) { code.AddHeaderLine($"\t\treturn runner.{_methodInfo.ReturnFieldName};"); } }
private string GenerateParamsStructs(RecursiveCalculatorClassInfo classInfo, IReadOnlyList <CalculatorMethodGenerator> methodGens) { var code = new CodeBuilder(); if (classInfo.IsMultiSite) { code.AddHeaderLine(GenerateCallSiteEnum(classInfo, methodGens)); code.AddHeaderLine(GenerateDispatchStruct(classInfo, methodGens)); code.AddHeaderLine(); } foreach (var methodGen in methodGens) { code.AddHeaderLine(methodGen.GenerateMethodParamsStruct()); } return(code.BuildString()); }
private string GenerateCallSiteEnum(RecursiveCalculatorClassInfo classInfo, IReadOnlyList <CalculatorMethodGenerator> methodGens) { var code = new CodeBuilder(); code.AddBlockHeader($"\tprivate enum {classInfo.CallSiteEnumName} : int"); foreach (var methodGen in methodGens) { code.AddHeaderLine($"\t\t{methodGen.MethodInfo.MethodEnumValueName},"); } return(code.BuildString()); }
internal string GenerateProxyMethod() { var code = new CodeBuilder(); code.AddHeaderLine("\t//Proxy method"); GenerateProxyMethodAttributes(code); GenerateProxyMethodModifiers(code); // method signature itself GenerateProxyMethodSignature(code); GenerateProxyMethodBody(code); return(code.BuildString()); }
private void GenerateProxyMethodSignature(CodeBuilder code) { code.AddHeader(" "); code.AddHeader(_methodInfo.ReturnType); code.AddHeader(_methodInfo.MethodName); code.AddHeader(_methodInfo.GenericParams); code.AddHeader(GetProxyMethodParamsList().ToFullString()); if (!string.IsNullOrEmpty(_methodInfo.ConstraintClauses)) { code.AddHeader("\t\t"); code.AddHeaderLine(_methodInfo.ConstraintClauses); } }
private string GenerateProxyMethods(List <CalculatorMethodGenerator> methodGens) { bool alwaysExpose = methodGens.Count == 1; var code = new CodeBuilder(); foreach (var methodGen in methodGens) { if (alwaysExpose || methodGen.MethodInfo.CodeGenProps.ExposeAsEntryPoint) { code.AddHeaderLine(methodGen.GenerateProxyMethod()); } } return(code.BuildString()); }
private string GenerateAll() { var allCode = new CodeBuilder(); var classInfo = _classInfo; var classSyntax = _classInfo.ContainingClassSyntax; var namespaceName = classInfo.ContainingClassSymbol.ContainingNamespace.ToDisplayString(); var methodGens = _methodInfos.Select(mi => new CalculatorMethodGenerator(_context, classInfo, mi)).ToList(); var targetMethods = new TargetMethodsInfo(_methodInfos); // copy all the usings from the original file var root = (CompilationUnitSyntax)classSyntax.SyntaxTree.GetRoot(); allCode.AddHeaderLine(root.Usings.ToFullString()); allCode.AddHeaderLine(); // namespace allCode.AddBlockHeader($"namespace {namespaceName}"); // class declaration var classModifiersString = classSyntax.Modifiers.ToFullString(); if (!classModifiersString.Contains("partial")) { _context.ReportUnsupportedSyntaxError(classSyntax, $"Target class '{_classInfo.ContainingClassSymbol.Name}' for '{classInfo.RecursionId}' is not partial"); return(null); } allCode.AddBlockHeader($"{classModifiersString} class {_classInfo.ContainingClassSymbol.Name}"); allCode.AddHeaderLine(); allCode.AddHeaderLine(GenerateProxyMethods(methodGens)); allCode.AddHeaderLine(); allCode.AddHeaderLine(GenerateParamsStructs(classInfo, methodGens)); allCode.AddHeaderLine(); allCode.AddHeaderLine(GenerateCalculatorClass(classInfo, methodGens, targetMethods)); return(allCode.BuildString()); }
private void GenerateProxyMethodAttributes(CodeBuilder code) { // copy all attributes as is except for our target attribute foreach (var attrListSyntax in _methodInfo.MethodSyntax.AttributeLists) { var rawAttrList = attrListSyntax.Attributes .Where(asy => { var attrConstrSymbol = (IMethodSymbol)_methodInfo.MethodModel.GetSymbolInfo(asy).Symbol; ITypeSymbol?attrSymbol = attrConstrSymbol.ReceiverType; return(!attrSymbol.MatchesRuntimeType(SafeRecursionGenerator.TargetAttribute)); }).ToList(); if (rawAttrList.Any()) { // calling GetSeparators on the original list is a hack but seems to work so far var attrList = SyntaxFactory.SeparatedList(rawAttrList, attrListSyntax.Attributes.GetSeparators()); // remove the trivia like #region that might be attached to the first attribute var attrs = attrListSyntax.Update(attrListSyntax.OpenBracketToken, attrListSyntax.Target, attrList, attrListSyntax.CloseBracketToken).WithoutTrivia(); code.AddHeaderLine("\t" + attrs.ToFullString()); } } }
internal string GenerateCalculatorMethodBody(TargetMethodsInfo targetMethods) { var code = new CodeBuilder(); // unpack params code.AddHeaderLine("\t\t// unpack params"); code.AddHeaderLine(string.Join("\n", _methodInfo.MethodSyntax.ParameterList.Parameters.Select(parameterSyntax => { var paramSymbol = _methodInfo.MethodModel.GetDeclaredSymbol(parameterSyntax); return($"\t\tvar {paramSymbol.Name} = {ParamsVarName}.{paramSymbol.Name};"); }))); // find all recursive invocations var nodesToReplace = CollectInvocationsWalker.CollectInvocations(_context, _methodInfo, targetMethods); if (nodesToReplace.ContainsCriticalFailure) { _context.Log(_methodInfo.MethodSyntax.GetLocation(), $"An unsupported code was found for '{_methodInfo.MethodSymbol.Name}'. Abort processing."); return(null); } _context.Log(_methodInfo.MethodSyntax.GetLocation(), $"'{_methodInfo.MethodSymbol.Name}':" + $" {nodesToReplace.VoidCalls.Count} void call(s)" + $" {nodesToReplace.Assignments.Count} assignment(s)" + $", {nodesToReplace.DeclarationAndAssignments.Count} var declaration(s)" + $", {nodesToReplace.ReturnsRecursive.Count} recursive call(s) in return(s)" + $", {nodesToReplace.Returns.Count} return(s)"); // process all supported kinds of recursive invocations var allNodesToReplace = nodesToReplace.Returns.Cast <StatementSyntax>() .Concat(nodesToReplace.ReturnsRecursive) .Concat(nodesToReplace.VoidCalls.Select(t => t.containingStatement)) .Concat(nodesToReplace.Assignments.Select((t) => t.containingStatement)) .Concat(nodesToReplace.DeclarationAndAssignments.Select(t => t.containingStatement)); var newRoot = _methodInfo.MethodSyntax.Body.ReplaceNodes(allNodesToReplace, (origNode, curNode) => { // _context.Log("!! " + origNode.Kind() + " " + origNode.ToFullStringTrimmed()); switch (origNode.Kind()) { case SyntaxKind.ReturnStatement: return(ReplaceReturn(targetMethods, (ReturnStatementSyntax)origNode, (ReturnStatementSyntax)curNode)); case SyntaxKind.ExpressionStatement: var origExpressionNode = (ExpressionStatementSyntax)origNode; var expression = origExpressionNode.Expression; switch (expression.Kind()) { case SyntaxKind.InvocationExpression: return(ReplaceVoidCall(targetMethods, origExpressionNode, (ExpressionStatementSyntax)curNode)); case SyntaxKind.SimpleAssignmentExpression: return(ReplaceAssignment(targetMethods, origExpressionNode, (ExpressionStatementSyntax)curNode)); default: throw new AntiSOGenException($"Unexpected expression kind {expression.Kind()} to replace {origNode.ToFullStringTrimmed()}"); } case SyntaxKind.LocalDeclarationStatement: return(ReplaceLocalVarDeclrAssignment(targetMethods, (LocalDeclarationStatementSyntax)origNode, (LocalDeclarationStatementSyntax)curNode)); default: throw new AntiSOGenException($"Unexpected statement kind {origNode.Kind()} to replace {origNode.ToFullStringTrimmed()}"); } }); code.AddHeaderLine("\t\t// method body"); code.AddHeader(newRoot.ToFullString()); return(code.BuildString()); }
private string GenerateDispatchStruct(RecursiveCalculatorClassInfo classInfo, IReadOnlyList <CalculatorMethodGenerator> methodGens) { var code = new CodeBuilder(); // generic struct can't have an explicit layout so use just a simple class in such case instead bool isGeneric = !string.IsNullOrWhiteSpace(classInfo.GenericParams); if (!isGeneric) { code.AddHeaderLine("\t[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]"); code.AddHeaderLine($"\tprivate struct {classInfo.ParamsStructName} {classInfo.GenericParams}"); } else { code.AddHeaderLine($"\tprivate class {classInfo.ParamsStructName} {classInfo.GenericParams}"); } if (!string.IsNullOrEmpty(classInfo.ConstraintClauses)) { code.AddHeaderLine($"\t\t{classInfo.ConstraintClauses}"); } code.AddBlockHeader("\t"); // the call site dispatch enum field if (!isGeneric) { code.AddHeaderLine("\t\t[System.Runtime.InteropServices.FieldOffset(0)]"); } code.AddHeaderLine($"\t\tinternal readonly {classInfo.CallSiteEnumName} {RecursiveCalculatorClassInfo.CallSiteEnumFieldName};"); // params fields for each method foreach (var methodGen in methodGens) { code.AddHeaderLine(); if (!isGeneric) { code.AddHeaderLine($"\t\t[System.Runtime.InteropServices.FieldOffset(sizeof({classInfo.CallSiteEnumName}))]"); } code.AddHeaderLine( $"\t\tinternal readonly {methodGen.MethodInfo.MethodParamsStructName}{methodGen.MethodInfo.GenericParams} {methodGen.MethodInfo.MethodParamsStructName};"); } // constructors: 1 for each method foreach (var methodGen in methodGens) { var methodInfo = methodGen.MethodInfo; // constructor header //TODO: improve CodeBuilder to use code.AddBlockHeader() here code.AddHeaderLine($"\t\tinternal {classInfo.ParamsStructName}({methodInfo.MethodParamsStructName}{methodInfo.GenericParams} {methodInfo.MethodParamsStructName})"); if (!isGeneric) { code.AddHeaderLine("\t\t\t:this()"); } code.AddHeaderLine("\t\t{"); // constructor body code.AddHeaderLine($"\t\t\tthis.{RecursiveCalculatorClassInfo.CallSiteEnumFieldName} = {classInfo.CallSiteEnumName}.{methodInfo.MethodEnumValueName};"); code.AddHeaderLine($"\t\t\tthis.{methodInfo.MethodParamsStructName} = {methodInfo.MethodParamsStructName};"); code.AddHeaderLine("\t\t}"); // using an implicit operator is a small hack that simplifies code rewriting code.AddHeaderLine( $"\t\tpublic static implicit operator {classInfo.ParamsStructName}{classInfo.GenericParams}({methodInfo.MethodParamsStructName}{methodInfo.GenericParams} {methodInfo.MethodParamsStructName}) " + $" => new {classInfo.ParamsStructName}{classInfo.GenericParams}({methodInfo.MethodParamsStructName});"); } return(code.BuildString()); }