?T 의 페이로드가 null 이 될 수 없으면 태그를 따로 두지 않고 그 불가능한 값을
null 로 쓴다. ?^T 와 ?&T 가 그렇다. 그런데 if let 이 그것을 풀 때 두 경우를
한 갈래로 처리하고 있었다.
바인딩이 참조인 이유가 둘이다. 페이로드가 값이면 바인딩은 그것이 래퍼 안에
앉은 자리를 가리켜야 하고(주소), 페이로드가 이미 포인터면 바인딩은 그
포인터여야 한다(값). 후자에 주소를 쓰면 포인터의 포인터가 되고, 프로그램은
값이 있어야 할 자리에서 주소를 읽는다. 컴파일도 되고 실행도 됐다.
?i32 5 (맞았음 -- 태그가 있어서 다른 길로 갔다)
?^i32 6125480 → 5
?&i32 6125496 → 5
그리고 옵셔널을 null 과 비교하는 것이 lowering 되지 않았다 -- 래퍼 전체를
값으로 읽으려 해서 'cannot lower an aggregate as a value' 였다. 태그만 보면
되는 질문이다. optional/oknull.fe 가 검사만 하는 fixture 라 드러나지 않았다.
exec/optref.fe 가 세 모양을 전부 고정한다: if let, orelse, .?, == null,
그리고 R7 관용구인 mem.replace(&mut box, null).? 로 소유자를 꺼내 놓는 것까지.
229/229, 33/33.
구조체도 필드를 n 이라 부를 수 있다. lowering 은 이름만 보고 슬라이스 길이
자리(포인터 다음 4바이트)를 읽어서, 그 자리에 있던 그럴듯한 숫자를 돌려주고
있었다. 체커는 제대로 필드로 풀고 있었으니 같은 함수 안에서 쓰기와 읽기가
어긋났다.
Box{ room: ^[]mut u8, n: usize, m: usize }
n 777 m 999 (전에는 n 12 -- room 의 길이)
배열/슬라이스/str 일 때만 길이로 읽는다. fieldn.fe 가 이것과, 옆의 슬라이스가
여전히 길이로 답하는 것을 함께 고정한다.
222/222, 30/30.
렉서 다음은 파서다. 노드는 한 배열에 살고 자식은 그 안의 인덱스다 -- 노드는
^Node 를 들 수 없고(여럿이며 한 번씩 소유하지 않는다) &Node 도 들 수 없다(R4).
인덱스는 둘 다 아니다. 소스도 필드가 아니라 매 단계에 같이 다닌다.
unit demo / fn answer @2 / let n = (+ 1 (* 2 3)) / return n / balanced
전위 표기로 다시 찍는 것이 시험의 요점이다. 1 + 2 * 3 이 어떻게 묶였는지는
그렇게만 보인다.
쓰면서 나온 컴파일러 버그 넷:
1. 다른 유닛의 타입을 필드로 쓰면 그 필드 타입이 영영 UNKNOWN 이었다. 필드
해석이 유닛마다 선언 직후에 돌아서, 아직 선언되지 않은 유닛의 타입을 찾다
실패하고 그 답을 굳혔다. 이제 모든 유닛이 선언을 마친 뒤에 한 번 푼다.
2. 그리고 그 해석은 타입을 선언한 유닛에서 해야 한다. 필드 타입은 그 유닛의
import 로 쓰였는데 아무 유닛에서나 풀고 있었다. 타입 계층에 enter/leave
콜백을 두고 체커가 그 자리로 데려간다.
3. cycle_state 를 재귀 검사와 크기 계산이 같이 썼다. 첫 번째가 보는 중인 구조체
가 두 번째에게는 다 끝난 것으로 보여서, 필드가 하나뿐인 것처럼 1 바이트로
자리를 잡았다 -- Parser 가 그래서 자기 토큰을 밟았다. layout_state 로 나눴다.
4. 다른 유닛의 상수(ast.NONE)를 lowering 이 필드 접근으로 봤다. 체커가 이미
링크 이름을 붙여두었으니 그것이 있으면 전역이다.
그리고 R1 을 실제로 지키게 했다: 소유자를 놓으면 그것이 가진 것도 놓는다.
전에는 자기 drop 이 있거나 자기가 owned 일 때만이어서, drop 을 가진 타입을
필드로 담은 구조체는 그것을 놓을 방법이 없었다(drop 은 손으로 못 부른다).
이제 release_at 이 drop 을 부르고 필드로 내려간다. 그 덕에 List/Arena/Map 의
drop 이 전부 필요 없어져서 지웠다 -- 버퍼가 owned 이니 R1 이 알아서 한다.
221/221, 29/29.
AST 도 IR 도 본질이 태그드 유니온이다. 태그 비교만 되고 안을 꺼내지 못하면
검사 없이 필드를 읽어야 하고, 그러면 안전성 이야기가 통째로 무너진다.
무언가를 담는 변이는 메모리다: 태그가 앞, 페이로드가 뒤. 태그를 읽는 것은
어느 쪽이든 같은 질문이고 자리만 다르다. 페이로드는 가리키지 않고 복사한다 --
매치한 것의 소유권을 arm 이 가져가는 것이 보통이고, 그게 허용되는지는 검사기가
이미 판단했다.
레코드 변이의 필드 오프셋을 아무도 기록하지 않고 있었다. 레이아웃이 크기는
재면서 자리는 버렸다.
Enum.Variant{...} 와 Enum.Variant(x) 와 페이로드 없는 Enum.Variant 를 모두
만든다. 마지막 것은 페이로드를 가진 enum 안에서는 태그만 든 값이다.
some 7 / none / point 3 4 / empty
exec.py 25/25.
check.c 3,937 줄, lower.c 1,913 줄이었다. 가장 큰 파일이 978 줄이 됐다.
check.c 679 스코프·심볼·흐름·소유권 접착
checkexp.c 739 포매팅 검사와 표현식
checkstm.c 635 문장, 함수, 메서드
checkgen.c 618 제네릭 실체화
checkcal.c 978 유닛 경계 호출과 옵셔널/에러 유니온
checkpro.c 184 선언 패스와 프로그램
lower.c 567 타입·슬롯·지역·블록·mem.*
lowerprn.c 238 포매팅 빌트인 전개
lowerexp.c 468 표현식
lowerstm.c 543 문장·함수·프로그램
줄 범위로 잘랐다. 주제별로 묶는 것보다 정확한데, 한 줄도 잃거나 겹치지
않기 때문이다. 파일 순서가 이미 단계를 따라가서 경계가 실제 이음매에 떨어진다.
모든 정의가 static 을 잃고 비공개 헤더에 프로토타입을 갖는다. 대안 --
static 을 유지하고 #include 로 텍스트만 나누는 것 -- 은 결합을 보여주는 대신
숨긴다.
두 스위트 그대로: 209/209, 21/21.