wcc: case T => variant validity check (C + selfhost)
`match (u) { case T => ... }` where T isn't a variant of u was
silently accepted by both checkers. The cgen would emit a tag
comparison against an index that never appears, leaving the arm
unreachable — wasted code that's almost always a bug or typo.
C check.c now mirrors the existing is/as rule for match arms:
each `case T` and each alt of multi-pattern `case T1 | T2` is
checked against the scrutinee's variant list via variant_present.
selfhost check.ww gets the same shape with AST-level type_eq_ast
comparison. Both checks land in the same scope-aware pass that
already runs exhaustiveness and ? subset.
New test rows in 300_check (C side) and 950_selfcheck (selfhost
side) exercise both single-pattern and multi-pattern alt typos.
The 950 driver's err_present detector picks up the new
"case: not a variant" prefix.
This commit is contained in:
@@ -909,6 +909,28 @@ cexpr(Checker *c, Node *n)
|
||||
cs->type = vt;
|
||||
for (Node *alt = cs->list; alt; alt = alt->next)
|
||||
alt->type = resolve_type(c, alt);
|
||||
/* Validity: every `case T =>` pattern must
|
||||
* refer to a variant of the scrutinee's
|
||||
* tagged union. Mirrors the existing is/as
|
||||
* check; `match (u) { case f64 => ... }`
|
||||
* where f64 isn't a variant of u is dead code
|
||||
* the dispatch never reaches, so refuse it. */
|
||||
if (vt && vt != ty_err &&
|
||||
!variant_present(u->params, vt))
|
||||
err(c, cs->pos,
|
||||
"case: %s is not a variant of %s",
|
||||
type_name(c->a, vt),
|
||||
type_name(c->a, st));
|
||||
for (Node *alt = cs->list; alt; alt = alt->next) {
|
||||
if (alt->type == NULL ||
|
||||
alt->type == ty_err) continue;
|
||||
if (!variant_present(u->params,
|
||||
alt->type))
|
||||
err(c, cs->pos,
|
||||
"case: %s is not a variant of %s",
|
||||
type_name(c->a, alt->type),
|
||||
type_name(c->a, st));
|
||||
}
|
||||
if (cs->str && cs->str[0])
|
||||
scope_define(c->cur, cs->str, SK_VAR, vt, cs);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user