wcc/ww: reject reassignment of a const binding (catB-22)

The sym carried an is_const flag (lib/ww/sym.ww) but wwstage never
set it at the let-install nor consumed it at assignment, so mutating
a `const` slipped through silently. Mirror cstage's two sites: set
is_const when n.op == TK_CONST at the local let-install (cmd/wcc/
check.c:2408) and reject an N_ASSIGN whose lhs ident resolves to an
is_const sym (cmd/wcc/check.c:1889-1896). cstage already rejected;
this aligns wwstage's w6c_ww UP. A bare `_` discard lvalue (empty
str) is skipped.

Regen w6c/wwdump combined.ww (checker embeds in both). Valid-program
codegen unchanged → cs==ww byte-id gate stays green.
This commit is contained in:
2026-06-15 03:55:46 +09:00
parent d0adfe5aab
commit 5ae3787cb9
5 changed files with 249 additions and 3 deletions

View File

@@ -11247,7 +11247,14 @@ fn resolvewalk(c: *checker, n: *node) void = {
let nm: str = n.str;
if (nm.len > 0) {
checkmoduleshadow(c, nm, "let");
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
let s: *sym = scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
// catB-22: flag a `const` binding so checkassign can
// reject a later reassignment. The parser stamps
// n.op = TK_CONST (parse/stmt.ww). Mirror cstage
// cmd/wcc/check.c:2408.
if (s != nil) {
if (n.op == tkind.TK_CONST) { s.is_const = 1i32; };
};
};
};
@@ -15947,6 +15954,21 @@ fn checkassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) { return; };
if (n.rhs == nil) { return; };
// catB-22: reject reassigning a `const`-flagged binding (the
// is_const bit set at the let-install above). A bare `_` discard
// lvalue carries an empty str and is skipped. Mirror cstage
// cmd/wcc/check.c:1889-1896.
if (n.lhs.kind == nkind.N_IDENT) {
if (n.lhs.str.len > 0) {
let s: *sym = scopelookupprefer(c.cur, c.curmod, n.lhs.str);
if (s != nil) {
if (s.is_const != 0i32) {
cerr("error: cannot assign to const binding\n");
c.errs += 1;
};
};
};
};
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
// #29: a rune literal narrowing into an integer assign/index-store

View File

@@ -773,7 +773,14 @@ fn resolvewalk(c: *checker, n: *node) void = {
let nm: str = n.str;
if (nm.len > 0) {
checkmoduleshadow(c, nm, "let");
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
let s: *sym = scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
// catB-22: flag a `const` binding so checkassign can
// reject a later reassignment. The parser stamps
// n.op = TK_CONST (parse/stmt.ww). Mirror cstage
// cmd/wcc/check.c:2408.
if (s != nil) {
if (n.op == tkind.TK_CONST) { s.is_const = 1i32; };
};
};
};
@@ -5473,6 +5480,21 @@ fn checkassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) { return; };
if (n.rhs == nil) { return; };
// catB-22: reject reassigning a `const`-flagged binding (the
// is_const bit set at the let-install above). A bare `_` discard
// lvalue carries an empty str and is skipped. Mirror cstage
// cmd/wcc/check.c:1889-1896.
if (n.lhs.kind == nkind.N_IDENT) {
if (n.lhs.str.len > 0) {
let s: *sym = scopelookupprefer(c.cur, c.curmod, n.lhs.str);
if (s != nil) {
if (s.is_const != 0i32) {
cerr("error: cannot assign to const binding\n");
c.errs += 1;
};
};
};
};
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
// #29: a rune literal narrowing into an integer assign/index-store

View File

@@ -11247,7 +11247,14 @@ fn resolvewalk(c: *checker, n: *node) void = {
let nm: str = n.str;
if (nm.len > 0) {
checkmoduleshadow(c, nm, "let");
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
let s: *sym = scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
// catB-22: flag a `const` binding so checkassign can
// reject a later reassignment. The parser stamps
// n.op = TK_CONST (parse/stmt.ww). Mirror cstage
// cmd/wcc/check.c:2408.
if (s != nil) {
if (n.op == tkind.TK_CONST) { s.is_const = 1i32; };
};
};
};
@@ -15947,6 +15954,21 @@ fn checkassign(c: *checker, n: *node) void = {
if (n == nil) { return; };
if (n.lhs == nil) { return; };
if (n.rhs == nil) { return; };
// catB-22: reject reassigning a `const`-flagged binding (the
// is_const bit set at the let-install above). A bare `_` discard
// lvalue carries an empty str and is skipped. Mirror cstage
// cmd/wcc/check.c:1889-1896.
if (n.lhs.kind == nkind.N_IDENT) {
if (n.lhs.str.len > 0) {
let s: *sym = scopelookupprefer(c.cur, c.curmod, n.lhs.str);
if (s != nil) {
if (s.is_const != 0i32) {
cerr("error: cannot assign to const binding\n");
c.errs += 1;
};
};
};
};
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
// #29: a rune literal narrowing into an integer assign/index-store