lint: 파서 오류 복구와 두 lint — 남은 부채를 턴다
파서 오류 복구: 항목 단위로만 회복한다. 문 단위로 더 잘게 회복하려 하면 파서가 추측을 하게 되고, 틀린 추측은 없는 오류를 지어낸다. 한 항목에 오류 하나가 상한이라는 것은 정직한 한계다. 동기화 지점은 중괄호 깊이 0 + 줄 첫머리 + 선언 시작 토큰 — 셋 다 필요하다. 본문 안의 fn을 새 항목으로 오인하면 그 뒤가 전부 어긋난다. 샘플 08이 이제 오류 넷을 한 번에 보고한다. 두 lint (취향이 아니라 비용이다): - 미사용 import는 재검사 범위를 넓힌다. 쓰지 않는 모듈의 시그니처가 바뀌면 이 모듈이 재검사된다. - effect 과잉 선언은 호출자에게 없는 의무를 지운다. 시그니처는 실제보다 좁아도 안 되고 넓어도 안 된다. 과잉 선언은 effect 변수가 있거나 본문에 모르는 이름이 있으면 판정하지 않는다. 첫 구현이 샘플 03/06을 오탐으로 잡았는데, 원인이 외부 타입이었다 — 외부 capability의 메서드는 effect를 모르므로 "수행하지 않았다"고 말할 근거가 없다. saw_unknown으로 판정을 보류한다. Resolve.error에 blocking을 나눴다. 이름 해소 실패는 뒤 단계를 막지만 lint는 막지 않는다 — lint 하나가 진짜 타입 오류를 가리면 루프가 느려진다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+39
-1
@@ -35,6 +35,9 @@ type env = {
|
||||
말할 수 있다. 클로저에 들어가면 저장하고 비운다 — 클로저의 effect는
|
||||
정의한 자리가 아니라 부르는 자리에서 일어난다. *)
|
||||
mutable performed : (T.atom * Token.pos) list;
|
||||
(* 이 본문에서 모르는 것을 만났는가. 과잉 선언 판정에만 쓴다 — 외부 타입의
|
||||
메서드는 effect를 알 수 없으므로 "수행하지 않았다"고 말할 근거가 없다. *)
|
||||
mutable saw_unknown : bool;
|
||||
mutable errors : error list;
|
||||
}
|
||||
|
||||
@@ -182,7 +185,9 @@ let rec infer env (e : expr) : T.t =
|
||||
| None -> (
|
||||
match Hashtbl.find_opt env.ctors n with
|
||||
| Some enum -> nullary_ctor env enum n
|
||||
| None -> T.TUnknown)))))
|
||||
| None ->
|
||||
env.saw_unknown <- true;
|
||||
T.TUnknown)))))
|
||||
| E_list (xs, pos) ->
|
||||
let elem = T.fresh () in
|
||||
List.iter
|
||||
@@ -429,6 +434,7 @@ and infer_call env callee args pos =
|
||||
in
|
||||
match fn_ty with
|
||||
| None ->
|
||||
env.saw_unknown <- true;
|
||||
List.iter (fun a -> ignore (infer env a)) args;
|
||||
T.TUnknown
|
||||
| Some (T.TFn { params; eff; ret; _ }) ->
|
||||
@@ -678,6 +684,7 @@ let check_fn env (d : fn_decl) =
|
||||
in
|
||||
env.ret <- declared;
|
||||
env.performed <- [];
|
||||
env.saw_unknown <- false;
|
||||
let got = infer_block env body in
|
||||
if not (T.unify declared got) then
|
||||
mismatch env d.fn_pos declared got
|
||||
@@ -699,6 +706,36 @@ let check_fn env (d : fn_decl) =
|
||||
(T.atom_show m) d.fn_name (T.eff_show declared_eff)))
|
||||
missing)
|
||||
(List.rev env.performed);
|
||||
(* 과잉 선언도 오류다. 선언한 effect를 수행하지 않으면 호출자는 하지도
|
||||
않는 일에 대한 의무를 진다 — 자기 effects 절을 넓히거나 capability를
|
||||
받아오게 된다. 시그니처는 실제보다 좁아도 안 되고 넓어도 안 된다.
|
||||
|
||||
effect 변수가 있으면 판정하지 않는다. e에 무엇이 묶일지는 호출
|
||||
지점이 정하고, 본문만 보고는 알 수 없다 — 모르는 것을 틀렸다고
|
||||
말하지 않는다. *)
|
||||
let has_var =
|
||||
List.exists (function T.A_var _ -> true | _ -> false) declared_eff
|
||||
in
|
||||
if (not has_var) && not env.saw_unknown then
|
||||
List.iter
|
||||
(fun a ->
|
||||
match a with
|
||||
| T.A_name (cap, meth) ->
|
||||
let performed = List.map fst env.performed |> T.eff_resolve in
|
||||
if
|
||||
not
|
||||
(List.exists
|
||||
(function
|
||||
| T.A_name (c, m) -> c = cap && m = meth
|
||||
| T.A_var _ | T.A_meta _ -> true)
|
||||
performed)
|
||||
then
|
||||
err env d.fn_pos
|
||||
(Printf.sprintf
|
||||
"%s은(는) %s을(를) 선언했지만 수행하지 않습니다 (effects 절에서 지우십시오)"
|
||||
d.fn_name (T.atom_show a))
|
||||
| _ -> ())
|
||||
declared_eff;
|
||||
env.performed <- [];
|
||||
pop env
|
||||
|
||||
@@ -735,6 +772,7 @@ let check ?(imports : item list = []) (m : modul) : error list =
|
||||
locals = [];
|
||||
ret = T.TUnit;
|
||||
performed = [];
|
||||
saw_unknown = false;
|
||||
errors = [];
|
||||
}
|
||||
in
|
||||
|
||||
Reference in New Issue
Block a user