diff --git a/AGENTS.md b/AGENTS.md index bc8dcf9..f881759 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,8 +9,7 @@ DOS/Windows용 시스템 프로그래밍 언어 Ferro와 그 컴파일러 `fec`. |---|---| | `SPEC.md` | 언어 명세. 유일한 규범 문서 | | `IR.md` | 중간 표현. 프론트엔드와 기계 사이 | -| `TODO.md` | 남은 작업, 미결 결정, 정해진 것 | -| `GOAL.md` | 부트스트랩까지의 실행 계획. P0~P4 와 안 하기로 한 것 | +| `TODO.md` | 남은 작업과 정해진 것. 언어 규칙은 `SPEC.md` 를 가리키기만 한다 | | `fec/tests/*/README.md` | 각 fixture 디렉터리가 무엇을 검사하는지 | ## 파이프라인 diff --git a/GOAL.md b/GOAL.md deleted file mode 100644 index cbaad00..0000000 --- a/GOAL.md +++ /dev/null @@ -1,88 +0,0 @@ -# GOAL — 부트스트랩까지 남은 작업 - -외부 스펙 감사(v0.1.8)와, 그 항목들을 실제로 빌드해서 확인한 결과를 합친 실행 -계획이었다. **P0~P4 는 전부 끝났다.** 감사는 문서를 읽었고 여기 적힌 것은 -컴파일러에 물어본 답이다. - -``` -uv run python tests/run.py 245/245 + std 밖 unsafe/*T 예산 검사 -uv run python tests/exec.py 38/38 -``` - ---- - -## 끝난 것 - -### P0 — 조용히 틀린 것 - -| # | 무엇이었나 | | -|---|---|---| -| 0-1 | 니치 옵셔널(`?^T`, `?&T`)이 포인터가 아니라 포인터가 든 자리를 넘겼다. `if let` 이 컴파일되고 쓰레기를 반환했다 | `exec/optref.fe` | -| 0-2 | `Handle(Node){ raw: 7 }` 이 파싱되지 않았다. 제네릭 인스턴스는 `Self{...}` 나 생성자로만 지을 수 있었다 | `exec/geninst/` | - -### P1 — 스펙의 빈칸 - -| # | 결정 | | -|---|---|---| -| 1-1 | 정수 리터럴은 문맥이 요구하는 타입, 없으면 `i32`. 범위를 벗어나면 거부 | `types/badlit.fe`, `types/oklit.fe` | -| — | 그 자리에서 나온 것: **store 폭이 목적지가 아니라 값에서 왔다.** `let b: u8 = 200;` 이 4바이트를 1바이트 자리에 써서 옆 지역을 지웠다 | `exec/narrow.fe` | -| 1-2 | 미사용 타입 파라미터는 정상. typed handle 이 그 모양이다 | `generic/okphant.fe`, `badphant.fe` | -| 1-3 | `--no-checks` 에서 오버플로는 랩어라운드로 정의된다 | SPEC §7.4 | -| 1-4 | 작은 규칙 일곱 (SPEC §7.9). 다섯은 이미 옳았고, `defer` 안의 `return` 과 순회 중 원본 변경 둘은 그냥 통과하고 있었다 | `own/bdefret.fe`, `own/badforwr.fe`, `own/okforrd.fe` | - -### P2 — SPEC 에서 죽은 C 백엔드 제거 - -`.fei`, `--emit-c`, `fe_errors.h`, "호스트 C 방출", "C 방출 시 static inline" -열 군데. 감사가 그 문서를 충실히 읽고 존재하지 않는 문제(C 의 부호 있는 -오버플로 UB)를 보고했다 — 명세가 거짓말을 하면 그것을 읽는 사람이 틀린 답을 -낸다. 오류 코드 절은 실제대로 다시 썼고, `--target=`/`--model=` 은 드라이버에서 -없앴다. - -### P3 — stdlib - -| # | | | -|---|---|---| -| 3-1 | `arena.Handle(T)` — 8바이트 고정. 세대·epoch·arena id 를 담아 놓아준 뒤, reset 뒤, 다른 아레나에 물으면 `null` | `exec/arenat.fe` | -| 3-2 | `arena.Arena(T)` — 짧은 대여만. `get` 은 사본을 준다. `get_mut` 은 두지 않았다 | | -| 3-3 | `list.List(T)` 에 `pop`/`take`/`swap`/`slice`/`slice_mut`/`clear` | `exec/listmor.fe` | -| 3-4 | `intern.Interner` — `str` 을 꺼내는 API 없음. `eq`/`len_of`/`hash_of`/`find`/`copy_into` | `exec/interns.fe` | -| 3-5 | `std.map` 이 `StrId` 를 바이트 키로 받는다. 별도 `IntMap` 불필요 | | - -길에서 고친 것 둘: -- 메서드 호출이 시그니처를 **호출자 유닛**에서 풀었다. -- 제네릭 인스턴스를 필드로 담은 구조체가 **크기 0 으로 굳었다** — 짓는 중인 - 인스턴스가 배치되어 버려서. `Holder` 가 36 바이트 대신 12 였다. - -### P4 — 측정 - -`--report-unsafe`, `--report-instances`. 그리고 그 숫자를 `run.py` 가 검사한다: -`std.mem`/`std.sys` 밖의 `unsafe` 와 `*T` 는 0 이어야 하고, 늘어나면 빌드가 -실패한다. - -``` -unit unsafe *T unchecked -std.io 6 1 0 -std.sys 10 12 0 -total 16 13 0 -outside std 0 0 0 -``` - ---- - -## 채택하지 않은 것 - -| 감사 항목 | 판정 | 근거 | -|---|---|---| -| 참조 튜플 `-> (&mut T, &mut T)` | 보류 | struct 는 필드 단위 대여로 풀렸다. 컨테이너의 두 원소만 남는데 렉서·파서에서 필요했던 자리가 0 번 | -| `Arena.get_mut -> ?&mut T` | 거부 | 아레나 하나를 통째로 잠그는 참조를 오래 들고 있게 하는 API | -| `fmt.fmt_strid` | 거부 | `@print` 가 interner 인스턴스에 닿을 수 없다 (R10) | -| 별도 `IntMap(V)` | 불필요 | `std.map` 이 이미 포괄 | -| C 오버플로 방출 규칙 | 대체 | C 백엔드가 없다. 결론만 1-3 으로 흡수 | -| R4 완화 | 영구 제외 | | - ---- - -## 다음 - -부트스트랩까지 남은 것은 `TODO.md` 에 있다. 도구는 다 갖췄다 — 아레나, 핸들, -맵, interner, 그리고 리졸버가 쓸 `Node.bind` 와 `Map.clear`. diff --git a/SPEC.md b/SPEC.md index c6a1a9a..13b0147 100644 --- a/SPEC.md +++ b/SPEC.md @@ -581,7 +581,7 @@ pub fn main() -> !void { --- -### 7.9 작은 규칙들 +### 7.7 작은 규칙들 구현자가 임의로 정하면 갈라지는 것들. 각각 한 줄이면 끝나므로 여기 모아 둔다. diff --git a/TODO.md b/TODO.md index d14139a..0db2f06 100644 --- a/TODO.md +++ b/TODO.md @@ -25,7 +25,7 @@ uv run python tests/exec.py 38/38 컴파일된 프로그램이 실제로 | `std.map` | 키를 맵이 소유한 버퍼에 복사하고 슬롯은 위치만 든다 | | **Ferro 파서를 Ferro 로** | 노드 배열 하나 + 인덱스. `1 + 2 * 3` 이 `(+ 1 (* 2 3))` 로 묶인다 | | 필드 단위 대여 | `p.a` 와 `p.b` 는 다른 place 다. `std.map` 의 `keep` 이 다시 함수 하나가 됐다 | -| **GOAL.md P0~P4** | 외부 감사에서 나온 것 전부. stdlib 다섯, 스펙 빈칸, 죽은 C 백엔드, 측정 | +| 외부 스펙 감사 대응 | 니치 옵셔널·store 폭·배치 세 버그, 스펙 빈칸, 죽은 C 백엔드, stdlib 다섯, 측정 | 파서가 알려준 것: **자기 참조 자료구조는 인덱스로 짓는다.** 노드는 `^Node` 를 들 수 없고(자식이 여럿이며 한 번씩 소유하지 않는다) `&Node` 도 들 수 없다(R4). @@ -35,8 +35,6 @@ uv run python tests/exec.py 38/38 컴파일된 프로그램이 실제로 ## 셀프호스팅으로 가는 길 -순서와 근거는 `GOAL.md` 에 있다 (P0~P4). 여기는 무엇이 남았는지만 적는다. - | # | 일 | 규모 | 비고 | |---|---|---|---| | 1 | 리졸버를 Ferro 로 | 중 | 도구는 다 있다: `intern`, `map`, `Node.bind`, `Map.clear` | @@ -62,29 +60,43 @@ uv run python tests/exec.py 38/38 컴파일된 프로그램이 실제로 --- -## 정해진 것 +## 정해진 것 — 언어 + +`SPEC.md` 가 유일한 출처다. 여기는 **어디를 보는지만** 적는다. 문장을 옮겨 +적으면 한쪽만 고쳐져서 갈라진다. | | | |---|---| -| 타깃 | **i386 하나.** 세그먼트 없음, `far` 영구 제외 (SPEC §2) | -| `usize`/`isize` | **타깃의 포인터 폭.** 비트 수를 약속하지 않아 64비트 문이 닫히지 않음 | -| 제네릭 | 모노모피제이션. 순수 프론트엔드 기능이라 IR 에 제네릭 개념이 없음 | -| 덩어리 전달 | 전부 주소로. ISA 마다 다른 구조체 전달 ABI 를 피해감 | -| 트랩 | `trap ` → `fe_trap(reason, FE_FILE_n, line)`. 파일은 검사가 쓰인 유닛 | -| 슬라이스 배치 | 포인터 다음 길이. 오프셋은 `lowerpri.h` 한 군데에만 | -| 오류 코드 | `error.Name` 을 빌드 전체에서 모아 철자 순으로 1부터 | -| 레지스터 | ebx·esi·edi 를 블록 안에 머무는 임시값에 준다. eax/ecx/edx 는 스크래치 | -| 정수 리터럴 | 문맥이 요구하는 타입, 없으면 `i32`. 범위를 벗어나면 거부 (SPEC §3) | -| store 폭 | 값이 아니라 **자리**가 정한다 | -| unsafe 예산 | `std.mem`/`std.sys` 밖은 0. `run.py` 가 검사하고 늘어나면 실패한다 | -| 해제 | 소유자를 놓으면 그것이 가진 것도 놓는다 (R1). `drop` 을 부른 뒤 필드로 내려간다 — 그래서 `List`/`Arena`/`Map` 은 `drop` 이 없다 | -| 배타 대여를 호출에 넘기기 | **이동이 아니라 호출 동안의 재대여** (SPEC §4.2, §5 R6). 없으면 배타 파라미터를 함수당 한 번만 넘길 수 있어 재귀 하강 파서를 못 쓴다 | -| 자기 `drop` 안의 부분 이동 | **허용** (SPEC §5 R7 예외). 객체가 사라지는 중이라 뒤에 남아 읽힐 것이 없다 | -| 대여 단위 | **place 단위** (SPEC §5 R6). 루트 변수의 직속 필드 한 겹까지 갈라지고, 그 아래와 인덱스·역참조는 전체 대여다. 한 값당 4 필드까지, 넘으면 전체로 되돌아간다 | +| 타깃, `usize`/`isize` | §2 | +| 정수 리터럴의 타입과 범위 | §3 | +| 호출 인자·반환 위치의 대여 약화 | §4.2 | +| 해제는 소유자를 따라 내려간다 | §5 R1 | +| 대여는 place 단위 | §5 R6 | +| 배타 대여를 호출에 넘기는 것은 재대여 | §4.2, §5 R6 | +| 자기 `drop` 안의 부분 이동 | §5 R7 | +| `--no-checks` 의 오버플로는 랩어라운드 | §7.4 | +| `match`·`defer`·순회·`undefined` 등 작은 규칙 일곱 | §7.7 | +| 제네릭은 모노모피제이션 | §9 | + +## 정해진 것 — 구현 + +`SPEC.md` 에 없는 것들. **여기가 출처다.** + +| | | 사는 곳 | +|---|---|---| +| 덩어리 전달 | 전부 주소로. ISA 마다 다른 구조체 전달 ABI 를 피해감 | `lower.c` | +| 슬라이스 배치 | 포인터 다음 길이 | `lowerpri.h` 한 군데에만 | +| store 폭 | 값이 아니라 **자리**가 정한다 | `lowerstm.c` `store_into` | +| 트랩 | `fe_trap(reason, FE_FILE_n, line)`. 파일은 검사가 쓰인 유닛 | `x86.c`, `rt/start.asm` | +| 오류 코드 | 빌드 전체에서 모아 철자 순으로 1부터 | `lowerstm.c` | +| 레지스터 | ebx·esi·edi 를 블록 안에 머무는 임시값에. eax/ecx/edx 는 스크래치 | `x86.c` | +| 제네릭 인스턴스 | 짓는 중에는 배치하지 않는다. 멤버가 안 정해진 집합 타입은 굳지 않고 물러난다 | `types.c` `layout_type` | +| unsafe 예산 | `std.mem`/`std.sys` 밖은 0. 늘어나면 빌드가 실패한다 | `tests/run.py` | --- ## 파일 크기 규칙 **2,000 줄을 넘기지 않는다. 웬만하면 1,000 줄.** 넘으면 나눈다 -- `check.c` 는 -3,937 줄이었고 `lower.c` 는 1,913 줄이었다. 지금 가장 큰 것은 1,000 줄이다. +3,937 줄이었고 `lower.c` 는 1,913 줄이었다. 지금 가장 큰 것은 `checkcal.c` +1,044 줄이다. diff --git a/audits/2026-08-17-spec-fec-parser.md b/audits/2026-08-17-spec-fec-parser.md new file mode 100644 index 0000000..0a406b4 --- /dev/null +++ b/audits/2026-08-17-spec-fec-parser.md @@ -0,0 +1,25 @@ +# SPEC–fec parser audit + +- 날짜: 2026-08-17 +- 기준 커밋: `52aaff62e490e37a0995aaaf7cbda47cf98e54a7` +- 범위: `SPEC.md` §6과 `fec/src/lexer.c`, `fec/src/parser.c` + +## 현재 문제 + +| ID | SPEC | 현재 구현 | 재현 결과 | +|---|---|---|---| +| PARSE-01 | 최상위 `comptime if` 선언 허용 | `comptime if`는 문장에서만 처리 | 최상위 사용을 `expected declaration`으로 거부 | +| PARSE-02 | 타입 이름은 `ident ('.' ident)*` | 타입에서 점 하나만 처리 | `alpha.beta.Gamma`를 파싱하지 못함 | +| PARSE-03 | `catch` EBNF가 binding 없는 block과 binding 뒤 expression도 허용 | 짧은 `catch expr`과 `catch \|e\| block`만 처리 | 구현은 §4.6 설명과 맞고 §6.1 EBNF가 지나치게 넓음 | +| PARSE-04 | `\|`와 `^`는 같은 우선순위, 단항 비트 NOT은 `~` | 각각 우선순위 5와 6, `~` 토큰 없음, 단항 `^` 허용 | `~x`를 거부하고 `a \| b ^ c`를 `a \| (b ^ c)`로 파싱 | +| PARSE-05 | 전역 `static`과 `var`의 타입 필수 | 타입 표기를 선택적으로 처리하고 초기값에서 추론 | `static A = 1;`, `var B = 2;` 모두 검사 통과 | +| PARSE-06 | error code는 정수 literal | 일반 expression을 파싱하며 literal이 아니면 code 검증을 건너뜀 | `error E { Bad = 1 + 2, }`가 검사 통과 | +| PARSE-07 | struct field와 enum vfield의 쉼표 필수 | 닫는 `}` 바로 앞에서는 쉼표 생략 허용 | `struct S { x: i32 }`가 검사 통과 | + +## 검증 + +- `uv run python tests/run.py`: `240/240` 통과 +- 위 항목의 최소 입력을 현재 `fec`에 직접 넣어 파싱 및 `--check` 결과를 확인함 +- 기존 fixture에는 위 괴리를 직접 고정하는 사례가 없음 + +이 문서는 조사 시점의 구현 상태를 기록한다. 언어 규칙의 기준은 `SPEC.md`다. diff --git a/fixture-report.md b/fixture-report.md deleted file mode 100644 index 0bec0ce..0000000 --- a/fixture-report.md +++ /dev/null @@ -1,597 +0,0 @@ -# 마커 없는 fixture 진단 증거 보고서 - -기준선: `uv run python tests/run.py` → `150/188 passed (58 pin a line and message)` - -이 보고서는 마커가 없는 37개 fixture를 직접 읽고, 기준선에서 새로 빌드된 -`.build/fec.exe --check`의 실제 진단을 확인한 결과다. 진단 전문의 색상 제어 문자는 -가독성을 위해 제거했으며, 텍스트·줄·열·진단 순서는 그대로 기록했다. - -## types - -### `fec/tests/types/bad_ari.fe` - -- 검사 대상: 함수 호출 인자 개수 불일치. -- 근거: `add`는 두 인자를 받지만 8행에서 `add(1)`로 한 인자만 전달한다. -- 실제 진단: - - ```text - fec/tests/types/bad_ari.fe:8:15: error: wrong number of arguments - 8 | return add(1); - | ^ - ``` - -- 일치 여부: 예 — 호출의 인자 개수 위반을 직접 진단한다. - -### `fec/tests/types/bad_asgn.fe` - -- 검사 대상: 불변 `let` 변수에 대입. -- 근거: 4행에서 `let value`로 선언한 뒤 5행에서 `value = 2`로 대입한다. -- 실제 진단: - - ```text - fec/tests/types/bad_asgn.fe:5:5: error: cannot assign to immutable let - 5 | value = 2; - | ^ - ``` - -- 일치 여부: 예 — `let`의 불변성 위반을 직접 진단한다. - -### `fec/tests/types/bad_cast.fe` - -- 검사 대상: 허용되지 않는 타입의 `as` 변환. -- 근거: 4행에서 `bool` 값 `true`를 `i32`로 변환한다. -- 실제 진단: - - ```text - fec/tests/types/bad_cast.fe:4:23: error: 'as' requires integer or char types - 4 | let x: i32 = true as i32; - | ^ - ``` - -- 일치 여부: 예 — 정수/문자가 아닌 피연산자의 캐스트를 직접 진단한다. - -### `fec/tests/types/bad_cond.fe` - -- 검사 대상: 조건식의 비-`bool` 값 사용. -- 근거: 4행의 `if 1`에서 정수 리터럴을 조건으로 사용한다. -- 실제 진단: - - ```text - fec/tests/types/bad_cond.fe:4:5: error: if condition must be bool - 4 | if 1 { return 0; } - | ^ - ``` - -- 일치 여부: 예 — 조건식이 `bool`이어야 한다는 규칙을 직접 진단한다. - -### `fec/tests/types/bad_mlet.fe` - -- 검사 대상: `let`으로 mutable slice를 바인딩. -- 근거: 4행에서 mutable 배열 slice를 만든 뒤 5행의 `let s: []mut u8`에 바인딩한다. -- 실제 진단: - - ```text - fec/tests/types/bad_mlet.fe:5:5: error: let cannot bind a mutable slice - 5 | let s: []mut u8 = raw[..]; - | ^ - ``` - -- 일치 여부: 예 — mutable slice의 `let` 바인딩 금지를 직접 진단한다. - -### `fec/tests/types/bad_ret.fe` - -- 검사 대상: 반환식과 함수 반환 타입의 불일치. -- 근거: `main`은 `i32`를 반환한다고 선언했지만 4행에서 `true`를 반환한다. -- 실제 진단: - - ```text - fec/tests/types/bad_ret.fe:4:5: error: return type mismatch - 4 | return true; - | ^ - ``` - -- 일치 여부: 예 — 반환 타입 불일치를 직접 진단한다. - -### `fec/tests/types/bad_shwr.fe` - -- 검사 대상: shared slice를 통한 쓰기. -- 근거: `s`는 `[]u8` shared slice인데 4행에서 `s[0] = 1`로 쓴다. -- 실제 진단: - - ```text - fec/tests/types/bad_shwr.fe:4:6: error: cannot write through shared slice - 4 | s[0] = 1; - | ^ - ``` - -- 일치 여부: 예 — shared slice 쓰기 위반을 직접 진단한다. - -### `fec/tests/types/bad_type.fe` - -- 검사 대상: 함수 인자 타입 불일치. -- 근거: `add`의 첫 인자는 `i32`인데 8행에서 `true`를 전달한다. -- 실제 진단: - - ```text - fec/tests/types/bad_type.fe:8:16: error: argument type mismatch - 8 | return add(true, 1); - | ^ - ``` - -- 일치 여부: 예 — 함수 인자의 타입 불일치를 직접 진단한다. - -### `fec/tests/types/bad_unit.fe` - -- 검사 대상: 초기화되지 않은 지역 변수 사용. -- 근거: 4행에서 `var value: i32`만 선언하고 값을 넣지 않은 채 5행에서 반환한다. -- 실제 진단: - - ```text - fec/tests/types/bad_unit.fe:5:12: error: use of uninitialized variable - 5 | return value; - | ^ - ``` - -- 일치 여부: 예 — 초기화되지 않은 변수 사용을 직접 진단한다. - -### `fec/tests/types/bad_unk.fe` - -- 검사 대상: 정의되지 않은 이름 사용. -- 근거: 4행에서 선언되지 않은 `missing_name`을 반환한다. -- 실제 진단: - - ```text - fec/tests/types/bad_unk.fe:4:12: error: unknown name - 4 | return missing_name; - | ^ - ``` - -- 일치 여부: 예 — 미정의 이름 사용을 직접 진단한다. - -### `fec/tests/types/bad_void.fe` - -- 검사 대상: `void` 표현식을 값 변수의 초기화식으로 사용. -- 근거: 반환값이 없는 `noop()`의 결과를 8행에서 `i32` 변수에 넣는다. -- 실제 진단: - - ```text - fec/tests/types/bad_void.fe:8:5: error: initializer type mismatch - 8 | let value: i32 = noop(); - | ^ - fec/tests/types/bad_void.fe:8:5: error: void expression cannot initialize a variable - 8 | let value: i32 = noop(); - | ^ - ``` - -- 일치 여부: 예 — void 표현식의 값 초기화 사용을 직접 진단한다. - -### `fec/tests/types/badarr.fe` - -- 검사 대상: 배열 리터럴의 원소 타입 및 선언된 배열 타입 불일치. -- 근거: `[2]i32`에 세 원소를 쓰고, 둘째 원소로 `bool`인 `true`를 넣는다. -- 실제 진단: - - ```text - fec/tests/types/badarr.fe:3:25: error: array element type mismatch - 3 | let a: [2]i32 = [1, true, 3]; - | ^ - fec/tests/types/badarr.fe:3:5: error: initializer type mismatch - 3 | let a: [2]i32 = [1, true, 3]; - | ^ - ``` - -- 일치 여부: 예 — 배열 원소 타입 위반을 직접 진단하고, 선언 타입 불일치도 함께 진단한다. - -### `fec/tests/types/badchar.fe` - -- 검사 대상: 명시적 캐스트 없는 `char`와 `u8`의 대입. -- 근거: 4행에서 `char` 리터럴을 `u8` 변수에 직접 넣는다. -- 실제 진단: - - ```text - fec/tests/types/badchar.fe:4:5: error: initializer type mismatch - 4 | let u: u8 = 'A'; - | ^ - fec/tests/types/badchar.fe:5:5: error: return type mismatch - 5 | return u; - | ^ - ``` - -- 일치 여부: 예 — 첫 진단이 char/u8 직접 대입의 타입 불일치를 짚는다. 5행 진단은 연쇄 오류다. - -### `fec/tests/types/badcycle.fe` - -- 검사 대상: 값으로 연결된 재귀 구조체 타입. -- 근거: `A`가 `B`를 값으로 포함하고 `B`가 다시 `A`를 값으로 포함한다. -- 실제 진단: - - ```text - fec/tests/types/badcycle.fe:1:1: error: by-value recursive type - 1 | unit badcycle; - | ^ - ``` - -- 일치 여부: 예 — 값 기반 재귀 타입을 직접 진단한다. 위치가 선언부 첫 줄로 올라가지만 진단 종류는 정확하다. - -### `fec/tests/types/badfield.fe` - -- 검사 대상: 불변 구조체 값의 필드에 대입. -- 근거: `p`는 `let`으로 선언됐고 6행에서 `p.x = 3`을 수행한다. -- 실제 진단: - - ```text - fec/tests/types/badfield.fe:6:6: error: cannot assign through immutable value - 6 | p.x = 3; - | ^ - ``` - -- 일치 여부: 예 — 불변 값의 projection을 통한 쓰기를 직접 진단한다. - -### `fec/tests/types/badfld.fe` - -- 검사 대상: 구조체 초기화 필드 누락과 존재하지 않는 필드 접근. -- 근거: `Point`는 `x`, `y`를 요구하지만 4행 초기화에는 `x`만 있고, 5행에서 없는 `z`에 접근한다. -- 실제 진단: - - ```text - fec/tests/types/badfld.fe:4:20: error: missing struct field - 4 | let p: Point = Point{ x: 1 }; - | ^ - fec/tests/types/badfld.fe:5:13: error: unknown struct field - 5 | return p.z; - | ^ - ``` - -- 일치 여부: 예 — 두 필드 규칙 위반을 모두 직접 진단한다. - -### `fec/tests/types/badindex.fe` - -- 검사 대상: 불변 배열을 통한 요소 쓰기. -- 근거: `a`는 `let` 배열인데 5행에서 `a[0] = 3`을 수행한다. -- 실제 진단: - - ```text - fec/tests/types/badindex.fe:5:6: error: cannot assign through immutable value - 5 | a[0] = 3; - | ^ - ``` - -- 일치 여부: 예 — 불변 배열 index projection을 통한 쓰기를 직접 진단한다. - -### `fec/tests/types/badmat.fe` - -- 검사 대상: 비-완전 `match`. -- 근거: `Shape`에는 `Empty`, `Circle` 두 variant가 있는데 4행 match에는 `Empty`만 있다. -- 실제 진단: - - ```text - fec/tests/types/badmat.fe:4:5: error: non-exhaustive match - 4 | match Shape.Empty { Empty => 0; } - | ^ - ``` - -- 일치 여부: 예 — match의 비-완전성을 직접 진단한다. - -### `fec/tests/types/badstr.fe` - -- 검사 대상: shared string/slice를 통한 쓰기. -- 근거: `str`인 `text`의 4행에서 인덱스 요소에 대입한다. -- 실제 진단: - - ```text - fec/tests/types/badstr.fe:4:9: error: cannot write through shared slice - 4 | text[0] = 'z'; - | ^ - fec/tests/types/badstr.fe:4:13: error: assignment type mismatch - 4 | text[0] = 'z'; - | ^ - ``` - -- 일치 여부: 예 — 첫 진단이 shared string 쓰기를 직접 짚고, 두 번째는 요소 타입의 연쇄 진단이다. - -## format - -### `fec/tests/format/bad_ari.fe` - -- 검사 대상: format placeholder와 인자 개수 불일치. -- 근거: 4행의 format 문자열에는 `{}`가 두 개지만 인자는 `1` 하나다. -- 실제 진단: - - ```text - fec/tests/format/bad_ari.fe:4:6: error: format argument count mismatch - 4 | @print("{} {}", 1); - | ^ - fec/tests/format/bad_ari.fe:4:6: error: format argument count mismatch - 4 | @print("{} {}", 1); - | ^ - ``` - -- 일치 여부: 예 — 인자 개수 불일치를 직접 진단한다. 동일 진단이 중복 출력된다. - -### `fec/tests/format/bad_bufw.fe` - -- 검사 대상: `io.buf_writer(buf)`를 통한 buffer writer 구성. -- 근거: `[]mut u8` 버퍼를 `io.buf_writer`에 전달하지만, 이 호출이 정확히 어떤 금지 규칙을 의도하는지는 파일만으로 확정하기 어렵다. 명세의 `io.Writer`는 enum handle이며 `io.buf_writer` API는 정의되어 있지 않다. -- 실제 진단: - - ```text - fec/tests/format/bad_bufw.fe:6:13: error: unknown name - 6 | let w = io.buf_writer(buf); - | ^ - fec/tests/format/bad_bufw.fe:6:26: error: invalid enum variant constructor - 6 | let w = io.buf_writer(buf); - | ^ - ``` - -- 일치 여부: 애매 — 존재하지 않는 `io.buf_writer`를 거부한다는 점은 맞지만, fixture가 검사하려는 구체적인 buffer-writer 규칙을 진단한 것인지 코드만으로 판정할 수 없다. - -### `fec/tests/format/bad_cls.fe` - -- 검사 대상: 닫히지 않은 placeholder가 아니라 unmatched `}` 형식 오류. -- 근거: 4행의 format 문자열이 단독 `}`를 포함한다. -- 실제 진단: - - ```text - fec/tests/format/bad_cls.fe:4:6: error: unmatched '}' in format - 4 | @print("}", 1); - | ^ - fec/tests/format/bad_cls.fe:4:6: error: format argument count mismatch - 4 | @print("}", 1); - | ^ - ``` - -- 일치 여부: 예 — 첫 진단이 unmatched `}`를 직접 짚는다. 두 번째는 파생된 개수 진단이다. - -### `fec/tests/format/bad_many.fe` - -- 검사 대상: placeholder보다 많은 format 인자. -- 근거: 문자열에는 `{}` 하나뿐인데 4행에서 `1, 2` 두 인자를 전달한다. -- 실제 진단: - - ```text - fec/tests/format/bad_many.fe:4:6: error: format argument count mismatch - 4 | @print("{}", 1, 2); - | ^ - ``` - -- 일치 여부: 예 — format 인자 개수 불일치를 직접 진단한다. - -### `fec/tests/format/bad_open.fe` - -- 검사 대상: 닫히지 않은 format placeholder. -- 근거: 4행의 문자열에 여는 `{`만 있고 닫는 `}`가 없다. -- 실제 진단: - - ```text - fec/tests/format/bad_open.fe:4:6: error: unterminated format placeholder - 4 | @print("{", 1); - | ^ - fec/tests/format/bad_open.fe:4:6: error: format argument count mismatch - 4 | @print("{", 1); - | ^ - ``` - -- 일치 여부: 예 — 첫 진단이 종료되지 않은 placeholder를 직접 짚는다. 두 번째는 파생된 개수 진단이다. - -### `fec/tests/format/bad_run.fe` - -- 검사 대상: 런타임 문자열을 format 문자열로 사용. -- 근거: 4행에서 `fmt`를 `var str`로 선언하고 5행에서 `@print(fmt, 1)`에 전달한다. -- 실제 진단: - - ```text - fec/tests/format/bad_run.fe:5:6: error: format must be a comptime string - 5 | @print(fmt, 1); - | ^ - ``` - -- 일치 여부: 예 — format 문자열의 comptime 제약을 직접 진단한다. - -### `fec/tests/format/bad_try.fe` - -- 검사 대상: 오류 결과가 아닌 `@print`에 `try` 사용. -- 근거: 명세상 `@print`은 `void`를 반환하는데 4행에서 `try @print(...)`을 쓴다. -- 실제 진단: - - ```text - fec/tests/format/bad_try.fe:4:5: error: try requires an error result - 4 | try @print("nope"); - | ^ - ``` - -- 일치 여부: 예 — `try`의 오류 결과 요구를 직접 진단한다. - -### `fec/tests/format/bad_type.fe` - -- 검사 대상: format writer가 없는 타입을 format 인자로 사용. -- 근거: `Point` 구조체 값을 7행에서 `{}` placeholder의 인자로 전달한다. -- 실제 진단: - - ```text - fec/tests/format/bad_type.fe:7:18: error: no fmt writer for argument type - 7 | @print("{}", p); - | ^ - ``` - -- 일치 여부: 예 — 해당 타입을 포맷할 writer가 없음을 직접 진단한다. - -### `fec/tests/format/bad_verb.fe` - -- 검사 대상: 지원되지 않는 format verb. -- 근거: 4행의 `{q}`에서 `q`는 명세에 없는 verb다. -- 실제 진단: - - ```text - fec/tests/format/bad_verb.fe:4:6: error: unsupported format verb - 4 | @print("{q}", 1); - | ^ - ``` - -- 일치 여부: 예 — 지원되지 않는 verb를 직접 진단한다. - -### `fec/tests/format/bad_writ.fe` - -- 검사 대상: `@fprint` 첫 인자의 `io.Writer` 타입 위반. -- 근거: 5행에서 writer 대신 `i32` 변수 `x`를 첫 인자로 전달한다. -- 실제 진단: - - ```text - fec/tests/format/bad_writ.fe:5:13: error: @fprint requires io.Writer - 5 | @fprint(x, "bad"); - | ^ - ``` - -- 일치 여부: 예 — `@fprint`의 writer 요구를 직접 진단한다. - -## own - -### `fec/tests/own/bad_clos.fe` - -- 검사 대상: 소유 값을 close 후 다시 사용. -- 근거: 11행의 `try file.close()`가 `file`을 이동시키고 12행에서 다시 `file.close()`를 호출한다. -- 실제 진단: - - ```text - fec/tests/own/bad_clos.fe:12:5: error: use of moved value - 12 | file.close(); - | ^ - fec/tests/own/bad_clos.fe:11:9: note: value was moved here - 11 | try file.close(); - | ^ - fec/tests/own/bad_clos.fe:12:5: error: use of moved value - 12 | file.close(); - | ^ - fec/tests/own/bad_clos.fe:11:9: note: value was moved here - 11 | try file.close(); - | ^ - ``` - -- 일치 여부: 예 — 이동된 값을 재사용한 위치와 이동 위치를 직접 진단한다. 동일 진단이 중복 출력된다. - -### `fec/tests/own/bad_cond.fe` - -- 검사 대상: 조건부 이동 후 값의 무조건 사용. -- 근거: 6행의 조건 분기 안에서 `take(p)`가 `p`를 이동할 수 있고 7행에서 무조건 `p`를 쓴다. -- 실제 진단: - - ```text - fec/tests/own/bad_cond.fe:7:5: error: use of possibly moved value - 7 | p.^ = 3; - | ^ - fec/tests/own/bad_cond.fe:7:5: error: use of possibly moved value - 7 | p.^ = 3; - | ^ - ``` - -- 일치 여부: 예 — 조건부 이동 가능성을 직접 진단한다. 동일 진단이 중복 출력된다. - -### `fec/tests/own/bad_dbl.fe` - -- 검사 대상: 소유 포인터의 이중 destroy. -- 근거: 4행에서 `p`를 destroy한 뒤 5행에서 같은 `p`를 다시 destroy한다. -- 실제 진단: - - ```text - fec/tests/own/bad_dbl.fe:5:17: error: use of moved value - 5 | mem.destroy(p); - | ^ - fec/tests/own/bad_dbl.fe:4:17: note: value was moved here - 4 | mem.destroy(p); - | ^ - fec/tests/own/bad_dbl.fe:5:17: error: use of moved value - 5 | mem.destroy(p); - | ^ - fec/tests/own/bad_dbl.fe:4:17: note: value was moved here - 4 | mem.destroy(p); - | ^ - ``` - -- 일치 여부: 예 — 두 번째 destroy의 이동 후 사용을 직접 진단한다. 동일 진단이 중복 출력된다. - -### `fec/tests/own/bad_dest.fe` - -- 검사 대상: owned pointer가 아닌 값을 `mem.destroy`에 전달. -- 근거: 4행에서 일반 `i32` 값 `x`를 `mem.destroy(x)`에 전달한다. -- 실제 진단: - - ```text - fec/tests/own/bad_dest.fe:4:16: error: mem.destroy requires exactly one owned pointer - 4 | mem.destroy(x); - | ^ - ``` - -- 일치 여부: 예 — `mem.destroy`의 owned pointer 요구를 직접 진단한다. - -### `fec/tests/own/bad_drop.fe` - -- 검사 대상: 사용자 `drop` 메서드의 직접 호출. -- 근거: `Box`에 `drop`을 정의했지만 10행에서 `b.drop()`으로 직접 호출한다. -- 실제 진단: - - ```text - fec/tests/own/bad_drop.fe:10:11: error: drop may only be invoked by scope cleanup - 10 | b.drop(); - | ^ - ``` - -- 일치 여부: 예 — drop은 scope cleanup에서만 호출된다는 규칙을 직접 진단한다. - -### `fec/tests/own/bad_loop.fe` - -- 검사 대상: 반복문 안의 이동으로 인한 possibly-moved 값 사용. -- 근거: 6행의 `while` 본문에서 매 반복 `take(p)`가 `p`를 이동할 수 있다. -- 실제 진단: - - ```text - fec/tests/own/bad_loop.fe:6:24: error: use of possibly moved value - 6 | while again { take(p); } - | ^ - fec/tests/own/bad_loop.fe:6:24: error: use of possibly moved value - 6 | while again { take(p); } - | ^ - ``` - -- 일치 여부: 예 — 반복에 따른 possibly-moved 상태를 직접 진단한다. 동일 진단이 중복 출력된다. - -### `fec/tests/own/bad_move.fe` - -- 검사 대상: 함수 인자의 이중 이동. -- 근거: 6행의 첫 `take(p)`가 `p`를 이동한 뒤 7행에서 다시 `take(p)`를 호출한다. -- 실제 진단: - - ```text - fec/tests/own/bad_move.fe:7:10: error: use of moved value - 7 | take(p); - | ^ - fec/tests/own/bad_move.fe:6:10: note: value was moved here - 6 | take(p); - | ^ - fec/tests/own/bad_move.fe:7:10: error: use of moved value - 7 | take(p); - | ^ - fec/tests/own/bad_move.fe:6:10: note: value was moved here - 6 | take(p); - | ^ - ``` - -- 일치 여부: 예 — 이동된 인자의 재사용과 최초 이동 위치를 직접 진단한다. 동일 진단이 중복 출력된다. - -### `fec/tests/own/bad_proj.fe` - -- 검사 대상: 구조체 projection에서 non-Copy 소유 필드 이동. -- 근거: `Holder.p`는 owned pointer이고 7행에서 `take(h.p)`로 필드 밖으로 직접 이동하려 한다. -- 실제 진단: - - ```text - fec/tests/own/bad_proj.fe:7:11: error: cannot move a non-Copy value out of a projection; use mem.replace - 7 | take(h.p); - | ^ - ``` - -- 일치 여부: 예 — projection에서 non-Copy 값을 이동할 수 없다는 규칙과 대안을 직접 진단한다. - -## 판정 요약 - -- `아니오`: 없음. -- `애매`: `fec/tests/format/bad_bufw.fe` — `io.buf_writer` 자체가 명세에 없으므로 fixture의 구체적 의도를 확정할 수 없음. -- 나머지 36개: 실제 진단이 코드가 검사하려는 규칙과 일치.