check: widen const def-ref to declared int type in def init (#113)

A def initializer whose rhs references another def -- `def INT_MIN: int
= I32_MIN;`, `def SIZE_MAX: size = U64_MAX;` -- failed to compile: an
N_IDENT->SK_DEF types as the referent's DECLARED type (i32, u64), so the
def-init assignability check (type_assignable) rejected i32 -> int /
u64 -> size, even though the value is a compile-time constant that fits.
This blocked faithful types/types::c limit defs (no cast in the Hare
source).

In a def initializer the rhs is a flexible constant. When it folds to a
compile-time integer (the #88 eval_def_const path: sibling/imported def
refs, casts, arithmetic) and the value fits the declared integer target,
re-flexibilize it to UNTYPED_INT so the existing untyped-int->typed
assignability path accepts it. This emulates Hare's flexible-constant
promotion (ICONST -> promote_flexible/lower_flexible,
ref/harec/src/types.c:860); def_cast_fits is the range check that keeps a
genuine out-of-range narrowing a loud "not assignable" error, never a
silent truncation (rule 7). It is strictly the const subset: the general
CONCRETE (non-const) integer widening Hare does at types.c:1021-1037 is
intentionally stricter in ww -- #115.

cstage-only: the wwstage checker (selfhost/cmd/wcc/check.ww, "let init /
return assignability") intentionally never checks def-init assignability
(it stays quiet, leaving full inference to the C side), so it never
rejected the widening -- the #88 stamp already laid the correct DATA row.
Relaxing the cstage aligns the richer side DOWN to the leaner side
(rule 10); both stages stamp the identical folded value, so emitted asm
is byte-identical. The bootstrap corpus has zero cross-prim-width def-ref
defs, so the new path is dead there and 990-997 are unperturbed.

Coverage: test/wcc/760_def_widen_const (i32->int neg, u64->size, byte-id
on each, cstage-only out-of-range narrowing fail-loud).
This commit is contained in:
2026-05-26 02:39:35 +09:00
parent 9a265a31f5
commit 5e4d67d90a
3 changed files with 355 additions and 11 deletions

View File

@@ -2160,21 +2160,48 @@ check_file(Checker *c, Node *file)
case N_DEF: {
if (d->rhs) {
Type *rt = cexpr(c, d->rhs);
/* #88: fold sibling/imported def refs, casts, and
* arithmetic to a constant. litfold (plain literal
* leaf) is already typed UNTYPED_INT by cexpr;
* constfold (#88, gated on litfold missing) covers
* the richer shapes and is the one we stamp, so
* existing literal/unary defs keep their rhs node
* and the emitted bytes stay byte-identical. */
u64 dv;
int litfold = rt != ty_err
&& fold_int_literal(d->rhs, &dv);
int constfold = rt != ty_err && !litfold
&& eval_def_const(c, d->rhs, &dv, 0);
/* #113: a def-ref that folds to a compile-time
* constant (a def ref like `def INT_MIN: int =
* I32_MIN`) carries its referent's concrete declared
* type (i32), not UNTYPED_INT, so the assignability
* check below rejected i32 -> int. In a def
* initializer the rhs is a flexible constant, so
* re-flexibilize the folded value to UNTYPED_INT here
* when it fits the declared integer target — emulating
* Hare's flexible-constant promotion (ICONST ->
* promote_flexible/lower_flexible, range-checked:
* ref/harec/src/types.c:860, reached via the
* STORAGE_ICONST assignability case at :1012/:1019).
* ww has no ICONST flexible-range type; def_cast_fits
* is the range check that keeps a genuine out-of-range
* value a loud "not assignable" error, never a silent
* truncation (rule 7). This is strictly the const
* subset: the general CONCRETE (non-const) integer
* widening Hare does at ref/harec/src/types.c:1021-1037
* is intentionally stricter in ww, see #115. */
Type *art = rt;
if (constfold && d->type && type_isint(d->type)
&& type_isint(rt) && !type_isuntyped(rt)
&& def_cast_fits(d->type, dv))
art = ty_untyped_int;
if (d->type && rt != ty_err && d->type != ty_err
&& !type_assignable(d->type, rt))
&& !type_assignable(d->type, art))
err(c, d->pos, "def %s init %s not assignable to %s",
d->str, type_name(c->a, rt),
type_name(c->a, d->type));
/* #88: const-fold sibling/imported def refs,
* casts, and arithmetic so cgen's literal-only
* emit can lay down the DATA row. GATED on the
* plain literal fold missing first, so existing
* literal/unary defs keep their rhs node and the
* emitted bytes stay byte-identical. */
u64 dv;
if (rt != ty_err
&& !fold_int_literal(d->rhs, &dv)
&& eval_def_const(c, d->rhs, &dv, 0))
if (constfold)
stamp_intlit(c, d->rhs, dv);
}
break;