源代码生成:如何从 InvocationExpressionSyntax 获取涉及的类型

Source generation: How to get involved types from InvocationExpressionSyntax

我正在尝试编写源代码生成器并需要涉及扩展方法调用的类型。

问题是,这个扩展方法是由源代码生成器自己生成的。因此,如果我尝试获取 ISymbol,我会在生成器 class.

中获取 null

是否可以通过其他方式获得我需要的信息?

示例如下

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

ConvertTo<T>() 扩展方法是从源代码生成器生成的。我可以找到正确的 InvocationExpressionSyntax,但是如何获得 someObjectOtherType 的完全限定类型?

这是生成器

[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);
            }
        }
    }
}

更新: 另外我想我需要的不仅仅是类型。我需要 ISymbol 来获取类型

的所有属性

更新 2: 我做了一小步,使 ConvertTo<T> 方法部分化,并使用此方法引用单独的项目。我现在得到 IMethodSymbol 并且 ITypeSymbol 用于 OtherType,但是 ITypeSymbol 用于 someObjectobject 类型,因为扩展方法签名。但是我需要 someObject

的具体类型符号

我找到了解决方案。

首先,ConvertTo<T> 方法应该在我的项目中声明为部分方法,这样我就可以为调用获取 ISymbol。它给了我 ReturnType

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

然后我可以使用 invocationSyntax.Expression 来获取 someObject 的类型或扩展方法的参数

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;
    }
}