简体   繁体   中英

Source generation: How to get involved types from InvocationExpressionSyntax

I'm try to write an source generator and need the types that involved in a extension method call.

The problem is, this extension method is generated by source generator itself. So if I try get ISymbol , I get null in my Generator class.

Is it possible to get the information I need otherwise?

Here is the example

var result = someObject.ConvertTo<OtherType>();

The ConvertTo<T>() extension method is generated from source generator. I can find the correct InvocationExpressionSyntax , but how can I get the fully qualified type of the someObject and OtherType ?

Here is the generator

[Generator]
public class ConvertGenerator : ISourceGenerator
{
    private const string defaultNamespace = "AutoGenerators";
    private const string extensionsClassName = "ConvertExtensions";
    private static readonly string _classText = @$"
namespace {defaultNamespace}
{{
public static class {extensionsClassName}
{{
    public static TDestination ConvertTo<TDestination>(this object source)
    {{
        /* generated */

        return default;
    }}
}} }}";

    public void Initialize(GeneratorInitializationContext context)
    {
#if DEBUG
        if (!Debugger.IsAttached)
        {
            Debugger.Launch();
        }
#endif
        // Register a syntax receiver that will be created for each generation pass
        context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // retrieve the populated receiver 
        if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
            return;

        // for testing
        var invocationSyntax = receiver.Methods.FirstOrDefault();
        if (invocationSyntax != null)
        {
           var semanticModel = context.Compilation.GetSemanticModel(invocationSyntax.SyntaxTree);
           
           // symbol is null here
           var symbol = semanticModel.GetSymbolInfo(invocationSyntax.Expression).Symbol;
           
           // TODO: how to get type description for sourceObjectName and destinationTypeName
           var convertInvocationString = invocationSyntax.ToString();
           var sourceObjectName = convertInvocationString.Substring(0, convertInvocationString.IndexOf('.'));
           var destTypeSubs = convertInvocationString.Substring(convertInvocationString.IndexOf('<') + 1);
           var destinationTypeName = destTypeSubs.Substring(0, destTypeSubs.IndexOf('(') - 1);
           
        }

        var classSource = _classText;
        context.AddSource($"{extensionsClassName}.cs", SourceText.From(classSource, Encoding.UTF8));
    }

    /// <summary>
    /// Created on demand before each generation pass
    /// </summary>
    class SyntaxReceiver : ISyntaxReceiver
    {
        public List<InvocationExpressionSyntax> Methods { get; } = new List<InvocationExpressionSyntax>();

        /// <summary>
        /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
        /// </summary>
        public void OnVisitSyntaxNode(SyntaxNode context)
        {
            // any field with at least one attribute is a candidate for property generation
            if (context is InvocationExpressionSyntax invocationExpressionSyntax
                && invocationExpressionSyntax.ToString().Contains("ConvertTo<"))
            {
                Methods.Add(invocationExpressionSyntax);
            }
        }
    }
}

Update: Also I think I need more then just the type. I need ISymbol to get all the properties of the types

Update 2: I did a small step by making the ConvertTo<T> method partial and reference the separat project with this method. I'm getting the IMethodSymbol now and have the ITypeSymbol for OtherType , but the ITypeSymbol for someObject is the object type, because of the extension method signature. But I need the concrete type symbol for someObject

I found the solution.

First of all the ConvertTo<T> method should be declared in my project as partial, so I can get ISymbol for the invocation. It gives me the ReturnType

var semanticModel = context.Compilation.GetSemanticModel(invocationSyntax.SyntaxTree);
var mapToSymbol = semanticModel.GetSymbolInfo(invocationSyntax.Expression).Symbol as IMethodSymbol;
var convertToType = mapToSymbol.ReturnType;

Then I can use the invocationSyntax.Expression to get the type of the someObject or the parameter of the extension method

var convertFromType = TryGetSourceType(invocationSyntax.Expression, semanticModel);

...

private static ITypeSymbol TryGetSourceType(ExpressionSyntax invocationExpression, SemanticModel semanticModel)
{
    switch (invocationExpression)
    {
        case MemberAccessExpressionSyntax memberAccessExpressionSyntax:
            var symbol = semanticModel.GetSymbolInfo(memberAccessExpressionSyntax.Expression).Symbol;
            return symbol switch
            {
                ILocalSymbol local => local.Type,
                IParameterSymbol param => param.Type,
                IFieldSymbol field => field.Type,
                IPropertySymbol prop => prop.Type,
                IMethodSymbol method => method.MethodKind == MethodKind.Constructor ? method.ReceiverType : method.ReturnType,
                _ => null
            };
        default:
            return null;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM