resolve: 이름 해소와 모듈 의존 표면 산출

이 단계가 답하는 질문은 하나다 — 이 모듈 하나만 보고 무엇을 결정할 수 있는가.
그 경계를 산출물로 만들었다.

결정할 수 있는 것은 오류로 보고한다: 중복 정의, 중복 파라미터·제네릭,
선언되지 않은 effect 변수, 같은 블록의 재바인딩, 불변 바인딩에 대한 대입,
모듈에 없는 reexport 대상, 지역 바인딩이 아닌 scope 이름, variant 인자 개수.

결정할 수 없는 것은 외부 참조로 기록하고 오류로 만들지 않는다. 모듈 로딩이
아직 없으므로 해소할 방법이 없고, 이 목록이 곧 모듈의 의존 표면이자
interface hash가 소비할 입력이다. cool deps로 볼 수 있다.

이름 해소가 아니면 못 하는 판정 하나를 구현했다: match의 맨 이름이 바인딩인지
인자 없는 생성자인지는 구문으로 갈리지 않는다. enum 선언에서 만든 생성자
표로 판정하고, 같은 표로 인자 개수도 검사한다.

샘플에서 실제 오류 둘을 잡았다:
- 03의 scope inner가 어디서도 오지 않는다. scope X { }의 X가 "이미 가진
  TaskScope를 쓴다"인지 "새 자식 스코프를 만들어 X로 묶는다"인지가 정해지지
  않은 탓이다. 후자라면 자식의 부모가 구문에 없어 ambient authority가 된다.
  결정 전까지 해당 예제를 주석으로 두고 이유를 적었다.
- 03이 List를 import 없이 쓰고 있었다. 외부 참조 목록에 드러나 채웠다.

TaskScope가 use 값이라는 사실에서 검사 하나가 따라 나온다: scope의 머리는
모듈 수준 이름일 수 없고 반드시 지역 바인딩이어야 한다.

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 02:30:52 +09:00
co-authored by Claude Opus 5
parent 1bd88fc4d7
commit 94e28d427b
5 changed files with 522 additions and 20 deletions
+106
View File
@@ -362,3 +362,109 @@ let () =
errors;
check (f ^ " 구문 분석") false))
files
(* ================================================================== *)
(* 이름 해소 *)
(* ================================================================== *)
let resolve_errs src =
let m = parse_ok src in
let _, errors = Resolve.resolve m in
List.map (fun (e : Resolve.error) -> e.msg) errors
let resolve_ext src =
let m = parse_ok src in
let info, _ = Resolve.resolve m in
List.map fst info.Resolve.externals
let has_err 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)
(resolve_errs src)
(* --- 모듈 하나로 결정할 수 있는 것 = 오류 --- *)
let () =
check "중복 정의" (has_err "fn f()\nfn f()" "두 번 정의");
check "중복 파라미터" (has_err "fn f(a: Int, a: Int)" "파라미터 a");
check "중복 제네릭" (has_err "fn f[a, a]()" "제네릭 파라미터 a");
check "선언되지 않은 effect 변수" (has_err "fn f() effects e" "선언되지 않은 effect 변수 e");
check "선언된 effect 변수는 통과" (resolve_errs "fn f[e: effects]() effects e" = []);
check "파라미터 타입 안의 effect 변수도 본다"
(has_err "fn f(g: fn() effects e)" "선언되지 않은 effect 변수 e");
check "같은 블록의 재바인딩"
(has_err "fn f() {\n let a = 1\n let a = 2\n a\n}" "다시 묶을 수 없습니다");
check "중첩 블록의 가림은 허용"
(resolve_errs
"fn f() {\n let a = 1\n if a {\n let a = 2\n a\n }\n}"
= []);
check "불변 바인딩에 대입" (has_err "fn f() {\n let a = 1\n a = 2\n}" "불변 바인딩");
check "mut이면 통과" (resolve_errs "fn f() {\n let mut a = 1\n a = 2\n}" = []);
check "모듈에 없는 reexport" (has_err "reexport N" "이 모듈에 없습니다");
check "모듈에 있는 reexport는 통과" (resolve_errs "enum N {\n A,\n}\nreexport N" = [])
(* --- scope 머리는 지역 바인딩이어야 한다 --- *)
let () =
check "지역 바인딩이 아닌 scope 이름"
(has_err "fn f() {\n scope sc {\n sc\n }\n}" "지역 바인딩이 아닙니다");
check "파라미터로 온 scope는 통과"
(resolve_errs "fn f(sc: TaskScope) {\n scope sc {\n sc\n }\n}" = [])
(* --- variant는 구문이 아니라 이름 해소가 판정한다 --- *)
let () =
let enum = "enum E {\n A(Code),\n B,\n}\n" in
check "인자 없는 variant는 바인딩이 아니라 생성자"
(resolve_errs
(enum ^ "fn f(e: E) {\n match e {\n B => 1,\n _ => 2,\n }\n}")
= []);
check "variant 인자 개수"
(has_err
(enum ^ "fn f(e: E) {\n match e {\n A => 1,\n _ => 2,\n }\n}")
"인자 1개가 필요합니다");
check "패턴의 중복 바인딩"
(has_err
(enum ^ "fn f(e: E) {\n match e {\n A(x) => x,\n _ => 0,\n }\n}"
^ "\nfn g(e: E) {\n match e {\n A(x) => x,\n _ => 0,\n }\n}")
"두 번 나옵니다"
= false)
(* --- 결정할 수 없는 것 = 외부 참조 --- *)
let () =
check "모르는 타입은 외부 참조" (resolve_ext "fn f(a: Widget)" = [ "Widget" ]);
check "import한 이름은 외부 참조가 아니다"
(resolve_ext "import \"d/p\" as P\nfn f() {\n P.go()\n}" = []);
check "지역 이름은 외부 참조가 아니다" (resolve_ext "fn f(a: Int) {\n a\n}" = []);
check "builtin은 외부 참조가 아니다"
(resolve_ext "fn f(a: Int) -> Result[Int, Int] {\n Ok(a)\n}" = []);
check "effect 집합의 capability도 표면에 든다"
(resolve_ext "fn f() effects {Gw.pay}" = [ "Gw" ])
(* --- 샘플: 01~07은 이름 해소를 통과해야 한다 --- *)
let () =
let dir = "../samples" in
let files =
Sys.readdir dir |> Array.to_list
|> List.filter (fun f -> Filename.check_suffix f ".cool")
|> List.filter (fun f -> f <> "08_syntax_errors.cool")
|> List.sort compare
in
List.iter
(fun f ->
let path = Filename.concat dir f in
match Driver.resolve path with
| Ok _ -> ()
| Error errors ->
List.iter
(fun e -> Printf.printf " %s\n" (Driver.string_of_error e))
errors;
check (f ^ " 이름 해소") false)
files