feat(lang): universal raw identifier — parser exhaustiveness + raw type continuations + sema/LSP [F0.6]
Closes the remaining three F0.6 findings so the universal backtick raw identifier holds in BOTH classifiers and at EVERY parser construction site. 1. Struct-body constants thread is_raw + name_span. The struct-body const forms (untyped `` `s2 :: 5 `` and typed `` `s2 : T : v ``) built the const_decl node without name_span/is_raw, so a backtick const was falsely rejected and a bare reserved-name const caretted at 1:1. They now capture both. Structural cure: `ast.ConstDecl`'s name_span + is_raw carry NO default, so the compiler rejects any construction site that omits them (mirrors checkBindingName's required `is_raw` arg). FnDecl keeps its defaults — every parser fn_decl routes through parseFnDecl whose `name_is_raw` is a required parameter (equivalent guarantee). 2. Raw identifier in TYPE position flows through the normal continuations. parseTypeExpr no longer returns a terminal type_expr for a raw atom; the raw flag rides the atom through the qualified-path / Closure / parameterized continuations, so `` `s2(s64) ``, `` *`s2 ``, `` ?`s2 `` all parse. ParameterizedTypeExpr carries is_raw; resolveParameterizedWithBindings skips the `Vector` intrinsic when raw. 3. sema/LSP (the second classifier) honors is_raw. Type.fromTypeExpr returns null for a raw type_expr; resolveTypeNode skips the builtin classifier when raw; resolveTypeNameStr takes a skip_builtin arg threaded from te/id.is_raw (compound inner names pass false). A backtick reserved-name annotation now resolves to the user type in the editor index, not the builtin. Tests: examples/0156 (struct-body const), 0157 (parameterized raw type + wrappers), 1142 (bare struct-body const errors, caret on name); src/sema.test.zig pins the LSP raw-type resolution (fail-before verified). Gate: 365 unit tests, 429 examples, 0 failed.
This commit is contained in:
86
src/sema.test.zig
Normal file
86
src/sema.test.zig
Normal file
@@ -0,0 +1,86 @@
|
||||
// Tests for sema.zig — the editor/LSP type classifier (the SECOND resolver,
|
||||
// distinct from the codegen-side `ir/type_resolver.zig`). These pin behavior
|
||||
// the example suite can't reach: the example runner exercises the codegen
|
||||
// path (`sx run`), never sema's hover/completion/index resolution.
|
||||
|
||||
const std = @import("std");
|
||||
const ast = @import("ast.zig");
|
||||
const Node = ast.Node;
|
||||
const Parser = @import("parser.zig").Parser;
|
||||
const sema = @import("sema.zig");
|
||||
const types = @import("types.zig");
|
||||
const Type = types.Type;
|
||||
|
||||
// issue 0089 — the backtick raw escape must hold in BOTH classifiers. A raw
|
||||
// reserved-name type reference (`` `s2 ``) resolves to the user-declared type,
|
||||
// while a BARE `s2` stays the builtin int. Before the fix sema's
|
||||
// `resolveTypeNode` ran `Type.fromName` first and ignored `is_raw`, so the
|
||||
// editor index would show the builtin for backtick code (the issue-0083
|
||||
// two-resolver divergence applied to raw types).
|
||||
test "sema: backtick raw type reference resolves to the user type; bare stays builtin" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`s2 :: struct { x: s64; }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
const root = try parser.parse();
|
||||
|
||||
var analyzer = sema.Analyzer.init(alloc);
|
||||
_ = try analyzer.analyze(root);
|
||||
|
||||
// The reserved-spelled user type registered under its plain name.
|
||||
try std.testing.expect(analyzer.struct_types.contains("s2"));
|
||||
|
||||
// RAW reference (`` `s2 ``) → the user struct, NOT the 2-bit signed int.
|
||||
var raw_node = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s2", .is_raw = true } } };
|
||||
const raw_ty = analyzer.resolveTypeNode(&raw_node);
|
||||
try std.testing.expect(raw_ty == .struct_type);
|
||||
try std.testing.expectEqualStrings("s2", raw_ty.struct_type);
|
||||
|
||||
// BARE `s2` → the builtin 2-bit signed int.
|
||||
var bare_node = Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s2", .is_raw = false } } };
|
||||
const bare_ty = analyzer.resolveTypeNode(&bare_node);
|
||||
try std.testing.expect(bare_ty == .signed);
|
||||
try std.testing.expectEqual(@as(u8, 2), bare_ty.signed);
|
||||
}
|
||||
|
||||
// The same divergence guard for the string-keyed entry (`resolveTypeNameStr`,
|
||||
// reached via `fieldType` when registering struct field types): a raw field
|
||||
// annotation (`` `u8 ``) resolves to the user struct, a bare one (`u8`) to the
|
||||
// builtin. Driven through the real analyze pipeline (no private access).
|
||||
test "sema: a raw struct-field annotation resolves to the user type; bare stays builtin" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const src =
|
||||
\\`u8 :: struct { y: s64; }
|
||||
\\Holder :: struct { a: `u8; b: u8; }
|
||||
\\
|
||||
;
|
||||
var parser = Parser.init(alloc, src);
|
||||
const root = try parser.parse();
|
||||
|
||||
var analyzer = sema.Analyzer.init(alloc);
|
||||
_ = try analyzer.analyze(root);
|
||||
|
||||
const holder = analyzer.struct_types.get("Holder").?;
|
||||
var a_ty: ?Type = null;
|
||||
var b_ty: ?Type = null;
|
||||
for (holder.field_names, holder.field_types) |fname, fty| {
|
||||
if (std.mem.eql(u8, fname, "a")) a_ty = fty;
|
||||
if (std.mem.eql(u8, fname, "b")) b_ty = fty;
|
||||
}
|
||||
|
||||
// field `a : `u8` → the user struct named "u8".
|
||||
try std.testing.expect(a_ty.? == .struct_type);
|
||||
try std.testing.expectEqualStrings("u8", a_ty.?.struct_type);
|
||||
|
||||
// field `b : u8` → the builtin unsigned 8-bit int.
|
||||
try std.testing.expect(b_ty.? == .unsigned);
|
||||
try std.testing.expectEqual(@as(u8, 8), b_ty.?.unsigned);
|
||||
}
|
||||
Reference in New Issue
Block a user