unluac 1.2.2

Multi-dialect Lua decompiler written in Rust.
Documentation
# Readability

> **职责**:把已经合法的 AST 收敛成更接近源码、适合进入 Naming 的稳定形状。
>
> **不负责**:补前层事实、替 AST build 修结构、替 Naming 发明 binding 身份。
>
> **例子**`local __t = obj["n"]``local __t = obj.n`(field-access-sugar pass)

## 入口

```
src/ast/readability.rs
  make_readable(module, target, options, timings, dump_passes) -> AstModule
```

## 模块布局

```
src/ast/readability/
  traverse.rs       共享子节点递归骨架
  walk.rs           rewrite pass 能力(HirRewritePass / AstRewritePass)
  visit.rs          只读收集能力
  binding_flow.rs   binding use 统计 / 替换 / 可见性分析
  binding_tree.rs   binding 树结构分析
  expr_analysis.rs  表达式复杂度 / 内联安全性 / 屏障判断
  cleanup.rs
  statement_merge.rs
  loop_header_merge.rs
  local_coalesce.rs
  branch_pretty.rs
  field_access_sugar.rs
  inline_exprs/
    (use-site 判定 / candidate 收集 / run-collapse)
  short_circuit_pretty.rs
  materialize_temps.rs
  installer_iife.rs
  function_sugar/
    (函数声明 sugar + method-call sugar)
  global_decl_pretty/
  luajit_goto_safety.rs
```

## AST Readability Pass 清单

调度器由 `src/scheduler.rs::run_invalidation_loop` 驱动,5 个 invalidation tag(`AstInvalidation`):
`StatementAdjacency` / `ControlFlowShape` / `ExprShape` / `BindingStructure` / `TempPresence`。

| pass 名 | phase | 主要作用 |
|---|---|---|
| `cleanup` | Normal | 空 block / 冗余节点清理 |
| `local-coalesce` | Normal | seed local + carried local 认同 / 冗余写回裁剪 |
| `statement-merge` | Normal | hoisted temp 声明下沉回首次赋值附近(尊重 goto/label 词法边界) |
| `loop-header-merge` | Normal | loop 头部合并 |
| `branch-pretty` | Normal | goto/label 壳 → 普通 if / guard-return |
| `field-access-sugar` | Normal | `obj["key"]``obj.key` |
| `inline-exprs` | Normal | 局部变量内联(受 ReadabilityOptions 阈值控制),包括相邻调用准备 run 中的简单表构造参数 |
| `short-circuit-pretty` | Normal | 短路表达式规范化 |
| `materialize-temps` | Deferred | 残留 temp → synthetic local(边界 pass) |
| `installer-iife` | Deferred | installer IIFE → 局部函数声明 |
| `function-sugar` | Deferred | 函数声明 sugar + method-call sugar(`local f=obj.m; f(obj,...)``obj:m(...)`|
| `global-decl-pretty` | Deferred | global 声明美化(Lua 5.5 `do + global *` 最小 canonical 形状) |
| `luajit-goto-safety` | Deferred | LuaJIT goto 词法安全性检查 |

**重要**:多个 pass 可能在同一个 dirty 循环内多次执行——这是设计预期,不是重复。例如:
- `field-access-sugar` 会在 `inline-exprs` 之后再跑一轮(inline 暴露了新字符串索引)。
- `statement-merge` 会在 `branch-pretty` 之后再跑一轮(控制流收敛后新暴露 hoisted temp)。
- `local-coalesce` 会在 `branch-pretty` 之后再跑一轮(稳定暴露 "hoisted carried + seed" 结构)。

### 新增 pass 步骤

1. `readability.rs::PASS_DESCRIPTORS` 添加 `PassDescriptor { name, phase, depends_on, invalidates }`2.`PASS_ENTRIES` 添加 `ReadabilityPassEntry { apply: xxx::apply }`(与描述符一一对应)。
3. 新建 `readability/xxx.rs`,优先使用 `walk.rs` / `visit.rs` / `traverse.rs` 共享设施。
4. **优先判断是否已有同类 owner**:如果只是现有 pass 约束过窄漏收了某种形状,应扩展现有 pass 而不是新建。

## 应优先复用的共享设施

| 设施 | 用途 |
|---|---|
| `walk.rs` | rewrite pass,默认先完整递归所有子节点再执行当前 hook,不短路 |
| `visit.rs` | 只读 collector |
| `traverse.rs` | 共享子节点递归骨架,不要为单个 pass 复制一整套递归样板 |
| `binding_flow.rs` | binding use 统计 / mention 集合 / 替换 / 可见性分析 |
| `binding_tree.rs` | binding 树结构分析 |
| `expr_analysis.rs` | 复杂度 / 内联安全性 / 屏障判断 |
| `inline_exprs/use_sites.rs` / `candidate.rs` | use-site 判定共享逻辑 |

**walker 契约**:所有子节点必须先完整递归;不能因为前一子树 `changed=true` 就短路跳过后续;当前节点 hook 也必须总是执行。

## 维护规范

1. **新 pass 默认先接共享 walker/visitor**:不复制专用递归框架。
2. **binding / 表达式约束复用共享 helper**:binding use 统计、mention 集合、替换、access-base 安全判断、单值判定等,补到共享 helper 而不是分散在 pass 内。
3. **`materialize_temps` 是边界 pass**:Readability 结束后不应再把原生 `TempId` 留给 Naming。依赖原生 temp 的新 pass 应放在 `materialize_temps` 之前。
4. **branch-local 值壳回 HIR**`local x; if cond then x=a else x=b end` 这类,如果前层已经足以证明是"给同一 binding 选值",应回 HIR 用 `Decision->Expr` 处理,不在 AST 层重新发明。
5. **`statement_merge` 尊重 goto/label 边界**:不能把 `local` 下沉到 forward goto 之后,否则生成"跳进 local 作用域"的非法 Lua。
6. **`local_coalesce` 也处理 hoist 后的 carried local**:carried local 被 AST build hoist 到块首,seed local 在后面,也应由同一 owner 认回,并裁掉改写后形成的 `x = x` 冗余写回。
7. **`function_sugar` 统一承接 method-call sugar**`local f = recv.field; f(recv, ...)` / `recv.field(recv, ...) and truthy or falsy` 等形状都在同一 owner 判断。

## 向后提供的事实

稳定 AST / 最终保留下来的 synthetic local / 已收敛好的语法糖与源码形状。

## 排错指引

| 症状 | 检查点 |
|---|---|
| hoisted temp 未下沉 | `statement_merge.rs` goto 边界判定 |
| `obj["key"]` 未变 `obj.key` | `field_access_sugar.rs` 字符串 key 识别 |
| method call 未收回 `:` 形式 | `function_sugar/method_alias` |
| carried local 冗余赋值残留 | `local_coalesce.rs` hoist 情形处理 |
| temp 残留到 Naming | `materialize_temps.rs` 是否漏处理某种 TempId |
| 想看 Readability 结果 | `unluac --debug --target-stage readability <file>` |
| 想看某 pass 的 before/after | `unluac --dump-pass cleanup,inline-exprs <file>` |