Roslyn分析器编辑时深层基类解析失效,报SyntaxTree不属于编译错误
问题根因
编辑场景下Roslyn会使用增量编译快照执行分析,此时compilationContext.Compilation仅包含当前未修改、或修改后纳入本次增量编译的语法树。基类对应的源文件如果在编辑过程中发生过修改,其DeclaringSyntaxReferences指向的语法树可能不在当前编译快照的语法树集合中,直接调用compilation.GetSemanticModel()就会抛出「SyntaxTree不属于当前编译」的ArgumentException。全量编译时所有项目内语法树都被纳入编译,因此不会触发该错误。
现有实现的问题
- 未校验基类声明对应的语法树是否属于当前编译,直接获取SemanticModel
- 自行遍历语法树根节点查找类声明的逻辑冗余,且容易引入不必要的性能开销
- 无增量编译场景下的降级处理逻辑,遇到不完整快照时直接崩溃
修复方案
- 调用
GetSemanticModel前先通过Compilation.ContainsSyntaxTree()校验语法树归属,不属于当前编译的直接跳过本次分析 - 直接使用
DeclaringSyntaxReference.GetSyntax()获取类声明节点,无需自行遍历语法树 - 增加增量场景降级逻辑,遇到不完整编译快照时提前终止分析,避免报错
- 可缓存已获取的SemanticModel,减少重复实例化开销
修改后的代码示例
public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(compilationContext => { // 缓存SemanticModel减少重复创建 var semanticModelCache = new Dictionary<SyntaxTree, SemanticModel>(); compilationContext.RegisterSyntaxNodeAction(syntaxNodeAnalysisContext => { syntaxNodeAnalysisContext.CancellationToken.ThrowIfCancellationRequested(); var classDeclaration = (ClassDeclarationSyntax)syntaxNodeAnalysisContext.Node; var meAndBaseClasses = GetClassDeclarationsDeep(classDeclaration, compilationContext.Compilation, semanticModelCache, syntaxNodeAnalysisContext.CancellationToken); // 后续分析逻辑 }, SyntaxKind.ClassDeclaration); }); } protected IEnumerable<ClassDeclarationSyntax> GetClassDeclarationsDeep(ClassDeclarationSyntax classDeclaration, Compilation compilation, Dictionary<SyntaxTree, SemanticModel> semanticModelCache, CancellationToken cancellationToken) { if (!semanticModelCache.TryGetValue(classDeclaration.SyntaxTree, out var currentSemanticModel)) { currentSemanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree); semanticModelCache[classDeclaration.SyntaxTree] = currentSemanticModel; } foreach (var type in classDeclaration.GetMeAndBaseTypes(currentSemanticModel)) { cancellationToken.ThrowIfCancellationRequested(); foreach (var partialType in GetClassDeclarations(type, compilation, semanticModelCache, cancellationToken)) { yield return partialType; } } } public static IEnumerable<INamedTypeSymbol> GetMeAndBaseTypes(this ClassDeclarationSyntax classDeclaration, SemanticModel semanticModel) { return GetMeAndBaseTypes(semanticModel.GetDeclaredSymbol(classDeclaration)); } protected IEnumerable<ClassDeclarationSyntax> GetClassDeclarations(INamedTypeSymbol classType, Compilation compilation, Dictionary<SyntaxTree, SemanticModel> semanticModelCache, CancellationToken cancellationToken) { // 跳过元数据程序集中的类型,无语法声明 if (classType.Locations.All(l => l.IsInMetadata)) yield break; foreach (var syntaxRef in classType.DeclaringSyntaxReferences) { cancellationToken.ThrowIfCancellationRequested(); var tree = syntaxRef.SyntaxTree; // 关键校验:语法树不属于当前编译直接跳过 if (!compilation.ContainsSyntaxTree(tree)) continue; if (!semanticModelCache.TryGetValue(tree, out var semanticModel)) { semanticModel = compilation.GetSemanticModel(tree); semanticModelCache[tree] = semanticModel; } if (syntaxRef.GetSyntax(cancellationToken) is ClassDeclarationSyntax classDecl && SymbolEqualityComparer.Default.Equals(classType, semanticModel.GetDeclaredSymbol(classDecl, cancellationToken))) { yield return classDecl; } } } public static IEnumerable<INamedTypeSymbol> GetMeAndBaseTypes(this INamedTypeSymbol type) { while (type != null) { yield return type; type = type.BaseType; } }
额外说明
如果业务逻辑要求必须拿到所有基类的信息才能执行分析,可以在发现语法树缺失时直接终止当前分析,Roslyn后续会在更新的编译快照中重新触发分析规则,不会影响最终结果的准确性。
内容的提问来源于stack exchange,提问作者Lori
相关产品推荐
相关产品推荐

