typecheck: 타입 검사기와 scope 부모 문법 확정
세 가지를 자율 결정으로 닫고 타입 검사까지 세웠다.
1. scope 부모 문법: scope 자식 = 부모 { ... }로 확정. 부모를 적지 않으면
자식의 부모가 "가장 가까운 스코프"가 되는데 그것이 정확히 ambient
authority다. 권한 사슬이 main의 루트 TaskScope부터 끊기지 않으려면 모든
자식이 부모를 이름으로 지목해야 한다. 이름 해소가 이 구멍을 잡아준 건이라,
주석 처리했던 중첩 예제를 되살렸다.
2. prelude: 타입 이름은 그 타입에 딸린 함수의 이름공간이다(String.len,
File.close). 정적 메서드를 위한 별도 문법을 두지 않는다.
3. 타입 검사: 이 모듈 안에서 아는 것만 검사한다. 외부 이름은 TUnknown이
되어 무엇과도 맞는다 — 모르는 것을 틀렸다고 말하지 않기 위해서다.
제네릭 해소는 호출 지점의 지역 unification이고 함수 하나를 넘지 않는다.
클로저 파라미터 타입은 기대 타입에서 읽어온다(양방향 검사, 로컬).
단계 소유권을 하나 정정했다. affinity는 타입 동등성의 일부가 아니다. 값이
affine인지는 무엇을 capture했는지로 정해지는 substructural 성질이고
move/affinity 검사가 소유한다. 타입 검사가 이걸 판정하려다 정당한 코드를
거부하는 것을 06에서 확인하고 unify에서 분리했다.
unify 버그 하나: 같은 미지수끼리 unify할 때 occurs check가 자기 자신을
발견해 실패하고 있었다. 02의 fold 호출에서 잡혔다.
09_type_errors.cool 추가 — 외부 타입이 하나도 없어 검사기가 TUnknown으로
빠져나갈 구석이 없는 파일이다. 18개 진단이 전부 잡히고, 첫 오류에서 멈추지
않고 모두 보고한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+4
-1
@@ -197,7 +197,10 @@ if_expr = "if" , expr_ns , block , [ "else" , ( block | if_expr ) ] ;
|
|||||||
match_expr = "match" , expr_ns , "{" , { arm } , "}" ;
|
match_expr = "match" , expr_ns , "{" , { arm } , "}" ;
|
||||||
arm = pattern , "=>" , ( expr | block ) , { NEWLINE } ,
|
arm = pattern , "=>" , ( expr | block ) , { NEWLINE } ,
|
||||||
[ "," , { NEWLINE } ] ;
|
[ "," , { NEWLINE } ] ;
|
||||||
scope_expr = "scope" , ident , block ;
|
scope_expr = "scope" , ident , "=" , ident , block ;
|
||||||
|
(* scope 자식 = 부모 { ... }
|
||||||
|
* 부모를 구문에 적는다. 적지 않으면 자식의 부모가 "가장 가까운 스코프"가 되어
|
||||||
|
* 정확히 ambient authority가 된다 — 이 언어가 배제하는 것 *)
|
||||||
|
|
||||||
(* expr_ns = struct_lit로 시작하지 않는 expr.
|
(* expr_ns = struct_lit로 시작하지 않는 expr.
|
||||||
* if/match/scope의 머리 자리에서 "{"가 블록의 시작인지 struct 리터럴인지
|
* if/match/scope의 머리 자리에서 "{"가 블록의 시작인지 struct 리터럴인지
|
||||||
|
|||||||
+13
-6
@@ -119,13 +119,15 @@ Affinity 전이 (보안 주장의 필수 전제):
|
|||||||
※ v0는 affine 미소비(누수)를 허용하므로 handle 기반 보장은 애초에 불가능하다.
|
※ v0는 affine 미소비(누수)를 허용하므로 handle 기반 보장은 애초에 불가능하다.
|
||||||
실행 의미로 옮겨야 그 허점이 사라진다
|
실행 의미로 옮겨야 그 허점이 사라진다
|
||||||
- 루트 TaskScope는 main에 주입 → ambient authority 없이 권한 사슬이 main부터 닫힌다
|
- 루트 TaskScope는 main에 주입 → ambient authority 없이 권한 사슬이 main부터 닫힌다
|
||||||
- scope는 이름을 갖는다: scope sc { sc.spawn(...) }. 암묵 바인딩은 채택하지 않는다
|
- scope는 자식을 만들고 부모를 구문에 적는다: scope inner = outer { ... }
|
||||||
※ 중첩 시 결정적이다. scope outer { scope inner { ... } }에서 안쪽 블록이
|
※ 부모를 적지 않으면 자식의 부모가 "가장 가까운 스코프"가 되는데, 그것이
|
||||||
바깥 스코프에 spawn하는 것(수명이 다른 태스크를 의도적으로 부모에 붙이는
|
정확히 ambient authority다. 권한 사슬이 main의 루트 TaskScope부터
|
||||||
정당한 패턴)이 암묵 바인딩으로는 표현 불가이거나 섀도잉 규칙이라는
|
끊기지 않고 이어지려면 모든 자식이 부모를 이름으로 지목해야 한다
|
||||||
새 복잡성을 부른다. 이름이 있으면 outer.spawn / inner.spawn으로 그냥 갈린다
|
※ 중첩이 결정적이다. 안쪽 블록에서 바깥 스코프에 붙이는 것(수명이 다른
|
||||||
|
태스크를 의도적으로 부모에 붙이는 정당한 패턴)이 outer.spawn /
|
||||||
|
inner.spawn으로 그냥 갈린다
|
||||||
※ "어느 스코프에 붙는 태스크인가"가 리뷰어 눈에 보이는 것 자체가 이 언어가
|
※ "어느 스코프에 붙는 태스크인가"가 리뷰어 눈에 보이는 것 자체가 이 언어가
|
||||||
파는 물건이다. 축약형은 체감 불만이 실제로 쌓이면 v1에서 검토
|
파는 물건이다. 타이핑을 줄이려고 여기서 깎으면 팔 물건이 없어진다
|
||||||
|
|
||||||
Effect 다형성 (철학 2,5에서 파생):
|
Effect 다형성 (철학 2,5에서 파생):
|
||||||
- effect 변수는 타입 파라미터와 정확히 같은 규율을 따른다:
|
- effect 변수는 타입 파라미터와 정확히 같은 규율을 따른다:
|
||||||
@@ -243,6 +245,8 @@ Generics (철학 2에서 파생):
|
|||||||
※ 가드가 붙는 순간 그 분기가 패턴 전체를 덮는다고 말할 수 없어 exhaustiveness가
|
※ 가드가 붙는 순간 그 분기가 패턴 전체를 덮는다고 말할 수 없어 exhaustiveness가
|
||||||
흐려지고, SMT 없이는 보수적으로 _ 분기를 강요하게 된다. 철학 1의 대표 항목을
|
흐려지고, SMT 없이는 보수적으로 _ 분기를 강요하게 된다. 철학 1의 대표 항목을
|
||||||
문법 편의와 바꾸지 않는다. 필요하면 분기 본문에서 if를 쓴다
|
문법 편의와 바꾸지 않는다. 필요하면 분기 본문에서 if를 쓴다
|
||||||
|
- 타입 이름은 그 타입에 딸린 함수의 이름공간이다: String.len(s), File.close(f).
|
||||||
|
별도의 정적 메서드 문법을 두지 않는다
|
||||||
- ?는 Result 전용으로 하드코딩한다 (trait solver가 없으므로 일반화 경로가 없다)
|
- ?는 Result 전용으로 하드코딩한다 (trait solver가 없으므로 일반화 경로가 없다)
|
||||||
※ 조기 탈출도 블록 종료이므로 scope의 join/cancel은 ? 경로에서도 실행된다
|
※ 조기 탈출도 블록 종료이므로 scope의 join/cancel은 ? 경로에서도 실행된다
|
||||||
※ v1에서 linear를 넣으면 ?의 조기 탈출 경로마다 해제가 필요해진다.
|
※ v1에서 linear를 넣으면 ?의 조기 탈출 경로마다 해제가 필요해진다.
|
||||||
@@ -261,6 +265,9 @@ Generics (철학 2에서 파생):
|
|||||||
|
|
||||||
포함 (되돌리기 비싼 것 전부):
|
포함 (되돌리기 비싼 것 전부):
|
||||||
- parse → name resolution → type check → effect/capability check
|
- parse → name resolution → type check → effect/capability check
|
||||||
|
※ 각 단계는 그 단계가 소유한 성질만 판정한다. 예: affinity는 타입 동등성이
|
||||||
|
아니라 substructural 성질이므로 타입 검사가 아니라 move 검사가 소유한다.
|
||||||
|
단계가 서로의 결론을 앞지르면 진단이 엉뚱한 곳에서 난다
|
||||||
- move/affinity 검사, capability use 규칙, affinity 전이
|
- move/affinity 검사, capability use 규칙, affinity 전이
|
||||||
- effect 변수 (effect 다형성)
|
- effect 변수 (effect 다형성)
|
||||||
- interface artifact + hash 기반 incremental invalidation
|
- interface artifact + hash 기반 incremental invalidation
|
||||||
|
|||||||
+3
-3
@@ -55,7 +55,7 @@ type expr =
|
|||||||
| E_closure of closure
|
| E_closure of closure
|
||||||
| E_if of { cond : expr; then_ : block; else_ : expr option; pos : pos }
|
| E_if of { cond : expr; then_ : block; else_ : expr option; pos : pos }
|
||||||
| E_match of { scrutinee : expr; arms : arm list; pos : pos }
|
| E_match of { scrutinee : expr; arms : arm list; pos : pos }
|
||||||
| E_scope of { name : string; body : block; pos : pos }
|
| E_scope of { name : string; parent : string; body : block; pos : pos }
|
||||||
| E_block of block
|
| E_block of block
|
||||||
| E_call of { callee : expr; args : expr list; pos : pos }
|
| E_call of { callee : expr; args : expr list; pos : pos }
|
||||||
| E_field of { obj : expr; name : string; pos : pos }
|
| E_field of { obj : expr; name : string; pos : pos }
|
||||||
@@ -285,8 +285,8 @@ let rec buf_expr b = function
|
|||||||
Buffer.add_char b ')')
|
Buffer.add_char b ')')
|
||||||
arms;
|
arms;
|
||||||
Buffer.add_char b ')'
|
Buffer.add_char b ')'
|
||||||
| E_scope { name; body; _ } ->
|
| E_scope { name; parent; body; _ } ->
|
||||||
Buffer.add_string b ("(scope " ^ name ^ " ");
|
Buffer.add_string b ("(scope " ^ name ^ " = " ^ parent ^ " ");
|
||||||
buf_block b body;
|
buf_block b body;
|
||||||
Buffer.add_char b ')'
|
Buffer.add_char b ')'
|
||||||
| E_block bl -> buf_block b bl
|
| E_block bl -> buf_block b bl
|
||||||
|
|||||||
+32
-7
@@ -56,6 +56,33 @@ let resolve (file : string) : (Resolve.info, error list) result =
|
|||||||
{ file; line = e.pos.line; col = e.pos.col; message = e.msg })
|
{ file; line = e.pos.line; col = e.pos.col; message = e.msg })
|
||||||
errors))
|
errors))
|
||||||
|
|
||||||
|
let typecheck (file : string) : (unit, error list) result =
|
||||||
|
match parse_file file with
|
||||||
|
| Error e -> Error [ e ]
|
||||||
|
| Ok m -> (
|
||||||
|
let _, rerrors = Resolve.resolve m in
|
||||||
|
match rerrors with
|
||||||
|
| _ :: _ ->
|
||||||
|
Error
|
||||||
|
(List.map
|
||||||
|
(fun (e : Resolve.error) ->
|
||||||
|
{ file; line = e.pos.line; col = e.pos.col; message = e.msg })
|
||||||
|
rerrors)
|
||||||
|
| [] -> (
|
||||||
|
match Typecheck.check m with
|
||||||
|
| [] -> Ok ()
|
||||||
|
| terrors ->
|
||||||
|
Error
|
||||||
|
(List.map
|
||||||
|
(fun (e : Typecheck.error) ->
|
||||||
|
{
|
||||||
|
file;
|
||||||
|
line = e.pos.line;
|
||||||
|
col = e.pos.col;
|
||||||
|
message = e.msg;
|
||||||
|
})
|
||||||
|
terrors)))
|
||||||
|
|
||||||
let check (files : string list) : (unit, error list) result =
|
let check (files : string list) : (unit, error list) result =
|
||||||
match files with
|
match files with
|
||||||
| [] ->
|
| [] ->
|
||||||
@@ -63,16 +90,13 @@ let check (files : string list) : (unit, error list) result =
|
|||||||
| _ ->
|
| _ ->
|
||||||
let errors =
|
let errors =
|
||||||
List.filter_map
|
List.filter_map
|
||||||
(fun f ->
|
(fun f -> match typecheck f with Ok _ -> None | Error es -> Some es)
|
||||||
match resolve f with
|
|
||||||
| Ok _ -> None
|
|
||||||
| Error (e :: _) -> Some e
|
|
||||||
| Error [] -> None)
|
|
||||||
files
|
files
|
||||||
|
|> List.concat
|
||||||
in
|
in
|
||||||
if errors <> [] then Error errors
|
if errors <> [] then Error errors
|
||||||
else
|
else
|
||||||
(* 이름 해소는 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
|
(* 타입 검사는 통과했다. 통과했다고 말하지 않는다 — 파이프라인의
|
||||||
나머지가 아직 없으므로 검사되지 않은 것이다. *)
|
나머지가 아직 없으므로 검사되지 않은 것이다. *)
|
||||||
Error
|
Error
|
||||||
(List.map
|
(List.map
|
||||||
@@ -81,7 +105,8 @@ let check (files : string list) : (unit, error list) result =
|
|||||||
file = f;
|
file = f;
|
||||||
line = 0;
|
line = 0;
|
||||||
col = 0;
|
col = 0;
|
||||||
message = "구문 분석까지 통과. 이름 해소가 아직 구현되지 않았습니다";
|
message =
|
||||||
|
"타입 검사까지 통과. effect/capability 검사와 move 검사가 아직 구현되지 않았습니다";
|
||||||
})
|
})
|
||||||
files)
|
files)
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -385,9 +385,11 @@ and parse_primary st =
|
|||||||
| Token.Kw_match -> parse_match st
|
| Token.Kw_match -> parse_match st
|
||||||
| Token.Kw_scope ->
|
| Token.Kw_scope ->
|
||||||
adv st;
|
adv st;
|
||||||
let name = ident st "scope 이름" in
|
let name = ident st "새 scope 이름" in
|
||||||
|
expect st Token.Eq "= (자식 scope의 부모를 명시해야 합니다)";
|
||||||
|
let parent = ident st "부모 scope 이름" in
|
||||||
let body = parse_block st in
|
let body = parse_block st in
|
||||||
E_scope { name; body; pos = p }
|
E_scope { name; parent; body; pos = p }
|
||||||
| Token.Ident n ->
|
| Token.Ident n ->
|
||||||
adv st;
|
adv st;
|
||||||
if (not st.no_struct) && kind st = Token.LBrace then
|
if (not st.no_struct) && kind st = Token.LBrace then
|
||||||
|
|||||||
+13
-7
@@ -139,6 +139,8 @@ let rec resolve_expr st = function
|
|||||||
if Hashtbl.mem st.items n then ()
|
if Hashtbl.mem st.items n then ()
|
||||||
else if List.mem n builtin_values then ()
|
else if List.mem n builtin_values then ()
|
||||||
else if Hashtbl.mem st.ctors n then ()
|
else if Hashtbl.mem st.ctors n then ()
|
||||||
|
else if List.mem n builtin_types then ()
|
||||||
|
(* 타입 이름은 그 타입에 딸린 함수의 이름공간이다: String.len *)
|
||||||
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 } ->
|
||||||
@@ -171,16 +173,20 @@ let rec resolve_expr st = function
|
|||||||
resolve_expr st a.arm_body;
|
resolve_expr st a.arm_body;
|
||||||
pop st)
|
pop st)
|
||||||
arms
|
arms
|
||||||
| E_scope { name; body; pos } ->
|
| E_scope { name; parent; body; pos } ->
|
||||||
(* TaskScope는 use 값이라 모듈 수준에 있을 수 없다. scope의 머리는
|
(* 부모는 이미 가진 TaskScope여야 한다. TaskScope는 use 값이라 모듈
|
||||||
반드시 지역 바인딩(파라미터 포함)이어야 한다. *)
|
수준에 있을 수 없으므로 반드시 지역 바인딩(파라미터 포함)이다.
|
||||||
if lookup_local st name = None then
|
자식 scope 이름은 블록 안에서만 산다. *)
|
||||||
|
if lookup_local st parent = None then
|
||||||
error st pos
|
error st pos
|
||||||
(Printf.sprintf
|
(Printf.sprintf
|
||||||
"scope 이름 %s이(가) 지역 바인딩이 아닙니다 (TaskScope는 use 값이라 파라미터나 상위 블록에서 \
|
"부모 scope %s이(가) 지역 바인딩이 아닙니다 (TaskScope는 use 값이라 파라미터나 상위 블록에서 \
|
||||||
와야 합니다)"
|
와야 합니다)"
|
||||||
name);
|
parent);
|
||||||
resolve_block_scoped st body
|
push st;
|
||||||
|
bind st pos name false;
|
||||||
|
resolve_block st body;
|
||||||
|
pop st
|
||||||
| E_block b -> resolve_block_scoped st b
|
| E_block b -> resolve_block_scoped st b
|
||||||
| E_call { callee; args; _ } ->
|
| E_call { callee; args; _ } ->
|
||||||
resolve_expr st callee;
|
resolve_expr st callee;
|
||||||
|
|||||||
@@ -0,0 +1,604 @@
|
|||||||
|
(* 타입 검사.
|
||||||
|
|
||||||
|
범위: 이 모듈 안에서 아는 것만 검사한다. 외부 이름은 TUnknown이 되어
|
||||||
|
무엇과도 맞는다 — 모르는 것을 틀렸다고 말하지 않는다.
|
||||||
|
|
||||||
|
제네릭 해소는 호출 지점의 지역 unification이다. 함수 하나를 넘어가지
|
||||||
|
않으므로 전역 추론이 아니다. *)
|
||||||
|
|
||||||
|
open Ast
|
||||||
|
module T = Types
|
||||||
|
|
||||||
|
type error = { pos : Token.pos; msg : string }
|
||||||
|
|
||||||
|
type scheme = {
|
||||||
|
s_gen : string list; (* 타입 파라미터 이름 (effect 파라미터는 제외) *)
|
||||||
|
s_params : T.t list;
|
||||||
|
s_ret : T.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
type env = {
|
||||||
|
structs : (string, string list * (string * T.t) list) Hashtbl.t;
|
||||||
|
enums : (string, string list * (string * T.t list) list) Hashtbl.t;
|
||||||
|
caps : (string, (string * scheme) list) Hashtbl.t;
|
||||||
|
fns : (string, scheme) Hashtbl.t;
|
||||||
|
consts : (string, T.t) Hashtbl.t;
|
||||||
|
ctors : (string, string) Hashtbl.t; (* variant -> enum *)
|
||||||
|
mutable locals : (string * T.t) list list;
|
||||||
|
mutable ret : T.t; (* 현재 함수의 선언된 반환 타입 *)
|
||||||
|
mutable errors : error list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let err env pos msg = env.errors <- { pos; msg } :: env.errors
|
||||||
|
|
||||||
|
let mismatch env pos expected got what =
|
||||||
|
err env pos
|
||||||
|
(Printf.sprintf "%s: %s이(가) 필요한데 %s입니다" what (T.show expected) (T.show got))
|
||||||
|
|
||||||
|
let push env = env.locals <- [] :: env.locals
|
||||||
|
let pop env = match env.locals with _ :: r -> env.locals <- r | [] -> ()
|
||||||
|
|
||||||
|
let bind env n t =
|
||||||
|
match env.locals with
|
||||||
|
| s :: r -> env.locals <- ((n, t) :: s) :: r
|
||||||
|
| [] -> env.locals <- [ [ (n, t) ] ]
|
||||||
|
|
||||||
|
let lookup env n =
|
||||||
|
let rec go = function
|
||||||
|
| [] -> None
|
||||||
|
| s :: r -> (
|
||||||
|
match List.assoc_opt n s with Some t -> Some t | None -> go r)
|
||||||
|
in
|
||||||
|
go env.locals
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* Ast.ty -> Types.t *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
let rec conv env (gen : string list) (t : Ast.ty) : T.t =
|
||||||
|
match t with
|
||||||
|
| T_named { name; args; _ } -> (
|
||||||
|
let args =
|
||||||
|
List.filter_map
|
||||||
|
(function TA_ty t -> Some (conv env gen t) | TA_eff _ -> None)
|
||||||
|
args
|
||||||
|
in
|
||||||
|
if List.mem name gen then T.TVar name
|
||||||
|
else
|
||||||
|
match name with
|
||||||
|
| "Int" -> T.TInt
|
||||||
|
| "Bool" -> T.TBool
|
||||||
|
| "String" -> T.TString
|
||||||
|
| "Unit" -> T.TUnit
|
||||||
|
| "List" | "Option" | "Result" -> T.TCon (name, args)
|
||||||
|
| _ ->
|
||||||
|
if
|
||||||
|
Hashtbl.mem env.structs name
|
||||||
|
|| Hashtbl.mem env.enums name || Hashtbl.mem env.caps name
|
||||||
|
then T.TCon (name, args)
|
||||||
|
else T.TUnknown)
|
||||||
|
| T_fn { affine; params; ret; _ } ->
|
||||||
|
T.TFn
|
||||||
|
{
|
||||||
|
affine;
|
||||||
|
params = List.map (conv env gen) params;
|
||||||
|
ret = (match ret with None -> T.TUnit | Some r -> conv env gen r);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scheme_of env (d : fn_decl) : scheme =
|
||||||
|
let gen =
|
||||||
|
List.filter_map
|
||||||
|
(fun g -> if g.gp_effect then None else Some g.gp_name)
|
||||||
|
d.fn_gen
|
||||||
|
in
|
||||||
|
{
|
||||||
|
s_gen = gen;
|
||||||
|
s_params = List.map (fun p -> conv env gen p.p_ty) d.fn_params;
|
||||||
|
s_ret = (match d.fn_ret with None -> T.TUnit | Some r -> conv env gen r);
|
||||||
|
}
|
||||||
|
|
||||||
|
(* 호출 지점 인스턴스화: 타입 파라미터마다 새 미지수 *)
|
||||||
|
let instantiate (s : scheme) =
|
||||||
|
let sub = List.map (fun v -> (v, T.fresh ())) s.s_gen in
|
||||||
|
(List.map (T.subst sub) s.s_params, T.subst sub 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
|
||||||
|
(List.map (T.subst sub) s.s_params, T.subst sub s.s_ret)
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* 내장 생성자 *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
let builtin_ctor = function
|
||||||
|
| "Ok" ->
|
||||||
|
let a = T.fresh () and b = T.fresh () in
|
||||||
|
Some ([ a ], T.TCon ("Result", [ a; b ]))
|
||||||
|
| "Err" ->
|
||||||
|
let a = T.fresh () and b = T.fresh () in
|
||||||
|
Some ([ b ], T.TCon ("Result", [ a; b ]))
|
||||||
|
| "Some" ->
|
||||||
|
let a = T.fresh () in
|
||||||
|
Some ([ a ], T.TCon ("Option", [ a ]))
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* 식 *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
let rec infer env (e : expr) : T.t =
|
||||||
|
match e with
|
||||||
|
| E_lit (L_int _, _) -> T.TInt
|
||||||
|
| E_lit (L_str _, _) -> T.TString
|
||||||
|
| E_lit (L_bool _, _) -> T.TBool
|
||||||
|
| E_ident (n, _) -> (
|
||||||
|
match lookup env n with
|
||||||
|
| Some t -> t
|
||||||
|
| None -> (
|
||||||
|
match Hashtbl.find_opt env.consts n with
|
||||||
|
| Some t -> t
|
||||||
|
| None -> (
|
||||||
|
match n with
|
||||||
|
| "unit" -> T.TUnit
|
||||||
|
| "None" -> T.TCon ("Option", [ T.fresh () ])
|
||||||
|
| _ -> (
|
||||||
|
match Hashtbl.find_opt env.fns n with
|
||||||
|
| Some s ->
|
||||||
|
let params, ret = instantiate s in
|
||||||
|
T.TFn { affine = false; params; ret }
|
||||||
|
| None -> (
|
||||||
|
match Hashtbl.find_opt env.ctors n with
|
||||||
|
| Some enum -> nullary_ctor env enum n
|
||||||
|
| None -> T.TUnknown)))))
|
||||||
|
| E_list (xs, pos) ->
|
||||||
|
let elem = T.fresh () in
|
||||||
|
List.iter
|
||||||
|
(fun x ->
|
||||||
|
let t = infer env x in
|
||||||
|
if not (T.unify elem t) then
|
||||||
|
mismatch env pos elem t "리스트 원소의 타입이 서로 다릅니다")
|
||||||
|
xs;
|
||||||
|
T.TCon ("List", [ elem ])
|
||||||
|
| E_struct { name; fields; pos } -> infer_struct env name fields pos
|
||||||
|
| E_closure c -> infer_closure env c None
|
||||||
|
| E_if { cond; then_; else_; pos } -> (
|
||||||
|
let c = infer env cond in
|
||||||
|
if not (T.unify c T.TBool) then mismatch env pos T.TBool c "if의 조건";
|
||||||
|
let t1 = infer_block env then_ in
|
||||||
|
match else_ with
|
||||||
|
| None ->
|
||||||
|
if not (T.unify t1 T.TUnit) then
|
||||||
|
err env pos "else가 없는 if의 본문은 값을 남길 수 없습니다";
|
||||||
|
T.TUnit
|
||||||
|
| Some e2 ->
|
||||||
|
let t2 = infer env e2 in
|
||||||
|
if not (T.unify t1 t2) then mismatch env pos t1 t2 "if의 두 분기 타입이 다릅니다";
|
||||||
|
t1)
|
||||||
|
| E_match { scrutinee; arms; pos } ->
|
||||||
|
let s = infer env scrutinee in
|
||||||
|
let result = T.fresh () in
|
||||||
|
List.iter
|
||||||
|
(fun a ->
|
||||||
|
push env;
|
||||||
|
check_pattern env s a.arm_pat;
|
||||||
|
let t = infer env a.arm_body in
|
||||||
|
if not (T.unify result t) then
|
||||||
|
mismatch env a.arm_pos result t "match 팔의 타입이 서로 다릅니다";
|
||||||
|
pop env)
|
||||||
|
arms;
|
||||||
|
if arms = [] then err env pos "match에 팔이 없습니다";
|
||||||
|
result
|
||||||
|
| E_scope { name; parent; body; pos } ->
|
||||||
|
(match lookup env parent with
|
||||||
|
| Some t when not (T.unify t (T.TCon ("TaskScope", []))) ->
|
||||||
|
if T.resolve t <> T.TUnknown then
|
||||||
|
mismatch env pos (T.TCon ("TaskScope", [])) t "scope의 부모"
|
||||||
|
| _ -> ());
|
||||||
|
push env;
|
||||||
|
bind env name (T.TCon ("TaskScope", []));
|
||||||
|
let t = infer_block env body in
|
||||||
|
pop env;
|
||||||
|
t
|
||||||
|
| E_block b ->
|
||||||
|
push env;
|
||||||
|
let t = infer_block env b in
|
||||||
|
pop env;
|
||||||
|
t
|
||||||
|
| E_call { callee; args; pos } -> infer_call env callee args pos
|
||||||
|
| E_field { obj; name; pos } -> infer_field env obj name pos
|
||||||
|
| E_inst { callee; args; pos } -> infer_inst env callee args pos
|
||||||
|
| E_try { inner; pos } -> (
|
||||||
|
let t = infer env inner in
|
||||||
|
match T.resolve t with
|
||||||
|
| T.TUnknown -> T.TUnknown
|
||||||
|
| T.TCon ("Result", [ ok; _ ]) ->
|
||||||
|
(match T.resolve env.ret with
|
||||||
|
| T.TCon ("Result", _) | T.TUnknown -> ()
|
||||||
|
| r ->
|
||||||
|
err env pos
|
||||||
|
(Printf.sprintf "?는 Result를 반환하는 함수 안에서만 쓸 수 있습니다 (현재 반환 타입 %s)"
|
||||||
|
(T.show r)));
|
||||||
|
ok
|
||||||
|
| other ->
|
||||||
|
err env pos
|
||||||
|
(Printf.sprintf "?는 Result에만 쓸 수 있습니다 (%s에 쓰였습니다)" (T.show other));
|
||||||
|
T.TUnknown)
|
||||||
|
| E_unary { op; operand; pos } ->
|
||||||
|
let t = infer env operand in
|
||||||
|
let want = match op with U_not -> T.TBool | U_neg -> T.TInt in
|
||||||
|
if not (T.unify t want) then mismatch env pos want t "단항 연산자의 피연산자";
|
||||||
|
want
|
||||||
|
| E_binary { op; lhs; rhs; pos } -> (
|
||||||
|
let a = infer env lhs and b = infer env rhs in
|
||||||
|
match op with
|
||||||
|
| B_or | B_and ->
|
||||||
|
if not (T.unify a T.TBool) then mismatch env pos T.TBool a "논리 연산자";
|
||||||
|
if not (T.unify b T.TBool) then mismatch env pos T.TBool b "논리 연산자";
|
||||||
|
T.TBool
|
||||||
|
| B_add | B_sub | B_mul | B_div | B_rem ->
|
||||||
|
if not (T.unify a T.TInt) then mismatch env pos T.TInt a "산술 연산자";
|
||||||
|
if not (T.unify b T.TInt) then mismatch env pos T.TInt b "산술 연산자";
|
||||||
|
T.TInt
|
||||||
|
| B_lt | B_le | B_gt | B_ge ->
|
||||||
|
if not (T.unify a T.TInt) then mismatch env pos T.TInt a "비교 연산자";
|
||||||
|
if not (T.unify b T.TInt) then mismatch env pos T.TInt b "비교 연산자";
|
||||||
|
T.TBool
|
||||||
|
| B_eq | B_ne ->
|
||||||
|
if not (T.unify a b) then mismatch env pos a b "같은 타입끼리만 비교할 수 있습니다";
|
||||||
|
T.TBool)
|
||||||
|
|
||||||
|
and nullary_ctor env enum name =
|
||||||
|
match Hashtbl.find_opt env.enums enum with
|
||||||
|
| None -> T.TUnknown
|
||||||
|
| Some (gen, variants) -> (
|
||||||
|
let sub = List.map (fun v -> (v, T.fresh ())) gen in
|
||||||
|
match List.assoc_opt name variants with
|
||||||
|
| Some [] -> T.TCon (enum, List.map snd sub)
|
||||||
|
| _ -> T.TCon (enum, List.map snd sub))
|
||||||
|
|
||||||
|
and infer_struct env name fields pos =
|
||||||
|
match Hashtbl.find_opt env.structs name with
|
||||||
|
| None ->
|
||||||
|
List.iter (fun (_, e) -> ignore (infer env e)) fields;
|
||||||
|
T.TUnknown
|
||||||
|
| Some (gen, decl_fields) ->
|
||||||
|
let sub = List.map (fun v -> (v, T.fresh ())) gen in
|
||||||
|
List.iter
|
||||||
|
(fun (fname, fe) ->
|
||||||
|
match List.assoc_opt fname decl_fields with
|
||||||
|
| None -> err env pos (Printf.sprintf "%s에 %s 필드가 없습니다" name fname)
|
||||||
|
| Some ft ->
|
||||||
|
let want = T.subst sub ft in
|
||||||
|
let got = infer env fe in
|
||||||
|
if not (T.unify want got) then
|
||||||
|
mismatch env pos want got (Printf.sprintf "%s.%s 필드" name fname))
|
||||||
|
fields;
|
||||||
|
List.iter
|
||||||
|
(fun (fname, _) ->
|
||||||
|
if not (List.mem_assoc fname fields) then
|
||||||
|
err env pos (Printf.sprintf "%s의 %s 필드가 빠졌습니다" name fname))
|
||||||
|
decl_fields;
|
||||||
|
T.TCon (name, List.map snd sub)
|
||||||
|
|
||||||
|
and infer_closure env (c : closure) (expected : T.t option) =
|
||||||
|
let expected_params, expected_ret =
|
||||||
|
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 _ -> None) c.cl_params, None)
|
||||||
|
in
|
||||||
|
push env;
|
||||||
|
let param_tys =
|
||||||
|
List.map2
|
||||||
|
(fun (n, ann) exp ->
|
||||||
|
let t =
|
||||||
|
match ann with
|
||||||
|
| Some a -> conv env [] a
|
||||||
|
| None -> ( match exp with Some t -> t | None -> T.TUnknown)
|
||||||
|
in
|
||||||
|
bind env n t;
|
||||||
|
t)
|
||||||
|
c.cl_params expected_params
|
||||||
|
in
|
||||||
|
let declared_ret = Option.map (conv env []) c.cl_ret in
|
||||||
|
let saved = env.ret in
|
||||||
|
env.ret <-
|
||||||
|
(match declared_ret with
|
||||||
|
| Some t -> t
|
||||||
|
| None -> ( match expected_ret with Some t -> t | None -> T.TUnknown));
|
||||||
|
let body = infer_block env c.cl_body in
|
||||||
|
(match declared_ret with
|
||||||
|
| Some t when not (T.unify t body) -> mismatch env c.cl_pos t body "클로저의 반환"
|
||||||
|
| _ -> ());
|
||||||
|
let ret = match declared_ret with Some t -> t | None -> body in
|
||||||
|
env.ret <- saved;
|
||||||
|
pop env;
|
||||||
|
T.TFn { affine = false; params = param_tys; ret }
|
||||||
|
|
||||||
|
and infer_call env callee args pos =
|
||||||
|
let fn_ty =
|
||||||
|
match callee with
|
||||||
|
| E_ident (n, _) when lookup env n = None -> (
|
||||||
|
match Hashtbl.find_opt env.ctors n with
|
||||||
|
| Some enum -> Some (ctor_fn env enum n)
|
||||||
|
| None -> (
|
||||||
|
match builtin_ctor n with
|
||||||
|
| Some (params, ret) -> Some (T.TFn { affine = false; params; ret })
|
||||||
|
| None -> (
|
||||||
|
match Hashtbl.find_opt env.fns n with
|
||||||
|
| Some s ->
|
||||||
|
let params, ret = instantiate s in
|
||||||
|
Some (T.TFn { affine = false; params; ret })
|
||||||
|
| None -> None)))
|
||||||
|
| _ -> (
|
||||||
|
match T.resolve (infer env callee) with
|
||||||
|
| T.TFn _ as t -> Some t
|
||||||
|
| _ -> None)
|
||||||
|
in
|
||||||
|
match fn_ty with
|
||||||
|
| None ->
|
||||||
|
List.iter (fun a -> ignore (infer env a)) args;
|
||||||
|
T.TUnknown
|
||||||
|
| Some (T.TFn { params; ret; _ }) ->
|
||||||
|
if List.length params <> List.length args then (
|
||||||
|
err env pos
|
||||||
|
(Printf.sprintf "인자 %d개가 필요한데 %d개가 주어졌습니다" (List.length params)
|
||||||
|
(List.length args));
|
||||||
|
List.iter (fun a -> ignore (infer env a)) args)
|
||||||
|
else
|
||||||
|
List.iter2
|
||||||
|
(fun p a ->
|
||||||
|
let got =
|
||||||
|
match a with
|
||||||
|
| E_closure c -> infer_closure env c (Some p)
|
||||||
|
| _ -> infer env a
|
||||||
|
in
|
||||||
|
if not (T.unify p got) then mismatch env pos p got "인자")
|
||||||
|
params args;
|
||||||
|
ret
|
||||||
|
| Some _ -> T.TUnknown
|
||||||
|
|
||||||
|
and ctor_fn env enum name =
|
||||||
|
match Hashtbl.find_opt env.enums enum with
|
||||||
|
| None -> T.TUnknown
|
||||||
|
| Some (gen, variants) ->
|
||||||
|
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
|
||||||
|
| None -> []
|
||||||
|
in
|
||||||
|
T.TFn { affine = false; params; ret = T.TCon (enum, List.map snd sub) }
|
||||||
|
|
||||||
|
and infer_field env obj name pos =
|
||||||
|
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 ->
|
||||||
|
err env pos (Printf.sprintf "%s에 %s 필드가 없습니다" cname name);
|
||||||
|
T.TUnknown)
|
||||||
|
| None -> (
|
||||||
|
match Hashtbl.find_opt env.caps cname with
|
||||||
|
| Some methods -> (
|
||||||
|
match List.assoc_opt name methods with
|
||||||
|
| Some s ->
|
||||||
|
let params, ret = instantiate s in
|
||||||
|
T.TFn { affine = false; params; 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 =
|
||||||
|
let n = List.length gen in
|
||||||
|
let rec take k xs =
|
||||||
|
if k = 0 then []
|
||||||
|
else
|
||||||
|
match xs with
|
||||||
|
| [] -> T.fresh () :: take (k - 1) []
|
||||||
|
| x :: r -> x :: take (k - 1) r
|
||||||
|
in
|
||||||
|
take n args
|
||||||
|
|
||||||
|
and infer_inst env callee args pos =
|
||||||
|
let tys =
|
||||||
|
List.filter_map
|
||||||
|
(function TA_ty t -> Some (conv env [] t) | TA_eff _ -> None)
|
||||||
|
args
|
||||||
|
in
|
||||||
|
match callee with
|
||||||
|
| E_ident (n, _) when lookup env n = None -> (
|
||||||
|
match Hashtbl.find_opt env.fns n with
|
||||||
|
| None -> T.TUnknown
|
||||||
|
| Some s ->
|
||||||
|
if List.length tys <> List.length s.s_gen then (
|
||||||
|
err env pos
|
||||||
|
(Printf.sprintf "타입 인자 %d개가 필요한데 %d개가 주어졌습니다"
|
||||||
|
(List.length s.s_gen) (List.length tys));
|
||||||
|
T.TUnknown)
|
||||||
|
else
|
||||||
|
let params, ret = instantiate_with s tys in
|
||||||
|
T.TFn { affine = false; params; ret })
|
||||||
|
| _ -> T.TUnknown
|
||||||
|
|
||||||
|
and check_pattern env (scrutinee : T.t) (p : pattern) =
|
||||||
|
match p with
|
||||||
|
| P_wild _ -> ()
|
||||||
|
| P_lit (l, pos) ->
|
||||||
|
let t =
|
||||||
|
match l with
|
||||||
|
| L_int _ -> T.TInt
|
||||||
|
| L_str _ -> T.TString
|
||||||
|
| L_bool _ -> T.TBool
|
||||||
|
in
|
||||||
|
if not (T.unify scrutinee t) then mismatch env pos scrutinee t "패턴의 리터럴"
|
||||||
|
| P_bind (n, _) -> (
|
||||||
|
match Hashtbl.find_opt env.ctors n with
|
||||||
|
| Some enum ->
|
||||||
|
check_ctor env scrutinee enum n [] Token.{ line = 0; col = 0 }
|
||||||
|
| None -> bind env n scrutinee)
|
||||||
|
| P_ctor { name; args; pos } -> (
|
||||||
|
match Hashtbl.find_opt env.ctors name with
|
||||||
|
| Some enum -> check_ctor env scrutinee enum name args pos
|
||||||
|
| None -> List.iter (check_pattern env T.TUnknown) args)
|
||||||
|
|
||||||
|
and check_ctor env scrutinee enum name args pos =
|
||||||
|
match Hashtbl.find_opt env.enums enum with
|
||||||
|
| None -> ()
|
||||||
|
| Some (gen, variants) ->
|
||||||
|
let sub = List.map (fun v -> (v, T.fresh ())) gen in
|
||||||
|
let ety = T.TCon (enum, List.map snd sub) in
|
||||||
|
if not (T.unify scrutinee ety) then
|
||||||
|
mismatch env pos scrutinee ety "패턴이 match 대상과 다른 타입입니다";
|
||||||
|
let fields =
|
||||||
|
match List.assoc_opt name variants with Some ts -> ts | None -> []
|
||||||
|
in
|
||||||
|
if List.length fields = List.length args then
|
||||||
|
List.iter2
|
||||||
|
(fun ft ap -> check_pattern env (T.subst sub ft) ap)
|
||||||
|
fields args
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* 문과 블록 *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
and infer_block env (b : block) : T.t =
|
||||||
|
let rec go = function
|
||||||
|
| [] -> T.TUnit
|
||||||
|
| [ S_expr e ] -> infer env e
|
||||||
|
| s :: rest ->
|
||||||
|
check_stmt env s;
|
||||||
|
go rest
|
||||||
|
in
|
||||||
|
go b.stmts
|
||||||
|
|
||||||
|
and check_stmt env = function
|
||||||
|
| S_let { pat; ty; value; pos; _ } ->
|
||||||
|
let declared = Option.map (conv env []) ty in
|
||||||
|
let got =
|
||||||
|
match (value, declared) with
|
||||||
|
| E_closure c, Some t -> infer_closure env c (Some t)
|
||||||
|
| _ -> infer env value
|
||||||
|
in
|
||||||
|
let t =
|
||||||
|
match declared with
|
||||||
|
| None -> got
|
||||||
|
| Some d ->
|
||||||
|
if not (T.unify d got) then mismatch env pos d got "let의 타입 주석";
|
||||||
|
d
|
||||||
|
in
|
||||||
|
check_pattern env t pat
|
||||||
|
| S_return { value; pos } ->
|
||||||
|
let got = match value with None -> T.TUnit | Some e -> infer env e in
|
||||||
|
if not (T.unify env.ret got) then mismatch env pos env.ret got "return의 값"
|
||||||
|
| S_assign { place; value; pos } ->
|
||||||
|
let p = infer env place in
|
||||||
|
let v = infer env value in
|
||||||
|
if not (T.unify p v) then mismatch env pos p v "대입"
|
||||||
|
| S_expr e -> ignore (infer env e)
|
||||||
|
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
(* 모듈 *)
|
||||||
|
(* ------------------------------------------------------------------ *)
|
||||||
|
|
||||||
|
let check_fn env (d : fn_decl) =
|
||||||
|
match d.fn_body with
|
||||||
|
| None -> ()
|
||||||
|
| Some body ->
|
||||||
|
let gen =
|
||||||
|
List.filter_map
|
||||||
|
(fun g -> if g.gp_effect then None else Some g.gp_name)
|
||||||
|
d.fn_gen
|
||||||
|
in
|
||||||
|
push env;
|
||||||
|
List.iter (fun p -> bind env p.p_name (conv env gen p.p_ty)) d.fn_params;
|
||||||
|
let declared =
|
||||||
|
match d.fn_ret with None -> T.TUnit | Some r -> conv env gen r
|
||||||
|
in
|
||||||
|
env.ret <- declared;
|
||||||
|
let got = infer_block env body in
|
||||||
|
if not (T.unify declared got) then
|
||||||
|
mismatch env d.fn_pos declared got
|
||||||
|
(Printf.sprintf "%s의 본문이 남기는 값" d.fn_name);
|
||||||
|
pop env
|
||||||
|
|
||||||
|
let check (m : modul) : error list =
|
||||||
|
let env =
|
||||||
|
{
|
||||||
|
structs = Hashtbl.create 16;
|
||||||
|
enums = Hashtbl.create 16;
|
||||||
|
caps = Hashtbl.create 16;
|
||||||
|
fns = Hashtbl.create 16;
|
||||||
|
consts = Hashtbl.create 16;
|
||||||
|
ctors = Hashtbl.create 16;
|
||||||
|
locals = [];
|
||||||
|
ret = T.TUnit;
|
||||||
|
errors = [];
|
||||||
|
}
|
||||||
|
in
|
||||||
|
(* 1차: 타입과 생성자 이름부터 (선언 순서에 의존하지 않는다) *)
|
||||||
|
List.iter
|
||||||
|
(fun it ->
|
||||||
|
match it with
|
||||||
|
| I_struct { name; gen; _ } ->
|
||||||
|
Hashtbl.replace env.structs name
|
||||||
|
(List.map (fun g -> g.gp_name) gen, [])
|
||||||
|
| I_enum { name; gen; variants; _ } ->
|
||||||
|
Hashtbl.replace env.enums name (List.map (fun g -> g.gp_name) gen, []);
|
||||||
|
List.iter (fun v -> Hashtbl.replace env.ctors v.v_name name) variants
|
||||||
|
| I_capability { name; _ } -> Hashtbl.replace env.caps name []
|
||||||
|
| _ -> ())
|
||||||
|
m.items;
|
||||||
|
(* 2차: 본문을 채운다 *)
|
||||||
|
List.iter
|
||||||
|
(fun it ->
|
||||||
|
match it with
|
||||||
|
| I_struct { name; gen; fields; _ } ->
|
||||||
|
let g = List.map (fun x -> x.gp_name) gen in
|
||||||
|
Hashtbl.replace env.structs name
|
||||||
|
(g, List.map (fun f -> (f.f_name, conv env g f.f_ty)) fields)
|
||||||
|
| I_enum { name; gen; variants; _ } ->
|
||||||
|
let g = List.map (fun x -> x.gp_name) gen in
|
||||||
|
Hashtbl.replace env.enums name
|
||||||
|
( g,
|
||||||
|
List.map
|
||||||
|
(fun v -> (v.v_name, List.map (conv env g) v.v_args))
|
||||||
|
variants )
|
||||||
|
| I_capability { name; methods; _ } ->
|
||||||
|
Hashtbl.replace env.caps name
|
||||||
|
(List.map (fun d -> (d.fn_name, scheme_of env d)) methods)
|
||||||
|
| I_fn { decl; _ } ->
|
||||||
|
Hashtbl.replace env.fns decl.fn_name (scheme_of env decl)
|
||||||
|
| I_const { name; ty; _ } ->
|
||||||
|
Hashtbl.replace env.consts name (conv env [] ty)
|
||||||
|
| _ -> ())
|
||||||
|
m.items;
|
||||||
|
(* 3차: 본문 검사 *)
|
||||||
|
List.iter
|
||||||
|
(fun it ->
|
||||||
|
env.locals <- [];
|
||||||
|
match it with
|
||||||
|
| I_fn { decl; _ } -> check_fn env decl
|
||||||
|
| I_const { ty; value; pos; _ } ->
|
||||||
|
let want = conv env [] ty in
|
||||||
|
let got = infer env value in
|
||||||
|
if not (T.unify want got) then mismatch env pos want got "상수의 값"
|
||||||
|
| _ -> ())
|
||||||
|
m.items;
|
||||||
|
List.sort
|
||||||
|
(fun a b ->
|
||||||
|
compare
|
||||||
|
(a.pos.Token.line, a.pos.Token.col)
|
||||||
|
(b.pos.Token.line, b.pos.Token.col))
|
||||||
|
(List.rev env.errors)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
(* 타입 표현과 지역 unification.
|
||||||
|
|
||||||
|
TUnknown이 핵심이다. 외부 모듈에서 오는 이름은 모듈 로딩이 없는 v0에서
|
||||||
|
해소할 수 없다. 그런 타입은 TUnknown이 되고 무엇과도 맞는다 — 모르는 것을
|
||||||
|
틀렸다고 말하지 않기 위해서다. 아는 범위에서만 검사한다.
|
||||||
|
|
||||||
|
TMeta는 호출 지점에서 제네릭을 인스턴스화할 때 생기는 미지수다. 함수 하나
|
||||||
|
범위에서만 살고 전역으로 흐르지 않는다 (철학 2: 전역 추론 없음). *)
|
||||||
|
|
||||||
|
type t =
|
||||||
|
| TUnknown
|
||||||
|
| TInt
|
||||||
|
| TBool
|
||||||
|
| TString
|
||||||
|
| TUnit
|
||||||
|
| TVar of string
|
||||||
|
| TCon of string * t list
|
||||||
|
| TFn of { affine : bool; params : t list; ret : t }
|
||||||
|
| TMeta of meta ref
|
||||||
|
|
||||||
|
and meta = Unbound of int | Bound of t
|
||||||
|
|
||||||
|
let counter = ref 0
|
||||||
|
|
||||||
|
let fresh () =
|
||||||
|
incr counter;
|
||||||
|
TMeta (ref (Unbound !counter))
|
||||||
|
|
||||||
|
let rec resolve t =
|
||||||
|
match t with TMeta { contents = Bound u } -> resolve u | _ -> t
|
||||||
|
|
||||||
|
let rec show t =
|
||||||
|
match resolve t with
|
||||||
|
| TUnknown -> "?"
|
||||||
|
| 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; ret } -> (
|
||||||
|
(if affine then "affine fn(" else "fn(")
|
||||||
|
^ String.concat ", " (List.map show params)
|
||||||
|
^ ")"
|
||||||
|
^ 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
|
||||||
|
|
||||||
|
(* 성공하면 true. 실패해도 예외를 던지지 않는다 — 호출자가 위치를 알고
|
||||||
|
진단을 만든다. *)
|
||||||
|
let rec unify a b =
|
||||||
|
match (resolve a, resolve b) with
|
||||||
|
| TUnknown, _ | _, TUnknown -> 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 f.ret g.ret
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
|
(* 제네릭 인스턴스화: TVar를 주어진 대입으로 바꾼다 *)
|
||||||
|
let rec subst env t =
|
||||||
|
match resolve t with
|
||||||
|
| TVar v -> ( match List.assoc_opt v env with Some u -> u | None -> TVar v)
|
||||||
|
| TCon (n, args) -> TCon (n, List.map (subst env) args)
|
||||||
|
| TFn f ->
|
||||||
|
TFn
|
||||||
|
{
|
||||||
|
affine = f.affine;
|
||||||
|
params = List.map (subst env) f.params;
|
||||||
|
ret = subst env f.ret;
|
||||||
|
}
|
||||||
|
| u -> u
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
// 03. TaskScope capability와 이름 있는 scope
|
// 03. TaskScope capability와 이름 있는 scope
|
||||||
//
|
//
|
||||||
// scope는 이름을 갖는다. 중첩 시 어느 스코프에 붙는 태스크인지가 코드에 보인다.
|
// scope 자식 = 부모 { ... }. 부모를 구문에 적는 이유는 하나다 —
|
||||||
|
// 적지 않으면 자식의 부모가 "가장 가까운 스코프"가 되고, 그것이 ambient authority다.
|
||||||
|
|
||||||
import "cool.dev/std/list" as List
|
import "cool.dev/std/list" as List
|
||||||
|
|
||||||
pub fn main(
|
pub fn main(
|
||||||
sc: TaskScope,
|
root: TaskScope,
|
||||||
fs: FileSystem,
|
fs: FileSystem,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
|
) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
|
||||||
let paths = [Path("a.txt"), Path("b.txt"), Path("c.txt")]
|
let paths = [Path("a.txt"), Path("b.txt"), Path("c.txt")]
|
||||||
|
|
||||||
// 블록 종료 시 런타임이 자식 전원을 join한다. handle 소비에 의존하지 않는다.
|
// 블록 종료 시 런타임이 자식 전원을 join한다. handle 소비에 의존하지 않는다.
|
||||||
scope sc {
|
scope work = root {
|
||||||
List.each(paths, fn(p) {
|
List.each(paths, fn(p) {
|
||||||
sc.spawn(fn() {
|
work.spawn(fn() {
|
||||||
let body = fs.read(p)
|
fs.read(p)
|
||||||
log.write("read: ", p)
|
log.write("read: ", p)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -24,37 +25,32 @@ pub fn main(
|
|||||||
log.write("all done")
|
log.write("all done")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 중첩 — 아직 표현할 수 없다. 열린 결정 하나가 걸려 있다.
|
// 중첩. 부모가 이름으로 지목되므로 어느 스코프에 붙는 태스크인지가 코드에 보인다.
|
||||||
//
|
pub fn fan_out(
|
||||||
// scope X { } 의 X가 "이미 가진 TaskScope를 쓴다"인지 "새 자식 스코프를 만들어
|
outer: TaskScope,
|
||||||
// X로 묶는다"인지가 정해지지 않았다. 전자면 아래 inner가 어디서도 오지 않고,
|
fs: FileSystem,
|
||||||
// 후자면 자식의 부모가 무엇인지 구문에 없다(= ambient authority).
|
log: Logger,
|
||||||
// 이름 해소가 이 구멍을 잡았다. 결정 전까지 주석으로 둔다.
|
groups: List[List[Path]],
|
||||||
//
|
) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
|
||||||
// pub fn fan_out(
|
List.each(groups, fn(g) {
|
||||||
// outer: TaskScope,
|
scope inner = outer {
|
||||||
// fs: FileSystem,
|
List.each(g, fn(p) {
|
||||||
// log: Logger,
|
inner.spawn(fn() { fs.read(p) })
|
||||||
// groups: List[List[Path]],
|
})
|
||||||
// ) effects {TaskScope.spawn, FileSystem.read, Logger.write} {
|
// 이 태스크는 inner가 아니라 outer의 수명을 따른다.
|
||||||
// List.each(groups, fn(g) {
|
outer.spawn(fn() { log.write("group done") })
|
||||||
// scope inner {
|
}
|
||||||
// List.each(g, fn(p) {
|
})
|
||||||
// inner.spawn(fn() { fs.read(p) })
|
}
|
||||||
// })
|
|
||||||
// outer.spawn(fn() { log.write("group done") })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// spawn 클로저는 by-move 또는 immutable capture만 가능하다.
|
// spawn 클로저는 by-move 또는 immutable capture만 가능하다.
|
||||||
pub fn broadcast(
|
pub fn broadcast(
|
||||||
sc: TaskScope,
|
root: TaskScope,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
msg: String,
|
msg: String,
|
||||||
) effects {TaskScope.spawn, Logger.write} {
|
) effects {TaskScope.spawn, Logger.write} {
|
||||||
scope sc {
|
scope s = root {
|
||||||
sc.spawn(fn() { log.write(msg) })
|
s.spawn(fn() { log.write(msg) })
|
||||||
sc.spawn(fn() { log.write(msg) })
|
s.spawn(fn() { log.write(msg) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ pub fn misuse_affine_closure(own f: File) -> fn() effects {File.close} {
|
|||||||
|
|
||||||
// [E-spawn-capture] spawn 클로저의 mutable capture
|
// [E-spawn-capture] spawn 클로저의 mutable capture
|
||||||
pub fn spawn_mutable(sc: TaskScope, mut counter: Int) effects {TaskScope.spawn} {
|
pub fn spawn_mutable(sc: TaskScope, mut counter: Int) effects {TaskScope.spawn} {
|
||||||
scope sc {
|
scope s = sc {
|
||||||
sc.spawn(fn() { counter = counter + 1 })
|
sc.spawn(fn() { counter = counter + 1 })
|
||||||
// ERROR: spawn 클로저는 mutable 참조를 capture할 수 없음
|
// ERROR: spawn 클로저는 mutable 참조를 capture할 수 없음
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// 09. 타입 검사기가 거부해야 하는 코드
|
||||||
|
//
|
||||||
|
// 이 파일에는 외부 타입이 하나도 없다. 전부 이 모듈 안에서 정의되므로
|
||||||
|
// 검사기가 TUnknown으로 빠져나갈 구석이 없다 — 이빨이 있는지 보는 파일이다.
|
||||||
|
|
||||||
|
pub enum Shape {
|
||||||
|
Circle(Int),
|
||||||
|
Rect(Int, Int),
|
||||||
|
Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub copyable struct Box {
|
||||||
|
width: Int,
|
||||||
|
label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn area(s: Shape) -> Int {
|
||||||
|
match s {
|
||||||
|
Circle(r) => r * r,
|
||||||
|
Rect(w, h) => w * h,
|
||||||
|
Point => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 여기서부터 전부 오류다 ---
|
||||||
|
|
||||||
|
// [E-type-if-cond] if의 조건은 Bool이어야 한다
|
||||||
|
pub fn bad_cond(n: Int) -> Int {
|
||||||
|
if n {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-if-branches] 두 분기의 타입이 달라서는 안 된다
|
||||||
|
pub fn bad_branches(c: Bool) -> Int {
|
||||||
|
if c {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
"둘"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-return] 본문이 남기는 값이 선언된 반환 타입과 달라서는 안 된다
|
||||||
|
pub fn bad_return(n: Int) -> String {
|
||||||
|
n + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-arity] 인자 개수
|
||||||
|
pub fn bad_arity(s: Shape) -> Int {
|
||||||
|
area(s, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-arg] 인자 타입
|
||||||
|
pub fn bad_arg(b: Box) -> Int {
|
||||||
|
area(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-arith] 산술의 피연산자는 Int
|
||||||
|
pub fn bad_arith(b: Box) -> Int {
|
||||||
|
b.width + b.label
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-field] 없는 필드
|
||||||
|
pub fn bad_field(b: Box) -> Int {
|
||||||
|
b.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-struct-missing] 빠진 필드
|
||||||
|
pub fn bad_struct_missing() -> Box {
|
||||||
|
Box { width: 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-struct-unknown] 없는 필드에 대입
|
||||||
|
pub fn bad_struct_unknown() -> Box {
|
||||||
|
Box { width: 1, label: "a", depth: 2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-struct-field] 필드 타입
|
||||||
|
pub fn bad_struct_field() -> Box {
|
||||||
|
Box { width: "넓이", label: "a" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-match-arms] match 팔의 타입이 서로 달라서는 안 된다
|
||||||
|
pub fn bad_arms(s: Shape) -> Int {
|
||||||
|
match s {
|
||||||
|
Circle(_) => 1,
|
||||||
|
Rect(_, _) => "둘",
|
||||||
|
Point => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-pattern] 패턴이 match 대상과 다른 타입
|
||||||
|
pub fn bad_pattern(b: Box) -> Int {
|
||||||
|
match b {
|
||||||
|
Circle(r) => r,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-let] let의 타입 주석
|
||||||
|
pub fn bad_let() -> Int {
|
||||||
|
let x: Int = "하나"
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-assign] 대입의 타입
|
||||||
|
pub fn bad_assign() -> Int {
|
||||||
|
let mut x = 1
|
||||||
|
x = "하나"
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-try] ?는 Result에만
|
||||||
|
pub fn bad_try(n: Int) -> Int {
|
||||||
|
n?
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-eq] 다른 타입끼리의 비교
|
||||||
|
pub fn bad_eq(n: Int, s: String) -> Bool {
|
||||||
|
n == s
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-list] 리스트 원소의 타입이 서로 다름
|
||||||
|
pub fn bad_list() -> List[Int] {
|
||||||
|
[1, "둘", 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
// [E-type-const] 상수의 값
|
||||||
|
pub const LIMIT: Int = "많이"
|
||||||
+11
-5
@@ -16,13 +16,17 @@
|
|||||||
| 06_affine_closure | callable affinity (fn vs affine fn), own과의 직교성 |
|
| 06_affine_closure | callable affinity (fn vs affine fn), own과의 직교성 |
|
||||||
| 07_module_interface | interface artifact가 담아야 할 것 전부 |
|
| 07_module_interface | interface artifact가 담아야 할 것 전부 |
|
||||||
| 08_syntax_errors | **파서가** 거부해야 하는 코드 |
|
| 08_syntax_errors | **파서가** 거부해야 하는 코드 |
|
||||||
|
| 09_type_errors | **타입 검사기가** 거부해야 하는 코드 (외부 타입 0개) |
|
||||||
|
|
||||||
05와 08은 통과하면 안 되는 파일이다. 각 함수 주석의 `[E-...]` 태그가 기대
|
05, 08, 09는 통과하면 안 되는 파일이다. 각 함수 주석의 `[E-...]` 태그가 기대
|
||||||
진단이다. 둘의 목적이 다르다 — **05는 구문은 맞지만 검사기가 거부해야 하고,
|
진단이며, 셋의 목적이 다르다 — **08은 파서가, 09는 타입 검사기가, 05는 아직
|
||||||
08은 파서가 거부해야 한다.** 그래서 파일을 나눴다: 한 파일에 섞으면 파서가
|
없는 move/affinity 검사가** 거부해야 한다. 단계별로 파일을 나눈 이유는
|
||||||
첫 오류에서 멈춰 검사기 케이스에 영영 도달하지 못한다.
|
앞 단계가 첫 오류에서 멈추면 뒤 단계 케이스에 영영 도달하지 못하기 때문이다.
|
||||||
|
|
||||||
오류 복구는 아직 없다. 파서는 첫 오류에서 멈춘다.
|
09에는 외부 타입이 하나도 없다. 전부 모듈 안에서 정의되므로 검사기가
|
||||||
|
TUnknown으로 빠져나갈 구석이 없다 — 검사기에 이빨이 있는지 보는 파일이다.
|
||||||
|
|
||||||
|
파서는 첫 오류에서 멈춘다(오류 복구 미구현). 타입 검사기는 오류를 전부 모은다.
|
||||||
|
|
||||||
## 확정된 표기
|
## 확정된 표기
|
||||||
|
|
||||||
@@ -41,5 +45,7 @@
|
|||||||
- 문 구분은 줄바꿈 (Go식 자동 삽입)
|
- 문 구분은 줄바꿈 (Go식 자동 삽입)
|
||||||
- 블록은 식. 마지막 식이 값이고 `return`은 조기 탈출 전용
|
- 블록은 식. 마지막 식이 값이고 `return`은 조기 탈출 전용
|
||||||
- `if`와 `match`는 식. `match` 가드 없음
|
- `if`와 `match`는 식. `match` 가드 없음
|
||||||
|
- scope: `scope inner = outer { ... }` — 부모를 구문에 명시
|
||||||
|
- 타입 이름은 그 타입의 함수 이름공간: `String.len(s)`, `File.close(f)`
|
||||||
- 리스트 리터럴 `[a, b, c]`, 빈 리터럴은 타입 주석 필요
|
- 리스트 리터럴 `[a, b, c]`, 빈 리터럴은 타입 주석 필요
|
||||||
- 오류 전파 `?`는 `Result` 전용
|
- 오류 전파 `?`는 `Result` 전용
|
||||||
|
|||||||
+136
-7
@@ -306,9 +306,10 @@ let () =
|
|||||||
check "match"
|
check "match"
|
||||||
(one "fn f() {\n match e {\n A(_) => 1,\n _ => 2,\n }\n}"
|
(one "fn f() {\n match e {\n A(_) => 1,\n _ => 2,\n }\n}"
|
||||||
= "(fn f () (block (match e ((A _) => 1) (_ => 2))))");
|
= "(fn f () (block (match e ((A _) => 1) (_ => 2))))");
|
||||||
check "scope"
|
check "scope는 부모를 명시한다"
|
||||||
(one "fn f() {\n scope sc {\n sc.spawn(g)\n }\n}"
|
(one "fn f(root: TaskScope) {\n scope sc = root {\n sc.spawn(g)\n }\n}"
|
||||||
= "(fn f () (block (scope sc (block (call (. sc spawn) g)))))");
|
= "(fn f (root:TaskScope) (block (scope sc = root (block (call (. sc \
|
||||||
|
spawn) g)))))");
|
||||||
check "struct 리터럴"
|
check "struct 리터럴"
|
||||||
(one "fn f() {\n H { a: 1 }\n}" = "(fn f () (block (struct H (a 1))))")
|
(one "fn f() {\n H { a: 1 }\n}" = "(fn f () (block (struct H (a 1))))")
|
||||||
|
|
||||||
@@ -411,10 +412,15 @@ let () =
|
|||||||
(* --- scope 머리는 지역 바인딩이어야 한다 --- *)
|
(* --- scope 머리는 지역 바인딩이어야 한다 --- *)
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
check "지역 바인딩이 아닌 scope 이름"
|
check "부모 scope가 지역 바인딩이 아니면 오류"
|
||||||
(has_err "fn f() {\n scope sc {\n sc\n }\n}" "지역 바인딩이 아닙니다");
|
(has_err "fn f() {\n scope s = root {\n s\n }\n}" "지역 바인딩이 아닙니다");
|
||||||
check "파라미터로 온 scope는 통과"
|
check "파라미터로 온 부모는 통과"
|
||||||
(resolve_errs "fn f(sc: TaskScope) {\n scope sc {\n sc\n }\n}" = [])
|
(resolve_errs "fn f(root: TaskScope) {\n scope s = root {\n s\n }\n}"
|
||||||
|
= []);
|
||||||
|
check "자식 scope 이름은 블록 안에서만 산다"
|
||||||
|
(resolve_ext
|
||||||
|
"fn f(root: TaskScope) {\n scope s = root {\n s\n }\n s\n}"
|
||||||
|
= [ "TaskScope"; "s" ])
|
||||||
|
|
||||||
(* --- variant는 구문이 아니라 이름 해소가 판정한다 --- *)
|
(* --- variant는 구문이 아니라 이름 해소가 판정한다 --- *)
|
||||||
|
|
||||||
@@ -468,3 +474,126 @@ let () =
|
|||||||
errors;
|
errors;
|
||||||
check (f ^ " 이름 해소") false)
|
check (f ^ " 이름 해소") false)
|
||||||
files
|
files
|
||||||
|
|
||||||
|
(* ================================================================== *)
|
||||||
|
(* 타입 검사 *)
|
||||||
|
(* ================================================================== *)
|
||||||
|
|
||||||
|
let type_errs src =
|
||||||
|
List.map (fun (e : Typecheck.error) -> e.msg) (Typecheck.check (parse_ok src))
|
||||||
|
|
||||||
|
let type_ok src = type_errs src = []
|
||||||
|
|
||||||
|
let type_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)
|
||||||
|
(type_errs src)
|
||||||
|
|
||||||
|
(* --- 제네릭은 호출 지점에서 지역 unification으로 풀린다 --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
check "제네릭 인스턴스화" (type_ok "fn id[a](x: a) -> a\nfn f() -> Int {\n id(1)\n}");
|
||||||
|
check "제네릭 결과가 반환 타입과 안 맞으면 오류"
|
||||||
|
(type_has "fn id[a](x: a) -> a\nfn f() -> String {\n id(1)\n}" "String");
|
||||||
|
check "명시적 인스턴스화의 인자 개수"
|
||||||
|
(type_has "fn id[a](x: a) -> a\nfn f() -> Int {\n id[Int, Int](1)\n}"
|
||||||
|
"타입 인자 1개가 필요한데 2개");
|
||||||
|
check "고차 함수의 클로저 파라미터 타입은 기대 타입에서 온다"
|
||||||
|
(type_ok
|
||||||
|
"fn map[a, b](xs: List[a], f: fn(a) -> b) -> List[b]\n\
|
||||||
|
fn g(xs: List[Int]) -> List[Int] {\n\
|
||||||
|
\ map(xs, fn(x) { x + 1 })\n\
|
||||||
|
}");
|
||||||
|
check "클로저 본문의 타입 오류는 잡힌다"
|
||||||
|
(type_has
|
||||||
|
"fn map[a, b](xs: List[a], f: fn(a) -> b) -> List[b]\n\
|
||||||
|
fn g(xs: List[Int]) -> List[Int] {\n\
|
||||||
|
\ map(xs, fn(x) { x + \"1\" })\n\
|
||||||
|
}"
|
||||||
|
"산술 연산자")
|
||||||
|
|
||||||
|
(* --- Result와 ? --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
check "?는 Result를 벗긴다"
|
||||||
|
(type_ok
|
||||||
|
"fn f(x: Result[Int, String]) -> Result[Int, String] {\n\
|
||||||
|
\ let a = x?\n\
|
||||||
|
\ Ok(a)\n\
|
||||||
|
}");
|
||||||
|
check "?는 Result 반환 함수 안에서만"
|
||||||
|
(type_has "fn f(x: Result[Int, String]) -> Int {\n x?\n}"
|
||||||
|
"Result를 반환하는 함수 안에서만")
|
||||||
|
|
||||||
|
(* --- enum과 struct --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let e = "enum E {\n A(Int),\n B,\n}\n" in
|
||||||
|
check "생성자 호출" (type_ok (e ^ "fn f() -> E {\n A(1)\n}"));
|
||||||
|
check "생성자 인자 타입" (type_has (e ^ "fn f() -> E {\n A(\"x\")\n}") "인자");
|
||||||
|
check "인자 없는 생성자" (type_ok (e ^ "fn f() -> E {\n B\n}"));
|
||||||
|
check "match는 값을 낸다"
|
||||||
|
(type_ok
|
||||||
|
(e
|
||||||
|
^ "fn f(x: E) -> Int {\n match x {\n A(n) => n,\n B => 0,\n }\n}"
|
||||||
|
));
|
||||||
|
check "제네릭 struct 필드"
|
||||||
|
(type_ok "struct P[a] {\n v: a,\n}\nfn f(p: P[Int]) -> Int {\n p.v\n}");
|
||||||
|
check "제네릭 struct 필드 타입 오류"
|
||||||
|
(type_has "struct P[a] {\n v: a,\n}\nfn f(p: P[Int]) -> String {\n p.v\n}"
|
||||||
|
"String")
|
||||||
|
|
||||||
|
(* --- capability 메서드 --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let c = "capability G {\n fn pay(n: Int) -> Bool\n}\n" in
|
||||||
|
check "capability 메서드 타입"
|
||||||
|
(type_ok (c ^ "fn f(g: G) -> Bool {\n g.pay(1)\n}"));
|
||||||
|
check "없는 메서드"
|
||||||
|
(type_has (c ^ "fn f(g: G) -> Bool {\n g.nope(1)\n}") "메서드가 없습니다");
|
||||||
|
check "메서드 인자 타입"
|
||||||
|
(type_has (c ^ "fn f(g: G) -> Bool {\n g.pay(\"x\")\n}") "인자")
|
||||||
|
|
||||||
|
(* --- 외부 이름은 검사를 막지 않는다 --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
check "모르는 타입은 무엇과도 맞는다"
|
||||||
|
(type_ok "fn f(w: Widget) -> Int {\n w.anything(1, 2)\n}");
|
||||||
|
check "모르는 것을 틀렸다고 말하지 않는다" (type_ok "fn f(w: Widget) -> Widget {\n w\n}")
|
||||||
|
|
||||||
|
(* --- affinity는 타입 검사가 소유하지 않는다 --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
check "affine fn과 fn은 타입 동등성에서 구분되지 않는다"
|
||||||
|
(type_ok "fn f() -> affine fn() {\n fn() {\n unit\n }\n}")
|
||||||
|
|
||||||
|
(* --- 샘플 --- *)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let dir = "../samples" in
|
||||||
|
let ok_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" && f <> "09_type_errors.cool")
|
||||||
|
|> List.sort compare
|
||||||
|
in
|
||||||
|
List.iter
|
||||||
|
(fun f ->
|
||||||
|
match Driver.typecheck (Filename.concat dir f) with
|
||||||
|
| Ok () -> ()
|
||||||
|
| Error errors ->
|
||||||
|
List.iter
|
||||||
|
(fun e -> Printf.printf " %s\n" (Driver.string_of_error e))
|
||||||
|
errors;
|
||||||
|
check (f ^ " 타입 검사") false)
|
||||||
|
ok_files;
|
||||||
|
match Driver.typecheck (Filename.concat dir "09_type_errors.cool") with
|
||||||
|
| Ok () -> check "09는 타입 오류를 내야 한다" false
|
||||||
|
| Error errors ->
|
||||||
|
check "09의 오류를 전부 모은다 (첫 오류에서 멈추지 않는다)" (List.length errors >= 18)
|
||||||
|
|||||||
Reference in New Issue
Block a user