Found in a modularity review of main @ 8e8970a. This is the cheapest structural fix available in the codebase: the mechanism is already paid for and simply disabled.
The measurement
Makefile:9 sets -Werror=switch. Coverage of switch (node->type) over ASTType (33 members, eigenscript.h:257-268):
| File |
ASTType switches |
with default: |
actually exhaustive |
src/compiler.c |
11 |
11 |
0 |
src/lint.c |
22 |
22 |
0 |
src/eigenlsp.c |
2 |
2 |
0 |
src/parser.c |
2 |
1 |
1 |
1 of 38. Adding an ASTType today produces zero diagnostics — the node is silently skipped by the compiler, the linter, and the LSP. free_ast (parser.c:196) names all 33 cases but still carries a default:, so a missed case there becomes a silent leak rather than a build error.
The opcode tables are worse: op_has_u16, op_verify_operands, and op_stack_effect all take uint8_t op rather than OpCode, so -Wswitch cannot apply at all. That is the mechanical reason those tables have already drifted (filed separately).
Related: the ValType build-error premise is not true
CLAUDE.md states that #709 made a new ValType a build error. I could not find that mechanism. val_type_name (eigenscript.c:212-225) ends in default: return "?";, so an 11th ValType compiles clean and prints "?". CHANGELOG.md:124 describes #709 as "json_raw values are measured instead of reported as 0" — an observer-measurement fix, not an exhaustiveness gate. A new ValType must currently reach ~14 unenforced type-switches plus gc_value_is_node, GC_FOR_EACH_CHILD, gc_clear_node, and the destructor.
Worth correcting the CLAUDE.md claim either way, since it is the kind of statement a contributor relies on rather than re-verifies.
Suggested fix
Delete the default: arm from every switch over a closed enum (ASTType, ValType, OpCode) and type the opcode-table parameters as OpCode. Where a genuine catch-all is needed at runtime, put the fallback after the switch rather than inside it, so the compiler still checks exhaustiveness:
switch (t) {
case AST_X: ... return r;
/* no default */
}
return fallback; /* unreachable for valid enum values */
This is a mechanical change with an immediate payoff: it converts an entire class of "new node/opcode/type silently skipped" from a review responsibility into a compile error, which is the difference between the tables that stayed correct (SANDBOX_ALLOW, gated by a test — 148 entries, zero drift) and the ones that didn't (eigenlsp.c's builtin_docs, ungated — 179 of 235 builtins absent plus a phantom entry).
Found in a modularity review of
main@8e8970a. This is the cheapest structural fix available in the codebase: the mechanism is already paid for and simply disabled.The measurement
Makefile:9sets-Werror=switch. Coverage ofswitch (node->type)overASTType(33 members,eigenscript.h:257-268):default:src/compiler.csrc/lint.csrc/eigenlsp.csrc/parser.c1 of 38. Adding an
ASTTypetoday produces zero diagnostics — the node is silently skipped by the compiler, the linter, and the LSP.free_ast(parser.c:196) names all 33 cases but still carries adefault:, so a missed case there becomes a silent leak rather than a build error.The opcode tables are worse:
op_has_u16,op_verify_operands, andop_stack_effectall takeuint8_t oprather thanOpCode, so-Wswitchcannot apply at all. That is the mechanical reason those tables have already drifted (filed separately).Related: the ValType build-error premise is not true
CLAUDE.mdstates that #709 made a newValTypea build error. I could not find that mechanism.val_type_name(eigenscript.c:212-225) ends indefault: return "?";, so an 11thValTypecompiles clean and prints"?".CHANGELOG.md:124describes #709 as "json_rawvalues are measured instead of reported as 0" — an observer-measurement fix, not an exhaustiveness gate. A newValTypemust currently reach ~14 unenforced type-switches plusgc_value_is_node,GC_FOR_EACH_CHILD,gc_clear_node, and the destructor.Worth correcting the CLAUDE.md claim either way, since it is the kind of statement a contributor relies on rather than re-verifies.
Suggested fix
Delete the
default:arm from every switch over a closed enum (ASTType,ValType,OpCode) and type the opcode-table parameters asOpCode. Where a genuine catch-all is needed at runtime, put the fallback after the switch rather than inside it, so the compiler still checks exhaustiveness:This is a mechanical change with an immediate payoff: it converts an entire class of "new node/opcode/type silently skipped" from a review responsibility into a compile error, which is the difference between the tables that stayed correct (
SANDBOX_ALLOW, gated by a test — 148 entries, zero drift) and the ones that didn't (eigenlsp.c'sbuiltin_docs, ungated — 179 of 235 builtins absent plus a phantom entry).