Files
doslang-mirror/fec/src/checkcal.c
T
coolguy d9bced830c GOAL P1-4: 작은 규칙 일곱을 정하고, 어긋난 둘을 고친다
일곱 중 다섯은 구현이 이미 옳게 답하고 있었다 -- match 는 enum 전용이고,
enum payload 에 소유 타입을 담을 수 있고, undefined 배열은 원소 단위로
추적하지 않고, 다른 유닛에서 private 필드가 있는 리터럴은 거부되고,
by-value self 의 부분 이동도 mem.replace 가 필요하다. SPEC §7.9 에 적었다.

나머지 둘은 그냥 통과하고 있었다.

defer { return 1; } 이 받아들여졌다. 지연 블록은 함수가 무엇을 반환할지 이미
정한 뒤 스코프 정리 중에 도는데 거기서 return 이 뜻할 것이 없다.

for x in xs { xs[0] = 9; } 도 받아들여졌다. x 가 xs 안을 가리키는 참조이니
순회 몸통에서 xs 에 쓰는 것은 그 참조가 가리키는 것을 옮기는 일이다 (R6).
순회는 이제 도는 동안 대여한다. 읽기는 공유 순회에서 그대로 된다.

240/240, 35/35.
2026-08-17 16:10:44 +09:00

1044 lines
41 KiB
C

#include "checkpri.h"
int is_error_set_member(FeCheckerState *s, FeNode *n)
{
return n && n->kind==FE_N_MEMBER && n->a && n->a->kind==FE_N_IDENT &&
n->a->text && strcmp(n->a->text,"error")==0 &&
n->b && n->b->text && !find_symbol(s->scope,"error");
}
/* `binding.name` used as a value rather than called. */
FeType *cross_unit_value(FeCheckerState *s, FeNode *n, int *handled)
{
FeUnit *home=binding_unit(s,n->a);
FeSym *sym;
*handled=0;
if (!home) return 0;
*handled=1;
sym=unit_member(s->c,home,n->b && n->b->text ? n->b->text : "");
if (!sym) {
/* A name in another unit can be a type as well as a value --
`binding.Enum.Variant` reaches one through the other. */
FeType *there=unit_type(s->c,home,n->b && n->b->text ? n->b->text : "");
FeNode *decl=unit_type_decl(s->c,home,
n->b && n->b->text ? n->b->text : "");
if (there && decl) {
if (!decl_is_public(decl)) {
err(s->c,n->loc,"type is private to its unit");
return unknown(s->c);
}
n->sem_type=there;
return there;
}
err(s->c,n->loc,"unknown name");
return unknown(s->c);
}
if (!decl_is_public(sym->decl)) {
err(s->c,n->loc,"name is private to its unit");
return unknown(s->c);
}
n->cname=sym->cname;
n->sem_decl=sym->decl;
n->sem_type=sym->type;
return sym->type;
}
/* `skip` leading parameters and arguments have already been consumed as
comptime type arguments. */
FeType *check_call_args(FeCheckerState *s, FeNode *n, FeSym *sym,
const char *home, unsigned skip)
{
FeCheck *c=s->c;
FeNode *param;
FeNode *arg;
FeType *a;
FeType *b;
unsigned k;
if (n->a) n->a->cname = sym->cname;
n->sem_decl = sym->fn;
if (!sym->fn) {
err(c, n->loc, "name is not a function");
return unknown(c);
}
param = sym->fn->a ? sym->fn->a->children : 0;
arg = n->children;
for (k=0;k<skip;++k) {
if (param) param=param->next;
if (arg) arg=arg->next;
}
while (param && arg) {
a = check_expr(s, arg);
b = node_type_in(c, home, 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 if (call_reborrows(b, a)) {
/* Handing an exclusive borrow to a call lends it for the length of
that call and takes it back after: the caller cannot touch it
meanwhile, so nothing is aliased. Without this an exclusive
parameter could be passed onwards exactly once. */
} 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;
}
if (param || arg) err(c, n->loc, "wrong number of arguments");
a = sym->fn->b ? node_type_in(c, home, sym->fn->b) :
fe_type_intern(&c->types, "void");
n->sem_type = a;
return a;
}
FeType *check_call(FeCheckerState *s, FeNode *n)
{
FeCheck *c;
FeNode *arg;
FeNode *value;
FeNode *param;
FeSym *sym;
FeType *a;
FeType *b;
FeType *expected;
c=s->c;
if (n->a && n->a->kind==FE_N_IDENT && n->a->text &&
strcmp(n->a->text,"Some")==0) {
err(c,n->loc,"Some is only valid as an optional pattern");
n->sem_type=unknown(c);
return n->sem_type;
}
if (n->a && n->a->kind==FE_N_MEMBER && n->a->a &&
n->a->a->kind==FE_N_IDENT && n->a->a->text &&
strcmp(n->a->a->text,"mem")==0 && n->a->b && n->a->b->text) {
arg=n->children;
if (strcmp(n->a->b->text,"replace")==0) {
value=arg ? arg->next : 0;
if (!arg || !value || value->next) {
err(c,n->loc,"mem.replace requires destination and value");
n->sem_type=unknown(c);
return n->sem_type;
}
a=check_expr(s,arg);
if (!a || a->kind!=FE_TYPE_REF || !a->ref_mut ||
!arg->a || !lvalue_writable(s,arg->a))
err(c,n->loc,"mem.replace destination must be a mutable place");
expected=a && a->kind==FE_TYPE_REF ? a->elem : 0;
b=m7_check_expected(s,value,expected);
if (expected && !fe_type_equal(expected,b) &&
!m7_actual_compatible(expected,b,value))
err(c,value->loc,"mem.replace value type mismatch");
mark_moved(s,value,value->sem_type ? value->sem_type : b);
own_release_temporary_borrow(s,arg);
n->sem_type=expected ? expected : unknown(c);
fe_type_require_replace(&c->types,n->sem_type);
return n->sem_type;
}
if (strcmp(n->a->b->text,"destroy")==0) {
a=arg ? check_expr(s,arg) : unknown(c);
if (!arg || arg->next || !a || a->kind!=FE_TYPE_OWNED)
err(c,n->loc,"mem.destroy requires exactly one owned pointer");
else mark_moved(s,arg,a);
n->sem_type=fe_type_intern(&c->types,"void");
return n->sem_type;
}
if (strcmp(n->a->b->text,"create")==0 ||
strcmp(n->a->b->text,"alloc_slice")==0)
return check_expr_core(s,n);
}
if (n->a && n->a->kind==FE_N_IDENT) {
sym=find_symbol(s->scope,n->a->text ? n->a->text : "");
if (!sym || !sym->fn) {
err(c,n->loc,"unknown function");
n->sem_type=unknown(c);
return n->sem_type;
}
if (decl_is_generic(sym->fn))
return check_generic_call(s,n,sym,current_unit(c));
n->a->cname=sym->cname;
n->sem_decl=sym->fn;
param=sym->fn->a ? sym->fn->a->children : 0;
arg=n->children;
while (param && arg) {
b=node_type(c,param->a);
a=m7_check_expected(s,arg,b);
if (b && a && b->kind==FE_TYPE_REF && !b->ref_mut &&
a->kind==FE_TYPE_REF && a->ref_mut) {
FeSym *root;
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 &&
a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut) &&
!call_reborrows(b, a))
mark_moved(s,arg,arg->sem_type ? arg->sem_type : a);
if (!fe_type_equal(b,a) && !m7_actual_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;
}
if (param || arg) err(c,n->loc,"wrong number of arguments");
n->sem_type=sym->fn->b ? node_type(c,sym->fn->b) :
fe_type_intern(&c->types,"void");
return n->sem_type;
}
return check_expr_core(s,n);
}
void m7_capture_flow(FeCheckerState *s, FeFlowSlot *slots,
FeOwnState **own, FeFlowBorrow **borrow,
unsigned *count)
{
*count=flow_capture(s->scope,slots,FE_M7_FLOW_CAP);
*own=flow_own_new(s,*count);
*borrow=flow_borrow_new(s,*count);
flow_own_capture(slots,*own,*count);
flow_borrow_capture(slots,*borrow,*count);
}
void m7_restore_flow(FeFlowSlot *slots, FeOwnState *own,
FeFlowBorrow *borrow, unsigned count)
{
flow_restore(slots,count);
flow_own_restore(slots,own,count);
flow_borrow_restore(slots,borrow,count);
}
void m7_merge_rhs_flow(FeCheckerState *s, FeFlowSlot *base,
FeOwnState *own_base,
FeFlowBorrow *borrow_base,
unsigned count, FeFlowSlot *rhs,
FeOwnState *own_rhs,
FeFlowBorrow *borrow_rhs)
{
(void)s;
flow_merge(base,base,rhs,count);
flow_own_merge(base,own_base,own_rhs,count);
flow_borrow_merge(base,borrow_base,borrow_rhs,count);
}
int m7_stmt_definitely_exits(FeNode *n)
{
FeNode *last;
if (!n) return 0;
if (n->kind==FE_N_RETURN || n->kind==FE_N_BREAK ||
n->kind==FE_N_CONTINUE) return 1;
if (n->kind==FE_N_BLOCK) {
last=n->children;
if (!last) return 0;
while (last->next) last=last->next;
return m7_stmt_definitely_exits(last);
}
if (n->kind==FE_N_IF && n->b && n->c)
return m7_stmt_definitely_exits(n->b) &&
m7_stmt_definitely_exits(n->c);
return 0;
}
FeType *m7_check_lazy(FeCheckerState *s, FeNode *n,
FeM7LazyKind kind)
{
FeType *left_type;
FeType *payload;
FeType *right_type;
FeFlowSlot base[FE_M7_FLOW_CAP];
FeFlowSlot rhs[FE_M7_FLOW_CAP];
FeOwnState *own_base;
FeOwnState *own_rhs;
FeFlowBorrow *borrow_base;
FeFlowBorrow *borrow_rhs;
unsigned count;
unsigned rhs_count;
FeScope *old;
FeType *error_type;
left_type=check_expr(s,n->a);
if (kind==FE_M7_LAZY_ORELSE) {
if (!left_type || left_type->kind!=FE_TYPE_OPTIONAL) {
err(s->c,n->loc,"orelse requires an optional left operand");
n->sem_type=unknown(s->c);
return n->sem_type;
}
payload=left_type->elem;
if (!fe_own_is_copy_type(payload)) {
if (m7_place_is_projection(n->a))
err(s->c,n->loc,
"non-Copy optional projection requires mem.replace before orelse");
else
mark_moved(s,n->a,left_type);
}
m7_capture_flow(s,base,&own_base,&borrow_base,&count);
right_type=m7_check_expected(s,n->b,payload);
if (!fe_type_equal(payload,right_type) &&
!m7_actual_compatible(payload,right_type,n->b))
err(s->c,n->b ? n->b->loc : n->loc,"orelse fallback type mismatch");
mark_moved(s,n->b,n->b && n->b->sem_type ? n->b->sem_type : right_type);
rhs_count=flow_capture(s->scope,rhs,FE_M7_FLOW_CAP);
own_rhs=flow_own_new(s,rhs_count);
borrow_rhs=flow_borrow_new(s,rhs_count);
flow_own_capture(rhs,own_rhs,rhs_count);
flow_borrow_capture(rhs,borrow_rhs,rhs_count);
if (rhs_count==count)
m7_merge_rhs_flow(s,base,own_base,borrow_base,count,
rhs,own_rhs,borrow_rhs);
n->sem_type=payload;
return payload;
}
if (!left_type || left_type->kind!=FE_TYPE_ERROR_UNION) {
err(s->c,n->loc,"catch requires an error result");
n->sem_type=unknown(s->c);
return n->sem_type;
}
payload=left_type->error_value;
mark_moved(s,n->a,left_type);
m7_capture_flow(s,base,&own_base,&borrow_base,&count);
if (n->c) {
old=s->scope;
s->scope=scope_new(s,old);
error_type=fe_m7_error_type(&s->c->types,left_type);
if (n->b && n->b->text)
add_symbol(s,s->scope,n->b->text,error_type,0,0,1,
local_cname(s->c,n->b->text),n->b);
check_stmt(s,n->c);
s->scope=old;
if (payload && payload->kind!=FE_TYPE_VOID &&
!m7_stmt_definitely_exits(n->c))
err(s->c,n->loc,
"catch block for a value result must exit instead of falling through");
if (payload && payload->kind==FE_TYPE_VOID) {
rhs_count=flow_capture(s->scope,rhs,FE_M7_FLOW_CAP);
own_rhs=flow_own_new(s,rhs_count);
borrow_rhs=flow_borrow_new(s,rhs_count);
flow_own_capture(rhs,own_rhs,rhs_count);
flow_borrow_capture(rhs,borrow_rhs,rhs_count);
if (rhs_count==count)
m7_merge_rhs_flow(s,base,own_base,borrow_base,count,
rhs,own_rhs,borrow_rhs);
} else {
m7_restore_flow(base,own_base,borrow_base,count);
}
n->sem_type=payload;
return payload;
}
right_type=m7_check_expected(s,n->b,payload);
if (!fe_type_equal(payload,right_type) &&
!m7_actual_compatible(payload,right_type,n->b))
err(s->c,n->b ? n->b->loc : n->loc,"catch fallback type mismatch");
mark_moved(s,n->b,n->b && n->b->sem_type ? n->b->sem_type : right_type);
rhs_count=flow_capture(s->scope,rhs,FE_M7_FLOW_CAP);
own_rhs=flow_own_new(s,rhs_count);
borrow_rhs=flow_borrow_new(s,rhs_count);
flow_own_capture(rhs,own_rhs,rhs_count);
flow_borrow_capture(rhs,borrow_rhs,rhs_count);
if (rhs_count==count)
m7_merge_rhs_flow(s,base,own_base,borrow_base,count,
rhs,own_rhs,borrow_rhs);
n->sem_type=payload;
return payload;
}
static FeType *check_expr_dispatch(FeCheckerState *s, FeNode *n);
/* Every expression goes through here, which is where a projection can tell
the identifier underneath it which field is actually being reached. */
FeType *check_expr(FeCheckerState *s, FeNode *n)
{
const char *save_field=s->proj_field;
FeNode *save_base=s->proj_base;
FeType *t;
if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX))
s->proj_field=own_projected_field(n,&s->proj_base);
t=check_expr_dispatch(s,n);
s->proj_field=save_field;
s->proj_base=save_base;
return t;
}
static FeType *check_expr_dispatch(FeCheckerState *s, FeNode *n)
{
FeType *a;
FeType *b;
FeType *ret_error;
FeType *got_error;
FeM7LazyKind lazy;
const char *op;
if (!n) return unknown(s->c);
if (fe_m7_is_null(n)) {
err(s->c,n->loc,"null requires a contextual optional type");
n->sem_type=unknown(s->c);
return n->sem_type;
}
if (n->kind==FE_N_IDENT)
return check_identifier(s,n);
if (n->kind==FE_N_LITERAL)
return check_expr_core(s,n);
if (n->kind==FE_N_CALL)
return check_call(s,n);
if (n->kind==FE_N_MEMBER) {
int handled;
FeType *cross;
if (is_error_set_member(s,n)) {
n->sem_type=fe_type_intern(&s->c->types,"core.Error");
return n->sem_type;
}
cross=cross_unit_value(s,n,&handled);
if (handled) return cross;
a=check_expr(s,n->a);
return m7_member_field(s,n,a);
}
if (n->kind==FE_N_INDEX)
return check_index(s,n);
if (n->kind==FE_N_UNARY) {
op=n->text ? n->text : "";
if (strcmp(op,"try")==0) {
a=check_expr(s,n->a);
if (!a || a->kind!=FE_TYPE_ERROR_UNION) {
err(s->c,n->loc,"try requires an error result");
n->sem_type=unknown(s->c);
return n->sem_type;
}
if (!s->ret || s->ret->kind!=FE_TYPE_ERROR_UNION) {
err(s->c,n->loc,"try requires an enclosing error result");
} else {
ret_error=fe_m7_error_type(&s->c->types,s->ret);
got_error=fe_m7_error_type(&s->c->types,a);
if (!ret_error || !got_error || !fe_type_equal(ret_error,got_error))
err(s->c,n->loc,
"try error type must exactly match the enclosing error result");
}
mark_moved(s,n->a,a);
n->sem_type=a->error_value;
return n->sem_type;
}
if (strcmp(op,"&")==0 || strcmp(op,"&mut")==0) {
a=check_expr(s,n->a);
own_borrow_expr(s,n->a,strcmp(op,"&mut")==0);
n->sem_type=fe_type_ref(&s->c->types,a,strcmp(op,"&mut")==0);
return n->sem_type;
}
return check_expr_core(s,n);
}
if (n->kind==FE_N_BINARY) {
lazy=fe_m7_lazy_kind(n);
if (lazy!=FE_M7_LAZY_NONE)
return m7_check_lazy(s,n,lazy);
op=n->text ? n->text : "";
if ((strcmp(op,"==")==0 || strcmp(op,"!=")==0) &&
(fe_m7_is_null(n->a) || fe_m7_is_null(n->b))) {
FeNode *nonnull;
FeNode *nullnode;
nonnull=fe_m7_is_null(n->a) ? n->b : n->a;
nullnode=fe_m7_is_null(n->a) ? n->a : n->b;
a=check_expr(s,nonnull);
if (!a || a->kind!=FE_TYPE_OPTIONAL)
err(s->c,n->loc,"null comparison requires an optional value");
else {
nullnode->sem_type=a;
nullnode->sem_context=a;
}
n->sem_type=fe_type_intern(&s->c->types,"bool");
return n->sem_type;
}
a=check_expr(s,n->a);
b=check_expr(s,n->b);
if (strcmp(op,"and")==0 || strcmp(op,"or")==0) {
if ((known(a) && a->kind!=FE_TYPE_BOOL) ||
(known(b) && b->kind!=FE_TYPE_BOOL))
err(s->c,n->loc,"logical operator requires bool operands");
n->sem_type=fe_type_intern(&s->c->types,"bool");
return n->sem_type;
}
if (strcmp(op,"==")==0 || strcmp(op,"!=")==0 ||
strcmp(op,"<")==0 || strcmp(op,"<=")==0 ||
strcmp(op,">")==0 || strcmp(op,">=")==0) {
if (known(a) && known(b) && !fe_type_equal(a,b) &&
!m7_actual_compatible(a,b,n->b) &&
!m7_actual_compatible(b,a,n->a))
err(s->c,n->loc,"comparison operands have different types");
/* Only numbers and characters have an order. */
else if (strcmp(op,"==")!=0 && strcmp(op,"!=")!=0 &&
((known(a) && !ordered_type(a)) ||
(known(b) && !ordered_type(b))))
err(s->c,n->loc,"ordering requires integer or char operands");
n->sem_type=fe_type_intern(&s->c->types,"bool");
return n->sem_type;
}
/* A raw pointer plus a number is an address further along. Only raw
pointers: an owner or a borrow has a place it belongs to, and
walking away from it is what `*T` is for. */
if (known(a) && a->kind==FE_TYPE_RAW && known(b) &&
fe_type_is_integer(b) && op[0] && (op[0]=='+' || op[0]=='-')) {
n->sem_type=a;
return n->sem_type;
}
if ((known(a) && !fe_type_is_integer(a)) ||
(known(b) && !fe_type_is_integer(b)) ||
(known(a) && known(b) && !fe_type_equal(a,b) &&
!m7_actual_compatible(a,b,n->b) &&
!m7_actual_compatible(b,a,n->a)))
err(s->c,n->loc,"arithmetic operands must have the same integer type");
n->sem_type=a;
return a;
}
if (n->kind==FE_N_TYPE && n->text && strcmp(n->text,"as")==0)
return check_expr_core(s,n);
if (n->kind==FE_N_STRUCT_INIT)
return check_struct_init(s,n);
if (n->kind==FE_N_ARRAY_INIT)
return check_array_init(s,n);
return check_expr_core(s,n);
}
static FeType *check_lvalue_dispatch(FeCheckerState *s, FeNode *n, int read);
/* An assignment target is a projection too, so it leaves the same word for the
identifier underneath: `self.room = x` reaches `room` and nothing else. */
FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read)
{
const char *save_field=s->proj_field;
FeNode *save_base=s->proj_base;
FeType *t;
if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX))
s->proj_field=own_projected_field(n,&s->proj_base);
t=check_lvalue_dispatch(s,n,read);
s->proj_field=save_field;
s->proj_base=save_base;
return t;
}
static FeType *check_lvalue_dispatch(FeCheckerState *s, FeNode *n, int read)
{
FeType *base=0;
FeFieldType *field;
FeType *owner;
if (!n) return unknown(s->c);
if (n->kind==FE_N_MEMBER) {
base=check_expr(s,n->a);
if (base && base->kind==FE_TYPE_OPTIONAL) {
err(s->c,n->loc,"optional value must be projected with '.?' first");
return unknown(s->c);
}
if (base && base->kind==FE_TYPE_REF && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
if (!base->ref_mut)
err(s->c,n->loc,"cannot write through shared reference");
n->sem_type=base->elem;
return base->elem;
}
owner=base;
if ((base->kind==FE_TYPE_REF || base->kind==FE_TYPE_OWNED) &&
base->elem && base->elem->kind==FE_TYPE_STRUCT)
owner=base->elem;
if (owner && owner->kind==FE_TYPE_STRUCT && n->b && n->b->text) {
if (base->kind==FE_TYPE_REF && !base->ref_mut)
err(s->c,n->loc,"cannot write through shared reference");
/* Writing a field still needs a writable place. This branch used to
be reached only by units mentioning M7 syntax, so it never had to
repeat the check the M6 path does. */
if (base->kind!=FE_TYPE_REF && base->kind!=FE_TYPE_OWNED &&
!lvalue_writable(s,n->a))
err(s->c,n->loc,"cannot assign through immutable value");
field=fe_type_field(owner,n->b->text);
if (!field) {
err(s->c,n->loc,"assignment requires a valid struct field");
return unknown(s->c);
}
n->sem_type=field->type;
return field->type;
}
}
return check_lvalue_core(s,n,read,n->kind==FE_N_MEMBER ? base : 0);
}
FeType *m7_pattern_binding_type(FeCheckerState *s, FeType *payload,
FeNode *source, int *borrow_mut)
{
FeSym *root;
int mutable;
*borrow_mut=0;
if (fe_own_is_copy_type(payload)) return payload;
root=own_root_symbol(s,source);
mutable=root && root->mutable;
*borrow_mut=mutable;
if (payload->kind==FE_TYPE_OWNED && payload->elem)
return fe_type_ref(&s->c->types,payload->elem,mutable);
return fe_type_ref(&s->c->types,payload,mutable);
}
void m7_check_if_let(FeCheckerState *s, FeNode *n)
{
FeType *opt;
FeType *binding_type;
FeNode *binding;
FeSym *root;
FeScope *old;
FeFlowSlot base[FE_M7_FLOW_CAP];
FeFlowSlot left[FE_M7_FLOW_CAP];
FeFlowSlot right[FE_M7_FLOW_CAP];
FeOwnState *own_base;
FeOwnState *own_left;
FeOwnState *own_right;
FeFlowBorrow *borrow_base;
FeFlowBorrow *borrow_left;
FeFlowBorrow *borrow_right;
unsigned count;
unsigned i;
int borrow_mut;
int is_some;
opt=check_expr(s,n->a);
if (!opt || opt->kind!=FE_TYPE_OPTIONAL) {
err(s->c,n->loc,"if let Some/None requires an optional value");
return;
}
is_some=n->aux_text && strcmp(n->aux_text,"Some")==0;
if (!is_some && (!n->aux_text || strcmp(n->aux_text,"None")!=0))
err(s->c,n->loc,"if let optional pattern must be Some or None");
m7_capture_flow(s,base,&own_base,&borrow_base,&count);
old=s->scope;
s->scope=scope_new(s,old);
root=0;
borrow_mut=0;
binding=n->children;
if (is_some && binding) {
binding_type=m7_pattern_binding_type(s,opt->elem,n->a,&borrow_mut);
add_symbol(s,s->scope,binding->text,binding_type,0,borrow_mut,1,
local_cname(s->c,binding->text),binding);
if (!fe_own_is_copy_type(opt->elem)) {
root=own_root_symbol(s,n->a);
if (root)
fe_own_access(s->c->diags,&root->own,
borrow_mut ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
n->loc);
}
}
check_stmt(s,n->b);
if (root) {
if (borrow_mut) fe_own_release_exclusive(&root->own);
else fe_own_release_shared(&root->own);
}
s->scope=old;
flow_capture(s->scope,left,count);
own_left=flow_own_new(s,count);
borrow_left=flow_borrow_new(s,count);
flow_own_capture(left,own_left,count);
flow_borrow_capture(left,borrow_left,count);
m7_restore_flow(base,own_base,borrow_base,count);
if (n->c) check_stmt(s,n->c);
if (n->c) {
flow_capture(s->scope,right,count);
own_right=flow_own_new(s,count);
borrow_right=flow_borrow_new(s,count);
flow_own_capture(right,own_right,count);
flow_borrow_capture(right,borrow_right,count);
} else {
own_right=flow_own_new(s,count);
borrow_right=flow_borrow_new(s,count);
for (i=0;i<count;++i) right[i]=base[i];
if (own_right && own_base)
for (i=0;i<count;++i) own_right[i]=own_base[i];
if (borrow_right && borrow_base)
for (i=0;i<count;++i) borrow_right[i]=borrow_base[i];
}
flow_merge(base,left,right,count);
flow_own_merge(base,own_left,own_right,count);
flow_borrow_merge(base,borrow_left,borrow_right,count);
}
void m7_check_optional_match(FeCheckerState *s, FeNode *n,
FeType *opt)
{
FeNode *arm;
FeNode *binding;
FeScope *old;
FeType *binding_type;
FeSym *root;
int borrow_mut;
int seen_some;
int seen_none;
int wildcard;
FeFlowSlot base[FE_M7_FLOW_CAP];
FeFlowSlot current[FE_M7_FLOW_CAP];
FeFlowSlot merged[FE_M7_FLOW_CAP];
FeOwnState *own_base;
FeOwnState *own_current;
FeOwnState *own_merged;
FeFlowBorrow *borrow_base;
FeFlowBorrow *borrow_current;
FeFlowBorrow *borrow_merged;
unsigned count;
unsigned i;
int have;
seen_some=0;
seen_none=0;
wildcard=0;
have=0;
m7_capture_flow(s,base,&own_base,&borrow_base,&count);
own_merged=flow_own_new(s,count);
borrow_merged=flow_borrow_new(s,count);
for (arm=n->children;arm;arm=arm->next) {
m7_restore_flow(base,own_base,borrow_base,count);
old=s->scope;
s->scope=scope_new(s,old);
root=0;
borrow_mut=0;
if (arm->text && strcmp(arm->text,"Some")==0) {
if (seen_some) err(s->c,arm->loc,"duplicate Some match arm");
seen_some=1;
binding=arm->children;
if (binding) {
binding_type=m7_pattern_binding_type(s,opt->elem,n->a,&borrow_mut);
add_symbol(s,s->scope,binding->text,binding_type,0,borrow_mut,1,
local_cname(s->c,binding->text),binding);
if (!fe_own_is_copy_type(opt->elem)) {
root=own_root_symbol(s,n->a);
if (root)
fe_own_access(s->c->diags,&root->own,
borrow_mut ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
arm->loc);
}
}
} else if (arm->text && strcmp(arm->text,"None")==0) {
if (seen_none) err(s->c,arm->loc,"duplicate None match arm");
seen_none=1;
} else if (arm->text && strcmp(arm->text,"_")==0) {
wildcard=1;
} else {
err(s->c,arm->loc,"optional match arm must be Some, None, or _");
}
if (arm->a && arm->a->kind==FE_N_BLOCK) check_stmt(s,arm->a);
else if (arm->a) check_expr(s,arm->a);
if (root) {
if (borrow_mut) fe_own_release_exclusive(&root->own);
else fe_own_release_shared(&root->own);
}
s->scope=old;
flow_capture(s->scope,current,count);
own_current=flow_own_new(s,count);
borrow_current=flow_borrow_new(s,count);
flow_own_capture(current,own_current,count);
flow_borrow_capture(current,borrow_current,count);
if (!have) {
for (i=0;i<count;++i) merged[i]=current[i];
if (own_merged && own_current)
for (i=0;i<count;++i) own_merged[i]=own_current[i];
if (borrow_merged && borrow_current)
for (i=0;i<count;++i) borrow_merged[i]=borrow_current[i];
have=1;
} else {
flow_merge(merged,merged,current,count);
if (own_merged && own_current)
for (i=0;i<count;++i)
own_merged[i]=fe_own_merge_state(own_merged[i],own_current[i]);
if (borrow_merged && borrow_current)
for (i=0;i<count;++i) {
if (!borrow_merged[i].root)
borrow_merged[i].root=borrow_current[i].root;
borrow_merged[i].mutable=
borrow_merged[i].mutable || borrow_current[i].mutable;
}
}
}
if (!wildcard && (!seen_some || !seen_none))
err(s->c,n->loc,"non-exhaustive optional match");
if (have) m7_restore_flow(merged,own_merged,borrow_merged,count);
}
void m7_check_match_stmt(FeCheckerState *s, FeNode *n)
{
FeType *value;
value=check_expr(s,n->a);
if (value && value->kind==FE_TYPE_OPTIONAL) {
m7_check_optional_match(s,n,value);
n->sem_type=unknown(s->c);
return;
}
check_match(s,n);
}
void m7_check_decl_stmt(FeCheckerState *s, FeNode *n, int mutable)
{
FeType *expected;
FeType *actual;
FeType *stored;
FeSym *sym;
int initialized;
expected=n->a ? node_type(s->c,n->a) : 0;
if (n->b)
stored=m7_check_expected(s,n->b,expected);
else
stored=expected ? expected : unknown(s->c);
actual=n->b && n->b->sem_type ? n->b->sem_type : stored;
if (!expected) expected=stored;
if (!n->a && n->b && fe_m7_is_null(n->b))
err(s->c,n->loc,"null initializer requires an explicit optional type");
if (expected && expected->kind==FE_TYPE_VOID)
err(s->c,n->loc,"variable cannot have void type");
if (n->b && !fe_type_equal(expected,stored) &&
!m7_actual_compatible(expected,stored,n->b)) {
/* Say which rule was hit. Weakening &mut to & is a distinct thing from
two unrelated types not matching, and "type mismatch" told the reader
nothing about why the exclusive borrow could not be shared. */
if (expected && stored && expected->kind==FE_TYPE_REF &&
stored->kind==FE_TYPE_REF && !expected->ref_mut && stored->ref_mut)
err(s->c,n->loc,
"cannot rebind a mut borrow as a shared reference");
else if (expected && stored && expected->kind==FE_TYPE_SLICE &&
stored->kind==FE_TYPE_SLICE && !expected->ref_mut &&
stored->ref_mut)
err(s->c,n->loc,
"cannot rebind a mut slice as a shared slice");
else
err(s->c,n->loc,"initializer type mismatch");
}
/* Rules the M6 declaration case carried that this one has to repeat now
that it is the only declaration case. */
if (n->b && stored && stored->kind==FE_TYPE_VOID)
err(s->c,n->loc,"void expression cannot initialize a variable");
if (!mutable && expected && expected->kind==FE_TYPE_SLICE &&
expected->ref_mut)
err(s->c,n->loc,"let cannot bind a mutable slice");
if (!n->b && !n->a)
err(s->c,n->loc,"uninitialized var requires an explicit type");
if (n->b) mark_moved(s,n->b,actual);
initialized=n->b!=0;
sym=add_symbol(s,s->scope,n->text,expected,0,mutable,initialized,
local_cname(s->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_field=own_projected_field(n->b->a,0);
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);
}
void check_stmt(FeCheckerState *s, FeNode *n)
{
FeScope *old;
FeNode *x;
FeType *expected;
FeType *actual;
FeType *stored;
FeSym *sym;
if (!n) return;
switch (n->kind) {
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);
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:
case FE_N_CONST:
m7_check_decl_stmt(s,n,0);
break;
case FE_N_VAR:
if (!n->a && !n->b)
err(s->c,n->loc,"uninitialized var requires an explicit type");
m7_check_decl_stmt(s,n,1);
break;
case FE_N_ASSIGN:
expected=check_lvalue(s,n->a,compound_operator(n->text));
stored=m7_check_expected(s,n->b,expected);
actual=n->b && n->b->sem_type ? n->b->sem_type : stored;
if (!fe_type_equal(expected,stored) &&
!m7_actual_compatible(expected,stored,n->b))
err(s->c,n->loc,"assignment type mismatch");
mark_moved(s,n->b,actual);
sym=n->a && n->a->kind==FE_N_IDENT ?
find_symbol(s->scope,n->a->text) : 0;
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;
/* Rebinding a reference, from the M6 assignment case: the new
source has to live at least as long as the reference does, and
the previous borrow has to be released. */
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(s->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);
break;
case FE_N_DEFER:
++s->defer_depth;
check_stmt(s,n->a);
--s->defer_depth;
break;
case FE_N_IF:
if (n->text && strcmp(n->text,"comptime if")==0) {
int taken=0;
if (!comptime_condition(s,n->a,&taken)) {
err(s->c,n->a?n->a->loc:n->loc,
"comptime condition must be decidable at compile time");
break;
}
/* SPEC 9: the branch that is not taken is parsed and nothing more. */
if (taken) check_stmt(s,n->b);
else if (n->c) check_stmt(s,n->c);
break;
}
if (n->text && strcmp(n->text,"if let")==0)
m7_check_if_let(s,n);
else {
FeType *cond;
FeFlowSlot base[FE_M7_FLOW_CAP];
FeFlowSlot left[FE_M7_FLOW_CAP];
FeFlowSlot right[FE_M7_FLOW_CAP];
FeOwnState *own_base;
FeOwnState *own_left;
FeOwnState *own_right;
FeFlowBorrow *borrow_base;
FeFlowBorrow *borrow_left;
FeFlowBorrow *borrow_right;
unsigned count;
unsigned i;
cond=check_expr(s,n->a);
if (known(cond) && cond->kind!=FE_TYPE_BOOL)
err(s->c,n->loc,"if condition must be bool");
/* Once a function is on the M7 checker path, branch bodies must
remain on that path as well. In particular, return T inside
E!T relies on contextual success construction even when the
branch itself contains no surface M7 syntax. */
m7_capture_flow(s,base,&own_base,&borrow_base,&count);
check_stmt(s,n->b);
flow_capture(s->scope,left,count);
own_left=flow_own_new(s,count);
borrow_left=flow_borrow_new(s,count);
flow_own_capture(left,own_left,count);
flow_borrow_capture(left,borrow_left,count);
/* A branch that always leaves contributes nothing to what follows.
Merging its state would make a value it consumed look consumed
afterwards, on a path that never ran it. */
if (m7_stmt_definitely_exits(n->b)) {
for (i=0;i<count;++i) left[i]=base[i];
if (own_left && own_base)
for (i=0;i<count;++i) own_left[i]=own_base[i];
if (borrow_left && borrow_base)
for (i=0;i<count;++i) borrow_left[i]=borrow_base[i];
}
m7_restore_flow(base,own_base,borrow_base,count);
if (n->c) check_stmt(s,n->c);
if (n->c) {
flow_capture(s->scope,right,count);
own_right=flow_own_new(s,count);
borrow_right=flow_borrow_new(s,count);
flow_own_capture(right,own_right,count);
flow_borrow_capture(right,borrow_right,count);
if (m7_stmt_definitely_exits(n->c)) {
for (i=0;i<count;++i) right[i]=base[i];
if (own_right && own_base)
for (i=0;i<count;++i) own_right[i]=own_base[i];
if (borrow_right && borrow_base)
for (i=0;i<count;++i) borrow_right[i]=borrow_base[i];
}
} else {
own_right=flow_own_new(s,count);
borrow_right=flow_borrow_new(s,count);
for (i=0;i<count;++i) right[i]=base[i];
if (own_right && own_base)
for (i=0;i<count;++i) own_right[i]=own_base[i];
if (borrow_right && borrow_base)
for (i=0;i<count;++i) borrow_right[i]=borrow_base[i];
}
flow_merge(base,left,right,count);
flow_own_merge(base,own_left,own_right,count);
flow_borrow_merge(base,borrow_left,borrow_right,count);
}
break;
case FE_N_MATCH:
m7_check_match_stmt(s,n);
break;
case FE_N_RETURN:
/* A deferred block runs during scope cleanup, on the way out of a
function that has already decided what it returns. There is nothing
for a `return` in there to mean. */
if (s->defer_depth != 0)
err(s->c, n->loc, "cannot return from inside defer");
expected=s->ret;
if (n->a)
stored=m7_check_expected(s,n->a,expected);
else
stored=fe_type_intern(&s->c->types,"void");
actual=n->a && n->a->sem_type ? n->a->sem_type : stored;
/* R8, from the M6 return case: a returned reference has to come from a
parameter or a static, never from a local. */
if (expected && fe_own_is_reference_like(expected) &&
!own_return_from_allowed_root(s,n->a))
err(s->c,n->loc,
"reference return must be derived from a parameter or static");
if (n->a && stored && stored->kind==FE_TYPE_VOID &&
expected && expected->kind!=FE_TYPE_VOID)
err(s->c,n->loc,"void expression returned from value function");
if (expected && expected->kind==FE_TYPE_ERROR_UNION && n->a &&
actual && actual->kind==FE_TYPE_ERROR_UNION &&
!fe_type_equal(expected,actual))
err(s->c,n->loc,"error result type mismatch");
/* A bare `return` in a function returning `!void` is the success case:
there is no value to give, and no error either. */
else if (!n->a && expected && expected->kind==FE_TYPE_ERROR_UNION &&
expected->error_value &&
expected->error_value->kind==FE_TYPE_VOID) { }
else if (!fe_type_equal(expected,stored) &&
!m7_actual_compatible(expected,stored,n->a))
err(s->c,n->loc,"return type mismatch");
if (n->a) mark_moved(s,n->a,actual);
break;
case FE_N_WHILE:
case FE_N_FOR:
/* The core loop case carries the flow capture and merge that detects a
value moved on every iteration, and it already recurses into the body
through this function, so there is nothing to special-case here. The
M7 half used to skip all of it. */
check_stmt_core(s,n);
break;
case FE_N_BREAK:
case FE_N_CONTINUE:
if (!s->loop_depth)
err(s->c,n->loc,"break or continue outside loop");
break;
case FE_N_UNSAFE:
check_stmt(s,n->a);
break;
default:
check_stmt_core(s,n);
break;
}
}