From 497d450ba75342b7ceaf75fc903eeaad625ebf50 Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 17:37:27 +0300 Subject: [PATCH] fix(lower): diagnose `.*` on a non-pointer instead of codegen panic `lowerDerefExpr` left the deref's result type `.unresolved` when the operand wasn't a pointer (e.g. a stale `value.*` after a parameter changed from `*T` to `T`), and emitted the `.deref` anyway. That unresolved type slipped through to emit_llvm's "unresolved type reached LLVM emission" panic with no source location. Now it emits a clean diagnostic at the deref site ("cannot dereference with `.*`: 'T' is not a pointer") and recovers. Regression: examples/254-deref-non-pointer-reject.sx. --- examples/254-deref-non-pointer-reject.sx | 14 ++++++++++++++ src/ir/lower.zig | 14 ++++++++++---- tests/expected/254-deref-non-pointer-reject.exit | 1 + tests/expected/254-deref-non-pointer-reject.txt | 5 +++++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 examples/254-deref-non-pointer-reject.sx create mode 100644 tests/expected/254-deref-non-pointer-reject.exit create mode 100644 tests/expected/254-deref-non-pointer-reject.txt diff --git a/examples/254-deref-non-pointer-reject.sx b/examples/254-deref-non-pointer-reject.sx new file mode 100644 index 0000000..0c3a008 --- /dev/null +++ b/examples/254-deref-non-pointer-reject.sx @@ -0,0 +1,14 @@ +// `.*` on a non-pointer must be a clean compile diagnostic, NOT a codegen +// panic. Regression: a stale `value.*` (e.g. after a parameter changed from +// `*T` to `T` by value) used to lower a `.deref` with an `.unresolved` result +// type, which slipped through to emit_llvm's "unresolved type reached LLVM +// emission" panic with no source location. `lowerDerefExpr` now diagnoses it. +// Expected: a clean error pointing at the deref; exit 1. + +Point :: struct { x: s32; y: s32; } + +main :: () -> s32 { + p : Point = .{ x = 3, y = 4 }; + q := p.*; // ERROR: `p` is a Point value, not a pointer + return q.x; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 06ea225..d83da96 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -5483,16 +5483,22 @@ pub const Lowering = struct { fn lowerDerefExpr(self: *Lowering, de: *const ast.DerefExpr) Ref { const ptr = self.lowerExpr(de.operand); - // Resolve pointee type from the pointer type + // Resolve pointee type from the pointer type. const ptr_ty = self.inferExprType(de.operand); - var pointee_ty: TypeId = .unresolved; if (!ptr_ty.isBuiltin()) { const info = self.module.types.get(ptr_ty); if (info == .pointer) { - pointee_ty = info.pointer.pointee; + return self.builder.emit(.{ .deref = .{ .operand = ptr } }, info.pointer.pointee); } } - return self.builder.emit(.{ .deref = .{ .operand = ptr } }, pointee_ty); + // Operand isn't a pointer — `.*` is invalid. Diagnose here instead of + // emitting a `.deref` with an `.unresolved` result type, which would + // otherwise slip through to emit_llvm's "unresolved type reached LLVM + // emission" panic with no source location. + if (self.diagnostics) |d| { + d.addFmt(.err, de.operand.span, "cannot dereference with `.*`: '{s}' is not a pointer", .{self.formatTypeName(ptr_ty)}); + } + return ptr; } fn lowerForceUnwrap(self: *Lowering, fu: *const ast.ForceUnwrap) Ref { diff --git a/tests/expected/254-deref-non-pointer-reject.exit b/tests/expected/254-deref-non-pointer-reject.exit new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/tests/expected/254-deref-non-pointer-reject.exit @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/tests/expected/254-deref-non-pointer-reject.txt b/tests/expected/254-deref-non-pointer-reject.txt new file mode 100644 index 0000000..9f51321 --- /dev/null +++ b/tests/expected/254-deref-non-pointer-reject.txt @@ -0,0 +1,5 @@ +error: cannot dereference with `.*`: 'Point' is not a pointer + --> /Users/agra/projects/sx/examples/254-deref-non-pointer-reject.sx:12:10 + | +12 | q := p.*; // ERROR: `p` is a Point value, not a pointer + | ^