Генерация исходного кода: как задействовать типы из 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;
}
}