feat(selfhost): @[Checked] borrow checker — basic write-through-ptr detection

- Add isChecked field to Decl struct (ast.bux)
- Parse @[Checked] attribute in parserParseDecl (parser.bux)
- Add checkedFunc flag and movedVars to Sema struct (sema.bux)
- Enable borrow checking per-function in Sema_Analyze
- Reject assignment through raw pointer (*T) in @[Checked] functions
- Skip newlines between @[Checked] attribute and the declaration keyword
  to avoid parser seeing the attribute and function as separate decls
This commit is contained in:
2026-06-09 17:25:28 +03:00
parent c14578d9dd
commit 81281cbb11
3 changed files with 56 additions and 4 deletions
+25 -2
View File
@@ -14,6 +14,9 @@ struct Sema {
diags: *SemaDiag;
hasError: bool;
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
}
struct SemaDiag {
@@ -325,6 +328,14 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let right: int = Sema_CheckExpr(sema, expr.child2);
let op: int = expr.intValue;
// Borrow check: reject assignment through deref in @[Checked] functions
if sema.checkedFunc && op == tkAssign {
if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead");
}
}
// Assignment operators return target type
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
return left;
@@ -360,7 +371,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if kind == ekAssign {
let target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2);
// Basic type compatibility (permissive for now)
// Borrow check: reject assignment through deref in @[Checked] functions
if sema.checkedFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead");
}
return target;
}
@@ -895,9 +910,16 @@ func Sema_Analyze(mod: *Module) -> *Sema {
if decl.retType != null as *TypeExpr {
s.currentRetType = Sema_ResolveType(s, decl.retType);
} else {
s.currentRetType = tyVoid;
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
let wasChecked: bool = s.checkedFunc;
s.checkedFunc = decl.isChecked != 0;
// Check body statements
var stmt: *Stmt = decl.refBody.firstStmt;
while stmt != null as *Stmt {
@@ -905,6 +927,7 @@ func Sema_Analyze(mod: *Module) -> *Sema {
stmt = stmt.nextStmt;
}
s.checkedFunc = wasChecked;
s.scope = prevScope;
}
decl = decl.childDecl2;