1

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);
        }
3
  • You MUST NOT include Compilation in the incremental pipeline. Commented Oct 9, 2023 at 9:52
  • @Youssef13 when you use RegisterImplementationSourceOutput in the incremental pipeline, it literally gives you the Compilation in the callback, which is presumably what Dani is talking about here, since that's also the API that gives you ImmutableArray<TSource> for your TSource (in this case ClassDeclarationSyntax); it is literally impossible to avoid the Compilation Commented Oct 9, 2023 at 9:55
  • There are cases where it's hard/impossible to avoid, but those are considered cases not suitable for source generators. But generally speaking, compilation should never be included in the pipeline. You get it in the transform, but you shouldn't return it back, instead, the information needed should be extracted into a value-equatable model. Commented Oct 9, 2023 at 10:00

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

3 Comments

To add to that, the transform step should produce a value-equatable custom model that must not include compilation or symbols. Instead, it should extract what it needs from compilation/symbols.
@Youssef13 out of curiosity, any citation on that?
Well, that info is quite hidden with chats with Roslyn team over the Discord channel. But there is a hint on that at github.com/dotnet/roslyn/blob/main/docs/features/…. btw, that is why it's difficult to write incremental generators or convert existing non-incremental to incremental ones. You can also see an example GH issue for a generator that includes compilation in the pipeline github.com/k94ll13nn3/AutoConstructor/issues/82.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.