PsiElement.firstChild与children.first()不等价原因及KtElement差异场景
1. 二者不等价的核心原因
两个API属于PSI框架的不同层级,遍历逻辑从设计上就不一致:
PsiElement.firstChild是AST底层节点的原生方法,无任何过滤逻辑,直接返回当前节点在原始AST子节点链表的第一个节点。这个节点可能是承载语义的PSI元素,也可能是花括号、括号、关键字、分号、空白、注释这类仅作为语法标记的无实义叶子节点。PsiElement.children是PSI语义层对外暴露的遍历属性,默认会过滤掉所有非语义的语法标记节点、空白节点、注释节点,仅返回承载代码结构逻辑的PsiElement实例,children.first()自然是过滤完成后的第一个语义节点。
你观察到的KtBlockExpression返回差异是逻辑必然:
KtBlockExpression的AST结构首个子节点是对应左花括号{的叶子节点,属于纯语法标记,会被children的过滤规则跳过;后续还会跳过花括号后的换行、缩进空白节点,最终定位到代码块内的第一个表达式,因此才会出现两个API返回结果不同的现象。而测试的KtTypeReference,其AST首个子节点本身就是类型名、类型投影这类语义PSI节点,开头没有夹着会被过滤的无实义节点,因此两个API返回结果完全一致。
2. KtElement子类的等价/差异判断标准
不需要枚举所有KtElement子类的表现,根据节点AST结构可以直接判定:
- 结果等价场景:当前KtElement的AST第一个子节点本身就是语义PSI节点,节点起始位置没有任何会被
children过滤的无实义节点(标点、关键字、空白、注释等),两个方法会返回同一个实例。除了KtTypeReference,无前置注释/注解的简单标识符表达式KtSimpleNameExpression、纯字符串字面量KtStringLiteralExpression等节点通常都符合这个特征。 - 结果存在差异场景:只要当前KtElement的AST起始位置存在任意一个会被
children过滤的节点,二者返回结果一定不同。常见的触发差异的节点包括:- 包裹类语法标记:比如代码块的
{}、参数列表的()、数组下标访问的[] - 声明/表达式开头的关键字:比如
val/var、return、if/else、fun、class - 节点开头的空白、换行、缩进、块注释/行注释
- 访问修饰符、suspend/inline这类修饰符关键字节点
- 包裹类语法标记:比如代码块的
如果需要高性能获取和children.first()一致的首个语义子节点,不需要遍历全量子节点列表生成children数组,可以直接调用PsiTreeUtil.skipWhitespacesAndComments(element.firstChild),跳过无实义节点后的返回值和children.first()完全一致。
内容的提问来源于stack exchange,提问作者James Smith
相关产品推荐
相关产品推荐

