fix: CompoundAssignUpvalue支持 + const变量赋值检查

- 新增 CompoundAssignUpvalue 字节码,闭包中 x+=5 不再报错
- Local/Upvalue 上的 is_const 编译期检查
- globals 改为 HashMap<String, (Value, bool)> 追踪 const,StoreGlobal 运行时拒绝
- DefineGlobal 指令新增 1 字节 mutable 标志
- 更新 README:双执行模式、for-in、require()、VM 模块说明

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
0264408
2026-06-26 09:42:38 +08:00
co-authored by Claude
parent 7b9bded5ee
commit abe0844816
4 changed files with 112 additions and 29 deletions
+35 -12
View File
@@ -77,7 +77,6 @@ struct Local {
name: String,
depth: u8, // scope depth where declared; 0 = uninitialized
is_captured: bool,
#[allow(dead_code)]
is_const: bool,
}
@@ -85,6 +84,7 @@ struct Local {
struct Upvalue {
index: u8,
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue
is_const: bool, // true = the source variable was declared `const`
name: String, // variable name (for transitive upvalue resolution)
}
@@ -203,6 +203,7 @@ impl Compiler {
if self.scope_depth == 0 {
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
self.function.code.push(if mutable { 1 } else { 0 });
// DefineGlobal pushes value back; pop it for statement-level let
self.emit_op(OpCode::Pop);
} else {
@@ -395,6 +396,7 @@ impl Compiler {
if self.scope_depth == 0 {
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
self.function.code.push(1); // mutable=true for fn declarations
} else {
let slot = self.locals.len() as u8;
self.locals.push(Local {
@@ -493,8 +495,23 @@ impl Compiler {
// Simple assignment
self.compile_expr(value)?;
if let Some(slot) = self.resolve_local(name) {
// Check const for local
if self.locals[slot as usize].is_const {
return Err(RuntimeError::RuntimeError {
message: format!("Cannot reassign constant '{}'", name),
token: None,
});
}
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
} else if let Some(uv_idx) = self.resolve_upvalue(name) {
// Check const for upvalue
let upvalues = self.upvalues.borrow();
if upvalues[uv_idx as usize].is_const {
return Err(RuntimeError::RuntimeError {
message: format!("Cannot reassign constant '{}'", name),
token: None,
});
}
emit_u8(&mut self.function.code, OpCode::StoreUpvalue, uv_idx);
} else {
let name_idx = self.add_string_constant(name);
@@ -514,14 +531,9 @@ impl Compiler {
if let Some(slot) = self.resolve_local(name) {
emit_u8(&mut self.function.code, OpCode::CompoundAssignLocal, slot);
self.function.code.push(compound_op as u8);
} else if self.resolve_upvalue(name).is_some() {
// Compound assign on upvalue: load upvalue, compound op, store
// For simplicity, use a workaround: re-emit this as a separate step
// TODO: add CompoundAssignUpvalue opcode
return Err(RuntimeError::RuntimeError {
message: "Compound assignment on upvalue not yet supported".into(),
token: None,
});
} else if let Some(uv_idx) = self.resolve_upvalue(name) {
emit_u8(&mut self.function.code, OpCode::CompoundAssignUpvalue, uv_idx);
self.function.code.push(compound_op as u8);
} else {
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
@@ -793,7 +805,9 @@ impl Compiler {
// Found in direct parent's locals → capture as upvalue
let idx = self.upvalues.borrow().len() as u8;
self.upvalues.borrow_mut().push(Upvalue {
index: i as u8, is_local: true, name: name.to_string()
index: i as u8, is_local: true,
is_const: local.is_const,
name: name.to_string()
});
return Some(idx);
} else {
@@ -832,15 +846,18 @@ impl Compiler {
gp_upvalues.borrow_mut().push(Upvalue {
index: (i - self.parent_locals_count) as u8,
is_local: true,
is_const: false, // can't easily resolve const-ness at this depth
name: name.to_string()
});
idx
}
};
// Parent's upvalue is transitive through grandparent
let gp_uv_is_const = gp_upvalues.borrow()[gp_idx as usize].is_const;
enc_upvalues.borrow_mut().push(Upvalue {
index: gp_idx,
is_local: false,
is_const: gp_uv_is_const,
name: name.to_string()
});
} else {
@@ -848,6 +865,7 @@ impl Compiler {
enc_upvalues.borrow_mut().push(Upvalue {
index: (i - self.parent_locals_count) as u8,
is_local: true,
is_const: local.is_const,
name: name.to_string()
});
}
@@ -855,9 +873,12 @@ impl Compiler {
}
};
// Add transitive upvalue in self pointing to parent's
let parent_uv_is_const = enc_upvalues.borrow()[parent_idx as usize].is_const;
let idx = self.upvalues.borrow().len() as u8;
self.upvalues.borrow_mut().push(Upvalue {
index: parent_idx, is_local: false, name: name.to_string()
index: parent_idx, is_local: false,
is_const: parent_uv_is_const,
name: name.to_string()
});
return Some(idx);
}
@@ -871,7 +892,9 @@ impl Compiler {
if uv.name == name {
let idx = self.upvalues.borrow().len() as u8;
self.upvalues.borrow_mut().push(Upvalue {
index: uv.index, is_local: false, name: name.to_string()
index: uv.index, is_local: false,
is_const: uv.is_const,
name: name.to_string()
});
return Some(idx);
}