如何用CSharpSyntaxRewriter在Roslyn中实现初始化器符号全限定?
问题描述
我在编写Roslyn源生成器时,需要获取字段的VariableDeclaratorSyntax.Initializer初始化器并输出到生成文件,要求将初始化器内所有符号转为全限定名(无需依赖using指令)。例如:
原代码:
using MyNamespace.SomeStuff; ... partial class Bar { [Convert] Foo _fooField = Foo.Baz; }
期望生成代码:
partial class Bar { MyNamespace.SomeStuff.Foo _fooField = MyNamespace.SomeStuff.Foo.Baz; }
我能通过IFieldSymbol轻松拿到字段类型的全限定名,但不知道怎么处理初始化器的全限定化——初始化器可以是任意内容,比如嵌套构造函数等。我试过自定义CSharpSyntaxRewriter,重写VisitMemberAccessExpression方法:
public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { var symbol = SemanticModel.GetSymbolInfo(node); if (symbol.Symbol is not null) return node.WithName(SyntaxFactory.IdentifierName(symbol.Symbol.ToString())); else return base.VisitMemberAccessExpression(node); }
但得到的结果是Foo.MyNamespace.SomeStuff.Foo.Baz,不是正确的全限定名。我知道应该用WithExpression而非WithName,但不确定怎么处理任意类型的表达式。求解决方案或者更好的实现方式?
附完整上下文代码:
[Generator] public class StyleSourceGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // 查找所有标记了[Convert]的字段 var fields = context.SyntaxProvider.ForAttributeWithMetadataName(typeof(ConvertAttribute).FullName, (node, token) => { return node is VariableDeclaratorSyntax; }, (ctx, token) => { var syntax = (VariableDeclaratorSyntax)ctx.TargetNode; syntax.Initializer.Value.ToString(); // 这里会打印Foo.Baz,但我需要MyNamespace.SomeStuff.Foo.Baz }); } }
解决方案
核心是遍历初始化器语法树,把每个符号节点替换为对应的全限定名语法节点,而非只修改成员访问的局部。需要改进CSharpSyntaxRewriter,处理不同类型的表达式节点,同时借助语义模型获取正确的符号全限定名。
1. 实现完整的符号替换重写器
创建重写器,不仅处理成员访问表达式,还要处理标识符名称(比如单独的类型名、常量名),确保所有符号都转为全限定形式:
public class FullyQualifiedNameRewriter : CSharpSyntaxRewriter { private readonly SemanticModel _semanticModel; private readonly CancellationToken _cancellationToken; public FullyQualifiedNameRewriter(SemanticModel semanticModel, CancellationToken cancellationToken) { _semanticModel = semanticModel; _cancellationToken = cancellationToken; } // 处理单独的标识符(比如Foo) public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) { var symbolInfo = _semanticModel.GetSymbolInfo(node, _cancellationToken); if (symbolInfo.Symbol is ISymbol symbol && !symbol.IsImplicitlyDeclared) { return BuildFullyQualifiedName(symbol); } return base.VisitIdentifierName(node); } // 处理成员访问表达式(比如Foo.Baz) public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { // 先递归处理表达式部分,确保嵌套符号也被替换 var rewrittenExpr = Visit(node.Expression) as ExpressionSyntax; if (rewrittenExpr == null) { return base.VisitMemberAccessExpression(node); } var symbolInfo = _semanticModel.GetSymbolInfo(node, _cancellationToken); if (symbolInfo.Symbol is ISymbol symbol && !symbol.IsImplicitlyDeclared) { // 直接生成全限定名节点,不用保留原有成员访问结构 return BuildFullyQualifiedName(symbol); } return node.WithExpression(rewrittenExpr); } // 辅助方法:从ISymbol生成全限定名语法节点 private NameSyntax BuildFullyQualifiedName(ISymbol symbol) { var nameParts = symbol.ToDisplayParts(SymbolDisplayFormat.FullyQualifiedFormat) .Where(part => part.Kind != SymbolDisplayPartKind.Punctuation) .Select(part => SyntaxFactory.IdentifierName(part.Text)); return nameParts.Aggregate<IdentifierNameSyntax, NameSyntax>( (current, next) => SyntaxFactory.QualifiedName(current, next) ); } }
2. 在生成器中使用重写器
在SyntaxProvider的回调里,获取语义模型,初始化重写器后处理初始化器表达式:
var fields = context.SyntaxProvider.ForAttributeWithMetadataName( typeof(ConvertAttribute).FullName, (node, token) => node is VariableDeclaratorSyntax, (ctx, token) => { var declarator = (VariableDeclaratorSyntax)ctx.TargetNode; var semanticModel = ctx.SemanticModel; ExpressionSyntax? rewrittenInitializer = null; if (declarator.Initializer?.Value != null) { var rewriter = new FullyQualifiedNameRewriter(semanticModel, token); rewrittenInitializer = rewriter.Visit(declarator.Initializer.Value) as ExpressionSyntax; } // 获取字段类型的全限定名 var fieldSymbol = semanticModel.GetDeclaredSymbol(declarator, token) as IFieldSymbol; var fullyQualifiedType = fieldSymbol?.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); // 组装生成的字段声明示例 if (fullyQualifiedType != null && rewrittenInitializer != null) { var fieldDeclaration = SyntaxFactory.FieldDeclaration( SyntaxFactory.VariableDeclaration( SyntaxFactory.ParseTypeName(fullyQualifiedType) ).WithVariables( SyntaxFactory.SingletonSeparatedList( declarator.WithInitializer( SyntaxFactory.EqualsValueClause(rewrittenInitializer) ) ) ) ).WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PartialKeyword))); // 后续可将fieldDeclaration整合到生成的语法树中 } return declarator; });
关键细节
SymbolDisplayFormat.FullyQualifiedFormat:Roslyn内置的全限定名格式,能正确生成包含命名空间的完整名称,避免手动拼接出错。- 递归处理:重写器会先处理成员访问的左半部分(比如
Foo.Baz中的Foo),确保嵌套符号也被转为全限定名。 - 扩展支持:如果需要处理构造函数调用(
new Foo()),可以添加VisitObjectCreationExpression方法,同样通过语义模型获取类型符号后替换为全限定名。
内容的提问来源于stack exchange,提问作者Aaa
相关产品推荐
相关产品推荐

