资讯动态

Wing 语言的变量遮蔽(Shadowing)语义解析:inflight 捕获与编译器的“DefinedLater“约束

发布时间:2026/10/16 1:35:56 来源:尧图企业网站定制
编程语言编译器云原生语言运行时【免费下载链接】wingA programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡项目地址https://gitcode.com/gh_mirrors/wi/wing点击查看免费下载导读本文以 Wing 开源仓库中 shadowing.test.w 的 sim 平台测试快照 为切入点深入讲解 Wing 语言的变量遮蔽Shadowing规则尤其是它和 inflight 闭包捕获capture机制的相互作用。你将理解为什么在嵌套作用域里遮蔽一个已被捕获的外层变量是合法的而在同一个作用域内先用后定义同名变量却会被编译器拒绝以及这些规则在 wingc 编译器 的 lifting提升/捕获分析阶段是如何落地实现的。快照文档是什么hangar 测试语料库的一条证据链先厘清关联文档的定位。tools/hangar/__snapshots__/test_corpus/valid/shadowing.test.w_test_sim.md不是手写的使用手册而是 Wing 仓库自带的测试快照系统产物它记录了wing test -t sim在 sim 模拟器平台上运行tests/valid/shadowing.test.w后的标准输出。该快照由 hangar 工具 的createMarkdownSnapshot生成头部以# 文件名 | 测试用例 | 目标平台标记来源正文是经过脱敏sanitize处理的 stdoutpass ─ shadowing.test.wsim » root/Default/test:capture shadow interaction Tests 1 passed (1) Snapshots 1 skipped Test Files 1 passed (1) Duration DURATION其中DURATION是快照系统的占位符——utils.ts 中的sanitizeOutput会把不稳定输出如运行时长、绝对路径、端口、时间戳统一替换为DURATION、ABSOLUTE等标记保证快照在 CI 中可重复比对。快照对应的 .w 测试文件位于 tests/valid/shadowing.test.w它属于tests/valid/目录该目录存放应当编译并运行成功的合法用例。hangar 会通过 generate_tests.ts 为每个.w文件自动生成两条 Vitest 用例wing compile -t tf-aws编译到 Terraform/AWS 平台与wing test -t sim在模拟器中运行测试前者校验编译产物后者即产出我们看到的这份快照。因此这条快照实际上是影子遮蔽在 inflight 闭包中行为正确这条语义契约的可执行证据。测试用例全景一个遮蔽了捕获变量的 inflight 闭包快照背后真正的主角是这段 Wing 代码完整内容见 tests/valid/shadowing.test.wbring cloud; let bar hola!; let foo not captured; let fn inflight (): Arraystr { let result MutArraystr[]; // 当前作用域内没有同名变量因此捕获外层 bar 是允许的 result.push(bar); if true { // 在 if 子作用域内遮蔽已捕获的 bar let bar world; result.push(bar); } // 在定义 foo 之前没有尝试引用外层 foo因此这合法 let foo bang; result.push(foo); return result.copy(); }; test capture shadow interaction { let result fn(); assert(result.length 3); assert(result.at(0) hola!); assert(result.at(1) world); assert(result.at(2) bang); }这份用例的核心要点可以拆成三个层面捕获外层变量bar定义在 inflight 闭包之外preflight 作用域闭包内第一次result.push(bar)引用的是外层bar值应为hola!。由于当前闭包作用域内尚不存在同名符号编译器允许捕获。子作用域遮蔽在if true { ... }这个新引入的子作用域内let bar world重新声明了bar此时闭包内的bar指向新值result.push(bar)推入world。遮蔽结束后回到外层bar仍指向被捕获的hola!。局部定义优先外层 preflight 也有一个foo但闭包内在let foo bang之前从未引用过外层foo因此直接定义本地变量不会触犯遮蔽捕获禁令最终推入bang。测试断言最终数组长度为 3且三个元素依次为hola!、world、bang——快照中的Tests 1 passed (1)即此断言通过。这个用例同时验证了 Wing 闭包捕获的按值语义闭包捕获的是变量值而非可变引用遮蔽发生在完全独立的符号上互不干扰。遮蔽规则与捕获机制的分界线要理解上述用例为什么合法需要先明确 Wing 编译器的两条核心规则。规则一遮蔽Shadowing本身是语言一等公民Wing 允许在任意嵌套作用域内用let声明同名变量这是标准的词法作用域行为。仓库中的 symbol_shadow.test.w 把这一规则延伸到了 inflight 场景bring cloud; let s top; if true { let s inner; assert(s inner); test inflight nested should not capture the shadowed var { assert(s inner); } } assert(s top);值得注意test inflight nested should not capture the shadowed var内部的s解析到的是if块内的inner而不是最外层的top——测试名里的 should not capture 正是指编译器不会把外层s提升lift进这个 inflight 作用域因为内层符号已经遮蔽了它。规则二同作用域内先用后定义是编译错误与嵌套作用域遮蔽不同Wing 编译器禁止在同一个作用域内先引用一个会被后面定义的同名变量遮蔽的符号。这正是 tests/invalid/capture_redefinition.test.w 要锁定的错误场景其源码中用// ^注释精确标注了报错位置let y h; () { log(hello); log(y); // ^ Cannot capture symbol y because it is shadowed by another symbol with the same name let y y; }; inflight () { let y hi; inflight () { log(y); // ^ Cannot capture symbol y because it is shadowed by another symbol with the same name let y world; }; };错误信息中的关键字是 shadowed by another symbol with the same name它与本文快照所验证的合法场景互为镜像只要引用发生在同作用域同名声明之前无论这个同名声明在外层作用域还是兄弟作用域都会被判定为遮蔽了捕获目标而报错。编译器实现原理lifting 阶段与DefinedLater查找上述规则的实现位于 wingc 编译器的lifting捕获分析阶段核心代码在 packages/winglang/wingc/src/lifting.rs 的LiftVisitor::verify_defined_in_current_env方法lifting.rs 第 51-80 行// If the symbol is defined later in the current environment, it means we cant reference an identical symbol // from an outer scope (and we cant capture it either). In that case well report an error. // Note: // In theory this can be supported, but itll fail later on when running the JS code because of how // JS creates all symbols *before* running the code of the current scope (https://tc39.es/ecma262/#sec-let-and-const-declarations). // Solving this will require renaming the symbols in the current scope to avoid the conflict, so the easy way is just to adopt // the JS limitation. Will be good to improve on this in the future. if let Some(env) self.ctx.current_env() { if matches!( env.lookup_ext(symbol, Some(self.ctx.current_stmt_idx())), LookupResult::DefinedLater(_) ) { report_diagnostic(Diagnostic { span: Some(symbol.span.clone()), message: format!(Cannot access \{symbol}\ because it is shadowed by another symbol with the same name), annotations: vec![], hints: vec![], severity: DiagnosticSeverity::Error, }); } }从源码可以提炼出三个关键事实触发条件是LookupResult::DefinedLaterverify_defined_in_current_env只在遍历到标识符引用时调用见 visit_reference 中对Reference::Identifier的分支它用lookup_ext在当前环境里查找同名符号并传入当前语句索引。如果符号在引用点之后才定义环境返回DefinedLater编译器立即上报诊断错误。DefinedLater的产生位置在 symbol_env.rs 的lookup_ext中symbol_map命中了同名符号但lookup_index definition_idx时返回DefinedLater(entry.span.clone())。也就是说DefinedLater的本质是符号存在但定义时机晚于当前语句——这正是同作用域先用后定义与跨作用域捕获的分野跨作用域引用在父环境链中查找不会命中当前作用域的DefinedLater。为什么采用这条限制源码注释给出了直接原因——Wing 的 inflight 代码最终会转译成 JavaScript 运行而 JS 的let/const声明会在作用域代码执行前就完成符号创建即 TDZ暂存死区语义见 ECMAScript 规范对 let/const 声明的规定。若允许同作用域先用后定义生成的 JS 代码在运行时会抛 ReferenceError。编译器选择直接采用 JS 的这一限制并在编译期拦截而非在运行期暴露错误注释同时承认理论上可通过重命名当前作用域符号来消除冲突是未来的改进方向。同类测试矩阵从合法到非法的边界全集仓库为这条语义规则配备了从完全合法到边界合法再到非法的完整测试矩阵便于读者对照理解测试文件分类验证点shadowing.test.w合法inflight 闭包内捕获外层bar→if子作用域遮蔽bar→ 先引用后定义本地foo未发生symbol_shadow.test.w合法嵌套作用域遮蔽后inflight 测试捕获的是内层符号而非外层类方法与顶层作用域同理lift_redefinition.test.w边界合法test 块内先断言外层y hello捕获外层随后let y z在块内重新定义——因为引用发生在定义之前且定义后不再引用外层编译通过capture_redefinition.test.w非法同作用域log(y)之后才let y以及嵌套 inflight 中同类模式均报 Cannot capture symbol ... because it is shadowed其中 lift_redefinition.test.w 尤其值得细看它证明先用后定义的禁令严格限制在对同作用域后续定义的引用上只要引用发生在外层捕获变量上、且时机早于遮蔽声明就仍然合法——这与本文主用例中result.push(bar)引用外层被捕获变量和let foo bang定义本地新变量的两步操作形成完美对照。如何本地复现与验证如果你希望在自己的环境中运行这条测试可以按以下步骤操作仓库为只读仅需安装与运行安装依赖并构建 Wing CLI 与 wingc 编译器参考仓库根目录 package.json 与 Cargo.toml 的脚本配置对单个用例执行 sim 平台测试命令形如wing test tests/valid/shadowing.test.w -t sim该命令会编译.w文件并在本地模拟器simulator中运行test capture shadow interaction成功时输出与快照一致的pass ─ shadowing.test.wsim » root/Default/test:capture shadow interaction与Tests 1 passed (1)若要复现快照比对流程可进入 tools/hangar它依赖wing compile -t tf-aws与wing test -t sim两条自动生成的 Vitest 用例见 generate_tests.ts运行结果会通过createMarkdownSnapshot与tools/hangar/__snapshots__/下的快照文件做逐字比对想验证非法场景把tests/invalid/capture_redefinition.test.w提交给编译器应得到Cannot capture symbol y because it is shadowed by another symbol with the same name的编译诊断。小结遮蔽、捕获与代码生成的三方契约通过这条 sim 快照及其背后的测试与编译器源码可以得出 Wing 变量遮蔽语义的完整图景词法遮蔽是基础嵌套作用域内let同名变量完全合法inflight 代码按词法作用域解析符号被遮蔽的外层变量不会进入捕获清单见 tests/valid/symbol_shadow.test.w捕获与遮蔽的交互有一条硬约束同作用域内不得在引用之后声明同名变量否则引用会被判定为被遮蔽而报错原因在于 JS 代码生成的 TDZ 限制见 lifting.rs 的实现与注释快照是契约的固化shadowing.test.w_test_sim.md这份快照把合法用例必须通过写进了 CI与tests/invalid/中的报错用例一起从正反两面锁定了该语义保证编译器后续改动不会意外放宽或收紧这一行为。对于 Wing 开发者而言记住一句口诀即可规避绝大多数坑想捕获外层变量就先引用它想遮蔽它就换一个作用域或在引用之后才声明。这正是 shadowing.test.w 想要教会你的。若需进一步理解 inflight 与 preflight 的运行时模型可继续阅读仓库中的 preflight 与 inflight 概念文档。赞分享编程语言编译器云原生语言运行时【免费下载链接】wingA programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡项目地址https://gitcode.com/gh_mirrors/wi/wing点击查看免费下载相关推荐在 MicroPhase ANTSDRZynq-7020 AD9361上移植 openwifi板卡支持、设备树解析与 RF 前端频率规划在 MicroPhase ANTSDRZynq 7020 AD9361上移植 openwifi板卡支持、设备树解析与 RF 前端频率规划 本篇技术指南编程语言编译器云原生语言运行时Wing 语言 inflight 内部类可变捕获语义inflight_class_inner_capture_mutable.test.w 快照深度剖析Wing 语言 inflight 内部类可变捕获语义 inflight_class_inner_capture_mutable.test.w 快照深度剖析 导编程语言编译器云原生语言运行时Wing 语言中变量重定义Shadowing与 Lift 机制的编译产物深度解析Wing 语言中变量重定义Shadowing与 Lift 机制的编译产物深度解析 导读 在 Wingwinglang这一面向云开发的语言中 lift编程语言编译器云原生语言运行时上一篇5分钟快速上手免费PDF工具箱PDF补丁丁完整使用指南下一篇MediaPipe Awesome项目终极指南快速上手AI视觉开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑