审查基线
本报告使用以下官方仓库快照,不以文档概述代替源码控制流。
| 实现 | 版本 | 固定 SHA | 本地路径 |
|---|---|---|---|
| Java | v1.5.1 | e9b4654225ef3a14667059b08cdee40c01f86628 | /tmp/tyme4j-official |
| TypeScript | v1.5.2 | 26bd357607a361c2b3460ac62ce33cce307446dc | /tmp/tyme4ts-official |
| Rust | v1.5.0 | ba6ab751e4346354c4bc792027986ea7f67050cd | /tmp/tyme4rs-official |
| Go | v1.5.0 | 82896412d626fe78adbcd6779ae1f1bc70fdc5b0 | /tmp/tyme4go-current |
判定总表
| 项目 | 判定 | MoonBit 影响 |
|---|---|---|
| 闰月逐月胎神 | 共同契约 闰月返回空值 | get_fetus 不应 unwrap() |
| LoopTyme 核心能力 | 共同契约 size/index/name/steps/next | 五个包装类型缺少公开转发 |
| JD 0 与非法周起点 | 共同契约 必须失败 | 应以 Result 保留原因 |
| Go 边界 panic | Go 缺陷 丢弃 error 后解引用 nil | 不得复制该错误传播方式 |
| Event 改名与删除 | 部分统一 Java/Rust/Go 一致,TS 异常 | 改名时需删除旧键 |
| Event.updateData | 共同上游缺陷 新记录缺失名称 | Map 语义更合理,不应退化 |
| 空名称与非法 9 字符数据 | 未统一 下游行为分叉 | 需要明确自身校验契约 |
| Provider 替换 | 未统一 Rust 不可外部替换 | 当前 setter 与 Java/TS/Go 一致 |
一、跨语言共同契约
1. 闰月没有逐月胎神
Java、TypeScript、Rust 的公开 API 都明确保留空值;Go 的底层 helper 也返回 nil。
- Java:
culture/fetus/FetusMonth.java:26-28返回null;lunar/LunarMonth.java:293-294原样返回。 - TypeScript:
lib/index.ts:1856-1858与:2030-2032返回FetusMonth | null。 - Rust:
culture/fetus.rs:306-311与lunar.rs:455-457返回Option<FetusMonth>。 - Go:
FetusMonth.go:17-24在闰月返回 nil。
2. LoopTyme 循环能力属于共同公开 API
四种官方实现都提供 getIndex、getName、getSize、三个 steps* 方法和具体类型化 next。
- Java:
com/tyme/LoopTyme.java:43-132,具体类型通过继承获得能力。 - TypeScript:
lib/index.ts:222-273,具体类型通过继承获得能力。 - Rust:
tyme/mod.rs:170-271,具体类型通过Deref<Target = LoopTyme>获得能力。 - Go:
tyme/LoopTyme.go:16-90,具体类型通过匿名嵌入提升方法。
已逐一确认的具体类型:Animal、Week、HeavenStem、EarthBranch、SixtyCycle。
3. 非法日期边界必须失败
JulianDay(0.0) 会换算到年份 -4712,最终被公历支持范围 1..9999 拒绝。
SolarDay.getSolarWeek(7) 同样会进入 WeekUnit,被 0..6 范围校验拒绝。
- Java:
JulianDay.java:79-112、SolarYear.java:16-17、WeekUnit.java:42-45。 - TypeScript:
index.ts:2885-2922、:3611-3613、:150-153。 - Rust:
jd.rs:76-131、solar.rs:63-64、unit.rs:235-237。 - Go:构造器正确返回 error,但上层丢弃错误。
二、Go 单实现缺陷
当前确认的三处问题具有同一根因:受限构造器返回 (*T, error),上层忽略 error 并继续解引用。
/tmp/tyme4go-current/tyme/LunarMonth.go:238-240:把合法的“无胎神”空值立即解引用。/tmp/tyme4go-current/tyme/JulianDay.go:91-96:丢弃公历年份范围错误,再解引用 nil。/tmp/tyme4go-current/tyme/SolarDay.go:121-124:丢弃非法周起点错误,再解引用 nil。
三、语言间未统一,不能机械照搬
1. Event 改名与删除
对已存在的 old 执行 update("old", eventName="new"):
| 实现 | old | new | 判定 |
|---|---|---|---|
| Java | false | true | 按旧名定位,整条替换为新名称 |
| Rust | false | true | 与 Java 一致 |
| Go | false | true | 与 Java 一致,不是 Go 缺陷 |
| TypeScript | true | false | 匹配后使用普通字符串 replace,实际未替换 |
| MoonBit | true | true | 用新名称写 Map,但没有删除旧键 |
- Java:
EventManager.java:45-52,61-63 - Rust:
event.rs:416-424,433-440 - Go:
EventManager.go:27-34,38-44 - TypeScript:
index.ts:6096-6106 - MoonBit:
tyme/event_manager.mbt:21-24
2. updateData 是共同继承的上游缺陷
Java、TypeScript、Rust、Go 都只追加 9 字符 data,没有追加 name。调用:
updateData("raw", "@0VV__000")
fromName("raw") // 无值
all().length // 0
这与“新增或更新事件”的方法说明矛盾。MoonBit 使用 Map 存储 name -> data,行为更合理,不应为了复刻运行结果而退化。
3. 空名称与非法 9 字符数据
空名称在四个上游字符串实现中表现为 fromName("") 可找到、但 all() 不可枚举,这是正则和序列化格式的副作用。
四种官方实现的 Event.validate() 都只检查长度。输入 !!!!!!!!! 后,getType() 的行为分叉:
| 实现 | 结果 |
|---|---|
| Java | 返回 null |
| TypeScript | 默认为 SOLAR_DAY(0) |
| Rust | Option::unwrap() panic |
| Go | 默认为“公历日期” |
| MoonBit | 默认为 SolarDay |
4. Provider 运行时替换能力
Provider 抽象是共同设计,但“外部调用者可替换全局实现”并未统一。
- Java:
ChildLimit.java:22、LunarHour.java:31暴露公开静态字段。 - TypeScript:
index.ts:4868、:2706暴露公开静态属性。 - Go:
ChildLimit.go:4、LunarHour.go:8暴露包变量。 - Rust:trait 公开,但
CHILD_LIMIT_PROVIDER与EIGHT_CHAR_PROVIDER私有,且无 setter。
error[E0603]: struct `CHILD_LIMIT_PROVIDER` is private error[E0603]: struct `EIGHT_CHAR_PROVIDER` is private
DeepWiki 索引偏差
DeepWiki 本轮仅用于定位。以下回答已被当前官方源码和运行行为直接证伪:
- 错称 Java、TypeScript、Rust 没有事件模块。
- 错称 TypeScript 没有
stepsBackTo、stepsCloseTo。 - 错称 Rust 的 JD 0 能成功转换。
- 错称 Rust Provider 可由 crate 外部替换。
- Java 胎神回答漏掉
FetusMonth.java,无法识别明确的 null 分支。
最小运行复现
复现程序保存在 /tmp/tyme-crosscheck,没有写入官方克隆或项目源码目录。
日期与胎神结果
Java:
jd0 = IllegalArgumentException: illegal solar year: -4712
week7 = IllegalArgumentException: illegal week start: 7
fetus = null
TypeScript:
jd0 = Error: illegal solar year: -4712
week7 = Error: illegal week start: 7
fetus = null
Rust:
jd0 = panic,Err("illegal solar year: -4712")
week7 = panic,Err("illegal week start: 7")
fetus = None
Go:
jd0 = nil pointer panic
week7 = nil pointer panic
fetus = nil pointer panic
Event 结果
重命名 old -> new:
Java / Rust / Go : old=false, new=true
TypeScript : old=true, new=false
updateData 新增:
Java / TypeScript / Rust / Go:
found=false, all=0
空名称:
Java / TypeScript / Rust / Go:
fromName("")=true, all=0
MoonBit 影响与接口判定
| 模块 | 当前状态 | 共同契约要求 |
|---|---|---|
| 胎神 | FetusMonth::from_lunar_month -> Self?,但 LunarMonth::get_fetus -> FetusMonth 并 unwrap |
公开返回 FetusMonth? |
| LoopTyme | 基础类型完整;五个具体包装类型未公开转发 size/steps,Animal 还缺 equals | 补足具体类型调用能力 |
| JulianDay | get_solar_day 已返回 Result;get_solar_time 仍会制造非法值 |
失败必须保留,不得绕过受限构造器 |
| SolarWeek | 通过 Result 正确返回非法起点错误 | 已对齐 |
| Event | Map 架构避免 updateData 上游缺陷;改名没有删除旧键 | 按旧名定位,改名后移除旧键 |
| Provider | 两个 setter 均公开 | 与 Java/TypeScript/Go 一致,可保留 |
关键生成接口:pkg.generated.mbti:557,772-783,891,1393,1527;
EventManager:tyme/event_manager.mbt:21-70;
Provider setter:tyme/child_limit.mbt:166-168、tyme/lunar_hour.mbt:246-248。