feat(selfhost): use-after-move tracking in @[Checked] functions

- Add inline movedName0..7 fields to Sema (avoids dynamic allocation issues)
- Sema_AddMoved: track variable moves from let/var init expressions
- Sema_IsMoved: check if variable was moved before use
- Sema_RemoveMoved: clear moved status on reinitialization (x = value)
- Report 'use of moved value' error for moved variable access
This commit is contained in:
2026-06-09 17:40:20 +03:00
parent 41c0fb1d6f
commit 3ce7ba51d2
+82 -2
View File
@@ -16,7 +16,14 @@ struct Sema {
currentRetType: int; // return type of the function being checked
checkedFunc: bool; // true inside @[Checked] function
movedCount: int; // number of moved variables
movedNames: *String; // array of moved variable names
movedName0: String; // inline moved var names (up to 8)
movedName1: String;
movedName2: String;
movedName3: String;
movedName4: String;
movedName5: String;
movedName6: String;
movedName7: String;
}
struct SemaDiag {
@@ -272,6 +279,64 @@ func Sema_ResolveCallArgs(sema: *Sema, expr: *Expr) {
expr.callArgCount = newCount;
}
// ---------------------------------------------------------------------------
// Borrow checker helpers (inline array, matching Decl param pattern)
// ---------------------------------------------------------------------------
func Sema_AddMoved(sema: *Sema, name: String) {
if sema == null as *Sema { return; }
if sema.movedCount >= 8 { return; }
if sema.movedCount == 0 { sema.movedName0 = name; }
else if sema.movedCount == 1 { sema.movedName1 = name; }
else if sema.movedCount == 2 { sema.movedName2 = name; }
else if sema.movedCount == 3 { sema.movedName3 = name; }
else if sema.movedCount == 4 { sema.movedName4 = name; }
else if sema.movedCount == 5 { sema.movedName5 = name; }
else if sema.movedCount == 6 { sema.movedName6 = name; }
else if sema.movedCount == 7 { sema.movedName7 = name; }
sema.movedCount = sema.movedCount + 1;
}
func Sema_IsMoved(sema: *Sema, name: String) -> bool {
if sema == null as *Sema { return false; }
if sema.movedCount > 0 && String_Eq(sema.movedName0, name) { return true; }
if sema.movedCount > 1 && String_Eq(sema.movedName1, name) { return true; }
if sema.movedCount > 2 && String_Eq(sema.movedName2, name) { return true; }
if sema.movedCount > 3 && String_Eq(sema.movedName3, name) { return true; }
if sema.movedCount > 4 && String_Eq(sema.movedName4, name) { return true; }
if sema.movedCount > 5 && String_Eq(sema.movedName5, name) { return true; }
if sema.movedCount > 6 && String_Eq(sema.movedName6, name) { return true; }
if sema.movedCount > 7 && String_Eq(sema.movedName7, name) { return true; }
return false;
}
func Sema_RemoveMoved(sema: *Sema, name: String) {
if sema == null as *Sema { return; }
var found: int = -1;
if sema.movedCount > 0 && String_Eq(sema.movedName0, name) { found = 0; }
else if sema.movedCount > 1 && String_Eq(sema.movedName1, name) { found = 1; }
else if sema.movedCount > 2 && String_Eq(sema.movedName2, name) { found = 2; }
else if sema.movedCount > 3 && String_Eq(sema.movedName3, name) { found = 3; }
else if sema.movedCount > 4 && String_Eq(sema.movedName4, name) { found = 4; }
else if sema.movedCount > 5 && String_Eq(sema.movedName5, name) { found = 5; }
else if sema.movedCount > 6 && String_Eq(sema.movedName6, name) { found = 6; }
else if sema.movedCount > 7 && String_Eq(sema.movedName7, name) { found = 7; }
if found >= 0 {
var i: int = found;
while i < sema.movedCount - 1 {
if i == 0 { sema.movedName0 = sema.movedName1; }
else if i == 1 { sema.movedName1 = sema.movedName2; }
else if i == 2 { sema.movedName2 = sema.movedName3; }
else if i == 3 { sema.movedName3 = sema.movedName4; }
else if i == 4 { sema.movedName4 = sema.movedName5; }
else if i == 5 { sema.movedName5 = sema.movedName6; }
else if i == 6 { sema.movedName6 = sema.movedName7; }
i = i + 1;
}
sema.movedCount = sema.movedCount - 1;
}
}
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
@@ -294,6 +359,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Identifier — look up in scope
if kind == ekIdent {
// Borrow check: use-after-move in @[Checked] functions
if sema.checkedFunc && Sema_IsMoved(sema, expr.strValue) {
Sema_EmitError(sema, expr.line, expr.column,
String_Concat("use of moved value '", String_Concat(expr.strValue, "'")));
}
let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
let errMsg: String = String_Concat("undeclared identifier '", expr.strValue);
@@ -336,6 +406,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
}
}
// Borrow check: reinitialization after move
if sema.checkedFunc && op == tkAssign {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
Sema_RemoveMoved(sema, expr.child1.strValue);
}
}
// Assignment operators return target type
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
return left;
@@ -501,6 +578,10 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
// Let/var
if kind == skLet {
let initType: int = Sema_CheckExpr(sema, stmt.child1);
// Borrow check: move tracking
if sema.checkedFunc && stmt.child1 != null as *Expr && stmt.child1.kind == ekIdent {
Sema_AddMoved(sema, stmt.child1.strValue);
}
// Register variable in scope
var sym: Symbol;
sym.kind = skVar;
@@ -941,7 +1022,6 @@ func Sema_Analyze(mod: *Module) -> *Sema {
s.currentRetType = tyVoid;
s.checkedFunc = false;
s.movedCount = 0;
s.movedNames = bux_alloc(64 as uint * 256) as *String; // up to 64 moved var names
}
// Enable borrow checking for @[Checked] functions