List <IdentifierNameSyntax> FilterCandidates(GeneratorExecutionContext context, IEnumerable <IdentifierNameSyntax> candidates) { var filteredCandidates = new List <IdentifierNameSyntax>(); foreach (var candidate in candidates) { var model = context.Compilation.GetSemanticModel(candidate.SyntaxTree); var containingClassDeclaration = candidate.Ancestors().OfType <ClassDeclarationSyntax>().FirstOrDefault(); if (containingClassDeclaration != null) { var candidateContainingClassSymbol = model.GetDeclaredSymbol(containingClassDeclaration); if (candidateContainingClassSymbol != null && (candidateContainingClassSymbol.Is("Unity.Entities.SystemBase") || candidateContainingClassSymbol.Is("Unity.Entities.JobComponentSystem"))) { if (!containingClassDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) { var lambdaJobName = "Entities.ForEach"; if (candidate.Identifier.ToString() == "Job") { lambdaJobName = "Job.WithCode"; } context.LogError("SG0001", "LambdaJobs", $"{lambdaJobName} is in system {candidateContainingClassSymbol.Name}, but {candidateContainingClassSymbol.Name} is not defined with partial. Source generated lambda jobs must exist in a partial system class. Please add the `partial` keyword as part of the class definition.", candidate.GetLocation()); } else { filteredCandidates.Add(candidate); } } } } return(filteredCandidates); }
public void Execute(GeneratorExecutionContext context) { bool IsGeneratedAuthoringComponentAttribute(AttributeData data) { return(data.AttributeClass.ContainingAssembly.Name == "Unity.Entities" && data.AttributeClass.Name == "GenerateAuthoringComponentAttribute"); } try { if (context.Compilation.Assembly.Name.Contains("CodeGen.Tests")) { return; } var authoringComponentReceiver = (AuthoringComponentReceiver)context.SyntaxReceiver; if (!authoringComponentReceiver.CandidateSyntaxes.Any() || !context.Compilation.ReferencedAssemblyNames.Any(n => n.Name == "Unity.Entities.Hybrid")) { // TODO: I believe it is valid to have GenerateAuthoringComponent but not reference entities (DocCodeSamples.Tests currently does this). // We should probably throw a warning here though. //throw new ArgumentException("Using the [GenerateAuthoringComponent] attribute requires a reference to the Unity.Entities.Hybrid assembly."); return; } SourceGenHelpers.LogInfo($"Source generating assembly {context.Compilation.Assembly.Name} for authoring components..."); var stopwatch = Stopwatch.StartNew();; foreach (var candidateSyntax in authoringComponentReceiver.CandidateSyntaxes) { var semanticModel = context.Compilation.GetSemanticModel(candidateSyntax.SyntaxTree); var candidateSymbol = semanticModel.GetDeclaredSymbol(candidateSyntax); LogInfo($"Parsing authoring component {candidateSymbol.Name}"); if (!candidateSymbol.GetAttributes().Any(IsGeneratedAuthoringComponentAttribute)) { continue; } var authoringComponent = new AuthoringComponent(candidateSyntax, candidateSymbol, context); CheckValidity(authoringComponent); var compilationUnit = CompilationUnit().AddMembers(GenerateAuthoringTypeFrom(authoringComponent)) .NormalizeWhitespace(); var generatedSourceHint = candidateSyntax.SyntaxTree.GetGeneratedSourceFileName(context.Compilation.Assembly); var generatedSourceFullPath = candidateSyntax.SyntaxTree.GetGeneratedSourceFilePath(context.Compilation.Assembly); var sourceTextForNewClass = compilationUnit.GetText(Encoding.UTF8); sourceTextForNewClass = sourceTextForNewClass.WithInitialLineDirectiveToGeneratedSource(generatedSourceFullPath); context.AddSource(generatedSourceHint, sourceTextForNewClass); File.WriteAllLines( generatedSourceFullPath, sourceTextForNewClass.ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)); } stopwatch.Stop(); SourceGenHelpers.LogInfo($"TIME : AuthoringComponent : {context.Compilation.Assembly.Name} : {stopwatch.ElapsedMilliseconds}ms"); } catch (Exception exception) { //context.WaitForDebuggerInAssembly(); context.LogError("SGICE002", "Authoring Component", exception.ToString(), context.Compilation.SyntaxTrees.First().GetRoot().GetLocation()); } }
public void Execute(GeneratorExecutionContext sourceGeneratorContext) { try { var iJobEntityReceiver = (IJobEntityReceiver)sourceGeneratorContext.SyntaxReceiver; if (!iJobEntityReceiver.JobEntityTypeCandidates.Any()) { return; } var syntaxTreeToCandidates = iJobEntityReceiver .JobEntityTypeCandidates .GroupBy(j => j.TypeNode.SyntaxTree) .ToDictionary(group => group.Key, group => group.ToArray()); foreach (var kvp in syntaxTreeToCandidates) { var syntaxTree = kvp.Key; var candidates = kvp.Value; try { foreach (var(typeNode, onUpdateMethodNode) in candidates) { SemanticModel semanticModel = sourceGeneratorContext.Compilation.GetSemanticModel(typeNode.SyntaxTree); ITypeSymbol candidateTypeSymbol = (ITypeSymbol)semanticModel.GetDeclaredSymbol(typeNode); if (!candidateTypeSymbol.InheritsFromInterface("Unity.Entities.IJobEntity")) { continue; } var jobEntityDescription = new JobEntityDescription(typeNode, onUpdateMethodNode, candidateTypeSymbol, sourceGeneratorContext); if (jobEntityDescription.FieldDescriptions.Any(f => !f.IsValueType)) { throw new ArgumentException("IJobEntity types may only contain value-type fields."); } var jobEntityData = new JobEntityData { UserWrittenJobEntity = jobEntityDescription, GeneratedIJobEntityBatchType = JobEntityBatchTypeGenerator.GenerateFrom(jobEntityDescription), }; AllJobEntityTypes[jobEntityDescription.DeclaringTypeFullyQualifiedName] = jobEntityData; AddJobEntityBatchSourceToContext(jobEntityData, sourceGeneratorContext); } } catch (Exception exception) { sourceGeneratorContext.LogError("SGICE001", "JobEntity", exception.ToString(), syntaxTree.GetRoot().GetLocation()); } } SyntaxNode[] entitiesInSystemBaseDerivedTypes = GetEntitiesInSystemBaseDerivedTypes( sourceGeneratorContext, iJobEntityReceiver.EntitiesGetterCandidates).ToArray(); if (!entitiesInSystemBaseDerivedTypes.Any()) { return; } var syntaxTreesToEntities = entitiesInSystemBaseDerivedTypes .GroupBy(e => e.SyntaxTree) .ToDictionary(group => group.Key, group => group.ToArray()); foreach (var kvp in syntaxTreesToEntities) { var syntaxTree = kvp.Key; var entityNodes = kvp.Value; try { foreach (SyntaxNode entityNode in entityNodes) { var systemBaseDescription = SystemBaseDescription.From(entityNode, sourceGeneratorContext); var updatedSystemBaseDescription = UpdatedSystemBaseTypeGenerator.GenerateFrom(systemBaseDescription); AddUpdatedSystemBaseSourceToContext( systemBaseDescription, updatedSystemBaseDescription, sourceGeneratorContext); } } catch (Exception exception) { sourceGeneratorContext.LogError("SGICE001", "JobEntity", exception.ToString(), syntaxTree.GetRoot().GetLocation()); } } } catch (Exception exception) { sourceGeneratorContext.LogError( "SG0002", "Unknown Exception", exception.ToString(), sourceGeneratorContext.Compilation.SyntaxTrees.First().GetRoot().GetLocation()); } }
public void Execute(GeneratorExecutionContext context) { try { if (context.Compilation.ReferencedAssemblyNames.All(n => n.Name != "Unity.Entities") || context.Compilation.Assembly.Name.Contains("CodeGen.Tests")) { return; } SourceGenHelpers.LogInfo($"Source generating assembly {context.Compilation.Assembly.Name} for lambda jobs..."); var stopwatch = Stopwatch.StartNew();; //context.WaitForDebugger("HelloCube"); // Setup project path for logging and emitting debug source // This isn't fantastic but I haven't come up with a better way to get the project path since we might be running out of process if (context.AdditionalFiles.Any()) { SourceGenHelpers.SetProjectPath(context.AdditionalFiles[0].Path); } // Do initial filter for early out var entitiesSyntaxReceiver = (EntitiesSyntaxReceiver)context.SyntaxReceiver; var lambdaJobCandidates = FilterCandidates(context, entitiesSyntaxReceiver.EntitiesGetterCandidates.Concat(entitiesSyntaxReceiver.JobGetterCandidates)); if (!lambdaJobCandidates.Any()) { return; } // Create map from SyntaxTrees to candidates var syntaxTreeToCandidates = lambdaJobCandidates.GroupBy(c => c.SyntaxTree).ToDictionary(group => group.Key, group => group.ToList()); // Outer loop - iterate over syntax tree foreach (var treeKVP in syntaxTreeToCandidates) { var syntaxTree = treeKVP.Key; var treeCandidates = treeKVP.Value; try { // Build up list of job descriptions inside of containing class declarations var classDeclarationToDescriptions = new Dictionary <ClassDeclarationSyntax, List <LambdaJobDescription> >(); var jobIndex = 0; foreach (var candidate in treeCandidates) { var declaringType = candidate.Ancestors().OfType <ClassDeclarationSyntax>().FirstOrDefault(); var containingMethod = candidate.Ancestors().OfType <MethodDeclarationSyntax>().FirstOrDefault(); if (declaringType == null || containingMethod == null) { continue; } SourceGenHelpers.LogInfo($"Parsing LambdaJobDescription in {declaringType.Identifier}.{containingMethod.Identifier}"); var jobDescription = LambdaJobDescription.From(candidate, context, jobIndex++); if (jobDescription == null) { continue; } if (classDeclarationToDescriptions.ContainsKey(declaringType)) { classDeclarationToDescriptions[declaringType].Add(jobDescription); } else { classDeclarationToDescriptions[declaringType] = new List <LambdaJobDescription>() { jobDescription } }; } // Inner loop - iterate through class descriptions with lambda jobs and generate new class declaration nodes var originalToGeneratedNode = new Dictionary <SyntaxNode, MemberDeclarationSyntax>(); foreach (var kvp in classDeclarationToDescriptions) { var classDeclaration = kvp.Key; var descriptionsInClass = kvp.Value; SourceGenHelpers.LogInfo($"Generating code for system type: {classDeclaration.Identifier}"); var newClassDeclaration = GenerateNewClassDeclarationForDescriptions(classDeclaration, descriptionsInClass); //SourceGenHelpers.LogInfo(newClassDeclaration.ToString()); originalToGeneratedNode[classDeclaration] = newClassDeclaration; } // recurse and create nodes down to our created system nodes var rootNodesWithGenerated = new List <MemberDeclarationSyntax>(); foreach (var child in syntaxTree.GetRoot().ChildNodes()) { if (child is NamespaceDeclarationSyntax || child is ClassDeclarationSyntax) { var generatedRootNode = ConstructTreeWithGeneratedNodes(child, originalToGeneratedNode); if (generatedRootNode != null) { rootNodesWithGenerated.Add(generatedRootNode); } } } if (rootNodesWithGenerated.Any()) { OutputGeneratedSyntaxTreeNodes(context, syntaxTree, rootNodesWithGenerated); } } catch (Exception exception) { context.LogError("SGICE001", "LambdaJobs", exception.ToString(), syntaxTree.GetRoot().GetLocation()); } } stopwatch.Stop(); SourceGenHelpers.LogInfo($"TIME : LambdaJobs : {context.Compilation.Assembly.Name} : {stopwatch.ElapsedMilliseconds}ms"); } catch (Exception exception) { context.LogError("SGICE001", "LambdaJobs", exception.ToString(), context.Compilation.SyntaxTrees.First().GetRoot().GetLocation()); } }