Search code examples
c#csharp-source-generator

Get base class properties in C# source generation - IIncrementalGenerator


I have following Execute method in my source generator. I can get all the properties (x.Item1.Members.AsEnumerable()...) of my class. But I can't manage to get the properties of the base class too. How can I list all properties of the base class?

    private static void Execute(Compilation compilation, ImmutableArray<(ClassDeclarationSyntax, AttributeData)> classes, SourceProductionContext context)
    {
        foreach (var (x, i) in classes.Select((x, i) => (x, i)))
        {
            TypedConstant aggregateParam = x.Item2.ConstructorArguments[0];
            if (aggregateParam.Kind == TypedConstantKind.Primitive &&
                aggregateParam.Value is string fullyQualifiedAggregateName)
            {
                var aggregateName = "Aggregate";

                var props = x.Item1.Members.AsEnumerable().Where(o => o.IsKind(SyntaxKind.PropertyDeclaration));

                var sb = new StringBuilder();

                context.AddSource(
                    $"generated_{aggregateName}_{i}.g.cs",
                    sb.ToString());
            }
        }
    }

Update

        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            IncrementalValuesProvider<(ClassDeclarationSyntax, AttributeData)> classDeclarations = context.SyntaxProvider
                .ForAttributeWithMetadataName(
                    EventApplyAttribute,
                    predicate: static (node, _) => node is ClassDeclarationSyntax,
                    transform: static (ctx, ct) => GetSemanticTargetForGeneration(ctx, ct))
                .Where(m => m.Item1 is not null && m.Item2 is not null);

            IncrementalValueProvider<(Compilation, ImmutableArray<(ClassDeclarationSyntax, AttributeData)>)> compilationAndClasses
                = context.CompilationProvider.Combine(classDeclarations.Collect());

            context.RegisterSourceOutput(compilationAndClasses,
                static (spc, source) => Execute(source.Item1, source.Item2, spc));
        }

        private static (ClassDeclarationSyntax, AttributeData) GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken ct)
        {
            if (context.TargetNode is not ClassDeclarationSyntax classDeclaration)
            {
                return (null, null);
            }

            AttributeData? attribute = context.Attributes.FirstOrDefault(a => a.AttributeClass?.Name == "EventApplyAttribute");

            return (classDeclaration, attribute);
        }

Solution

  • Really, you want the semantic model for this - not the syntax model. And you don't need to wait for Execute for that - when using incremental generators, presumably you're using CreateSyntaxProvider, where you provide a pre-filter on the syntax model, and a transform that is more flexible. In that transform, you can access the semantic model. This is advantageous for performance, as it allows any processing in your transform step to be cached as long as the relevant syntax tree is not invalidated.

    During transform, you can get the semantic model via ctx.SemanticModel - you probably want ctx.SemanticModel.GetDeclaredSymbol(...) on the ClassDeclarationSyntax. Since this is declaring a type, the symbol it gives you should be an ITypeSymbol and very likely a INamedTypeSymbol. This means you have now full access to the type data, including .BaseType.

    But: at either transform or execute, you can get the semantic model

    You can also get the semantic model at Execute via state.Compilation.GetSemanticModel(...) passing in the syntax-tree for a relevant syntax-node, but: if you're doing that, you're probably repeating a lot of work, adversely impacting IDE performance.