순서가 요점이다. 문법에 test_decl과 panic_expr을 넣고 파서는 안 고친 채로
대조 장치를 돌렸더니 즉시 잡혔다:
문장 300개 중 파서가 거부한 것 140개
[1] 선언 (fn, struct, enum, capability, const)이(가) 필요합니다 — test 발견
파서를 따라가게 하니 다시 0건. 문법과 구현이 어긋나는 상태가 관측 가능한
것이 되었다는 뜻이다.
panic:
- 키워드다. prelude가 없어 함수로 두면 쓸 때마다 import해야 한다
- effect가 아니다. 경계 검사 하나에 {Panic}이 호출자 전부로 전염되면
effect 절은 신호가 아니라 잡음이 된다
- Never는 어떤 타입 자리에도 놓인다. 없으면 panic을 match 팔에서 못 쓴다
- 언어 수준 recover 없음. 되감기 없음. 자원 해제 여부는 열어둔다
- 0으로 나누기, assert 실패가 이 하나로 모인다
test:
- 파라미터가 없어 capability를 받을 수 없고, 만들 문법도 없다. 그래서
effect-free임이 증명된다 — 관례가 아니라 검사다. 시험해 보니 실제로
"테스트는 effect를 수행할 수 없습니다"로 거부한다
- 일반 코드와 같은 타입/effect/move 검사를 받는다
- interface hash에서 제외 — 테스트를 고쳤다고 downstream이 재검사되면 안 된다
- 격리는 런타임의 일이다. 하나가 죽어도 나머지는 돈다
assert는 std/test.cool에 coollang으로 쓰였다 — panic 위의 설탕임이 코드로
보이고, std에서 본문이 있는 첫 함수가 됐다. 그 바람에 std/런타임 양방향
테스트가 걸렸고(본문 있는 함수에 런타임 구현을 요구했다), 그 구분을 넣었다.
samples/app/config.cool에 첫 테스트 넷.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
189 lines
6.2 KiB
OCaml
189 lines
6.2 KiB
OCaml
(* 타입과 effect 표현, 그리고 지역 unification.
|
|
|
|
TUnknown이 핵심이다. 외부 모듈에서 오는 이름은 모듈 로딩이 없는 v0에서
|
|
해소할 수 없다. 그런 타입은 TUnknown이 되고 무엇과도 맞는다 — 모르는 것을
|
|
틀렸다고 말하지 않기 위해서다.
|
|
|
|
effect는 순서 없는 집합이고 합성은 합집합이다. 변수는 집합 변수이며,
|
|
호출 지점에서 결정 위치(파라미터의 effect 자리에 단독으로 선 변수)를 통해
|
|
메타에 묶인다. 함수 하나 범위를 넘지 않는다. *)
|
|
|
|
type t =
|
|
| TUnknown
|
|
(* 값을 내지 않는 타입. panic의 타입이고 어떤 자리에도 놓일 수 있다.
|
|
TUnknown과 다르다 — TUnknown은 "모른다"이고 TNever는 "돌아오지
|
|
않는다"이다. 둘 다 무엇과도 맞지만 생기는 이유가 다르다. *)
|
|
| TNever
|
|
| TInt
|
|
| TBool
|
|
| TString
|
|
| TUnit
|
|
| TVar of string
|
|
| TCon of string * t list
|
|
| TFn of { affine : bool; params : t list; eff : eff; ret : t }
|
|
| TMeta of meta ref
|
|
|
|
and meta = Unbound of int | Bound of t
|
|
|
|
(* effect 집합. 원소는 구체 이름, 집합 변수, 또는 호출 지점의 미지수다. *)
|
|
and eff = atom list
|
|
|
|
and atom =
|
|
| A_name of string * string (* Cap.method *)
|
|
| A_var of string
|
|
| A_meta of emeta ref
|
|
|
|
and emeta = EUnbound of int | EBound of eff
|
|
|
|
let counter = ref 0
|
|
|
|
let fresh () =
|
|
incr counter;
|
|
TMeta (ref (Unbound !counter))
|
|
|
|
let fresh_eff () =
|
|
incr counter;
|
|
[ A_meta (ref (EUnbound !counter)) ]
|
|
|
|
let rec resolve t =
|
|
match t with TMeta { contents = Bound u } -> resolve u | _ -> t
|
|
|
|
(* 묶인 메타를 펼치고 중복을 없앤다. 집합이므로 순서는 의미가 없다. *)
|
|
let rec eff_resolve (e : eff) : eff =
|
|
let expand a =
|
|
match a with
|
|
| A_meta { contents = EBound inner } -> eff_resolve inner
|
|
| _ -> [ a ]
|
|
in
|
|
let flat = List.concat_map expand e in
|
|
let mem a acc =
|
|
List.exists
|
|
(fun b ->
|
|
match (a, b) with
|
|
| A_name (c1, m1), A_name (c2, m2) -> c1 = c2 && m1 = m2
|
|
| A_var x, A_var y -> x = y
|
|
| A_meta r, A_meta r' -> r == r'
|
|
| _ -> false)
|
|
acc
|
|
in
|
|
List.fold_left (fun acc a -> if mem a acc then acc else a :: acc) [] flat
|
|
|> List.rev
|
|
|
|
let atom_show = function
|
|
| A_name (c, m) -> c ^ "." ^ m
|
|
| A_var v -> v
|
|
| A_meta { contents = EUnbound n } -> Printf.sprintf "_e%d" n
|
|
| A_meta { contents = EBound _ } -> "?"
|
|
|
|
let eff_show e =
|
|
match eff_resolve e with
|
|
| [] -> "{}"
|
|
| atoms -> "{" ^ String.concat ", " (List.map atom_show atoms) ^ "}"
|
|
|
|
let atom_eq a b =
|
|
match (a, b) with
|
|
| A_name (c1, m1), A_name (c2, m2) -> c1 = c2 && m1 = m2
|
|
| A_var x, A_var y -> x = y
|
|
| A_meta r, A_meta r' -> r == r'
|
|
| _ -> false
|
|
|
|
(* declared가 덮지 못하는 원소들. 미지수는 판정을 미룬다 —
|
|
결정되지 않은 것을 위반이라고 말하지 않는다. *)
|
|
let eff_missing ~declared ~performed =
|
|
let declared = eff_resolve declared in
|
|
List.filter
|
|
(fun a ->
|
|
match a with
|
|
| A_meta { contents = EUnbound _ } -> false
|
|
| _ -> not (List.exists (atom_eq a) declared))
|
|
(eff_resolve performed)
|
|
|
|
let rec show t =
|
|
match resolve t with
|
|
| TUnknown -> "?"
|
|
| TNever -> "Never"
|
|
| TInt -> "Int"
|
|
| TBool -> "Bool"
|
|
| TString -> "String"
|
|
| TUnit -> "Unit"
|
|
| TVar v -> v
|
|
| TCon (n, []) -> n
|
|
| TCon (n, args) -> n ^ "[" ^ String.concat ", " (List.map show args) ^ "]"
|
|
| TFn { affine; params; eff; ret } -> (
|
|
(if affine then "affine fn(" else "fn(")
|
|
^ String.concat ", " (List.map show params)
|
|
^ ")"
|
|
^ (match eff_resolve eff with [] -> "" | e -> " effects " ^ eff_show e)
|
|
^ match resolve ret with TUnit -> "" | r -> " -> " ^ show r)
|
|
| TMeta { contents = Unbound n } -> Printf.sprintf "_%d" n
|
|
| TMeta { contents = Bound _ } -> "?"
|
|
|
|
let rec occurs r t =
|
|
match resolve t with
|
|
| TMeta r' -> r == r'
|
|
| TCon (_, args) -> List.exists (occurs r) args
|
|
| TFn { params; ret; _ } -> List.exists (occurs r) params || occurs r ret
|
|
| _ -> false
|
|
|
|
(* 결정 위치의 해소: 파라미터의 effect 자리에 단독으로 선 미지수만 묶는다.
|
|
그 외에는 참을 돌려주고, 실제 포함 검사는 호출 지점에서 방향을 아는
|
|
쪽이 한다 (진단 품질 때문에). *)
|
|
let unify_eff a b =
|
|
match (eff_resolve a, eff_resolve b) with
|
|
| [ A_meta ({ contents = EUnbound _ } as r) ], other ->
|
|
r := EBound other;
|
|
true
|
|
| other, [ A_meta ({ contents = EUnbound _ } as r) ] ->
|
|
r := EBound other;
|
|
true
|
|
| _ -> true
|
|
|
|
let rec unify a b =
|
|
match (resolve a, resolve b) with
|
|
| TUnknown, _ | _, TUnknown -> true
|
|
(* Never는 어떤 타입 자리에도 놓인다. panic이 match 팔에 설 수 있는 이유다 *)
|
|
| TNever, _ | _, TNever -> true
|
|
| TMeta r, TMeta r' when r == r' -> true
|
|
| TMeta r, t | t, TMeta r ->
|
|
if occurs r t then false
|
|
else (
|
|
r := Bound t;
|
|
true)
|
|
| TInt, TInt | TBool, TBool | TString, TString | TUnit, TUnit -> true
|
|
| TVar x, TVar y -> x = y
|
|
| TCon (n, xs), TCon (m, ys) ->
|
|
n = m && List.length xs = List.length ys && List.for_all2 unify xs ys
|
|
| TFn f, TFn g ->
|
|
(* affinity는 타입 동등성의 일부가 아니다. 값이 affine인지는 무엇을
|
|
capture했는지로 정해지는 substructural 성질이고, move/affinity 검사가
|
|
소유한다. 여기서 섞으면 두 검사가 서로의 결론을 앞질러 버린다. *)
|
|
List.length f.params = List.length g.params
|
|
&& List.for_all2 unify f.params g.params
|
|
&& unify_eff f.eff g.eff && unify f.ret g.ret
|
|
| _ -> false
|
|
|
|
(* 제네릭 인스턴스화: 타입 변수와 effect 변수를 동시에 바꾼다 *)
|
|
let rec subst tenv eenv t =
|
|
match resolve t with
|
|
| TVar v -> ( match List.assoc_opt v tenv with Some u -> u | None -> TVar v)
|
|
| TCon (n, args) -> TCon (n, List.map (subst tenv eenv) args)
|
|
| TFn f ->
|
|
TFn
|
|
{
|
|
affine = f.affine;
|
|
params = List.map (subst tenv eenv) f.params;
|
|
eff = subst_eff eenv f.eff;
|
|
ret = subst tenv eenv f.ret;
|
|
}
|
|
| u -> u
|
|
|
|
and subst_eff eenv (e : eff) : eff =
|
|
eff_resolve
|
|
(List.concat_map
|
|
(fun a ->
|
|
match a with
|
|
| A_var v -> (
|
|
match List.assoc_opt v eenv with Some s -> s | None -> [ a ])
|
|
| _ -> [ a ])
|
|
e)
|