GOAL P0-1: 니치 옵셔널이 포인터 대신 포인터가 든 자리를 넘겼다

?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.
This commit is contained in:
2026-08-17 15:56:48 +09:00
parent 51f555a830
commit dacf1e1b1b
3 changed files with 129 additions and 4 deletions
+22
View File
@@ -78,6 +78,28 @@ Slot lower_expr_core(Lower *L, FeNode *n)
if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or")))
return lower_logical(L, n, !strcmp(n->text, "and"));
op = binary_op(n->text, &is_cmp);
/* Comparing an optional with `null` asks about its tag, not about the
bytes of the whole wrapper -- which has no value form at all. */
if ((op == FE_IR_EQ || op == FE_IR_NE) && n->a && n->b) {
FeNode *w = fe_m7_is_null(n->b) ? n->a :
(fe_m7_is_null(n->a) ? n->b : 0);
FeType *wt = w ? w->sem_type : 0;
if (wt && wt->kind == FE_TYPE_OPTIONAL) {
Slot s = lower_expr(L, w);
unsigned t0;
unsigned z;
if (!s.is_place) {
fail(L, "an optional with no place", w);
return slot_void();
}
t0 = wrapper_tag(L, s, wt, w);
z = fe_ir_const(L->m, L->b,
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, 0);
return slot_value(fe_ir_binary(L->m, L->b, op,
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, t0, z, 1),
FE_IR_I8);
}
}
operand = ir_type(n->a ? n->a->sem_type : 0);
if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32;
a = as_value(L, lower_expr(L, n->a), n->a);
+14 -4
View File
@@ -344,10 +344,20 @@ void lower_if_let(Lower *L, FeNode *n)
FeType *bt = binding->sem_type;
Slot payload = wrapper_payload(L, value, opt);
unsigned local = declare_var(L, binding->cname, bt, binding->text);
if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW))
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0),
as_address(L, payload, n), FE_IR_PTR);
else
if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW)) {
/* The binding is a reference either way, but for two different
reasons. When the payload is itself a single pointer (`^T`,
`&T`) the binding *is* that pointer, so it has to be read out.
When the payload is a value the binding points at where it sits
inside the wrapper, so the address is what is wanted. Taking the
address in the first case gives a pointer to the pointer, and
the program reads an address where it expects a value. */
FeType *pl = opt ? (opt->kind == FE_TYPE_ERROR_UNION
? opt->error_value : opt->elem) : 0;
unsigned p = pl && ir_type(pl) == FE_IR_PTR
? as_value(L, payload, n) : as_address(L, payload, n);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), p, FE_IR_PTR);
} else
store_into(L, fe_ir_at_local(local, 0), payload, n, ir_size(bt));
}
lower_stmt(L, n->b);