Tyme4MB · Cross-language Review

Java / TypeScript / Rust / Go
跨语言一致性审查

以官方当前源码和最小运行复现为最终证据,区分跨语言共同契约、Go 单实现缺陷, 以及语言间尚未统一、不能机械照搬的行为。

只读审查 固定源码 SHA 复现目录 /tmp/tyme-crosscheck DeepWiki 仅用于定位

审查基线

本报告使用以下官方仓库快照,不以文档概述代替源码控制流。

实现版本固定 SHA本地路径
Javav1.5.1e9b4654225ef3a14667059b08cdee40c01f86628/tmp/tyme4j-official
TypeScriptv1.5.226bd357607a361c2b3460ac62ce33cce307446dc/tmp/tyme4ts-official
Rustv1.5.0ba6ab751e4346354c4bc792027986ea7f67050cd/tmp/tyme4rs-official
Gov1.5.082896412d626fe78adbcd6779ae1f1bc70fdc5b0/tmp/tyme4go-current

判定总表

3明确的 Go 单实现错误传播缺陷
3已确认的跨语言共同核心契约
4不能按单一语言机械照搬的行为组
项目判定MoonBit 影响
闰月逐月胎神共同契约 闰月返回空值get_fetus 不应 unwrap()
LoopTyme 核心能力共同契约 size/index/name/steps/next五个包装类型缺少公开转发
JD 0 与非法周起点共同契约 必须失败应以 Result 保留原因
Go 边界 panicGo 缺陷 丢弃 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 返回 nulllunar/LunarMonth.java:293-294 原样返回。
  • TypeScript:lib/index.ts:1856-1858:2030-2032 返回 FetusMonth | null
  • Rust:culture/fetus.rs:306-311lunar.rs:455-457 返回 Option<FetusMonth>
  • Go:FetusMonth.go:17-24 在闰月返回 nil。
“闰月无逐月胎神”是正常业务边界,不是异常。公开调用链必须保留可空结果。

2. LoopTyme 循环能力属于共同公开 API

四种官方实现都提供 getIndexgetNamegetSize、三个 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,具体类型通过匿名嵌入提升方法。

已逐一确认的具体类型:AnimalWeekHeavenStemEarthBranchSixtyCycle

3. 非法日期边界必须失败

JulianDay(0.0) 会换算到年份 -4712,最终被公历支持范围 1..9999 拒绝。

JulianDay 0.0
换算出 -4712 年
进入 SolarTime / SolarDay
SolarYear 范围校验失败

SolarDay.getSolarWeek(7) 同样会进入 WeekUnit,被 0..6 范围校验拒绝。

  • Java:JulianDay.java:79-112SolarYear.java:16-17WeekUnit.java:42-45
  • TypeScript:index.ts:2885-2922:3611-3613:150-153
  • Rust:jd.rs:76-131solar.rs:63-64unit.rs:235-237
  • Go:构造器正确返回 error,但上层丢弃错误。

二、Go 单实现缺陷

当前确认的三处问题具有同一根因:受限构造器返回 (*T, error),上层忽略 error 并继续解引用。

受限构造器
返回 nil + error
调用方丢弃 error
nil 指针 panic
  1. /tmp/tyme4go-current/tyme/LunarMonth.go:238-240:把合法的“无胎神”空值立即解引用。
  2. /tmp/tyme4go-current/tyme/JulianDay.go:91-96:丢弃公历年份范围错误,再解引用 nil。
  3. /tmp/tyme4go-current/tyme/SolarDay.go:121-124:丢弃非法周起点错误,再解引用 nil。
Go 的缺陷不是“这些输入应该成功”,而是业务错误被退化成没有上下文的 nil 指针 panic。

三、语言间未统一,不能机械照搬

1. Event 改名与删除

对已存在的 old 执行 update("old", eventName="new")

实现oldnew判定
Javafalsetrue按旧名定位,整条替换为新名称
Rustfalsetrue与 Java 一致
Gofalsetrue与 Java 一致,不是 Go 缺陷
TypeScripttruefalse匹配后使用普通字符串 replace,实际未替换
MoonBittruetrue用新名称写 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)
RustOption::unwrap() panic
Go默认为“公历日期”
MoonBit默认为 SolarDay

4. Provider 运行时替换能力

Provider 抽象是共同设计,但“外部调用者可替换全局实现”并未统一。

  • Java:ChildLimit.java:22LunarHour.java:31 暴露公开静态字段。
  • TypeScript:index.ts:4868:2706 暴露公开静态属性。
  • Go:ChildLimit.go:4LunarHour.go:8 暴露包变量。
  • Rust:trait 公开,但 CHILD_LIMIT_PROVIDEREIGHT_CHAR_PROVIDER 私有,且无 setter。
error[E0603]: struct `CHILD_LIMIT_PROVIDER` is private
error[E0603]: struct `EIGHT_CHAR_PROVIDER` is private

DeepWiki 索引偏差

DeepWiki 本轮仅用于定位。以下回答已被当前官方源码和运行行为直接证伪:

  1. 错称 Java、TypeScript、Rust 没有事件模块。
  2. 错称 TypeScript 没有 stepsBackTostepsCloseTo
  3. 错称 Rust 的 JD 0 能成功转换。
  4. 错称 Rust Provider 可由 crate 外部替换。
  5. Java 胎神回答漏掉 FetusMonth.java,无法识别明确的 null 分支。
证据优先级:实际运行行为 → 当前官方源码 → 官方测试 → 接口文档 → DeepWiki 索引概述。

最小运行复现

复现程序保存在 /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-168tyme/lunar_hour.mbt:246-248