From ad45ae07efc8d2f1446868138a5fa0965743b494 Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 21 Jun 2026 09:10:38 +0300 Subject: [PATCH] fix: diagnose unknown generic #builtin instead of silently returning 0 (issue 0144) A bodiless #builtin with a $T: Type param routes through monomorphization. When resolveBuiltin returned null for an unrecognized name, the builtin-body branch fell through to ensureTerminator's constInt(0) -- a silent-fallback default the CLAUDE.md REJECTED PATTERNS forbid. Emit a loud 'error: unknown #builtin ' diagnostic instead. Regression: examples/1189-diagnostics-unknown-builtin.sx --- examples/1189-diagnostics-unknown-builtin.sx | 16 ++++++++++++++++ .../1189-diagnostics-unknown-builtin.exit | 1 + .../1189-diagnostics-unknown-builtin.stderr | 5 +++++ .../1189-diagnostics-unknown-builtin.stdout | 1 + ...nrecognized-type-param-builtin-silent-zero.md | 8 ++++++++ src/ir/lower/generic.zig | 10 ++++++++++ 6 files changed, 41 insertions(+) create mode 100644 examples/1189-diagnostics-unknown-builtin.sx create mode 100644 examples/expected/1189-diagnostics-unknown-builtin.exit create mode 100644 examples/expected/1189-diagnostics-unknown-builtin.stderr create mode 100644 examples/expected/1189-diagnostics-unknown-builtin.stdout diff --git a/examples/1189-diagnostics-unknown-builtin.sx b/examples/1189-diagnostics-unknown-builtin.sx new file mode 100644 index 00000000..00b947e0 --- /dev/null +++ b/examples/1189-diagnostics-unknown-builtin.sx @@ -0,0 +1,16 @@ +// A bodiless `#builtin` carrying a `$T: Type` parameter, whose name the +// compiler does not recognize, must fail LOUDLY with an "unknown #builtin" +// diagnostic — not silently evaluate to 0 (the CLAUDE.md silent-fallback +// pattern). The generic monomorphization path (monomorphizeFunction's +// builtin-body branch) now diagnoses an unresolved builtin name instead of +// falling through to `ensureTerminator`'s `constInt(0)`. +// +// Regression (issue 0144). +#import "modules/std.sx"; + +// `mystery` is not a recognized builtin. +mystery :: ($T: Type, x: T) -> T #builtin; + +main :: () { + print("mystery(42) = {}\n", mystery(i64, 42)); +} diff --git a/examples/expected/1189-diagnostics-unknown-builtin.exit b/examples/expected/1189-diagnostics-unknown-builtin.exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/examples/expected/1189-diagnostics-unknown-builtin.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1189-diagnostics-unknown-builtin.stderr b/examples/expected/1189-diagnostics-unknown-builtin.stderr new file mode 100644 index 00000000..7bc2e69a --- /dev/null +++ b/examples/expected/1189-diagnostics-unknown-builtin.stderr @@ -0,0 +1,5 @@ +error: unknown #builtin 'mystery' + --> examples/1189-diagnostics-unknown-builtin.sx:12:1 + | +12 | mystery :: ($T: Type, x: T) -> T #builtin; + | ^^^^^^^ diff --git a/examples/expected/1189-diagnostics-unknown-builtin.stdout b/examples/expected/1189-diagnostics-unknown-builtin.stdout new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1189-diagnostics-unknown-builtin.stdout @@ -0,0 +1 @@ + diff --git a/issues/0144-unrecognized-type-param-builtin-silent-zero.md b/issues/0144-unrecognized-type-param-builtin-silent-zero.md index 25bc1dff..ef453c02 100644 --- a/issues/0144-unrecognized-type-param-builtin-silent-zero.md +++ b/issues/0144-unrecognized-type-param-builtin-silent-zero.md @@ -1,5 +1,13 @@ # 0144 — unrecognized `$T`-param `#builtin` silently returns 0 +> **RESOLVED.** The generic monomorphization path (`monomorphizeFunction`'s +> `builtin_expr` body branch in `src/ir/lower/generic.zig`) no longer falls +> through to `ensureTerminator`'s silent `constInt(0)` when `resolveBuiltin` +> returns null for an unknown name. It now emits a loud +> `error: unknown #builtin ''` diagnostic — removing exactly the +> silent-fallback-default the CLAUDE.md REJECTED PATTERNS forbid. Regression +> test: `examples/1189-diagnostics-unknown-builtin.sx`. + ## Symptom A **bodiless `#builtin`** whose signature takes a `$T: Type` parameter, when the diff --git a/src/ir/lower/generic.zig b/src/ir/lower/generic.zig index 216119f2..12d8b545 100644 --- a/src/ir/lower/generic.zig +++ b/src/ir/lower/generic.zig @@ -152,6 +152,16 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name const result = self.builder.callBuiltin(bid, &.{param0}, ret_ty); self.builder.ret(result, ret_ty); } else { + // Unknown bodiless #builtin: the name is not claimed by any + // recognizer (atomics/reflection are handled earlier in call.zig). + // Emitting `ensureTerminator(ret_ty)` here would synthesize a + // silent `constInt(0, ret_ty)` for a non-void return — a silent + // fallback default (issue 0144). Surface the failure loudly. + const span = if (fd.name_span.end != 0) fd.name_span else fd.body.span; + if (self.diagnostics) |d| + d.addFmt(.err, span, "unknown #builtin '{s}'", .{fd.name}); + // Lowering has already failed; close the block with a terminator + // so the IR stays well-formed for the rest of the pass. self.ensureTerminator(ret_ty); } self.builder.finalize();