modules: 모듈 경계, interface hash, 그리고 고정점 invalidation
되돌리기 비싼 결정 중 마지막 하나 — incremental 아키텍처 — 를 코드와
테스트로 닫는다.
- iface.ml: exported surface 추출과 해시. 별칭 한정(qualify)은 소비 시점에만
일어나므로 가져오는 쪽의 별칭이 정의 모듈의 hash에 새지 않는다.
- session.ml: 모듈 로딩과 고정점 전파. hash 비교가 dependents 재검사보다
앞선다 — 이 순서가 "본문만 수정 시 downstream 0건"의 전부다.
- 한정 이름(Alias.Type, Alias.Ctor, Alias.fn)을 타입 검사, 패턴, 소진성,
move 검사가 모두 하나의 키("Alias.name")로 본다.
- 패키지 경로(cool.dev/std/list)는 v0에서 해소하지 않고 불투명하게 둔다.
없다고 말하지 않는다.
- 회귀 테스트: 본문만 고치면 자기 자신만 재검사(1건), variant를 추가하면
downstream까지 전파되고 실제로 소진성이 깨진다(2건).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+31
-2
@@ -2,7 +2,8 @@ let usage =
|
|||||||
{|coollang toolchain
|
{|coollang toolchain
|
||||||
|
|
||||||
사용법:
|
사용법:
|
||||||
cool check <file.cool>... 타입/effect/capability 검사 (fast path)
|
cool check <file.cool>... 타입/effect/capability 검사 (import를 따라 모듈 그래프 전체)
|
||||||
|
cool iface <file.cool> interface 표면과 해시 출력
|
||||||
cool run <file.cool> typed IR 인터프리터로 실행
|
cool run <file.cool> typed IR 인터프리터로 실행
|
||||||
cool tokens <file.cool> 토큰 덤프 (렉서 디버깅)
|
cool tokens <file.cool> 토큰 덤프 (렉서 디버깅)
|
||||||
cool ast <file.cool> 구문 트리 덤프 (파서 디버깅)
|
cool ast <file.cool> 구문 트리 덤프 (파서 디버깅)
|
||||||
@@ -32,6 +33,33 @@ let dump_ast file =
|
|||||||
m.Coollang.Ast.items;
|
m.Coollang.Ast.items;
|
||||||
0
|
0
|
||||||
|
|
||||||
|
(* import를 따라 모듈 그래프를 로드하고 전부 검사한다. root는 첫 파일의 디렉터리다. *)
|
||||||
|
let check_graph files =
|
||||||
|
match files with
|
||||||
|
| [] ->
|
||||||
|
prerr_endline "검사할 파일이 없습니다";
|
||||||
|
2
|
||||||
|
| first :: _ ->
|
||||||
|
let st = Coollang.Session.create ~root:(Filename.dirname first) () in
|
||||||
|
List.iter (fun f -> Coollang.Session.load st f) files;
|
||||||
|
let errors = Coollang.Session.errors st in
|
||||||
|
List.iter
|
||||||
|
(fun e -> prerr_endline (Coollang.Session.string_of_error e))
|
||||||
|
errors;
|
||||||
|
if errors = [] then 0 else 1
|
||||||
|
|
||||||
|
let dump_iface file =
|
||||||
|
let st = Coollang.Session.create ~root:(Filename.dirname file) () in
|
||||||
|
Coollang.Session.load st file;
|
||||||
|
match Coollang.Session.find st file with
|
||||||
|
| None -> 1
|
||||||
|
| Some e ->
|
||||||
|
Printf.printf "hash %s\n" e.iface.Coollang.Iface.hash;
|
||||||
|
List.iter
|
||||||
|
(fun it -> print_endline (Coollang.Ast.show_item it))
|
||||||
|
e.iface.Coollang.Iface.items;
|
||||||
|
0
|
||||||
|
|
||||||
let dump_deps file =
|
let dump_deps file =
|
||||||
match Coollang.Driver.resolve file with
|
match Coollang.Driver.resolve file with
|
||||||
| Error errors -> report_errors errors
|
| Error errors -> report_errors errors
|
||||||
@@ -46,7 +74,8 @@ let () =
|
|||||||
let argv = Array.to_list Sys.argv in
|
let argv = Array.to_list Sys.argv in
|
||||||
let code =
|
let code =
|
||||||
match List.tl argv with
|
match List.tl argv with
|
||||||
| "check" :: files -> report (Coollang.Driver.check files)
|
| "check" :: files -> check_graph files
|
||||||
|
| [ "iface"; file ] -> dump_iface file
|
||||||
| [ "run"; file ] -> report (Coollang.Driver.run file)
|
| [ "run"; file ] -> report (Coollang.Driver.run file)
|
||||||
| [ "tokens"; file ] -> dump_tokens file
|
| [ "tokens"; file ] -> dump_tokens file
|
||||||
| [ "ast"; file ] -> dump_ast file
|
| [ "ast"; file ] -> dump_ast file
|
||||||
|
|||||||
+18
-4
@@ -11,7 +11,14 @@ type eff_atom = Eff_var of string | Eff_set of eff_name list
|
|||||||
type eff_result = eff_atom list (* 합집합. 길이 1이면 단일 *)
|
type eff_result = eff_atom list (* 합집합. 길이 1이면 단일 *)
|
||||||
|
|
||||||
type ty =
|
type ty =
|
||||||
| T_named of { name : string; args : targ list; pos : pos }
|
(* modl: 다른 모듈의 타입은 별칭으로 한정한다 (Shapes.Shape).
|
||||||
|
한정하지 않으면 이 모듈의 이름이다 — 암묵적으로 끌어오지 않는다. *)
|
||||||
|
| T_named of {
|
||||||
|
modl : string option;
|
||||||
|
name : string;
|
||||||
|
args : targ list;
|
||||||
|
pos : pos;
|
||||||
|
}
|
||||||
| T_fn of {
|
| T_fn of {
|
||||||
affine : bool;
|
affine : bool;
|
||||||
params : ty list;
|
params : ty list;
|
||||||
@@ -28,7 +35,12 @@ type pattern =
|
|||||||
| P_wild of pos
|
| P_wild of pos
|
||||||
| P_lit of lit * pos
|
| P_lit of lit * pos
|
||||||
| P_bind of string * pos
|
| P_bind of string * pos
|
||||||
| P_ctor of { name : string; args : pattern list; pos : pos }
|
| P_ctor of {
|
||||||
|
modl : string option;
|
||||||
|
name : string;
|
||||||
|
args : pattern list;
|
||||||
|
pos : pos;
|
||||||
|
}
|
||||||
|
|
||||||
type unop = U_not | U_neg
|
type unop = U_not | U_neg
|
||||||
|
|
||||||
@@ -164,7 +176,8 @@ let buf_eff_atom b = function
|
|||||||
names
|
names
|
||||||
|
|
||||||
let rec buf_ty b = function
|
let rec buf_ty b = function
|
||||||
| T_named { name; args; _ } ->
|
| T_named { modl; name; args; _ } ->
|
||||||
|
let name = match modl with None -> name | Some m -> m ^ "." ^ name in
|
||||||
if args = [] then Buffer.add_string b name
|
if args = [] then Buffer.add_string b name
|
||||||
else (
|
else (
|
||||||
Buffer.add_string b ("(" ^ name);
|
Buffer.add_string b ("(" ^ name);
|
||||||
@@ -202,7 +215,8 @@ let rec buf_pattern b = function
|
|||||||
| P_wild _ -> Buffer.add_char b '_'
|
| P_wild _ -> Buffer.add_char b '_'
|
||||||
| P_lit (l, _) -> buf_lit b l
|
| P_lit (l, _) -> buf_lit b l
|
||||||
| P_bind (n, _) -> Buffer.add_string b n
|
| P_bind (n, _) -> Buffer.add_string b n
|
||||||
| P_ctor { name; args; _ } ->
|
| P_ctor { modl; name; args; _ } ->
|
||||||
|
let name = match modl with None -> name | Some m -> m ^ "." ^ name in
|
||||||
Buffer.add_string b ("(" ^ name);
|
Buffer.add_string b ("(" ^ name);
|
||||||
List.iter
|
List.iter
|
||||||
(fun p ->
|
(fun p ->
|
||||||
|
|||||||
+2
-1
@@ -56,7 +56,8 @@ let rec of_pattern env (p : Ast.pattern) : cpat =
|
|||||||
| Ast.P_lit (Ast.L_int n, _) -> CCtor ("<" ^ n ^ ">", [])
|
| Ast.P_lit (Ast.L_int n, _) -> CCtor ("<" ^ n ^ ">", [])
|
||||||
| Ast.P_lit (Ast.L_str v, _) -> CCtor ("<" ^ String.escaped v ^ ">", [])
|
| Ast.P_lit (Ast.L_str v, _) -> CCtor ("<" ^ String.escaped v ^ ">", [])
|
||||||
| Ast.P_bind (n, _) -> if env.is_ctor n then CCtor (n, []) else CWild
|
| Ast.P_bind (n, _) -> if env.is_ctor n then CCtor (n, []) else CWild
|
||||||
| Ast.P_ctor { name; args; _ } ->
|
| Ast.P_ctor { modl; name; args; _ } ->
|
||||||
|
let name = match modl with Some a -> a ^ "." ^ name | None -> name in
|
||||||
if env.is_ctor name then CCtor (name, List.map (of_pattern env) args)
|
if env.is_ctor name then CCtor (name, List.map (of_pattern env) args)
|
||||||
else CWild
|
else CWild
|
||||||
|
|
||||||
|
|||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
(* interface artifact와 그 해시.
|
||||||
|
|
||||||
|
hash 입력 = 모듈 exported surface 전체의 의미적 정규형이다 (문서 P7).
|
||||||
|
함수 시그니처(effect 포함), 타입 정의 본문(struct 필드, enum variant),
|
||||||
|
타입의 affinity, 상수의 타입과 값, capability 선언, 그리고 reexport된
|
||||||
|
선언을 완전히 해소한 정의 본문.
|
||||||
|
|
||||||
|
원칙: downstream 검사 결과에 영향을 줄 수 있는 모든 것을 포함한다.
|
||||||
|
의심스러우면 넣는다 — 과잉 포함의 비용은 재검사지만 누락의 비용은
|
||||||
|
잘못된 캐시라는 비대칭이 있다.
|
||||||
|
|
||||||
|
함수 본문은 들어가지 않는다. 본문 한 줄 수정이 해시를 흔들면 incremental
|
||||||
|
전제가 무너진다. *)
|
||||||
|
|
||||||
|
open Ast
|
||||||
|
|
||||||
|
type t = { items : item list; (* 본문을 벗긴 exported surface *) hash : string }
|
||||||
|
|
||||||
|
let strip_body (d : fn_decl) = { d with fn_body = None }
|
||||||
|
|
||||||
|
let is_exported = function
|
||||||
|
| I_fn { pub; _ } -> pub
|
||||||
|
| I_struct { pub; _ } -> pub
|
||||||
|
| I_enum { pub; _ } -> pub
|
||||||
|
| I_capability { pub; _ } -> pub
|
||||||
|
| I_const { pub; _ } -> pub
|
||||||
|
| I_reexport _ -> true
|
||||||
|
| I_import _ -> false
|
||||||
|
|
||||||
|
let strip = function
|
||||||
|
| I_fn { pub; decl } -> I_fn { pub; decl = strip_body decl }
|
||||||
|
| it -> it
|
||||||
|
|
||||||
|
let item_name = function
|
||||||
|
| I_fn { decl; _ } -> decl.fn_name
|
||||||
|
| I_struct { name; _ } -> name
|
||||||
|
| I_enum { name; _ } -> name
|
||||||
|
| I_capability { name; _ } -> name
|
||||||
|
| I_const { name; _ } -> name
|
||||||
|
| I_reexport { name; _ } -> name
|
||||||
|
| I_import { alias; _ } -> alias
|
||||||
|
|
||||||
|
(* reexport는 이름이 아니라 해소된 정의 본문이 hash에 들어간다.
|
||||||
|
A의 enum에 variant가 추가되면 B의 소스가 그대로여도 B의 hash가 변하고,
|
||||||
|
C의 exhaustive match가 재검사된다. *)
|
||||||
|
let surface (m : modul) : item list =
|
||||||
|
let is_definition = function
|
||||||
|
| I_reexport _ | I_import _ -> false
|
||||||
|
| _ -> true
|
||||||
|
in
|
||||||
|
let find name =
|
||||||
|
List.find_opt (fun it -> is_definition it && item_name it = name) m.items
|
||||||
|
in
|
||||||
|
List.concat_map
|
||||||
|
(fun it ->
|
||||||
|
match it with
|
||||||
|
| I_reexport { name; _ } -> (
|
||||||
|
match find name with Some d -> [ strip d ] | None -> [])
|
||||||
|
| it when is_exported it && is_definition it -> [ strip it ]
|
||||||
|
| _ -> [])
|
||||||
|
m.items
|
||||||
|
|
||||||
|
(* 정규형: 항목을 이름순으로 정렬해 선언 순서가 해시에 새지 않게 한다.
|
||||||
|
소스에서 함수 둘의 위치를 바꾸는 것은 downstream에 아무 영향이 없다. *)
|
||||||
|
let render (items : item list) : string =
|
||||||
|
items |> List.map show_item |> List.sort compare |> String.concat "\n"
|
||||||
|
|
||||||
|
let of_module (m : modul) : t =
|
||||||
|
let items = surface m in
|
||||||
|
{ items; hash = Digest.to_hex (Digest.string (render items)) }
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* 소비 측 한정 *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
(* 가져온 모듈의 exported surface를 소비 측 이름 공간으로 옮긴다.
|
||||||
|
`import "shapes" as Shapes`라면 Shape는 "Shapes.Shape"가 된다.
|
||||||
|
|
||||||
|
왜 소비 시점인가 — 별칭은 가져오는 쪽의 선택이므로 정의한 모듈의
|
||||||
|
interface hash에 새어서는 안 된다. of_module은 한정하지 않은 표면을
|
||||||
|
해시하고, 한정은 여기서만 한다.
|
||||||
|
|
||||||
|
v0의 한계 두 가지, 의도적으로 남긴다:
|
||||||
|
- 가져온 모듈이 다시 다른 모듈의 타입을 참조하면(전이 참조) 불투명해진다.
|
||||||
|
- effect atom의 capability 이름은 한정하지 않는다. 즉 effect 이름은 v0에서
|
||||||
|
전역이다. 모듈별 identity는 v1 과제다. *)
|
||||||
|
|
||||||
|
let builtin_ty_names =
|
||||||
|
[ "Int"; "Bool"; "String"; "Unit"; "List"; "Option"; "Result"; "TaskScope" ]
|
||||||
|
|
||||||
|
let opaque pos = T_named { modl = None; name = "«외부»"; args = []; pos }
|
||||||
|
|
||||||
|
let rec q_ty alias defined gen (t : ty) : ty =
|
||||||
|
match t with
|
||||||
|
| T_named { modl = Some _; pos; _ } -> opaque pos
|
||||||
|
| T_named { modl = None; name; args; pos } ->
|
||||||
|
let args = List.map (q_targ alias defined gen) args in
|
||||||
|
if List.mem name gen || List.mem name builtin_ty_names then
|
||||||
|
T_named { modl = None; name; args; pos }
|
||||||
|
else if List.mem name defined then
|
||||||
|
T_named { modl = None; name = alias ^ "." ^ name; args; pos }
|
||||||
|
else opaque pos
|
||||||
|
| T_fn { affine; params; eff; ret; pos } ->
|
||||||
|
T_fn
|
||||||
|
{
|
||||||
|
affine;
|
||||||
|
params = List.map (q_ty alias defined gen) params;
|
||||||
|
eff;
|
||||||
|
ret = Option.map (q_ty alias defined gen) ret;
|
||||||
|
pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
and q_targ alias defined gen = function
|
||||||
|
| TA_ty t -> TA_ty (q_ty alias defined gen t)
|
||||||
|
| TA_eff e -> TA_eff e
|
||||||
|
|
||||||
|
let q_fn alias defined (d : fn_decl) : fn_decl =
|
||||||
|
let gen = List.map (fun g -> g.gp_name) d.fn_gen in
|
||||||
|
{
|
||||||
|
d with
|
||||||
|
fn_params =
|
||||||
|
List.map
|
||||||
|
(fun p -> { p with p_ty = q_ty alias defined gen p.p_ty })
|
||||||
|
d.fn_params;
|
||||||
|
fn_ret = Option.map (q_ty alias defined gen) d.fn_ret;
|
||||||
|
fn_body = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let defined_names (items : item list) =
|
||||||
|
List.filter_map
|
||||||
|
(function
|
||||||
|
| I_struct { name; _ } | I_enum { name; _ } | I_capability { name; _ } ->
|
||||||
|
Some name
|
||||||
|
| _ -> None)
|
||||||
|
items
|
||||||
|
|
||||||
|
let qualify (alias : string) (items : item list) : item list =
|
||||||
|
let defined = defined_names items in
|
||||||
|
let p n = alias ^ "." ^ n in
|
||||||
|
List.map
|
||||||
|
(fun it ->
|
||||||
|
match it with
|
||||||
|
| I_fn { pub; decl } ->
|
||||||
|
I_fn
|
||||||
|
{
|
||||||
|
pub;
|
||||||
|
decl = { (q_fn alias defined decl) with fn_name = p decl.fn_name };
|
||||||
|
}
|
||||||
|
| I_struct { pub; copyable; name; gen; fields; pos } ->
|
||||||
|
let g = List.map (fun x -> x.gp_name) gen in
|
||||||
|
I_struct
|
||||||
|
{
|
||||||
|
pub;
|
||||||
|
copyable;
|
||||||
|
name = p name;
|
||||||
|
gen;
|
||||||
|
fields =
|
||||||
|
List.map
|
||||||
|
(fun f -> { f with f_ty = q_ty alias defined g f.f_ty })
|
||||||
|
fields;
|
||||||
|
pos;
|
||||||
|
}
|
||||||
|
| I_enum { pub; name; gen; variants; pos } ->
|
||||||
|
let g = List.map (fun x -> x.gp_name) gen in
|
||||||
|
I_enum
|
||||||
|
{
|
||||||
|
pub;
|
||||||
|
name = p name;
|
||||||
|
gen;
|
||||||
|
variants =
|
||||||
|
List.map
|
||||||
|
(fun v ->
|
||||||
|
{
|
||||||
|
v with
|
||||||
|
v_name = p v.v_name;
|
||||||
|
v_args = List.map (q_ty alias defined g) v.v_args;
|
||||||
|
})
|
||||||
|
variants;
|
||||||
|
pos;
|
||||||
|
}
|
||||||
|
| I_capability { pub; name; methods; pos } ->
|
||||||
|
I_capability
|
||||||
|
{
|
||||||
|
pub;
|
||||||
|
name = p name;
|
||||||
|
methods = List.map (q_fn alias defined) methods;
|
||||||
|
pos;
|
||||||
|
}
|
||||||
|
| I_const { pub; name; ty; value; pos } ->
|
||||||
|
I_const
|
||||||
|
{ pub; name = p name; ty = q_ty alias defined [] ty; value; pos }
|
||||||
|
| it -> it)
|
||||||
|
items
|
||||||
+11
-9
@@ -55,7 +55,8 @@ let builtin_containers = [ "List"; "Option"; "Result" ]
|
|||||||
let rec ty_affine st (t : Ast.ty) =
|
let rec ty_affine st (t : Ast.ty) =
|
||||||
match t with
|
match t with
|
||||||
| T_fn { affine; _ } -> affine
|
| T_fn { affine; _ } -> affine
|
||||||
| T_named { name; args; _ } ->
|
| T_named { modl; name; args; _ } ->
|
||||||
|
let name = match modl with Some a -> a ^ "." ^ name | None -> name in
|
||||||
let self =
|
let self =
|
||||||
match Hashtbl.find_opt st.aff name with Some b -> b | None -> false
|
match Hashtbl.find_opt st.aff name with Some b -> b | None -> false
|
||||||
in
|
in
|
||||||
@@ -68,7 +69,7 @@ let rec ty_affine st (t : Ast.ty) =
|
|||||||
|
|
||||||
(* capability가 뿌리다. struct/enum은 필드에서 전이된다. 상호 재귀 타입을 위해
|
(* capability가 뿌리다. struct/enum은 필드에서 전이된다. 상호 재귀 타입을 위해
|
||||||
변화가 없을 때까지 돈다. *)
|
변화가 없을 때까지 돈다. *)
|
||||||
let derive_affinity st (m : modul) =
|
let derive_affinity st (items : item list) =
|
||||||
List.iter
|
List.iter
|
||||||
(fun it ->
|
(fun it ->
|
||||||
match it with
|
match it with
|
||||||
@@ -76,7 +77,7 @@ let derive_affinity st (m : modul) =
|
|||||||
| I_struct { name; _ } -> Hashtbl.replace st.aff name false
|
| I_struct { name; _ } -> Hashtbl.replace st.aff name false
|
||||||
| I_enum { name; _ } -> Hashtbl.replace st.aff name false
|
| I_enum { name; _ } -> Hashtbl.replace st.aff name false
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
m.items;
|
items;
|
||||||
let changed = ref true in
|
let changed = ref true in
|
||||||
while !changed do
|
while !changed do
|
||||||
changed := false;
|
changed := false;
|
||||||
@@ -96,7 +97,7 @@ let derive_affinity st (m : modul) =
|
|||||||
(fun v -> List.exists (ty_affine st) v.v_args)
|
(fun v -> List.exists (ty_affine st) v.v_args)
|
||||||
variants)
|
variants)
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
m.items
|
items
|
||||||
done;
|
done;
|
||||||
(* copyable 선언과 affine 필드는 공존할 수 없다 *)
|
(* copyable 선언과 affine 필드는 공존할 수 없다 *)
|
||||||
List.iter
|
List.iter
|
||||||
@@ -112,7 +113,7 @@ let derive_affinity st (m : modul) =
|
|||||||
fields;
|
fields;
|
||||||
ignore pos
|
ignore pos
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
m.items
|
items
|
||||||
|
|
||||||
(* ------------------------------------------------------------------ *)
|
(* ------------------------------------------------------------------ *)
|
||||||
(* 스코프 *)
|
(* 스코프 *)
|
||||||
@@ -398,7 +399,8 @@ and callee_info st callee =
|
|||||||
with
|
with
|
||||||
| Some f -> Some f
|
| Some f -> Some f
|
||||||
| None -> None)
|
| None -> None)
|
||||||
| None -> None)
|
(* 모듈 별칭을 통한 호출: 가져온 함수는 "Alias.f" 키로 들어와 있다 *)
|
||||||
|
| None -> Hashtbl.find_opt st.fns (o ^ "." ^ name))
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
(* ------------------------------------------------------------------ *)
|
(* ------------------------------------------------------------------ *)
|
||||||
@@ -431,7 +433,7 @@ let check_fn st (d : fn_decl) =
|
|||||||
| _ -> ());
|
| _ -> ());
|
||||||
pop st
|
pop st
|
||||||
|
|
||||||
let check (m : modul) : error list =
|
let check ?(imports : item list = []) (m : modul) : error list =
|
||||||
let st =
|
let st =
|
||||||
{
|
{
|
||||||
aff = Hashtbl.create 16;
|
aff = Hashtbl.create 16;
|
||||||
@@ -445,7 +447,7 @@ let check (m : modul) : error list =
|
|||||||
errors = [];
|
errors = [];
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
derive_affinity st m;
|
derive_affinity st (imports @ m.items);
|
||||||
List.iter
|
List.iter
|
||||||
(fun it ->
|
(fun it ->
|
||||||
match it with
|
match it with
|
||||||
@@ -459,7 +461,7 @@ let check (m : modul) : error list =
|
|||||||
(d.fn_name, { f_params = d.fn_params; f_ret = d.fn_ret }))
|
(d.fn_name, { f_params = d.fn_params; f_ret = d.fn_ret }))
|
||||||
methods)
|
methods)
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
m.items;
|
(imports @ m.items);
|
||||||
List.iter
|
List.iter
|
||||||
(fun it -> match it with I_fn { decl; _ } -> check_fn st decl | _ -> ())
|
(fun it -> match it with I_fn { decl; _ } -> check_fn st decl | _ -> ())
|
||||||
m.items;
|
m.items;
|
||||||
|
|||||||
+19
-4
@@ -129,8 +129,14 @@ let rec parse_ty st =
|
|||||||
| Token.Ident n ->
|
| Token.Ident n ->
|
||||||
let p = pos st in
|
let p = pos st in
|
||||||
adv st;
|
adv st;
|
||||||
|
let modl, n =
|
||||||
|
if kind st = Token.Dot then (
|
||||||
|
adv st;
|
||||||
|
(Some n, ident st "타입 이름"))
|
||||||
|
else (None, n)
|
||||||
|
in
|
||||||
let args = if kind st = Token.LBracket then parse_targs st else [] in
|
let args = if kind st = Token.LBracket then parse_targs st else [] in
|
||||||
T_named { name = n; args; pos = p }
|
T_named { modl; name = n; args; pos = p }
|
||||||
| _ -> err_expect st "타입"
|
| _ -> err_expect st "타입"
|
||||||
|
|
||||||
and parse_fn_ty st affine p =
|
and parse_fn_ty st affine p =
|
||||||
@@ -190,8 +196,14 @@ let rec parse_pattern st =
|
|||||||
| Token.Kw_false ->
|
| Token.Kw_false ->
|
||||||
adv st;
|
adv st;
|
||||||
P_lit (L_bool false, p)
|
P_lit (L_bool false, p)
|
||||||
| Token.Ident n ->
|
| Token.Ident n -> (
|
||||||
adv st;
|
adv st;
|
||||||
|
let modl, n =
|
||||||
|
if kind st = Token.Dot then (
|
||||||
|
adv st;
|
||||||
|
(Some n, ident st "생성자 이름"))
|
||||||
|
else (None, n)
|
||||||
|
in
|
||||||
if kind st = Token.LParen then (
|
if kind st = Token.LParen then (
|
||||||
adv st;
|
adv st;
|
||||||
let rec loop acc =
|
let rec loop acc =
|
||||||
@@ -203,8 +215,11 @@ let rec parse_pattern st =
|
|||||||
in
|
in
|
||||||
let args = if kind st = Token.RParen then [] else loop [] in
|
let args = if kind st = Token.RParen then [] else loop [] in
|
||||||
expect_close st Token.RParen ")";
|
expect_close st Token.RParen ")";
|
||||||
P_ctor { name = n; args; pos = p })
|
P_ctor { modl; name = n; args; pos = p })
|
||||||
else P_bind (n, p)
|
else
|
||||||
|
match modl with
|
||||||
|
| Some _ -> P_ctor { modl; name = n; args = []; pos = p }
|
||||||
|
| None -> P_bind (n, p))
|
||||||
| _ -> err_expect st "패턴"
|
| _ -> err_expect st "패턴"
|
||||||
|
|
||||||
(* ------------------------------------------------------------------ *)
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|||||||
+11
-3
@@ -77,7 +77,12 @@ let resolve_eff_atom st = function
|
|||||||
names
|
names
|
||||||
|
|
||||||
let rec resolve_ty st = function
|
let rec resolve_ty st = function
|
||||||
| T_named { name; args; pos } ->
|
(* 한정된 이름은 별칭이 이 모듈에 있는지만 본다. 그 모듈 안에 그 타입이
|
||||||
|
있는지는 모듈 하나만 보고 결정할 수 없다 — 외부 참조로 기록한다. *)
|
||||||
|
| T_named { modl = Some a; args; pos; _ } ->
|
||||||
|
if Hashtbl.find_opt st.items a <> Some K_import then external_ref st a pos;
|
||||||
|
List.iter (resolve_targ st pos) args
|
||||||
|
| T_named { modl = None; name; args; pos } ->
|
||||||
if
|
if
|
||||||
(not (List.mem name st.ty_params))
|
(not (List.mem name st.ty_params))
|
||||||
&& (not (List.mem name builtin_types))
|
&& (not (List.mem name builtin_types))
|
||||||
@@ -118,7 +123,10 @@ let rec resolve_pattern st seen = function
|
|||||||
else (
|
else (
|
||||||
seen := n :: !seen;
|
seen := n :: !seen;
|
||||||
bind st pos n false))
|
bind st pos n false))
|
||||||
| P_ctor { name; args; pos } ->
|
| P_ctor { modl = Some a; args; pos; _ } ->
|
||||||
|
if Hashtbl.find_opt st.items a <> Some K_import then external_ref st a pos;
|
||||||
|
List.iter (resolve_pattern st seen) args
|
||||||
|
| P_ctor { modl = None; name; args; pos } ->
|
||||||
(match Hashtbl.find_opt st.ctors name with
|
(match Hashtbl.find_opt st.ctors name with
|
||||||
| Some (enum, arity) when arity <> List.length args ->
|
| Some (enum, arity) when arity <> List.length args ->
|
||||||
error st pos
|
error st pos
|
||||||
@@ -144,7 +152,7 @@ let rec resolve_expr st = function
|
|||||||
else external_ref st n pos
|
else external_ref st n pos
|
||||||
| E_list (xs, _) -> List.iter (resolve_expr st) xs
|
| E_list (xs, _) -> List.iter (resolve_expr st) xs
|
||||||
| E_struct { name; fields; pos } ->
|
| E_struct { name; fields; pos } ->
|
||||||
resolve_ty st (T_named { name; args = []; pos });
|
resolve_ty st (T_named { modl = None; name; args = []; pos });
|
||||||
List.iter (fun (_, e) -> resolve_expr st e) fields
|
List.iter (fun (_, e) -> resolve_expr st e) fields
|
||||||
| E_closure c ->
|
| E_closure c ->
|
||||||
push st;
|
push st;
|
||||||
|
|||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
(* 모듈 로딩, interface 캐시, 그리고 고정점 invalidation.
|
||||||
|
|
||||||
|
여기가 v0가 존재하는 이유다. 검사 자체보다 "무엇을 다시 검사해야 하는가"를
|
||||||
|
좁게 유지하는 것이 아키텍처의 주장이고, 그 주장은 측정으로만 증명된다.
|
||||||
|
|
||||||
|
전파는 고정점 규칙이다 (문서 P8):
|
||||||
|
1. 변경된 모듈 자체를 재검사
|
||||||
|
2. 재검사 전후의 interface hash를 비교
|
||||||
|
3. 달라졌을 때만 그 모듈의 dependents를 큐에 추가
|
||||||
|
4. 큐가 빌 때까지 반복
|
||||||
|
순서가 중요하다. dependents를 먼저 재검사하면 "본문만 수정 시 downstream
|
||||||
|
0건"이 성립하지 않는다 — hash 비교가 dependents 재검사보다 앞서야 한다. *)
|
||||||
|
|
||||||
|
type error = { file : string; line : int; col : int; message : string }
|
||||||
|
|
||||||
|
let string_of_error { file; line; col; message } =
|
||||||
|
Printf.sprintf "%s:%d:%d: %s" file line col message
|
||||||
|
|
||||||
|
type entry = {
|
||||||
|
path : string;
|
||||||
|
ast : Ast.modul;
|
||||||
|
imports : (string * string) list; (* 별칭 -> 해소된 경로 *)
|
||||||
|
iface : Iface.t;
|
||||||
|
errors : error list;
|
||||||
|
}
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
root : string;
|
||||||
|
modules : (string, entry) Hashtbl.t;
|
||||||
|
(* 통계: 무엇이 몇 번 재검사됐는지. 측정이 목적이므로 처음부터 센다. *)
|
||||||
|
mutable checked : string list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let create ?(root = ".") () =
|
||||||
|
{ root; modules = Hashtbl.create 16; checked = [] }
|
||||||
|
|
||||||
|
(* 패키지 경로와 지역 모듈 경로를 구분한다. 첫 세그먼트에 점이 있으면
|
||||||
|
패키지 참조다 (cool.dev/std/list). v0에는 패키지 해소가 없으므로 그런
|
||||||
|
import는 불투명하게 남는다 — 없다고 말하지 않는다. *)
|
||||||
|
let is_package path =
|
||||||
|
match String.index_opt path '/' with
|
||||||
|
| Some i -> String.contains (String.sub path 0 i) '.'
|
||||||
|
| None -> String.contains path '.'
|
||||||
|
|
||||||
|
let resolve_import st path = Filename.concat st.root (path ^ ".cool")
|
||||||
|
|
||||||
|
let read_file file =
|
||||||
|
let ic = open_in_bin file in
|
||||||
|
let n = in_channel_length ic in
|
||||||
|
let s = really_input_string ic n in
|
||||||
|
close_in ic;
|
||||||
|
s
|
||||||
|
|
||||||
|
let err_of file (pos : Token.pos) msg =
|
||||||
|
{ file; line = pos.line; col = pos.col; message = msg }
|
||||||
|
|
||||||
|
let parse_file file =
|
||||||
|
match Lexer.lex_result (read_file file) with
|
||||||
|
| Error e -> Error (err_of file e.pos e.msg)
|
||||||
|
| Ok toks -> (
|
||||||
|
match Parser.parse_result toks with
|
||||||
|
| Error e -> Error (err_of file e.pos e.msg)
|
||||||
|
| Ok m -> Ok m)
|
||||||
|
|
||||||
|
let imports_of (m : Ast.modul) =
|
||||||
|
List.filter_map
|
||||||
|
(function
|
||||||
|
| Ast.I_import { path; alias; _ } -> Some (alias, path) | _ -> None)
|
||||||
|
m.items
|
||||||
|
|
||||||
|
(* 한 모듈을 검사한다. 의존 모듈의 interface는 이미 로드되어 있어야 한다. *)
|
||||||
|
let check_module st path : entry =
|
||||||
|
st.checked <- path :: st.checked;
|
||||||
|
match parse_file path with
|
||||||
|
| Error e ->
|
||||||
|
{
|
||||||
|
path;
|
||||||
|
ast = { items = [] };
|
||||||
|
imports = [];
|
||||||
|
iface = { items = []; hash = "" };
|
||||||
|
errors = [ e ];
|
||||||
|
}
|
||||||
|
| Ok ast ->
|
||||||
|
let imports =
|
||||||
|
List.filter_map
|
||||||
|
(fun (a, p) ->
|
||||||
|
if is_package p then None else Some (a, resolve_import st p))
|
||||||
|
(imports_of ast)
|
||||||
|
in
|
||||||
|
(* 의존 모듈의 exported surface를 소비 측 별칭으로 한정해 합친다.
|
||||||
|
여기서부터 검사기는 "이 모듈 + 아는 외부 표면"만 본다. *)
|
||||||
|
let dep_surface =
|
||||||
|
List.concat_map
|
||||||
|
(fun (alias, p) ->
|
||||||
|
match Hashtbl.find_opt st.modules p with
|
||||||
|
| Some e -> Iface.qualify alias e.iface.Iface.items
|
||||||
|
| None -> [])
|
||||||
|
imports
|
||||||
|
in
|
||||||
|
let iface = Iface.of_module ast in
|
||||||
|
let _, rerrors = Resolve.resolve ast in
|
||||||
|
let errors =
|
||||||
|
List.map (fun (e : Resolve.error) -> err_of path e.pos e.msg) rerrors
|
||||||
|
in
|
||||||
|
let errors =
|
||||||
|
if errors <> [] then errors
|
||||||
|
else
|
||||||
|
List.map
|
||||||
|
(fun (e : Typecheck.error) -> err_of path e.pos e.msg)
|
||||||
|
(Typecheck.check ~imports:dep_surface ast)
|
||||||
|
@ List.map
|
||||||
|
(fun (e : Move.error) -> err_of path e.pos e.msg)
|
||||||
|
(Move.check ~imports:dep_surface ast)
|
||||||
|
in
|
||||||
|
let errors =
|
||||||
|
List.sort (fun a b -> compare (a.line, a.col) (b.line, b.col)) errors
|
||||||
|
in
|
||||||
|
{ path; ast; imports; iface; errors }
|
||||||
|
|
||||||
|
(* 의존 순서대로 로드한다. 순환은 오류다. *)
|
||||||
|
let rec load st ?(visiting = []) path : unit =
|
||||||
|
if Hashtbl.mem st.modules path then ()
|
||||||
|
else if List.mem path visiting then ()
|
||||||
|
else if not (Sys.file_exists path) then
|
||||||
|
Hashtbl.replace st.modules path
|
||||||
|
{
|
||||||
|
path;
|
||||||
|
ast = { items = [] };
|
||||||
|
imports = [];
|
||||||
|
iface = { items = []; hash = "" };
|
||||||
|
errors =
|
||||||
|
[ { file = path; line = 0; col = 0; message = "모듈을 찾을 수 없습니다" } ];
|
||||||
|
}
|
||||||
|
else begin
|
||||||
|
(match parse_file path with
|
||||||
|
| Error _ -> ()
|
||||||
|
| Ok ast ->
|
||||||
|
List.iter
|
||||||
|
(fun (_, p) ->
|
||||||
|
if not (is_package p) then
|
||||||
|
load st ~visiting:(path :: visiting) (resolve_import st p))
|
||||||
|
(imports_of ast));
|
||||||
|
Hashtbl.replace st.modules path (check_module st path)
|
||||||
|
end
|
||||||
|
|
||||||
|
let dependents st path =
|
||||||
|
Hashtbl.fold
|
||||||
|
(fun p e acc ->
|
||||||
|
if List.exists (fun (_, d) -> d = path) e.imports then p :: acc else acc)
|
||||||
|
st.modules []
|
||||||
|
|
||||||
|
(* 고정점 전파. 반환값은 실제로 재검사한 모듈 목록이다. *)
|
||||||
|
let recheck st (changed : string list) : string list =
|
||||||
|
st.checked <- [];
|
||||||
|
let queue = ref changed in
|
||||||
|
let seen = Hashtbl.create 8 in
|
||||||
|
while !queue <> [] do
|
||||||
|
let path = List.hd !queue in
|
||||||
|
queue := List.tl !queue;
|
||||||
|
if not (Hashtbl.mem seen path) then begin
|
||||||
|
Hashtbl.replace seen path ();
|
||||||
|
let before =
|
||||||
|
match Hashtbl.find_opt st.modules path with
|
||||||
|
| Some e -> e.iface.Iface.hash
|
||||||
|
| None -> ""
|
||||||
|
in
|
||||||
|
let entry = check_module st path in
|
||||||
|
Hashtbl.replace st.modules path entry;
|
||||||
|
(* hash 비교가 dependents 재검사보다 앞선다 *)
|
||||||
|
if entry.iface.Iface.hash <> before then
|
||||||
|
queue := !queue @ dependents st path
|
||||||
|
end
|
||||||
|
done;
|
||||||
|
List.rev st.checked
|
||||||
|
|
||||||
|
let errors st =
|
||||||
|
Hashtbl.fold (fun _ e acc -> e.errors @ acc) st.modules []
|
||||||
|
|> List.sort (fun a b ->
|
||||||
|
compare (a.file, a.line, a.col) (b.file, b.line, b.col))
|
||||||
|
|
||||||
|
let find st path = Hashtbl.find_opt st.modules path
|
||||||
+83
-31
@@ -26,6 +26,9 @@ type env = {
|
|||||||
fns : (string, scheme) Hashtbl.t;
|
fns : (string, scheme) Hashtbl.t;
|
||||||
consts : (string, T.t) Hashtbl.t;
|
consts : (string, T.t) Hashtbl.t;
|
||||||
ctors : (string, string) Hashtbl.t; (* variant -> enum *)
|
ctors : (string, string) Hashtbl.t; (* variant -> enum *)
|
||||||
|
(* 가져온 모듈의 별칭. `Alias.x`는 "Alias.x"라는 하나의 키로 찾는다 —
|
||||||
|
별칭은 식별자에 쓸 수 없는 점(.)을 포함하므로 지역 이름과 충돌하지 않는다. *)
|
||||||
|
aliases : string list;
|
||||||
mutable locals : (string * T.t) list list;
|
mutable locals : (string * T.t) list list;
|
||||||
mutable ret : T.t; (* 현재 함수의 선언된 반환 타입 *)
|
mutable ret : T.t; (* 현재 함수의 선언된 반환 타입 *)
|
||||||
(* 현재 본문이 수행한 effect. 위치를 같이 들고 다녀야 "어디서 수행했는지"를
|
(* 현재 본문이 수행한 effect. 위치를 같이 들고 다녀야 "어디서 수행했는지"를
|
||||||
@@ -71,15 +74,16 @@ let conv_eff_result (atoms : Ast.eff_result) : T.eff =
|
|||||||
|
|
||||||
let rec conv env (gen : string list) (t : Ast.ty) : T.t =
|
let rec conv env (gen : string list) (t : Ast.ty) : T.t =
|
||||||
match t with
|
match t with
|
||||||
| T_named { name; args; _ } -> (
|
| T_named { modl; name; args; _ } -> (
|
||||||
let args =
|
let args =
|
||||||
List.filter_map
|
List.filter_map
|
||||||
(function TA_ty t -> Some (conv env gen t) | TA_eff _ -> None)
|
(function TA_ty t -> Some (conv env gen t) | TA_eff _ -> None)
|
||||||
args
|
args
|
||||||
in
|
in
|
||||||
if List.mem name gen then T.TVar name
|
let name = match modl with Some a -> a ^ "." ^ name | None -> name in
|
||||||
|
if modl = None && List.mem name gen then T.TVar name
|
||||||
else
|
else
|
||||||
match name with
|
match if modl = None then name else "" with
|
||||||
| "Int" -> T.TInt
|
| "Int" -> T.TInt
|
||||||
| "Bool" -> T.TBool
|
| "Bool" -> T.TBool
|
||||||
| "String" -> T.TString
|
| "String" -> T.TString
|
||||||
@@ -491,33 +495,59 @@ and infer_field env obj name pos =
|
|||||||
(Printf.sprintf
|
(Printf.sprintf
|
||||||
"capability %s의 메서드는 값을 통해서만 부를 수 있습니다 (%s를 파라미터로 받아야 합니다)" n n)
|
"capability %s의 메서드는 값을 통해서만 부를 수 있습니다 (%s를 파라미터로 받아야 합니다)" n n)
|
||||||
| _ -> ());
|
| _ -> ());
|
||||||
let t = infer env obj in
|
match obj with
|
||||||
match T.resolve t with
|
| E_ident (a, _) when lookup env a = None && List.mem a env.aliases -> (
|
||||||
| T.TUnknown -> T.TUnknown
|
(* 모듈 별칭을 통한 접근. 가져온 표면은 "Alias.name" 키로 들어와 있다. *)
|
||||||
| T.TCon (cname, args) -> (
|
let key = a ^ "." ^ name in
|
||||||
match Hashtbl.find_opt env.structs cname with
|
let unknown () =
|
||||||
| Some (gen, fields) -> (
|
err env pos (Printf.sprintf "%s에 %s이(가) 없습니다" a name);
|
||||||
let sub = List.map2 (fun v a -> (v, a)) gen (adjust gen args) in
|
T.TUnknown
|
||||||
match List.assoc_opt name fields with
|
in
|
||||||
| Some ft -> T.subst sub [] ft
|
match Hashtbl.find_opt env.consts key with
|
||||||
| None ->
|
| Some t -> t
|
||||||
err env pos (Printf.sprintf "%s에 %s 필드가 없습니다" cname name);
|
|
||||||
T.TUnknown)
|
|
||||||
| None -> (
|
| None -> (
|
||||||
match Hashtbl.find_opt env.caps cname with
|
match Hashtbl.find_opt env.fns key with
|
||||||
| Some methods -> (
|
| Some s ->
|
||||||
match List.assoc_opt name methods with
|
let params, eff, ret = instantiate s in
|
||||||
| Some s ->
|
T.TFn { affine = false; params; eff; ret }
|
||||||
let params, eff, ret = instantiate s in
|
| None -> (
|
||||||
T.TFn { affine = false; params; eff; ret }
|
match Hashtbl.find_opt env.ctors key with
|
||||||
|
| Some enum -> (
|
||||||
|
match Hashtbl.find_opt env.enums enum with
|
||||||
|
| Some (_, variants)
|
||||||
|
when List.assoc_opt key variants = Some [] ->
|
||||||
|
nullary_ctor env enum key
|
||||||
|
| Some _ -> ctor_fn env enum key
|
||||||
|
| None -> T.TUnknown)
|
||||||
|
| None -> unknown ())))
|
||||||
|
| _ -> (
|
||||||
|
let t = infer env obj in
|
||||||
|
match T.resolve t with
|
||||||
|
| T.TUnknown -> T.TUnknown
|
||||||
|
| T.TCon (cname, args) -> (
|
||||||
|
match Hashtbl.find_opt env.structs cname with
|
||||||
|
| Some (gen, fields) -> (
|
||||||
|
let sub = List.map2 (fun v a -> (v, a)) gen (adjust gen args) in
|
||||||
|
match List.assoc_opt name fields with
|
||||||
|
| Some ft -> T.subst sub [] ft
|
||||||
| None ->
|
| None ->
|
||||||
err env pos
|
err env pos (Printf.sprintf "%s에 %s 필드가 없습니다" cname name);
|
||||||
(Printf.sprintf "capability %s에 %s 메서드가 없습니다" cname name);
|
|
||||||
T.TUnknown)
|
T.TUnknown)
|
||||||
| None -> T.TUnknown))
|
| None -> (
|
||||||
| other ->
|
match Hashtbl.find_opt env.caps cname with
|
||||||
err env pos (Printf.sprintf "%s에는 필드가 없습니다" (T.show other));
|
| Some methods -> (
|
||||||
T.TUnknown
|
match List.assoc_opt name methods with
|
||||||
|
| Some s ->
|
||||||
|
let params, eff, ret = instantiate s in
|
||||||
|
T.TFn { affine = false; params; eff; ret }
|
||||||
|
| None ->
|
||||||
|
err env pos
|
||||||
|
(Printf.sprintf "capability %s에 %s 메서드가 없습니다" cname name);
|
||||||
|
T.TUnknown)
|
||||||
|
| None -> T.TUnknown))
|
||||||
|
| other ->
|
||||||
|
err env pos (Printf.sprintf "%s에는 필드가 없습니다" (T.show other));
|
||||||
|
T.TUnknown)
|
||||||
|
|
||||||
and adjust gen args =
|
and adjust gen args =
|
||||||
let n = List.length gen in
|
let n = List.length gen in
|
||||||
@@ -567,7 +597,8 @@ and check_pattern env (scrutinee : T.t) (p : pattern) =
|
|||||||
| Some enum ->
|
| Some enum ->
|
||||||
check_ctor env scrutinee enum n [] Token.{ line = 0; col = 0 }
|
check_ctor env scrutinee enum n [] Token.{ line = 0; col = 0 }
|
||||||
| None -> bind env n scrutinee)
|
| None -> bind env n scrutinee)
|
||||||
| P_ctor { name; args; pos } -> (
|
| P_ctor { modl; name; args; pos } -> (
|
||||||
|
let name = match modl with Some a -> a ^ "." ^ name | None -> name in
|
||||||
match Hashtbl.find_opt env.ctors name with
|
match Hashtbl.find_opt env.ctors name with
|
||||||
| Some enum -> check_ctor env scrutinee enum name args pos
|
| Some enum -> check_ctor env scrutinee enum name args pos
|
||||||
| None -> List.iter (check_pattern env T.TUnknown) args)
|
| None -> List.iter (check_pattern env T.TUnknown) args)
|
||||||
@@ -671,7 +702,7 @@ let check_fn env (d : fn_decl) =
|
|||||||
env.performed <- [];
|
env.performed <- [];
|
||||||
pop env
|
pop env
|
||||||
|
|
||||||
let check (m : modul) : error list =
|
let check ?(imports : item list = []) (m : modul) : error list =
|
||||||
let env =
|
let env =
|
||||||
{
|
{
|
||||||
structs = Hashtbl.create 16;
|
structs = Hashtbl.create 16;
|
||||||
@@ -680,6 +711,27 @@ let check (m : modul) : error list =
|
|||||||
fns = Hashtbl.create 16;
|
fns = Hashtbl.create 16;
|
||||||
consts = Hashtbl.create 16;
|
consts = Hashtbl.create 16;
|
||||||
ctors = Hashtbl.create 16;
|
ctors = Hashtbl.create 16;
|
||||||
|
(* 표면을 실제로 받아온 별칭만 안다. 해소되지 않은 모듈(예: 아직 가져오지
|
||||||
|
못한 패키지)의 별칭은 모르는 것이므로 그 아래 이름을 틀렸다고 말하지
|
||||||
|
않는다. *)
|
||||||
|
aliases =
|
||||||
|
List.sort_uniq compare
|
||||||
|
(List.filter_map
|
||||||
|
(fun it ->
|
||||||
|
let n =
|
||||||
|
match it with
|
||||||
|
| I_fn { decl; _ } -> decl.fn_name
|
||||||
|
| I_struct { name; _ }
|
||||||
|
| I_enum { name; _ }
|
||||||
|
| I_capability { name; _ }
|
||||||
|
| I_const { name; _ } ->
|
||||||
|
name
|
||||||
|
| _ -> ""
|
||||||
|
in
|
||||||
|
match String.index_opt n '.' with
|
||||||
|
| Some i -> Some (String.sub n 0 i)
|
||||||
|
| None -> None)
|
||||||
|
imports);
|
||||||
locals = [];
|
locals = [];
|
||||||
ret = T.TUnit;
|
ret = T.TUnit;
|
||||||
performed = [];
|
performed = [];
|
||||||
@@ -698,7 +750,7 @@ let check (m : modul) : error list =
|
|||||||
List.iter (fun v -> Hashtbl.replace env.ctors v.v_name name) variants
|
List.iter (fun v -> Hashtbl.replace env.ctors v.v_name name) variants
|
||||||
| I_capability { name; _ } -> Hashtbl.replace env.caps name []
|
| I_capability { name; _ } -> Hashtbl.replace env.caps name []
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
m.items;
|
(imports @ m.items);
|
||||||
(* 2차: 본문을 채운다 *)
|
(* 2차: 본문을 채운다 *)
|
||||||
List.iter
|
List.iter
|
||||||
(fun it ->
|
(fun it ->
|
||||||
@@ -722,7 +774,7 @@ let check (m : modul) : error list =
|
|||||||
| I_const { name; ty; _ } ->
|
| I_const { name; ty; _ } ->
|
||||||
Hashtbl.replace env.consts name (conv env [] ty)
|
Hashtbl.replace env.consts name (conv env [] ty)
|
||||||
| _ -> ())
|
| _ -> ())
|
||||||
m.items;
|
(imports @ m.items);
|
||||||
(* 3차: 본문 검사 *)
|
(* 3차: 본문 검사 *)
|
||||||
List.iter
|
List.iter
|
||||||
(fun it ->
|
(fun it ->
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 모듈 B. A의 표면에만 의존한다.
|
||||||
|
//
|
||||||
|
// Shape의 variant가 늘면 이 match가 깨진다 — 그래서 enum 정의 본문이
|
||||||
|
// interface hash 입력이고, A의 시그니처 변경은 여기까지 전파되어야 한다.
|
||||||
|
|
||||||
|
import "shapes" as Shapes
|
||||||
|
|
||||||
|
pub fn area(s: Shapes.Shape) -> Int {
|
||||||
|
match s {
|
||||||
|
Shapes.Circle(r) => Shapes.double(r),
|
||||||
|
Shapes.Square(w) => w * w,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 모듈 A. downstream이 보는 것은 이 파일의 exported surface뿐이다.
|
||||||
|
|
||||||
|
pub enum Shape {
|
||||||
|
Circle(Int),
|
||||||
|
Square(Int),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn double(n: Int) -> Int {
|
||||||
|
// 본문. 이 안을 아무리 고쳐도 interface hash는 변하지 않는다.
|
||||||
|
n + n
|
||||||
|
}
|
||||||
@@ -913,3 +913,76 @@ let () =
|
|||||||
in
|
in
|
||||||
check "variant 둘일 때는 통과" (type_ok (two ^ user));
|
check "variant 둘일 때는 통과" (type_ok (two ^ user));
|
||||||
check "variant가 늘면 같은 코드가 깨진다" (type_has (three ^ user) "빠진 경우: C")
|
check "variant가 늘면 같은 코드가 깨진다" (type_has (three ^ user) "빠진 경우: C")
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* 모듈 경계와 incremental 전파 *)
|
||||||
|
(* *)
|
||||||
|
(* 이 세 검사가 아키텍처 주장 전체다: *)
|
||||||
|
(* 1. 다른 모듈의 타입과 생성자가 별칭으로 보인다 *)
|
||||||
|
(* 2. 본문만 고치면 downstream은 재검사되지 않는다 *)
|
||||||
|
(* 3. 시그니처를 고치면 downstream까지 전파되고, 실제로 깨진다 *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
let has_sub hay needle =
|
||||||
|
let n = String.length needle and h = String.length hay in
|
||||||
|
let rec go i = i + n <= h && (String.sub hay i n = needle || go (i + 1)) in
|
||||||
|
n = 0 || go 0
|
||||||
|
|
||||||
|
let write file s =
|
||||||
|
let oc = open_out_bin file in
|
||||||
|
output_string oc s;
|
||||||
|
close_out oc
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_modtest" in
|
||||||
|
ignore (Sys.command (Printf.sprintf "mkdir -p %s" (Filename.quote dir)));
|
||||||
|
let a = Filename.concat dir "shapes.cool" in
|
||||||
|
let b = Filename.concat dir "area.cool" in
|
||||||
|
let shapes_body body =
|
||||||
|
"pub enum Shape {\n\
|
||||||
|
\ Circle(Int),\n\
|
||||||
|
\ Square(Int),\n\
|
||||||
|
}\n\n\
|
||||||
|
pub fn double(n: Int) -> Int {\n\
|
||||||
|
\ " ^ body ^ "\n}\n"
|
||||||
|
in
|
||||||
|
let shapes_three =
|
||||||
|
"pub enum Shape {\n\
|
||||||
|
\ Circle(Int),\n\
|
||||||
|
\ Square(Int),\n\
|
||||||
|
\ Tri(Int),\n\
|
||||||
|
}\n\n\
|
||||||
|
pub fn double(n: Int) -> Int {\n\
|
||||||
|
\ n + n\n\
|
||||||
|
}\n"
|
||||||
|
in
|
||||||
|
let area =
|
||||||
|
"import \"shapes\" as Shapes\n\n\
|
||||||
|
pub fn area(s: Shapes.Shape) -> Int {\n\
|
||||||
|
\ match s {\n\
|
||||||
|
\ Shapes.Circle(r) => Shapes.double(r),\n\
|
||||||
|
\ Shapes.Square(w) => w * w,\n\
|
||||||
|
\ }\n\
|
||||||
|
}\n"
|
||||||
|
in
|
||||||
|
write a (shapes_body "n + n");
|
||||||
|
write b area;
|
||||||
|
let st = Session.create ~root:dir () in
|
||||||
|
Session.load st b;
|
||||||
|
check "모듈 경계를 넘는 타입과 생성자가 보인다" (Session.errors st = []);
|
||||||
|
|
||||||
|
(* 본문만 수정 — hash가 그대로이므로 downstream은 손대지 않는다 *)
|
||||||
|
write a (shapes_body "n * 2");
|
||||||
|
let touched = Session.recheck st [ a ] in
|
||||||
|
check "본문만 고치면 자기 자신만 재검사된다" (touched = [ a ]);
|
||||||
|
check "본문 수정 후에도 오류 없음" (Session.errors st = []);
|
||||||
|
|
||||||
|
(* 시그니처 수정 — hash가 변하므로 dependents까지 전파된다 *)
|
||||||
|
write a shapes_three;
|
||||||
|
let touched = Session.recheck st [ a ] in
|
||||||
|
check "variant를 추가하면 downstream까지 전파된다" (List.mem b touched);
|
||||||
|
check "전파된 downstream이 실제로 깨진다"
|
||||||
|
(List.exists
|
||||||
|
(fun (e : Session.error) ->
|
||||||
|
e.file = b && String.length e.message > 0 && has_sub e.message "빠진 경우")
|
||||||
|
(Session.errors st))
|
||||||
|
|||||||
Reference in New Issue
Block a user