own: 함수 타입과 클로저 파라미터의 소유권 — 구멍이 숨기던 버그가 나왔다

D5를 고친다. 함수 타입에 own을 적을 수 없어 "소유권을 가져가는 클로저"를
표현할 수 없었고, move 검사기가 클로저 파라미터를 무조건 소유로 봐서 고차
경계에서 소유권 검사가 뚫려 있었다.

클로저 파라미터의 소유권은 리터럴이 스스로 적는다. 타입은 기대 타입에서
읽어오지만 소유권은 읽어오지 않는다 — move 검사는 타입 검사와 별도 순회라
타입을 모르고, 소유권은 타입보다 결과가 크기 때문이다.
unify는 정확히 일치를 요구한다. 방향을 다루려면 부분 타입이 필요하고 없다.

그리고 구멍이 자기가 숨긴 버그를 덮고 있었다. std/list.cool의 fold가
f: fn(acc, a) -> acc 로 적혀 있었는데 틀렸다 — 누적자는 매 단계 소비되고
새것으로 바뀌므로 own이다. 빌림으로 적혀 있어 affine 값을 fold로 실어나를
수 없었는데, 클로저 파라미터를 소유로 봤으니 아무 오류도 안 났다.
고치니 samples/app이 즉시 깨졌고, own을 붙여 고쳤다.

남은 한계를 기록했다: move 검사는 타입이 없어 제네릭을 통과해 affinity를
보지 못한다. 양쪽 다 표기가 없으면 통과한다. 근본 해법은 두 순회를 합치는
것이고 v0에서는 하지 않는다.

대가도 기록했다: own이 흔해진다. fold가 항상 요구하므로 copyable 누적자에도
붙는다. 표기의 신호가 약해지는지 지켜본다.

문법 먼저 고치고 대조 장치가 파서를 지적하게 했다. 지금은 문장 500개,
파일 29개 모두 갈림 0건.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
2026-08-30 19:06:17 +09:00
co-authored by Claude Opus 5
parent 410363b230
commit d85816abae
12 changed files with 167 additions and 45 deletions
+36 -14
View File
@@ -14,7 +14,8 @@ type error = { pos : Token.pos; msg : string }
type scheme = {
s_gen : string list; (* 타입 파라미터 *)
s_eff_gen : string list; (* effect 파라미터 *)
s_params : T.t list;
(* (own, 타입). 무표기는 빌림 *)
s_params : (bool * T.t) list;
s_eff : T.eff; (* 이 함수를 부르면 수행되는 effect *)
s_ret : T.t;
}
@@ -122,7 +123,10 @@ let rec conv env (gen : string list) (t : Ast.ty) : T.t =
T.TFn
{
affine;
params = List.map (conv env gen) params;
params =
List.map
(fun (p : fn_param_ty) -> (p.pt_own, conv env gen p.pt_ty))
params;
eff = (match eff with None -> [] | Some a -> conv_eff_atom a);
ret = (match ret with None -> T.TUnit | Some r -> conv env gen r);
}
@@ -141,7 +145,7 @@ let scheme_of env (d : fn_decl) : scheme =
{
s_gen = gen;
s_eff_gen = egen;
s_params = List.map (fun p -> conv env gen p.p_ty) d.fn_params;
s_params = List.map (fun p -> (p.p_own, conv env gen p.p_ty)) d.fn_params;
s_eff =
(match d.fn_eff with None -> [] | Some atoms -> conv_eff_result atoms);
s_ret = (match d.fn_ret with None -> T.TUnit | Some r -> conv env gen r);
@@ -151,14 +155,14 @@ let scheme_of env (d : fn_decl) : scheme =
let instantiate (s : scheme) =
let sub = List.map (fun v -> (v, T.fresh ())) s.s_gen in
let esub = List.map (fun v -> (v, T.fresh_eff ())) s.s_eff_gen in
( List.map (T.subst sub esub) s.s_params,
( List.map (fun (o, t) -> (o, T.subst sub esub t)) s.s_params,
T.subst_eff esub s.s_eff,
T.subst sub esub s.s_ret )
let instantiate_with (s : scheme) (args : T.t list) =
let sub = List.map2 (fun v a -> (v, a)) s.s_gen args in
let esub = List.map (fun v -> (v, T.fresh_eff ())) s.s_eff_gen in
( List.map (T.subst sub esub) s.s_params,
( List.map (fun (o, t) -> (o, T.subst sub esub t)) s.s_params,
T.subst_eff esub s.s_eff,
T.subst sub esub s.s_ret )
@@ -384,20 +388,31 @@ and infer_closure env (c : closure) (expected : T.t option) =
match Option.map T.resolve expected with
| Some (T.TFn { params; ret; _ })
when List.length params = List.length c.cl_params ->
(List.map Option.some params, Some ret)
(List.map (fun (o, t) -> Some (o, t)) params, Some ret)
| _ -> (List.map (fun _ -> None) c.cl_params, None)
in
push env;
let param_tys =
List.map2
(fun (n, ann) exp ->
(fun (p : cl_param) exp ->
let t =
match ann with
match p.cp_ty with
| Some a -> conv env [] a
| None -> ( match exp with Some t -> t | None -> T.TUnknown)
| None -> ( match exp with Some (_, t) -> t | None -> T.TUnknown)
in
bind env n t;
t)
(* 기대 타입이 소유를 말하는데 리터럴이 안 적었으면 오류다.
반대도 오류다 — 빌리는 자리에 소유를 주장하면 빌린 값을 소비한다.
방향을 다루려면 부분 타입이 필요하고 우리에겐 없다. *)
(match exp with
| Some (o, _) when o <> p.cp_own ->
err env c.cl_pos
(Printf.sprintf "클로저 파라미터 %s의 소유권이 기대와 다릅니다 (기대: %s, 적힌 것: %s)"
p.cp_name
(if o then "own" else "빌림")
(if p.cp_own then "own" else "빌림"))
| _ -> ());
bind env p.cp_name t;
(p.cp_own, t))
c.cl_params expected_params
in
let declared_ret = Option.map (conv env []) c.cl_ret in
@@ -446,7 +461,14 @@ and infer_call env callee args pos =
| None -> (
match builtin_ctor n with
| Some (params, ret) ->
Some (T.TFn { affine = false; params; eff = []; ret })
Some
(T.TFn
{
affine = false;
params = List.map (fun t -> (false, t)) params;
eff = [];
ret;
})
| None -> (
match Hashtbl.find_opt env.fns n with
| Some s ->
@@ -474,7 +496,7 @@ and infer_call env callee args pos =
List.iter (fun a -> ignore (infer env a)) args)
else
List.iter2
(fun p a ->
(fun (_, p) a ->
let got =
match a with
| E_closure c -> infer_closure env c (Some p)
@@ -507,7 +529,7 @@ and ctor_fn env enum name =
let sub = List.map (fun v -> (v, T.fresh ())) gen in
let params =
match List.assoc_opt name variants with
| Some ts -> List.map (T.subst sub []) ts
| Some ts -> List.map (fun t -> (false, T.subst sub [] t)) ts
| None -> []
in
T.TFn