Go 不同 FileSet 的 Pos 为什么不能直接比较大小
来源:17golang原创
时间:2026-11-10 17:37:53 326浏览 收藏
结论先说:token.Pos 虽然底层是整数,但它不是整个项目共享的全局字节偏移。它属于创建它的 token.FileSet;两个独立的 FileSet 都可能从相同的基准开始分配位置,所以跨 FileSet 直接写 p1 ,比较到的只是两个局部坐标域里的数字,不能推出源码顺序。
如果位置来自同一个 FileSet,可以用 Pos 比较同一文件内的偏移,也可以比较该 FileSet 中文件的加入顺序。跨 FileSet 则先转换为带文件身份的 token.Position,或者构造自己的显式排序键。
先把 Pos 放回所属 FileSet
Go 的 token.File 记录文件名、大小和行偏移表;FileSet 在添加文件时为它分配一个不重叠的位置区间。File.Pos(offset) 只是把文件内的字节偏移映射到这个 FileSet 的位置域,FileSet.Position(pos) 再把它还原成文件名、行号和列号。
因此,同一个整数必须连同“由哪个 FileSet 解释”一起看。下面的例子故意创建两个独立 FileSet,并让两个文件都取各自的第一个字节位置:
package main
import (
"fmt"
"go/token"
)
func main() {
setA := token.NewFileSet()
setB := token.NewFileSet()
fileA := setA.AddFile("a.go", -1, 20)
fileB := setB.AddFile("b.go", -1, 20)
posA := fileA.Pos(0)
posB := fileB.Pos(0)
// 两个 FileSet 的基准都从 1 开始,因此数值可能相等。
fmt.Println(posA == posB, int(posA), int(posB))
fmt.Println(setA.Position(posA), setB.Position(posB))
}
这里的两个 Pos(0) 都是各自文件的起点,但它们并不指向同一份源码。即使某次添加文件后数值恰好不同,也不能把数值差当作跨文件集的距离;解释这些数值所需的文件表并不相同。

同一 FileSet 内,Pos 比较什么时候成立
官方定义给出的边界很重要:如果两个 Pos 属于同一个文件,比较它们等价于比较该文件中的源偏移;如果属于同一个 FileSet 但来自不同文件,比较结果反映的是文件加入 FileSet 的先后顺序。这个顺序是位置分配规则,不是文件名的字典序。
解析一组文件时,最稳妥的流程是让 parser、AST 和类型检查共用一个 FileSet,然后再比较位置:
package positionorder
import (
"fmt"
"go/token"
)
// Before 判断两个位置是否在同一个 FileSet 中且顺序可按 Pos 解释。
func Before(set *token.FileSet, left, right token.Pos) (bool, error) {
leftFile := set.File(left)
rightFile := set.File(right)
if leftFile == nil || rightFile == nil {
return false, fmt.Errorf("位置不属于当前 FileSet")
}
// 同一 FileSet 内,Pos 比较可表达文件加入顺序和同文件偏移。
return left
这个辅助函数只接受同一个 set 解释的两个位置。FileSet.File 能先把位置映射回所属文件,调用方就能在比较前发现把别的坐标域混入的错误。它仍然不等价于“按文件名排序”:如果业务需要字母序,应该显式取出文件名再排序。
跨 FileSet 先转换,再建立排序键
两个独立 FileSet 的位置没有天然的全局先后。跨域排序前,应先定义业务真正关心的顺序。例如代码索引器可以按文件名、行号、列号排序;需要保留字节级精度时,再把文件内偏移加入键。
package positionorder
import (
"fmt"
"go/token"
)
type Key struct {
Filename string
Line int
Column int
Offset int
}
// MakeKey 把 FileSet 局部位置转换为带文件身份的业务键。
func MakeKey(set *token.FileSet, pos token.Pos) (Key, error) {
file := set.File(pos)
if file == nil {
return Key{}, fmt.Errorf("位置不属于当前 FileSet")
}
position := set.Position(pos)
// Offset 取文件内字节偏移,不把 Pos 当成跨域全局偏移。
return Key{
Filename: position.Filename,
Line: position.Line,
Column: position.Column,
Offset: file.Offset(pos),
}, nil
}
拿到 Key 后,排序规则就可以由业务明确决定:先按 Filename,再按 Line、Column,最后按 Offset。如果文件名可能重复,还应加入模块路径、文件来源或调用方定义的文档 ID。关键不是选择哪一组字段,而是把跨域身份写进键里。

从目标到落地的完整工作流
遇到位置比较问题时,可以按下面的顺序处理,先判断坐标域,再选择比较方式:
- 确认来源:给每个 Pos 标记创建它的 FileSet,检查 parser 或检查器是否复用了同一个 FileSet。
- 判断范围:同一文件可比较源偏移;同一 FileSet 的不同文件可表达加入顺序;不同 FileSet 不直接比较。
- 转换语义:用所属 FileSet 的
Position取得文件名、行列;用所属 File 的Offset取得文件内字节偏移。 - 选择业务键:索引、诊断和展示通常按文件名与行列排序;编辑器内部若要稳定定位,可额外保留文档 ID。
- 保留生命周期:让产生 Pos 的 FileSet 与相关 AST、类型信息一起传递,不要把裸 Pos 脱离上下文长期存储。
一个简单的决策表如下:
| 场景 | 建议做法 | 不能做的事 |
|---|---|---|
| 同一 FileSet、同一文件 | 直接比较 Pos,或比较 File.Offset | 把比较结果解释成跨项目全局序号 |
| 同一 FileSet、不同文件 | 按 Pos 得到 FileSet 中的加入顺序 | 误认为这是文件名字典序 |
| 不同 FileSet | 分别转换 Position,再按显式键排序 | 直接比较 p1、p2 或相减 |
| 需要持久化位置 | 保存文档身份、文件内偏移或行列 | 只保存一个裸 token.Pos 整数 |
常见误区与边界
Pos 是不是永远不能比较?不是。同一 FileSet 的位置比较是有定义的;问题在于把不同 FileSet 的局部整数当成同一坐标系。
为什么两个 FileSet 会出现相同的 Pos?每个新 FileSet 都从自己的基准开始分配位置,独立创建的第一个文件通常会得到相同的起点范围。相等只说明数值相等,不说明文件身份相等。
Position 能不能直接作为排序依据?token.Position 是结构化结果,适合展示和构造键,但最终排序仍要由业务决定,尤其要处理文件名重复、未加载文件和列号语义。
最小原则是什么?让 Pos 与所属 FileSet 成对传递;跨 FileSet 时先恢复文件身份和文件内偏移,再进行比较。这样排序依据就从“碰巧相等的整数”变成了可解释的业务数据。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
191 收藏
-
181 收藏
-
179 收藏
-
127 收藏
-
413 收藏
-
199 收藏
-
293 收藏
-
342 收藏
-
323 收藏
-
479 收藏
-
416 收藏
-
269 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习