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