From aba9370ea6e0fd6a90e17e1eb08fe7d4a18be6c5 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 20:46:45 +0900 Subject: [PATCH] implement M6 borrow checker integration --- fec/src/check.c | 488 +++++++++++++++++++++++++++++++++++++++++++++-- fec/src/own.c | 5 +- fec/src/parser.c | 2 +- 3 files changed, 481 insertions(+), 14 deletions(-) diff --git a/fec/src/check.c b/fec/src/check.c index 88d6588..01a9396 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -15,6 +15,14 @@ struct FeSym { int initialized; int moved; FeNode *decl; + /* M6 ownership is tracked at the root local/parameter. A reference + binding remembers that root so releasing the binding's last use can + release the root borrow without a separate alias engine. */ + FeOwnState own; + FeSym *borrow_root; + int borrow_mut; + int borrow_defer; + FeScope *owner; }; struct FeScope { @@ -33,6 +41,8 @@ typedef struct FeCheckerState { FeType *ret; unsigned loop_depth; unsigned defer_depth; + FeOwnLiveness liveness; + FeNode *fn_node; } FeCheckerState; static FeType *unknown(FeCheck *c) @@ -55,6 +65,30 @@ static void mark_moved(FeCheckerState *s, FeNode *n, FeType *t) FeSym *sym=0; if (n && n->kind==FE_N_IDENT) sym=find_symbol(s->scope,n->text ? n->text : ""); + if (s->defer_depth != 0) { + /* A defer capture keeps the owner live until scope cleanup; its body + is not an immediate consuming use. */ + fe_own_mark_consumed(s->c->diags, + sym ? &sym->moved : 0, + sym ? sym->decl : 0, + n,t,1); + return; + } + if (sym && t && !fe_own_is_copy_type(t)) { + if (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX) { + fe_diag_error(s->c->diags,n->loc, + "cannot move a non-Copy value out of a projection; use mem.replace"); + return; + } + if (fe_own_access(s->c->diags,&sym->own,FE_OWN_MOVE,n->loc)) { + sym->moved=sym->own.move; + /* Keep the existing emitter contract: ownership-consuming AST + uses carry this flag, while FeOwnState is the diagnostic + authority. */ + fe_own_mark_consumed(s->c->diags,&sym->moved,sym->decl,n,t,0); + } + return; + } fe_own_mark_consumed(s->c->diags, sym ? &sym->moved : 0, sym ? sym->decl : 0, @@ -212,6 +246,11 @@ static FeSym *add_symbol(FeCheckerState *s, FeScope *scope, sym->initialized = initialized; sym->moved = FE_OWN_AVAILABLE; sym->decl = decl; + fe_own_state_init(&sym->own, initialized); + sym->borrow_root = 0; + sym->borrow_mut = 0; + sym->borrow_defer = 0; + sym->owner = scope; if (decl) { decl->cname = cname; decl->sem_type = type; @@ -264,6 +303,8 @@ typedef struct FeFlowSlot { FeSym *sym; int moved; int initialized; + int own_move; + int own_initialized; } FeFlowSlot; static unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap) @@ -276,6 +317,8 @@ static unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap) slots[count].sym=&p->items[i]; slots[count].moved=p->items[i].moved; slots[count].initialized=p->items[i].initialized; + slots[count].own_move=p->items[i].own.move; + slots[count].own_initialized=p->items[i].own.initialized; ++count; } return count; @@ -287,6 +330,8 @@ static void flow_restore(FeFlowSlot *slots, unsigned count) for (i=0; imoved=slots[i].moved; slots[i].sym->initialized=slots[i].initialized; + slots[i].sym->own.move=slots[i].own_move; + slots[i].sym->own.initialized=slots[i].own_initialized; } } @@ -297,6 +342,237 @@ static void flow_merge(FeFlowSlot *base, FeFlowSlot *left, FeFlowSlot *right, for (i=0; imoved=fe_own_merge_move(left[i].moved,right[i].moved); base[i].sym->initialized=left[i].initialized && right[i].initialized; + base[i].sym->own.move=fe_own_merge_move(left[i].own_move,right[i].own_move); + base[i].sym->own.initialized=left[i].own_initialized && right[i].own_initialized; + } +} + +static FeSym *own_root_symbol(FeCheckerState *s, FeNode *expr) +{ + FeOwnPlace place; + if (!fe_own_place_from_expr(expr,&place)) return 0; + return find_symbol(s->scope,place.root->text ? place.root->text : ""); +} + +static int own_is_global(FeCheckerState *s, FeSym *sym) +{ + FeScope *p; + if (!s || !sym) return 0; + for (p=s->globals; p; p=p->parent) { + unsigned i; + for (i=0;icount;++i) if (&p->items[i]==sym) return 1; + } + return 0; +} + +static void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable) +{ + FeSym *root=own_root_symbol(s,expr); + if (!root) return; + if (mutable && root->type && root->type->kind==FE_TYPE_REF && + !root->type->ref_mut) { + err(s->c,expr->loc,"cannot create mutable borrow from a shared reference"); + return; + } + if (own_is_global(s,root) && + !(root->decl && root->decl->kind==FE_N_GLOBAL && + (root->decl->flags & 2U) && !mutable)) { + err(s->c,expr->loc,"cannot borrow a mutable global"); + return; + } + fe_own_access(s->c->diags,&root->own, + mutable ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED, + expr->loc); +} + +static void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr) +{ + FeSym *root; + if (!expr || expr->kind!=FE_N_UNARY || !expr->text) return; + if (strcmp(expr->text,"&")!=0 && strcmp(expr->text,"&mut")!=0) return; + root=own_root_symbol(s,expr->a); + if (!root) return; + if (strcmp(expr->text,"&mut")==0) fe_own_release_exclusive(&root->own); + else fe_own_release_shared(&root->own); +} + +/* Return-reference provenance is represented at call sites by retaining a + borrow of the unique reference-derived argument (or method receiver). */ +static FeSym *own_derived_call_root(FeCheckerState *s, FeNode *call) +{ + FeNode *param; + FeNode *arg; + FeNode *source=0; + unsigned refs=0; + if (!call || call->kind!=FE_N_CALL || !call->sem_type || + !fe_own_is_reference_like(call->sem_type)) return 0; + if (call->a && call->a->kind==FE_N_MEMBER && call->sem_decl) { + param=call->sem_decl->a ? call->sem_decl->a->children : 0; + if (param && param->text && strcmp(param->text,"self")==0) + return own_root_symbol(s,call->a->a); + } + if (!call->sem_decl) return 0; + param=call->sem_decl->a ? call->sem_decl->a->children : 0; + arg=call->children; + while (param && arg) { + FeType *t=node_type(s->c,param->a); + if (fe_own_is_reference_like(t)) { ++refs; source=arg; } + param=param->next; + arg=arg->next; + } + return refs==1 ? own_root_symbol(s,source) : 0; +} + +static void own_bind_derived_call(FeCheckerState *s, FeSym *binding, + FeNode *value) +{ + FeSym *root; + if (!binding || !value || value->kind!=FE_N_CALL) return; + root=own_derived_call_root(s,value); + if (!root) return; /* Static provenance. */ + if (root->borrow_root) root=root->borrow_root; + if (value->sem_type->kind==FE_TYPE_REF && value->sem_type->ref_mut) + fe_own_access(s->c->diags,&root->own,FE_OWN_BORROW_MUT,value->loc); + else + fe_own_access(s->c->diags,&root->own,FE_OWN_BORROW_SHARED,value->loc); + binding->borrow_root=root; + binding->borrow_mut=value->sem_type->kind==FE_TYPE_REF && value->sem_type->ref_mut; +} + +static int own_stmt_uses(FeNode *node, const char *name) +{ + FeNode *x; + if (!node || !name) return 0; + if (node->kind==FE_N_IDENT && node->text && strcmp(node->text,name)==0) + return 1; + if (own_stmt_uses(node->a,name) || own_stmt_uses(node->b,name) || + own_stmt_uses(node->c,name)) return 1; + for (x=node->children;x;x=x->next) + if (own_stmt_uses(x,name)) return 1; + return 0; +} + +static int own_defer_uses(FeNode *node, const char *name) +{ + FeNode *x; + if (!node) return 0; + if (node->kind==FE_N_DEFER && own_stmt_uses(node->a,name)) return 1; + if (own_defer_uses(node->a,name) || own_defer_uses(node->b,name) || + own_defer_uses(node->c,name)) return 1; + for (x=node->children;x;x=x->next) + if (own_defer_uses(x,name)) return 1; + return 0; +} + +static int own_contains_node(FeNode *node, FeNode *needle) +{ + FeNode *x; + if (!node || !needle) return 0; + if (node==needle) return 1; + if (own_contains_node(node->a,needle) || + own_contains_node(node->b,needle) || + own_contains_node(node->c,needle)) return 1; + for (x=node->children;x;x=x->next) + if (own_contains_node(x,needle)) return 1; + return 0; +} + +static void own_release_after_stmt(FeCheckerState *s, FeScope *scope, + FeNode *stmt, int scope_end) +{ + unsigned i; + FeScope *p; + const FeOwnLastUse *last; + for (p=scope;p;p=scope_end ? 0 : p->parent) for (i=0;icount;++i) { + FeSym *ref=&p->items[i]; + if (!ref->borrow_root) continue; + last=fe_own_last_use(&s->liveness, + ref->decl && ref->decl->text ? ref->decl->text : ref->name); + if (!scope_end && (ref->borrow_defer || !last || last->defer_extended || + !own_contains_node(stmt,last->last_node))) continue; + if (ref->borrow_mut) fe_own_release_exclusive(&ref->borrow_root->own); + else fe_own_release_shared(&ref->borrow_root->own); + ref->borrow_root=0; + } +} + +/* Full borrow snapshots live in the AST arena, rather than on the 16-bit + compiler stack. The compact FeFlowSlot arrays retain the pre-M6 move and + initialization flow handling. */ +static FeOwnState *flow_own_new(FeCheckerState *s, unsigned count) +{ + if (!s || !count) return 0; + return (FeOwnState *)fe_arena_alloc(&s->c->ast->arena, + count*sizeof(FeOwnState)); +} + +static void flow_own_capture(FeFlowSlot *slots, FeOwnState *states, + unsigned count) +{ + unsigned i; + if (!states) return; + for (i=0;iown; +} + +static void flow_own_restore(FeFlowSlot *slots, FeOwnState *states, + unsigned count) +{ + unsigned i; + if (!states) return; + for (i=0;iown=states[i]; +} + +static void flow_own_merge(FeFlowSlot *slots, FeOwnState *left, + FeOwnState *right, unsigned count) +{ + unsigned i; + if (!left || !right) return; + for (i=0;iown=fe_own_merge_state(left[i],right[i]); +} + +typedef struct FeFlowBorrow { + FeSym *root; + int mutable; +} FeFlowBorrow; + +static FeFlowBorrow *flow_borrow_new(FeCheckerState *s, unsigned count) +{ + if (!s || !count) return 0; + return (FeFlowBorrow *)fe_arena_alloc(&s->c->ast->arena, + count*sizeof(FeFlowBorrow)); +} + +static void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states, + unsigned count) +{ + unsigned i; + if (!states) return; + for (i=0;iborrow_root; + states[i].mutable=slots[i].sym->borrow_mut; + } +} + +static void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states, + unsigned count) +{ + unsigned i; + if (!states) return; + for (i=0;iborrow_root=states[i].root; + slots[i].sym->borrow_mut=states[i].mutable; + } +} + +static void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left, + FeFlowBorrow *right, unsigned count) +{ + unsigned i; + if (!left || !right) return; + for (i=0;iborrow_root=left[i].root ? left[i].root : right[i].root; + slots[i].sym->borrow_mut=left[i].mutable || right[i].mutable; } } @@ -544,9 +820,10 @@ static FeType *check_identifier(FeCheckerState *s, FeNode *n, int read) } n->cname = sym->cname; n->sem_type = sym->type; - fe_own_check_use(s->c->diags,sym->moved,n->loc); - if (read && !sym->initialized && !sym->fn) - err(s->c, n->loc, "use of uninitialized variable"); + if (!sym->fn) { + fe_own_access(s->c->diags,&sym->own,FE_OWN_READ,n->loc); + sym->moved=sym->own.move; + } return sym->type; } @@ -601,6 +878,9 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) a=unknown(c); } } else if (strcmp(op,"&")==0 || strcmp(op,"&mut")==0) { + if (strcmp(op,"&mut")==0 && a && a->kind==FE_TYPE_REF && !a->ref_mut) + err(c,n->loc,"cannot create mutable borrow from a shared reference"); + own_borrow_expr(s,n->a,strcmp(op,"&mut")==0); a=fe_type_ref(&c->types,a,strcmp(op,"&mut")==0); } n->sem_type = a; @@ -761,6 +1041,13 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) fe_type_intern(&c->types,"void"); return n->sem_type; } + if (et && (et->kind==FE_TYPE_SLICE || et->kind==FE_TYPE_STR) && + n->a->b && n->a->b->text && + strcmp(n->a->b->text,"trim")==0) { + if (n->children) err(c,n->loc,"trim takes no arguments"); + n->sem_type=fe_type_slice(&c->types,et->elem); + return n->sem_type; + } variant=et && et->kind==FE_TYPE_ENUM ? fe_type_variant(et,n->a->b ? n->a->b->text : "") : 0; arg=n->children; @@ -787,13 +1074,24 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) arg = n->children; while (param && arg) { a = check_expr(s, arg); - mark_moved(s,arg,a); b = node_type(c, param->a); + if (b && a && b->kind==FE_TYPE_REF && !b->ref_mut && + a->kind==FE_TYPE_REF && a->ref_mut) { + FeSym *root=own_root_symbol(s,arg); + if (root && root->borrow_root) root=root->borrow_root; + if (root) fe_own_call_shared_view(c->diags,&root->own,arg->loc); + } else if (b && a && b->kind==FE_TYPE_SLICE && !b->ref_mut && + a->kind==FE_TYPE_SLICE && a->ref_mut) { + /* Call-only []mut -> [] weakening is a temporary view. */ + } else mark_moved(s,arg,a); if (!compatible(b, a, arg) && !(b && a && b->kind==FE_TYPE_SLICE && a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut && fe_type_equal(b->elem,a->elem)) && + !(b && a && b->kind==FE_TYPE_REF && a->kind==FE_TYPE_REF && + !b->ref_mut && a->ref_mut && fe_type_equal(b->elem,a->elem)) && a->kind != FE_TYPE_UNKNOWN) err(c, arg->loc, "argument type mismatch"); + own_release_temporary_borrow(s,arg); param = param->next; arg = arg->next; } @@ -869,8 +1167,10 @@ static FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read) err(s->c, n->loc, "cannot assign to immutable let"); n->cname = sym->cname; n->sem_type = sym->type; - if (read && !sym->initialized) - err(s->c, n->loc, "use of uninitialized variable"); + if (read) { + fe_own_access(s->c->diags,&sym->own,FE_OWN_READ,n->loc); + sym->moved=sym->own.move; + } return sym->type; } if (n && n->kind == FE_N_MEMBER) { @@ -1090,6 +1390,64 @@ static void check_type_cycles(FeCheck *c) for (t=c->types.types;t;t=t->next) check_type_cycle(c,t); } +static int own_ast_reference_type(FeNode *type) +{ + if (!type || !type->text) return 0; + return strcmp(type->text,"&")==0 || strcmp(type->text,"&mut")==0 || + (strcmp(type->text,"[")==0 && !type->a) || strcmp(type->text,"str")==0; +} + +static int own_ast_pointer_to_reference(FeNode *type) +{ + return type && type->text && strcmp(type->text,"*")==0 && + own_ast_reference_type(type->a); +} + +static void check_reference_storage(FeCheck *c, FeNode *decl) +{ + FeNode *m; + if (!decl) return; + if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) { + for (m=decl->children;m;m=m->next) + if (m->kind==FE_N_FIELD && + (own_ast_reference_type(m->a) || own_ast_pointer_to_reference(m->a))) + err(c,m->loc,"reference type is not allowed in aggregate storage"); + } + if ((decl->kind==FE_N_GLOBAL || decl->kind==FE_N_CONST) && decl->a && + own_ast_reference_type(decl->a) && + !(decl->kind==FE_N_CONST && decl->a->text && strcmp(decl->a->text,"str")==0)) + err(c,decl->loc,"reference type is not allowed in global storage"); + if (decl->kind==FE_N_FN && decl->b && own_ast_pointer_to_reference(decl->b)) + err(c,decl->b->loc,"reference type is not allowed as a pointer target"); + if (decl->kind==FE_N_FN) + for (m=decl->a ? decl->a->children : 0;m;m=m->next) + if (own_ast_pointer_to_reference(m->a)) + err(c,m->loc,"reference type is not allowed as a pointer target"); +} + +static int own_return_from_allowed_root(FeCheckerState *s, FeNode *expr) +{ + FeSym *root; + FeNode *p; + unsigned refs=0; + if (!expr) return 0; + root=own_root_symbol(s,expr); + if (!root) return 1; /* Static-producing builtins/methods are checked by + their declared R8 interface. */ + if (own_is_global(s,root)) + return root->decl && root->decl->kind==FE_N_GLOBAL && + (root->decl->flags & 2U); + if (!root->decl || root->decl->kind!=FE_N_PARAM) return 0; + for (p=s->fn_node && s->fn_node->a ? s->fn_node->a->children : 0; + p;p=p->next) { + FeType *t=p->sem_type ? p->sem_type : node_type(s->c,p->a); + if (fe_own_is_reference_like(t)) ++refs; + } + if (s->fn_node && s->fn_node->text && refs && + root->name && strcmp(root->name,"self")==0) return 1; + return refs==1; +} + static void check_stmt(FeCheckerState *s, FeNode *n) { FeCheck *c = s->c; @@ -1104,7 +1462,11 @@ static void check_stmt(FeCheckerState *s, FeNode *n) case FE_N_BLOCK: old = s->scope; s->scope = scope_new(s, old); - for (x = n->children; x; x = x->next) check_stmt(s, x); + for (x = n->children; x; x = x->next) { + check_stmt(s,x); + own_release_after_stmt(s,s->scope,x,0); + } + own_release_after_stmt(s,s->scope,n,1); s->scope = old; break; case FE_N_LET: @@ -1121,8 +1483,16 @@ static void check_stmt(FeCheckerState *s, FeNode *n) if (n->kind==FE_N_LET && a->kind==FE_TYPE_SLICE && a->ref_mut) err(c,n->loc,"let cannot bind a mutable slice"); mark_moved(s,n->b,b); - add_symbol(s, s->scope, n->text, a, 0, 0, 1, + sym=add_symbol(s, s->scope, n->text, a, 0, 0, 1, local_cname(c, n->text ? n->text : "local"), n); + if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text && + (strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) { + sym->borrow_root=own_root_symbol(s,n->b->a); + sym->borrow_mut=strcmp(n->b->text,"&mut")==0; + sym->borrow_defer=s->defer_depth != 0 || + own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text); + } + own_bind_derived_call(s,sym,n->b); break; case FE_N_VAR: a = n->a ? node_type(c, n->a) : unknown(c); @@ -1138,8 +1508,16 @@ static void check_stmt(FeCheckerState *s, FeNode *n) err(c, n->loc, "void expression cannot initialize a variable"); mark_moved(s,n->b,b); initialized = n->b != 0; - add_symbol(s, s->scope, n->text, a, 0, 1, initialized, + sym=add_symbol(s, s->scope, n->text, a, 0, 1, initialized, local_cname(c, n->text ? n->text : "local"), n); + if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text && + (strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) { + sym->borrow_root=own_root_symbol(s,n->b->a); + sym->borrow_mut=strcmp(n->b->text,"&mut")==0; + sym->borrow_defer=s->defer_depth != 0 || + own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text); + } + own_bind_derived_call(s,sym,n->b); break; case FE_N_ASSIGN: b = check_expr(s, n->b); @@ -1149,7 +1527,26 @@ static void check_stmt(FeCheckerState *s, FeNode *n) mark_moved(s,n->b,b); sym = n->a && n->a->kind == FE_N_IDENT ? find_symbol(s->scope, n->a->text) : 0; - if (sym && sym->mutable) sym->initialized = 1; + if (sym && sym->mutable) { + sym->initialized = 1; + fe_own_access(s->c->diags,&sym->own,FE_OWN_WRITE,n->a->loc); + sym->moved=sym->own.move; + if (n->b && n->b->kind==FE_N_UNARY && n->b->text && + (strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0) && + fe_own_is_reference_like(sym->type)) { + FeSym *root=own_root_symbol(s,n->b->a); + if (root && root->owner!=sym->owner) + err(c,n->b->loc,"reference would outlive its source scope"); + else if (root) { + if (sym->borrow_root) { + if (sym->borrow_mut) fe_own_release_exclusive(&sym->borrow_root->own); + else fe_own_release_shared(&sym->borrow_root->own); + } + sym->borrow_root=root; + sym->borrow_mut=strcmp(n->b->text,"&mut")==0; + } + } + } break; case FE_N_EXPR_STMT: check_expr(s, n->a); @@ -1165,50 +1562,104 @@ static void check_stmt(FeCheckerState *s, FeNode *n) break; case FE_N_IF: { FeFlowSlot base[64], left[64], right[64]; + FeOwnState *own_base, *own_left, *own_right; + FeFlowBorrow *borrow_base, *borrow_left, *borrow_right; unsigned flow_count; a = check_expr(s, n->a); if (known(a) && a->kind != FE_TYPE_BOOL) err(c, n->loc, "if condition must be bool"); flow_count=flow_capture(s->scope,base,64); + own_base=flow_own_new(s,flow_count); + own_left=flow_own_new(s,flow_count); + own_right=flow_own_new(s,flow_count); + borrow_base=flow_borrow_new(s,flow_count); + borrow_left=flow_borrow_new(s,flow_count); + borrow_right=flow_borrow_new(s,flow_count); + flow_own_capture(base,own_base,flow_count); + flow_borrow_capture(base,borrow_base,flow_count); check_stmt(s, n->b); flow_capture(s->scope,left,flow_count); + flow_own_capture(left,own_left,flow_count); + flow_borrow_capture(left,borrow_left,flow_count); flow_restore(base,flow_count); + flow_own_restore(base,own_base,flow_count); + flow_borrow_restore(base,borrow_base,flow_count); if (n->c) check_stmt(s, n->c); - if (n->c) flow_capture(s->scope,right,flow_count); + if (n->c) { + flow_capture(s->scope,right,flow_count); + flow_own_capture(right,own_right,flow_count); + flow_borrow_capture(right,borrow_right,flow_count); + } else { unsigned i; - for (i=0;ia); if (known(a) && a->kind != FE_TYPE_BOOL) err(c, n->loc, "while condition must be bool"); flow_count=flow_capture(s->scope,base,64); + own_base=flow_own_new(s,flow_count); + own_body=flow_own_new(s,flow_count); + own_entry2=flow_own_new(s,flow_count); + borrow_base=flow_borrow_new(s,flow_count); + borrow_body=flow_borrow_new(s,flow_count); + borrow_entry2=flow_borrow_new(s,flow_count); + flow_own_capture(base,own_base,flow_count); + flow_borrow_capture(base,borrow_base,flow_count); if (s->loop_depth < 255U) ++s->loop_depth; check_stmt(s, n->b); if (s->loop_depth) --s->loop_depth; flow_capture(s->scope,body,flow_count); + flow_own_capture(body,own_body,flow_count); + flow_borrow_capture(body,borrow_body,flow_count); for (i=0;iloop_depth < 255U) ++s->loop_depth; check_stmt(s,n->b); if (s->loop_depth) --s->loop_depth; flow_capture(s->scope,body,flow_count); + flow_own_capture(body,own_body,flow_count); + flow_borrow_capture(body,borrow_body,flow_count); for(i=0;ia ? check_expr(s, n->a) : fe_type_intern(&c->types, "void"); + if (s->ret && fe_own_is_reference_like(s->ret) && + !own_return_from_allowed_root(s,n->a)) + err(c,n->loc,"reference return must be derived from a parameter or static"); mark_moved(s,n->a,b); if (known(b) && b->kind == FE_TYPE_VOID && s->ret->kind != FE_TYPE_VOID) err(c, n->loc, "void expression returned from value function"); @@ -1253,6 +1707,9 @@ static void check_fn(FeCheck *c, FeNode *fn, FeScope *globals) s.ret = fn->b ? node_type(c, fn->b) : fe_type_intern(&c->types, "void"); s.loop_depth=0; s.defer_depth=0; + s.fn_node=fn; + fe_own_liveness_init(&s.liveness,&c->ast->arena); + fe_own_collect_last_uses(&s.liveness,fn); fn->sem_type = s.ret; for (x = fn->a ? fn->a->children : 0; x; x = x->next) { t = node_type(c, x->a); @@ -1278,6 +1735,9 @@ static void check_method(FeCheck *c, FeNode *fn, FeScope *globals, s.ret=fn->b ? method_type(c,fn->b,owner) : fe_type_intern(&c->types,"void"); s.loop_depth=0; s.defer_depth=0; + s.fn_node=fn; + fe_own_liveness_init(&s.liveness,&c->ast->arena); + fe_own_collect_last_uses(&s.liveness,fn); fn->sem_type=s.ret; for(x=fn->a ? fn->a->children : 0; x; x=x->next) { t=method_type(c,x->a,owner); @@ -1302,6 +1762,8 @@ int fe_check_program(FeCheck *c) for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) if (n->kind == FE_N_STRUCT) fe_type_declare_struct(&c->types, n, (n->flags & 1U) != 0); + for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) + check_reference_storage(c,n); for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) if (n->kind == FE_N_ENUM) fe_type_declare_enum(&c->types, n); for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) @@ -1370,5 +1832,7 @@ FeType *fe_check_expr_type(FeCheck *c, FeNode *n) s.ret = fe_type_intern(&c->types, "void"); s.loop_depth=0; s.defer_depth=0; + s.fn_node=0; + fe_own_liveness_init(&s.liveness,&c->ast->arena); return check_expr(&s, n); } diff --git a/fec/src/own.c b/fec/src/own.c index f40fd43..ee37c9b 100644 --- a/fec/src/own.c +++ b/fec/src/own.c @@ -314,7 +314,10 @@ static int fe_own_live_add(FeOwnLiveness *live, FeNode *decl) FeOwnLastUse *slot; unsigned capacity; const char *cname; - if (!live || !decl || !live->arena || !fe_own_is_reference_like(decl->sem_type)) + /* This prepass runs before local inference. Recording every local is + harmless (only reference bindings consume the result) and lets an + inferred `let r = &x` participate in last-use analysis. */ + if (!live || !decl || !live->arena) return 1; cname = decl->cname ? decl->cname : decl->text; if (!cname || fe_own_live_find(live, cname)) return 1; diff --git a/fec/src/parser.c b/fec/src/parser.c index 2e9366d..8d8f8f4 100644 --- a/fec/src/parser.c +++ b/fec/src/parser.c @@ -221,7 +221,7 @@ static FeNode *decl(FeParser *p) if(eat(p,FE_TOK_ENUM)) { n=toknode(p,FE_N_ENUM,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected enum name");want(p,FE_TOK_LBRACE,"expected '{' in enum");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected variant name");recover(p);break;}if(eat(p,FE_TOK_LPAREN)){v->a=type(p);want(p,FE_TOK_RPAREN,"expected ')' in variant");}else if(eat(p,FE_TOK_LBRACE)){while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))fe_node_add(v,field(p));want(p,FE_TOK_RBRACE,"expected '}' in variant");}fe_node_add(n,v);if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RBRACE,"expected '}' after enum");return n; } if(eat(p,FE_TOK_ERROR_KW)) { n=toknode(p,FE_N_ERROR_DECL,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected error name");want(p,FE_TOK_LBRACE,"expected '{' in error declaration");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected error member");recover(p);break;}want(p,FE_TOK_EQ,"expected '=' in error member");v->a=expr(p,0);want(p,FE_TOK_COMMA,"expected ',' in error declaration");fe_node_add(n,v);}want(p,FE_TOK_RBRACE,"expected '}' after error");return n; } if(eat(p,FE_TOK_SHARED)) { shared=1; if(eat(p,FE_TOK_ATOMIC)) atomic=1; if(!is(p,FE_TOK_VAR)) error(p,"expected 'var' after shared"); } - if(is(p,FE_TOK_CONST)||is(p,FE_TOK_STATIC)||is(p,FE_TOK_VAR)) { FeTokKind kk=p->current.kind;next(p);n=toknode(p,kk==FE_TOK_CONST?FE_N_CONST:FE_N_GLOBAL,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected declaration name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in declaration");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after declaration");return n; } + if(is(p,FE_TOK_CONST)||is(p,FE_TOK_STATIC)||is(p,FE_TOK_VAR)) { FeTokKind kk=p->current.kind;next(p);n=toknode(p,kk==FE_TOK_CONST?FE_N_CONST:FE_N_GLOBAL,t);if(kk==FE_TOK_STATIC)n->flags|=2U;if(shared)n->flags|=4U;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected declaration name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in declaration");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after declaration");return n; } error(p,"expected declaration"); before=p->current.kind; recover(p); if (p->current.kind==before && p->current.kind!=FE_TOK_EOF) next(p); return 0;