Search code examples
metaprogrammingaoproslyn

Using Roslyn, if I have an IdentifierNameSyntax, can I find the member type it refers to (field, property, method...)


I am attempting to use the Roslyn SDK and StackExchange.Precompilation (thank you!) to implement aspect-oriented programming in C#6. My specific problem right now is, starting with an IdentifierNameSyntax instance, I want to find the "member type" (method, property, field, var, etc.) that the identifier refers to. (How) can this be done?


Background:

The first proof-of-concept I am working on is some good old design-by-contract. I have a NonNullAttribute which can be applied to parameters, properties, or method return values. Along with the attribute there is a class implementing the StackExchange.Precompilation.ICompileModule interface, which on compilation will insert null checks on the marked parameters or return values.

This is the same idea as PostSharp's NonNullAttribute, but the transformation is being done on one of Roslyn's syntax trees, not on an already compiled assembly. It is also similar to Code Contracts, but with a declarative attribute approach, and again operating on syntax trees not IL.

For example, this source code:

[return: NonNull]
public string Capitalize([NonNull] string text) {
    return text.ToUpper();
}

will be transformed into this during precompilation:

[return: NonNull]
public string Capitalize([NonNull] string text) {
    if (Object.Equals(text, null)) 
        throw new ArgumentNullException(nameof(text));

    var result = text.ToUpper();
    if (Object.Equals(result, null))
        throw new PostconditionFailedException("Result cannot be null.");
    return result;
}

(PostconditionFailedException is a custom exception I made to compliment ArgumentException for return values. If there is already something like this in the framework please let me know.)

For properties with this attribute, there would be a similar transformation, but with preconditions and postconditions implemented separately in the set and get accessors, respectively.


The specific reason I need to find the "member type" of an identifier here is for an optimization on implementing postconditions. Note in the post-compilation sample above, the value that would have been returned is stored in a local variable, checked, and then the local is returned. This storage is necessary for transforming return statements that evaluate a method or complex expression, but if the returned expression is just a field or local variable reference, creating that temporary storage local is wasteful.

So, when the return statement is being scanned, I first check if the statement is of the form ReturnKeyword-IdentifierSyntaxToken-SemicolonToken. If so, I then need to check what that identifier refers to, so I avoid that local variable allocation if the referent is a field or var.

Update For more context, check out the project this is in reference to on GitHub.


Solution

  • You'll need to use SemanticModel.GetSymbolInfo to determine the symbol an identifier binds to.