fix(core): support Stage-3 clone decorators - #3078
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
GuoLei1990
left a comment
There was a problem hiding this comment.
🫧 尘小沫
结论
阻塞级别:P1(2 项)。本轮对 4dab08da528bb5284b17afdfbabfe3f95aa7832b...51dffa3d711578a314f26f89e665d160400bbb49 的两个增量 commit 做了首次完整审查,实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 51dffa3d711578a314f26f89e665d160400bbb49。Stage-3 字符串字段的基本路径已经接通,但字段 key 契约和 metadata owner 尚未收口。自动 CR 不替代人工 Reviewer 的合入门禁,修复后仍需人工 Reviewer 审核确认。
问题
-
[P1] Stage-3 overload 接受了 clone traversal 永远不会访问的字段(
packages/core/src/clone/CloneManager.ts:14-17, 51-80;下游packages/core/src/clone/CloneUtil.ts:23-30)ClassFieldDecoratorContext.name合法地包含symbol,并且 Stage-3 也允许装饰 private field;当前 overload 对@assignmentClone [symbol]和@assignmentClone #privateField都能通过 TypeScript 5.6 类型检查,注册表也会写入对应 key。但下游仍以Object.keys(source)作为唯一字段集合,只会返回 enumerable string keys;symbol 字段完全不会进入 clone,private field 也不可能由该路径访问。实际结果是装饰器静默失效,例如 symbol-keyed@assignmentClone字段在 clone 上保留 constructor preset/undefined,而不是共享 source 值。请统一公开契约与消费域:若支持 symbol,就由CloneUtil以Reflect.ownKeys加 enumerable 过滤遍历,并补一条 symbol-keyed Stage-3 回归测试;private field 无法纳入当前 enumerable-field clone 模型,应读取context.private并在装饰阶段明确拒绝,不能静默登记一个永远消费不到的 mode。 -
[P1] 类级 clone metadata 被持久化成每个 Stage-3 实例一份(
packages/core/src/clone/CloneManager.ts:35-36, 95-104)legacy 路径把
_fieldModes写在 class prototype;Stage-3 initializer 却把this直接传给_registerFieldMode,因此每构造一个带 clone decorator 的迁移组件,都会分配一个新的_fieldModes对象并重复写入同一组 class-invariant modes。clone source 与 constructor-built target 还会各持一份。这既与 96 行“Each class gets its own_fieldModes”的现行契约矛盾,也把 owner 从“一类一份”扩成“一实例一份”,给场景加载/批量实例化增加无必要的对象分配和 metadata 同步面。应保留 class prototype 作为唯一权威 owner:initializer 只负责取得实例原型(例如Object.getPrototypeOf(this))并注册到该原型,删除实例自有_fieldModes;测试至少用同类两个实例验证行为共用同一原型表且实例本身没有镜像表。
架构、熵增与测试治理
直接链路的上游 owner 是 TypeScript/Gravity 提供的 decorator context,写入 authority 是 CloneManager,下游唯一消费者是 CloneUtil._deepCloneObject。改动前 metadata 只有 prototype 一种落点、字段域只有 enumerable string key;改动后落点净增为 prototype + per-instance 两种,注册 key 域扩成 string | symbol,消费域却仍停在 string,形成了第二种持久状态和一条断裂协议。应保留 CloneManager 的 prototype registry 与 CloneUtil 的 enumerable-own-field traversal 作为唯一 owner/协议:删除 per-instance registry;让 traversal 完整消费 enumerable symbol,无法消费的 private field 在入口拒绝。legacy/Stage-3 的调用分支是真实编译边界,可以保留,但两条分支必须汇入同一个 metadata owner,不能各自维持一种存储形态。
新增测试只构造了 name: string 的 mock context、只创建一个实例,因此同时漏掉 symbol/private 的负向契约和 per-instance 镜像状态;它没有引入为了旧测试保留的 production fallback,既有 legacy 测试也仍有效,无需删除。修复时应扩展这组新契约测试,而不是再增加 symbol/private fallback、第二套 registry 或额外转换层。19 个 package version 变更为同一 experimental 版本的机械同步,未发现第三份版本状态或旧 fixture/snapshot 残留。
51dffa3 to
2b82411
Compare
2b82411 to
4e9925c
Compare
|
已按最新
本地验证: |
GuoLei1990
left a comment
There was a problem hiding this comment.
🫧 尘小沫
结论
阻塞级别:P1(1 项)。本轮基于现行 feat/migrate-2.0@f6da19d79673cfe8f8a5ffa739a82a7ebdeb4619...4e9925cd6f629ea56cd6c13ae720c7648771badf 的 1 个 commit、2 个文件做了完整增量审查,并复核了上一轮两项 P1;实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 4e9925cd6f629ea56cd6c13ae720c7648771badf。字段域拒绝和 prototype metadata owner 已基本收口,但 legacy / Stage-3 混合继承时,最派生字段 override 仍会被基类 initializer 覆盖。自动 CR 不替代人工 Reviewer 的合入门禁,修复后仍需人工 Reviewer 审核确认。
已关闭问题清单
- 已修复:Stage-3 overload 接受 traversal 无法消费的 symbol/private 字段。
4e9925cd6f629ea56cd6c13ae720c7648771badf在CloneDecorators.ts:42-44于装饰器边界拒绝 private、static、symbol 和 non-field context,并在Clone.test.ts增加对应负向测试;无效 key 不再进入 registry。 - 已修复:类级 metadata 被持久化成每个 Stage-3 实例一份。 同一 commit 的 initializer 改为向
Object.getPrototypeOf(this)注册,CloneMetadata只在 prototype 上定义fieldCloneModesKey;多实例测试也验证两个实例没有新增自有 symbol。此项关闭仅针对上一轮的实例镜像问题,不覆盖下述混合继承注册次序问题。
问题
-
[P1] Stage-3 基类 initializer 会覆盖 legacy 子类已登记的同名字段 override(
packages/core/src/clone/CloneDecorators.ts:46-54, 109-118;下游packages/core/src/clone/ComponentCloner.ts:15-24)当前 legacy 路径在类求值时把 mode 写到声明类 prototype,而 Stage-3 路径要等实例构造,再把 mode 写到
Object.getPrototypeOf(this)。因此当 Stage-3 编译的基类用@assignmentClone target、legacy 编译的子类用@ignoreClone target时,legacy decorator 会先把Ignore正确写到子类 prototype;第一次new Sub()又执行基类的 Stage-3 initializer,此时Object.getPrototypeOf(this)仍是Sub.prototype,第 51 行会把该 own entry 无条件改回Assignment,随后WeakSet还会把这个错误结果固定下来。ComponentCloner最终读取到基类 mode,clone 会共享 source 引用,而不是保留子类 constructor preset。我用 TypeScript 5.6.3 的真实 Stage-3 emit 反向验证,表项在构造前为ignore、构造后变为assignment。现有继承测试只模拟 Stage-3 → Stage-3;子类 initializer 随后再次覆盖,因此恰好遮住了混合协议方向。请保留“每个声明类一张
fieldCloneModesKey表”作为唯一权威 owner,让 Stage-3 注册锚定装饰字段的声明类,而不是运行时实例的最派生 prototype(可使用 Stage-3 的声明级 metadata carrier 或等价机制解析声明 owner)。随后删除 concrete leaf prototype 上机械复制的基类 mode 和为这些副本服务的registeredPrototypes镜像;下游继续只沿声明类 metadata 继承链读取最终 mode。测试至少补齐 legacy base → Stage-3 sub 与 Stage-3 base → legacy sub 两个同名字段 override 方向,尤其要让后者在修复前失败;不要用兼容分支在 clone consumer 侧二次猜测优先级。
架构、熵增与测试治理
这条链路的上游协议 owner 是 TypeScript decorator context,写入 owner 是 CloneMetadata.registerFieldMode,下游只由 ComponentCloner / CloneUtil 消费 fieldCloneModesKey。改动前 legacy decorator 在声明期向声明 prototype 写一次,继承优先级完全由 prototype 链决定;改动后 Stage-3 每个字段净增一个实例 initializer、一个 WeakSet,并把基类 mode 复制到每个实际构造过的最派生 prototype。纯 legacy 与纯 Stage-3 两端各自成立,但混合后注册时间参与优先级,形成了第二套时序规则,且可覆盖更派生声明的权威事实。
应收口回声明类 metadata:入口可以保留 legacy / Stage-3 两种调用协议,但两者都只登记各自声明 owner;consumer 机械沿同一继承链取值。这样可删除 leaf 副本、WeakSet 注册镜像和任何下游优先级补偿。上一轮失效的单实例 mock 已按新公开契约重写,多实例与拒绝边界测试有效,无需删除;本轮没有发现为了旧测试保留的 production compatibility branch、legacy fallback、fixture 或 snapshot,但新增继承 fixture 仅覆盖同协议路径,必须补混合协议反向测试,不能让生产代码迁就现有测试形态。
|
感谢指出旧 HEAD 中 Stage-3 基类 initializer 会覆盖派生类声明结果的问题。这个 P1 成立: 当前 HEAD
我也用 Editor Gravity 实际加载的 验证结果: |
|
这项 P2 我重新按仓库真实编译边界验证后不采纳。
两条路径都会在 SWC transform 阶段失败: 因此恢复 98 个 class annotations 会让 当前实现也没有两份 non-terminal 映射:98 个 所以保留当前声明式 |
GuoLei1990
left a comment
There was a problem hiding this comment.
🫧 尘小沫
结论
阻塞级别:无。本轮基于 feat/migrate-2.0@f6da19d79673cfe8f8a5ffa739a82a7ebdeb4619...aa7a1a077b46e3e1331f9709ca0e3138c7093769 的现行 53 文件稳态完成增量复审;相较上一份同 HEAD review,GitHub compare 为 0 个 commit、0 个文件,实际增量是作者对 Shader AST P2 的编译边界说明。本轮未发现新的 P0/P1/P2,实际 review 动作为 COMMENTED,目标 HEAD 为 aa7a1a077b46e3e1331f9709ca0e3138c7093769。作者对剩余 P2 的解释经仓库锁定 SWC 版本与真实配置复核成立。自动 CR 不替代人工 Reviewer 的合入门禁,仍需人工 Reviewer 审核确认。
已关闭问题清单
- 已修复:Stage-3 decorator 接受 traversal 无法消费的 symbol/private 字段。
4e9925cd6f629ea56cd6c13ae720c7648771badf已在入口拒绝 private、static、symbol 和 non-field context;当前 HEAD 的真实标准装饰器测试继续覆盖四类非法目标,字段域与Object.keysconsumer 一致。 - 已修复:类级 clone metadata 被持久化成实例或 concrete leaf prototype 镜像。
aa7a1a077b46e3e1331f9709ca0e3138c7093769只在类声明求值时写context.metadata,旧 initializer、registeredPrototypes/WeakSet、实例与 leaf prototype 副本均已删除。 - 已修复:基类注册时序覆盖派生类同名字段 override。
2a88dcf19443120759f1e55bfce3fe15e69516e6删除 legacy decorator ABI,aa7a1a077b46e3e1331f9709ca0e3138c7093769由getFieldCloneModes沿真实 constructor chain 合并各声明 own metadata;继承优先级不再依赖实例构造时序。 - 作者解释成立:Shader AST 应保留当前
static nonTerminal+ 统一 pool 方案。 我用@swc/core@1.9.2对 baseAST.ts分别执行 PR 的 runtime(2022-03 + useDefineForClassFields + loose + ES5)与 bundler(2022-03 + useDefineForClassFields + ES2020)配置,两者均在 namespace 内 decoratedexport class处报ESM-style module declarations are not permitted in a namespace;当前 HEAD 在两套配置下均转换成功。base 的 98 个 decorator 参数与当前 98 个static nonTerminal一一替换,后者是唯一 non-terminal 映射;TreeNode._pools提供 constructor → pool 查找,ShaderCompilerUtils._shaderCompilerObjectPoolSet只枚举同一批 pool 以统一 clear/release,二者不是重复 authority。上一轮 P2 的删除方案在真实编译边界不可行,本项关闭。
架构、熵增与测试治理
clone 链路的上游协议 owner 是 Babel/SWC 提供的标准 decorator context,唯一写入 owner 是各声明的 context.metadata;getFieldCloneModes 沿 JavaScript constructor chain 机械生成只读派生缓存,下游仍只有 CloneUtil 与 ComponentCloner 两个 consumer。相较旧路径,legacy ABI、实例 initializer、注册 WeakSet、prototype/leaf 镜像均已删除,owner、持久状态和兼容分支净减少;真实语法测试覆盖声明 metadata、继承 override 与非法目标,未发现为旧测试保留的 production fallback、wrapper、镜像状态或失效 fixture/snapshot。
Shader AST 链路由每个节点的 static nonTerminal 保有唯一语法事实,TreeNode.nt 机械读取该事实;静态 pool getter 通过 WeakMap 为 constructor 建立唯一 pool,createObjectPool 再把同一对象登记到统一 clear/release 列表,下游 CFG / GrammarUtils 继续直接消费 Node.pool。映射数量保持 98 → 98;新增索引替代了受 SWC namespace 限制而无法保留的 decorator side effect,没有形成第三份真相、平行状态机或 legacy/compat 路径。当前 HEAD 已通过两套真实 SWC transform 边界,现有 parser 与 clone 契约测试无需为旧实现增加兼容逻辑。
为什么要改
Gravity 按 TypeScript Stage-3 语义编译字段装饰器,调用协议是
(undefined, ClassFieldDecoratorContext)。Engine 原实现使用另一套装饰器 ABI,把第一个参数当作 prototype,因此迁移脚本在模块求值阶段就会把
undefined传入克隆元数据注册并报错。最新
feat/migrate-2.0的克隆行为由CloneDecorators、字段模式元数据和现有 clonetraversal 共同拥有。本 PR 直接统一到标准 Stage-3 语义,不保留旧 ABI 的重载、运行时判型或
平行元数据路径。
设计
deepClone、assignmentClone、ignoreClone只接受标准ClassFieldDecoratorContextcontext.metadata;每个类声明只拥有自己的字段模式表,不依赖实例构造时序
实例或 concrete leaf prototype
WeakSet去重、prototype 元数据副本或第二套 registry反向覆盖
string-named instance field;private、static、symbol、method 与 accessor 均显式拒绝
@ignoreClone:方法位于 prototype,从未进入字段遍历,这些标注没有可观察语义
Gravity 与编译链路
@babel/standalone@7.24.7,并以proposal-decorators { version: "2023-11" }转换脚本;实测产物会向字段 decoratorcontext 传入
metadata,并在子类转换时传递父类experimentalDecoratorsobject prototype 当作语义前提
nonTerminal,对象池由TreeNode统一惰性创建;映射已与原 98 项逐项比对一致TDD 与验证
experimentalDecorators后,64 个无效方法装饰器立即产生类型错误;浏览器未downlevel 时产生装饰器语法错误;使用
context.addInitializer时出现 48 个克隆、Camera、引用计数与 Physics 连锁失败
prototype 又在 SWC 产物中触发 10 个文件、31 个测试失败
pnpm run b:module:通过pnpm run b:types:通过pnpm lint:0 errorpnpm exec vitest run:135 files、1758 tests 全部通过Clone.test.ts:109/109ClassFieldDecoratorContext;构建产物只使用标准装饰器 helper发布边界
0.0.0-experimental-2.0-migrate.5已从更早实现发布,本 PR 不复用或重发该版本;包含本修复的后续发布必须使用新版本。