Генерация исходного кода: как задействовать типы из InvocationExpressionSyntax

Я пытаюсь написать генератор исходного кода, и мне нужны типы, которые участвуют в вызове метода расширения.

Проблема в том, что этот метод расширения генерируется самим генератором исходного кода. Поэтому, если я попытаюсь получить, я получу null в своем классе Generator.

Можно ли получить нужную мне информацию иначе?

Вот пример

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

В ConvertTo<T>()метод расширения генерируется из исходного генератора. Я могу найти правильный InvocationExpressionSyntax, но как я могу получить полностью квалифицированный тип someObject и OtherType?

Вот генератор

      [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 получить все свойства типов

1 ответ

Решение

Я нашел решение.

Прежде всего 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;
    }
}
Другие вопросы по тегам