move: move/affinity 검사 — v0 fast path 완성
보안 정리 (ii) — safe code에서 capability는 복제·위조되지 않는다 — 를 코드로 닫는다. 검사는 전부 함수 로컬 데이터플로우이고 전역 분석이 없다. affinity의 뿌리는 capability다. 필드로 가진 타입은 전이적으로 affine이며 고정점까지 돌려 상호 재귀 타입도 유도한다. 이 전이가 없으면 wrapper 하나를 복사해 capability가 사실상 복제되므로 정리가 깨진다. copyable 선언과 affine 필드의 공존은 오류다. 구현한 규칙: - affine 값은 소유 자리로 갈 때 move된다(own 파라미터, 반환, struct 저장, 컨테이너 삽입, let 바인딩, by-move capture). moved 이후 사용은 오류이고 진단이 어디서 소비됐는지를 말한다 - 분기 병합은 보수적 합집합. 한 분기에서라도 moved면 병합 이후 moved - 빌린 값은 탈출하지 못한다: 반환, struct 저장, 소유 자리로 넘기기 전부 거부 - use의 전염: 빌린 값을 capture한 클로저는 그 자체가 빌린 값이라 소유 자리로 갈 수 없다. 별도의 nonescaping 개념 없이 use 규칙 하나로 닫힌다 - callable affinity: affine 값을 capture한 클로저는 affine fn이며 fn 자리에 갈 수 없다 자율 결정 둘: - 클로저는 mut 바인딩을 capture할 수 없다. spawn만 막는 특수 규칙 대신 일반 규칙으로 뒀다 — v0에 참조가 없으므로 별칭도 조용한 복사도 만들 수 없고, spawn 제한은 이 규칙의 특수 사례가 된다 - v0에 부분 move는 없다. 필드 접근은 빌림이고 결과도 빌린 값이다. affine 필드만 꺼내려면 부분 move 상태 추적이 필요한데 v0가 살 복잡도가 아니다 05를 자족적으로 다시 썼다. affinity의 뿌리가 capability라 자원 타입을 모듈 안에서 정의해야 검사기가 affine임을 유도할 수 있다. 외부 타입은 affine임을 증명할 수 없으므로 copyable로 본다. 이로써 fast path(L0 parse / L1 type·effect·capability·ownership)가 완성됐다. cool check가 처음으로 성공을 선언한다 — 01~04, 06, 07이 exit 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:
+132
-3
@@ -580,7 +580,8 @@ let () =
|
||||
Sys.readdir dir |> Array.to_list
|
||||
|> List.filter (fun f -> Filename.check_suffix f ".cool")
|
||||
|> List.filter (fun f ->
|
||||
f <> "08_syntax_errors.cool"
|
||||
f <> "05_move_errors.cool"
|
||||
&& f <> "08_syntax_errors.cool"
|
||||
&& f <> "09_type_errors.cool"
|
||||
&& f <> "10_effect_errors.cool")
|
||||
|> List.sort compare
|
||||
@@ -599,9 +600,12 @@ let () =
|
||||
| Ok () -> check "09는 타입 오류를 내야 한다" false
|
||||
| Error errors ->
|
||||
check "09의 오류를 전부 모은다 (첫 오류에서 멈추지 않는다)" (List.length errors >= 18));
|
||||
match Driver.typecheck (Filename.concat dir "10_effect_errors.cool") with
|
||||
(match Driver.typecheck (Filename.concat dir "10_effect_errors.cool") with
|
||||
| Ok () -> check "10은 effect 오류를 내야 한다" false
|
||||
| Error errors -> check "10의 effect 오류" (List.length errors >= 6)
|
||||
| Error errors -> check "10의 effect 오류" (List.length errors >= 6));
|
||||
match Driver.typecheck (Filename.concat dir "05_move_errors.cool") with
|
||||
| Ok () -> check "05는 move 오류를 내야 한다" false
|
||||
| Error errors -> check "05의 move 오류" (List.length errors >= 9)
|
||||
|
||||
(* ================================================================== *)
|
||||
(* effect / capability 검사 *)
|
||||
@@ -688,3 +692,128 @@ let () =
|
||||
(type_has
|
||||
(cap ^ "fn f() effects {Db.read} -> Int {\n Db.read(1)\n}")
|
||||
"값을 통해서만")
|
||||
|
||||
(* ================================================================== *)
|
||||
(* move / affinity 검사 *)
|
||||
(* ================================================================== *)
|
||||
|
||||
let move_errs src =
|
||||
List.map (fun (e : Move.error) -> e.msg) (Move.check (parse_ok src))
|
||||
|
||||
let move_ok src = move_errs src = []
|
||||
|
||||
let move_has src frag =
|
||||
List.exists
|
||||
(fun m ->
|
||||
let n = String.length frag in
|
||||
let rec go i =
|
||||
i + n <= String.length m && (String.sub m i n = frag || go (i + 1))
|
||||
in
|
||||
go 0)
|
||||
(move_errs src)
|
||||
|
||||
let res =
|
||||
"capability F {\n\
|
||||
\ fn size() -> Int\n\
|
||||
}\n\
|
||||
fn drop(own f: F)\n\
|
||||
fn peek(f: F) -> Int\n"
|
||||
|
||||
(* --- 이중 소비와 분기 병합 --- *)
|
||||
|
||||
let () =
|
||||
check "빌리기만 하면 여러 번 써도 된다"
|
||||
(move_ok (res ^ "fn f(x: F) -> Int {\n peek(x) + peek(x)\n}"));
|
||||
check "이중 소비는 오류"
|
||||
(move_has (res ^ "fn f(own x: F) {\n drop(x)\n drop(x)\n}") "이미 move");
|
||||
check "양쪽 분기에서 소비하면 통과"
|
||||
(move_ok
|
||||
(res
|
||||
^ "fn f(own x: F, c: Bool) {\n\
|
||||
\ if c {\n\
|
||||
\ drop(x)\n\
|
||||
\ } else {\n\
|
||||
\ drop(x)\n\
|
||||
\ }\n\
|
||||
}"));
|
||||
check "한 분기에서만 소비해도 병합 이후는 moved (보수적 합집합)"
|
||||
(move_has
|
||||
(res
|
||||
^ "fn f(own x: F, c: Bool) {\n if c {\n drop(x)\n }\n drop(x)\n}")
|
||||
"이미 move");
|
||||
check "소비한 자리를 진단에 담는다"
|
||||
(move_has (res ^ "fn f(own x: F) {\n drop(x)\n drop(x)\n}") "에서 소비")
|
||||
|
||||
(* --- 빌린 값은 탈출하지 못한다 --- *)
|
||||
|
||||
let () =
|
||||
check "빌린 값의 반환" (move_has (res ^ "fn f(x: F) -> F {\n x\n}") "반환할 수 없습니다");
|
||||
check "빌린 값을 소유 자리로" (move_has (res ^ "fn f(x: F) {\n drop(x)\n}") "빌린 값이라");
|
||||
check "own으로 받으면 넘길 수 있다" (move_ok (res ^ "fn f(own x: F) {\n drop(x)\n}"));
|
||||
check "빌린 값의 struct 저장"
|
||||
(move_has
|
||||
(res ^ "struct H {\n f: F,\n}\nfn g(x: F) -> H {\n H { f: x }\n}")
|
||||
"struct에 저장할 수 없습니다")
|
||||
|
||||
(* --- use의 전염 --- *)
|
||||
|
||||
let () =
|
||||
check "빌린 값을 capture한 클로저는 빌린 값이다"
|
||||
(move_has
|
||||
(res ^ "fn sink(own h: fn())\nfn f(x: F) {\n sink(fn() { peek(x) })\n}")
|
||||
"use 값은 탈출하지 못합니다");
|
||||
check "빌려 쓰는 자리로는 넘길 수 있다"
|
||||
(move_ok
|
||||
(res ^ "fn borrow(h: fn())\nfn f(x: F) {\n borrow(fn() { peek(x) })\n}"))
|
||||
|
||||
(* --- callable affinity --- *)
|
||||
|
||||
let () =
|
||||
check "affine 값을 capture하면 affine fn"
|
||||
(move_ok (res ^ "fn f(own x: F) -> affine fn() {\n fn() { drop(x) }\n}"));
|
||||
check "affine 클로저를 fn 자리에 반환하면 오류"
|
||||
(move_has
|
||||
(res ^ "fn f(own x: F) -> fn() {\n fn() { drop(x) }\n}")
|
||||
"affine fn이어야 합니다");
|
||||
check "by-move capture는 바깥에서 소비다"
|
||||
(move_has
|
||||
(res
|
||||
^ "fn f(own x: F) -> affine fn() {\n\
|
||||
\ let g = fn() { drop(x) }\n\
|
||||
\ drop(x)\n\
|
||||
\ g\n\
|
||||
}")
|
||||
"이미 move")
|
||||
|
||||
(* --- affinity 전이 --- *)
|
||||
|
||||
let () =
|
||||
check "capability를 필드로 가지면 전이적으로 affine"
|
||||
(move_has
|
||||
(res ^ "struct B {\n f: F,\n}\nfn g(b: B) -> B {\n b\n}")
|
||||
"반환할 수 없습니다");
|
||||
check "copyable 선언과 affine 필드는 공존할 수 없다"
|
||||
(move_has (res ^ "copyable struct B {\n f: F,\n}") "copyable로 선언되었지만");
|
||||
check "affine이 없으면 copyable"
|
||||
(move_ok "copyable struct B {\n n: Int,\n}\nfn g(b: B) -> B {\n b\n}");
|
||||
check "컨테이너를 통해서도 전이된다"
|
||||
(move_has (res ^ "fn g(x: List[F]) -> List[F] {\n x\n}") "반환할 수 없습니다")
|
||||
|
||||
(* --- 클로저의 mut capture 금지 --- *)
|
||||
|
||||
let () =
|
||||
check "클로저는 mut 바인딩을 capture할 수 없다"
|
||||
(move_has
|
||||
"fn sink(h: fn())\n\
|
||||
fn f() {\n\
|
||||
\ let mut n = 0\n\
|
||||
\ sink(fn() { n = n + 1 })\n\
|
||||
}"
|
||||
"mut 바인딩");
|
||||
check "불변 바인딩은 capture해도 된다"
|
||||
(move_ok "fn sink(h: fn())\nfn f() {\n let n = 0\n sink(fn() { n })\n}")
|
||||
|
||||
(* --- 외부 타입은 affine임을 증명할 수 없다 --- *)
|
||||
|
||||
let () =
|
||||
check "모르는 타입은 copyable로 본다" (move_ok "fn f(x: Widget) -> Widget {\n x\n}")
|
||||
|
||||
Reference in New Issue
Block a user