ERR/E0.1: error-set decls + ! / !Named type exprs (parser)

Parser-only first step of the error-handling stream. No sema/codegen.

- token: `kw_error` keyword (`!` reuses existing `.bang`).
- ast: `ErrorSetDecl { name, tag_names }` + `ErrorTypeExpr { name: ?[] }`
  (null = inferred `!`, non-null = `!Named`); wired into Node.Data and
  declName.
- parser: `parseErrorSetDecl` (comma-separated tags, optional trailing
  comma/`;`) dispatched from parseConstBinding; `!` / `!Named` parsed in
  parseTypeExpr; result-list loop enforces error type as trailing-only;
  hasFnBodyAfterArrow skips `.bang` so failable-return fns are recognised.
- print: new focused AST round-trip printer (decls + type exprs); loud
  `error.UnsupportedNode` otherwise. Registered in root.zig.
- sema/lsp: exhaustive switch arms for the two new nodes.
- tests: 11 inline parser unit tests (shapes + 3 round-trip prints + 2
  trailing-position rejections).

zig build, zig build test, and 254/254 examples green.
This commit is contained in:
agra
2026-05-31 16:40:22 +03:00
parent f7f9def0e7
commit e88ee66953
7 changed files with 300 additions and 1 deletions

70
src/print.zig Normal file
View File

@@ -0,0 +1,70 @@
//! Focused AST round-trip printer.
//!
//! Reconstructs sx source text from AST nodes. The scope is intentionally
//! narrow: it covers the declaration and type-expression node kinds the
//! error-handling (ERR) stream's parser tests round-trip, and bails loudly
//! (`error.UnsupportedNode`) on anything it does not yet handle — so an
//! unsupported node can never be silently mis-printed (CLAUDE.md REJECTED
//! PATTERNS: no silent arms). Later ERR steps extend it as new surface
//! syntax lands.
const std = @import("std");
const ast = @import("ast.zig");
const Node = ast.Node;
const Writer = *std.Io.Writer;
/// Print a declaration node back to source text. Currently handles
/// `error { ... }` set declarations; falls through to `printType` for
/// type-expression nodes.
pub fn printNode(node: *const Node, writer: Writer) !void {
switch (node.data) {
.error_set_decl => |d| {
try writer.writeAll(d.name);
try writer.writeAll(" :: error {");
for (d.tag_names, 0..) |tag, i| {
try writer.writeAll(if (i == 0) " " else ", ");
try writer.writeAll(tag);
}
if (d.tag_names.len > 0) try writer.writeByte(' ');
try writer.writeByte('}');
},
else => try printType(node, writer),
}
}
/// Print a type-expression node back to source text.
pub fn printType(node: *const Node, writer: Writer) !void {
switch (node.data) {
.type_expr => |t| try writer.writeAll(t.name),
.error_type_expr => |e| {
try writer.writeByte('!');
if (e.name) |n| try writer.writeAll(n);
},
.pointer_type_expr => |p| {
try writer.writeByte('*');
try printType(p.pointee_type, writer);
},
.optional_type_expr => |o| {
try writer.writeByte('?');
try printType(o.inner_type, writer);
},
.slice_type_expr => |s| {
try writer.writeAll("[]");
try printType(s.element_type, writer);
},
.many_pointer_type_expr => |m| {
try writer.writeAll("[*]");
try printType(m.element_type, writer);
},
.tuple_type_expr => |t| {
try writer.writeByte('(');
for (t.field_types, 0..) |ft, i| {
if (i > 0) try writer.writeAll(", ");
try printType(ft, writer);
}
try writer.writeByte(')');
},
else => return error.UnsupportedNode,
}
}