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;
+35
View File
@@ -687,7 +687,15 @@ 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);
/* 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;
}
@@ -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));
}
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;
}
+50 -3
View File
@@ -1,6 +1,53 @@
unit list;
unit std.list;
// A growable sequence. The buffer is owned, so a List owns its elements and
// releasing it releases them (SPEC 5 R1). Growth doubles, which keeps the
// total copying proportional to the number of pushes.
pub struct List(T) {
items: ^[]T,
items: ^[]mut T,
len: usize,
pub fn at(self: &Self, i: usize) -> &T;
pub fn with_capacity(n: usize) -> !Self {
let room: ^[]mut T = try mem.alloc_slice(T, n);
return Self{ items: room, len: 0 };
}
pub fn count(self: &Self) -> usize { return self.len; }
pub fn at(self: &Self, i: usize) -> T {
return self.items.^[i];
}
pub fn set(self: &mut Self, i: usize, v: T) -> void {
self.items.^[i] = v;
}
pub fn push(self: &mut Self, v: T) -> !void {
if self.len == self.items.^.n { try self.grow(); }
self.items.^[self.len] = v;
self.len = self.len + 1;
return;
}
/// Move to a buffer twice the size. Kept apart from `push` because the
/// borrow that hands over the old buffer must not be live while the old
/// buffer is still being read (SPEC 5 R6).
fn grow(self: &mut Self) -> !void {
var room: usize = self.items.^.n * 2;
if room == 0 { room = 4; }
let bigger: ^[]mut T = try mem.alloc_slice(T, room);
var i: usize = 0;
while i < self.len {
bigger.^[i] = self.items.^[i];
i = i + 1;
}
let old: ^[]mut T = mem.replace(&mut self.items, bigger);
mem.destroy(old);
return;
}
pub fn drop(self: &mut Self) -> void {
mem.destroy(self.items);
}
}
+66 -3
View File
@@ -1,3 +1,66 @@
unit str;
pub fn eq(a: str, b: str) -> bool;
pub fn trim(s: str) -> str;
unit std.str;
// `str` is `[]u8` (SPEC 4.2), so these take and give plain byte slices.
pub fn eq(a: []u8, b: []u8) -> bool {
if a.n != b.n { return false; }
var i: usize = 0;
while i < a.n {
if a[i] != b[i] { return false; }
i = i + 1;
}
return true;
}
pub fn starts_with(s: []u8, prefix: []u8) -> bool {
if prefix.n > s.n { return false; }
return eq(s[0..prefix.n], prefix);
}
/// Where `needle` first appears in `s`, or the length of `s` when it does not.
/// An index past the end is how "not found" is said without an optional.
pub fn find(s: []u8, needle: []u8) -> usize {
if needle.n == 0 { return 0; }
if needle.n > s.n { return s.n; }
var at: usize = 0;
let last: usize = s.n - needle.n;
while at <= last {
if eq(s[at..at + needle.n], needle) { return at; }
at = at + 1;
}
return s.n;
}
pub fn trim(s: []u8) -> []u8 {
var from: usize = 0;
var to: usize = s.n;
while from < to {
if s[from] != 32 and s[from] != 9 and s[from] != 10 and s[from] != 13 {
break;
}
from = from + 1;
}
while to > from {
let c: u8 = s[to - 1];
if c != 32 and c != 9 and c != 10 and c != 13 { break; }
to = to - 1;
}
return s[from..to];
}
pub fn parse_int(s: []u8) -> ?i32 {
if s.n == 0 { return null; }
var value: i32 = 0;
var i: usize = 0;
var negative: bool = false;
if s[0] == 45 { negative = true; i = 1; }
if i >= s.n { return null; }
while i < s.n {
let c: u8 = s[i];
if c < 48 or c > 57 { return null; }
value = value * 10 + ((c - 48) as i32);
i = i + 1;
}
if negative { return 0 - value; }
return value;
}
+42
View File
@@ -0,0 +1,42 @@
// EXIT:0
// OUTPUT:count 20 sum 190 balanced
unit listuse;
import std.io;
import std.fmt;
import std.list;
import std.sys;
fn show(label: []u8, v: i32) -> void {
var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
let n: usize = fmt.fmt_i32(buf[..], v);
io.print(label);
io.print(" ");
io.print(buf[0..n]);
io.print(" ");
}
fn build() -> !i32 {
var xs: list.List(i32) = try list.List(i32).with_capacity(2);
var i: i32 = 0;
while i < 20 {
try xs.push(i);
i = i + 1;
}
var sum: i32 = 0;
var k: usize = 0;
while k < xs.count() {
sum = sum + xs.at(k);
k = k + 1;
}
show("count", xs.count() as i32);
show("sum", sum);
return sum;
}
fn main() -> i32 {
let sum: i32 = build() catch |e| { return 1; };
if sum != 190 { return 2; }
if sys.allocs() != sys.frees() { io.print("leaked\n"); return 3; }
io.print("balanced\n");
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
// EXIT:0
// OUTPUT:ok 42 -7
unit strings;
import std.io;
import std.str;
import std.fmt;
fn show(v: i32) -> void {
var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
let n: usize = fmt.fmt_i32(buf[..], v);
io.print(" ");
io.print(buf[0..n]);
}
fn main() -> i32 {
if not str.eq(str.trim(" hello "), "hello") { return 1; }
if not str.starts_with("ferro", "fer") { return 2; }
if str.find("abcdef", "cd") != 2 { return 3; }
if str.find("abcdef", "zz") != 6 { return 4; }
let a: i32 = str.parse_int("42") orelse 0;
let b: i32 = str.parse_int("-7") orelse 0;
let bad: i32 = str.parse_int("12x") orelse 0;
if bad != 0 { return 5; }
io.print("ok");
show(a);
show(b);
io.print("\n");
return 0;
}