P5.7 Step B2: remove the #compiler attribute + compiler_expr AST node
The #compiler struct attribute + #compiler-suffixed bodyless methods were fully superseded by abi(.compiler) (P5.5) — no sx code uses them. Remove the hash_compiler token (token/lexer/lsp), the is_compiler_struct / struct_default_compiler parser machinery + the two compiler_expr body- synthesis branches, the compiler_expr AST variant, and every .builtin_expr/.compiler_expr switch arm + == .compiler_expr check across sema/resolver/semantic_diagnostics/generic/decl/call/calls (kept .builtin_expr). abi(.compiler) is untouched. Delete the obsolete calls.test.zig dispatch test. 500/500 unit + 706/0 corpus.
This commit is contained in:
@@ -431,6 +431,23 @@ when reached (sentinels or accessor fns; see the design doc Risks).
|
||||
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||
|
||||
## Log
|
||||
- **P5.7 Step B — deleted the `#compiler`/`compiler_call`/hook-Registry mechanism end-to-end (2026-06-19).**
|
||||
All superseded by `abi(.compiler)` VM-native dispatch (P5.5) — no sx code emits any of it. Two green commits:
|
||||
**B1** (`e2971f2`) removed the `compiler_call` IR op: the op variant + `CompilerCall` struct (`inst.zig`), the
|
||||
`Builder.compilerCall` emitter (`module.zig`), the two dead producer blocks in `lower/call.zig` (the
|
||||
`compiler_expr`-bodied free-fn + method dispatch), every consumer arm (`emit_llvm`, `ops.emitCompilerCall`,
|
||||
`print`, the `interp.zig` hook-dispatch arm), and the `interp.hooks` field + init/deinit. Stripped
|
||||
`compiler_hooks.zig` to its still-live `BuildConfig` / `BuildHooks` (link/emit vtable, P5.2b) / `AssetDir` —
|
||||
deleted `HookError`/`HookFn`/`Registry`/`registerDefaults` + all 37 `hookXxx` fns + the now-unused
|
||||
`interp`/`Value` imports. The two VM unit tests that used `compiler_call` as a sample unported op now use
|
||||
`vec_splat`. **B2** removed the `#compiler` attribute + `compiler_expr` AST node: the `hash_compiler` token
|
||||
(`token.zig`/`lexer.zig`/`lsp/server.zig`), the `is_compiler_struct` / `struct_default_compiler` parser
|
||||
machinery + the two `compiler_expr` body-synthesis branches (`parser.zig`), the `compiler_expr: void` AST
|
||||
variant (`ast.zig`), and every `.builtin_expr, .compiler_expr =>` arm / `== .compiler_expr` check across
|
||||
sema/resolver/semantic_diagnostics/generic/decl/call/calls (dropped `.compiler_expr`, kept `.builtin_expr`).
|
||||
`abi(.compiler)` (the NEW mechanism) is untouched. Deleted the obsolete `calls.test.zig` `#compiler`-dispatch
|
||||
unit test. **500/500 unit (−1 obsolete test) + 706/0 corpus, no snapshot churn.** NEXT: Step C — delete
|
||||
`interp.zig` + the `regToValue`/`valueToReg` bridge; move `#insert` (`evalComptimeString`) to the VM.
|
||||
- **P5.7 Step A — the flip: VM is the SOLE comptime evaluator at the emit-time + type-fn sites; NO fallback
|
||||
(2026-06-19).** Removed the `if (self.comptime_flat or need_vm)` gate + the `vm_result orelse fallback`
|
||||
legacy-interp blocks from `emit_llvm.zig` (`runComptimeSideEffects` AND the const-init path in `emitGlobals`)
|
||||
|
||||
@@ -81,7 +81,6 @@ pub const Node = struct {
|
||||
undef_literal: void,
|
||||
inferred_type: void,
|
||||
builtin_expr: void,
|
||||
compiler_expr: void,
|
||||
library_decl: LibraryDecl,
|
||||
framework_decl: FrameworkDecl,
|
||||
function_type_expr: FunctionTypeExpr,
|
||||
|
||||
@@ -320,32 +320,6 @@ test "plan: protocol dispatch selects method index + prepends receiver" {
|
||||
try std.testing.expect(p.prepends_receiver);
|
||||
}
|
||||
|
||||
test "plan: struct (UFCS) method via #compiler dispatch + prepends receiver" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// struct Point, with a `#compiler` method Point.scale(self) -> i64.
|
||||
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &.{} } });
|
||||
const self_param = ast.Param{ .name = "self", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "Point") };
|
||||
const params = [_]ast.Param{self_param};
|
||||
const compiler_body = mk(alloc, .{ .compiler_expr = {} });
|
||||
const method_fd = ast.FnDecl{ .name = "Point.scale", .params = ¶ms, .return_type = typeExpr(alloc, "i64"), .body = compiler_body };
|
||||
l.program_index.fn_ast_map.put("Point.scale", &method_fd) catch unreachable;
|
||||
|
||||
const recv = callNode(alloc, ident(alloc, "cast"), &[_]*Node{ typeExpr(alloc, "Point"), intLit(alloc, 0) });
|
||||
const call = callNode(alloc, fieldAccess(alloc, recv, "scale"), &.{});
|
||||
const p = cr.plan(&call.data.call);
|
||||
try std.testing.expectEqual(CallPlan.Kind.struct_method, p.kind);
|
||||
try std.testing.expectEqualStrings("Point.scale", p.target.named);
|
||||
try std.testing.expectEqual(TypeId.i64, p.return_type);
|
||||
try std.testing.expect(p.prepends_receiver);
|
||||
}
|
||||
|
||||
test "plan: runtime-class instance vs static dispatch" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
@@ -282,18 +282,6 @@ pub const CallResolver = struct {
|
||||
if (oi == .@"struct") {
|
||||
const struct_name = self.l.module.types.getString(oi.@"struct".name);
|
||||
const qualified = std.fmt.allocPrint(self.l.alloc, "{s}.{s}", .{ struct_name, cfa.field }) catch cfa.field;
|
||||
// Generic #compiler method dispatch — return type from declaration.
|
||||
if (self.l.program_index.fn_ast_map.get(qualified)) |method_fd| {
|
||||
if (method_fd.body.data == .compiler_expr) {
|
||||
return .{
|
||||
.kind = .struct_method,
|
||||
.return_type = if (method_fd.return_type) |rt| type_bridge.resolveAstType(rt, &self.l.module.types, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map) else .void,
|
||||
.target = .{ .named = qualified },
|
||||
.prepends_receiver = true,
|
||||
.expands_defaults = defaultsFor(method_fd, c.args.len + 1),
|
||||
};
|
||||
}
|
||||
}
|
||||
if (self.l.resolveFuncByName(qualified)) |fid| {
|
||||
const func = &self.l.module.functions.items[@intFromEnum(fid)];
|
||||
return .{
|
||||
|
||||
@@ -718,7 +718,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// literal global symbol — the existing bare-name
|
||||
// machinery below resolves them.
|
||||
switch (fd.body.data) {
|
||||
.builtin_expr, .compiler_expr => break :gate,
|
||||
.builtin_expr => break :gate,
|
||||
else => {},
|
||||
}
|
||||
if (hasComptimeParams(fd)) return self.lowerComptimeCall(fd, c);
|
||||
@@ -919,7 +919,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// Generic method on a non-template struct: `obj.method($T, ...)`
|
||||
// or inferred form `obj.method(val)` where val's type pins $T.
|
||||
if (self.program_index.fn_ast_map.get(qualified)) |gen_fd| {
|
||||
if (gen_fd.type_params.len > 0 and gen_fd.body.data != .compiler_expr) {
|
||||
if (gen_fd.type_params.len > 0) {
|
||||
// Effective AST args: prepend receiver so positions
|
||||
// line up with fd.params (which has self at index 0).
|
||||
var eff_args = std.ArrayList(*const Node).empty;
|
||||
@@ -2090,7 +2090,7 @@ pub fn checkCallArity(self: *Lowering, fd: *const ast.FnDecl, callee_name: []con
|
||||
|
||||
if (fd.type_params.len > 0 or hasComptimeParams(fd) or isPackFn(fd)) return false;
|
||||
switch (fd.body.data) {
|
||||
.compiler_expr, .builtin_expr => return false,
|
||||
.builtin_expr => return false,
|
||||
else => {},
|
||||
}
|
||||
var min: usize = 0;
|
||||
|
||||
@@ -523,7 +523,7 @@ pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool {
|
||||
// C ABI, no sx context (Phase 2, gap iv).
|
||||
if (fd.extern_export != .none) return false;
|
||||
return switch (fd.body.data) {
|
||||
.builtin_expr, .compiler_expr => false,
|
||||
.builtin_expr => false,
|
||||
else => !isExportedEntryName(fd.name),
|
||||
};
|
||||
}
|
||||
@@ -2402,10 +2402,10 @@ pub fn registerQualifiedFn(self: *Lowering, ns_name: []const u8, fd: *const ast.
|
||||
// collision assert; registering a qualified alias for them
|
||||
// would divert that machinery and strand a per-call type binding.
|
||||
if (fd.type_params.len > 0 or hasComptimeParams(fd) or isPackFn(fd)) return;
|
||||
// Extern / builtin / #compiler bodies keep their literal name; a
|
||||
// qualified alias has no distinct symbol to resolve to.
|
||||
// Extern / builtin bodies keep their literal name; a qualified alias has
|
||||
// no distinct symbol to resolve to.
|
||||
switch (fd.body.data) {
|
||||
.builtin_expr, .compiler_expr => return,
|
||||
.builtin_expr => return,
|
||||
else => {},
|
||||
}
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ns_name, short }) catch return;
|
||||
@@ -2521,8 +2521,8 @@ pub fn lazyLowerFunction(self: *Lowering, name: []const u8) void {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Builtins / #compiler bodies stay as compiler-handled — no extern stub needed.
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .compiler_expr) return;
|
||||
// Builtin bodies stay as compiler-handled — no extern stub needed.
|
||||
if (fd.body.data == .builtin_expr) return;
|
||||
if (fd.type_params.len > 0) return; // generics handled by monomorphization (Step 3.13)
|
||||
|
||||
// Defer functions with type-category matches until all types are registered.
|
||||
@@ -2728,7 +2728,7 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
// declare-only — its Zig/VM handler is the impl. A BODIED `abi(.compiler)`
|
||||
// function DOES need its body lowered for VM eval (emit-skipped later via
|
||||
// `is_compiler_domain`), so it falls through to normal lowering below.
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .compiler_expr or
|
||||
if (fd.body.data == .builtin_expr or
|
||||
fd.extern_export == .extern_ or fnIsBodilessCompiler(fd))
|
||||
{
|
||||
// Already declared by scanDecls/declareFunction (which handles #extern renames)
|
||||
|
||||
@@ -822,7 +822,7 @@ pub fn isPlainFreeFn(fd: *const ast.FnDecl) bool {
|
||||
// fn. `export` DEFINES a real body, so it stays plain-free.
|
||||
if (fd.extern_export == .extern_) return false;
|
||||
return switch (fd.body.data) {
|
||||
.builtin_expr, .compiler_expr => false,
|
||||
.builtin_expr => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ pub fn isPlainFreeFnDecl(fd: *const ast.FnDecl) bool {
|
||||
// body, so it stays plain-free.
|
||||
if (fd.extern_export == .extern_) return false;
|
||||
return switch (fd.body.data) {
|
||||
.builtin_expr, .compiler_expr => false,
|
||||
.builtin_expr => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -355,7 +355,6 @@ pub const UnknownTypeChecker = struct {
|
||||
.undef_literal,
|
||||
.inferred_type,
|
||||
.builtin_expr,
|
||||
.compiler_expr,
|
||||
.framework_decl,
|
||||
.function_type_expr,
|
||||
.closure_type_expr,
|
||||
|
||||
@@ -87,7 +87,6 @@ pub const Lexer = struct {
|
||||
.{ "#insert", Tag.hash_insert },
|
||||
.{ "#run", Tag.hash_run },
|
||||
.{ "#builtin", Tag.hash_builtin },
|
||||
.{ "#compiler", Tag.hash_compiler },
|
||||
.{ "#library", Tag.hash_library },
|
||||
.{ "#framework", Tag.hash_framework },
|
||||
.{ "#using", Tag.hash_using },
|
||||
|
||||
@@ -1690,7 +1690,6 @@ pub const Server = struct {
|
||||
.hash_import,
|
||||
.hash_insert,
|
||||
.hash_builtin,
|
||||
.hash_compiler,
|
||||
.hash_library,
|
||||
.hash_framework,
|
||||
.hash_using,
|
||||
|
||||
@@ -21,11 +21,6 @@ pub const Parser = struct {
|
||||
diagnostics: ?*errors.DiagnosticList = null,
|
||||
/// Type param names from enclosing generic struct (set while parsing methods)
|
||||
struct_type_params: []const []const u8 = &.{},
|
||||
/// When true (set while parsing methods inside `struct #compiler { ... }`),
|
||||
/// a missing function body (just `name :: (params);`) is synthesized as
|
||||
/// a `.compiler_expr` body so the per-method `#compiler` suffix can be
|
||||
/// omitted.
|
||||
struct_default_compiler: bool = false,
|
||||
/// When true (set while parsing a `for` header's iterable expressions),
|
||||
/// a top-level `(` group immediately followed by `{` or `=>` is the loop
|
||||
/// CAPTURE, never call arguments — `for xs (x) { }` reads `(x)` as the
|
||||
@@ -950,15 +945,6 @@ pub const Parser = struct {
|
||||
fn parseStructDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
|
||||
self.advance(); // skip 'struct'
|
||||
|
||||
// Optional `#compiler` attribute: all methods inside this struct are
|
||||
// implicitly compiler hooks (no per-method `#compiler` suffix needed).
|
||||
// Mirrors `protocol #inline { ... }` shape.
|
||||
var is_compiler_struct = false;
|
||||
if (self.current.tag == .hash_compiler) {
|
||||
is_compiler_struct = true;
|
||||
self.advance();
|
||||
}
|
||||
|
||||
// Optional welded-binding annotation: `struct abi(.zig) extern <lib> { … }`.
|
||||
// `abi(...)` (the ABI/layout selector) sits before the `extern` linkage
|
||||
// keyword, mirroring the fn-decl slot order; the library handle follows.
|
||||
@@ -1023,13 +1009,6 @@ pub const Parser = struct {
|
||||
self.struct_type_params = tp_names.items;
|
||||
defer self.struct_type_params = saved_struct_type_params;
|
||||
|
||||
// Propagate the struct-level `#compiler` flag to nested method
|
||||
// parsing so a bodyless `name :: (params);` synthesizes a
|
||||
// `.compiler_expr` body.
|
||||
const saved_struct_default_compiler = self.struct_default_compiler;
|
||||
self.struct_default_compiler = is_compiler_struct;
|
||||
defer self.struct_default_compiler = saved_struct_default_compiler;
|
||||
|
||||
var field_names = std.ArrayList([]const u8).empty;
|
||||
var field_types = std.ArrayList(*Node).empty;
|
||||
var field_defaults = std.ArrayList(?*Node).empty;
|
||||
@@ -2014,17 +1993,6 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
try self.expect(.semicolon);
|
||||
break :blk try self.createNode(bi_start, .{ .builtin_expr = {} });
|
||||
} else if (self.current.tag == .hash_compiler) blk: {
|
||||
const ci_start = self.current.loc.start;
|
||||
self.advance();
|
||||
try self.expect(.semicolon);
|
||||
break :blk try self.createNode(ci_start, .{ .compiler_expr = {} });
|
||||
} else if (self.struct_default_compiler and self.current.tag == .semicolon) blk: {
|
||||
// Inside `struct #compiler { ... }`: a bodyless method is
|
||||
// implicitly a `#compiler` hook.
|
||||
const ci_start = self.current.loc.start;
|
||||
self.advance();
|
||||
break :blk try self.createNode(ci_start, .{ .compiler_expr = {} });
|
||||
} else if (self.current.tag == .fat_arrow) blk: {
|
||||
is_arrow = true;
|
||||
self.advance();
|
||||
@@ -3765,16 +3733,12 @@ pub const Parser = struct {
|
||||
|
||||
fn isFunctionDef(self: *Parser) bool {
|
||||
const tag = self.peekPastParens() orelse return false;
|
||||
// Inside `struct #compiler { ... }`, a bodyless method declaration
|
||||
// ends with `;` directly after the param list — recognise it as a
|
||||
// function def (not a constant) so it goes through parseFnDecl.
|
||||
if (self.struct_default_compiler and tag == .semicolon) return true;
|
||||
// `(T1, T2) -> R` without a trailing body (`{`, `=>`, or an extern/
|
||||
// builtin marker) is a function-type literal, not a function def.
|
||||
if (tag == .arrow) return self.hasFnBodyAfterArrow();
|
||||
// `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;`
|
||||
// with no return type) marks a fn decl just like `abi(...)`.
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .hash_compiler or tag == .fat_arrow or tag == .kw_abi or tag == .kw_extern or tag == .kw_export;
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .fat_arrow or tag == .kw_abi or tag == .kw_extern or tag == .kw_export;
|
||||
}
|
||||
|
||||
fn hasFnBodyAfterArrow(self: *Parser) bool {
|
||||
@@ -3800,17 +3764,11 @@ pub const Parser = struct {
|
||||
while (self.current.tag != .eof) {
|
||||
if (self.current.tag == .fat_arrow) return true;
|
||||
if (self.current.tag == .l_brace) return true;
|
||||
if (self.current.tag == .hash_builtin or self.current.tag == .hash_compiler) return true;
|
||||
if (self.current.tag == .hash_builtin) return true;
|
||||
if (self.current.tag == .kw_abi) return true;
|
||||
// Postfix linkage modifier after the return type: `-> R extern;` /
|
||||
// `-> R export { … }` (and `-> R abi(.c) extern`). Marks a fn def.
|
||||
if (self.current.tag == .kw_extern or self.current.tag == .kw_export) return true;
|
||||
// Inside a `struct #compiler` block, a `(...) -> Ret;` ending
|
||||
// with `;` after the return type is a `#compiler` method
|
||||
// declaration (body implicit). Outside that context, the same
|
||||
// shape is a function-type alias (no body) and falls through to
|
||||
// const-decl parsing.
|
||||
if (self.struct_default_compiler and self.current.tag == .semicolon) return true;
|
||||
if (self.current.tag == .identifier or self.current.tag.isTypeKeyword() or
|
||||
self.current.tag == .dot or self.current.tag == .dollar or
|
||||
self.current.tag == .l_bracket or self.current.tag == .r_bracket or
|
||||
|
||||
@@ -1294,7 +1294,6 @@ pub const Analyzer = struct {
|
||||
.undef_literal,
|
||||
.inferred_type,
|
||||
.builtin_expr,
|
||||
.compiler_expr,
|
||||
.library_decl,
|
||||
.framework_decl,
|
||||
.function_type_expr,
|
||||
@@ -1767,7 +1766,6 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
.undef_literal,
|
||||
.inferred_type,
|
||||
.builtin_expr,
|
||||
.compiler_expr,
|
||||
.library_decl,
|
||||
.framework_decl,
|
||||
.function_type_expr,
|
||||
|
||||
@@ -118,7 +118,6 @@ pub const Tag = enum {
|
||||
hash_import, // #import
|
||||
hash_insert, // #insert
|
||||
hash_builtin, // #builtin
|
||||
hash_compiler, // #compiler
|
||||
hash_library, // #library
|
||||
hash_framework, // #framework
|
||||
hash_using, // #using
|
||||
|
||||
Reference in New Issue
Block a user