Search code examples
c#roslynsourcegeneratorscsharp-source-generatorincremental-generator

GetOperation returning null for VariableDeclaratorSyntax, VariableDeclarationSyntax, and FieldDeclarationSyntax


In a C# incremental generator I am authoring, beginning from an ArgumentSyntax, I am trying to inspect the inline initializer for a readonly field referenced as a method argument. Understanding that value inspection is impossible, I thought that I could traverse the syntax tree to find the definition of the field, and if it had an inline initializer, then I could at least use that value (with the understanding that the field could be overwritten in a constructor).

var argumentOperation = (IArgumentOperation)context.SemanticModel.GetOperation(argumentSyntax, cancellationToken)!;
switch (argumentOperation.Parameter?.Name)
{
    case "argumentToInspect":
        break;
    default:
        return;
}
if (argumentOperation.Value is not IFieldReferenceOperation fieldReferenceOperation ||
    !fieldReferenceOperation.Field.IsReadOnly)
{
    return;
}
var fieldSyntaxNode = fieldReferenceOperation.Field.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
Debug.WriteLine($"fieldSyntaxNode kind: {fieldSyntaxNode.Kind()}"); // VariableDeclarator
Debug.WriteLine($"parent kind: {fieldSyntaxNode.Parent.Kind()}"); // VariableDeclaration
Debug.WriteLine($"grandparent kind: {fieldSyntaxNode.Parent.Parent.Kind()}"); // FieldDeclaration

var nodeOperation = context.SemanticModel.GetOperation(fieldSyntaxNode, cancellationToken); // null
var parentOperation = context.SemanticModel.GetOperation(fieldSyntaxNode.Parent, cancellationToken); // null
var grandparentOperation = context.SemanticModel.GetOperation(fieldSyntaxNode.Parent.Parent, cancellationToken); // null

The field in question is declared such as:

private static readonly MyClass instance = new();

Which is then referenced as:

MyMethod(instance);

Given that I am able to find the FieldDeclarationSyntax (fieldSyntaxNode.Parent.Parent), I'm not sure why I'm unable to find any relevant IOperations (e.g., IVariableDeclaratorOperation, IVariableDeclarationOperation, or IFieldInitializerOperation). The field declaration and field reference passed as an argument (to MyMethod) are in the same file, so my understanding is that the same semantic model (context.SemanticModel) should be able to produce the relevant operations, but I am only getting null from GetOperation on the field declaration.

Am I approaching this the wrong way? Given that I can find the syntax of the field declaration, I could try to manually parse the text for the information that I need, but I don't understand why I can't use the IOperation API to inspect the field initializer.


Solution

  • While I am still unclear as to which nodes might generate either IVariableDeclaratorOperation or IVariableDeclarationOperation, I was able to discover the IFieldInitializerOperation (which is ultimately the one I was actually looking for).

    In the case of an inline field initializer, GetOperation will yield the IFieldInitializerOperation for the EqualsValueClauseSyntax node, which is a descendant node of the FieldDeclarationSyntax.

    Tangentially related to what I originally asked, I can discover an initializing assignment in the constructor by finding an AssignmentExpressionSyntax with Kind of SimpleAssignmentExpression which has a ConstructorDeclarationSyntax ancestor node. From this node I can receive an ISimpleAssignmentOperation with a Target of IFieldReferenceOperation.

    Given the Value of the IFieldInitializationOperation and/or ISimpleAssignmentOperation, I am able to use this information to more powerfully (and correctly) parse the initialization of readonly fields.