wcc: forrange bindings get a per-loop scope; dup binders reject
The TODO(#11) silent-accept of `for (let (a, a) .. xs)` rested on a stale premise -- resolvewalk has per-block scopes since #53 and IS the live w6c_ww checker pass. Bindings now install in a per-LOOP scope (the N_MCASE pattern), so a duplicate name within one pattern errs "redeclared in same scope" (cstage parity via stamptuplebinds' scopedefine-nil check) while sequential same-name loops stay legal. kwtab restores its stated alphabetical order (`import` before `is`, both stages, kinds swapped in lockstep with names).
This commit is contained in:
@@ -24,8 +24,8 @@ static const struct kwent kwtab[] = {
|
|||||||
{ "fn", TK_FN },
|
{ "fn", TK_FN },
|
||||||
{ "for", TK_FOR },
|
{ "for", TK_FOR },
|
||||||
{ "if", TK_IF },
|
{ "if", TK_IF },
|
||||||
{ "is", TK_IS },
|
|
||||||
{ "import", TK_USE },
|
{ "import", TK_USE },
|
||||||
|
{ "is", TK_IS },
|
||||||
{ "let", TK_LET },
|
{ "let", TK_LET },
|
||||||
{ "match", TK_MATCH },
|
{ "match", TK_MATCH },
|
||||||
{ "nil", TK_NIL },
|
{ "nil", TK_NIL },
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package wwfixture;
|
package wwfixture;
|
||||||
|
|
||||||
def protocolversion: i32 = 1;
|
def protocolversion: i32 = 1;
|
||||||
def corpuscount: i32 = 1758;
|
def corpuscount: i32 = 1759;
|
||||||
def errorcount: i32 = 351;
|
def errorcount: i32 = 352;
|
||||||
def compilecount: i32 = 21;
|
def compilecount: i32 = 21;
|
||||||
def runcount: i32 = 209;
|
def runcount: i32 = 209;
|
||||||
def runexitcount: i32 = 1177;
|
def runexitcount: i32 = 1177;
|
||||||
def nativecount: i32 = 3516;
|
def nativecount: i32 = 3518;
|
||||||
def corpushash: str = "ab5ae796c10e476af0c1f40e0d0b216b3eb708f62b6ee17aafcf79d5e1dd8a69";
|
def corpushash: str = "38613db49b4c87a14d87fb9392e5500e17cc0c95767fb31d8eb9bc4fe2bf74c5";
|
||||||
|
|
||||||
type directive = enum i32 {
|
type directive = enum i32 {
|
||||||
ERROR = 0,
|
ERROR = 0,
|
||||||
|
|||||||
@@ -156,16 +156,16 @@ export type tok = struct {
|
|||||||
// (probe at cc69daf — len() returns 0, no diagnostic); filed bug.
|
// (probe at cc69daf — len() returns 0, no diagnostic); filed bug.
|
||||||
let kwnames: [30]str = [
|
let kwnames: [30]str = [
|
||||||
"as", "break", "case", "chan", "const", "continue", "def", "defer",
|
"as", "break", "case", "chan", "const", "continue", "def", "defer",
|
||||||
"else", "enum", "export", "false", "fn", "for", "if", "is",
|
"else", "enum", "export", "false", "fn", "for", "if", "import",
|
||||||
"import", "let", "match", "nil", "package", "proc", "return",
|
"is", "let", "match", "nil", "package", "proc", "return",
|
||||||
"static", "struct", "switch", "true", "type", "void", "yield",
|
"static", "struct", "switch", "true", "type", "void", "yield",
|
||||||
];
|
];
|
||||||
let kwkinds: [30]tkind = [
|
let kwkinds: [30]tkind = [
|
||||||
tkind.TK_AS, tkind.TK_BREAK, tkind.TK_CASE, tkind.TK_CHAN,
|
tkind.TK_AS, tkind.TK_BREAK, tkind.TK_CASE, tkind.TK_CHAN,
|
||||||
tkind.TK_CONST, tkind.TK_CONTINUE, tkind.TK_DEF, tkind.TK_DEFER,
|
tkind.TK_CONST, tkind.TK_CONTINUE, tkind.TK_DEF, tkind.TK_DEFER,
|
||||||
tkind.TK_ELSE, tkind.TK_ENUM, tkind.TK_EXPORT, tkind.TK_FALSE,
|
tkind.TK_ELSE, tkind.TK_ENUM, tkind.TK_EXPORT, tkind.TK_FALSE,
|
||||||
tkind.TK_FN, tkind.TK_FOR, tkind.TK_IF, tkind.TK_IS,
|
tkind.TK_FN, tkind.TK_FOR, tkind.TK_IF, tkind.TK_USE,
|
||||||
tkind.TK_USE, tkind.TK_LET, tkind.TK_MATCH, tkind.TK_NIL,
|
tkind.TK_IS, tkind.TK_LET, tkind.TK_MATCH, tkind.TK_NIL,
|
||||||
tkind.TK_MODULE, tkind.TK_PROC, tkind.TK_RETURN, tkind.TK_STATIC,
|
tkind.TK_MODULE, tkind.TK_PROC, tkind.TK_RETURN, tkind.TK_STATIC,
|
||||||
tkind.TK_STRUCT, tkind.TK_SWITCH, tkind.TK_TRUE, tkind.TK_TYPE,
|
tkind.TK_STRUCT, tkind.TK_SWITCH, tkind.TK_TRUE, tkind.TK_TYPE,
|
||||||
tkind.TK_VOID, tkind.TK_YIELD,
|
tkind.TK_VOID, tkind.TK_YIELD,
|
||||||
|
|||||||
@@ -443,7 +443,19 @@ fn stamptuplebinds(c: *checker, binds: *syntax.node, elems: *syntax.node,
|
|||||||
let bnm: str = b.str;
|
let bnm: str = b.str;
|
||||||
if (bnm.len > 0) {
|
if (bnm.len > 0) {
|
||||||
checkmoduleshadow(c, bnm, what);
|
checkmoduleshadow(c, bnm, what);
|
||||||
syntax.scopedefine(c.cur, bnm, syntax.skind.SK_VAR, nil, b);
|
// first registration wins; a nil return is a
|
||||||
|
// same-scope duplicate — `let (a, a) = ..` /
|
||||||
|
// `for (let (a, a) .. xs)`. Mirrors cstage
|
||||||
|
// scope_define == NULL → "redeclared".
|
||||||
|
let ds: *syntax.sym = syntax.scopedefine(c.cur, bnm, syntax.skind.SK_VAR, nil, b);
|
||||||
|
if (ds == nil) {
|
||||||
|
cerr("error: ");
|
||||||
|
cerr(what);
|
||||||
|
cerr(" '");
|
||||||
|
cerr(bnm);
|
||||||
|
cerr("' redeclared in same scope\n");
|
||||||
|
c.errs += 1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (b.type_ == nil) {
|
if (b.type_ == nil) {
|
||||||
@@ -550,17 +562,17 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
// `for (let x .. slice) body` / `for (let (a, b) .. slice) body` —
|
// `for (let x .. slice) body` / `for (let (a, b) .. slice) body` —
|
||||||
// each binding name becomes a fresh local. Walk the slice expr first
|
// each binding name becomes a fresh local. Walk the slice expr first
|
||||||
// so its idents resolve before the bindings shadow anything, then
|
// so its idents resolve before the bindings shadow anything, then
|
||||||
// install bindings and walk the body/else.
|
// install bindings and walk the body/else. The bindings live in a
|
||||||
//
|
// per-LOOP scope (the N_MCASE pattern): sequential same-name loops
|
||||||
// TODO(#11): cstage check.c (post-#32) errors `binding '%s'
|
// each rebind fresh, and a duplicate name WITHIN one pattern
|
||||||
// redeclared in same scope` when the tuple-pattern lists the same
|
// (`for (let (a, a) .. xs)`) now errs via stamptuplebinds' dup
|
||||||
// name twice (`for (let (a, a) .. xs)`). Wwstage's resolvewalk has
|
// check — the old TODO(#11) silent-accept rested on a stale
|
||||||
// no per-block scope (see resolvefnbody's docstring) and is used
|
// premise (resolvewalk HAS block scopes since #53 and IS the live
|
||||||
// only by wwdump_ww as a diagnostic, so silent-accept here avoids
|
// w6c_ww checker pass).
|
||||||
// false-positives on legal cross-block shadow until #11 adds the
|
|
||||||
// scoping infrastructure.
|
|
||||||
if (k == syntax.nkind.N_FORRANGE) {
|
if (k == syntax.nkind.N_FORRANGE) {
|
||||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||||
|
let frouter: *syntax.scope = c.cur;
|
||||||
|
c.cur = syntax.newscope(frouter);
|
||||||
if (n.list != nil) {
|
if (n.list != nil) {
|
||||||
// Tuple destructure `for (let (a,b) .. xs)`: peel the
|
// Tuple destructure `for (let (a,b) .. xs)`: peel the
|
||||||
// iterable's element type and distribute its tuple
|
// iterable's element type and distribute its tuple
|
||||||
@@ -628,10 +640,12 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
|||||||
};
|
};
|
||||||
// `break`/`continue` in the body target this loop; the `else`
|
// `break`/`continue` in the body target this loop; the `else`
|
||||||
// block runs at normal cond-false exit (skipped by break) and
|
// block runs at normal cond-false exit (skipped by break) and
|
||||||
// targets an ENCLOSING loop. Mirrors cstage cmd/wcc/check.c:2494.
|
// targets an ENCLOSING loop — outside the loop scope, like
|
||||||
|
// cstage's saved-scope restore. Mirrors cmd/wcc/check.c:2494.
|
||||||
c.loops += 1;
|
c.loops += 1;
|
||||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||||
c.loops -= 1;
|
c.loops -= 1;
|
||||||
|
c.cur = frouter;
|
||||||
if (n.els != nil) { resolvewalk(c, n.els); };
|
if (n.els != nil) { resolvewalk(c, n.els); };
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
11
test/wcc/data/forrange_dup_binder_reject/case.ww
Normal file
11
test/wcc/data/forrange_dup_binder_reject/case.ww
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//ww:error "redeclared in same scope"
|
||||||
|
// A duplicate name within one destructure pattern; the old TODO(#11)
|
||||||
|
// silent-accept rested on a stale premise (resolvewalk has block
|
||||||
|
// scopes since #53). Bindings live in a per-LOOP scope, so sequential
|
||||||
|
// same-name loops stay legal.
|
||||||
|
package main;
|
||||||
|
export fn main() i32 = {
|
||||||
|
let s: [](i64, i64) = alloc([], 1u64)!;
|
||||||
|
for (let (a, a) .. s) { };
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user