From 390345ef85f26b96307b42aa213dbdfb76310295 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Mon, 17 Aug 2026 07:04:35 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20check.c=20=EC=99=80=20lower.c=20?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EB=9E=8C=EC=9D=B4=20=EB=A8=B8=EB=A6=AC?= =?UTF-8?q?=EC=97=90=20=EB=8B=B4=EC=9D=84=20=ED=81=AC=EA=B8=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=82=98=EB=88=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check.c 3,937 줄, lower.c 1,913 줄이었다. 가장 큰 파일이 978 줄이 됐다. check.c 679 스코프·심볼·흐름·소유권 접착 checkexp.c 739 포매팅 검사와 표현식 checkstm.c 635 문장, 함수, 메서드 checkgen.c 618 제네릭 실체화 checkcal.c 978 유닛 경계 호출과 옵셔널/에러 유니온 checkpro.c 184 선언 패스와 프로그램 lower.c 567 타입·슬롯·지역·블록·mem.* lowerprn.c 238 포매팅 빌트인 전개 lowerexp.c 468 표현식 lowerstm.c 543 문장·함수·프로그램 줄 범위로 잘랐다. 주제별로 묶는 것보다 정확한데, 한 줄도 잃거나 겹치지 않기 때문이다. 파일 순서가 이미 단계를 따라가서 경계가 실제 이음매에 떨어진다. 모든 정의가 static 을 잃고 비공개 헤더에 프로토타입을 갖는다. 대안 -- static 을 유지하고 #include 로 텍스트만 나누는 것 -- 은 결합을 보여주는 대신 숨긴다. 두 스위트 그대로: 209/209, 21/21. --- fec/rt/start.asm | 90 +- fec/src/check.c | 3354 +------------------------------------------- fec/src/checkcal.c | 978 +++++++++++++ fec/src/checkexp.c | 739 ++++++++++ fec/src/checkgen.c | 618 ++++++++ fec/src/checkpri.h | 252 ++++ fec/src/checkpro.c | 182 +++ fec/src/checkstm.c | 635 +++++++++ fec/src/lower.c | 1271 +---------------- fec/src/lowerexp.c | 468 +++++++ fec/src/lowerpri.h | 153 ++ fec/src/lowerprn.c | 236 ++++ fec/src/lowerstm.c | 543 +++++++ tests/run.py | 3 +- 14 files changed, 4982 insertions(+), 4540 deletions(-) create mode 100644 fec/src/checkcal.c create mode 100644 fec/src/checkexp.c create mode 100644 fec/src/checkgen.c create mode 100644 fec/src/checkpri.h create mode 100644 fec/src/checkpro.c create mode 100644 fec/src/checkstm.c create mode 100644 fec/src/lowerexp.c create mode 100644 fec/src/lowerpri.h create mode 100644 fec/src/lowerprn.c create mode 100644 fec/src/lowerstm.c diff --git a/fec/rt/start.asm b/fec/rt/start.asm index 45ead57..c2726e6 100644 --- a/fec/rt/start.asm +++ b/fec/rt/start.asm @@ -26,7 +26,7 @@ prefix db 'ferro: ',0 at_word db ' at ',0 colon db ':',0 newline db 13,10,0 -numbuf db 16 dup(0) +numbuf db 24 dup(0) written dd 0 allocs dd 0 frees dd 0 @@ -210,6 +210,94 @@ fe_rt_frees proc near ret fe_rt_frees endp +; fe_rt_write_int(handle, value, is_unsigned) -- decimal, with a sign when +; the value is negative and signed was asked for. +public fe_rt_write_int +fe_rt_write_int proc near + push ebp + mov ebp, esp + push ebx + push esi + push edi + mov edi, offset numbuf + 15 + mov byte ptr [edi], 0 + mov eax, [ebp+12] + xor ebx, ebx ; ebx = 1 when a '-' is needed + cmp dword ptr [ebp+16], 0 + jne int_digits + test eax, eax + jge int_digits + neg eax + mov ebx, 1 +int_digits: + mov ecx, 10 +int_loop: + xor edx, edx + div ecx + add dl, '0' + dec edi + mov [edi], dl + test eax, eax + jnz int_loop + test ebx, ebx + je int_write + dec edi + mov byte ptr [edi], '-' +int_write: + mov esi, offset numbuf + 15 + sub esi, edi + push esi + push edi + push dword ptr [ebp+8] + call fe_rt_write + add esp, 12 + pop edi + pop esi + pop ebx + mov esp, ebp + pop ebp + ret +fe_rt_write_int endp + +; fe_rt_write_hex(handle, value) +public fe_rt_write_hex +fe_rt_write_hex proc near + push ebp + mov ebp, esp + push ebx + push esi + push edi + mov edi, offset numbuf + 15 + mov byte ptr [edi], 0 + mov eax, [ebp+12] +hex_loop: + mov edx, eax + and edx, 15 + cmp dl, 10 + jb hex_digit + add dl, 'a' - 10 - '0' +hex_digit: + add dl, '0' + dec edi + mov [edi], dl + shr eax, 4 + test eax, eax + jnz hex_loop + mov esi, offset numbuf + 15 + sub esi, edi + push esi + push edi + push dword ptr [ebp+8] + call fe_rt_write + add esp, 12 + pop edi + pop esi + pop ebx + mov esp, ebp + pop ebp + ret +fe_rt_write_hex endp + ; fe_rt_exit(code) -- never returns public fe_rt_exit fe_rt_exit proc near diff --git a/fec/src/check.c b/fec/src/check.c index 6f238da..ea1a250 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -1,77 +1,28 @@ -#include "check.h" -#include "m7.h" -#include +#include "checkpri.h" -#define FE_M7_FLOW_CAP 64U -#include "own.h" -#include -#include - -typedef struct FeSym FeSym; -/* FeScope is forward declared in check.h. */ - -struct FeSym { - const char *name; - char *cname; - FeType *type; - FeNode *fn; - int mutable; - 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 { - FeScope *parent; - FeSym *items; - unsigned count; - unsigned capacity; -}; - -static FeSym *find_symbol(FeScope *scope, const char *name); - -typedef struct FeCheckerState { - FeCheck *c; - FeScope *scope; - FeScope *globals; - FeType *ret; - unsigned loop_depth; - unsigned defer_depth; - FeOwnLiveness liveness; - FeNode *fn_node; -} FeCheckerState; - -static FeType *unknown(FeCheck *c) +FeType *unknown(FeCheck *c) { return fe_type_intern(&c->types, ""); } -static void err(FeCheck *c, FeLoc loc, const char *msg) +void err(FeCheck *c, FeLoc loc, const char *msg) { fe_diag_error(c->diags, loc, msg); } /* Only numbers and characters have an order (SPEC 6.2). */ -static int ordered_type(const FeType *t) +int ordered_type(const FeType *t) { return t && (t->kind==FE_TYPE_INT || t->kind==FE_TYPE_CHAR); } -static int known(FeType *t) +int known(FeType *t) { return t && t->kind != FE_TYPE_UNKNOWN && t->kind != FE_TYPE_ERROR; } /* Is this a projection of `self` inside that type's own `drop`? */ -static int in_own_drop(FeCheckerState *s, FeNode *n) +int in_own_drop(FeCheckerState *s, FeNode *n) { FeNode *base; if (!s->fn_node || !s->fn_node->text || strcmp(s->fn_node->text,"drop")!=0) @@ -83,7 +34,7 @@ static int in_own_drop(FeCheckerState *s, FeNode *n) strcmp(base->text,"self")==0; } -static void mark_moved(FeCheckerState *s, FeNode *n, FeType *t) +void mark_moved(FeCheckerState *s, FeNode *n, FeType *t) { FeSym *sym=0; /* Inside a type's own `drop` the object is going away, so taking a field @@ -128,7 +79,7 @@ static void mark_moved(FeCheckerState *s, FeNode *n, FeType *t) n,t,s->defer_depth != 0); } -static int compatible(FeType *want, FeType *got, FeNode *value) +int compatible(FeType *want, FeType *got, FeNode *value) { FeNode *item; unsigned long count; @@ -159,7 +110,7 @@ static int compatible(FeType *want, FeType *got, FeNode *value) /* Does passing `arg` to a parameter of type `param` lend it rather than give it away? An exclusive borrow handed to a call comes back when the call returns, so it is not a move. */ -static int call_reborrows(const FeType *param, const FeType *arg) +int call_reborrows(const FeType *param, const FeType *arg) { if (!param || !arg) return 0; if (param->kind==FE_TYPE_REF && arg->kind==FE_TYPE_REF && @@ -169,7 +120,7 @@ static int call_reborrows(const FeType *param, const FeType *arg) return 0; } -static int explicit_castable(FeType *a, FeType *b) +int explicit_castable(FeType *a, FeType *b) { if (!a || !b) return 0; /* An enum without a payload is a number with names on it, so reading it @@ -181,7 +132,7 @@ static int explicit_castable(FeType *a, FeType *b) (fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR); } -static FeType *node_type(FeCheck *c, FeNode *n) +FeType *node_type(FeCheck *c, FeNode *n) { FeType *t; if (!n) return unknown(c); @@ -193,7 +144,7 @@ static FeType *node_type(FeCheck *c, FeNode *n) /* A link-visible name. A unit path has dots in it and a generic instance has brackets and commas, none of which an assembler will accept, so everything outside the portable identifier set becomes an underscore. */ -static char *unit_cname(FeCheck *c, const char *name) +char *unit_cname(FeCheck *c, const char *name) { char *u; char *p; @@ -217,7 +168,7 @@ static char *unit_cname(FeCheck *c, const char *name) return p; } -static char *local_cname(FeCheck *c, const char *name) +char *local_cname(FeCheck *c, const char *name) { char number[24]; char *p; @@ -234,7 +185,7 @@ static char *local_cname(FeCheck *c, const char *name) return p; } -static FeScope *scope_new(FeCheckerState *s, FeScope *parent) +FeScope *scope_new(FeCheckerState *s, FeScope *parent) { FeScope *scope; scope = (FeScope *)fe_arena_alloc(&s->c->arena, sizeof(FeScope)); @@ -249,7 +200,7 @@ static FeScope *scope_new(FeCheckerState *s, FeScope *parent) return scope; } -static FeSym *find_current(FeScope *scope, const char *name) +FeSym *find_current(FeScope *scope, const char *name) { unsigned i; if (!scope) return 0; @@ -259,7 +210,7 @@ static FeSym *find_current(FeScope *scope, const char *name) return 0; } -static FeSym *find_symbol(FeScope *scope, const char *name) +FeSym *find_symbol(FeScope *scope, const char *name) { FeSym *sym; while (scope) { @@ -270,7 +221,7 @@ static FeSym *find_symbol(FeScope *scope, const char *name) return 0; } -static FeSym *add_symbol(FeCheckerState *s, FeScope *scope, +FeSym *add_symbol(FeCheckerState *s, FeScope *scope, const char *name, FeType *type, FeNode *fn, int mutable, int initialized, char *cname, FeNode *decl) @@ -322,7 +273,7 @@ static FeSym *add_symbol(FeCheckerState *s, FeScope *scope, /* Make `unit` the one being checked. Types intern against its name, cnames are built from it, and diagnostics quote its source rather than whichever file happened to be parsed last. */ -static void enter_unit(FeCheck *c, unsigned index) +void enter_unit(FeCheck *c, unsigned index) { FeUnit *u = &c->build->units[index]; c->unit = u; @@ -331,13 +282,7 @@ static void enter_unit(FeCheck *c, unsigned index) fe_diags_source(c->diags, u->source, u->size); } -/* The type bindings in force, saved across a nested instantiation. */ -typedef struct FeBindSave { - FeTypeBind params[FE_TYPE_PARAM_MAX]; - unsigned count; -} FeBindSave; -static FeType *instantiate_type_node(void *owner, const FeNode *node); void fe_check_init(FeCheck *c, FeBuild *build, FeDiags *diags, unsigned pointer_bits, int no_checks) @@ -367,7 +312,7 @@ void fe_check_destroy(FeCheck *c) fe_arena_destroy(&c->arena); } -static unsigned unit_index(FeCheck *c, const FeUnit *u) +unsigned unit_index(FeCheck *c, const FeUnit *u) { return (unsigned)(u - c->build->units); } @@ -376,7 +321,7 @@ static unsigned unit_index(FeCheck *c, const FeUnit *u) unit it names. A local of the same spelling wins -- shadowing a binding is legal and means the local -- so this only answers when the base name is not otherwise in scope. */ -static FeUnit *binding_unit(FeCheckerState *s, FeNode *base) +FeUnit *binding_unit(FeCheckerState *s, FeNode *base) { if (!base || base->kind!=FE_N_IDENT || !base->text) return 0; if (!s->c->build || !s->c->unit) return 0; @@ -385,12 +330,12 @@ static FeUnit *binding_unit(FeCheckerState *s, FeNode *base) } /* SPEC 8.2: a declaration is visible outside its unit only with `pub`. */ -static int decl_is_public(const FeNode *decl) +int decl_is_public(const FeNode *decl) { return decl && (decl->flags & FE_NODE_PUB)!=0; } -static FeSym *unit_member(FeCheck *c, FeUnit *u, const char *name) +FeSym *unit_member(FeCheck *c, FeUnit *u, const char *name) { if (!u || !name) return 0; return find_current(c->unit_scope[unit_index(c,u)],name); @@ -399,7 +344,7 @@ static FeSym *unit_member(FeCheck *c, FeUnit *u, const char *name) /* A type another unit declares, or null if it declares no such type. Interning is keyed on the declaring unit, so this cannot collide with a same-named type here. */ -static FeType *unit_type(FeCheck *c, FeUnit *u, const char *name) +FeType *unit_type(FeCheck *c, FeUnit *u, const char *name) { FeType *t; if (!u || !name) return 0; @@ -411,7 +356,7 @@ static FeType *unit_type(FeCheck *c, FeUnit *u, const char *name) /* The AST declaration of a type another unit declares, for its visibility and for its methods. */ -static FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name) +FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name) { FeNode *n; (void)c; @@ -425,7 +370,7 @@ static FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name) /* Resolve a type written in another unit's source. Names in a signature mean what they meant where the signature was written, not where it is called. */ -static FeType *node_type_in(FeCheck *c, const char *unit, FeNode *node) +FeType *node_type_in(FeCheck *c, const char *unit, FeNode *node) { const char *save=c->types.unit_name; FeType *t; @@ -435,9 +380,8 @@ static FeType *node_type_in(FeCheck *c, const char *unit, FeNode *node) return t; } -static FeType *check_expr(FeCheckerState *s, FeNode *n); -static FeNode *find_method(FeCheck *c, FeType *owner, const char *name) +FeNode *find_method(FeCheck *c, FeType *owner, const char *name) { FeNode *decl; FeNode *method; @@ -457,7 +401,7 @@ static FeNode *find_method(FeCheck *c, FeType *owner, const char *name) return 0; } -static FeType *method_type(FeCheck *c, FeNode *node, FeType *owner) +FeType *method_type(FeCheck *c, FeNode *node, FeType *owner) { if(node && node->kind==FE_N_TYPE && node->text && strcmp(node->text,"Self")==0) return owner; @@ -467,57 +411,9 @@ static FeType *method_type(FeCheck *c, FeNode *node, FeType *owner) return fe_type_ref(&c->types,owner,strcmp(node->text,"&mut")==0); return node_type(c,node); } -static void check_match(FeCheckerState *s, FeNode *n); -static void check_stmt(FeCheckerState *s, FeNode *n); -static FeType *check_expr_core(FeCheckerState *s, FeNode *n); -static void check_stmt_core(FeCheckerState *s, FeNode *n); -static FeType *check_lvalue_core(FeCheckerState *s, FeNode *n, int read, - FeType *base_in); -static FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read); -static FeType *check_call(FeCheckerState *s, FeNode *n); -static FeType *check_call_args(FeCheckerState *s, FeNode *n, FeSym *sym, - const char *home, unsigned skip); -static int is_error_set_member(FeCheckerState *s, FeNode *n); -static void check_fn(FeCheck *c, FeNode *n, FeScope *globals); -static void check_method(FeCheck *c, FeNode *n, FeScope *globals, FeType *owner); -static char *unit_cname(FeCheck *c, const char *name); -static unsigned unit_index(FeCheck *c, const FeUnit *u); -static FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name); -static FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym, - FeUnit *home); -static FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok); -static FeUnit *current_unit(FeCheck *c); -static int decl_is_generic(const FeNode *decl); -static void check_generic_params(FeCheck *c, FeNode *decl); -static int comptime_condition(FeCheckerState *s, FeNode *n, int *out); -static FeType *check_static_method_call(FeCheckerState *s, FeNode *n, - FeType *owner, FeNode *method); -static FeNode *type_method(FeType *t, const char *name); -static int method_is_static(const FeNode *method); -static int const_names_type(FeCheckerState *s, FeNode *n); -static void push_instance_bindings(FeCheck *c, FeBindSave *save, FeType *t); -static void pop_bindings(FeCheck *c, const FeBindSave *save); -static void bind_self(FeCheck *c, FeType *owner); -static void instance_key(char *out, const char *unit, const char *name, - FeType **args, unsigned count); -static int instance_record(FeCheck *c, const char *key, FeLoc loc, - FeNode *decl, FeUnit *home, FeType *owner); -static const char *instance_cname(FeCheck *c, const char *key); -static int instance_descend(FeCheck *c, FeLoc loc); -static void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl, - FeType *owner, FeBindSave *bindings, FeLoc site); -static void check_instance_method(FeCheckerState *s, FeType *owner, - FeNode *method, FeLoc site, FeNode *call); -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) +unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap) { unsigned count=0; unsigned i; @@ -534,7 +430,7 @@ static unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap) return count; } -static void flow_restore(FeFlowSlot *slots, unsigned count) +void flow_restore(FeFlowSlot *slots, unsigned count) { unsigned i; for (i=0; iscope,place.root->text ? place.root->text : ""); } -static int own_is_global(FeCheckerState *s, FeSym *sym) +int own_is_global(FeCheckerState *s, FeSym *sym) { FeScope *p; if (!s || !sym) return 0; @@ -575,7 +471,7 @@ static int own_is_global(FeCheckerState *s, FeSym *sym) return 0; } -static void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable) +void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable) { FeSym *root=own_root_symbol(s,expr); if (!root) return; @@ -595,7 +491,7 @@ static void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable) expr->loc); } -static void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr) +void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr) { FeSym *root; if (!expr || expr->kind!=FE_N_UNARY || !expr->text) return; @@ -608,7 +504,7 @@ static void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr) /* 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) +FeSym *own_derived_call_root(FeCheckerState *s, FeNode *call) { FeNode *param; FeNode *arg; @@ -633,7 +529,7 @@ static FeSym *own_derived_call_root(FeCheckerState *s, FeNode *call) return refs==1 ? own_root_symbol(s,source) : 0; } -static void own_bind_derived_call(FeCheckerState *s, FeSym *binding, +void own_bind_derived_call(FeCheckerState *s, FeSym *binding, FeNode *value) { FeSym *root; @@ -649,7 +545,7 @@ static void own_bind_derived_call(FeCheckerState *s, FeSym *binding, 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) +int own_stmt_uses(FeNode *node, const char *name) { FeNode *x; if (!node || !name) return 0; @@ -662,7 +558,7 @@ static int own_stmt_uses(FeNode *node, const char *name) return 0; } -static int own_defer_uses(FeNode *node, const char *name) +int own_defer_uses(FeNode *node, const char *name) { FeNode *x; if (!node) return 0; @@ -674,7 +570,7 @@ static int own_defer_uses(FeNode *node, const char *name) return 0; } -static int own_contains_node(FeNode *node, FeNode *needle) +int own_contains_node(FeNode *node, FeNode *needle) { FeNode *x; if (!node || !needle) return 0; @@ -687,7 +583,7 @@ static int own_contains_node(FeNode *node, FeNode *needle) return 0; } -static void own_release_after_stmt(FeCheckerState *s, FeScope *scope, +void own_release_after_stmt(FeCheckerState *s, FeScope *scope, FeNode *stmt, int scope_end) { unsigned i; @@ -709,14 +605,14 @@ static void own_release_after_stmt(FeCheckerState *s, FeScope *scope, /* 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) +FeOwnState *flow_own_new(FeCheckerState *s, unsigned count) { if (!s || !count) return 0; return (FeOwnState *)fe_arena_alloc(&s->c->arena, count*sizeof(FeOwnState)); } -static void flow_own_capture(FeFlowSlot *slots, FeOwnState *states, +void flow_own_capture(FeFlowSlot *slots, FeOwnState *states, unsigned count) { unsigned i; @@ -724,7 +620,7 @@ static void flow_own_capture(FeFlowSlot *slots, FeOwnState *states, for (i=0;iown; } -static void flow_own_restore(FeFlowSlot *slots, FeOwnState *states, +void flow_own_restore(FeFlowSlot *slots, FeOwnState *states, unsigned count) { unsigned i; @@ -732,7 +628,7 @@ static void flow_own_restore(FeFlowSlot *slots, FeOwnState *states, for (i=0;iown=states[i]; } -static void flow_own_merge(FeFlowSlot *slots, FeOwnState *left, +void flow_own_merge(FeFlowSlot *slots, FeOwnState *left, FeOwnState *right, unsigned count) { unsigned i; @@ -741,19 +637,15 @@ static void flow_own_merge(FeFlowSlot *slots, FeOwnState *left, slots[i].sym->own=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) +FeFlowBorrow *flow_borrow_new(FeCheckerState *s, unsigned count) { if (!s || !count) return 0; return (FeFlowBorrow *)fe_arena_alloc(&s->c->arena, count*sizeof(FeFlowBorrow)); } -static void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states, +void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states, unsigned count) { unsigned i; @@ -764,7 +656,7 @@ static void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states, } } -static void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states, +void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states, unsigned count) { unsigned i; @@ -775,7 +667,7 @@ static void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states, } } -static void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left, +void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left, FeFlowBorrow *right, unsigned count) { unsigned i; @@ -785,3153 +677,3 @@ static void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left, slots[i].sym->borrow_mut=left[i].mutable || right[i].mutable; } } - -static FeNode *find_const_node(FeCheck *c, const char *name) -{ - FeNode *n; - for (n=c->ast->root ? c->ast->root->children : 0; n; n=n->next) - if (n->kind==FE_N_CONST && n->text && name && strcmp(n->text,name)==0) - return n; - return 0; -} - -static int format_is_slice_u8(FeType *t); - -static const char *builtin_format(FeCheckerState *s, FeNode *fmt) -{ - FeNode *decl; - FeSym *sym; - if (fmt && fmt->kind==FE_N_LITERAL && fmt->text && fmt->text[0]=='"') - return fmt->text; - if (fmt && fmt->kind==FE_N_IDENT) { - sym=find_symbol(s->scope,fmt->text); - decl=sym && sym->decl && sym->decl->kind==FE_N_CONST ? - sym->decl : find_const_node(s->c,fmt->text); - if (decl && decl->b && decl->b->kind==FE_N_LITERAL && - decl->b->text && decl->b->text[0]=='"') { - if (!decl->a || format_is_slice_u8(fe_type_from_ast(&s->c->types,decl->a))) - return decl->b->text; - } - } - return 0; -} - -static int format_is_slice_u8(FeType *t) -{ - return t && t->kind==FE_TYPE_SLICE && t->elem && - t->elem->kind==FE_TYPE_INT && strcmp(t->elem->name,"u8")==0; -} - -static int format_is_writer_type(FeType *t) -{ - return t && t->kind==FE_TYPE_STRUCT && - (strcmp(t->name,"Writer")==0 || strcmp(t->name,"io.Writer")==0); -} - -static int format_arg_ok(FeType *t, int verb) -{ - if (!t) return 0; - if (verb=='x') return fe_type_is_integer(t); - if (verb=='c') return t->kind==FE_TYPE_CHAR; - if (verb=='s') return format_is_slice_u8(t); - if (verb=='b') return t->kind==FE_TYPE_BOOL; - if (t->kind==FE_TYPE_INT || t->kind==FE_TYPE_BOOL || - t->kind==FE_TYPE_CHAR) return 1; - return format_is_slice_u8(t) || - (t->kind==FE_TYPE_ENUM && t->is_error); -} - -static void check_format_call(FeCheckerState *s, FeNode *n) -{ - const char *fmt; - FeNode *fmt_node; - FeNode *arg; - FeNode *x; - FeType *t; - unsigned long i,j; - unsigned count=0; - unsigned argc=0; - unsigned offset=0; - int verb; - int bad=0; - int counted=0; - if (strcmp(n->text,"@fprint")==0) offset=1; - fmt_node=n->children; - if (offset) { - if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; } - t=check_expr(s,fmt_node); - if (!format_is_writer_type(t)) - err(s->c,fmt_node->loc,"@fprint requires io.Writer"); - fmt_node=fmt_node->next; - } - if (strcmp(n->text,"@sprint")==0) { - if (!fmt_node) { err(s->c,n->loc,"@sprint requires a buffer"); return; } - t=check_expr(s,fmt_node); - if (!format_is_slice_u8(t) || !t->ref_mut) - err(s->c,fmt_node->loc,"@sprint requires []mut u8 buffer"); - fmt_node=fmt_node->next; - } - fmt=builtin_format(s,fmt_node); - if (!fmt) { err(s->c,n->loc,"format must be a comptime string"); return; } - n->aux_text=(char *)fmt; - arg=fmt_node ? fmt_node->next : 0; - for (x=arg;x;x=x->next) { check_expr(s,x); ++argc; } - i=1; - while (fmt[i] && fmt[i]!='"') { - if (fmt[i]=='\\') { if (fmt[i+1]) ++i; ++i; continue; } - if (fmt[i]=='{' && fmt[i+1]=='{') { i+=2; continue; } - if (fmt[i]=='}' && fmt[i+1]=='}') { i+=2; continue; } - if (fmt[i]=='{') { - j=i+1; - while (fmt[j] && fmt[j]!='}') ++j; - if (!fmt[j]) { err(s->c,n->loc,"unterminated format placeholder"); bad=1; break; } - if (j==i+1) verb=' '; else if (j==i+2) verb=(unsigned char)fmt[i+1]; else verb='?'; - if (verb!=' ' && verb!='x' && verb!='c' && verb!='s' && verb!='b') { - err(s->c,n->loc,"unsupported format verb"); bad=1; - } - if (!arg) { - err(s->c,n->loc,"format argument count mismatch"); - bad=1; counted=1; - } - else { - t=arg->sem_type; - if (verb==' ' && t && t->kind==FE_TYPE_ENUM && t->is_error) verb='s'; - if (!format_arg_ok(t,verb)) { err(s->c,arg->loc,"no fmt writer for argument type"); bad=1; } - arg=arg->next; - } - ++count; i=j+1; continue; - } - if (fmt[i]=='}') { err(s->c,n->loc,"unmatched '}' in format"); bad=1; } - ++i; - } - /* Running out of arguments mid-string already said this. Saying it again - once the whole string has been walked adds nothing. */ - if (count!=argc && !counted) { err(s->c,n->loc,"format argument count mismatch"); bad=1; } - (void)bad; -} - -static int is_format_builtin(const char *name) -{ - return name && (strcmp(name,"@print")==0 || strcmp(name,"@fprint")==0 || - strcmp(name,"@sprint")==0); -} - -static int lvalue_writable(FeCheckerState *s, FeNode *n) -{ - FeSym *sym; - FeType *t; - if (!n) return 0; - if (n->kind == FE_N_IDENT) { - sym=find_symbol(s->scope,n->text ? n->text : ""); - return sym ? sym->mutable : 0; - } - if (n->kind == FE_N_MEMBER) { - t=n->a ? n->a->sem_type : 0; - if (t && t->kind==FE_TYPE_REF && n->b && n->b->text && - strcmp(n->b->text,"^")==0) return t->ref_mut; - return lvalue_writable(s,n->a); - } - if (n->kind == FE_N_INDEX) return lvalue_writable(s,n->a); - return 0; -} - -static int has_field(FeNode *list, const char *name) -{ - FeNode *f; - for (f=list; f; f=f->next) - if (f->text && name && strcmp(f->text,name)==0) return 1; - return 0; -} - -/* A field of a type declared elsewhere is reachable only with `pub`. Inside - the declaring unit every field is reachable, `pub` or not. */ -static int field_is_visible(FeCheckerState *s, const FeType *t, - const FeFieldType *field) -{ - if (!t || !t->unit) return 1; - if (s->c->types.unit_name && - strcmp(t->unit,s->c->types.unit_name)==0) return 1; - return field && field->ast_node && - (field->ast_node->flags & FE_NODE_PUB)!=0; -} - -/* The field list of a struct literal, once the type is known. Reached from - both `Type{...}` and `binding.Type{...}`. */ -static FeType *check_struct_fields(FeCheckerState *s, FeNode *n, FeType *t) -{ - FeFieldType *field; - FeNode *f; - FeType *v; - unsigned i; - for(f=n->children;f;f=f->next) if(f->kind==FE_N_FIELD) { - if(has_field(f->next,f->text)) { err(s->c,f->loc,"duplicate struct field"); } - field=fe_type_field(t,f->text); - if(!field) { err(s->c,f->loc,"invalid struct field"); continue; } - if(!field_is_visible(s,t,field)) { - err(s->c,f->loc,"field is private to its unit"); - continue; - } - v=check_expr(s,f->a); - mark_moved(s,f->a,v); - if(!compatible(field->type,v,f->a) && v->kind!=FE_TYPE_UNKNOWN) err(s->c,f->loc,"struct field type mismatch"); - } - for(i=0;ifield_count;i++) if(!has_field(n->children,t->fields[i].name)) err(s->c,n->loc,"missing struct field"); - n->sem_type=t; return t; -} - -static FeType *check_struct_init(FeCheckerState *s, FeNode *n) -{ - FeType *t; - FeFieldType *field; - FeNode *f; - FeType *v; - FeType *et; - FeVariantType *variant; - if (n->a && n->a->kind == FE_N_MEMBER) { - FeUnit *home=binding_unit(s,n->a->a); - if (home) { - /* `binding.Type{...}` names a type in another unit. */ - const char *want=n->a->b && n->a->b->text ? n->a->b->text : ""; - FeNode *decl=unit_type_decl(s->c,home,want); - t=unit_type(s->c,home,want); - if (!t || !decl) { err(s->c,n->a->loc,"unknown name"); return unknown(s->c); } - if (!decl_is_public(decl)) { - err(s->c,n->a->loc,"type is private to its unit"); - return unknown(s->c); - } - if (t->kind!=FE_TYPE_STRUCT) { - err(s->c,n->loc,"unknown struct type"); - return unknown(s->c); - } - return check_struct_fields(s,n,t); - } - et=check_expr(s,n->a->a); - variant=et && et->kind==FE_TYPE_ENUM ? - fe_type_variant(et,n->a->b ? n->a->b->text : "") : 0; - if (!variant) { err(s->c,n->loc,"invalid enum variant"); return unknown(s->c); } - if (variant->field_count != 0) { - for (f=n->children; f; f=f->next) { - if (f->kind != FE_N_FIELD) continue; - field=0; - if (variant->fields) { - unsigned i; - for(i=0;ifield_count;i++) if(strcmp(variant->fields[i].name,f->text)==0) field=&variant->fields[i]; - } - if (!field) { err(s->c,f->loc,"invalid enum payload field"); continue; } - v=check_expr(s,f->a); - mark_moved(s,f->a,v); - if (!compatible(field->type,v,f->a) && v->kind!=FE_TYPE_UNKNOWN) err(s->c,f->loc,"enum payload type mismatch"); - } - } else if (n->children) err(s->c,n->loc,"empty enum variant cannot have payload"); - n->sem_type=et; return et; - } - t=fe_type_intern(&s->c->types,n->text ? n->text : ""); - if (!t || t->kind!=FE_TYPE_STRUCT) { err(s->c,n->loc,"unknown struct type"); return unknown(s->c); } - return check_struct_fields(s,n,t); -} - -static FeType *check_array_init(FeCheckerState *s, FeNode *n) -{ - FeNode *x; FeType *elem=0; FeType *v; unsigned long count=0; - for(x=n->children;x;x=x->next) { v=check_expr(s,x); mark_moved(s,x,v); if(!elem) elem=v; else if(!compatible(elem,v,x)&&v->kind!=FE_TYPE_UNKNOWN) err(s->c,x->loc,"array element type mismatch"); ++count; } - if(!elem) elem=unknown(s->c); - n->sem_type=fe_type_array(&s->c->types,count,elem); return n->sem_type; -} - -static int array_slice_lvalue(FeNode *n) -{ - return n && (n->kind==FE_N_IDENT || n->kind==FE_N_MEMBER || - n->kind==FE_N_INDEX); -} - -static FeType *check_index(FeCheckerState *s, FeNode *n) -{ - FeType *base=check_expr(s,n->a); FeType *idx; FeType *elem; - if(!fe_type_is_indexable(base)) { err(s->c,n->loc,"indexing requires an array or slice"); return unknown(s->c); } - if(n->b) { idx=check_expr(s,n->b); if(known(idx)&&!fe_type_is_integer(idx)) err(s->c,n->loc,"index must be an integer"); } - if(n->c || !n->b) { - if (base->kind==FE_TYPE_ARRAY && !array_slice_lvalue(n->a)) - err(s->c,n->loc,"array slicing requires a stable lvalue"); - if(n->c) { - idx=check_expr(s,n->c); - if(known(idx)&&!fe_type_is_integer(idx)) - err(s->c,n->loc,"slice bound must be an integer"); - } - elem=base->elem; - n->sem_type=(base->kind==FE_TYPE_SLICE ? base->ref_mut : - lvalue_writable(s,n->a)) ? - fe_type_mut_slice(&s->c->types,elem) : - fe_type_slice(&s->c->types,elem); - return n->sem_type; - } - n->sem_type=base->elem; return n->sem_type; -} - -static FeType *check_identifier(FeCheckerState *s, FeNode *n) -{ - FeSym *sym; - sym = find_symbol(s->scope, n->text ? n->text : ""); - if (!sym) { - FeType *named=fe_type_intern(&s->c->types,n->text ? n->text : ""); - if(named->kind==FE_TYPE_STRUCT || named->kind==FE_TYPE_ENUM) { n->sem_type=named; return named; } - if(named->kind!=FE_TYPE_UNKNOWN) { - err(s->c, n->loc, "a type is not a value here"); - return unknown(s->c); - } - err(s->c, n->loc, "unknown name"); - return unknown(s->c); - } - n->cname = sym->cname; - n->sem_type = sym->type; - if (!sym->fn) { - fe_own_access(s->c->diags,&sym->own,FE_OWN_READ,n->loc); - sym->moved=sym->own.move; - } - return sym->type; -} - -static FeType *check_expr_core(FeCheckerState *s, FeNode *n) -{ - FeCheck *c = s->c; - FeType *a; - FeType *b; - FeSym *sym; - FeNode *x; - FeNode *param; - FeNode *arg; - FeType *et; - FeFieldType *field; - FeVariantType *variant; - const char *op; - if (!n) return unknown(c); - if (n->kind == FE_N_IDENT) - return check_identifier(s, n); - if (n->kind == FE_N_LITERAL) { - if (!n->text) return unknown(c); - if (strcmp(n->text, "true") == 0 || strcmp(n->text, "false") == 0) - a = fe_type_intern(&c->types, "bool"); - else if (n->text[0] == '\'') - a = fe_type_intern(&c->types, "char"); - else if (n->text[0] == '"') - a = fe_type_intern(&c->types, "str"); - else - a = fe_type_intern(&c->types, "i32"); - n->sem_type = a; - return a; - } - 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); - if (n->kind == FE_N_INDEX) return check_index(s,n); - if (n->kind == FE_N_MATCH) { check_match(s,n); n->sem_type=unknown(c); return n->sem_type; } - if (n->kind == FE_N_UNARY) { - a = check_expr(s, n->a); - op = n->text ? n->text : ""; - if (strcmp(op, "not") == 0) { - if (known(a) && a->kind != FE_TYPE_BOOL) - err(c, n->loc, "'not' requires bool"); - a = fe_type_intern(&c->types, "bool"); - } else if (strcmp(op, "-") == 0) { - if (known(a) && !fe_type_is_integer(a)) - err(c, n->loc, "unary '-' requires integer"); - } else if (strcmp(op, "try") == 0) { - /* SPEC 6.4: try is only allowed inside a function returning an error - union. Checked on the expression rather than on the statement so - that it also covers `var x = try e;` and `x = try e;`, which the - statement-level check walked straight past. */ - if (!s->ret || s->ret->kind != FE_TYPE_ERROR_UNION) - err(c,n->loc,"try requires an enclosing error result"); - if (a && a->kind==FE_TYPE_ERROR_UNION) - a=a->error_value; - else { - err(c,n->loc,"try requires an error result"); - 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; - return a; - } - if (n->kind == FE_N_TYPE && n->text && strcmp(n->text, "as") == 0) { - a = check_expr(s, n->a); - b = node_type(c, n->b); - if (b->kind == FE_TYPE_VOID) - err(c, n->loc, "cast target cannot be void"); - else if (known(a) && known(b) && !explicit_castable(a,b)) - err(c, n->loc, "'as' requires integer or char types"); - n->sem_type = b; - return b; - } - if (n->kind == FE_N_BINARY) { - a = check_expr(s, n->a); - b = check_expr(s, n->b); - op = n->text ? n->text : ""; - 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(c, n->loc, "logical operator requires bool operands"); - a = fe_type_intern(&c->types, "bool"); - } else 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) && - !compatible(a, b, n->b) && !compatible(b, a, n->a)) - err(c, n->loc, "comparison operands have different types"); - else if (strcmp(op,"==")!=0 && strcmp(op,"!=")!=0 && - ((known(a) && !ordered_type(a)) || - (known(b) && !ordered_type(b)))) - err(c, n->loc, "ordering requires integer or char operands"); - a = fe_type_intern(&c->types, "bool"); - } else { - if ((known(a) && !fe_type_is_integer(a)) || - (known(b) && !fe_type_is_integer(b)) || - (known(a) && known(b) && !fe_type_equal(a, b) && - !compatible(a, b, n->b) && !compatible(b, a, n->a))) - err(c, n->loc, - "arithmetic operands must have the same integer type"); - } - n->sem_type = a; - return a; - } - if (n->kind == FE_N_CALL) { - if (n->a && n->a->kind==FE_N_MEMBER && n->a->b && n->a->b->text && - strcmp(n->a->b->text,"drop")==0) { - err(c,n->loc,"drop may only be invoked by scope cleanup"); - return unknown(c); - } - 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) { - FeNode *arg=n->children; - 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) { - if (!arg || arg->next) - err(c,n->loc,"mem.create requires exactly one value"); - a=arg ? check_expr(s,arg) : unknown(c); - if(arg) mark_moved(s,arg,a); - a=fe_type_owned(&c->types,a); - n->sem_type=fe_type_error_union(&c->types,a); - return n->sem_type; - } - if (strcmp(n->a->b->text,"alloc_slice")==0) { - FeNode *count=arg ? arg->next : 0; - FeType *item; - if(!arg || arg->kind!=FE_N_IDENT || !count || count->next) - err(c,n->loc,"mem.alloc_slice requires a type and length"); - item=arg && arg->kind==FE_N_IDENT ? - fe_type_intern(&c->types,arg->text) : unknown(c); - b=count ? check_expr(s,count) : unknown(c); - if(known(b) && !fe_type_is_integer(b)) - err(c,count->loc,"slice length must be an integer"); - /* Freshly allocated storage is owned outright, so it is - writable: there is nobody else to disturb. */ - a=fe_type_owned(&c->types,fe_type_mut_slice(&c->types,item)); - n->sem_type=fe_type_error_union(&c->types,a); - return n->sem_type; - } - if (strcmp(n->a->b->text,"replace")==0) { - FeNode *value=arg ? arg->next : 0; - if(!arg || !value || value->next) - err(c,n->loc,"mem.replace requires destination and value"); - a=arg ? check_expr(s,arg) : unknown(c); - 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"); - b=value ? check_expr(s,value) : unknown(c); - if(a && a->kind==FE_TYPE_REF && !compatible(a->elem,b,value)) - err(c,value->loc,"mem.replace value type mismatch"); - if(value) mark_moved(s,value,b); - n->sem_type=a && a->kind==FE_TYPE_REF ? a->elem : unknown(c); - fe_type_require_replace(&c->types,n->sem_type); - 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,"io")==0 && n->a->b && n->a->b->text && - strcmp(n->a->b->text,"null_writer")==0) { - FeNode *arg=n->children; - if (arg) err(c,n->loc,"io.null_writer takes no arguments"); - n->sem_type=fe_type_intern(&c->types,"io.Writer"); - return n->sem_type; - } - if (n->text && is_format_builtin(n->text)) { - check_format_call(s,n); - if (strcmp(n->text,"@print")==0) - n->sem_type=fe_type_intern(&c->types,"void"); - else if (strcmp(n->text,"@sprint")==0) - n->sem_type=fe_type_intern(&c->types,"usize"); - else - n->sem_type=fe_type_error_union(&c->types,fe_type_intern(&c->types,"void")); - return n->sem_type; - } - if (!n->a && n->text && (strcmp(n->text,"@size_of")==0 || strcmp(n->text,"@align_of")==0)) { - FeNode *type_arg=n->children; - FeType *target=type_arg && type_arg->kind==FE_N_IDENT ? fe_type_intern(&c->types,type_arg->text) : unknown(c); - if(!target || !known(target)) err(c,n->loc,"size/align requires a known type"); - n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type; - } - if (n->a && n->a->kind == FE_N_MEMBER) { - FeNode *method; - FeNode *self_param; - FeUnit *home=binding_unit(s,n->a->a); - if (home) { - const char *want=n->a->b && n->a->b->text ? n->a->b->text : ""; - FeSym *fsym=unit_member(c,home,want); - if (!fsym) { - err(c,n->a->loc,"unknown name"); - for (x=n->children;x;x=x->next) check_expr(s,x); - return unknown(c); - } - if (!decl_is_public(fsym->decl)) { - err(c,n->a->loc,"name is private to its unit"); - for (x=n->children;x;x=x->next) check_expr(s,x); - return unknown(c); - } - if (decl_is_generic(fsym->fn)) - return check_generic_call(s,n,fsym,home); - return check_call_args(s,n,fsym,home->name,0); - } - { - int names_type=0; - FeType *owner_type=type_from_expr(s,n->a->a,&names_type); - if (names_type && owner_type && - owner_type->kind==FE_TYPE_STRUCT) { - FeNode *m=type_method(owner_type, - n->a->b ? n->a->b->text : ""); - if (!m) { err(c,n->a->loc,"unknown method"); return unknown(c); } - if (!method_is_static(m)) { - err(c,n->loc,"method requires a receiver"); - return unknown(c); - } - return check_static_method_call(s,n,owner_type,m); - } - } - et=check_expr(s,n->a->a); - /* A method can be reached through a reference or an owner as well - as through the value itself. */ - if (et && (et->kind==FE_TYPE_REF || et->kind==FE_TYPE_OWNED) && - et->elem && et->elem->kind==FE_TYPE_STRUCT && - find_method(c,et->elem,n->a->b ? n->a->b->text : "")) - et=et->elem; - method=et && et->kind==FE_TYPE_STRUCT ? - find_method(c,et,n->a->b ? n->a->b->text : "") : 0; - if(method) { - FeBindSave msave; - int bound=0; - self_param=method->a ? method->a->children : 0; - if(!self_param) { - err(c,n->loc,"method requires self parameter"); - return unknown(c); - } - /* A method of a generic instance reads its signature with that - instance's arguments bound. */ - if (et->bind_count) { - push_instance_bindings(c,&msave,et); - bind_self(c,et); - bound=1; - } - a=method_type(c,self_param->a,et); - if(a->kind==FE_TYPE_REF && a->ref_mut && - !lvalue_writable(s,n->a->a)) - err(c,n->loc,"mutable method requires a mutable receiver"); - if(a->kind!=FE_TYPE_REF) mark_moved(s,n->a->a,et); - param=self_param->next; - arg=n->children; - while(param && arg) { - a=check_expr(s,arg); - b=method_type(c,param->a,et); - if(!compatible(b,a,arg) && a->kind!=FE_TYPE_UNKNOWN) - err(c,arg->loc,"method argument type mismatch"); - mark_moved(s,arg,a); - param=param->next; - arg=arg->next; - } - if(param || arg) err(c,n->loc,"wrong number of method arguments"); - n->sem_decl=method; - n->sem_type=method->b ? method_type(c,method->b,et) : - fe_type_intern(&c->types,"void"); - if (bound) { - pop_bindings(c,&msave); - check_instance_method(s,et,method,n->loc,n); - } - 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; - if (!variant) { err(c,n->loc,"invalid enum variant constructor"); return unknown(c); } - if (variant->field_count==1 && arg) { - FeType *av=check_expr(s,arg); - if(!compatible(variant->fields[0].type,av,arg)&&av->kind!=FE_TYPE_UNKNOWN) err(c,arg->loc,"enum payload type mismatch"); - } else if (variant->field_count != 0 || arg) err(c,n->loc,"wrong enum payload arity"); - n->sem_type=et; return et; - } - if (n->a && n->a->kind == FE_N_IDENT) { - sym = find_symbol(s->scope, n->a->text ? n->a->text : ""); - if (!sym) { - err(c, n->loc, "unknown function"); - return unknown(c); - } - if (decl_is_generic(sym->fn)) - return check_generic_call(s,n,sym,current_unit(c)); - return check_call_args(s, n, sym, 0, 0); - } - for (x = n->children; x; x = x->next) check_expr(s, x); - return unknown(c); - } - if (n->kind == FE_N_MEMBER) { - if (is_error_set_member(s,n)) { - n->sem_type=fe_type_intern(&c->types,"core.Error"); - return n->sem_type; - } - if (n->a && n->a->kind==FE_N_IDENT && n->a->text && - strcmp(n->a->text,"io")==0 && n->b && n->b->text && - (strcmp(n->b->text,"stdout")==0 || - strcmp(n->b->text,"stderr")==0)) { - n->sem_type=fe_type_intern(&c->types,"io.Writer"); - return n->sem_type; - } - a=check_expr(s,n->a); - if (a->kind == FE_TYPE_REF && n->b && n->b->text && - strcmp(n->b->text,"^")==0) { - n->sem_type=a->elem; - return a->elem; - } - if(a->kind==FE_TYPE_REF && a->elem && - a->elem->kind==FE_TYPE_STRUCT) { - field=fe_type_field(a->elem,n->b ? n->b->text : ""); - if(!field) { err(c,n->loc,"unknown struct field"); return unknown(c); } - n->sem_type=field->type; - return field->type; - } - if (a->kind == FE_TYPE_OWNED && n->b && n->b->text && - strcmp(n->b->text,"^")==0) { - n->sem_type=a->elem; - return a->elem; - } - if(a->kind==FE_TYPE_STRUCT) { - field=fe_type_field(a,n->b ? n->b->text : ""); - if(!field) { err(c,n->loc,"unknown struct field"); return unknown(c); } - n->sem_type=field->type; return field->type; - } - if(a->kind==FE_TYPE_ENUM) { - if(!fe_type_variant(a,n->b ? n->b->text : "")) err(c,n->loc,"unknown enum variant"); - n->sem_type=a; return a; - } - if((a->kind==FE_TYPE_SLICE || a->kind==FE_TYPE_STR) && n->b && - strcmp(n->b->text,"n")==0) { - n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type; - } - return unknown(c); - } - return unknown(c); -} - -/* `base_in` is the already-checked type of a member expression's base. The M7 - lvalue path looks at that base before delegating here, and checking it a - second time reports any ownership violation on it a second time too. */ -static FeType *check_lvalue_core(FeCheckerState *s, FeNode *n, int read, - FeType *base_in) -{ - FeSym *sym; - FeType *base; - FeFieldType *field; - if (n && n->kind == FE_N_IDENT) { - sym = find_symbol(s->scope, n->text ? n->text : ""); - if (!sym) { - err(s->c, n->loc, "unknown name"); - return unknown(s->c); - } - if (sym->fn) { - err(s->c, n->loc, "function is not assignable"); - return unknown(s->c); - } - if (!sym->mutable) - err(s->c, n->loc, "cannot assign to immutable let"); - n->cname = sym->cname; - n->sem_type = sym->type; - 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) { - base=base_in ? base_in : check_expr(s,n->a); - 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; - } - if(base && base->kind==FE_TYPE_REF && base->elem && - base->elem->kind==FE_TYPE_STRUCT) { - if(!base->ref_mut) - err(s->c,n->loc,"cannot write through shared reference"); - field=fe_type_field(base->elem,n->b ? 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; - } - if (base && base->kind == FE_TYPE_OWNED && n->b && n->b->text && - strcmp(n->b->text,"^")==0) { - n->sem_type=base->elem; - return base->elem; - } - if (!lvalue_writable(s,n->a)) - err(s->c,n->loc,"cannot assign through immutable value"); - field=base && base->kind==FE_TYPE_STRUCT ? fe_type_field(base,n->b ? n->b->text : "") : 0; - 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; - } - if (n && n->kind == FE_N_INDEX) { - base=check_index(s,n); - if (n->a && n->a->sem_type && - n->a->sem_type->kind == FE_TYPE_SLICE && - !n->a->sem_type->ref_mut) - err(s->c,n->loc,"cannot write through shared slice"); - else if (n->a && n->a->sem_type && - n->a->sem_type->kind != FE_TYPE_SLICE && - !lvalue_writable(s,n->a)) - err(s->c,n->loc,"cannot assign through immutable value"); - return base; - } - if (n) err(s->c, n->loc, "assignment requires a variable"); - return unknown(s->c); -} - -static int compound_operator(const char *op) -{ - return op && strcmp(op, "=") != 0; -} - -static void check_match(FeCheckerState *s, FeNode *n) -{ - FeType *value; - FeNode *arm; - FeVariantType *variant; - int seen[256]; - int wildcard=0; - FeFlowSlot base[64], merged[64], current[64]; - unsigned flow_count; - int have_merged=0; - unsigned i; - for(i=0;i<256U;i++) seen[i]=0; - value=check_expr(s,n->a); - if(!value || value->kind!=FE_TYPE_ENUM) { err(s->c,n->loc,"match requires an enum value"); return; } - flow_count=flow_capture(s->scope,base,64); - for(arm=n->children;arm;arm=arm->next) { - FeScope *old=s->scope; - flow_restore(base,flow_count); - if(arm->text && strcmp(arm->text,"_")==0) wildcard=1; - else { - variant=fe_type_variant(value,arm->text); - if(!variant) { err(s->c,arm->loc,"unknown match variant"); continue; } - if(variant->tag<256U) { - if(seen[variant->tag]) err(s->c,arm->loc,"duplicate match variant"); - seen[variant->tag]=1; - } - s->scope=scope_new(s,old); - if(variant->field_count==1 && arm->children) { - add_symbol(s,s->scope,arm->children->text,variant->fields[0].type,0,0,1, - local_cname(s->c,arm->children->text),arm->children); - } else if(variant->field_count>0) { - FeNode *b=arm->children; - for(i=0;ifield_count && b;i++,b=b->next) { - FeFieldType *f=&variant->fields[i]; - add_symbol(s,s->scope,b->text,f->type,0,0,1, - local_cname(s->c,b->text),b); - } - } - } - if(arm->a && arm->a->kind==FE_N_BLOCK) check_stmt(s,arm->a); - else if(arm->a) check_expr(s,arm->a); - s->scope=old; - flow_capture(s->scope,current,flow_count); - if(!have_merged) { - for(i=0;ivariant_count && i<256U;i++) if(!seen[i]) err(s->c,n->loc,"non-exhaustive match"); -} - -static void check_for(FeCheckerState *s, FeNode *n) -{ - FeType *start; - FeType *finish; - FeType *elem; - FeType *ref_type; - FeSym *iter_sym; - char *index_cname; - char *item_cname; - int iter_mut; - FeScope *old=s->scope; - if(!n->c) { - start=check_expr(s,n->a); - if (!fe_type_is_indexable(start)) { - err(s->c,n->loc,"for iterable must be an array, slice, or str"); - return; - } - elem=start->elem; - iter_sym=0; - if (n->a && n->a->kind==FE_N_IDENT) - iter_sym=find_symbol(s->scope,n->a->text ? n->a->text : ""); - else if (n->a && n->a->kind==FE_N_INDEX && n->a->a && - n->a->a->kind==FE_N_IDENT) - iter_sym=find_symbol(s->scope,n->a->a->text ? n->a->a->text : ""); - iter_mut=start->kind==FE_TYPE_SLICE ? start->ref_mut : - (iter_sym && iter_sym->mutable); - ref_type=fe_type_ref(&s->c->types,elem,iter_mut); - if (iter_mut) n->flags |= 4U; - s->scope=scope_new(s,old); - if (n->aux_text) { - index_cname=local_cname(s->c,n->text ? n->text : "index"); - item_cname=local_cname(s->c,n->aux_text); - add_symbol(s,s->scope,n->text,fe_type_intern(&s->c->types,"usize"),0,0,1, - index_cname,n); - add_symbol(s,s->scope,n->aux_text,ref_type,0,iter_mut,1, - item_cname,0); - n->cname=index_cname; - n->aux_cname=item_cname; - } else { - item_cname=local_cname(s->c,n->text ? n->text : "item"); - add_symbol(s,s->scope,n->text,ref_type,0,iter_mut,1, - item_cname,n); - n->cname=item_cname; - } - check_stmt(s,n->b); - s->scope=old; - return; - } - start=check_expr(s,n->a); - finish=check_expr(s,n->c); - if(known(start)&&!fe_type_is_integer(start)) err(s->c,n->loc,"range start must be integer"); - if(known(finish)&&!fe_type_is_integer(finish)) err(s->c,n->loc,"range end must be integer"); - s->scope=scope_new(s,old); - index_cname=local_cname(s->c,n->text ? n->text : "index"); - add_symbol(s,s->scope,n->text,fe_type_intern(&s->c->types,"usize"),0,0,1, - index_cname,n); - n->cname=index_cname; - check_stmt(s,n->b); - s->scope=old; -} - -static void check_type_cycle(FeCheck *c, FeType *t) -{ - unsigned i; - FeType *next; - if (!t || t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR || - t->kind == FE_TYPE_REF || t->kind == FE_TYPE_OWNED || - t->kind == FE_TYPE_INT || t->kind == FE_TYPE_BOOL || - t->kind == FE_TYPE_CHAR || t->kind == FE_TYPE_VOID || - t->kind == FE_TYPE_UNKNOWN || t->kind == FE_TYPE_ERROR) return; - if (t->kind == FE_TYPE_ERROR_UNION) { - check_type_cycle(c,t->error_value); - return; - } - if (t->cycle_state == 1) { - if (c->ast->root) err(c, c->ast->root->loc, "by-value recursive type"); - return; - } - if (t->cycle_state == 2) return; - t->cycle_state = 1; - if (t->kind == FE_TYPE_ARRAY) { - check_type_cycle(c,t->elem); - } else if (t->kind == FE_TYPE_STRUCT) { - for (i=0;ifield_count;i++) { - if (!t->fields[i].type && t->fields[i].ast_node) - t->fields[i].type=fe_type_from_ast(&c->types,t->fields[i].ast_node->a); - check_type_cycle(c,t->fields[i].type); - } - } else if (t->kind == FE_TYPE_ENUM) { - for (i=0;ivariant_count;i++) { - unsigned j; - for (j=0;jvariants[i].field_count;j++) { - if (!t->variants[i].fields[j].type && t->variants[i].fields[j].ast_node) - t->variants[i].fields[j].type=fe_type_from_ast(&c->types, - t->variants[i].fields[j].ast_node->a); - next=t->variants[i].fields[j].type; - check_type_cycle(c,next); - } - } - } - t->cycle_state=2; -} - -static void check_type_cycles(FeCheck *c) -{ - FeType *t; - for (t=c->types.types;t;t=t->next) t->cycle_state=0; - 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_core(FeCheckerState *s, FeNode *n) -{ - FeCheck *c = s->c; - FeScope *old; - FeType *a; - FeType *b; - FeSym *sym; - FeNode *x; - int initialized; - 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: - a = n->a ? node_type(c, n->a) : unknown(c); - b = check_expr(s, n->b); - if (!n->a) a = b; - if (a->kind == FE_TYPE_VOID) - err(c, n->loc, "variable cannot have void type"); - if (n->a && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) - err(c, n->loc, "initializer type mismatch"); - if (b->kind == FE_TYPE_VOID) - err(c, n->loc, "void expression cannot initialize a variable"); - 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); - 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); - if (!n->b && !n->a) - err(c, n->loc, "uninitialized var requires an explicit type"); - b = n->b ? check_expr(s, n->b) : unknown(c); - if (!n->a && n->b) a = b; - if (a->kind == FE_TYPE_VOID) - err(c, n->loc, "variable cannot have void type"); - if (n->b && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) - err(c, n->loc, "initializer type mismatch"); - if (b->kind == FE_TYPE_VOID) - err(c, n->loc, "void expression cannot initialize a variable"); - mark_moved(s,n->b,b); - initialized = n->b != 0; - 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); - a = check_lvalue(s, n->a, compound_operator(n->text)); - if (!compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) - err(c, n->loc, "assignment type mismatch"); - 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; - 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: - /* The enclosing-error-result check lives on the try expression itself, - so a bare `try e;` needs nothing extra here. */ - 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: { - 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); - 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;iloop_depth < 255U) ++s->loop_depth; - check_for(s,n); - if (s->loop_depth) --s->loop_depth; - break; - case FE_N_MATCH: - check_match(s,n); - break; - case FE_N_BREAK: - case FE_N_CONTINUE: - if (!s->loop_depth) err(c,n->loc,"break or continue outside loop"); - break; - case FE_N_RETURN: - b = n->a ? 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"); - else if (known(s->ret) && known(b) && !fe_type_equal(s->ret, b) && - b->kind != FE_TYPE_UNKNOWN && - !compatible(s->ret,b,n->a)) - err(c, n->loc, "return type mismatch"); - break; - case FE_N_UNSAFE: - check_stmt(s, n->a); - break; - default: - break; - } -} - -static void check_fn(FeCheck *c, FeNode *fn, FeScope *globals) -{ - FeCheckerState s; - FeScope *old; - FeNode *x; - FeType *t; - s.c = c; - s.globals = globals; - s.scope = scope_new(&s, 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->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); - if (t->kind == FE_TYPE_VOID) - err(c, x->loc, "parameter cannot have void type"); - add_symbol(&s, s.scope, x->text, t, 0, 1, 1, - local_cname(c, x->text ? x->text : "arg"), x); - } - old = s.scope; - if (fn->c) check_stmt(&s, fn->c); - s.scope = old; -} - -static void check_method(FeCheck *c, FeNode *fn, FeScope *globals, - FeType *owner) -{ - FeCheckerState s; - FeNode *x; - FeType *t; - s.c=c; - s.globals=globals; - s.scope=scope_new(&s,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->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); - x->sem_type=t; - add_symbol(&s,s.scope,x->text,t,0,1,1, - local_cname(c,x->text ? x->text : "arg"),x); - } - if(fn->c) check_stmt(&s,fn->c); -} - -static int m7_actual_compatible(FeType *want, FeType *got, FeNode *value) -{ - if (fe_type_equal(want,got)) return 1; - return compatible(want,got,value); -} - -static FeType *m7_check_expected(FeCheckerState *s, FeNode *value, - FeType *expected) -{ - FeType *actual; - FeM7ContextKind context; - if (!value) return unknown(s->c); - if (fe_m7_is_null(value)) { - if (!fe_m7_can_contextual_null(expected)) { - err(s->c,value->loc,"null requires a contextual optional type"); - value->sem_type=unknown(s->c); - return value->sem_type; - } - value->sem_type=expected; - value->sem_context=expected; - return expected; - } - actual=check_expr(s,value); - if (!expected) return actual; - if (expected->kind==FE_TYPE_OPTIONAL && expected->elem && - m7_actual_compatible(expected->elem,actual,value)) { - value->sem_context=expected; - return expected; - } - if (expected->kind==FE_TYPE_ERROR_UNION) { - context=fe_m7_error_context(&s->c->types,expected,actual); - if (context!=FE_M7_CONTEXT_NONE) { - value->sem_context=expected; - return expected; - } - } - return actual; -} - -static FeType *m7_member_field(FeCheckerState *s, FeNode *n, FeType *base) -{ - FeFieldType *field; - FeType *owner; - if (!base) return unknown(s->c); - if (n->text && strcmp(n->text,".?")==0) { - if (base->kind!=FE_TYPE_OPTIONAL) { - err(s->c,n->loc,"optional projection '.?' requires an optional value"); - return unknown(s->c); - } - n->sem_type=base->elem; - return n->sem_type; - } - if (base->kind==FE_TYPE_OPTIONAL) { - err(s->c,n->loc,"optional value must be projected with '.?' first"); - return unknown(s->c); - } - if (base->kind==FE_TYPE_REF && n->b && n->b->text && - strcmp(n->b->text,"^")==0) { - n->sem_type=base->elem; - return n->sem_type; - } - if (base->kind==FE_TYPE_OWNED && n->b && n->b->text && - strcmp(n->b->text,"^")==0) { - n->sem_type=base->elem; - return n->sem_type; - } - 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) { - field=fe_type_field(owner,n->b->text); - if (!field) { - err(s->c,n->loc,"unknown struct field"); - return unknown(s->c); - } - n->sem_type=field->type; - return field->type; - } - if (base->kind==FE_TYPE_ENUM && n->b && n->b->text) { - if (!fe_type_variant(base,n->b->text)) - err(s->c,n->loc,"unknown enum variant"); - n->sem_type=base; - return base; - } - if ((base->kind==FE_TYPE_SLICE || base->kind==FE_TYPE_STR) && - n->b && n->b->text && strcmp(n->b->text,"n")==0) { - n->sem_type=fe_type_intern(&s->c->types,"usize"); - return n->sem_type; - } - n->sem_type=unknown(s->c); - return n->sem_type; -} - -static int m7_place_is_projection(FeNode *n) -{ - return n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX); -} - -/* ------------------------------------------------------------------------- * - * Generics (SPEC 9) - * - * A generic declaration is checked once per distinct list of type arguments. - * Those arguments are bound as types for the length of that check, so a name - * that is a type parameter simply is its argument -- in the body, in field - * types and in the signature alike. An instance is identified by its declaring - * unit, its declaration and the spelling of its arguments, so asking twice - * asks for the same instance, and a chain of new ones is bounded. - * ------------------------------------------------------------------------- */ - -#define FE_GENERIC_DEPTH_MAX 32 - -static unsigned decl_type_param_count(const FeNode *decl) -{ - FeNode *p; - unsigned n=0; - if (!decl) return 0; - if (decl->kind==FE_N_FN) { - for (p=decl->a?decl->a->children:0;p;p=p->next) - if (p->flags & FE_NODE_COMPTIME) ++n; - return n; - } - if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) - for (p=decl->a?decl->a->children:0;p;p=p->next) ++n; - return n; -} - -static FeNode *decl_type_param(const FeNode *decl, unsigned i) -{ - FeNode *p; - unsigned n=0; - if (!decl) return 0; - if (decl->kind==FE_N_FN) { - for (p=decl->a?decl->a->children:0;p;p=p->next) - if (p->flags & FE_NODE_COMPTIME) { if (n==i) return p; ++n; } - return 0; - } - for (p=decl->a?decl->a->children:0;p;p=p->next) { if (n==i) return p; ++n; } - return 0; -} - -static int decl_is_generic(const FeNode *decl) -{ - return decl_type_param_count(decl)!=0; -} - -/* SPEC 9: v0.1 has comptime type parameters and no other kind. */ -static void check_generic_params(FeCheck *c, FeNode *decl) -{ - FeNode *p; - if (!decl || decl->kind!=FE_N_FN) return; - for (p=decl->a?decl->a->children:0;p;p=p->next) { - if (!(p->flags & FE_NODE_COMPTIME)) continue; - if (!p->a || p->a->kind!=FE_N_TYPE || !p->a->text || - strcmp(p->a->text,"type")!=0) - err(c,p->loc,"a comptime parameter must be a type parameter"); - } -} - -static void push_bindings(FeCheck *c, FeBindSave *save, FeNode *decl, - FeType **args, unsigned count) -{ - unsigned i; - save->count=c->types.param_count; - for (i=0;iparams[i]=c->types.params[i]; - c->types.param_count=0; - for (i=0;itypes.params[i].name=p && p->text ? p->text : "?"; - c->types.params[i].type=args[i]; - ++c->types.param_count; - } -} - -/* Restore the bindings recorded on an instance, so a method sees exactly the - environment its type was built with. */ -static void push_instance_bindings(FeCheck *c, FeBindSave *save, FeType *t) -{ - unsigned i; - save->count=c->types.param_count; - for (i=0;iparams[i]=c->types.params[i]; - c->types.param_count=0; - for (i=0;ibind_count && itypes.params[c->types.param_count++]=t->binds[i]; -} - -static void bind_self(FeCheck *c, FeType *owner) -{ - if (c->types.param_count>=FE_TYPE_PARAM_MAX) return; - c->types.params[c->types.param_count].name="Self"; - c->types.params[c->types.param_count].type=owner; - ++c->types.param_count; -} - -static void pop_bindings(FeCheck *c, const FeBindSave *save) -{ - unsigned i; - for (i=0;itypes.params[i]=save->params[i]; - c->types.param_count=save->count; -} - -/* `unit.Name(arg,arg)` -- the canonical identity of one instance. - Nesting makes the readable spelling grow without bound, and a spelling that - got cut off would make two different instances look like the same one, so - past a length the arguments are written as serial numbers instead. Those are - unique, so identity stays exact even where the spelling stops being - readable. */ -#define FE_GENERIC_NAME_READABLE 200 - -static void instance_key(char *out, const char *unit, const char *name, - FeType **args, unsigned count) -{ - unsigned i; - unsigned long n=0; - unsigned long cap=(unsigned long)FE_GENERIC_NAME_READABLE; - const char *p; - char number[24]; - int readable=1; - for (p=unit?unit:"";*p;++p) { if (nname[0] ? args[i]->name : "?";*p;++p) { - if (nserial : 0U); - for (p=number;*p && ninstance_count;++i) - if (!strcmp(c->instances[i].key,key)) return c->instances[i].cname; - return 0; -} - -static int instance_known(FeCheck *c, const char *key) -{ - unsigned i; - for (i=0;iinstance_count;++i) - if (strcmp(c->instances[i].key,key)==0) return 1; - return 0; -} - -static int instance_record(FeCheck *c, const char *key, FeLoc loc, - FeNode *decl, FeUnit *home, FeType *owner) -{ - FeInstance *inst; - unsigned i; - if (instance_known(c,key)) return 0; - if (c->instance_count>=FE_GENERIC_INSTANCE_MAX) { - err(c,loc,"too many generic instances"); - return -1; - } - inst=&c->instances[c->instance_count]; - strcpy(inst->key,key); - inst->decl=decl; - inst->home=home ? home->name : 0; - inst->owner=owner; - inst->cname=unit_cname(c,key); - /* The bindings in force right now are the ones this instance was built - with, and lowering has to see exactly those again. */ - inst->bind_count=c->types.param_count; - for (i=0;itypes.param_count && ibinds[i]=c->types.params[i]; - ++c->instance_count; - return 1; -} - -/* One step further down a chain of instantiations. Chains that keep producing - new instances are the ones that never end, so the limit counts nesting. */ -static int instance_descend(FeCheck *c, FeLoc loc) -{ - if (c->instance_depth>=FE_GENERIC_DEPTH_MAX) { - err(c,loc,"generic instantiation depth exceeded"); - return 0; - } - ++c->instance_depth; - return 1; -} - -static FeUnit *current_unit(FeCheck *c) -{ - unsigned u; - for (u=0;ubuild->count;++u) - if (strcmp(c->build->units[u].name,c->types.unit_name)==0) - return &c->build->units[u]; - return c->unit; -} - -/* Build `Box(i32)`: the declaration's fields with the parameters bound, under - a name that records which arguments made it. */ -static FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl, - const char *key, FeType **args, - unsigned count) -{ - FeBindSave save; - FeType *t; - FeNode *f; - unsigned fields=0; - unsigned i=0; - t=fe_type_intern_unit(&c->types,home->name,key); - if (!t || t->kind!=FE_TYPE_UNKNOWN) return t; - t->kind=FE_TYPE_STRUCT; - t->packed=(decl->flags & FE_NODE_PACKED)!=0; - t->decl_node=decl; - t->bind_count=0; - for (i=0;ibinds[t->bind_count].name=p && p->text ? p->text : "?"; - t->binds[t->bind_count].type=args[i]; - ++t->bind_count; - } - t->cname=unit_cname(c,key); - for (f=decl->children;f;f=f->next) - if (f->kind==FE_N_FN && f->text && strcmp(f->text,"drop")==0) - t->has_drop=1; - for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) ++fields; - t->field_count=fields; - if (fields) { - t->fields=(FeFieldType *)fe_arena_alloc(&c->arena, - fields*sizeof(FeFieldType)); - if (!t->fields) { t->field_count=0; return t; } - push_instance_bindings(c,&save,t); - bind_self(c,t); - i=0; - for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) { - t->fields[i].name=f->text; - t->fields[i].type=node_type(c,f->a); - t->fields[i].offset=0; - t->fields[i].ast_node=f; - ++i; - } - pop_bindings(c,&save); - } - fe_type_layout_all(&c->types); - /* A type that says how to let go of itself needs that method to exist for - every instance, whether or not anyone calls it by name: scope cleanup - will. */ - { - FeNode *release; - for (release=decl->children;release;release=release->next) - if (release->kind==FE_N_FN && release->text && - !strcmp(release->text,"drop") && release->c) { - FeCheckerState s; - memset(&s,0,sizeof s); - s.c=c; - s.scope=c->unit_scope[unit_index(c,home)]; - s.globals=s.scope; - check_instance_method(&s,t,release,decl->loc,0); - break; - } - } - return t; -} - -static FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name, - FeType **args, unsigned count, FeLoc loc) -{ - FeNode *decl=unit_type_decl(c,home,name); - char key[FE_GENERIC_KEY_MAX]; - if (!decl || !decl_is_generic(decl)) { - err(c,loc,"type does not take generic arguments"); - return unknown(c); - } - if (decl->kind!=FE_N_STRUCT) { - err(c,loc,"only a generic struct can be instantiated"); - return unknown(c); - } - if (count!=decl_type_param_count(decl)) { - err(c,loc,"wrong number of generic arguments"); - return unknown(c); - } - instance_key(key,home->name,name,args,count); - if (instance_record(c,key,loc,decl,home,0)<0) return unknown(c); - return build_struct_instance(c,home,decl,key,args,count); -} - -/* `Name(args...)` written in type position. */ -static FeType *instantiate_type_node(void *owner, const FeNode *node) -{ - FeCheck *c=(FeCheck *)owner; - FeUnit *home=current_unit(c); - const char *name=node->text; - FeNode *arg; - FeType *args[FE_TYPE_PARAM_MAX]; - unsigned count=0; - FeType *result; - /* `binding.Name` names a type in another unit. The binding is not itself a - type, so it has to be peeled off before anything is looked up. */ - if (node->a && node->a->kind==FE_N_IDENT && node->a->text && c->build && - c->unit) { - FeUnit *bound=fe_build_binding(c->build,c->unit,node->text); - if (bound) { home=bound; name=node->a->text; } - } - if (!node->children) { - FeNode *decl=unit_type_decl(c,home,name ? name : ""); - if (decl && decl_is_generic(decl)) { - /* A generic declaration is not a type until it has arguments. */ - err(c,node->loc,"generic type requires type arguments"); - return unknown(c); - } - if (name!=node->text) { - FeType *there=unit_type(c,home,name); - if (there) return there; - } - return fe_type_intern(&c->types,name); - } - if (!instance_descend(c,node->loc)) return unknown(c); - for (arg=node->children;arg;arg=arg->next) { - if (counttypes,arg); - ++count; - } - if (count>FE_TYPE_PARAM_MAX) { - err(c,node->loc,"wrong number of generic arguments"); - --c->instance_depth; - return unknown(c); - } - result=instantiate_struct(c,home,name ? name : "",args,count, - node->loc); - --c->instance_depth; - return result; -} - -/* A type written where an expression is: `i32`, `Box(i32)`. Only a comptime - argument position accepts one. */ -static FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok) -{ - FeCheck *c=s->c; - FeType *t; - unsigned i; - *ok=0; - if (!n) return unknown(c); - if (n->kind==FE_N_IDENT && n->text) { - for (i=0;itypes.param_count;++i) - if (strcmp(c->types.params[i].name,n->text)==0) { - *ok=1; - return c->types.params[i].type; - } - if (find_symbol(s->scope,n->text)) { - /* A const alias of a type is that type (SPEC 4.7). */ - FeSym *sym=find_symbol(s->scope,n->text); - if (sym && sym->decl && sym->decl->kind==FE_N_CONST && - sym->decl->b && sym->decl->b->kind==FE_N_IDENT) - return type_from_expr(s,sym->decl->b,ok); - return unknown(c); - } - t=fe_type_intern(&c->types,n->text); - if (t && t->kind!=FE_TYPE_UNKNOWN) { *ok=1; return t; } - return unknown(c); - } - if (n->kind==FE_N_CALL && n->a && - (n->a->kind==FE_N_IDENT || - (n->a->kind==FE_N_MEMBER && n->a->a && - n->a->a->kind==FE_N_IDENT && n->a->b && n->a->b->text))) { - FeType *args[FE_TYPE_PARAM_MAX]; - unsigned count=0; - FeNode *arg; - FeType *result; - FeUnit *home=current_unit(c); - const char *want; - /* `Name(args)` here, `binding.Name(args)` when the declaration is in - another unit. */ - if (n->a->kind==FE_N_MEMBER) { - FeUnit *bound=binding_unit(s,n->a->a); - if (!bound) return unknown(c); - home=bound; - want=n->a->b->text; - } else { - want=n->a->text; - } - if (!want || !unit_type_decl(c,home,want)) return unknown(c); - if (!instance_descend(c,n->loc)) { *ok=1; return unknown(c); } - for (arg=n->children;arg;arg=arg->next) { - int inner=0; - if (countinstance_depth; return unknown(c); } - ++count; - } - if (count>FE_TYPE_PARAM_MAX) { --c->instance_depth; return unknown(c); } - result=instantiate_struct(c,home,want,args,count,n->loc); - --c->instance_depth; - *ok=1; - return result; - } - return unknown(c); -} - -/* A `comptime if` condition. Only the forms SPEC 9 allows: type equality and - the type predicates. Anything else is not decidable here. */ -static int comptime_condition(FeCheckerState *s, FeNode *n, int *out) -{ - FeType *a; - FeType *b; - int ok=0; - int eq; - if (!n) return 0; - if (n->kind==FE_N_BINARY && n->text && - (strcmp(n->text,"==")==0 || strcmp(n->text,"!=")==0)) { - a=type_from_expr(s,n->a,&ok); - if (!ok) return 0; - b=type_from_expr(s,n->b,&ok); - if (!ok) return 0; - eq=fe_type_equal(a,b); - *out=strcmp(n->text,"==")==0 ? eq : !eq; - return 1; - } - if (n->kind==FE_N_CALL && n->text && - (strcmp(n->text,"@is_int")==0 || strcmp(n->text,"@is_ptr")==0)) { - a=type_from_expr(s,n->children,&ok); - if (!ok) return 0; - *out=strcmp(n->text,"@is_int")==0 ? fe_type_is_integer(a) : - (a && (a->kind==FE_TYPE_OWNED || a->kind==FE_TYPE_REF)); - return 1; - } - return 0; -} - -/* Check a generic body once, in the unit that declared it and with the - instance's arguments bound. Errors land on the operation that is wrong; the - call site gets a note, because the call is context and not the defect. */ -static void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl, - FeType *owner, FeBindSave *bindings, FeLoc site) -{ - FeAst *save_ast=c->ast; - FeUnit *save_unit=c->unit; - const char *save_name=c->types.unit_name; - unsigned before=c->diags->errors; - (void)bindings; - c->ast=&home->ast; - c->unit=home; - c->types.unit_name=home->name; - fe_diags_source(c->diags,home->source,home->size); - if (owner) check_method(c,decl,c->unit_scope[unit_index(c,home)],owner); - else check_fn(c,decl,c->unit_scope[unit_index(c,home)]); - c->ast=save_ast; - c->unit=save_unit; - c->types.unit_name=save_name; - if (save_unit) fe_diags_source(c->diags,save_unit->source,save_unit->size); - if (c->diags->errors>before) - fe_diag_note_src(c->diags,site,"instantiated here"); -} - -/* A call to a generic function: read the type arguments, check the value - arguments against the bound signature, then check the body once. */ -static FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym, - FeUnit *home) -{ - FeCheck *c=s->c; - FeNode *decl=sym->fn; - unsigned want=decl_type_param_count(decl); - FeType *args[FE_TYPE_PARAM_MAX]; - FeNode *arg=n->children; - unsigned i; - char key[FE_GENERIC_KEY_MAX]; - FeBindSave save; - FeType *result; - int fresh; - if (want>FE_TYPE_PARAM_MAX) { - err(c,n->loc,"too many generic parameters"); - return unknown(c); - } - for (i=0;iloc,"generic call requires explicit type arguments"); - return unknown(c); - } - args[i]=type_from_expr(s,arg,&ok); - if (!ok) { - err(c,arg->loc,"a comptime type argument must name a type"); - return unknown(c); - } - arg=arg->next; - } - instance_key(key,home->name,decl->text,args,want); - push_bindings(c,&save,decl,args,want); - result=check_call_args(s,n,sym,home->name,want); - fresh=instance_record(c,key,n->loc,decl,home,0); - pop_bindings(c,&save); - /* The call goes to this instance, not to the declaration it came from. */ - if (n->a) n->a->cname=(char *)instance_cname(c,key); - if (fresh>0) { - if (!instance_descend(c,n->loc)) return result; - push_bindings(c,&save,decl,args,want); - instantiate_body(c,home,decl,0,&save,n->loc); - pop_bindings(c,&save); - --c->instance_depth; - } - return result; -} - -/* `Type.method(...)` where Type is a generic instance and the method takes no - self parameter. */ -/* The unit a name belongs to, by name. */ -static FeUnit *unit_named(FeCheck *c, const char *name) -{ - unsigned u; - if (!name) return 0; - for (u=0;ubuild->count;++u) - if (!strcmp(c->build->units[u].name,name)) return &c->build->units[u]; - return 0; -} - -static FeType *check_static_method_call(FeCheckerState *s, FeNode *n, - FeType *owner, FeNode *method) -{ - FeCheck *c=s->c; - /* A method belongs to the unit that declared its type, not to whichever - unit happens to be calling it. */ - FeUnit *home=unit_named(c,owner ? owner->unit : 0); - FeBindSave save; - FeType *result; - char key[FE_GENERIC_KEY_MAX]; - FeType *self_args[1]; - int fresh; - FeSym fake; - if (!home) home=current_unit(c); - self_args[0]=owner; - instance_key(key,home->name,method->text,self_args,1); - memset(&fake,0,sizeof fake); - fake.name=method->text; - fake.cname=method->cname; - fake.fn=method; - fake.decl=method; - push_instance_bindings(c,&save,owner); - bind_self(c,owner); - result=check_call_args(s,n,&fake,home->name,0); - fresh=instance_record(c,key,n->loc,method,home,owner); - pop_bindings(c,&save); - if (n->a) n->a->cname=(char *)instance_cname(c,key); - if (fresh>0) { - if (!instance_descend(c,n->loc)) return result; - push_instance_bindings(c,&save,owner); - bind_self(c,owner); - instantiate_body(c,home,method,owner,&save,n->loc); - pop_bindings(c,&save); - --c->instance_depth; - } - return result; -} - -/* The body of a method on a generic instance, checked once per instance. */ -static void check_instance_method(FeCheckerState *s, FeType *owner, - FeNode *method, FeLoc site, FeNode *call) -{ - FeCheck *c=s->c; - /* A method belongs to the unit that declared its type, not to whichever - unit happens to be calling it. */ - FeUnit *home=unit_named(c,owner ? owner->unit : 0); - FeBindSave save; - char key[FE_GENERIC_KEY_MAX]; - FeType *self_args[1]; - self_args[0]=owner; - if (!home) home=current_unit(c); - instance_key(key,home->name,method->text,self_args,1); - { - FeBindSave probe; - int fresh; - push_instance_bindings(c,&probe,owner); - bind_self(c,owner); - fresh=instance_record(c,key,site,method,home,owner); - pop_bindings(c,&probe); - /* The call names this instance's copy of the method. */ - if (call && call->a) call->a->cname=(char *)instance_cname(c,key); - if (fresh<=0) return; - } - if (!instance_descend(c,site)) return; - push_instance_bindings(c,&save,owner); - bind_self(c,owner); - instantiate_body(c,home,method,owner,&save,site); - pop_bindings(c,&save); - --c->instance_depth; -} - -/* SPEC 4.7: `const Word = i32;` is another spelling of a type, not a value. - It has no initializer to check and no storage. */ -static int const_names_type(FeCheckerState *s, FeNode *n) -{ - FeType *t; - if (!n->b || n->b->kind!=FE_N_IDENT || !n->b->text) return 0; - if (n->a) return 0; - if (find_symbol(s->globals,n->b->text)) return 0; - t=fe_type_intern(&s->c->types,n->b->text); - return t && t->kind!=FE_TYPE_UNKNOWN; -} - -static FeNode *type_method(FeType *t, const char *name) -{ - FeNode *m; - if (!t || !t->decl_node || !name) return 0; - for (m=t->decl_node->children;m;m=m->next) - if (m->kind==FE_N_FN && m->text && strcmp(m->text,name)==0) return m; - return 0; -} - -static int method_is_static(const FeNode *method) -{ - FeNode *first=method && method->a ? method->a->children : 0; - return !first || !first->text || strcmp(first->text,"self")!=0; -} - -/* A call to a named function. `home` is the unit the signature was written in, - null when that is the unit being checked: parameter and return types have to - be read where they were written or a name would mean the caller's type. */ -/* `error.Name` is a member of the default error set. That set is open -- names - are collected across the build and numbered later, not declared -- so any - name is well formed here and the value's type is core.Error. */ -static 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. */ -static 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) { 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. */ -static 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;knext; - 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; -} - -static 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); - 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); -} - -static 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); -} - -static 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); -} - -static 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); -} - -static 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; -} - -static 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(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; - } - 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(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); -} - -static 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); -} - -static 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;ichildren;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;ic,n->loc,"non-exhaustive optional match"); - if (have) m7_restore_flow(merged,own_merged,borrow_merged,count); -} - -static 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); -} - -static 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_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); -} - -static 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;ic) 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;iret; - 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; - } -} - -static int m7_ast_reference_storage(FeNode *type) -{ - if (!type || !type->text) return 0; - if (strcmp(type->text,"&")==0 || strcmp(type->text,"&mut")==0 || - (strcmp(type->text,"[")==0 && !type->a) || - strcmp(type->text,"str")==0) - return 1; - if (strcmp(type->text,"?")==0) - return m7_ast_reference_storage(type->a); - if (strcmp(type->text,"^")==0) return 0; - return 0; -} - -static void m7_check_storage(FeCheck *c, FeNode *decl) -{ - FeNode *m; - if (!decl) return; - if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) { - /* This pass exists for the shapes the other one cannot see, such as a - reference behind an optional. A plain `&T` field is seen by both, so - leave that one to check_reference_storage below. */ - for (m=decl->children;m;m=m->next) - if (m->kind==FE_N_FIELD && m7_ast_reference_storage(m->a) && - !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"); - } - check_reference_storage(c,decl); -} - -static void m7_validate_error_decl(FeCheck *c, FeNode *decl) -{ - FeNode *a; - FeNode *b; - unsigned long code; - unsigned long other; - if (!decl || decl->kind!=FE_N_ERROR_DECL) return; - for (a=decl->children;a;a=a->next) { - if (!a->a || a->a->kind!=FE_N_LITERAL || !a->a->text) continue; - code=strtoul(a->a->text,0,0); - if (code==0UL) - err(c,a->loc,"error code 0 is reserved for success"); - for (b=decl->children;b && b!=a;b=b->next) { - if (a->text && b->text && strcmp(a->text,b->text)==0) { - err(c,a->loc,"duplicate error member name"); - break; - } - if (b->a && b->a->kind==FE_N_LITERAL && b->a->text) { - other=strtoul(b->a->text,0,0); - if (other==code) { - err(c,a->loc,"duplicate error numeric code"); - break; - } - } - } - } -} - -/* Everything a unit declares, before any body anywhere is looked at. */ -static void declare_unit(FeCheck *c) -{ - FeNode *n; - /* A generic declaration is not a type; only its instances are. */ - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_STRUCT && !decl_is_generic(n)) - fe_type_declare_struct(&c->types,n,(n->flags & FE_NODE_PACKED)!=0); - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) { - FeNode *m; - m7_check_storage(c,n); - if (n->kind==FE_N_ERROR_DECL) m7_validate_error_decl(c,n); - check_generic_params(c,n); - for (m=n->kind==FE_N_STRUCT ? n->children : 0;m;m=m->next) - if (m->kind==FE_N_FN) check_generic_params(c,m); - } - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_ENUM && !decl_is_generic(n)) - fe_type_declare_enum(&c->types,n); - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_ERROR_DECL) fe_type_declare_error(&c->types,n); - check_type_cycles(c); -} - -/* The unit's top-level names, in a scope of their own so that another unit - can look into it later without inheriting anything else. */ -static FeScope *declare_unit_scope(FeCheck *c, FeCheckerState *s) -{ - FeNode *n; - FeNode *m; - FeType *t; - FeScope *globals; - char method_name[128]; - globals=scope_new(s,0); - s->scope=globals; - s->globals=globals; - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) { - if (n->kind==FE_N_STRUCT) { - for (m=n->children;m;m=m->next) if (m->kind==FE_N_FN) { - sprintf(method_name,"%s_%s",n->text ? n->text : "Type", - m->text ? m->text : "method"); - m->cname=unit_cname(c,method_name); - } - } - if (n->kind==FE_N_GLOBAL || n->kind==FE_N_CONST) { - t=n->a ? node_type(c,n->a) : unknown(c); - add_symbol(s,globals,n->text,t,0,n->kind==FE_N_GLOBAL, - n->b!=0,unit_cname(c,n->text ? n->text : "global"),n); - } - } - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_FN) { - t=fe_type_intern(&c->types,""); - /* `extern "c"` means the linker already knows this name, so it is - not decorated with the unit it was declared in. */ - add_symbol(s,globals,n->text,t,n,0,1, - (n->flags & FE_NODE_EXTERN) && n->text ? n->text : - unit_cname(c,n->text ? n->text : "fn"),n); - } - return globals; -} - -static void check_unit_bodies(FeCheck *c, FeCheckerState *s) -{ - FeNode *n; - FeNode *m; - FeSym *sym; - FeType *t; - FeType *iv; - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_GLOBAL || n->kind==FE_N_CONST) { - sym=find_current(s->globals,n->text ? n->text : ""); - if (n->kind==FE_N_CONST && const_names_type(s,n)) continue; - if (n->b) { - iv=m7_check_expected(s,n->b,sym ? sym->type : 0); - if (sym && sym->type->kind==FE_TYPE_UNKNOWN) { - sym->type=iv; - n->sem_type=iv; - } else if (sym && !fe_type_equal(sym->type,iv) && - !m7_actual_compatible(sym->type,iv,n->b)) - err(c,n->loc,"global initializer type mismatch"); - } - } - /* A generic body means nothing until its parameters are bound, so it is - checked once per instance and not here. */ - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_FN && !decl_is_generic(n)) check_fn(c,n,s->globals); - for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) - if (n->kind==FE_N_STRUCT && !decl_is_generic(n)) { - t=fe_type_intern(&c->types,n->text); - for (m=n->children;m;m=m->next) - if (m->kind==FE_N_FN) check_method(c,m,s->globals,t); - } -} - -int fe_check_program(FeCheck *c) -{ - FeCheckerState s; - unsigned u; - s.c=c; - s.scope=0; - s.globals=0; - 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->arena); - for (u=0;ubuild->count;++u) { enter_unit(c,u); declare_unit(c); } - fe_type_layout_all(&c->types); - for (u=0;ubuild->count;++u) { - enter_unit(c,u); - c->unit_scope[u]=declare_unit_scope(c,&s); - } - for (u=0;ubuild->count;++u) { - enter_unit(c,u); - s.scope=c->unit_scope[u]; - s.globals=c->unit_scope[u]; - check_unit_bodies(c,&s); - } - fe_type_layout_all(&c->types); - return c->diags->errors==0; -} - diff --git a/fec/src/checkcal.c b/fec/src/checkcal.c new file mode 100644 index 0000000..11e1d50 --- /dev/null +++ b/fec/src/checkcal.c @@ -0,0 +1,978 @@ +#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) { 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;knext; + 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); + 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; +} + +FeType *check_expr(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; + } + 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); +} + +FeType *check_lvalue(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;ichildren;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;ic,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_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;ic) 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;iret; + 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; + } +} diff --git a/fec/src/checkexp.c b/fec/src/checkexp.c new file mode 100644 index 0000000..33f18c9 --- /dev/null +++ b/fec/src/checkexp.c @@ -0,0 +1,739 @@ +#include "checkpri.h" + +FeNode *find_const_node(FeCheck *c, const char *name) +{ + FeNode *n; + for (n=c->ast->root ? c->ast->root->children : 0; n; n=n->next) + if (n->kind==FE_N_CONST && n->text && name && strcmp(n->text,name)==0) + return n; + return 0; +} + + +const char *builtin_format(FeCheckerState *s, FeNode *fmt) +{ + FeNode *decl; + FeSym *sym; + if (fmt && fmt->kind==FE_N_LITERAL && fmt->text && fmt->text[0]=='"') + return fmt->text; + if (fmt && fmt->kind==FE_N_IDENT) { + sym=find_symbol(s->scope,fmt->text); + decl=sym && sym->decl && sym->decl->kind==FE_N_CONST ? + sym->decl : find_const_node(s->c,fmt->text); + if (decl && decl->b && decl->b->kind==FE_N_LITERAL && + decl->b->text && decl->b->text[0]=='"') { + if (!decl->a || format_is_slice_u8(fe_type_from_ast(&s->c->types,decl->a))) + return decl->b->text; + } + } + return 0; +} + +int format_is_slice_u8(FeType *t) +{ + return t && t->kind==FE_TYPE_SLICE && t->elem && + t->elem->kind==FE_TYPE_INT && strcmp(t->elem->name,"u8")==0; +} + +int format_is_writer_type(FeType *t) +{ + return t && t->kind==FE_TYPE_STRUCT && + (strcmp(t->name,"Writer")==0 || strcmp(t->name,"io.Writer")==0); +} + +int format_arg_ok(FeType *t, int verb) +{ + if (!t) return 0; + if (verb=='x') return fe_type_is_integer(t); + if (verb=='c') return t->kind==FE_TYPE_CHAR; + if (verb=='s') return format_is_slice_u8(t); + if (verb=='b') return t->kind==FE_TYPE_BOOL; + if (t->kind==FE_TYPE_INT || t->kind==FE_TYPE_BOOL || + t->kind==FE_TYPE_CHAR) return 1; + return format_is_slice_u8(t) || + (t->kind==FE_TYPE_ENUM && t->is_error); +} + +void check_format_call(FeCheckerState *s, FeNode *n) +{ + const char *fmt; + FeNode *fmt_node; + FeNode *arg; + FeNode *x; + FeType *t; + unsigned long i,j; + unsigned count=0; + unsigned argc=0; + unsigned offset=0; + int verb; + int bad=0; + int counted=0; + if (strcmp(n->text,"@fprint")==0) offset=1; + fmt_node=n->children; + if (offset) { + if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; } + t=check_expr(s,fmt_node); + if (!format_is_writer_type(t)) + err(s->c,fmt_node->loc,"@fprint requires io.Writer"); + fmt_node=fmt_node->next; + } + if (strcmp(n->text,"@sprint")==0) { + if (!fmt_node) { err(s->c,n->loc,"@sprint requires a buffer"); return; } + t=check_expr(s,fmt_node); + if (!format_is_slice_u8(t) || !t->ref_mut) + err(s->c,fmt_node->loc,"@sprint requires []mut u8 buffer"); + fmt_node=fmt_node->next; + } + fmt=builtin_format(s,fmt_node); + if (!fmt) { err(s->c,n->loc,"format must be a comptime string"); return; } + n->aux_text=(char *)fmt; + arg=fmt_node ? fmt_node->next : 0; + for (x=arg;x;x=x->next) { check_expr(s,x); ++argc; } + i=1; + while (fmt[i] && fmt[i]!='"') { + if (fmt[i]=='\\') { if (fmt[i+1]) ++i; ++i; continue; } + if (fmt[i]=='{' && fmt[i+1]=='{') { i+=2; continue; } + if (fmt[i]=='}' && fmt[i+1]=='}') { i+=2; continue; } + if (fmt[i]=='{') { + j=i+1; + while (fmt[j] && fmt[j]!='}') ++j; + if (!fmt[j]) { err(s->c,n->loc,"unterminated format placeholder"); bad=1; break; } + if (j==i+1) verb=' '; else if (j==i+2) verb=(unsigned char)fmt[i+1]; else verb='?'; + if (verb!=' ' && verb!='x' && verb!='c' && verb!='s' && verb!='b') { + err(s->c,n->loc,"unsupported format verb"); bad=1; + } + if (!arg) { + err(s->c,n->loc,"format argument count mismatch"); + bad=1; counted=1; + } + else { + t=arg->sem_type; + if (verb==' ' && t && t->kind==FE_TYPE_ENUM && t->is_error) verb='s'; + if (!format_arg_ok(t,verb)) { err(s->c,arg->loc,"no fmt writer for argument type"); bad=1; } + arg=arg->next; + } + ++count; i=j+1; continue; + } + if (fmt[i]=='}') { err(s->c,n->loc,"unmatched '}' in format"); bad=1; } + ++i; + } + /* Running out of arguments mid-string already said this. Saying it again + once the whole string has been walked adds nothing. */ + if (count!=argc && !counted) { err(s->c,n->loc,"format argument count mismatch"); bad=1; } + (void)bad; +} + +int is_format_builtin(const char *name) +{ + return name && (strcmp(name,"@print")==0 || strcmp(name,"@fprint")==0 || + strcmp(name,"@sprint")==0); +} + +int lvalue_writable(FeCheckerState *s, FeNode *n) +{ + FeSym *sym; + FeType *t; + if (!n) return 0; + if (n->kind == FE_N_IDENT) { + sym=find_symbol(s->scope,n->text ? n->text : ""); + return sym ? sym->mutable : 0; + } + if (n->kind == FE_N_MEMBER) { + t=n->a ? n->a->sem_type : 0; + if (t && t->kind==FE_TYPE_REF && n->b && n->b->text && + strcmp(n->b->text,"^")==0) return t->ref_mut; + return lvalue_writable(s,n->a); + } + if (n->kind == FE_N_INDEX) return lvalue_writable(s,n->a); + return 0; +} + +int has_field(FeNode *list, const char *name) +{ + FeNode *f; + for (f=list; f; f=f->next) + if (f->text && name && strcmp(f->text,name)==0) return 1; + return 0; +} + +/* A field of a type declared elsewhere is reachable only with `pub`. Inside + the declaring unit every field is reachable, `pub` or not. */ +int field_is_visible(FeCheckerState *s, const FeType *t, + const FeFieldType *field) +{ + if (!t || !t->unit) return 1; + if (s->c->types.unit_name && + strcmp(t->unit,s->c->types.unit_name)==0) return 1; + return field && field->ast_node && + (field->ast_node->flags & FE_NODE_PUB)!=0; +} + +/* The field list of a struct literal, once the type is known. Reached from + both `Type{...}` and `binding.Type{...}`. */ +FeType *check_struct_fields(FeCheckerState *s, FeNode *n, FeType *t) +{ + FeFieldType *field; + FeNode *f; + FeType *v; + unsigned i; + for(f=n->children;f;f=f->next) if(f->kind==FE_N_FIELD) { + if(has_field(f->next,f->text)) { err(s->c,f->loc,"duplicate struct field"); } + field=fe_type_field(t,f->text); + if(!field) { err(s->c,f->loc,"invalid struct field"); continue; } + if(!field_is_visible(s,t,field)) { + err(s->c,f->loc,"field is private to its unit"); + continue; + } + v=check_expr(s,f->a); + mark_moved(s,f->a,v); + if(!compatible(field->type,v,f->a) && v->kind!=FE_TYPE_UNKNOWN) err(s->c,f->loc,"struct field type mismatch"); + } + for(i=0;ifield_count;i++) if(!has_field(n->children,t->fields[i].name)) err(s->c,n->loc,"missing struct field"); + n->sem_type=t; return t; +} + +FeType *check_struct_init(FeCheckerState *s, FeNode *n) +{ + FeType *t; + FeFieldType *field; + FeNode *f; + FeType *v; + FeType *et; + FeVariantType *variant; + if (n->a && n->a->kind == FE_N_MEMBER) { + FeUnit *home=binding_unit(s,n->a->a); + if (home) { + /* `binding.Type{...}` names a type in another unit. */ + const char *want=n->a->b && n->a->b->text ? n->a->b->text : ""; + FeNode *decl=unit_type_decl(s->c,home,want); + t=unit_type(s->c,home,want); + if (!t || !decl) { err(s->c,n->a->loc,"unknown name"); return unknown(s->c); } + if (!decl_is_public(decl)) { + err(s->c,n->a->loc,"type is private to its unit"); + return unknown(s->c); + } + if (t->kind!=FE_TYPE_STRUCT) { + err(s->c,n->loc,"unknown struct type"); + return unknown(s->c); + } + return check_struct_fields(s,n,t); + } + et=check_expr(s,n->a->a); + variant=et && et->kind==FE_TYPE_ENUM ? + fe_type_variant(et,n->a->b ? n->a->b->text : "") : 0; + if (!variant) { err(s->c,n->loc,"invalid enum variant"); return unknown(s->c); } + if (variant->field_count != 0) { + for (f=n->children; f; f=f->next) { + if (f->kind != FE_N_FIELD) continue; + field=0; + if (variant->fields) { + unsigned i; + for(i=0;ifield_count;i++) if(strcmp(variant->fields[i].name,f->text)==0) field=&variant->fields[i]; + } + if (!field) { err(s->c,f->loc,"invalid enum payload field"); continue; } + v=check_expr(s,f->a); + mark_moved(s,f->a,v); + if (!compatible(field->type,v,f->a) && v->kind!=FE_TYPE_UNKNOWN) err(s->c,f->loc,"enum payload type mismatch"); + } + } else if (n->children) err(s->c,n->loc,"empty enum variant cannot have payload"); + n->sem_type=et; return et; + } + t=fe_type_intern(&s->c->types,n->text ? n->text : ""); + if (!t || t->kind!=FE_TYPE_STRUCT) { err(s->c,n->loc,"unknown struct type"); return unknown(s->c); } + return check_struct_fields(s,n,t); +} + +FeType *check_array_init(FeCheckerState *s, FeNode *n) +{ + FeNode *x; FeType *elem=0; FeType *v; unsigned long count=0; + for(x=n->children;x;x=x->next) { v=check_expr(s,x); mark_moved(s,x,v); if(!elem) elem=v; else if(!compatible(elem,v,x)&&v->kind!=FE_TYPE_UNKNOWN) err(s->c,x->loc,"array element type mismatch"); ++count; } + if(!elem) elem=unknown(s->c); + n->sem_type=fe_type_array(&s->c->types,count,elem); return n->sem_type; +} + +int array_slice_lvalue(FeNode *n) +{ + return n && (n->kind==FE_N_IDENT || n->kind==FE_N_MEMBER || + n->kind==FE_N_INDEX); +} + +FeType *check_index(FeCheckerState *s, FeNode *n) +{ + FeType *base=check_expr(s,n->a); FeType *idx; FeType *elem; + if(!fe_type_is_indexable(base)) { err(s->c,n->loc,"indexing requires an array or slice"); return unknown(s->c); } + if(n->b) { idx=check_expr(s,n->b); if(known(idx)&&!fe_type_is_integer(idx)) err(s->c,n->loc,"index must be an integer"); } + if(n->c || !n->b) { + if (base->kind==FE_TYPE_ARRAY && !array_slice_lvalue(n->a)) + err(s->c,n->loc,"array slicing requires a stable lvalue"); + if(n->c) { + idx=check_expr(s,n->c); + if(known(idx)&&!fe_type_is_integer(idx)) + err(s->c,n->loc,"slice bound must be an integer"); + } + elem=base->elem; + n->sem_type=(base->kind==FE_TYPE_SLICE ? base->ref_mut : + lvalue_writable(s,n->a)) ? + fe_type_mut_slice(&s->c->types,elem) : + fe_type_slice(&s->c->types,elem); + return n->sem_type; + } + n->sem_type=base->elem; return n->sem_type; +} + +FeType *check_identifier(FeCheckerState *s, FeNode *n) +{ + FeSym *sym; + sym = find_symbol(s->scope, n->text ? n->text : ""); + if (!sym) { + FeType *named=fe_type_intern(&s->c->types,n->text ? n->text : ""); + if(named->kind==FE_TYPE_STRUCT || named->kind==FE_TYPE_ENUM) { n->sem_type=named; return named; } + if(named->kind!=FE_TYPE_UNKNOWN) { + err(s->c, n->loc, "a type is not a value here"); + return unknown(s->c); + } + err(s->c, n->loc, "unknown name"); + return unknown(s->c); + } + n->cname = sym->cname; + n->sem_type = sym->type; + if (!sym->fn) { + fe_own_access(s->c->diags,&sym->own,FE_OWN_READ,n->loc); + sym->moved=sym->own.move; + } + return sym->type; +} + +FeType *check_expr_core(FeCheckerState *s, FeNode *n) +{ + FeCheck *c = s->c; + FeType *a; + FeType *b; + FeSym *sym; + FeNode *x; + FeNode *param; + FeNode *arg; + FeType *et; + FeFieldType *field; + FeVariantType *variant; + const char *op; + if (!n) return unknown(c); + if (n->kind == FE_N_IDENT) + return check_identifier(s, n); + if (n->kind == FE_N_LITERAL) { + if (!n->text) return unknown(c); + if (strcmp(n->text, "true") == 0 || strcmp(n->text, "false") == 0) + a = fe_type_intern(&c->types, "bool"); + else if (n->text[0] == '\'') + a = fe_type_intern(&c->types, "char"); + else if (n->text[0] == '"') + a = fe_type_intern(&c->types, "str"); + else + a = fe_type_intern(&c->types, "i32"); + n->sem_type = a; + return a; + } + 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); + if (n->kind == FE_N_INDEX) return check_index(s,n); + if (n->kind == FE_N_MATCH) { check_match(s,n); n->sem_type=unknown(c); return n->sem_type; } + if (n->kind == FE_N_UNARY) { + a = check_expr(s, n->a); + op = n->text ? n->text : ""; + if (strcmp(op, "not") == 0) { + if (known(a) && a->kind != FE_TYPE_BOOL) + err(c, n->loc, "'not' requires bool"); + a = fe_type_intern(&c->types, "bool"); + } else if (strcmp(op, "-") == 0) { + if (known(a) && !fe_type_is_integer(a)) + err(c, n->loc, "unary '-' requires integer"); + } else if (strcmp(op, "try") == 0) { + /* SPEC 6.4: try is only allowed inside a function returning an error + union. Checked on the expression rather than on the statement so + that it also covers `var x = try e;` and `x = try e;`, which the + statement-level check walked straight past. */ + if (!s->ret || s->ret->kind != FE_TYPE_ERROR_UNION) + err(c,n->loc,"try requires an enclosing error result"); + if (a && a->kind==FE_TYPE_ERROR_UNION) + a=a->error_value; + else { + err(c,n->loc,"try requires an error result"); + 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; + return a; + } + if (n->kind == FE_N_TYPE && n->text && strcmp(n->text, "as") == 0) { + a = check_expr(s, n->a); + b = node_type(c, n->b); + if (b->kind == FE_TYPE_VOID) + err(c, n->loc, "cast target cannot be void"); + else if (known(a) && known(b) && !explicit_castable(a,b)) + err(c, n->loc, "'as' requires integer or char types"); + n->sem_type = b; + return b; + } + if (n->kind == FE_N_BINARY) { + a = check_expr(s, n->a); + b = check_expr(s, n->b); + op = n->text ? n->text : ""; + 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(c, n->loc, "logical operator requires bool operands"); + a = fe_type_intern(&c->types, "bool"); + } else 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) && + !compatible(a, b, n->b) && !compatible(b, a, n->a)) + err(c, n->loc, "comparison operands have different types"); + else if (strcmp(op,"==")!=0 && strcmp(op,"!=")!=0 && + ((known(a) && !ordered_type(a)) || + (known(b) && !ordered_type(b)))) + err(c, n->loc, "ordering requires integer or char operands"); + a = fe_type_intern(&c->types, "bool"); + } else { + if ((known(a) && !fe_type_is_integer(a)) || + (known(b) && !fe_type_is_integer(b)) || + (known(a) && known(b) && !fe_type_equal(a, b) && + !compatible(a, b, n->b) && !compatible(b, a, n->a))) + err(c, n->loc, + "arithmetic operands must have the same integer type"); + } + n->sem_type = a; + return a; + } + if (n->kind == FE_N_CALL) { + if (n->a && n->a->kind==FE_N_MEMBER && n->a->b && n->a->b->text && + strcmp(n->a->b->text,"drop")==0) { + err(c,n->loc,"drop may only be invoked by scope cleanup"); + return unknown(c); + } + 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) { + FeNode *arg=n->children; + 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) { + if (!arg || arg->next) + err(c,n->loc,"mem.create requires exactly one value"); + a=arg ? check_expr(s,arg) : unknown(c); + if(arg) mark_moved(s,arg,a); + a=fe_type_owned(&c->types,a); + n->sem_type=fe_type_error_union(&c->types,a); + return n->sem_type; + } + if (strcmp(n->a->b->text,"alloc_slice")==0) { + FeNode *count=arg ? arg->next : 0; + FeType *item; + if(!arg || arg->kind!=FE_N_IDENT || !count || count->next) + err(c,n->loc,"mem.alloc_slice requires a type and length"); + item=arg && arg->kind==FE_N_IDENT ? + fe_type_intern(&c->types,arg->text) : unknown(c); + b=count ? check_expr(s,count) : unknown(c); + if(known(b) && !fe_type_is_integer(b)) + err(c,count->loc,"slice length must be an integer"); + /* Freshly allocated storage is owned outright, so it is + writable: there is nobody else to disturb. */ + a=fe_type_owned(&c->types,fe_type_mut_slice(&c->types,item)); + n->sem_type=fe_type_error_union(&c->types,a); + return n->sem_type; + } + if (strcmp(n->a->b->text,"replace")==0) { + FeNode *value=arg ? arg->next : 0; + if(!arg || !value || value->next) + err(c,n->loc,"mem.replace requires destination and value"); + a=arg ? check_expr(s,arg) : unknown(c); + 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"); + b=value ? check_expr(s,value) : unknown(c); + if(a && a->kind==FE_TYPE_REF && !compatible(a->elem,b,value)) + err(c,value->loc,"mem.replace value type mismatch"); + if(value) mark_moved(s,value,b); + n->sem_type=a && a->kind==FE_TYPE_REF ? a->elem : unknown(c); + fe_type_require_replace(&c->types,n->sem_type); + 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,"io")==0 && n->a->b && n->a->b->text && + strcmp(n->a->b->text,"null_writer")==0) { + FeNode *arg=n->children; + if (arg) err(c,n->loc,"io.null_writer takes no arguments"); + n->sem_type=fe_type_intern(&c->types,"io.Writer"); + return n->sem_type; + } + if (n->text && is_format_builtin(n->text)) { + check_format_call(s,n); + if (strcmp(n->text,"@print")==0) + n->sem_type=fe_type_intern(&c->types,"void"); + else if (strcmp(n->text,"@sprint")==0) + n->sem_type=fe_type_intern(&c->types,"usize"); + else + n->sem_type=fe_type_error_union(&c->types,fe_type_intern(&c->types,"void")); + return n->sem_type; + } + if (!n->a && n->text && (strcmp(n->text,"@size_of")==0 || strcmp(n->text,"@align_of")==0)) { + FeNode *type_arg=n->children; + FeType *target=type_arg && type_arg->kind==FE_N_IDENT ? fe_type_intern(&c->types,type_arg->text) : unknown(c); + if(!target || !known(target)) err(c,n->loc,"size/align requires a known type"); + n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type; + } + if (n->a && n->a->kind == FE_N_MEMBER) { + FeNode *method; + FeNode *self_param; + FeUnit *home=binding_unit(s,n->a->a); + if (home) { + const char *want=n->a->b && n->a->b->text ? n->a->b->text : ""; + FeSym *fsym=unit_member(c,home,want); + if (!fsym) { + err(c,n->a->loc,"unknown name"); + for (x=n->children;x;x=x->next) check_expr(s,x); + return unknown(c); + } + if (!decl_is_public(fsym->decl)) { + err(c,n->a->loc,"name is private to its unit"); + for (x=n->children;x;x=x->next) check_expr(s,x); + return unknown(c); + } + if (decl_is_generic(fsym->fn)) + return check_generic_call(s,n,fsym,home); + return check_call_args(s,n,fsym,home->name,0); + } + { + int names_type=0; + FeType *owner_type=type_from_expr(s,n->a->a,&names_type); + if (names_type && owner_type && + owner_type->kind==FE_TYPE_STRUCT) { + FeNode *m=type_method(owner_type, + n->a->b ? n->a->b->text : ""); + if (!m) { err(c,n->a->loc,"unknown method"); return unknown(c); } + if (!method_is_static(m)) { + err(c,n->loc,"method requires a receiver"); + return unknown(c); + } + return check_static_method_call(s,n,owner_type,m); + } + } + et=check_expr(s,n->a->a); + /* A method can be reached through a reference or an owner as well + as through the value itself. */ + if (et && (et->kind==FE_TYPE_REF || et->kind==FE_TYPE_OWNED) && + et->elem && et->elem->kind==FE_TYPE_STRUCT && + find_method(c,et->elem,n->a->b ? n->a->b->text : "")) + et=et->elem; + method=et && et->kind==FE_TYPE_STRUCT ? + find_method(c,et,n->a->b ? n->a->b->text : "") : 0; + if(method) { + FeBindSave msave; + int bound=0; + self_param=method->a ? method->a->children : 0; + if(!self_param) { + err(c,n->loc,"method requires self parameter"); + return unknown(c); + } + /* A method of a generic instance reads its signature with that + instance's arguments bound. */ + if (et->bind_count) { + push_instance_bindings(c,&msave,et); + bind_self(c,et); + bound=1; + } + a=method_type(c,self_param->a,et); + if(a->kind==FE_TYPE_REF && a->ref_mut && + !lvalue_writable(s,n->a->a)) + err(c,n->loc,"mutable method requires a mutable receiver"); + if(a->kind!=FE_TYPE_REF) mark_moved(s,n->a->a,et); + param=self_param->next; + arg=n->children; + while(param && arg) { + a=check_expr(s,arg); + b=method_type(c,param->a,et); + if(!compatible(b,a,arg) && a->kind!=FE_TYPE_UNKNOWN) + err(c,arg->loc,"method argument type mismatch"); + mark_moved(s,arg,a); + param=param->next; + arg=arg->next; + } + if(param || arg) err(c,n->loc,"wrong number of method arguments"); + n->sem_decl=method; + n->sem_type=method->b ? method_type(c,method->b,et) : + fe_type_intern(&c->types,"void"); + if (bound) { + pop_bindings(c,&msave); + check_instance_method(s,et,method,n->loc,n); + } + 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; + if (!variant) { err(c,n->loc,"invalid enum variant constructor"); return unknown(c); } + if (variant->field_count==1 && arg) { + FeType *av=check_expr(s,arg); + if(!compatible(variant->fields[0].type,av,arg)&&av->kind!=FE_TYPE_UNKNOWN) err(c,arg->loc,"enum payload type mismatch"); + } else if (variant->field_count != 0 || arg) err(c,n->loc,"wrong enum payload arity"); + n->sem_type=et; return et; + } + if (n->a && n->a->kind == FE_N_IDENT) { + sym = find_symbol(s->scope, n->a->text ? n->a->text : ""); + if (!sym) { + err(c, n->loc, "unknown function"); + return unknown(c); + } + if (decl_is_generic(sym->fn)) + return check_generic_call(s,n,sym,current_unit(c)); + return check_call_args(s, n, sym, 0, 0); + } + for (x = n->children; x; x = x->next) check_expr(s, x); + return unknown(c); + } + if (n->kind == FE_N_MEMBER) { + if (is_error_set_member(s,n)) { + n->sem_type=fe_type_intern(&c->types,"core.Error"); + return n->sem_type; + } + if (n->a && n->a->kind==FE_N_IDENT && n->a->text && + strcmp(n->a->text,"io")==0 && n->b && n->b->text && + (strcmp(n->b->text,"stdout")==0 || + strcmp(n->b->text,"stderr")==0)) { + n->sem_type=fe_type_intern(&c->types,"io.Writer"); + return n->sem_type; + } + a=check_expr(s,n->a); + if (a->kind == FE_TYPE_REF && n->b && n->b->text && + strcmp(n->b->text,"^")==0) { + n->sem_type=a->elem; + return a->elem; + } + if(a->kind==FE_TYPE_REF && a->elem && + a->elem->kind==FE_TYPE_STRUCT) { + field=fe_type_field(a->elem,n->b ? n->b->text : ""); + if(!field) { err(c,n->loc,"unknown struct field"); return unknown(c); } + n->sem_type=field->type; + return field->type; + } + if (a->kind == FE_TYPE_OWNED && n->b && n->b->text && + strcmp(n->b->text,"^")==0) { + n->sem_type=a->elem; + return a->elem; + } + if(a->kind==FE_TYPE_STRUCT) { + field=fe_type_field(a,n->b ? n->b->text : ""); + if(!field) { err(c,n->loc,"unknown struct field"); return unknown(c); } + n->sem_type=field->type; return field->type; + } + if(a->kind==FE_TYPE_ENUM) { + if(!fe_type_variant(a,n->b ? n->b->text : "")) err(c,n->loc,"unknown enum variant"); + n->sem_type=a; return a; + } + if((a->kind==FE_TYPE_SLICE || a->kind==FE_TYPE_STR) && n->b && + strcmp(n->b->text,"n")==0) { + n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type; + } + return unknown(c); + } + return unknown(c); +} + +/* `base_in` is the already-checked type of a member expression's base. The M7 + lvalue path looks at that base before delegating here, and checking it a + second time reports any ownership violation on it a second time too. */ +FeType *check_lvalue_core(FeCheckerState *s, FeNode *n, int read, + FeType *base_in) +{ + FeSym *sym; + FeType *base; + FeFieldType *field; + if (n && n->kind == FE_N_IDENT) { + sym = find_symbol(s->scope, n->text ? n->text : ""); + if (!sym) { + err(s->c, n->loc, "unknown name"); + return unknown(s->c); + } + if (sym->fn) { + err(s->c, n->loc, "function is not assignable"); + return unknown(s->c); + } + if (!sym->mutable) + err(s->c, n->loc, "cannot assign to immutable let"); + n->cname = sym->cname; + n->sem_type = sym->type; + 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) { + base=base_in ? base_in : check_expr(s,n->a); + 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; + } + if(base && base->kind==FE_TYPE_REF && base->elem && + base->elem->kind==FE_TYPE_STRUCT) { + if(!base->ref_mut) + err(s->c,n->loc,"cannot write through shared reference"); + field=fe_type_field(base->elem,n->b ? 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; + } + if (base && base->kind == FE_TYPE_OWNED && n->b && n->b->text && + strcmp(n->b->text,"^")==0) { + n->sem_type=base->elem; + return base->elem; + } + if (!lvalue_writable(s,n->a)) + err(s->c,n->loc,"cannot assign through immutable value"); + field=base && base->kind==FE_TYPE_STRUCT ? fe_type_field(base,n->b ? n->b->text : "") : 0; + 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; + } + if (n && n->kind == FE_N_INDEX) { + base=check_index(s,n); + if (n->a && n->a->sem_type && + n->a->sem_type->kind == FE_TYPE_SLICE && + !n->a->sem_type->ref_mut) + err(s->c,n->loc,"cannot write through shared slice"); + else if (n->a && n->a->sem_type && + n->a->sem_type->kind != FE_TYPE_SLICE && + !lvalue_writable(s,n->a)) + err(s->c,n->loc,"cannot assign through immutable value"); + return base; + } + if (n) err(s->c, n->loc, "assignment requires a variable"); + return unknown(s->c); +} + +int compound_operator(const char *op) +{ + return op && strcmp(op, "=") != 0; +} diff --git a/fec/src/checkgen.c b/fec/src/checkgen.c new file mode 100644 index 0000000..9be5059 --- /dev/null +++ b/fec/src/checkgen.c @@ -0,0 +1,618 @@ +#include "checkpri.h" + +unsigned decl_type_param_count(const FeNode *decl) +{ + FeNode *p; + unsigned n=0; + if (!decl) return 0; + if (decl->kind==FE_N_FN) { + for (p=decl->a?decl->a->children:0;p;p=p->next) + if (p->flags & FE_NODE_COMPTIME) ++n; + return n; + } + if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) + for (p=decl->a?decl->a->children:0;p;p=p->next) ++n; + return n; +} + +FeNode *decl_type_param(const FeNode *decl, unsigned i) +{ + FeNode *p; + unsigned n=0; + if (!decl) return 0; + if (decl->kind==FE_N_FN) { + for (p=decl->a?decl->a->children:0;p;p=p->next) + if (p->flags & FE_NODE_COMPTIME) { if (n==i) return p; ++n; } + return 0; + } + for (p=decl->a?decl->a->children:0;p;p=p->next) { if (n==i) return p; ++n; } + return 0; +} + +int decl_is_generic(const FeNode *decl) +{ + return decl_type_param_count(decl)!=0; +} + +/* SPEC 9: v0.1 has comptime type parameters and no other kind. */ +void check_generic_params(FeCheck *c, FeNode *decl) +{ + FeNode *p; + if (!decl || decl->kind!=FE_N_FN) return; + for (p=decl->a?decl->a->children:0;p;p=p->next) { + if (!(p->flags & FE_NODE_COMPTIME)) continue; + if (!p->a || p->a->kind!=FE_N_TYPE || !p->a->text || + strcmp(p->a->text,"type")!=0) + err(c,p->loc,"a comptime parameter must be a type parameter"); + } +} + +void push_bindings(FeCheck *c, FeBindSave *save, FeNode *decl, + FeType **args, unsigned count) +{ + unsigned i; + save->count=c->types.param_count; + for (i=0;iparams[i]=c->types.params[i]; + c->types.param_count=0; + for (i=0;itypes.params[i].name=p && p->text ? p->text : "?"; + c->types.params[i].type=args[i]; + ++c->types.param_count; + } +} + +/* Restore the bindings recorded on an instance, so a method sees exactly the + environment its type was built with. */ +void push_instance_bindings(FeCheck *c, FeBindSave *save, FeType *t) +{ + unsigned i; + save->count=c->types.param_count; + for (i=0;iparams[i]=c->types.params[i]; + c->types.param_count=0; + for (i=0;ibind_count && itypes.params[c->types.param_count++]=t->binds[i]; +} + +void bind_self(FeCheck *c, FeType *owner) +{ + if (c->types.param_count>=FE_TYPE_PARAM_MAX) return; + c->types.params[c->types.param_count].name="Self"; + c->types.params[c->types.param_count].type=owner; + ++c->types.param_count; +} + +void pop_bindings(FeCheck *c, const FeBindSave *save) +{ + unsigned i; + for (i=0;itypes.params[i]=save->params[i]; + c->types.param_count=save->count; +} + +/* `unit.Name(arg,arg)` -- the canonical identity of one instance. + Nesting makes the readable spelling grow without bound, and a spelling that + got cut off would make two different instances look like the same one, so + past a length the arguments are written as serial numbers instead. Those are + unique, so identity stays exact even where the spelling stops being + readable. */ + +void instance_key(char *out, const char *unit, const char *name, + FeType **args, unsigned count) +{ + unsigned i; + unsigned long n=0; + unsigned long cap=(unsigned long)FE_GENERIC_NAME_READABLE; + const char *p; + char number[24]; + int readable=1; + for (p=unit?unit:"";*p;++p) { if (nname[0] ? args[i]->name : "?";*p;++p) { + if (nserial : 0U); + for (p=number;*p && ninstance_count;++i) + if (!strcmp(c->instances[i].key,key)) return c->instances[i].cname; + return 0; +} + +int instance_known(FeCheck *c, const char *key) +{ + unsigned i; + for (i=0;iinstance_count;++i) + if (strcmp(c->instances[i].key,key)==0) return 1; + return 0; +} + +int instance_record(FeCheck *c, const char *key, FeLoc loc, + FeNode *decl, FeUnit *home, FeType *owner) +{ + FeInstance *inst; + unsigned i; + if (instance_known(c,key)) return 0; + if (c->instance_count>=FE_GENERIC_INSTANCE_MAX) { + err(c,loc,"too many generic instances"); + return -1; + } + inst=&c->instances[c->instance_count]; + strcpy(inst->key,key); + inst->decl=decl; + inst->home=home ? home->name : 0; + inst->owner=owner; + inst->cname=unit_cname(c,key); + /* The bindings in force right now are the ones this instance was built + with, and lowering has to see exactly those again. */ + inst->bind_count=c->types.param_count; + for (i=0;itypes.param_count && ibinds[i]=c->types.params[i]; + ++c->instance_count; + return 1; +} + +/* One step further down a chain of instantiations. Chains that keep producing + new instances are the ones that never end, so the limit counts nesting. */ +int instance_descend(FeCheck *c, FeLoc loc) +{ + if (c->instance_depth>=FE_GENERIC_DEPTH_MAX) { + err(c,loc,"generic instantiation depth exceeded"); + return 0; + } + ++c->instance_depth; + return 1; +} + +FeUnit *current_unit(FeCheck *c) +{ + unsigned u; + for (u=0;ubuild->count;++u) + if (strcmp(c->build->units[u].name,c->types.unit_name)==0) + return &c->build->units[u]; + return c->unit; +} + +/* Build `Box(i32)`: the declaration's fields with the parameters bound, under + a name that records which arguments made it. */ +FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl, + const char *key, FeType **args, + unsigned count) +{ + FeBindSave save; + FeType *t; + FeNode *f; + unsigned fields=0; + unsigned i=0; + t=fe_type_intern_unit(&c->types,home->name,key); + if (!t || t->kind!=FE_TYPE_UNKNOWN) return t; + t->kind=FE_TYPE_STRUCT; + t->packed=(decl->flags & FE_NODE_PACKED)!=0; + t->decl_node=decl; + t->bind_count=0; + for (i=0;ibinds[t->bind_count].name=p && p->text ? p->text : "?"; + t->binds[t->bind_count].type=args[i]; + ++t->bind_count; + } + t->cname=unit_cname(c,key); + for (f=decl->children;f;f=f->next) + if (f->kind==FE_N_FN && f->text && strcmp(f->text,"drop")==0) + t->has_drop=1; + for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) ++fields; + t->field_count=fields; + if (fields) { + t->fields=(FeFieldType *)fe_arena_alloc(&c->arena, + fields*sizeof(FeFieldType)); + if (!t->fields) { t->field_count=0; return t; } + push_instance_bindings(c,&save,t); + bind_self(c,t); + i=0; + for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) { + t->fields[i].name=f->text; + t->fields[i].type=node_type(c,f->a); + t->fields[i].offset=0; + t->fields[i].ast_node=f; + ++i; + } + pop_bindings(c,&save); + } + fe_type_layout_all(&c->types); + /* A type that says how to let go of itself needs that method to exist for + every instance, whether or not anyone calls it by name: scope cleanup + will. */ + { + FeNode *release; + for (release=decl->children;release;release=release->next) + if (release->kind==FE_N_FN && release->text && + !strcmp(release->text,"drop") && release->c) { + FeCheckerState s; + memset(&s,0,sizeof s); + s.c=c; + s.scope=c->unit_scope[unit_index(c,home)]; + s.globals=s.scope; + check_instance_method(&s,t,release,decl->loc,0); + break; + } + } + return t; +} + +FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name, + FeType **args, unsigned count, FeLoc loc) +{ + FeNode *decl=unit_type_decl(c,home,name); + char key[FE_GENERIC_KEY_MAX]; + if (!decl || !decl_is_generic(decl)) { + err(c,loc,"type does not take generic arguments"); + return unknown(c); + } + if (decl->kind!=FE_N_STRUCT) { + err(c,loc,"only a generic struct can be instantiated"); + return unknown(c); + } + if (count!=decl_type_param_count(decl)) { + err(c,loc,"wrong number of generic arguments"); + return unknown(c); + } + instance_key(key,home->name,name,args,count); + if (instance_record(c,key,loc,decl,home,0)<0) return unknown(c); + return build_struct_instance(c,home,decl,key,args,count); +} + +/* `Name(args...)` written in type position. */ +FeType *instantiate_type_node(void *owner, const FeNode *node) +{ + FeCheck *c=(FeCheck *)owner; + FeUnit *home=current_unit(c); + const char *name=node->text; + FeNode *arg; + FeType *args[FE_TYPE_PARAM_MAX]; + unsigned count=0; + FeType *result; + /* `binding.Name` names a type in another unit. The binding is not itself a + type, so it has to be peeled off before anything is looked up. */ + if (node->a && node->a->kind==FE_N_IDENT && node->a->text && c->build && + c->unit) { + FeUnit *bound=fe_build_binding(c->build,c->unit,node->text); + if (bound) { home=bound; name=node->a->text; } + } + if (!node->children) { + FeNode *decl=unit_type_decl(c,home,name ? name : ""); + if (decl && decl_is_generic(decl)) { + /* A generic declaration is not a type until it has arguments. */ + err(c,node->loc,"generic type requires type arguments"); + return unknown(c); + } + if (name!=node->text) { + FeType *there=unit_type(c,home,name); + if (there) return there; + } + return fe_type_intern(&c->types,name); + } + if (!instance_descend(c,node->loc)) return unknown(c); + for (arg=node->children;arg;arg=arg->next) { + if (counttypes,arg); + ++count; + } + if (count>FE_TYPE_PARAM_MAX) { + err(c,node->loc,"wrong number of generic arguments"); + --c->instance_depth; + return unknown(c); + } + result=instantiate_struct(c,home,name ? name : "",args,count, + node->loc); + --c->instance_depth; + return result; +} + +/* A type written where an expression is: `i32`, `Box(i32)`. Only a comptime + argument position accepts one. */ +FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok) +{ + FeCheck *c=s->c; + FeType *t; + unsigned i; + *ok=0; + if (!n) return unknown(c); + if (n->kind==FE_N_IDENT && n->text) { + for (i=0;itypes.param_count;++i) + if (strcmp(c->types.params[i].name,n->text)==0) { + *ok=1; + return c->types.params[i].type; + } + if (find_symbol(s->scope,n->text)) { + /* A const alias of a type is that type (SPEC 4.7). */ + FeSym *sym=find_symbol(s->scope,n->text); + if (sym && sym->decl && sym->decl->kind==FE_N_CONST && + sym->decl->b && sym->decl->b->kind==FE_N_IDENT) + return type_from_expr(s,sym->decl->b,ok); + return unknown(c); + } + t=fe_type_intern(&c->types,n->text); + if (t && t->kind!=FE_TYPE_UNKNOWN) { *ok=1; return t; } + return unknown(c); + } + if (n->kind==FE_N_CALL && n->a && + (n->a->kind==FE_N_IDENT || + (n->a->kind==FE_N_MEMBER && n->a->a && + n->a->a->kind==FE_N_IDENT && n->a->b && n->a->b->text))) { + FeType *args[FE_TYPE_PARAM_MAX]; + unsigned count=0; + FeNode *arg; + FeType *result; + FeUnit *home=current_unit(c); + const char *want; + /* `Name(args)` here, `binding.Name(args)` when the declaration is in + another unit. */ + if (n->a->kind==FE_N_MEMBER) { + FeUnit *bound=binding_unit(s,n->a->a); + if (!bound) return unknown(c); + home=bound; + want=n->a->b->text; + } else { + want=n->a->text; + } + if (!want || !unit_type_decl(c,home,want)) return unknown(c); + if (!instance_descend(c,n->loc)) { *ok=1; return unknown(c); } + for (arg=n->children;arg;arg=arg->next) { + int inner=0; + if (countinstance_depth; return unknown(c); } + ++count; + } + if (count>FE_TYPE_PARAM_MAX) { --c->instance_depth; return unknown(c); } + result=instantiate_struct(c,home,want,args,count,n->loc); + --c->instance_depth; + *ok=1; + return result; + } + return unknown(c); +} + +/* A `comptime if` condition. Only the forms SPEC 9 allows: type equality and + the type predicates. Anything else is not decidable here. */ +int comptime_condition(FeCheckerState *s, FeNode *n, int *out) +{ + FeType *a; + FeType *b; + int ok=0; + int eq; + if (!n) return 0; + if (n->kind==FE_N_BINARY && n->text && + (strcmp(n->text,"==")==0 || strcmp(n->text,"!=")==0)) { + a=type_from_expr(s,n->a,&ok); + if (!ok) return 0; + b=type_from_expr(s,n->b,&ok); + if (!ok) return 0; + eq=fe_type_equal(a,b); + *out=strcmp(n->text,"==")==0 ? eq : !eq; + return 1; + } + if (n->kind==FE_N_CALL && n->text && + (strcmp(n->text,"@is_int")==0 || strcmp(n->text,"@is_ptr")==0)) { + a=type_from_expr(s,n->children,&ok); + if (!ok) return 0; + *out=strcmp(n->text,"@is_int")==0 ? fe_type_is_integer(a) : + (a && (a->kind==FE_TYPE_OWNED || a->kind==FE_TYPE_REF)); + return 1; + } + return 0; +} + +/* Check a generic body once, in the unit that declared it and with the + instance's arguments bound. Errors land on the operation that is wrong; the + call site gets a note, because the call is context and not the defect. */ +void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl, + FeType *owner, FeBindSave *bindings, FeLoc site) +{ + FeAst *save_ast=c->ast; + FeUnit *save_unit=c->unit; + const char *save_name=c->types.unit_name; + unsigned before=c->diags->errors; + (void)bindings; + c->ast=&home->ast; + c->unit=home; + c->types.unit_name=home->name; + fe_diags_source(c->diags,home->source,home->size); + if (owner) check_method(c,decl,c->unit_scope[unit_index(c,home)],owner); + else check_fn(c,decl,c->unit_scope[unit_index(c,home)]); + c->ast=save_ast; + c->unit=save_unit; + c->types.unit_name=save_name; + if (save_unit) fe_diags_source(c->diags,save_unit->source,save_unit->size); + if (c->diags->errors>before) + fe_diag_note_src(c->diags,site,"instantiated here"); +} + +/* A call to a generic function: read the type arguments, check the value + arguments against the bound signature, then check the body once. */ +FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym, + FeUnit *home) +{ + FeCheck *c=s->c; + FeNode *decl=sym->fn; + unsigned want=decl_type_param_count(decl); + FeType *args[FE_TYPE_PARAM_MAX]; + FeNode *arg=n->children; + unsigned i; + char key[FE_GENERIC_KEY_MAX]; + FeBindSave save; + FeType *result; + int fresh; + if (want>FE_TYPE_PARAM_MAX) { + err(c,n->loc,"too many generic parameters"); + return unknown(c); + } + for (i=0;iloc,"generic call requires explicit type arguments"); + return unknown(c); + } + args[i]=type_from_expr(s,arg,&ok); + if (!ok) { + err(c,arg->loc,"a comptime type argument must name a type"); + return unknown(c); + } + arg=arg->next; + } + instance_key(key,home->name,decl->text,args,want); + push_bindings(c,&save,decl,args,want); + result=check_call_args(s,n,sym,home->name,want); + fresh=instance_record(c,key,n->loc,decl,home,0); + pop_bindings(c,&save); + /* The call goes to this instance, not to the declaration it came from. */ + if (n->a) n->a->cname=(char *)instance_cname(c,key); + if (fresh>0) { + if (!instance_descend(c,n->loc)) return result; + push_bindings(c,&save,decl,args,want); + instantiate_body(c,home,decl,0,&save,n->loc); + pop_bindings(c,&save); + --c->instance_depth; + } + return result; +} + +/* `Type.method(...)` where Type is a generic instance and the method takes no + self parameter. */ +/* The unit a name belongs to, by name. */ +FeUnit *unit_named(FeCheck *c, const char *name) +{ + unsigned u; + if (!name) return 0; + for (u=0;ubuild->count;++u) + if (!strcmp(c->build->units[u].name,name)) return &c->build->units[u]; + return 0; +} + +FeType *check_static_method_call(FeCheckerState *s, FeNode *n, + FeType *owner, FeNode *method) +{ + FeCheck *c=s->c; + /* A method belongs to the unit that declared its type, not to whichever + unit happens to be calling it. */ + FeUnit *home=unit_named(c,owner ? owner->unit : 0); + FeBindSave save; + FeType *result; + char key[FE_GENERIC_KEY_MAX]; + FeType *self_args[1]; + int fresh; + FeSym fake; + if (!home) home=current_unit(c); + self_args[0]=owner; + instance_key(key,home->name,method->text,self_args,1); + memset(&fake,0,sizeof fake); + fake.name=method->text; + fake.cname=method->cname; + fake.fn=method; + fake.decl=method; + push_instance_bindings(c,&save,owner); + bind_self(c,owner); + result=check_call_args(s,n,&fake,home->name,0); + fresh=instance_record(c,key,n->loc,method,home,owner); + pop_bindings(c,&save); + if (n->a) n->a->cname=(char *)instance_cname(c,key); + if (fresh>0) { + if (!instance_descend(c,n->loc)) return result; + push_instance_bindings(c,&save,owner); + bind_self(c,owner); + instantiate_body(c,home,method,owner,&save,n->loc); + pop_bindings(c,&save); + --c->instance_depth; + } + return result; +} + +/* The body of a method on a generic instance, checked once per instance. */ +void check_instance_method(FeCheckerState *s, FeType *owner, + FeNode *method, FeLoc site, FeNode *call) +{ + FeCheck *c=s->c; + /* A method belongs to the unit that declared its type, not to whichever + unit happens to be calling it. */ + FeUnit *home=unit_named(c,owner ? owner->unit : 0); + FeBindSave save; + char key[FE_GENERIC_KEY_MAX]; + FeType *self_args[1]; + self_args[0]=owner; + if (!home) home=current_unit(c); + instance_key(key,home->name,method->text,self_args,1); + { + FeBindSave probe; + int fresh; + push_instance_bindings(c,&probe,owner); + bind_self(c,owner); + fresh=instance_record(c,key,site,method,home,owner); + pop_bindings(c,&probe); + /* The call names this instance's copy of the method. */ + if (call && call->a) call->a->cname=(char *)instance_cname(c,key); + if (fresh<=0) return; + } + if (!instance_descend(c,site)) return; + push_instance_bindings(c,&save,owner); + bind_self(c,owner); + instantiate_body(c,home,method,owner,&save,site); + pop_bindings(c,&save); + --c->instance_depth; +} + +/* SPEC 4.7: `const Word = i32;` is another spelling of a type, not a value. + It has no initializer to check and no storage. */ +int const_names_type(FeCheckerState *s, FeNode *n) +{ + FeType *t; + if (!n->b || n->b->kind!=FE_N_IDENT || !n->b->text) return 0; + if (n->a) return 0; + if (find_symbol(s->globals,n->b->text)) return 0; + t=fe_type_intern(&s->c->types,n->b->text); + return t && t->kind!=FE_TYPE_UNKNOWN; +} + +FeNode *type_method(FeType *t, const char *name) +{ + FeNode *m; + if (!t || !t->decl_node || !name) return 0; + for (m=t->decl_node->children;m;m=m->next) + if (m->kind==FE_N_FN && m->text && strcmp(m->text,name)==0) return m; + return 0; +} + +int method_is_static(const FeNode *method) +{ + FeNode *first=method && method->a ? method->a->children : 0; + return !first || !first->text || strcmp(first->text,"self")!=0; +} + +/* A call to a named function. `home` is the unit the signature was written in, + null when that is the unit being checked: parameter and return types have to + be read where they were written or a name would mean the caller's type. */ +/* `error.Name` is a member of the default error set. That set is open -- names + are collected across the build and numbered later, not declared -- so any + name is well formed here and the value's type is core.Error. */ diff --git a/fec/src/checkpri.h b/fec/src/checkpri.h new file mode 100644 index 0000000..d3c04b7 --- /dev/null +++ b/fec/src/checkpri.h @@ -0,0 +1,252 @@ +#ifndef FE_CHECKPRI_H +#define FE_CHECKPRI_H + +/* The checker's own vocabulary, shared by the files it is split across. + Nothing outside the checker includes this. */ + +#include "check.h" +#include "m7.h" +#include + +#define FE_M7_FLOW_CAP 64U +#include "own.h" +#include +#include + +typedef struct FeSym FeSym; +/* FeScope is forward declared in check.h. */ + +struct FeSym { + const char *name; + char *cname; + FeType *type; + FeNode *fn; + int mutable; + 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 { + FeScope *parent; + FeSym *items; + unsigned count; + unsigned capacity; +}; + + +typedef struct FeCheckerState { + FeCheck *c; + FeScope *scope; + FeScope *globals; + FeType *ret; + unsigned loop_depth; + unsigned defer_depth; + FeOwnLiveness liveness; + FeNode *fn_node; +} FeCheckerState; + +/* The type bindings in force, saved across a nested instantiation. */ +typedef struct FeBindSave { + FeTypeBind params[FE_TYPE_PARAM_MAX]; + unsigned count; +} FeBindSave; + +typedef struct FeFlowSlot { + FeSym *sym; + int moved; + int initialized; + int own_move; + int own_initialized; +} FeFlowSlot; + +typedef struct FeFlowBorrow { + FeSym *root; + int mutable; +} FeFlowBorrow; + +/* How long a chain of new generic instances may get, and how long an + instance's readable spelling may be before it falls back to serials. */ +#define FE_GENERIC_DEPTH_MAX 32 +#define FE_GENERIC_NAME_READABLE 200 + +/* Every definition in the checker, so the split files can see each other. */ +FeType *unknown(FeCheck *c); +void err(FeCheck *c, FeLoc loc, const char *msg); +int ordered_type(const FeType *t); +int known(FeType *t); +int in_own_drop(FeCheckerState *s, FeNode *n); +void mark_moved(FeCheckerState *s, FeNode *n, FeType *t); +int compatible(FeType *want, FeType *got, FeNode *value); +int call_reborrows(const FeType *param, const FeType *arg); +int explicit_castable(FeType *a, FeType *b); +FeType *node_type(FeCheck *c, FeNode *n); +char *unit_cname(FeCheck *c, const char *name); +char *local_cname(FeCheck *c, const char *name); +FeScope *scope_new(FeCheckerState *s, FeScope *parent); +FeSym *find_current(FeScope *scope, const char *name); +FeSym *find_symbol(FeScope *scope, const char *name); +FeSym *add_symbol(FeCheckerState *s, FeScope *scope, + const char *name, FeType *type, FeNode *fn, + int mutable, int initialized, char *cname, + FeNode *decl); +void enter_unit(FeCheck *c, unsigned index); +unsigned unit_index(FeCheck *c, const FeUnit *u); +FeUnit *binding_unit(FeCheckerState *s, FeNode *base); +int decl_is_public(const FeNode *decl); +FeSym *unit_member(FeCheck *c, FeUnit *u, const char *name); +FeType *unit_type(FeCheck *c, FeUnit *u, const char *name); +FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name); +FeType *node_type_in(FeCheck *c, const char *unit, FeNode *node); +FeNode *find_method(FeCheck *c, FeType *owner, const char *name); +FeType *method_type(FeCheck *c, FeNode *node, FeType *owner); +unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap); +void flow_restore(FeFlowSlot *slots, unsigned count); +void flow_merge(FeFlowSlot *base, FeFlowSlot *left, FeFlowSlot *right, + unsigned count); +FeSym *own_root_symbol(FeCheckerState *s, FeNode *expr); +int own_is_global(FeCheckerState *s, FeSym *sym); +void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable); +void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr); +FeSym *own_derived_call_root(FeCheckerState *s, FeNode *call); +void own_bind_derived_call(FeCheckerState *s, FeSym *binding, + FeNode *value); +int own_stmt_uses(FeNode *node, const char *name); +int own_defer_uses(FeNode *node, const char *name); +int own_contains_node(FeNode *node, FeNode *needle); +void own_release_after_stmt(FeCheckerState *s, FeScope *scope, + FeNode *stmt, int scope_end); +FeOwnState *flow_own_new(FeCheckerState *s, unsigned count); +void flow_own_capture(FeFlowSlot *slots, FeOwnState *states, + unsigned count); +void flow_own_restore(FeFlowSlot *slots, FeOwnState *states, + unsigned count); +void flow_own_merge(FeFlowSlot *slots, FeOwnState *left, + FeOwnState *right, unsigned count); +FeFlowBorrow *flow_borrow_new(FeCheckerState *s, unsigned count); +void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states, + unsigned count); +void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states, + unsigned count); +void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left, + FeFlowBorrow *right, unsigned count); +FeNode *find_const_node(FeCheck *c, const char *name); +const char *builtin_format(FeCheckerState *s, FeNode *fmt); +int format_is_slice_u8(FeType *t); +int format_is_writer_type(FeType *t); +int format_arg_ok(FeType *t, int verb); +void check_format_call(FeCheckerState *s, FeNode *n); +int is_format_builtin(const char *name); +int lvalue_writable(FeCheckerState *s, FeNode *n); +int has_field(FeNode *list, const char *name); +int field_is_visible(FeCheckerState *s, const FeType *t, + const FeFieldType *field); +FeType *check_struct_fields(FeCheckerState *s, FeNode *n, FeType *t); +FeType *check_struct_init(FeCheckerState *s, FeNode *n); +FeType *check_array_init(FeCheckerState *s, FeNode *n); +int array_slice_lvalue(FeNode *n); +FeType *check_index(FeCheckerState *s, FeNode *n); +FeType *check_identifier(FeCheckerState *s, FeNode *n); +FeType *check_expr_core(FeCheckerState *s, FeNode *n); +FeType *check_lvalue_core(FeCheckerState *s, FeNode *n, int read, + FeType *base_in); +int compound_operator(const char *op); +void check_match(FeCheckerState *s, FeNode *n); +void check_for(FeCheckerState *s, FeNode *n); +void check_type_cycle(FeCheck *c, FeType *t); +void check_type_cycles(FeCheck *c); +int own_ast_reference_type(FeNode *type); +int own_ast_pointer_to_reference(FeNode *type); +void check_reference_storage(FeCheck *c, FeNode *decl); +int own_return_from_allowed_root(FeCheckerState *s, FeNode *expr); +void check_stmt_core(FeCheckerState *s, FeNode *n); +void check_fn(FeCheck *c, FeNode *fn, FeScope *globals); +void check_method(FeCheck *c, FeNode *fn, FeScope *globals, + FeType *owner); +int m7_actual_compatible(FeType *want, FeType *got, FeNode *value); +FeType *m7_check_expected(FeCheckerState *s, FeNode *value, + FeType *expected); +FeType *m7_member_field(FeCheckerState *s, FeNode *n, FeType *base); +int m7_place_is_projection(FeNode *n); +unsigned decl_type_param_count(const FeNode *decl); +FeNode *decl_type_param(const FeNode *decl, unsigned i); +int decl_is_generic(const FeNode *decl); +void check_generic_params(FeCheck *c, FeNode *decl); +void push_bindings(FeCheck *c, FeBindSave *save, FeNode *decl, + FeType **args, unsigned count); +void push_instance_bindings(FeCheck *c, FeBindSave *save, FeType *t); +void bind_self(FeCheck *c, FeType *owner); +void pop_bindings(FeCheck *c, const FeBindSave *save); +void instance_key(char *out, const char *unit, const char *name, + FeType **args, unsigned count); +const char *instance_cname(FeCheck *c, const char *key); +int instance_known(FeCheck *c, const char *key); +int instance_record(FeCheck *c, const char *key, FeLoc loc, + FeNode *decl, FeUnit *home, FeType *owner); +int instance_descend(FeCheck *c, FeLoc loc); +FeUnit *current_unit(FeCheck *c); +FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl, + const char *key, FeType **args, + unsigned count); +FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name, + FeType **args, unsigned count, FeLoc loc); +FeType *instantiate_type_node(void *owner, const FeNode *node); +FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok); +int comptime_condition(FeCheckerState *s, FeNode *n, int *out); +void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl, + FeType *owner, FeBindSave *bindings, FeLoc site); +FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym, + FeUnit *home); +FeUnit *unit_named(FeCheck *c, const char *name); +FeType *check_static_method_call(FeCheckerState *s, FeNode *n, + FeType *owner, FeNode *method); +void check_instance_method(FeCheckerState *s, FeType *owner, + FeNode *method, FeLoc site, FeNode *call); +int const_names_type(FeCheckerState *s, FeNode *n); +FeNode *type_method(FeType *t, const char *name); +int method_is_static(const FeNode *method); +int is_error_set_member(FeCheckerState *s, FeNode *n); +FeType *cross_unit_value(FeCheckerState *s, FeNode *n, int *handled); +FeType *check_call_args(FeCheckerState *s, FeNode *n, FeSym *sym, + const char *home, unsigned skip); +FeType *check_call(FeCheckerState *s, FeNode *n); +void m7_capture_flow(FeCheckerState *s, FeFlowSlot *slots, + FeOwnState **own, FeFlowBorrow **borrow, + unsigned *count); +void m7_restore_flow(FeFlowSlot *slots, FeOwnState *own, + FeFlowBorrow *borrow, unsigned 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); +int m7_stmt_definitely_exits(FeNode *n); +FeType *m7_check_lazy(FeCheckerState *s, FeNode *n, + FeM7LazyKind kind); +FeType *check_expr(FeCheckerState *s, FeNode *n); +FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read); +FeType *m7_pattern_binding_type(FeCheckerState *s, FeType *payload, + FeNode *source, int *borrow_mut); +void m7_check_if_let(FeCheckerState *s, FeNode *n); +void m7_check_optional_match(FeCheckerState *s, FeNode *n, + FeType *opt); +void m7_check_match_stmt(FeCheckerState *s, FeNode *n); +void m7_check_decl_stmt(FeCheckerState *s, FeNode *n, int mutable); +void check_stmt(FeCheckerState *s, FeNode *n); +int m7_ast_reference_storage(FeNode *type); +void m7_check_storage(FeCheck *c, FeNode *decl); +void m7_validate_error_decl(FeCheck *c, FeNode *decl); +void declare_unit(FeCheck *c); +FeScope *declare_unit_scope(FeCheck *c, FeCheckerState *s); +void check_unit_bodies(FeCheck *c, FeCheckerState *s); + +#endif diff --git a/fec/src/checkpro.c b/fec/src/checkpro.c new file mode 100644 index 0000000..74acf81 --- /dev/null +++ b/fec/src/checkpro.c @@ -0,0 +1,182 @@ +#include "checkpri.h" + +int m7_ast_reference_storage(FeNode *type) +{ + if (!type || !type->text) return 0; + if (strcmp(type->text,"&")==0 || strcmp(type->text,"&mut")==0 || + (strcmp(type->text,"[")==0 && !type->a) || + strcmp(type->text,"str")==0) + return 1; + if (strcmp(type->text,"?")==0) + return m7_ast_reference_storage(type->a); + if (strcmp(type->text,"^")==0) return 0; + return 0; +} + +void m7_check_storage(FeCheck *c, FeNode *decl) +{ + FeNode *m; + if (!decl) return; + if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) { + /* This pass exists for the shapes the other one cannot see, such as a + reference behind an optional. A plain `&T` field is seen by both, so + leave that one to check_reference_storage below. */ + for (m=decl->children;m;m=m->next) + if (m->kind==FE_N_FIELD && m7_ast_reference_storage(m->a) && + !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"); + } + check_reference_storage(c,decl); +} + +void m7_validate_error_decl(FeCheck *c, FeNode *decl) +{ + FeNode *a; + FeNode *b; + unsigned long code; + unsigned long other; + if (!decl || decl->kind!=FE_N_ERROR_DECL) return; + for (a=decl->children;a;a=a->next) { + if (!a->a || a->a->kind!=FE_N_LITERAL || !a->a->text) continue; + code=strtoul(a->a->text,0,0); + if (code==0UL) + err(c,a->loc,"error code 0 is reserved for success"); + for (b=decl->children;b && b!=a;b=b->next) { + if (a->text && b->text && strcmp(a->text,b->text)==0) { + err(c,a->loc,"duplicate error member name"); + break; + } + if (b->a && b->a->kind==FE_N_LITERAL && b->a->text) { + other=strtoul(b->a->text,0,0); + if (other==code) { + err(c,a->loc,"duplicate error numeric code"); + break; + } + } + } + } +} + +/* Everything a unit declares, before any body anywhere is looked at. */ +void declare_unit(FeCheck *c) +{ + FeNode *n; + /* A generic declaration is not a type; only its instances are. */ + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_STRUCT && !decl_is_generic(n)) + fe_type_declare_struct(&c->types,n,(n->flags & FE_NODE_PACKED)!=0); + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) { + FeNode *m; + m7_check_storage(c,n); + if (n->kind==FE_N_ERROR_DECL) m7_validate_error_decl(c,n); + check_generic_params(c,n); + for (m=n->kind==FE_N_STRUCT ? n->children : 0;m;m=m->next) + if (m->kind==FE_N_FN) check_generic_params(c,m); + } + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_ENUM && !decl_is_generic(n)) + fe_type_declare_enum(&c->types,n); + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_ERROR_DECL) fe_type_declare_error(&c->types,n); + check_type_cycles(c); +} + +/* The unit's top-level names, in a scope of their own so that another unit + can look into it later without inheriting anything else. */ +FeScope *declare_unit_scope(FeCheck *c, FeCheckerState *s) +{ + FeNode *n; + FeNode *m; + FeType *t; + FeScope *globals; + char method_name[128]; + globals=scope_new(s,0); + s->scope=globals; + s->globals=globals; + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) { + if (n->kind==FE_N_STRUCT) { + for (m=n->children;m;m=m->next) if (m->kind==FE_N_FN) { + sprintf(method_name,"%s_%s",n->text ? n->text : "Type", + m->text ? m->text : "method"); + m->cname=unit_cname(c,method_name); + } + } + if (n->kind==FE_N_GLOBAL || n->kind==FE_N_CONST) { + t=n->a ? node_type(c,n->a) : unknown(c); + add_symbol(s,globals,n->text,t,0,n->kind==FE_N_GLOBAL, + n->b!=0,unit_cname(c,n->text ? n->text : "global"),n); + } + } + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_FN) { + t=fe_type_intern(&c->types,""); + /* `extern "c"` means the linker already knows this name, so it is + not decorated with the unit it was declared in. */ + add_symbol(s,globals,n->text,t,n,0,1, + (n->flags & FE_NODE_EXTERN) && n->text ? n->text : + unit_cname(c,n->text ? n->text : "fn"),n); + } + return globals; +} + +void check_unit_bodies(FeCheck *c, FeCheckerState *s) +{ + FeNode *n; + FeNode *m; + FeSym *sym; + FeType *t; + FeType *iv; + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_GLOBAL || n->kind==FE_N_CONST) { + sym=find_current(s->globals,n->text ? n->text : ""); + if (n->kind==FE_N_CONST && const_names_type(s,n)) continue; + if (n->b) { + iv=m7_check_expected(s,n->b,sym ? sym->type : 0); + if (sym && sym->type->kind==FE_TYPE_UNKNOWN) { + sym->type=iv; + n->sem_type=iv; + } else if (sym && !fe_type_equal(sym->type,iv) && + !m7_actual_compatible(sym->type,iv,n->b)) + err(c,n->loc,"global initializer type mismatch"); + } + } + /* A generic body means nothing until its parameters are bound, so it is + checked once per instance and not here. */ + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_FN && !decl_is_generic(n)) check_fn(c,n,s->globals); + for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) + if (n->kind==FE_N_STRUCT && !decl_is_generic(n)) { + t=fe_type_intern(&c->types,n->text); + for (m=n->children;m;m=m->next) + if (m->kind==FE_N_FN) check_method(c,m,s->globals,t); + } +} + +int fe_check_program(FeCheck *c) +{ + FeCheckerState s; + unsigned u; + s.c=c; + s.scope=0; + s.globals=0; + 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->arena); + for (u=0;ubuild->count;++u) { enter_unit(c,u); declare_unit(c); } + fe_type_layout_all(&c->types); + for (u=0;ubuild->count;++u) { + enter_unit(c,u); + c->unit_scope[u]=declare_unit_scope(c,&s); + } + for (u=0;ubuild->count;++u) { + enter_unit(c,u); + s.scope=c->unit_scope[u]; + s.globals=c->unit_scope[u]; + check_unit_bodies(c,&s); + } + fe_type_layout_all(&c->types); + return c->diags->errors==0; +} diff --git a/fec/src/checkstm.c b/fec/src/checkstm.c new file mode 100644 index 0000000..587e199 --- /dev/null +++ b/fec/src/checkstm.c @@ -0,0 +1,635 @@ +#include "checkpri.h" + +void check_match(FeCheckerState *s, FeNode *n) +{ + FeType *value; + FeNode *arm; + FeVariantType *variant; + int seen[256]; + int wildcard=0; + FeFlowSlot base[64], merged[64], current[64]; + unsigned flow_count; + int have_merged=0; + unsigned i; + for(i=0;i<256U;i++) seen[i]=0; + value=check_expr(s,n->a); + if(!value || value->kind!=FE_TYPE_ENUM) { err(s->c,n->loc,"match requires an enum value"); return; } + flow_count=flow_capture(s->scope,base,64); + for(arm=n->children;arm;arm=arm->next) { + FeScope *old=s->scope; + flow_restore(base,flow_count); + if(arm->text && strcmp(arm->text,"_")==0) wildcard=1; + else { + variant=fe_type_variant(value,arm->text); + if(!variant) { err(s->c,arm->loc,"unknown match variant"); continue; } + if(variant->tag<256U) { + if(seen[variant->tag]) err(s->c,arm->loc,"duplicate match variant"); + seen[variant->tag]=1; + } + s->scope=scope_new(s,old); + if(variant->field_count==1 && arm->children) { + add_symbol(s,s->scope,arm->children->text,variant->fields[0].type,0,0,1, + local_cname(s->c,arm->children->text),arm->children); + } else if(variant->field_count>0) { + FeNode *b=arm->children; + for(i=0;ifield_count && b;i++,b=b->next) { + FeFieldType *f=&variant->fields[i]; + add_symbol(s,s->scope,b->text,f->type,0,0,1, + local_cname(s->c,b->text),b); + } + } + } + if(arm->a && arm->a->kind==FE_N_BLOCK) check_stmt(s,arm->a); + else if(arm->a) check_expr(s,arm->a); + s->scope=old; + flow_capture(s->scope,current,flow_count); + if(!have_merged) { + for(i=0;ivariant_count && i<256U;i++) if(!seen[i]) err(s->c,n->loc,"non-exhaustive match"); +} + +void check_for(FeCheckerState *s, FeNode *n) +{ + FeType *start; + FeType *finish; + FeType *elem; + FeType *ref_type; + FeSym *iter_sym; + char *index_cname; + char *item_cname; + int iter_mut; + FeScope *old=s->scope; + if(!n->c) { + start=check_expr(s,n->a); + if (!fe_type_is_indexable(start)) { + err(s->c,n->loc,"for iterable must be an array, slice, or str"); + return; + } + elem=start->elem; + iter_sym=0; + if (n->a && n->a->kind==FE_N_IDENT) + iter_sym=find_symbol(s->scope,n->a->text ? n->a->text : ""); + else if (n->a && n->a->kind==FE_N_INDEX && n->a->a && + n->a->a->kind==FE_N_IDENT) + iter_sym=find_symbol(s->scope,n->a->a->text ? n->a->a->text : ""); + iter_mut=start->kind==FE_TYPE_SLICE ? start->ref_mut : + (iter_sym && iter_sym->mutable); + ref_type=fe_type_ref(&s->c->types,elem,iter_mut); + if (iter_mut) n->flags |= 4U; + s->scope=scope_new(s,old); + if (n->aux_text) { + index_cname=local_cname(s->c,n->text ? n->text : "index"); + item_cname=local_cname(s->c,n->aux_text); + add_symbol(s,s->scope,n->text,fe_type_intern(&s->c->types,"usize"),0,0,1, + index_cname,n); + add_symbol(s,s->scope,n->aux_text,ref_type,0,iter_mut,1, + item_cname,0); + n->cname=index_cname; + n->aux_cname=item_cname; + } else { + item_cname=local_cname(s->c,n->text ? n->text : "item"); + add_symbol(s,s->scope,n->text,ref_type,0,iter_mut,1, + item_cname,n); + n->cname=item_cname; + } + check_stmt(s,n->b); + s->scope=old; + return; + } + start=check_expr(s,n->a); + finish=check_expr(s,n->c); + if(known(start)&&!fe_type_is_integer(start)) err(s->c,n->loc,"range start must be integer"); + if(known(finish)&&!fe_type_is_integer(finish)) err(s->c,n->loc,"range end must be integer"); + s->scope=scope_new(s,old); + index_cname=local_cname(s->c,n->text ? n->text : "index"); + add_symbol(s,s->scope,n->text,fe_type_intern(&s->c->types,"usize"),0,0,1, + index_cname,n); + n->cname=index_cname; + check_stmt(s,n->b); + s->scope=old; +} + +void check_type_cycle(FeCheck *c, FeType *t) +{ + unsigned i; + FeType *next; + if (!t || t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR || + t->kind == FE_TYPE_REF || t->kind == FE_TYPE_OWNED || + t->kind == FE_TYPE_INT || t->kind == FE_TYPE_BOOL || + t->kind == FE_TYPE_CHAR || t->kind == FE_TYPE_VOID || + t->kind == FE_TYPE_UNKNOWN || t->kind == FE_TYPE_ERROR) return; + if (t->kind == FE_TYPE_ERROR_UNION) { + check_type_cycle(c,t->error_value); + return; + } + if (t->cycle_state == 1) { + if (c->ast->root) err(c, c->ast->root->loc, "by-value recursive type"); + return; + } + if (t->cycle_state == 2) return; + t->cycle_state = 1; + if (t->kind == FE_TYPE_ARRAY) { + check_type_cycle(c,t->elem); + } else if (t->kind == FE_TYPE_STRUCT) { + for (i=0;ifield_count;i++) { + if (!t->fields[i].type && t->fields[i].ast_node) + t->fields[i].type=fe_type_from_ast(&c->types,t->fields[i].ast_node->a); + check_type_cycle(c,t->fields[i].type); + } + } else if (t->kind == FE_TYPE_ENUM) { + for (i=0;ivariant_count;i++) { + unsigned j; + for (j=0;jvariants[i].field_count;j++) { + if (!t->variants[i].fields[j].type && t->variants[i].fields[j].ast_node) + t->variants[i].fields[j].type=fe_type_from_ast(&c->types, + t->variants[i].fields[j].ast_node->a); + next=t->variants[i].fields[j].type; + check_type_cycle(c,next); + } + } + } + t->cycle_state=2; +} + +void check_type_cycles(FeCheck *c) +{ + FeType *t; + for (t=c->types.types;t;t=t->next) t->cycle_state=0; + for (t=c->types.types;t;t=t->next) check_type_cycle(c,t); +} + +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; +} + +int own_ast_pointer_to_reference(FeNode *type) +{ + return type && type->text && strcmp(type->text,"*")==0 && + own_ast_reference_type(type->a); +} + +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"); +} + +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; +} + +void check_stmt_core(FeCheckerState *s, FeNode *n) +{ + FeCheck *c = s->c; + FeScope *old; + FeType *a; + FeType *b; + FeSym *sym; + FeNode *x; + int initialized; + 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: + a = n->a ? node_type(c, n->a) : unknown(c); + b = check_expr(s, n->b); + if (!n->a) a = b; + if (a->kind == FE_TYPE_VOID) + err(c, n->loc, "variable cannot have void type"); + if (n->a && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "initializer type mismatch"); + if (b->kind == FE_TYPE_VOID) + err(c, n->loc, "void expression cannot initialize a variable"); + 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); + 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); + if (!n->b && !n->a) + err(c, n->loc, "uninitialized var requires an explicit type"); + b = n->b ? check_expr(s, n->b) : unknown(c); + if (!n->a && n->b) a = b; + if (a->kind == FE_TYPE_VOID) + err(c, n->loc, "variable cannot have void type"); + if (n->b && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "initializer type mismatch"); + if (b->kind == FE_TYPE_VOID) + err(c, n->loc, "void expression cannot initialize a variable"); + mark_moved(s,n->b,b); + initialized = n->b != 0; + 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); + a = check_lvalue(s, n->a, compound_operator(n->text)); + if (!compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "assignment type mismatch"); + 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; + 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: + /* The enclosing-error-result check lives on the try expression itself, + so a bare `try e;` needs nothing extra here. */ + 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: { + 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); + 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;iloop_depth < 255U) ++s->loop_depth; + check_for(s,n); + if (s->loop_depth) --s->loop_depth; + break; + case FE_N_MATCH: + check_match(s,n); + break; + case FE_N_BREAK: + case FE_N_CONTINUE: + if (!s->loop_depth) err(c,n->loc,"break or continue outside loop"); + break; + case FE_N_RETURN: + b = n->a ? 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"); + else if (known(s->ret) && known(b) && !fe_type_equal(s->ret, b) && + b->kind != FE_TYPE_UNKNOWN && + !compatible(s->ret,b,n->a)) + err(c, n->loc, "return type mismatch"); + break; + case FE_N_UNSAFE: + check_stmt(s, n->a); + break; + default: + break; + } +} + +void check_fn(FeCheck *c, FeNode *fn, FeScope *globals) +{ + FeCheckerState s; + FeScope *old; + FeNode *x; + FeType *t; + s.c = c; + s.globals = globals; + s.scope = scope_new(&s, 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->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); + if (t->kind == FE_TYPE_VOID) + err(c, x->loc, "parameter cannot have void type"); + add_symbol(&s, s.scope, x->text, t, 0, 1, 1, + local_cname(c, x->text ? x->text : "arg"), x); + } + old = s.scope; + if (fn->c) check_stmt(&s, fn->c); + s.scope = old; +} + +void check_method(FeCheck *c, FeNode *fn, FeScope *globals, + FeType *owner) +{ + FeCheckerState s; + FeNode *x; + FeType *t; + s.c=c; + s.globals=globals; + s.scope=scope_new(&s,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->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); + x->sem_type=t; + add_symbol(&s,s.scope,x->text,t,0,1,1, + local_cname(c,x->text ? x->text : "arg"),x); + } + if(fn->c) check_stmt(&s,fn->c); +} + +int m7_actual_compatible(FeType *want, FeType *got, FeNode *value) +{ + if (fe_type_equal(want,got)) return 1; + return compatible(want,got,value); +} + +FeType *m7_check_expected(FeCheckerState *s, FeNode *value, + FeType *expected) +{ + FeType *actual; + FeM7ContextKind context; + if (!value) return unknown(s->c); + if (fe_m7_is_null(value)) { + if (!fe_m7_can_contextual_null(expected)) { + err(s->c,value->loc,"null requires a contextual optional type"); + value->sem_type=unknown(s->c); + return value->sem_type; + } + value->sem_type=expected; + value->sem_context=expected; + return expected; + } + actual=check_expr(s,value); + if (!expected) return actual; + if (expected->kind==FE_TYPE_OPTIONAL && expected->elem && + m7_actual_compatible(expected->elem,actual,value)) { + value->sem_context=expected; + return expected; + } + if (expected->kind==FE_TYPE_ERROR_UNION) { + context=fe_m7_error_context(&s->c->types,expected,actual); + if (context!=FE_M7_CONTEXT_NONE) { + value->sem_context=expected; + return expected; + } + } + return actual; +} + +FeType *m7_member_field(FeCheckerState *s, FeNode *n, FeType *base) +{ + FeFieldType *field; + FeType *owner; + if (!base) return unknown(s->c); + if (n->text && strcmp(n->text,".?")==0) { + if (base->kind!=FE_TYPE_OPTIONAL) { + err(s->c,n->loc,"optional projection '.?' requires an optional value"); + return unknown(s->c); + } + n->sem_type=base->elem; + return n->sem_type; + } + if (base->kind==FE_TYPE_OPTIONAL) { + err(s->c,n->loc,"optional value must be projected with '.?' first"); + return unknown(s->c); + } + if (base->kind==FE_TYPE_REF && n->b && n->b->text && + strcmp(n->b->text,"^")==0) { + n->sem_type=base->elem; + return n->sem_type; + } + if (base->kind==FE_TYPE_OWNED && n->b && n->b->text && + strcmp(n->b->text,"^")==0) { + n->sem_type=base->elem; + return n->sem_type; + } + 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) { + field=fe_type_field(owner,n->b->text); + if (!field) { + err(s->c,n->loc,"unknown struct field"); + return unknown(s->c); + } + n->sem_type=field->type; + return field->type; + } + if (base->kind==FE_TYPE_ENUM && n->b && n->b->text) { + if (!fe_type_variant(base,n->b->text)) + err(s->c,n->loc,"unknown enum variant"); + n->sem_type=base; + return base; + } + if ((base->kind==FE_TYPE_SLICE || base->kind==FE_TYPE_STR) && + n->b && n->b->text && strcmp(n->b->text,"n")==0) { + n->sem_type=fe_type_intern(&s->c->types,"usize"); + return n->sem_type; + } + n->sem_type=unknown(s->c); + return n->sem_type; +} + +int m7_place_is_projection(FeNode *n) +{ + return n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX); +} + +/* ------------------------------------------------------------------------- * + * Generics (SPEC 9) + * + * A generic declaration is checked once per distinct list of type arguments. + * Those arguments are bound as types for the length of that check, so a name + * that is a type parameter simply is its argument -- in the body, in field + * types and in the signature alike. An instance is identified by its declaring + * unit, its declaration and the spelling of its arguments, so asking twice + * asks for the same instance, and a chain of new ones is bounded. + * ------------------------------------------------------------------------- */ + diff --git a/fec/src/lower.c b/fec/src/lower.c index 07c2e08..da94f4d 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -1,103 +1,6 @@ -#include "lower.h" -#include -#include "m7.h" -#include "own.h" -#include +#include "lowerpri.h" -/* ------------------------------------------------------------------------- * - * Lowering - * - * One function at a time, one statement at a time. A `Slot` is what an - * expression produced: either a value already in a temporary, or a place in - * memory that a value can be read from or written to. Aggregates are always - * places -- they are never carried in a temporary, because a temporary is a - * register and an aggregate does not fit in one. - * ------------------------------------------------------------------------- */ - -#define LOWER_MAX_LOCALS 256 - -typedef struct LowerVar { - const char *cname; - unsigned local; - /* An aggregate parameter arrives as an address, so the slot holds a - pointer and the value is one dereference away. */ - int by_address; -} LowerVar; - -typedef struct Lower { - FeCheck *c; - FeIrModule *m; - FeIrFunc *fn; - FeIrBlock *b; /* the block being appended to */ - FeType *ret_type; - unsigned ret_local; /* hidden result address, when returning mem */ - LowerVar vars[LOWER_MAX_LOCALS]; - unsigned var_count; - /* Loop targets, for break and continue. */ - unsigned break_target[32]; - unsigned continue_target[32]; - unsigned loop_depth; - /* What a scope still owes when it ends: `defer` blocks to run and owned - values to release, in the order they were written. Every exit path runs - what is live, last first. - - A drop carries a flag beside the value. The flag is set when the value - is stored and cleared wherever it is moved away, so the release happens - exactly on the paths where the value is still there -- which is not - something the shape of the code can tell you on its own. */ - struct { - FeNode *block; /* a `defer`, when set */ - unsigned local; /* the owned value, otherwise */ - unsigned flag; - FeType *type; - } owed[64]; - unsigned owed_count; - /* Every `error.Name` used anywhere in the build, sorted, numbered from one. - SPEC 4.6: the names are collected rather than declared, and the order is - fixed by the spelling so that the same program always gets the same - codes however the build was ordered. */ - const char *error_names[256]; - unsigned error_count; - int failed; -} Lower; - -typedef struct Slot { - int is_place; - unsigned temp; /* the value, when is_place is 0 */ - FeIrPlace place; /* where it lives, when is_place is 1 */ - FeIrType type; - unsigned long size; /* for FE_IR_MEM */ -} Slot; - -static Slot lower_expr(Lower *L, FeNode *n); -static void lower_stmt(Lower *L, FeNode *n); -static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, - unsigned long size); -static void lower_for(Lower *L, FeNode *n); -static int lower_mem(Lower *L, FeNode *n, Slot *out); -static long error_code(Lower *L, const char *name); -static int fn_is_generic(const FeNode *fn); -static void lower_fn_as(Lower *L, FeNode *fn, const char *name); -static Slot lower_slice(Lower *L, FeNode *n); -static void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line); -static void lower_match(Lower *L, FeNode *n); -static int enum_has_payload(const FeType *t); -static void lower_global(Lower *L, FeNode *n); -static long literal_value(FeNode *n); -static Slot wrap_context(Lower *L, Slot v, FeNode *n); -static Slot lower_try(Lower *L, FeNode *n); -static Slot lower_lazy(Lower *L, FeNode *n, int is_catch); -static Slot wrapper_payload(Lower *L, Slot w, const FeType *t); -static unsigned scratch(Lower *L, const FeType *t, const char *why); -static int uses_niche(const FeType *t); -static FeIrType tag_type(const FeType *t); -static void run_deferred(Lower *L, unsigned from); -static unsigned declare_var(Lower *L, const char *cname, const FeType *t, - const char *name); -static void indexable_parts(Lower *L, Slot base, const FeType *t, - unsigned *data, unsigned *length, FeNode *n); - -static void fail(Lower *L, const char *why, FeNode *n) +void fail(Lower *L, const char *why, FeNode *n) { if (L->failed) return; L->failed = 1; @@ -111,7 +14,7 @@ static void fail(Lower *L, const char *why, FeNode *n) /* A Ferro type becomes what a register can hold, or a size in memory. Anything with more than one field is memory: the backend never has to decide whether an aggregate fits somewhere. */ -static FeIrType ir_type_of(const FeType *t) +FeIrType ir_type_of(const FeType *t) { if (!t) return FE_IR_VOID; switch (t->kind) { @@ -136,7 +39,7 @@ static FeIrType ir_type_of(const FeType *t) } } -static int enum_has_payload(const FeType *t) +int enum_has_payload(const FeType *t) { unsigned i; if (!t || t->kind != FE_TYPE_ENUM) return 0; @@ -145,30 +48,30 @@ static int enum_has_payload(const FeType *t) return 0; } -static FeIrType ir_type(const FeType *t) +FeIrType ir_type(const FeType *t) { if (t && t->kind == FE_TYPE_ENUM && enum_has_payload(t)) return FE_IR_MEM; return ir_type_of(t); } -static unsigned long ir_size(const FeType *t) +unsigned long ir_size(const FeType *t) { return t ? fe_type_size(t) : 0UL; } -static unsigned ir_align(const FeType *t) +unsigned ir_align(const FeType *t) { return t ? fe_type_align(t) : 1U; } -static int type_is_unsigned(const FeType *t) +int type_is_unsigned(const FeType *t) { return t && t->kind == FE_TYPE_INT && t->is_unsigned; } /* ---------------------------------------------------------------- slots --- */ -static Slot slot_value(unsigned temp, FeIrType t) +Slot slot_value(unsigned temp, FeIrType t) { Slot s; s.is_place = 0; s.temp = temp; s.type = t; s.size = 0; @@ -176,21 +79,21 @@ static Slot slot_value(unsigned temp, FeIrType t) return s; } -static Slot slot_place(FeIrPlace p, FeIrType t, unsigned long size) +Slot slot_place(FeIrPlace p, FeIrType t, unsigned long size) { Slot s; s.is_place = 1; s.temp = 0; s.place = p; s.type = t; s.size = size; return s; } -static Slot slot_void(void) +Slot slot_void(void) { return slot_value(0, FE_IR_VOID); } /* Read a slot as a value. An aggregate has no value form, so asking for one is a lowering bug rather than a program error. */ -static unsigned as_value(Lower *L, Slot s, FeNode *n) +unsigned as_value(Lower *L, Slot s, FeNode *n) { if (!s.is_place) return s.temp; if (s.type == FE_IR_MEM) { fail(L, "an aggregate as a value", n); return 0; } @@ -198,7 +101,7 @@ static unsigned as_value(Lower *L, Slot s, FeNode *n) } /* The address of a slot. */ -static unsigned as_address(Lower *L, Slot s, FeNode *n) +unsigned as_address(Lower *L, Slot s, FeNode *n) { if (!s.is_place) { fail(L, "the address of a temporary", n); return 0; } return fe_ir_addr(L->m, L->b, s.place); @@ -207,14 +110,14 @@ static unsigned as_address(Lower *L, Slot s, FeNode *n) /* --------------------------------------------------------------- locals --- */ /* Does letting go of this type have to do something? */ -static int needs_release(const FeType *t) +int needs_release(const FeType *t) { if (!t) return 0; if (t->kind == FE_TYPE_OWNED) return 1; return t->has_drop != 0; } -static unsigned declare_var(Lower *L, const char *cname, const FeType *t, +unsigned declare_var(Lower *L, const char *cname, const FeType *t, const char *name) { unsigned local = fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t), @@ -239,7 +142,7 @@ static unsigned declare_var(Lower *L, const char *cname, const FeType *t, } /* The liveness flag beside a local, or none. */ -static int release_flag(Lower *L, unsigned local, unsigned *flag) +int release_flag(Lower *L, unsigned local, unsigned *flag) { unsigned i; for (i = L->owed_count; i > 0; --i) @@ -250,7 +153,7 @@ static int release_flag(Lower *L, unsigned local, unsigned *flag) return 0; } -static LowerVar *find_var(Lower *L, const char *cname) +LowerVar *find_var(Lower *L, const char *cname) { unsigned i; if (!cname) return 0; @@ -262,7 +165,7 @@ static LowerVar *find_var(Lower *L, const char *cname) /* --------------------------------------------------------------- blocks --- */ -static FeIrBlock *new_block(Lower *L) +FeIrBlock *new_block(Lower *L) { return fe_ir_block(L->m, L->fn); } @@ -270,7 +173,7 @@ static FeIrBlock *new_block(Lower *L) /* A check that must hold. `ok` is a condition; when it is false the program stops where it is. `--no-checks` removes the comparison and the branch, not just the message, which is the whole point of the flag. */ -static void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line) +void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line) { FeIrBlock *bad = new_block(L); FeIrBlock *cont = new_block(L); @@ -283,30 +186,26 @@ static void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line) /* A tag says which of the two things a wrapper holds. An optional is one byte at the front unless the payload has a spare representation; an error union is a two-byte error code, and zero means there is no error. */ -static FeIrType tag_type(const FeType *t) +FeIrType tag_type(const FeType *t) { return t && t->kind == FE_TYPE_ERROR_UNION ? FE_IR_I16 : FE_IR_I8; } -static int uses_niche(const FeType *t) +int uses_niche(const FeType *t) { return t && t->kind == FE_TYPE_OPTIONAL && fe_m7_optional_uses_niche(t->elem); } /* Somewhere to build an aggregate that has no home of its own yet. */ -static unsigned scratch(Lower *L, const FeType *t, const char *why) +unsigned scratch(Lower *L, const FeType *t, const char *why) { return fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t), ir_align(t), why); } -/* A slice is a pointer and a length, in that order. Both the compiler and the - runtime read it this way, so the offsets live here and nowhere else. */ -#define SLICE_PTR_OFFSET 0L -#define SLICE_LEN_OFFSET 4L /* The number of elements an indexable place holds, and where the first element is. An array is its own storage; a slice points at someone else's. */ -static void indexable_parts(Lower *L, Slot base, const FeType *t, +void indexable_parts(Lower *L, Slot base, const FeType *t, unsigned *data, unsigned *length, FeNode *n) { if (t && t->kind == FE_TYPE_ARRAY) { @@ -326,7 +225,7 @@ static void indexable_parts(Lower *L, Slot base, const FeType *t, /* ------------------------------------------------------- error codes ----- */ -static void note_error_name(Lower *L, const char *name) +void note_error_name(Lower *L, const char *name) { unsigned i; unsigned at; @@ -343,7 +242,7 @@ static void note_error_name(Lower *L, const char *name) ++L->error_count; } -static void collect_error_names(Lower *L, FeNode *n) +void collect_error_names(Lower *L, FeNode *n) { FeNode *x; if (!n) return; @@ -364,7 +263,7 @@ static void collect_error_names(Lower *L, FeNode *n) for (x = n->children; x; x = x->next) collect_error_names(L, x); } -static long error_code(Lower *L, const char *name) +long error_code(Lower *L, const char *name) { unsigned i; for (i = 0; i < L->error_count; ++i) @@ -374,7 +273,7 @@ static long error_code(Lower *L, const char *name) /* ---------------------------------------------------------- expressions --- */ -static FeIrOp binary_op(const char *op, int *is_cmp) +FeIrOp binary_op(const char *op, int *is_cmp) { *is_cmp = 0; if (!op) return FE_IR_ADD; @@ -399,7 +298,7 @@ static FeIrOp binary_op(const char *op, int *is_cmp) return FE_IR_ADD; } -static long literal_value(FeNode *n) +long literal_value(FeNode *n) { const char *s = n->text; long v = 0; @@ -443,7 +342,7 @@ static long literal_value(FeNode *n) /* `and` and `or` do not evaluate the right side unless they have to, so they are control flow rather than an operation. */ -static Slot lower_logical(Lower *L, FeNode *n, int is_and) +Slot lower_logical(Lower *L, FeNode *n, int is_and) { unsigned result = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "logical"); FeIrBlock *rhs = new_block(L); @@ -467,7 +366,7 @@ static Slot lower_logical(Lower *L, FeNode *n, int is_and) /* The builtins that are not calls at all: they are a constant, or they stop the program. `@print` is expanded separately because it becomes several calls rather than one thing. */ -static int lower_builtin(Lower *L, FeNode *n, Slot *out) +int lower_builtin(Lower *L, FeNode *n, Slot *out) { const char *name = n->text; if (!name || name[0] != '@') return 0; @@ -510,7 +409,7 @@ static int lower_builtin(Lower *L, FeNode *n, Slot *out) static const char *RT_ALLOC = "fe_rt_alloc"; static const char *RT_FREE = "fe_rt_free"; -static int is_mem_call(const FeNode *n, const char *what) +int is_mem_call(const FeNode *n, const char *what) { return n && n->a && n->a->kind == FE_N_MEMBER && n->a->a && n->a->a->kind == FE_N_IDENT && n->a->a->text && @@ -520,7 +419,7 @@ static int is_mem_call(const FeNode *n, const char *what) /* Build `!^T`: zero and the pointer when the allocation worked, the out-of-memory code when it did not. */ -static Slot allocation_result(Lower *L, FeNode *n, unsigned pointer) +Slot allocation_result(Lower *L, FeNode *n, unsigned pointer) { FeType *t = n->sem_type; unsigned local = scratch(L, t, "allocated"); @@ -550,7 +449,7 @@ static Slot allocation_result(Lower *L, FeNode *n, unsigned pointer) return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); } -static int lower_mem(Lower *L, FeNode *n, Slot *out) +int lower_mem(Lower *L, FeNode *n, Slot *out) { unsigned args[2]; if (is_mem_call(n, "create")) { @@ -658,1103 +557,11 @@ static int lower_mem(Lower *L, FeNode *n, Slot *out) return 0; } -static Slot lower_call(Lower *L, FeNode *n) -{ - unsigned args[16]; - unsigned count = 0; - FeNode *arg = n->children; - FeType *ret = n->sem_type; - FeIrType rt = ir_type(ret); - unsigned result_local = 0; - const char *callee = n->a && n->a->cname ? n->a->cname : - (n->sem_decl && n->sem_decl->cname ? - n->sem_decl->cname : 0); - { - Slot built; - if (lower_builtin(L, n, &built)) return built; - if (lower_mem(L, n, &built)) return built; - } - if (!callee) { fail(L, "a call with no target", n); return slot_void(); } - /* An aggregate result is written through a hidden first argument. */ - if (rt == FE_IR_MEM) { - result_local = fe_ir_local(L->m, L->fn, FE_IR_MEM, ir_size(ret), - ir_align(ret), "result"); - args[count++] = fe_ir_addr(L->m, L->b, fe_ir_at_local(result_local, 0)); - } - /* A method call passes what it was reached through as its first argument. - `self: Self` and `self: &Self` are the same thing here: the address of - the receiver, because an aggregate never travels in a register. */ - if (n->a && n->a->kind == FE_N_MEMBER && n->sem_decl) { - FeNode *first = n->sem_decl->a ? n->sem_decl->a->children : 0; - if (first && first->text && !strcmp(first->text, "self")) { - FeType *rt = n->a->a ? n->a->a->sem_type : 0; - Slot recv = lower_expr(L, n->a->a); - /* A receiver that is already a reference or an owner is a pointer - already; taking its address would pass a pointer to the - pointer. */ - if (rt && (rt->kind == FE_TYPE_REF || - (rt->kind == FE_TYPE_OWNED && ir_type(rt) == FE_IR_PTR))) - args[count++] = as_value(L, recv, n->a->a); - else - args[count++] = recv.is_place ? as_address(L, recv, n->a->a) - : recv.temp; - } - } - /* A generic call passes its type arguments first. They were consumed when - the instance was chosen and carry no value, so they are not passed. */ - { - FeNode *p; - for (p = n->sem_decl && n->sem_decl->a ? n->sem_decl->a->children : 0; - p && arg; p = p->next) { - if (!(p->flags & FE_NODE_COMPTIME)) break; - arg = arg->next; - } - } - for (; arg; arg = arg->next) { - Slot a = lower_expr(L, arg); - if (count >= 16) { fail(L, "too many arguments", n); break; } - args[count++] = a.type == FE_IR_MEM ? as_address(L, a, arg) - : as_value(L, a, arg); - } - if (rt == FE_IR_MEM) { - fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count); - return slot_place(fe_ir_at_local(result_local, 0), FE_IR_MEM, - ir_size(ret)); - } - if (rt == FE_IR_VOID) { - fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count); - return slot_void(); - } - return slot_value(fe_ir_call(L->m, L->b, rt, callee, args, count), rt); -} - -static Slot lower_expr_core(Lower *L, FeNode *n); - -/* Every expression may be standing where a wrapper is expected, so the wrap is - applied once, here, rather than at each place that could need it. */ -static Slot lower_expr(Lower *L, FeNode *n) -{ - Slot v; - if (!n || L->failed) return slot_void(); - v = lower_expr_core(L, n); - /* The checker marked the uses that hand ownership away. Where one names a - local we track, the value is no longer ours to release. */ - if ((n->flags & FE_OWN_NODE_CONSUMED) && n->kind == FE_N_IDENT) { - LowerVar *var = find_var(L, n->cname); - unsigned flag; - if (var && release_flag(L, var->local, &flag)) { - unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0); - fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), zero, FE_IR_I8); - } - } - return n->sem_context ? wrap_context(L, v, n) : v; -} - -static Slot lower_expr_core(Lower *L, FeNode *n) -{ - FeType *t; - FeIrType it; - if (!n || L->failed) return slot_void(); - t = n->sem_type; - it = ir_type(t); - switch (n->kind) { - case FE_N_LITERAL: - if (n->text && n->text[0] == '"') { - /* The bytes live in the image; the value is a pointer to them and - how many there are. The lexer keeps the quotes and the escapes, - so this is where ` -` becomes one byte. */ - char text[1024]; - unsigned long raw = strlen(n->text); - unsigned long len = 0; - unsigned long i; - const char *label; - unsigned local; - unsigned p; - unsigned c; - if (raw >= 2) raw -= 2; - for (i = 0; i < raw && len + 1 < sizeof text; ++i) { - char ch = n->text[1 + i]; - if (ch == 92 && i + 1 < raw) { /* a backslash */ - ++i; - switch (n->text[1 + i]) { - case 'n': ch = 10; break; - case 't': ch = 9; break; - case 'r': ch = 13; break; - case '0': ch = 0; break; - default: ch = n->text[1 + i]; break; - } - } - text[len++] = ch; - } - label = fe_ir_string(L->m, text, len); - if (!label) { fail(L, "a string literal", n); return slot_void(); } - local = scratch(L, t, "text"); - p = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0)); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), p, - FE_IR_PTR); - c = fe_ir_const(L->m, L->b, FE_IR_I32, (long)len); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), c, - FE_IR_I32); - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); - } - return slot_value(fe_ir_const(L->m, L->b, - it == FE_IR_VOID ? FE_IR_I32 : it, - literal_value(n)), - it == FE_IR_VOID ? FE_IR_I32 : it); - case FE_N_IDENT: { - LowerVar *var = find_var(L, n->cname); - if (var) { - if (var->by_address) { - unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR, - fe_ir_at_local(var->local, 0)); - return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t)); - } - return slot_place(fe_ir_at_local(var->local, 0), it, ir_size(t)); - } - if (n->cname) - return slot_place(fe_ir_at_global(n->cname, 0), it, ir_size(t)); - fail(L, "an unresolved name", n); - return slot_void(); - } - case FE_N_BINARY: { - int is_cmp = 0; - FeIrOp op; - unsigned a; - unsigned b; - FeIrType operand; - if (n->text && !strcmp(n->text, "orelse")) return lower_lazy(L, n, 0); - if (n->text && !strcmp(n->text, "catch")) return lower_lazy(L, n, 1); - if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or"))) - return lower_logical(L, n, !strcmp(n->text, "and")); - op = binary_op(n->text, &is_cmp); - operand = ir_type(n->a ? n->a->sem_type : 0); - if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32; - a = as_value(L, lower_expr(L, n->a), n->a); - b = as_value(L, lower_expr(L, n->b), n->b); - return slot_value(fe_ir_binary(L->m, L->b, op, operand, a, b, - type_is_unsigned(n->a ? n->a->sem_type - : 0)), - is_cmp ? FE_IR_I8 : operand); - } - case FE_N_UNARY: - if (n->text && !strcmp(n->text, "try")) return lower_try(L, n); - if (n->text && !strcmp(n->text, "-")) { - unsigned zero = fe_ir_const(L->m, L->b, it, 0); - unsigned v = as_value(L, lower_expr(L, n->a), n->a); - return slot_value(fe_ir_binary(L->m, L->b, FE_IR_SUB, it, zero, v, - 0), it); - } - if (n->text && !strcmp(n->text, "not")) { - unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0); - unsigned v = as_value(L, lower_expr(L, n->a), n->a); - return slot_value(fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I8, v, - zero, 0), FE_IR_I8); - } - if (n->text && (!strcmp(n->text, "&") || !strcmp(n->text, "&mut"))) { - Slot inner = lower_expr(L, n->a); - return slot_value(as_address(L, inner, n->a), FE_IR_PTR); - } - fail(L, "this unary operator", n); - return slot_void(); - case FE_N_MEMBER: - /* A payload-free variant used as a value is just its tag. */ - if (t && t->kind == FE_TYPE_ENUM && !enum_has_payload(t) && - n->b && n->b->text) { - FeVariantType *v = fe_type_variant(t, n->b->text); - if (v) - return slot_value(fe_ir_const(L->m, L->b, ir_type(t), - (long)v->tag), ir_type(t)); - } - /* `error.Name` is a member of the open default set: a code, and - nothing to look up. */ - if (n->a && n->a->kind == FE_N_IDENT && n->a->text && - !strcmp(n->a->text, "error") && n->b && n->b->text) - return slot_value(fe_ir_const(L->m, L->b, FE_IR_I16, - error_code(L, n->b->text)), - FE_IR_I16); - /* `.?` is the payload of an optional the checker already proved is - there. */ - if (n->text && !strcmp(n->text, ".?")) { - FeType *bt = n->a ? n->a->sem_type : 0; - return wrapper_payload(L, lower_expr(L, n->a), bt); - } - /* `p.^` reads through a pointer -- except for an owned slice, whose - pointer and length are the value itself, so there is nothing to - step through. */ - if (n->text && !strcmp(n->text, ".^")) { - Slot base = lower_expr(L, n->a); - unsigned p; - if (base.type == FE_IR_MEM) - return slot_place(base.place, it, ir_size(t)); - p = as_value(L, base, n->a); - return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t)); - } - /* `.n` is how many elements there are, which an array knows at - compile time and a slice carries beside its pointer. */ - if (n->b && n->b->text && !strcmp(n->b->text, "n")) { - FeType *bt = n->a ? n->a->sem_type : 0; - Slot base; - if (bt && bt->kind == FE_TYPE_ARRAY) - return slot_value(fe_ir_const(L->m, L->b, FE_IR_I32, - (long)bt->length), FE_IR_I32); - base = lower_expr(L, n->a); - if (!base.is_place) { fail(L, "a length of a temporary", n); return slot_void(); } - base.place.offset += SLICE_LEN_OFFSET; - return slot_place(base.place, FE_IR_I32, 4); - } - /* A field is a constant offset from the base. */ - { - FeType *base = n->a ? n->a->sem_type : 0; - FeFieldType *field; - Slot b; - if (base && (base->kind == FE_TYPE_REF || - base->kind == FE_TYPE_OWNED)) base = base->elem; - field = fe_type_field(base, n->b && n->b->text ? n->b->text : ""); - if (!field) { fail(L, "an unresolved field", n); return slot_void(); } - b = lower_expr(L, n->a); - if (n->a->sem_type && (n->a->sem_type->kind == FE_TYPE_REF || - n->a->sem_type->kind == FE_TYPE_OWNED)) { - unsigned p = as_value(L, b, n->a); - return slot_place(fe_ir_at_temp(p, (long)field->offset), it, - ir_size(t)); - } - if (!b.is_place) { fail(L, "a field of a temporary", n); return slot_void(); } - b.place.offset += (long)field->offset; - return slot_place(b.place, it, ir_size(t)); - } - case FE_N_INDEX: { - FeType *bt = n->a ? n->a->sem_type : 0; - FeType *elem = bt ? bt->elem : 0; - Slot base; - unsigned data; - unsigned length; - unsigned index; - unsigned scale; - unsigned offset; - unsigned addr; - if (n->flags & FE_NODE_SLICE) return lower_slice(L, n); - base = lower_expr(L, n->a); - indexable_parts(L, base, bt, &data, &length, n); - index = as_value(L, lower_expr(L, n->b), n->b); - if (!L->c->no_checks) { - unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, - index, length, 1); - guard(L, ok, FE_TRAP_BOUNDS, n->loc.line); - } - scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem)); - offset = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, index, scale, 1); - addr = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, offset, 1); - return slot_place(fe_ir_at_temp(addr, 0), ir_type(elem), ir_size(elem)); - } - case FE_N_ARRAY_INIT: { - unsigned local = scratch(L, t, "array"); - FeType *elem = t ? t->elem : 0; - unsigned long step = ir_size(elem); - long at = 0; - FeNode *x; - for (x = n->children; x; x = x->next) { - Slot v = lower_expr(L, x); - store_into(L, fe_ir_at_local(local, at), v, x, step); - at += (long)step; - } - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); - } - case FE_N_STRUCT_INIT: { - unsigned local = scratch(L, t, "struct"); - FeNode *f; - for (f = n->children; f; f = f->next) { - FeFieldType *field; - Slot v; - if (f->kind != FE_N_FIELD) continue; - field = fe_type_field(t, f->text); - if (!field) { fail(L, "an unresolved field", f); return slot_void(); } - v = lower_expr(L, f->a); - store_into(L, fe_ir_at_local(local, (long)field->offset), v, f, - ir_size(field->type)); - } - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); - } - case FE_N_CALL: - return lower_call(L, n); - case FE_N_TYPE: - /* `x as T`: the operand is `a` and the target type is the node's own. - Between integers this only changes how wide the value is and whether - the top bits repeat the sign. */ - if (n->a) { - FeType *from = n->a->sem_type; - unsigned v = as_value(L, lower_expr(L, n->a), n->a); - if (ir_type(from) == it) return slot_value(v, it); - return slot_value(fe_ir_cast(L->m, L->b, ir_type(from), it, v, - type_is_unsigned(from)), it); - } - fail(L, "this type expression", n); - return slot_void(); - case FE_N_EXPR: - return lower_expr(L, n->a); - default: - fail(L, "this expression", n); - return slot_void(); - } -} - -/* The link name of the `drop` method for this type, found through the instance - the checker recorded. */ -static const char *drop_name(Lower *L, const FeType *t) -{ - unsigned i; - FeNode *method = 0; - if (!t || !t->decl_node) return 0; - for (method = t->decl_node->children; method; method = method->next) - if (method->kind == FE_N_FN && method->text && - !strcmp(method->text, "drop")) break; - if (!method) return 0; - for (i = 0; i < L->c->instance_count; ++i) - if (L->c->instances[i].decl == method && - L->c->instances[i].owner == t) - return L->c->instances[i].cname; - return method->cname; -} - -/* Settle what a scope owes, most recent first. A `return` in the middle of a - function still owes everything, so every exit path calls this. */ -static void run_deferred(Lower *L, unsigned from) -{ - unsigned i; - for (i = L->owed_count; i > from; --i) { - if (L->owed[i - 1].block) { - lower_stmt(L, L->owed[i - 1].block); - continue; - } - { - /* Release only where the value is still here. */ - unsigned live = fe_ir_load(L->m, L->b, FE_IR_I8, - fe_ir_at_local(L->owed[i - 1].flag, 0)); - FeIrBlock *doit = new_block(L); - FeIrBlock *skip = new_block(L); - unsigned args[1]; - FeType *t = L->owed[i - 1].type; - fe_ir_br(L->b, live, doit->id, skip->id); - L->b = doit; - if (t && t->kind == FE_TYPE_OWNED && t->elem && - t->elem->kind == FE_TYPE_SLICE) { - FeIrPlace at = fe_ir_at_local(L->owed[i - 1].local, - SLICE_PTR_OFFSET); - args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, at); - } else { - args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, - fe_ir_at_local(L->owed[i - 1].local, 0)); - } - if (t && t->has_drop) { - /* A type that says how to let go of itself is asked to; the - name is the one its instance was given. */ - const char *how = drop_name(L, t); - args[0] = fe_ir_addr(L->m, L->b, - fe_ir_at_local(L->owed[i - 1].local, 0)); - if (how) fe_ir_call(L->m, L->b, FE_IR_VOID, how, args, 1); - } else { - fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1); - } - fe_ir_jmp(L->b, skip->id); - L->b = skip; - } - } -} - -/* ------------------------------------------------------- wrappers -------- * - * An optional is a tag and a payload; an error union is an error code and a - * payload, where a code of zero means there is no error. Both are memory, and - * both are built the same way: write the tag, then write the value after it. +/* ------------------------------------------------------------ printing --- * + * SPEC 6.3.1: the formatting builtins are not variadic functions. A call is + * expanded here into one write per literal chunk and one per value, so the + * language never grows a variadic calling convention and the format string is + * gone by the time anything runs. * -------------------------------------------------------------------------- */ -static Slot wrap_context(Lower *L, Slot v, FeNode *n) -{ - FeType *want = n->sem_context; - unsigned local; - long payload_at; - if (!want) return v; - local = scratch(L, want, "wrapped"); - payload_at = (long)fe_type_payload_offset(want); - if (want->kind == FE_TYPE_OPTIONAL) { - if (fe_m7_is_null(n)) { - /* A payload with a spare representation uses it for "nothing" - instead of carrying a separate tag. */ - unsigned z = fe_ir_const(L->m, L->b, - uses_niche(want) ? FE_IR_PTR : FE_IR_I8, 0); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), z, - uses_niche(want) ? FE_IR_PTR : FE_IR_I8); - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); - } - if (!uses_niche(want)) { - unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), one, FE_IR_I8); - } - store_into(L, fe_ir_at_local(local, payload_at), v, n, - ir_size(want->elem)); - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); - } - if (want->kind == FE_TYPE_ERROR_UNION) { - FeType *value_type = want->error_value; - if (n->sem_type && n->sem_type->is_error) { - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), - as_value(L, v, n), FE_IR_I16); - } else { - unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), zero, FE_IR_I16); - if (value_type && value_type->kind != FE_TYPE_VOID) - store_into(L, fe_ir_at_local(local, payload_at), v, n, - ir_size(value_type)); - } - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); - } - return v; -} - -/* The tag of a wrapper that is already in memory. */ -static unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n) -{ - FeIrPlace p; - if (!w.is_place) { fail(L, "a wrapper with no place", n); return 0; } - p = w.place; - if (uses_niche(t)) return fe_ir_load(L->m, L->b, FE_IR_PTR, p); - return fe_ir_load(L->m, L->b, tag_type(t), p); -} - -static Slot wrapper_payload(Lower *L, Slot w, const FeType *t) -{ - FeType *payload = t ? (t->kind == FE_TYPE_ERROR_UNION ? t->error_value - : t->elem) : 0; - FeIrPlace p = w.place; - (void)L; - p.offset += (long)fe_type_payload_offset(t); - return slot_place(p, ir_type(payload), ir_size(payload)); -} - -/* Leave the function with this error code, after the deferred blocks. */ -static void return_error(Lower *L, unsigned err, FeNode *n) -{ - FeType *ret = L->ret_type; - unsigned local = scratch(L, ret, "failure"); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), err, FE_IR_I16); - run_deferred(L, 0); - if (L->fn->returns_by_address) { - unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, - fe_ir_at_local(L->ret_local, 0)); - fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0), fe_ir_at_local(local, 0), - ir_size(ret)); - fe_ir_ret(L->b, 0, 0); - return; - } - fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(ret), - fe_ir_at_local(local, 0)), 1); - (void)n; -} - -/* `try e` -- if e failed, leave with its error; otherwise the value. */ -static Slot lower_try(Lower *L, FeNode *n) -{ - FeType *t = n->a ? n->a->sem_type : 0; - Slot e = lower_expr(L, n->a); - unsigned err = wrapper_tag(L, e, t, n); - unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0); - unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I16, err, zero, 1); - FeIrBlock *bad = new_block(L); - FeIrBlock *good = new_block(L); - fe_ir_br(L->b, ok, good->id, bad->id); - L->b = bad; - return_error(L, err, n); - L->b = good; - return wrapper_payload(L, e, t); -} - -/* `e orelse d` and `e catch d` both mean "the value, or that instead". The - right-hand side is only evaluated when it is needed, so it is a branch. */ -static Slot lower_lazy(Lower *L, FeNode *n, int is_catch) -{ - FeType *t = n->a ? n->a->sem_type : 0; - FeType *payload = t ? (is_catch ? t->error_value : t->elem) : 0; - Slot e; - unsigned tag; - unsigned zero; - unsigned ok; - unsigned result; - FeIrBlock *other; - FeIrBlock *join; - FeIrBlock *have; - e = lower_expr(L, n->a); - tag = wrapper_tag(L, e, t, n); - zero = fe_ir_const(L->m, L->b, is_catch || uses_niche(t) ? FE_IR_PTR - : FE_IR_I8, 0); - /* An error union is fine when its code is zero; an optional is fine when - its tag is not. */ - ok = fe_ir_binary(L->m, L->b, is_catch ? FE_IR_EQ : FE_IR_NE, - is_catch ? FE_IR_I16 : (uses_niche(t) ? FE_IR_PTR - : FE_IR_I8), - tag, zero, 1); - result = scratch(L, payload, "result"); - have = new_block(L); - other = new_block(L); - join = new_block(L); - fe_ir_br(L->b, ok, have->id, other->id); - L->b = have; - store_into(L, fe_ir_at_local(result, 0), wrapper_payload(L, e, t), n, - ir_size(payload)); - fe_ir_jmp(L->b, join->id); - L->b = other; - if (is_catch && n->c) { - /* The block form handles the error and must not fall through with a - value, so whatever it leaves behind is what the checker allowed. */ - lower_stmt(L, n->c); - } else { - Slot d = lower_expr(L, n->b); - store_into(L, fe_ir_at_local(result, 0), d, n->b, ir_size(payload)); - } - fe_ir_jmp(L->b, join->id); - L->b = join; - return slot_place(fe_ir_at_local(result, 0), ir_type(payload), - ir_size(payload)); -} - -/* ----------------------------------------------------------- statements --- */ - -static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, - unsigned long size) -{ - if (value.type == FE_IR_MEM) { - if (!value.is_place) { fail(L, "an aggregate value", n); return; } - fe_ir_copy(L->m, L->b, dst, value.place, size); - return; - } - fe_ir_store(L->m, L->b, dst, as_value(L, value, n), value.type); -} - -static void lower_return(Lower *L, FeNode *n) -{ - Slot v; - if (!n->a) { - /* A bare return from a `!void` function still has to say that nothing - went wrong. */ - if (L->ret_type && L->ret_type->kind == FE_TYPE_ERROR_UNION) { - unsigned local = scratch(L, L->ret_type, "success"); - unsigned none = fe_ir_const(L->m, L->b, FE_IR_I16, 0); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), none, FE_IR_I16); - run_deferred(L, 0); - if (L->fn->returns_by_address) { - unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, - fe_ir_at_local(L->ret_local, 0)); - fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0), - fe_ir_at_local(local, 0), ir_size(L->ret_type)); - fe_ir_ret(L->b, 0, 0); - return; - } - fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(L->ret_type), - fe_ir_at_local(local, 0)), 1); - return; - } - run_deferred(L, 0); - fe_ir_ret(L->b, 0, 0); - return; - } - /* The value is computed before the deferred blocks run, because they may - destroy what it was read from. */ - v = lower_expr(L, n->a); - if (v.type != FE_IR_MEM && v.is_place) - v = slot_value(as_value(L, v, n->a), v.type); - run_deferred(L, 0); - if (L->fn->returns_by_address) { - unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, - fe_ir_at_local(L->ret_local, 0)); - store_into(L, fe_ir_at_temp(dst, 0), v, n, ir_size(L->ret_type)); - fe_ir_ret(L->b, 0, 0); - return; - } - fe_ir_ret(L->b, as_value(L, v, n->a), 1); -} - -static void lower_if(Lower *L, FeNode *n) -{ - FeIrBlock *then_b = new_block(L); - FeIrBlock *else_b = n->c ? new_block(L) : 0; - FeIrBlock *join = new_block(L); - unsigned cond = as_value(L, lower_expr(L, n->a), n->a); - fe_ir_br(L->b, cond, then_b->id, else_b ? else_b->id : join->id); - L->b = then_b; - lower_stmt(L, n->b); - fe_ir_jmp(L->b, join->id); - if (else_b) { - L->b = else_b; - lower_stmt(L, n->c); - fe_ir_jmp(L->b, join->id); - } - L->b = join; -} - -static void lower_while(Lower *L, FeNode *n) -{ - FeIrBlock *head = new_block(L); - FeIrBlock *body = new_block(L); - FeIrBlock *done = new_block(L); - unsigned cond; - fe_ir_jmp(L->b, head->id); - L->b = head; - cond = as_value(L, lower_expr(L, n->a), n->a); - fe_ir_br(L->b, cond, body->id, done->id); - if (L->loop_depth < 32) { - L->break_target[L->loop_depth] = done->id; - L->continue_target[L->loop_depth] = head->id; - ++L->loop_depth; - } - L->b = body; - lower_stmt(L, n->b); - fe_ir_jmp(L->b, head->id); - if (L->loop_depth) --L->loop_depth; - L->b = done; -} - -/* `x[a..b]` makes a pointer and a length out of part of something indexable. - Both ends are checked -- against each other and against what is there -- - before the pointer is formed. An empty slice of a valid range is fine; one - that starts past its end is not. */ -static Slot lower_slice(Lower *L, FeNode *n) -{ - FeType *bt = n->a ? n->a->sem_type : 0; - FeType *elem = bt ? bt->elem : 0; - FeType *t = n->sem_type; - Slot base = lower_expr(L, n->a); - unsigned data; - unsigned length; - unsigned from; - unsigned to; - unsigned local; - unsigned scale; - unsigned off; - unsigned at; - unsigned count; - indexable_parts(L, base, bt, &data, &length, n); - from = n->b ? as_value(L, lower_expr(L, n->b), n->b) - : fe_ir_const(L->m, L->b, FE_IR_I32, 0); - to = n->c ? as_value(L, lower_expr(L, n->c), n->c) : length; - if (!L->c->no_checks) { - unsigned ordered = fe_ir_binary(L->m, L->b, FE_IR_LE, FE_IR_I32, - from, to, 1); - unsigned within; - guard(L, ordered, FE_TRAP_BOUNDS, n->loc.line); - within = fe_ir_binary(L->m, L->b, FE_IR_LE, FE_IR_I32, to, length, 1); - guard(L, within, FE_TRAP_BOUNDS, n->loc.line); - } - scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem)); - off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, from, scale, 1); - at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, off, 1); - count = fe_ir_binary(L->m, L->b, FE_IR_SUB, FE_IR_I32, to, from, 1); - local = scratch(L, t, "slice"); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), at, - FE_IR_PTR); - fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), count, - FE_IR_I32); - return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); -} - -/* Three shapes share the keyword. - - for i in a..b { } counts - for x in thing { } walks, binding a reference to each element - for i, x in thing { } walks, binding the position as well - - The count is read once before the body, so a thing that grows underneath the - loop cannot walk past what was measured. The element binding is a reference - (`x.^` reads it), which is what lets a loop write back into the thing. */ -static void lower_for(Lower *L, FeNode *n) -{ - FeIrBlock *head; - FeIrBlock *body; - FeIrBlock *step; - FeIrBlock *done; - unsigned counter; - unsigned limit; - - if (n->c) { - /* The counting form: the variable is the count itself. */ - unsigned from = as_value(L, lower_expr(L, n->a), n->a); - unsigned to; - counter = declare_var(L, n->cname, 0, n->text); - L->fn->locals[counter].type = FE_IR_I32; - L->fn->locals[counter].size = 4; - L->fn->locals[counter].align = 4; - fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), from, FE_IR_I32); - to = as_value(L, lower_expr(L, n->c), n->c); - limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "limit"); - fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), to, FE_IR_I32); - head = new_block(L); - body = new_block(L); - step = new_block(L); - done = new_block(L); - fe_ir_jmp(L->b, head->id); - L->b = head; - { - unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, - fe_ir_at_local(counter, 0)); - unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32, - fe_ir_at_local(limit, 0)); - unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1); - fe_ir_br(L->b, more, body->id, done->id); - } - } else { - FeType *bt = n->a ? n->a->sem_type : 0; - FeType *elem = bt ? bt->elem : 0; - Slot base = lower_expr(L, n->a); - unsigned data; - unsigned length; - unsigned data_local; - unsigned item; - indexable_parts(L, base, bt, &data, &length, n); - data_local = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, "data"); - fe_ir_store(L->m, L->b, fe_ir_at_local(data_local, 0), data, FE_IR_PTR); - limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "count"); - fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), length, FE_IR_I32); - /* With two names the first is the position and the second the element; - with one it is the element. */ - counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index"); - if (n->aux_cname) { - L->vars[L->var_count].cname = n->cname; - L->vars[L->var_count].local = counter; - L->vars[L->var_count].by_address = 0; - if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; - item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->aux_text); - L->vars[L->var_count].cname = n->aux_cname; - L->vars[L->var_count].local = item; - L->vars[L->var_count].by_address = 0; - if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; - } else { - item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->text); - L->vars[L->var_count].cname = n->cname; - L->vars[L->var_count].local = item; - L->vars[L->var_count].by_address = 0; - if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; - } - { - unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I32, 0); - fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), zero, FE_IR_I32); - } - head = new_block(L); - body = new_block(L); - step = new_block(L); - done = new_block(L); - fe_ir_jmp(L->b, head->id); - L->b = head; - { - unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, - fe_ir_at_local(counter, 0)); - unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32, - fe_ir_at_local(limit, 0)); - unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1); - fe_ir_br(L->b, more, body->id, done->id); - } - L->b = body; - { - unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, - fe_ir_at_local(counter, 0)); - unsigned scale = fe_ir_const(L->m, L->b, FE_IR_I32, - (long)ir_size(elem)); - unsigned off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, i, - scale, 1); - unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR, - fe_ir_at_local(data_local, 0)); - unsigned at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, p, - off, 1); - fe_ir_store(L->m, L->b, fe_ir_at_local(item, 0), at, FE_IR_PTR); - } - L->b = head; - } - - if (L->loop_depth < 32) { - L->break_target[L->loop_depth] = done->id; - L->continue_target[L->loop_depth] = step->id; - ++L->loop_depth; - } - L->b = body; - lower_stmt(L, n->b); - fe_ir_jmp(L->b, step->id); - L->b = step; - { - unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, - fe_ir_at_local(counter, 0)); - unsigned one = fe_ir_const(L->m, L->b, FE_IR_I32, 1); - unsigned next = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_I32, i, one, 1); - fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), next, FE_IR_I32); - } - fe_ir_jmp(L->b, head->id); - if (L->loop_depth) --L->loop_depth; - L->b = done; -} - -/* `match` over a payload-free enum or an integer: compare the tag against each - arm's pattern in turn. The checker already proved the arms cover everything, - so falling off the end cannot happen in a program that compiled -- but the - generated code has to go somewhere, and going to the join is right. */ -static void lower_match(Lower *L, FeNode *n) -{ - FeType *t = n->a ? n->a->sem_type : 0; - FeIrType it = ir_type(t); - Slot subject = lower_expr(L, n->a); - unsigned value; - FeIrBlock *join; - FeNode *arm; - if (it == FE_IR_MEM) { fail(L, "a match over a payload", n); return; } - value = as_value(L, subject, n->a); - join = new_block(L); - for (arm = n->children; arm; arm = arm->next) { - FeIrBlock *body; - FeIrBlock *next; - unsigned want; - unsigned same; - FeVariantType *v; - if (arm->kind != FE_N_ARM) continue; - if (arm->text && !strcmp(arm->text, "_")) { - lower_stmt(L, arm->a); - fe_ir_jmp(L->b, join->id); - L->b = join; - return; - } - v = t && t->kind == FE_TYPE_ENUM && arm->text - ? fe_type_variant(t, arm->text) : 0; - want = fe_ir_const(L->m, L->b, it, - v ? (long)v->tag : literal_value(arm)); - same = fe_ir_binary(L->m, L->b, FE_IR_EQ, it, value, want, 1); - body = new_block(L); - next = new_block(L); - fe_ir_br(L->b, same, body->id, next->id); - L->b = body; - lower_stmt(L, arm->a); - fe_ir_jmp(L->b, join->id); - L->b = next; - } - fe_ir_jmp(L->b, join->id); - L->b = join; -} - -static void lower_stmt(Lower *L, FeNode *n) -{ - FeNode *x; - if (!n || L->failed) return; - switch (n->kind) { - case FE_N_BLOCK: { - unsigned outer = L->owed_count; - for (x = n->children; x; x = x->next) lower_stmt(L, x); - /* Leaving a block normally settles what it owes. An exit that jumped - away already settled on its way out. */ - if (!L->b->terminated) run_deferred(L, outer); - L->owed_count = outer; - return; - } - case FE_N_LET: - case FE_N_VAR: - case FE_N_CONST: { - unsigned local = declare_var(L, n->cname, n->sem_type, n->text); - if (n->b) { - Slot v = lower_expr(L, n->b); - unsigned flag; - store_into(L, fe_ir_at_local(local, 0), v, n, ir_size(n->sem_type)); - if (release_flag(L, local, &flag)) { - unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1); - fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), one, FE_IR_I8); - } - } - return; - } - case FE_N_ASSIGN: { - Slot dst = lower_expr(L, n->a); - Slot v = lower_expr(L, n->b); - if (!dst.is_place) { fail(L, "an assignment to a value", n); return; } - store_into(L, dst.place, v, n, dst.size); - return; - } - case FE_N_EXPR_STMT: - lower_expr(L, n->a); - return; - case FE_N_RETURN: - lower_return(L, n); - return; - case FE_N_IF: - lower_if(L, n); - return; - case FE_N_WHILE: - lower_while(L, n); - return; - case FE_N_BREAK: - if (L->loop_depth) fe_ir_jmp(L->b, L->break_target[L->loop_depth - 1]); - return; - case FE_N_CONTINUE: - if (L->loop_depth) - fe_ir_jmp(L->b, L->continue_target[L->loop_depth - 1]); - return; - case FE_N_UNSAFE: - lower_stmt(L, n->a); - return; - case FE_N_DEFER: - if (L->owed_count < 64) { - L->owed[L->owed_count].block = n->a; - L->owed[L->owed_count].local = 0; - L->owed[L->owed_count].flag = 0; - L->owed[L->owed_count].type = 0; - ++L->owed_count; - } - return; - case FE_N_FOR: - lower_for(L, n); - return; - case FE_N_MATCH: - lower_match(L, n); - return; - default: - fail(L, "this statement", n); - return; - } -} - -/* ------------------------------------------------------------ functions --- */ - -/* A global is static storage. SPEC 7.1: its initializer is evaluated at - compile time, so what reaches here is either a constant to place in the - image or nothing, and the storage starts as zeroes. */ -static void lower_global(Lower *L, FeNode *n) -{ - FeType *t = n->sem_type; - unsigned char *init = 0; - unsigned long size = ir_size(t); - if (!n->cname) return; - if (n->b && n->b->kind == FE_N_LITERAL && size && size <= 8) { - long v = literal_value(n->b); - unsigned long i; - init = (unsigned char *)fe_arena_alloc(&L->m->arena, (size_t)size); - if (init) - for (i = 0; i < size; ++i) - init[i] = (unsigned char)((v >> (i * 8)) & 0xFF); - } - fe_ir_global(L->m, n->cname, ir_type(t), size, ir_align(t), init); -} - -static int fn_is_generic(const FeNode *fn) -{ - FeNode *p; - if (!fn) return 0; - for (p = fn->a ? fn->a->children : 0; p; p = p->next) - if (p->flags & FE_NODE_COMPTIME) return 1; - return 0; -} - -static void lower_fn_as(Lower *L, FeNode *fn, const char *name) -{ - FeNode *p; - FeType *ret = fn->b ? fe_type_from_ast(&L->c->types, fn->b) : 0; - FeIrFunc *f; - if (!name) return; - f = fe_ir_func(L->m, name, ir_type(ret), ir_size(ret)); - if (!f) return; - L->fn = f; - L->ret_type = ret; - L->var_count = 0; - L->loop_depth = 0; - /* A hidden first parameter holds where an aggregate result goes. */ - if (f->returns_by_address) - L->ret_local = fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, "result"); - for (p = fn->a ? fn->a->children : 0; p; p = p->next) { - FeType *pt; - int by_address; - unsigned local; - /* A comptime parameter was consumed at compile time; it has no - storage and takes no argument slot. */ - if (p->flags & FE_NODE_COMPTIME) continue; - pt = fe_type_from_ast(&L->c->types, p->a); - /* An aggregate parameter arrives as an address. */ - by_address = ir_type(pt) == FE_IR_MEM; - local = by_address - ? fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, p->text) - : fe_ir_local(L->m, f, ir_type(pt), ir_size(pt), ir_align(pt), - p->text); - if (L->var_count < LOWER_MAX_LOCALS) { - L->vars[L->var_count].cname = p->cname; - L->vars[L->var_count].local = local; - L->vars[L->var_count].by_address = by_address; - ++L->var_count; - } - } - f->param_count = f->local_count; - L->b = fe_ir_block(L->m, f); - lower_stmt(L, fn->c); - /* A void function may just run off the end. */ - fe_ir_ret(L->b, 0, 0); -} - -static void lower_fn(Lower *L, FeNode *fn) -{ - lower_fn_as(L, fn, fn->cname); -} - -int fe_lower_program(FeCheck *c, FeIrModule *out) -{ - Lower L; - unsigned u; - FeNode *n; - memset(&L, 0, sizeof L); - L.c = c; - L.m = out; - /* The codes have to be known while the bodies are lowered, so the names - are gathered from the whole build first. */ - for (u = 0; u < c->build->count; ++u) - collect_error_names(&L, c->build->units[u].ast.root); - for (u = 0; u < c->build->count; ++u) { - FeUnit *unit = &c->build->units[u]; - c->ast = &unit->ast; - c->unit = unit; - c->types.unit_name = unit->name[0] ? unit->name : "unit"; - if (!out->unit_file || !out->unit_file[0]) out->unit_file = unit->path; - for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) - if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) - lower_global(&L, n); - else if (n->kind == FE_N_FN && !n->c) { - /* A declaration with no body is something the linker will - find: the runtime, or a C library. */ - FeType *ret = n->b ? fe_type_from_ast(&c->types, n->b) : 0; - FeIrFunc *f; - if (!n->cname) continue; - f = fe_ir_func(out, n->cname, ir_type(ret), ir_size(ret)); - if (f) f->is_extern = 1; - } - else if (n->kind == FE_N_FN && n->c && !fn_is_generic(n)) { - lower_fn(&L, n); - /* The entry unit is the one the build was rooted at. */ - if (u == 0 && n->text && !strcmp(n->text, "main")) - out->entry_main = n->cname; - } - } - /* Each instance the checker reached is a function of its own: the same - body, read with different types bound, under its own link name. This is - where monomorphisation actually produces code -- the front end only - decided which instances exist. */ - for (u = 0; u < c->instance_count && !L.failed; ++u) { - FeInstance *inst = &c->instances[u]; - FeUnit *home; - FeTypeBind save[FE_TYPE_PARAM_MAX]; - unsigned save_count; - unsigned k; - if (!inst->decl || !inst->decl->c || !inst->cname || !inst->home) - continue; - home = 0; - for (k = 0; k < c->build->count; ++k) - if (!strcmp(c->build->units[k].name, inst->home)) - home = &c->build->units[k]; - if (!home) continue; - c->ast = &home->ast; - c->unit = home; - c->types.unit_name = home->name; - save_count = c->types.param_count; - for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) save[k] = c->types.params[k]; - c->types.param_count = inst->bind_count; - for (k = 0; k < inst->bind_count && k < FE_TYPE_PARAM_MAX; ++k) - c->types.params[k] = inst->binds[k]; - lower_fn_as(&L, inst->decl, inst->cname); - c->types.param_count = save_count; - for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) c->types.params[k] = save[k]; - } - return !L.failed; -} +/* Write `len` bytes of a literal that is already in the image. */ diff --git a/fec/src/lowerexp.c b/fec/src/lowerexp.c new file mode 100644 index 0000000..e43cfc5 --- /dev/null +++ b/fec/src/lowerexp.c @@ -0,0 +1,468 @@ +#include "lowerpri.h" + +Slot lower_expr_core(Lower *L, FeNode *n) +{ + FeType *t; + FeIrType it; + if (!n || L->failed) return slot_void(); + t = n->sem_type; + it = ir_type(t); + switch (n->kind) { + case FE_N_LITERAL: + if (n->text && n->text[0] == '"') { + /* The bytes live in the image; the value is a pointer to them and + how many there are. The lexer keeps the quotes and the escapes, + so this is where ` +` becomes one byte. */ + char text[1024]; + unsigned long raw = strlen(n->text); + unsigned long len = 0; + unsigned long i; + const char *label; + unsigned local; + unsigned p; + unsigned c; + if (raw >= 2) raw -= 2; + for (i = 0; i < raw && len + 1 < sizeof text; ++i) { + char ch = n->text[1 + i]; + if (ch == 92 && i + 1 < raw) { /* a backslash */ + ++i; + switch (n->text[1 + i]) { + case 'n': ch = 10; break; + case 't': ch = 9; break; + case 'r': ch = 13; break; + case '0': ch = 0; break; + default: ch = n->text[1 + i]; break; + } + } + text[len++] = ch; + } + label = fe_ir_string(L->m, text, len); + if (!label) { fail(L, "a string literal", n); return slot_void(); } + local = scratch(L, t, "text"); + p = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0)); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), p, + FE_IR_PTR); + c = fe_ir_const(L->m, L->b, FE_IR_I32, (long)len); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), c, + FE_IR_I32); + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); + } + return slot_value(fe_ir_const(L->m, L->b, + it == FE_IR_VOID ? FE_IR_I32 : it, + literal_value(n)), + it == FE_IR_VOID ? FE_IR_I32 : it); + case FE_N_IDENT: { + LowerVar *var = find_var(L, n->cname); + if (var) { + if (var->by_address) { + unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(var->local, 0)); + return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t)); + } + return slot_place(fe_ir_at_local(var->local, 0), it, ir_size(t)); + } + if (n->cname) + return slot_place(fe_ir_at_global(n->cname, 0), it, ir_size(t)); + fail(L, "an unresolved name", n); + return slot_void(); + } + case FE_N_BINARY: { + int is_cmp = 0; + FeIrOp op; + unsigned a; + unsigned b; + FeIrType operand; + if (n->text && !strcmp(n->text, "orelse")) return lower_lazy(L, n, 0); + if (n->text && !strcmp(n->text, "catch")) return lower_lazy(L, n, 1); + if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or"))) + return lower_logical(L, n, !strcmp(n->text, "and")); + op = binary_op(n->text, &is_cmp); + operand = ir_type(n->a ? n->a->sem_type : 0); + if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32; + a = as_value(L, lower_expr(L, n->a), n->a); + b = as_value(L, lower_expr(L, n->b), n->b); + return slot_value(fe_ir_binary(L->m, L->b, op, operand, a, b, + type_is_unsigned(n->a ? n->a->sem_type + : 0)), + is_cmp ? FE_IR_I8 : operand); + } + case FE_N_UNARY: + if (n->text && !strcmp(n->text, "try")) return lower_try(L, n); + if (n->text && !strcmp(n->text, "-")) { + unsigned zero = fe_ir_const(L->m, L->b, it, 0); + unsigned v = as_value(L, lower_expr(L, n->a), n->a); + return slot_value(fe_ir_binary(L->m, L->b, FE_IR_SUB, it, zero, v, + 0), it); + } + if (n->text && !strcmp(n->text, "not")) { + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0); + unsigned v = as_value(L, lower_expr(L, n->a), n->a); + return slot_value(fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I8, v, + zero, 0), FE_IR_I8); + } + if (n->text && (!strcmp(n->text, "&") || !strcmp(n->text, "&mut"))) { + Slot inner = lower_expr(L, n->a); + return slot_value(as_address(L, inner, n->a), FE_IR_PTR); + } + fail(L, "this unary operator", n); + return slot_void(); + case FE_N_MEMBER: + /* A payload-free variant used as a value is just its tag. */ + if (t && t->kind == FE_TYPE_ENUM && !enum_has_payload(t) && + n->b && n->b->text) { + FeVariantType *v = fe_type_variant(t, n->b->text); + if (v) + return slot_value(fe_ir_const(L->m, L->b, ir_type(t), + (long)v->tag), ir_type(t)); + } + /* `error.Name` is a member of the open default set: a code, and + nothing to look up. */ + if (n->a && n->a->kind == FE_N_IDENT && n->a->text && + !strcmp(n->a->text, "error") && n->b && n->b->text) + return slot_value(fe_ir_const(L->m, L->b, FE_IR_I16, + error_code(L, n->b->text)), + FE_IR_I16); + /* `.?` is the payload of an optional the checker already proved is + there. */ + if (n->text && !strcmp(n->text, ".?")) { + FeType *bt = n->a ? n->a->sem_type : 0; + return wrapper_payload(L, lower_expr(L, n->a), bt); + } + /* `p.^` reads through a pointer -- except for an owned slice, whose + pointer and length are the value itself, so there is nothing to + step through. */ + if (n->text && !strcmp(n->text, ".^")) { + Slot base = lower_expr(L, n->a); + unsigned p; + if (base.type == FE_IR_MEM) + return slot_place(base.place, it, ir_size(t)); + p = as_value(L, base, n->a); + return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t)); + } + /* `.n` is how many elements there are, which an array knows at + compile time and a slice carries beside its pointer. */ + if (n->b && n->b->text && !strcmp(n->b->text, "n")) { + FeType *bt = n->a ? n->a->sem_type : 0; + Slot base; + if (bt && bt->kind == FE_TYPE_ARRAY) + return slot_value(fe_ir_const(L->m, L->b, FE_IR_I32, + (long)bt->length), FE_IR_I32); + base = lower_expr(L, n->a); + if (!base.is_place) { fail(L, "a length of a temporary", n); return slot_void(); } + base.place.offset += SLICE_LEN_OFFSET; + return slot_place(base.place, FE_IR_I32, 4); + } + /* A field is a constant offset from the base. */ + { + FeType *base = n->a ? n->a->sem_type : 0; + FeFieldType *field; + Slot b; + if (base && (base->kind == FE_TYPE_REF || + base->kind == FE_TYPE_OWNED)) base = base->elem; + field = fe_type_field(base, n->b && n->b->text ? n->b->text : ""); + if (!field) { fail(L, "an unresolved field", n); return slot_void(); } + b = lower_expr(L, n->a); + if (n->a->sem_type && (n->a->sem_type->kind == FE_TYPE_REF || + n->a->sem_type->kind == FE_TYPE_OWNED)) { + unsigned p = as_value(L, b, n->a); + return slot_place(fe_ir_at_temp(p, (long)field->offset), it, + ir_size(t)); + } + if (!b.is_place) { fail(L, "a field of a temporary", n); return slot_void(); } + b.place.offset += (long)field->offset; + return slot_place(b.place, it, ir_size(t)); + } + case FE_N_INDEX: { + FeType *bt = n->a ? n->a->sem_type : 0; + FeType *elem = bt ? bt->elem : 0; + Slot base; + unsigned data; + unsigned length; + unsigned index; + unsigned scale; + unsigned offset; + unsigned addr; + if (n->flags & FE_NODE_SLICE) return lower_slice(L, n); + base = lower_expr(L, n->a); + indexable_parts(L, base, bt, &data, &length, n); + index = as_value(L, lower_expr(L, n->b), n->b); + if (!L->c->no_checks) { + unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, + index, length, 1); + guard(L, ok, FE_TRAP_BOUNDS, n->loc.line); + } + scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem)); + offset = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, index, scale, 1); + addr = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, offset, 1); + return slot_place(fe_ir_at_temp(addr, 0), ir_type(elem), ir_size(elem)); + } + case FE_N_ARRAY_INIT: { + unsigned local = scratch(L, t, "array"); + FeType *elem = t ? t->elem : 0; + unsigned long step = ir_size(elem); + long at = 0; + FeNode *x; + for (x = n->children; x; x = x->next) { + Slot v = lower_expr(L, x); + store_into(L, fe_ir_at_local(local, at), v, x, step); + at += (long)step; + } + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); + } + case FE_N_STRUCT_INIT: { + unsigned local = scratch(L, t, "struct"); + FeNode *f; + for (f = n->children; f; f = f->next) { + FeFieldType *field; + Slot v; + if (f->kind != FE_N_FIELD) continue; + field = fe_type_field(t, f->text); + if (!field) { fail(L, "an unresolved field", f); return slot_void(); } + v = lower_expr(L, f->a); + store_into(L, fe_ir_at_local(local, (long)field->offset), v, f, + ir_size(field->type)); + } + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); + } + case FE_N_CALL: + return lower_call(L, n); + case FE_N_TYPE: + /* `x as T`: the operand is `a` and the target type is the node's own. + Between integers this only changes how wide the value is and whether + the top bits repeat the sign. */ + if (n->a) { + FeType *from = n->a->sem_type; + unsigned v = as_value(L, lower_expr(L, n->a), n->a); + if (ir_type(from) == it) return slot_value(v, it); + return slot_value(fe_ir_cast(L->m, L->b, ir_type(from), it, v, + type_is_unsigned(from)), it); + } + fail(L, "this type expression", n); + return slot_void(); + case FE_N_EXPR: + return lower_expr(L, n->a); + default: + fail(L, "this expression", n); + return slot_void(); + } +} + +/* The link name of the `drop` method for this type, found through the instance + the checker recorded. */ +const char *drop_name(Lower *L, const FeType *t) +{ + unsigned i; + FeNode *method = 0; + if (!t || !t->decl_node) return 0; + for (method = t->decl_node->children; method; method = method->next) + if (method->kind == FE_N_FN && method->text && + !strcmp(method->text, "drop")) break; + if (!method) return 0; + for (i = 0; i < L->c->instance_count; ++i) + if (L->c->instances[i].decl == method && + L->c->instances[i].owner == t) + return L->c->instances[i].cname; + return method->cname; +} + +/* Settle what a scope owes, most recent first. A `return` in the middle of a + function still owes everything, so every exit path calls this. */ +void run_deferred(Lower *L, unsigned from) +{ + unsigned i; + for (i = L->owed_count; i > from; --i) { + if (L->owed[i - 1].block) { + lower_stmt(L, L->owed[i - 1].block); + continue; + } + { + /* Release only where the value is still here. */ + unsigned live = fe_ir_load(L->m, L->b, FE_IR_I8, + fe_ir_at_local(L->owed[i - 1].flag, 0)); + FeIrBlock *doit = new_block(L); + FeIrBlock *skip = new_block(L); + unsigned args[1]; + FeType *t = L->owed[i - 1].type; + fe_ir_br(L->b, live, doit->id, skip->id); + L->b = doit; + if (t && t->kind == FE_TYPE_OWNED && t->elem && + t->elem->kind == FE_TYPE_SLICE) { + FeIrPlace at = fe_ir_at_local(L->owed[i - 1].local, + SLICE_PTR_OFFSET); + args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, at); + } else { + args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(L->owed[i - 1].local, 0)); + } + if (t && t->has_drop) { + /* A type that says how to let go of itself is asked to; the + name is the one its instance was given. */ + const char *how = drop_name(L, t); + args[0] = fe_ir_addr(L->m, L->b, + fe_ir_at_local(L->owed[i - 1].local, 0)); + if (how) fe_ir_call(L->m, L->b, FE_IR_VOID, how, args, 1); + } else { + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1); + } + fe_ir_jmp(L->b, skip->id); + L->b = skip; + } + } +} + +/* ------------------------------------------------------- wrappers -------- * + * An optional is a tag and a payload; an error union is an error code and a + * payload, where a code of zero means there is no error. Both are memory, and + * both are built the same way: write the tag, then write the value after it. + * -------------------------------------------------------------------------- */ + +Slot wrap_context(Lower *L, Slot v, FeNode *n) +{ + FeType *want = n->sem_context; + unsigned local; + long payload_at; + if (!want) return v; + local = scratch(L, want, "wrapped"); + payload_at = (long)fe_type_payload_offset(want); + if (want->kind == FE_TYPE_OPTIONAL) { + if (fe_m7_is_null(n)) { + /* A payload with a spare representation uses it for "nothing" + instead of carrying a separate tag. */ + unsigned z = fe_ir_const(L->m, L->b, + uses_niche(want) ? FE_IR_PTR : FE_IR_I8, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), z, + uses_niche(want) ? FE_IR_PTR : FE_IR_I8); + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); + } + if (!uses_niche(want)) { + unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), one, FE_IR_I8); + } + store_into(L, fe_ir_at_local(local, payload_at), v, n, + ir_size(want->elem)); + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); + } + if (want->kind == FE_TYPE_ERROR_UNION) { + FeType *value_type = want->error_value; + if (n->sem_type && n->sem_type->is_error) { + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), + as_value(L, v, n), FE_IR_I16); + } else { + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), zero, FE_IR_I16); + if (value_type && value_type->kind != FE_TYPE_VOID) + store_into(L, fe_ir_at_local(local, payload_at), v, n, + ir_size(value_type)); + } + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); + } + return v; +} + +/* The tag of a wrapper that is already in memory. */ +unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n) +{ + FeIrPlace p; + if (!w.is_place) { fail(L, "a wrapper with no place", n); return 0; } + p = w.place; + if (uses_niche(t)) return fe_ir_load(L->m, L->b, FE_IR_PTR, p); + return fe_ir_load(L->m, L->b, tag_type(t), p); +} + +Slot wrapper_payload(Lower *L, Slot w, const FeType *t) +{ + FeType *payload = t ? (t->kind == FE_TYPE_ERROR_UNION ? t->error_value + : t->elem) : 0; + FeIrPlace p = w.place; + (void)L; + p.offset += (long)fe_type_payload_offset(t); + return slot_place(p, ir_type(payload), ir_size(payload)); +} + +/* Leave the function with this error code, after the deferred blocks. */ +void return_error(Lower *L, unsigned err, FeNode *n) +{ + FeType *ret = L->ret_type; + unsigned local = scratch(L, ret, "failure"); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), err, FE_IR_I16); + run_deferred(L, 0); + if (L->fn->returns_by_address) { + unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(L->ret_local, 0)); + fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0), fe_ir_at_local(local, 0), + ir_size(ret)); + fe_ir_ret(L->b, 0, 0); + return; + } + fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(ret), + fe_ir_at_local(local, 0)), 1); + (void)n; +} + +/* `try e` -- if e failed, leave with its error; otherwise the value. */ +Slot lower_try(Lower *L, FeNode *n) +{ + FeType *t = n->a ? n->a->sem_type : 0; + Slot e = lower_expr(L, n->a); + unsigned err = wrapper_tag(L, e, t, n); + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0); + unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I16, err, zero, 1); + FeIrBlock *bad = new_block(L); + FeIrBlock *good = new_block(L); + fe_ir_br(L->b, ok, good->id, bad->id); + L->b = bad; + return_error(L, err, n); + L->b = good; + return wrapper_payload(L, e, t); +} + +/* `e orelse d` and `e catch d` both mean "the value, or that instead". The + right-hand side is only evaluated when it is needed, so it is a branch. */ +Slot lower_lazy(Lower *L, FeNode *n, int is_catch) +{ + FeType *t = n->a ? n->a->sem_type : 0; + FeType *payload = t ? (is_catch ? t->error_value : t->elem) : 0; + Slot e; + unsigned tag; + unsigned zero; + unsigned ok; + unsigned result; + FeIrBlock *other; + FeIrBlock *join; + FeIrBlock *have; + e = lower_expr(L, n->a); + tag = wrapper_tag(L, e, t, n); + zero = fe_ir_const(L->m, L->b, is_catch || uses_niche(t) ? FE_IR_PTR + : FE_IR_I8, 0); + /* An error union is fine when its code is zero; an optional is fine when + its tag is not. */ + ok = fe_ir_binary(L->m, L->b, is_catch ? FE_IR_EQ : FE_IR_NE, + is_catch ? FE_IR_I16 : (uses_niche(t) ? FE_IR_PTR + : FE_IR_I8), + tag, zero, 1); + result = scratch(L, payload, "result"); + have = new_block(L); + other = new_block(L); + join = new_block(L); + fe_ir_br(L->b, ok, have->id, other->id); + L->b = have; + store_into(L, fe_ir_at_local(result, 0), wrapper_payload(L, e, t), n, + ir_size(payload)); + fe_ir_jmp(L->b, join->id); + L->b = other; + if (is_catch && n->c) { + /* The block form handles the error and must not fall through with a + value, so whatever it leaves behind is what the checker allowed. */ + lower_stmt(L, n->c); + } else { + Slot d = lower_expr(L, n->b); + store_into(L, fe_ir_at_local(result, 0), d, n->b, ir_size(payload)); + } + fe_ir_jmp(L->b, join->id); + L->b = join; + return slot_place(fe_ir_at_local(result, 0), ir_type(payload), + ir_size(payload)); +} + +/* ----------------------------------------------------------- statements --- */ diff --git a/fec/src/lowerpri.h b/fec/src/lowerpri.h new file mode 100644 index 0000000..a72262b --- /dev/null +++ b/fec/src/lowerpri.h @@ -0,0 +1,153 @@ +#ifndef FE_LOWERPRI_H +#define FE_LOWERPRI_H + +/* Lowering's own vocabulary, shared by the files it is split across. */ + +#include "lower.h" +#include "m7.h" +#include "own.h" +#include +#include + +#include +#include "m7.h" +#include "own.h" +#include + +/* ------------------------------------------------------------------------- * + * Lowering + * + * One function at a time, one statement at a time. A `Slot` is what an + * expression produced: either a value already in a temporary, or a place in + * memory that a value can be read from or written to. Aggregates are always + * places -- they are never carried in a temporary, because a temporary is a + * register and an aggregate does not fit in one. + * ------------------------------------------------------------------------- */ + +#define LOWER_MAX_LOCALS 256 + +typedef struct LowerVar { + const char *cname; + unsigned local; + /* An aggregate parameter arrives as an address, so the slot holds a + pointer and the value is one dereference away. */ + int by_address; +} LowerVar; + +typedef struct Lower { + FeCheck *c; + FeIrModule *m; + FeIrFunc *fn; + FeIrBlock *b; /* the block being appended to */ + FeType *ret_type; + unsigned ret_local; /* hidden result address, when returning mem */ + LowerVar vars[LOWER_MAX_LOCALS]; + unsigned var_count; + /* Loop targets, for break and continue. */ + unsigned break_target[32]; + unsigned continue_target[32]; + unsigned loop_depth; + /* What a scope still owes when it ends: `defer` blocks to run and owned + values to release, in the order they were written. Every exit path runs + what is live, last first. + + A drop carries a flag beside the value. The flag is set when the value + is stored and cleared wherever it is moved away, so the release happens + exactly on the paths where the value is still there -- which is not + something the shape of the code can tell you on its own. */ + struct { + FeNode *block; /* a `defer`, when set */ + unsigned local; /* the owned value, otherwise */ + unsigned flag; + FeType *type; + } owed[64]; + unsigned owed_count; + /* Every `error.Name` used anywhere in the build, sorted, numbered from one. + SPEC 4.6: the names are collected rather than declared, and the order is + fixed by the spelling so that the same program always gets the same + codes however the build was ordered. */ + const char *error_names[256]; + unsigned error_count; + int failed; +} Lower; + +typedef struct Slot { + int is_place; + unsigned temp; /* the value, when is_place is 0 */ + FeIrPlace place; /* where it lives, when is_place is 1 */ + FeIrType type; + unsigned long size; /* for FE_IR_MEM */ +} Slot; + + + +/* A slice is a pointer and a length, in that order. Both the compiler and the + runtime read it this way, so the offsets live here and nowhere else. */ +#define SLICE_PTR_OFFSET 0L +#define SLICE_LEN_OFFSET 4L + +/* Every definition in lowering, so the split files can see each other. */ +void fail(Lower *L, const char *why, FeNode *n); +FeIrType ir_type_of(const FeType *t); +int enum_has_payload(const FeType *t); +FeIrType ir_type(const FeType *t); +unsigned long ir_size(const FeType *t); +unsigned ir_align(const FeType *t); +int type_is_unsigned(const FeType *t); +Slot slot_value(unsigned temp, FeIrType t); +Slot slot_place(FeIrPlace p, FeIrType t, unsigned long size); +Slot slot_void(void); +unsigned as_value(Lower *L, Slot s, FeNode *n); +unsigned as_address(Lower *L, Slot s, FeNode *n); +int needs_release(const FeType *t); +unsigned declare_var(Lower *L, const char *cname, const FeType *t, + const char *name); +int release_flag(Lower *L, unsigned local, unsigned *flag); +LowerVar *find_var(Lower *L, const char *cname); +FeIrBlock *new_block(Lower *L); +void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line); +FeIrType tag_type(const FeType *t); +int uses_niche(const FeType *t); +unsigned scratch(Lower *L, const FeType *t, const char *why); +void indexable_parts(Lower *L, Slot base, const FeType *t, + unsigned *data, unsigned *length, FeNode *n); +void note_error_name(Lower *L, const char *name); +void collect_error_names(Lower *L, FeNode *n); +long error_code(Lower *L, const char *name); +FeIrOp binary_op(const char *op, int *is_cmp); +long literal_value(FeNode *n); +Slot lower_logical(Lower *L, FeNode *n, int is_and); +int lower_builtin(Lower *L, FeNode *n, Slot *out); +int is_mem_call(const FeNode *n, const char *what); +Slot allocation_result(Lower *L, FeNode *n, unsigned pointer); +int lower_mem(Lower *L, FeNode *n, Slot *out); +void emit_text(Lower *L, unsigned handle, const char *text, + unsigned long len); +void emit_value_text(Lower *L, unsigned handle, FeNode *arg, int verb); +int lower_print(Lower *L, FeNode *n, Slot *out); +Slot lower_call(Lower *L, FeNode *n); +Slot lower_expr(Lower *L, FeNode *n); +Slot lower_expr_core(Lower *L, FeNode *n); +const char *drop_name(Lower *L, const FeType *t); +void run_deferred(Lower *L, unsigned from); +Slot wrap_context(Lower *L, Slot v, FeNode *n); +unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n); +Slot wrapper_payload(Lower *L, Slot w, const FeType *t); +void return_error(Lower *L, unsigned err, FeNode *n); +Slot lower_try(Lower *L, FeNode *n); +Slot lower_lazy(Lower *L, FeNode *n, int is_catch); +void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, + unsigned long size); +void lower_return(Lower *L, FeNode *n); +void lower_if(Lower *L, FeNode *n); +void lower_while(Lower *L, FeNode *n); +Slot lower_slice(Lower *L, FeNode *n); +void lower_for(Lower *L, FeNode *n); +void lower_match(Lower *L, FeNode *n); +void lower_stmt(Lower *L, FeNode *n); +void lower_global(Lower *L, FeNode *n); +int fn_is_generic(const FeNode *fn); +void lower_fn_as(Lower *L, FeNode *fn, const char *name); +void lower_fn(Lower *L, FeNode *fn); + +#endif diff --git a/fec/src/lowerprn.c b/fec/src/lowerprn.c new file mode 100644 index 0000000..eea3923 --- /dev/null +++ b/fec/src/lowerprn.c @@ -0,0 +1,236 @@ +#include "lowerpri.h" + +void emit_text(Lower *L, unsigned handle, const char *text, + unsigned long len) +{ + unsigned args[3]; + const char *label; + if (!len) return; + label = fe_ir_string(L->m, text, len); + if (!label) return; + args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle); + args[1] = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0)); + args[2] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)len); + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write", args, 3); +} + +/* Write one value, the way the verb asked for. */ +void emit_value_text(Lower *L, unsigned handle, FeNode *arg, int verb) +{ + FeType *t = arg ? arg->sem_type : 0; + Slot v = lower_expr(L, arg); + unsigned args[3]; + if (t && (t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR)) { + FeIrPlace at = v.place; + if (!v.is_place) { fail(L, "text with no place", arg); return; } + args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle); + at.offset = v.place.offset + SLICE_PTR_OFFSET; + args[1] = fe_ir_load(L->m, L->b, FE_IR_PTR, at); + at.offset = v.place.offset + SLICE_LEN_OFFSET; + args[2] = fe_ir_load(L->m, L->b, FE_IR_I32, at); + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write", args, 3); + return; + } + if (t && t->kind == FE_TYPE_BOOL) { + /* Two literals and a branch: cheaper than a runtime that knows about + Ferro's names for truth. */ + FeIrBlock *yes = new_block(L); + FeIrBlock *no = new_block(L); + FeIrBlock *join = new_block(L); + fe_ir_br(L->b, as_value(L, v, arg), yes->id, no->id); + L->b = yes; + emit_text(L, handle, "true", 4); + fe_ir_jmp(L->b, join->id); + L->b = no; + emit_text(L, handle, "false", 5); + fe_ir_jmp(L->b, join->id); + L->b = join; + return; + } + if (verb == 'c' || (t && t->kind == FE_TYPE_CHAR)) { + /* One byte, written from a slot of its own so it has an address. */ + unsigned cell = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "char"); + fe_ir_store(L->m, L->b, fe_ir_at_local(cell, 0), as_value(L, v, arg), + FE_IR_I8); + args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle); + args[1] = fe_ir_addr(L->m, L->b, fe_ir_at_local(cell, 0)); + args[2] = fe_ir_const(L->m, L->b, FE_IR_I32, 1); + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write", args, 3); + return; + } + args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle); + args[1] = as_value(L, v, arg); + if (verb == 'x') { + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write_hex", args, 2); + return; + } + args[2] = fe_ir_const(L->m, L->b, FE_IR_I32, + type_is_unsigned(t) || (t && t->kind == FE_TYPE_ENUM) + ? 1 : 0); + fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write_int", args, 3); +} + +/* `@print(fmt, ...)`, `@fprint(w, fmt, ...)`. The checker has already agreed + that the string is a literal and that the count matches. */ +int lower_print(Lower *L, FeNode *n, Slot *out) +{ + const char *name = n->text; + int to_writer; + unsigned handle; + FeNode *fmt; + FeNode *arg; + const char *text; + unsigned long raw; + unsigned long i; + unsigned long chunk; + char plain[1024]; + unsigned long plain_len; + if (!name || (strcmp(name, "@print") != 0 && strcmp(name, "@fprint") != 0)) + return 0; + to_writer = strcmp(name, "@fprint") == 0; + fmt = n->children; + if (to_writer) { + /* A Writer is a handle; Stdout is 1 and Stderr is 2 (std.io). */ + Slot w = lower_expr(L, fmt); + handle = 0; + (void)w; + fmt = fmt ? fmt->next : 0; + } + handle = to_writer ? 2 : 1; + if (!fmt || !fmt->text || fmt->text[0] != '"') { + fail(L, "a format string that is not a literal", n); + *out = slot_void(); + return 1; + } + text = fmt->text + 1; + raw = strlen(fmt->text); + if (raw >= 2) raw -= 2; + arg = fmt->next; + plain_len = 0; + chunk = 0; + (void)chunk; + for (i = 0; i < raw; ++i) { + char ch = text[i]; + if (ch == 92 && i + 1 < raw) { /* an escape */ + ++i; + switch (text[i]) { + case 'n': ch = 10; break; + case 't': ch = 9; break; + case 'r': ch = 13; break; + case '0': ch = 0; break; + default: ch = text[i]; break; + } + if (plain_len + 1 < sizeof plain) plain[plain_len++] = ch; + continue; + } + if (ch == '{') { + int verb = ' '; + unsigned long close = i + 1; + while (close < raw && text[close] != '}') ++close; + if (close == i + 2) verb = text[i + 1]; + emit_text(L, handle, plain, plain_len); + plain_len = 0; + emit_value_text(L, handle, arg, verb); + if (arg) arg = arg->next; + i = close; + continue; + } + if (ch == '}') continue; /* `}}` is one brace */ + if (plain_len + 1 < sizeof plain) plain[plain_len++] = ch; + } + emit_text(L, handle, plain, plain_len); + *out = slot_void(); + return 1; +} + +Slot lower_call(Lower *L, FeNode *n) +{ + unsigned args[16]; + unsigned count = 0; + FeNode *arg = n->children; + FeType *ret = n->sem_type; + FeIrType rt = ir_type(ret); + unsigned result_local = 0; + const char *callee = n->a && n->a->cname ? n->a->cname : + (n->sem_decl && n->sem_decl->cname ? + n->sem_decl->cname : 0); + { + Slot built; + if (lower_builtin(L, n, &built)) return built; + if (lower_mem(L, n, &built)) return built; + if (lower_print(L, n, &built)) return built; + } + if (!callee) { fail(L, "a call with no target", n); return slot_void(); } + /* An aggregate result is written through a hidden first argument. */ + if (rt == FE_IR_MEM) { + result_local = fe_ir_local(L->m, L->fn, FE_IR_MEM, ir_size(ret), + ir_align(ret), "result"); + args[count++] = fe_ir_addr(L->m, L->b, fe_ir_at_local(result_local, 0)); + } + /* A method call passes what it was reached through as its first argument. + `self: Self` and `self: &Self` are the same thing here: the address of + the receiver, because an aggregate never travels in a register. */ + if (n->a && n->a->kind == FE_N_MEMBER && n->sem_decl) { + FeNode *first = n->sem_decl->a ? n->sem_decl->a->children : 0; + if (first && first->text && !strcmp(first->text, "self")) { + FeType *rt = n->a->a ? n->a->a->sem_type : 0; + Slot recv = lower_expr(L, n->a->a); + /* A receiver that is already a reference or an owner is a pointer + already; taking its address would pass a pointer to the + pointer. */ + if (rt && (rt->kind == FE_TYPE_REF || + (rt->kind == FE_TYPE_OWNED && ir_type(rt) == FE_IR_PTR))) + args[count++] = as_value(L, recv, n->a->a); + else + args[count++] = recv.is_place ? as_address(L, recv, n->a->a) + : recv.temp; + } + } + /* A generic call passes its type arguments first. They were consumed when + the instance was chosen and carry no value, so they are not passed. */ + { + FeNode *p; + for (p = n->sem_decl && n->sem_decl->a ? n->sem_decl->a->children : 0; + p && arg; p = p->next) { + if (!(p->flags & FE_NODE_COMPTIME)) break; + arg = arg->next; + } + } + for (; arg; arg = arg->next) { + Slot a = lower_expr(L, arg); + if (count >= 16) { fail(L, "too many arguments", n); break; } + args[count++] = a.type == FE_IR_MEM ? as_address(L, a, arg) + : as_value(L, a, arg); + } + if (rt == FE_IR_MEM) { + fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count); + return slot_place(fe_ir_at_local(result_local, 0), FE_IR_MEM, + ir_size(ret)); + } + if (rt == FE_IR_VOID) { + fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count); + return slot_void(); + } + return slot_value(fe_ir_call(L->m, L->b, rt, callee, args, count), rt); +} + + +/* Every expression may be standing where a wrapper is expected, so the wrap is + applied once, here, rather than at each place that could need it. */ +Slot lower_expr(Lower *L, FeNode *n) +{ + Slot v; + if (!n || L->failed) return slot_void(); + v = lower_expr_core(L, n); + /* The checker marked the uses that hand ownership away. Where one names a + local we track, the value is no longer ours to release. */ + if ((n->flags & FE_OWN_NODE_CONSUMED) && n->kind == FE_N_IDENT) { + LowerVar *var = find_var(L, n->cname); + unsigned flag; + if (var && release_flag(L, var->local, &flag)) { + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), zero, FE_IR_I8); + } + } + return n->sem_context ? wrap_context(L, v, n) : v; +} diff --git a/fec/src/lowerstm.c b/fec/src/lowerstm.c new file mode 100644 index 0000000..1ac6395 --- /dev/null +++ b/fec/src/lowerstm.c @@ -0,0 +1,543 @@ +#include "lowerpri.h" + +void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, + unsigned long size) +{ + if (value.type == FE_IR_MEM) { + if (!value.is_place) { fail(L, "an aggregate value", n); return; } + fe_ir_copy(L->m, L->b, dst, value.place, size); + return; + } + fe_ir_store(L->m, L->b, dst, as_value(L, value, n), value.type); +} + +void lower_return(Lower *L, FeNode *n) +{ + Slot v; + if (!n->a) { + /* A bare return from a `!void` function still has to say that nothing + went wrong. */ + if (L->ret_type && L->ret_type->kind == FE_TYPE_ERROR_UNION) { + unsigned local = scratch(L, L->ret_type, "success"); + unsigned none = fe_ir_const(L->m, L->b, FE_IR_I16, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), none, FE_IR_I16); + run_deferred(L, 0); + if (L->fn->returns_by_address) { + unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(L->ret_local, 0)); + fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0), + fe_ir_at_local(local, 0), ir_size(L->ret_type)); + fe_ir_ret(L->b, 0, 0); + return; + } + fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(L->ret_type), + fe_ir_at_local(local, 0)), 1); + return; + } + run_deferred(L, 0); + fe_ir_ret(L->b, 0, 0); + return; + } + /* The value is computed before the deferred blocks run, because they may + destroy what it was read from. */ + v = lower_expr(L, n->a); + if (v.type != FE_IR_MEM && v.is_place) + v = slot_value(as_value(L, v, n->a), v.type); + run_deferred(L, 0); + if (L->fn->returns_by_address) { + unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(L->ret_local, 0)); + store_into(L, fe_ir_at_temp(dst, 0), v, n, ir_size(L->ret_type)); + fe_ir_ret(L->b, 0, 0); + return; + } + fe_ir_ret(L->b, as_value(L, v, n->a), 1); +} + +void lower_if(Lower *L, FeNode *n) +{ + FeIrBlock *then_b = new_block(L); + FeIrBlock *else_b = n->c ? new_block(L) : 0; + FeIrBlock *join = new_block(L); + unsigned cond = as_value(L, lower_expr(L, n->a), n->a); + fe_ir_br(L->b, cond, then_b->id, else_b ? else_b->id : join->id); + L->b = then_b; + lower_stmt(L, n->b); + fe_ir_jmp(L->b, join->id); + if (else_b) { + L->b = else_b; + lower_stmt(L, n->c); + fe_ir_jmp(L->b, join->id); + } + L->b = join; +} + +void lower_while(Lower *L, FeNode *n) +{ + FeIrBlock *head = new_block(L); + FeIrBlock *body = new_block(L); + FeIrBlock *done = new_block(L); + unsigned cond; + fe_ir_jmp(L->b, head->id); + L->b = head; + cond = as_value(L, lower_expr(L, n->a), n->a); + fe_ir_br(L->b, cond, body->id, done->id); + if (L->loop_depth < 32) { + L->break_target[L->loop_depth] = done->id; + L->continue_target[L->loop_depth] = head->id; + ++L->loop_depth; + } + L->b = body; + lower_stmt(L, n->b); + fe_ir_jmp(L->b, head->id); + if (L->loop_depth) --L->loop_depth; + L->b = done; +} + +/* `x[a..b]` makes a pointer and a length out of part of something indexable. + Both ends are checked -- against each other and against what is there -- + before the pointer is formed. An empty slice of a valid range is fine; one + that starts past its end is not. */ +Slot lower_slice(Lower *L, FeNode *n) +{ + FeType *bt = n->a ? n->a->sem_type : 0; + FeType *elem = bt ? bt->elem : 0; + FeType *t = n->sem_type; + Slot base = lower_expr(L, n->a); + unsigned data; + unsigned length; + unsigned from; + unsigned to; + unsigned local; + unsigned scale; + unsigned off; + unsigned at; + unsigned count; + indexable_parts(L, base, bt, &data, &length, n); + from = n->b ? as_value(L, lower_expr(L, n->b), n->b) + : fe_ir_const(L->m, L->b, FE_IR_I32, 0); + to = n->c ? as_value(L, lower_expr(L, n->c), n->c) : length; + if (!L->c->no_checks) { + unsigned ordered = fe_ir_binary(L->m, L->b, FE_IR_LE, FE_IR_I32, + from, to, 1); + unsigned within; + guard(L, ordered, FE_TRAP_BOUNDS, n->loc.line); + within = fe_ir_binary(L->m, L->b, FE_IR_LE, FE_IR_I32, to, length, 1); + guard(L, within, FE_TRAP_BOUNDS, n->loc.line); + } + scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem)); + off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, from, scale, 1); + at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, off, 1); + count = fe_ir_binary(L->m, L->b, FE_IR_SUB, FE_IR_I32, to, from, 1); + local = scratch(L, t, "slice"); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), at, + FE_IR_PTR); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), count, + FE_IR_I32); + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t)); +} + +/* Three shapes share the keyword. + + for i in a..b { } counts + for x in thing { } walks, binding a reference to each element + for i, x in thing { } walks, binding the position as well + + The count is read once before the body, so a thing that grows underneath the + loop cannot walk past what was measured. The element binding is a reference + (`x.^` reads it), which is what lets a loop write back into the thing. */ +void lower_for(Lower *L, FeNode *n) +{ + FeIrBlock *head; + FeIrBlock *body; + FeIrBlock *step; + FeIrBlock *done; + unsigned counter; + unsigned limit; + + if (n->c) { + /* The counting form: the variable is the count itself. */ + unsigned from = as_value(L, lower_expr(L, n->a), n->a); + unsigned to; + counter = declare_var(L, n->cname, 0, n->text); + L->fn->locals[counter].type = FE_IR_I32; + L->fn->locals[counter].size = 4; + L->fn->locals[counter].align = 4; + fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), from, FE_IR_I32); + to = as_value(L, lower_expr(L, n->c), n->c); + limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "limit"); + fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), to, FE_IR_I32); + head = new_block(L); + body = new_block(L); + step = new_block(L); + done = new_block(L); + fe_ir_jmp(L->b, head->id); + L->b = head; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(limit, 0)); + unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1); + fe_ir_br(L->b, more, body->id, done->id); + } + } else { + FeType *bt = n->a ? n->a->sem_type : 0; + FeType *elem = bt ? bt->elem : 0; + Slot base = lower_expr(L, n->a); + unsigned data; + unsigned length; + unsigned data_local; + unsigned item; + indexable_parts(L, base, bt, &data, &length, n); + data_local = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, "data"); + fe_ir_store(L->m, L->b, fe_ir_at_local(data_local, 0), data, FE_IR_PTR); + limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "count"); + fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), length, FE_IR_I32); + /* With two names the first is the position and the second the element; + with one it is the element. */ + counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index"); + if (n->aux_cname) { + L->vars[L->var_count].cname = n->cname; + L->vars[L->var_count].local = counter; + L->vars[L->var_count].by_address = 0; + if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; + item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->aux_text); + L->vars[L->var_count].cname = n->aux_cname; + L->vars[L->var_count].local = item; + L->vars[L->var_count].by_address = 0; + if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; + } else { + item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->text); + L->vars[L->var_count].cname = n->cname; + L->vars[L->var_count].local = item; + L->vars[L->var_count].by_address = 0; + if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; + } + { + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I32, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), zero, FE_IR_I32); + } + head = new_block(L); + body = new_block(L); + step = new_block(L); + done = new_block(L); + fe_ir_jmp(L->b, head->id); + L->b = head; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(limit, 0)); + unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1); + fe_ir_br(L->b, more, body->id, done->id); + } + L->b = body; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned scale = fe_ir_const(L->m, L->b, FE_IR_I32, + (long)ir_size(elem)); + unsigned off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, i, + scale, 1); + unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(data_local, 0)); + unsigned at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, p, + off, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(item, 0), at, FE_IR_PTR); + } + L->b = head; + } + + if (L->loop_depth < 32) { + L->break_target[L->loop_depth] = done->id; + L->continue_target[L->loop_depth] = step->id; + ++L->loop_depth; + } + L->b = body; + lower_stmt(L, n->b); + fe_ir_jmp(L->b, step->id); + L->b = step; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned one = fe_ir_const(L->m, L->b, FE_IR_I32, 1); + unsigned next = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_I32, i, one, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), next, FE_IR_I32); + } + fe_ir_jmp(L->b, head->id); + if (L->loop_depth) --L->loop_depth; + L->b = done; +} + +/* `match` over a payload-free enum or an integer: compare the tag against each + arm's pattern in turn. The checker already proved the arms cover everything, + so falling off the end cannot happen in a program that compiled -- but the + generated code has to go somewhere, and going to the join is right. */ +void lower_match(Lower *L, FeNode *n) +{ + FeType *t = n->a ? n->a->sem_type : 0; + FeIrType it = ir_type(t); + Slot subject = lower_expr(L, n->a); + unsigned value; + FeIrBlock *join; + FeNode *arm; + if (it == FE_IR_MEM) { fail(L, "a match over a payload", n); return; } + value = as_value(L, subject, n->a); + join = new_block(L); + for (arm = n->children; arm; arm = arm->next) { + FeIrBlock *body; + FeIrBlock *next; + unsigned want; + unsigned same; + FeVariantType *v; + if (arm->kind != FE_N_ARM) continue; + if (arm->text && !strcmp(arm->text, "_")) { + lower_stmt(L, arm->a); + fe_ir_jmp(L->b, join->id); + L->b = join; + return; + } + v = t && t->kind == FE_TYPE_ENUM && arm->text + ? fe_type_variant(t, arm->text) : 0; + want = fe_ir_const(L->m, L->b, it, + v ? (long)v->tag : literal_value(arm)); + same = fe_ir_binary(L->m, L->b, FE_IR_EQ, it, value, want, 1); + body = new_block(L); + next = new_block(L); + fe_ir_br(L->b, same, body->id, next->id); + L->b = body; + lower_stmt(L, arm->a); + fe_ir_jmp(L->b, join->id); + L->b = next; + } + fe_ir_jmp(L->b, join->id); + L->b = join; +} + +void lower_stmt(Lower *L, FeNode *n) +{ + FeNode *x; + if (!n || L->failed) return; + switch (n->kind) { + case FE_N_BLOCK: { + unsigned outer = L->owed_count; + for (x = n->children; x; x = x->next) lower_stmt(L, x); + /* Leaving a block normally settles what it owes. An exit that jumped + away already settled on its way out. */ + if (!L->b->terminated) run_deferred(L, outer); + L->owed_count = outer; + return; + } + case FE_N_LET: + case FE_N_VAR: + case FE_N_CONST: { + unsigned local = declare_var(L, n->cname, n->sem_type, n->text); + if (n->b) { + Slot v = lower_expr(L, n->b); + unsigned flag; + store_into(L, fe_ir_at_local(local, 0), v, n, ir_size(n->sem_type)); + if (release_flag(L, local, &flag)) { + unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), one, FE_IR_I8); + } + } + return; + } + case FE_N_ASSIGN: { + Slot dst = lower_expr(L, n->a); + Slot v = lower_expr(L, n->b); + if (!dst.is_place) { fail(L, "an assignment to a value", n); return; } + store_into(L, dst.place, v, n, dst.size); + return; + } + case FE_N_EXPR_STMT: + lower_expr(L, n->a); + return; + case FE_N_RETURN: + lower_return(L, n); + return; + case FE_N_IF: + lower_if(L, n); + return; + case FE_N_WHILE: + lower_while(L, n); + return; + case FE_N_BREAK: + if (L->loop_depth) fe_ir_jmp(L->b, L->break_target[L->loop_depth - 1]); + return; + case FE_N_CONTINUE: + if (L->loop_depth) + fe_ir_jmp(L->b, L->continue_target[L->loop_depth - 1]); + return; + case FE_N_UNSAFE: + lower_stmt(L, n->a); + return; + case FE_N_DEFER: + if (L->owed_count < 64) { + L->owed[L->owed_count].block = n->a; + L->owed[L->owed_count].local = 0; + L->owed[L->owed_count].flag = 0; + L->owed[L->owed_count].type = 0; + ++L->owed_count; + } + return; + case FE_N_FOR: + lower_for(L, n); + return; + case FE_N_MATCH: + lower_match(L, n); + return; + default: + fail(L, "this statement", n); + return; + } +} + +/* ------------------------------------------------------------ functions --- */ + +/* A global is static storage. SPEC 7.1: its initializer is evaluated at + compile time, so what reaches here is either a constant to place in the + image or nothing, and the storage starts as zeroes. */ +void lower_global(Lower *L, FeNode *n) +{ + FeType *t = n->sem_type; + unsigned char *init = 0; + unsigned long size = ir_size(t); + if (!n->cname) return; + if (n->b && n->b->kind == FE_N_LITERAL && size && size <= 8) { + long v = literal_value(n->b); + unsigned long i; + init = (unsigned char *)fe_arena_alloc(&L->m->arena, (size_t)size); + if (init) + for (i = 0; i < size; ++i) + init[i] = (unsigned char)((v >> (i * 8)) & 0xFF); + } + fe_ir_global(L->m, n->cname, ir_type(t), size, ir_align(t), init); +} + +int fn_is_generic(const FeNode *fn) +{ + FeNode *p; + if (!fn) return 0; + for (p = fn->a ? fn->a->children : 0; p; p = p->next) + if (p->flags & FE_NODE_COMPTIME) return 1; + return 0; +} + +void lower_fn_as(Lower *L, FeNode *fn, const char *name) +{ + FeNode *p; + FeType *ret = fn->b ? fe_type_from_ast(&L->c->types, fn->b) : 0; + FeIrFunc *f; + if (!name) return; + f = fe_ir_func(L->m, name, ir_type(ret), ir_size(ret)); + if (!f) return; + L->fn = f; + L->ret_type = ret; + L->var_count = 0; + L->loop_depth = 0; + /* A hidden first parameter holds where an aggregate result goes. */ + if (f->returns_by_address) + L->ret_local = fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, "result"); + for (p = fn->a ? fn->a->children : 0; p; p = p->next) { + FeType *pt; + int by_address; + unsigned local; + /* A comptime parameter was consumed at compile time; it has no + storage and takes no argument slot. */ + if (p->flags & FE_NODE_COMPTIME) continue; + pt = fe_type_from_ast(&L->c->types, p->a); + /* An aggregate parameter arrives as an address. */ + by_address = ir_type(pt) == FE_IR_MEM; + local = by_address + ? fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, p->text) + : fe_ir_local(L->m, f, ir_type(pt), ir_size(pt), ir_align(pt), + p->text); + if (L->var_count < LOWER_MAX_LOCALS) { + L->vars[L->var_count].cname = p->cname; + L->vars[L->var_count].local = local; + L->vars[L->var_count].by_address = by_address; + ++L->var_count; + } + } + f->param_count = f->local_count; + L->b = fe_ir_block(L->m, f); + lower_stmt(L, fn->c); + /* A void function may just run off the end. */ + fe_ir_ret(L->b, 0, 0); +} + +void lower_fn(Lower *L, FeNode *fn) +{ + lower_fn_as(L, fn, fn->cname); +} + +int fe_lower_program(FeCheck *c, FeIrModule *out) +{ + Lower L; + unsigned u; + FeNode *n; + memset(&L, 0, sizeof L); + L.c = c; + L.m = out; + /* The codes have to be known while the bodies are lowered, so the names + are gathered from the whole build first. */ + for (u = 0; u < c->build->count; ++u) + collect_error_names(&L, c->build->units[u].ast.root); + for (u = 0; u < c->build->count; ++u) { + FeUnit *unit = &c->build->units[u]; + c->ast = &unit->ast; + c->unit = unit; + c->types.unit_name = unit->name[0] ? unit->name : "unit"; + if (!out->unit_file || !out->unit_file[0]) out->unit_file = unit->path; + for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) + if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) + lower_global(&L, n); + else if (n->kind == FE_N_FN && !n->c) { + /* A declaration with no body is something the linker will + find: the runtime, or a C library. */ + FeType *ret = n->b ? fe_type_from_ast(&c->types, n->b) : 0; + FeIrFunc *f; + if (!n->cname) continue; + f = fe_ir_func(out, n->cname, ir_type(ret), ir_size(ret)); + if (f) f->is_extern = 1; + } + else if (n->kind == FE_N_FN && n->c && !fn_is_generic(n)) { + lower_fn(&L, n); + /* The entry unit is the one the build was rooted at. */ + if (u == 0 && n->text && !strcmp(n->text, "main")) + out->entry_main = n->cname; + } + } + /* Each instance the checker reached is a function of its own: the same + body, read with different types bound, under its own link name. This is + where monomorphisation actually produces code -- the front end only + decided which instances exist. */ + for (u = 0; u < c->instance_count && !L.failed; ++u) { + FeInstance *inst = &c->instances[u]; + FeUnit *home; + FeTypeBind save[FE_TYPE_PARAM_MAX]; + unsigned save_count; + unsigned k; + if (!inst->decl || !inst->decl->c || !inst->cname || !inst->home) + continue; + home = 0; + for (k = 0; k < c->build->count; ++k) + if (!strcmp(c->build->units[k].name, inst->home)) + home = &c->build->units[k]; + if (!home) continue; + c->ast = &home->ast; + c->unit = home; + c->types.unit_name = home->name; + save_count = c->types.param_count; + for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) save[k] = c->types.params[k]; + c->types.param_count = inst->bind_count; + for (k = 0; k < inst->bind_count && k < FE_TYPE_PARAM_MAX; ++k) + c->types.params[k] = inst->binds[k]; + lower_fn_as(&L, inst->decl, inst->cname); + c->types.param_count = save_count; + for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) c->types.params[k] = save[k]; + } + return !L.failed; +} diff --git a/tests/run.py b/tests/run.py index 4e50fdd..913c2fd 100644 --- a/tests/run.py +++ b/tests/run.py @@ -33,7 +33,8 @@ ROOT = Path(__file__).resolve().parent.parent FIXTURES = ROOT / "fec" / "tests" WATCOM = ROOT / ".dosboxx" / "watcom" SOURCES = ("arena", "diag", "lexer", "ast", "parser", "types", "m7", "own", - "check", "resolve", "ir", "lower", "x86", "driver") + "check", "checkexp", "checkstm", "checkgen", "checkcal", "checkpro", + "resolve", "ir", "lower", "lowerprn", "lowerexp", "lowerstm", "x86", "driver") MARKER = re.compile(r"^//\s*ERROR:(?:(\d+):)?(.*)$")