Appearance
2026-03-27 P2 Mutable Binding Assignment Diagnostics
背景
ql-typeck 之前对 = 只做左右两侧类型兼容检查,没有约束左侧绑定是否真的可写。
这会带来两个明显问题:
let value = 1; value = 2会被当作纯类型问题处理,缺少“不可变绑定被赋值”的诊断。self接收者已经有self/var self/move self的语法区分,但 typeck 还没有把这条差异落实到赋值语义。
本次收口范围
只补一个保守但真实有价值的语义切片:
var引入的 local binding 允许作为 bare assignment targetvar self允许作为 bare assignment targetletlocal、regular parameter、非var selfreceiver 在 bare assignment 上报错
本次不扩展到:
- field assignment
- index assignment
- place projection / aliasing / write-through 语义
- flow-sensitive reassignability
设计原则
- 不引入新的 resolver truth surface。
- 不为 future place system 预埋一套容易推翻的 ad-hoc member/index 写入规则。
- 复用 HIR 已有的唯一
LocalId,把var语义映射成 typeck 内部的 mutable binding 集合。 - 让 diagnostic 锚定到 assignment target token,而不是整条表达式。
实现策略
1. mutable local 记录
Checker 在遍历 StmtKind::Let { mutable, pattern, .. } 时:
- 先照常检查 initializer
- 绑定 pattern 类型
- 若
mutable == true,递归收集 pattern 中所有Binding(LocalId),记录到mutable_locals
这样 var (left, right) = ... 这种 pattern binding 也能统一继承可写性。
2. receiver mutability
check_function 进入函数时,从第一参数里识别 receiver:
self/move self-> 不可写var self-> 可写
该状态只影响当前函数体内对 bare self = ... 的检查。
3. assignment target 检查
在 BinaryOp::Assign 路径上新增 check_assignment_target(left):
Name-> 查询 resolver 的ValueResolutionLocal(local_id)-> 必须在mutable_localsParam(_)-> 报不可变参数错误SelfValue-> 只有var self通过- 其它 resolution 或非
Namelhs 本轮保持保守,不新增 place-level 结论
新增回归
- 正向:
vartuple destructuring binding 可以被再次赋值var self可以被整体替换
- 负向:
letlocal 赋值报错- regular parameter 赋值报错
- 非
var selfreceiver 赋值报错
- 渲染:
- rendered diagnostics 锚定到 lhs target span
后续安全下一步
如果要继续扩展 assignment 语义,安全顺序应当是:
- 先设计统一的 place model
- 再决定 field/index 写入的 target 分类
- 再把 borrowing / aliasing / cleanup state 与写入语义对齐
在这之前,不应把 member/index assignment 伪装成“已经有完整可写性检查”。