std: str 과 list, 그리고 자동 drop 이 사용자 타입까지 닿는다

std.str 은 eq/starts_with/find/trim/parse_int 을 바이트 슬라이스 위에서 한다.
std.list 는 자라는 제네릭 시퀀스다 -- 버퍼를 소유하므로 리스트를 놓으면
원소도 놓인다. 성장은 두 배씩이라 push 당 복사량이 상수로 눌린다.

찾은 버그 넷:

- 메서드가 자기 타입의 유닛이 아니라 호출한 유닛에 속한 것으로 계산됐다.
  다른 유닛의 제네릭 타입을 쓰면 필드가 전부 private 으로 보였다.
- 참조로 도달한 메서드를 찾지 못했다. self.grow() 가 안 됐다.
- 이미 참조인 수신자의 주소를 한 번 더 떠서 넘겼다. 포인터의 포인터를 받은
  메서드가 그것을 구조체로 읽었다.
- 유닛으로 한정된 제네릭 타입(list.List(i32))이 타입 자리에서도 식 자리에서도
  해석되지 않았다.

drop 을 가진 타입은 인스턴스마다 그 메서드가 존재해야 한다 -- 이름으로 부르는
사람이 없어도 스코프 정리가 부른다. 그리고 자기 drop 안에서는 필드를 꺼낼 수
있다. 객체가 사라지는 중이라 뒤에 남아 읽힐 것이 없다.

run.py 207/207, exec.py 19/19.
This commit is contained in:
2026-08-17 06:42:16 +09:00
parent b0c9338cf3
commit 206799d1cb
6 changed files with 317 additions and 18 deletions
+92 -9
View File
@@ -70,9 +70,27 @@ static 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)
{
FeNode *base;
if (!s->fn_node || !s->fn_node->text || strcmp(s->fn_node->text,"drop")!=0)
return 0;
base = n ? n->a : 0;
while (base && (base->kind==FE_N_MEMBER || base->kind==FE_N_INDEX))
base = base->a;
return base && base->kind==FE_N_IDENT && base->text &&
strcmp(base->text,"self")==0;
}
static 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
out of it leaves nothing behind that anyone could read. That is the one
place R7 has nothing to protect. */
if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX) && in_own_drop(s,n))
return;
if (n && n->kind==FE_N_IDENT)
sym=find_symbol(s->scope,n->text ? n->text : "");
if (s->defer_depth != 0) {
@@ -1283,6 +1301,12 @@ static FeType *check_expr_core(FeCheckerState *s, FeNode *n)
}
}
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) {
@@ -2361,6 +2385,23 @@ static FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl,
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;
}
@@ -2391,18 +2432,30 @@ 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) {
/* A generic declaration is not a type until it has its arguments. */
FeNode *decl=unit_type_decl(c,home,node->text ? node->text : "");
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);
}
return fe_type_intern(&c->types,node->text);
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) {
@@ -2415,7 +2468,7 @@ static FeType *instantiate_type_node(void *owner, const FeNode *node)
--c->instance_depth;
return unknown(c);
}
result=instantiate_struct(c,home,node->text ? node->text : "",args,count,
result=instantiate_struct(c,home,name ? name : "",args,count,
node->loc);
--c->instance_depth;
return result;
@@ -2448,13 +2501,27 @@ static FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok)
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->text) {
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);
if (!unit_type_decl(c,home,n->a->text)) return unknown(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;
@@ -2464,7 +2531,7 @@ static FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok)
++count;
}
if (count>FE_TYPE_PARAM_MAX) { --c->instance_depth; return unknown(c); }
result=instantiate_struct(c,home,n->a->text,args,count,n->loc);
result=instantiate_struct(c,home,want,args,count,n->loc);
--c->instance_depth;
*ok=1;
return result;
@@ -2578,17 +2645,30 @@ static FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym,
/* `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;u<c->build->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;
FeUnit *home=current_unit(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);
@@ -2618,11 +2698,14 @@ static void check_instance_method(FeCheckerState *s, FeType *owner,
FeNode *method, FeLoc site, FeNode *call)
{
FeCheck *c=s->c;
FeUnit *home=current_unit(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;
+38 -3
View File
@@ -687,9 +687,17 @@ static Slot lower_call(Lower *L, FeNode *n)
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);
args[count++] = recv.is_place ? as_address(L, recv, n->a->a)
: recv.temp;
/* 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
@@ -990,6 +998,24 @@ static Slot lower_expr_core(Lower *L, FeNode *n)
}
}
/* 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)
@@ -1019,7 +1045,16 @@ static void run_deferred(Lower *L, unsigned from)
args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(L->owed[i - 1].local, 0));
}
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1);
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;
}