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:
+32
-3
@@ -11,7 +11,10 @@
|
||||
|
||||
open Ast
|
||||
|
||||
type error = { pos : Token.pos; msg : string }
|
||||
(* blocking: 이름을 해소하지 못했다는 뜻이고, 이후 단계는 의미가 없다.
|
||||
lint는 다르다 — 코드는 분석 가능하고 검사도 계속되어야 한다.
|
||||
둘을 같은 통에 넣으면 lint 하나가 진짜 타입 오류를 가린다. *)
|
||||
type error = { pos : Token.pos; msg : string; blocking : bool }
|
||||
type info = { externals : (string * Token.pos) list }
|
||||
type item_kind = K_fn | K_type | K_const | K_import
|
||||
|
||||
@@ -23,13 +26,20 @@ type state = {
|
||||
mutable scopes : (string * bool) list list; (* 이름 -> 가변 여부 *)
|
||||
mutable errors : error list;
|
||||
mutable ext : (string * Token.pos) list;
|
||||
(* 실제로 참조된 import 별칭 *)
|
||||
used : (string, unit) Hashtbl.t;
|
||||
}
|
||||
|
||||
let builtin_types =
|
||||
[ "Int"; "Bool"; "String"; "Unit"; "List"; "Option"; "Result" ]
|
||||
|
||||
let builtin_values = [ "unit"; "Ok"; "Err"; "Some"; "None" ]
|
||||
let error st pos msg = st.errors <- { pos; msg } :: st.errors
|
||||
let error st pos msg = st.errors <- { pos; msg; blocking = true } :: st.errors
|
||||
let lint st pos msg = st.errors <- { pos; msg; blocking = false } :: st.errors
|
||||
|
||||
let use_alias st a =
|
||||
if Hashtbl.find_opt st.items a = Some K_import then
|
||||
Hashtbl.replace st.used a ()
|
||||
|
||||
let external_ref st name pos =
|
||||
if not (List.mem_assoc name st.ext) then st.ext <- (name, pos) :: st.ext
|
||||
@@ -80,6 +90,7 @@ let rec resolve_ty st = function
|
||||
(* 한정된 이름은 별칭이 이 모듈에 있는지만 본다. 그 모듈 안에 그 타입이
|
||||
있는지는 모듈 하나만 보고 결정할 수 없다 — 외부 참조로 기록한다. *)
|
||||
| T_named { modl = Some a; args; pos; _ } ->
|
||||
use_alias st a;
|
||||
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 } ->
|
||||
@@ -124,6 +135,7 @@ let rec resolve_pattern st seen = function
|
||||
seen := n :: !seen;
|
||||
bind st pos n false))
|
||||
| P_ctor { modl = Some a; args; pos; _ } ->
|
||||
use_alias st a;
|
||||
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 } ->
|
||||
@@ -199,7 +211,12 @@ let rec resolve_expr st = function
|
||||
| E_call { callee; args; _ } ->
|
||||
resolve_expr st callee;
|
||||
List.iter (resolve_expr st) args
|
||||
| E_field { obj; _ } -> resolve_expr st obj
|
||||
| E_field { obj; _ } ->
|
||||
(* Alias.f — 별칭 접근도 사용이다 *)
|
||||
(match obj with
|
||||
| E_ident (o, _) when lookup_local st o = None -> use_alias st o
|
||||
| _ -> ());
|
||||
resolve_expr st obj
|
||||
| E_inst { callee; args; pos } ->
|
||||
resolve_expr st callee;
|
||||
List.iter (resolve_targ st pos) args
|
||||
@@ -312,6 +329,7 @@ let resolve (m : modul) : info * error list =
|
||||
scopes = [];
|
||||
errors = [];
|
||||
ext = [];
|
||||
used = Hashtbl.create 8;
|
||||
}
|
||||
in
|
||||
(* 1차: 모듈 수준 이름을 모은다. 선언 순서에 의존하지 않는다. *)
|
||||
@@ -362,6 +380,17 @@ let resolve (m : modul) : info * error list =
|
||||
resolve_expr st value;
|
||||
pop st)
|
||||
m.items;
|
||||
(* 미사용 import는 오류다. 취향 문제가 아니라 invalidation 표면 문제다 —
|
||||
쓰지도 않는 모듈의 시그니처가 바뀌면 이 모듈이 재검사된다. 증분 루프의
|
||||
비용을 아무 이유 없이 넓히는 선언은 남겨둘 수 없다. *)
|
||||
List.iter
|
||||
(fun it ->
|
||||
match it with
|
||||
| I_import { alias; pos; _ } when not (Hashtbl.mem st.used alias) ->
|
||||
lint st pos
|
||||
(Printf.sprintf "%s을(를) 가져왔지만 쓰지 않습니다 (재검사 범위만 넓힙니다)" alias)
|
||||
| _ -> ())
|
||||
m.items;
|
||||
let by_pos (_, a) (_, b) =
|
||||
compare (a.Token.line, a.Token.col) (b.Token.line, b.Token.col)
|
||||
in
|
||||
|
||||
Reference in New Issue
Block a user