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:
@@ -632,15 +632,13 @@ pub const Parser = struct {
|
||||
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
|
||||
// A backtick raw identifier (`` `s2 ``) in type position is the
|
||||
// LITERAL name `s2` used as a type reference — never the builtin /
|
||||
// reserved keyword. It is always a plain named-type reference (no
|
||||
// qualified-path, `Closure`, or parameterized continuation), so emit
|
||||
// a raw `type_expr` and return; resolution skips the builtin
|
||||
// reserved keyword. The raw flag rides the type ATOM through the
|
||||
// SAME qualified-path / `Closure` / parameterized continuations as a
|
||||
// bare name (so `` `s2(s64) ``, `` `s2.Inner ``, `` *`s2 `` all
|
||||
// parse); it is threaded onto the final `type_expr` /
|
||||
// `parameterized_type_expr` so resolution skips the builtin
|
||||
// classifier and looks up a `` `s2 ``-declared type (issue 0089).
|
||||
if (self.current.is_raw) {
|
||||
const raw_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = raw_name, .is_raw = true } });
|
||||
}
|
||||
const atom_is_raw = self.current.is_raw;
|
||||
var name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
|
||||
@@ -781,6 +779,7 @@ pub const Parser = struct {
|
||||
return try self.createNode(start, .{ .parameterized_type_expr = .{
|
||||
.name = name,
|
||||
.args = try args.toOwnedSlice(self.allocator),
|
||||
.is_raw = atom_is_raw,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -789,7 +788,7 @@ pub const Parser = struct {
|
||||
for (self.struct_type_params) |tp| {
|
||||
if (std.mem.eql(u8, tp, name)) { is_struct_generic = true; break; }
|
||||
}
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = is_struct_generic } });
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = is_struct_generic, .is_raw = atom_is_raw } });
|
||||
}
|
||||
// Inline struct type in type position: struct { ... }
|
||||
if (self.current.tag == .kw_struct) {
|
||||
@@ -1067,6 +1066,8 @@ pub const Parser = struct {
|
||||
.name = method_name,
|
||||
.type_annotation = null,
|
||||
.value = value,
|
||||
.name_span = method_name_span,
|
||||
.is_raw = method_is_raw,
|
||||
} }));
|
||||
}
|
||||
continue;
|
||||
@@ -1080,6 +1081,13 @@ pub const Parser = struct {
|
||||
return self.fail("expected field name in struct");
|
||||
}
|
||||
const field_start = self.current.loc.start;
|
||||
// Captured for the single-name typed-const path (`name :Type: value`)
|
||||
// below: a struct-body const binds a name like any other decl, so
|
||||
// its name_span + raw flag must travel to the `const_decl` node
|
||||
// (finding 1 — they were being dropped to a 1:1 caret / false
|
||||
// reserved-name reject).
|
||||
const field_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
const field_is_raw = self.current.is_raw;
|
||||
try group_names.append(self.allocator, self.tokenSlice(self.current));
|
||||
self.advance();
|
||||
|
||||
@@ -1104,6 +1112,8 @@ pub const Parser = struct {
|
||||
.name = group_names.items[0],
|
||||
.type_annotation = field_type,
|
||||
.value = value,
|
||||
.name_span = field_name_span,
|
||||
.is_raw = field_is_raw,
|
||||
} }));
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user