GOAL P1-1..1-3: 리터럴이 자기 타입에 안 맞으면 거부한다

그리고 그 자리를 파다가 더 나쁜 것이 나왔다.

store 의 폭이 목적지가 아니라 값에서 왔다. 정수 리터럴은 더 좁은 것이
요구하기 전까지 i32 이므로 let b: u8 = 200; 은 4바이트가 1바이트 자리로
가는 것으로 도착하고, 4바이트를 쓰면 프레임이 그 옆에 놓은 것을 지운다.

  let a: i32 = 5;  let b: u8 = 300;  let d: u8 = 44;
  a 0 / b 0 / d 44        →   a 5 / b 44 / d 44

폭 넓은 지역 하나만 있으면 드러나지 않아서 여태 살아 있었다. exec/narrow.fe
가 폭이 섞인 지역을 나란히 두어 고정한다.

규칙 자체는 SPEC §3 에 넣었다: 리터럴의 타입은 문맥이 요구하는 정수 타입이고,
없으면 i32 다. 범위를 벗어나면 잘리는 것이 아니라 거부된다. 앞의 단항 -
는 리터럴의 일부로 보아 i8 = -128 은 되고 u8 = -1 은 안 된다.

같이 넣은 문장 둘:
- §9 미사용 타입 파라미터는 정상이다. typed handle 이 그 모양이고 구현은
  이미 그렇게 동작했다.
- §7.4 --no-checks 에서 오버플로는 랩어라운드로 정의된다. 타깃이 실제로
  하는 일이고 미정의로 두지 않는다.

237/237, 35/35.
This commit is contained in:
2026-08-17 16:07:45 +09:00
parent 32da64d7c1
commit 1a368dc13f
8 changed files with 152 additions and 247 deletions
+71
View File
@@ -549,6 +549,62 @@ int m7_actual_compatible(FeType *want, FeType *got, FeNode *value)
return compatible(want,got,value);
}
/* The magnitude an integer literal spells, ignoring any sign. The same shape
lowering uses on the same text, so the two cannot disagree about what was
written. */
static unsigned long literal_magnitude(const char *s)
{
unsigned long v = 0;
if (!s) return 0;
if (s[0]=='0' && (s[1]=='x' || s[1]=='X')) {
for (s += 2; *s; ++s) {
int d = *s>='0'&&*s<='9' ? *s-'0' :
*s>='a'&&*s<='f' ? *s-'a'+10 :
*s>='A'&&*s<='F' ? *s-'A'+10 : -1;
if (d < 0) { if (*s=='_') continue; break; }
v = v*16UL + (unsigned long)d;
}
return v;
}
for (; *s; ++s) {
if (*s=='_') continue;
if (*s<'0' || *s>'9') break;
v = v*10UL + (unsigned long)(*s-'0');
}
return v;
}
/* SPEC 4.1: an integer literal takes the type its context asks for, and a
value that does not fit that type is a mistake where it is written rather
than a truncation nobody sees. */
static int literal_fits(const FeType *want, const char *text, int negative)
{
unsigned long v;
unsigned long limit;
unsigned bits;
if (!want || want->kind != FE_TYPE_INT || !text) return 1;
bits = want->bits ? want->bits : 32U;
if (bits > 32U) bits = 32U;
v = literal_magnitude(text);
if (want->is_unsigned) {
if (negative) return v == 0UL;
if (bits >= 32U) return 1;
return v <= (1UL << bits) - 1UL;
}
limit = bits >= 32U ? 2147483647UL : (1UL << (bits - 1U)) - 1UL;
return v <= (negative ? limit + 1UL : limit);
}
/* Is this node a plain integer literal, rather than a character, a string, or
one of the word-shaped literals? */
static int plain_int_literal(const FeNode *n)
{
return n && n->kind==FE_N_LITERAL && n->text &&
n->text[0]!='\'' && n->text[0]!='"' &&
strcmp(n->text,"true") && strcmp(n->text,"false") &&
strcmp(n->text,"null") && strcmp(n->text,"undefined");
}
FeType *m7_check_expected(FeCheckerState *s, FeNode *value,
FeType *expected)
{
@@ -573,6 +629,21 @@ FeType *m7_check_expected(FeCheckerState *s, FeNode *value,
value->sem_context=expected;
return expected;
}
/* An integer literal is `i32` on its own; where an integer type is asked
for it is that type instead, and it has to fit in it. */
if (expected && expected->kind==FE_TYPE_INT) {
FeNode *lit = plain_int_literal(value) ? value :
(value->kind==FE_N_UNARY && value->text &&
!strcmp(value->text,"-") && plain_int_literal(value->a)
? value->a : 0);
if (lit) {
if (!literal_fits(expected, lit->text, lit!=value))
err(s->c,value->loc,"integer literal out of range for its type");
lit->sem_type=expected;
value->sem_type=expected;
return expected;
}
}
actual=check_expr(s,value);
if (!expected) return actual;
if (expected->kind==FE_TYPE_OPTIONAL && expected->elem &&
+7 -1
View File
@@ -8,7 +8,13 @@ void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n,
fe_ir_copy(L->m, L->b, dst, value.place, size);
return;
}
fe_ir_store(L->m, L->b, dst, as_value(L, value, n), value.type);
/* How wide the store is belongs to the place, not to the value. An
integer literal is `i32` until something narrower asks for it, so
`let b: u8 = 200;` arrives here as four bytes going into one -- and
writing four wipes out whatever the frame put next to it. */
fe_ir_store(L->m, L->b, dst, as_value(L, value, n),
size == 1UL ? FE_IR_I8 :
size == 2UL ? FE_IR_I16 : value.type);
}
void lower_return(Lower *L, FeNode *n)
+32
View File
@@ -0,0 +1,32 @@
// EXIT:0
// OUTPUT:a 5 b 200 c 44 d 9
// OUTPUT:e 5 f 1 g 65535
// OUTPUT:sum 253
unit narrow;
import std.io;
// How wide a store is belongs to the place, not to the value. An integer
// literal is `i32` until something narrower asks for it, so `let b: u8 = 200;`
// arrives at the store as four bytes going into one -- and writing four wipes
// out whatever the frame put beside it.
//
// Several locals of mixed width, next to each other, is what it takes to see
// it: each narrow store used to reach back over the one declared before it.
fn main() -> i32 {
let a: i32 = 5;
let b: u8 = 200;
let c: u8 = 44;
let d: i16 = 9;
@print("a {} b {} c {} d {}\n", a, b, c, d);
let e = 5; // no annotation: i32 (SPEC 4.1)
let f: i8 = 1;
let g: u16 = 65535;
@print("e {} f {} g {}\n", e, f as i32, g);
// And the values are still there after everything else was written.
@print("sum {}\n", (b as i32) + (c as i32) + (d as i32) + e - (a as i32));
return 0;
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:8:integer literal out of range
unit badlit;
// SPEC 4.1: an integer literal takes the type its context asks for. 300 is not
// a u8, and truncating it silently is the one thing that must not happen.
fn bad() -> u8 {
let n: u8 = 300;
return n;
}
+8
View File
@@ -0,0 +1,8 @@
// ERROR:6:integer literal out of range
unit badlitng;
// An unsigned type has no negative values to truncate to.
fn bad() -> u8 {
let n: u8 = -1;
return n;
}
+17
View File
@@ -0,0 +1,17 @@
unit oklit;
// The edges of each type are in range, and a literal with no context is i32.
fn ok() -> i32 {
let a: u8 = 255;
let b: i8 = -128;
let c: i8 = 127;
let d: u16 = 65535;
let e: i32 = 2147483647;
let f: i32 = -2147483648;
let g: u8 = 0xFF;
let h = 5;
let i: usize = 4294967295;
return h + (a as i32) + (b as i32) + (c as i32) + (d as i32) +
(e - e) + (f - f) + (g as i32) + (i as i32);
}