feat(lang): universal backtick raw identifier — valid in value, decl, AND type position [F0.6]

AGRA ruling (attempt 4): `` `name `` is THE LITERAL identifier `name`, usable in
EVERY position — the backtick only means "treat this token as a plain identifier,
never the reserved keyword/type", and is never part of the name's text.

- Raw in TYPE position is now VALID (reverses attempt-2 "raw is not a type"):
  `parseTypeExpr` emits a raw `type_expr`; `TypeResolver.resolveNamed` gains a
  `skip_builtin` flag (threaded from `te.is_raw` via lower.zig + type_bridge) so a
  `` `s2 `` reference resolves to a `` `s2 ``-declared type (struct/enum/union/alias),
  else a normal "unknown type 's2'" error (reportIfUnknownType skips the builtin
  exemption when raw). Bare `s2` in type position stays the builtin int.
- Every declaration-name site is is_raw-exemptible: `is_raw` added to TypeExpr +
  StructDecl/EnumDecl/UnionDecl/ErrorSetDecl/ProtocolDecl/ForeignClassDecl/UfcsAlias/
  NamespaceDecl/ImportDecl/CImportDecl/LibraryDecl; parser threads name_is_raw to
  every decl parse fn; namespace imports carry it through imports.addNamespace.
  Typed-const path (`` `s2 : s64 : 5 ``) now threads name_span+is_raw (fixes the
  1:1-caret bug).
- Check<->exemption made structurally symmetric: checkBindingName/checkDeclName take
  is_raw as a REQUIRED argument and skip inside the check, so no call site can
  validate a name without honoring the exemption (the desync cause of prior rounds).
- Bare reserved-name declarations of every kind still error (0076 preserved);
  `#import c` foreign names stay auto-raw + bare-callable.

specs.md + readme.md updated to the universal model. issue 0089 RESOLVED banner
rewritten. Examples: replace 1139 (raw-not-a-type) with 0154 (raw type reference);
add 0155 (typed const + union tag) and 1141 (bare type-decl negatives).
Gate: zig build + zig build test + run_examples (426 passed, 0 failed).
This commit is contained in:
agra
2026-06-04 20:27:53 +03:00
parent c0e1a5db82
commit 023971cae5
26 changed files with 441 additions and 212 deletions

View File

@@ -88,7 +88,7 @@ pub const Parser = struct {
// Check for #import c { ... } (C import block)
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "c") and self.peekNext() == .l_brace) {
self.advance(); // consume 'c'
return self.parseCImportBlock(start, null);
return self.parseCImportBlock(start, null, false);
}
if (self.current.tag != .string_literal) {
return self.fail("expected string path after '#import'");
@@ -183,7 +183,7 @@ pub const Parser = struct {
// Check for name :: #import c { ... }
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "c") and self.peekNext() == .l_brace) {
self.advance(); // consume 'c'
return self.parseCImportBlock(start_pos, name);
return self.parseCImportBlock(start_pos, name, name_is_raw);
}
if (self.current.tag != .string_literal) {
return self.fail("expected string path after '#import'");
@@ -192,7 +192,7 @@ pub const Parser = struct {
const path = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } });
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name, .is_raw = name_is_raw } });
}
// Named library: name :: #library "libname";
@@ -205,7 +205,7 @@ pub const Parser = struct {
const lib_name = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .library_decl = .{ .lib_name = lib_name, .name = name } });
return try self.createNode(start_pos, .{ .library_decl = .{ .lib_name = lib_name, .name = name, .is_raw = name_is_raw } });
}
// Compile-time evaluation: name :: #run expr;
@@ -229,22 +229,22 @@ pub const Parser = struct {
// Enum declaration
if (self.current.tag == .kw_enum) {
return self.parseEnumDecl(name, start_pos);
return self.parseEnumDecl(name, start_pos, name_is_raw);
}
// Error-set declaration: name :: error { TagA, TagB }
if (self.current.tag == .kw_error) {
return self.parseErrorSetDecl(name, start_pos);
return self.parseErrorSetDecl(name, start_pos, name_is_raw);
}
// Struct declaration
if (self.current.tag == .kw_struct) {
return self.parseStructDecl(name, start_pos);
return self.parseStructDecl(name, start_pos, name_is_raw);
}
// Protocol declaration
if (self.current.tag == .kw_protocol) {
return self.parseProtocolDecl(name, start_pos);
return self.parseProtocolDecl(name, start_pos, name_is_raw);
}
// Foreign-type binding with optional prefix modifiers:
@@ -255,12 +255,12 @@ pub const Parser = struct {
// `#foreign` flips that to "reference an existing class on the foreign side."
// `#jni_main` flags the class as the launchable entry (Android Activity).
if (self.tryParseForeignClassPrefix()) |prefix| {
return self.parseForeignClassDecl(name, start_pos, prefix.runtime, prefix.is_foreign, prefix.is_main);
return self.parseForeignClassDecl(name, start_pos, prefix.runtime, prefix.is_foreign, prefix.is_main, name_is_raw);
}
// C-style union declaration
if (self.current.tag == .kw_union) {
return self.parseUnionDecl(name, start_pos);
return self.parseUnionDecl(name, start_pos, name_is_raw);
}
// UFCS alias: name :: ufcs target;
@@ -272,7 +272,7 @@ pub const Parser = struct {
const target = self.tokenSlice(self.current);
self.advance();
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .ufcs_alias = .{ .name = name, .target = target } });
return try self.createNode(start_pos, .{ .ufcs_alias = .{ .name = name, .target = target, .is_raw = name_is_raw } });
}
// Function declaration: (params) -> type { body } or () { body }
@@ -332,7 +332,7 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value, .name_span = name_span, .is_raw = name_is_raw } });
}
fn parseCImportBlock(self: *Parser, start: u32, name: ?[]const u8) anyerror!*Node {
fn parseCImportBlock(self: *Parser, start: u32, name: ?[]const u8, name_is_raw: bool) anyerror!*Node {
try self.expect(.l_brace);
var includes = std.ArrayList([]const u8).empty;
var sources = std.ArrayList([]const u8).empty;
@@ -381,6 +381,7 @@ pub const Parser = struct {
.defines = try defines.toOwnedSlice(self.allocator),
.flags = try flags.toOwnedSlice(self.allocator),
.name = name,
.is_raw = name_is_raw,
} });
}
@@ -394,7 +395,7 @@ pub const Parser = struct {
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = type_node, .value = value } });
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = type_node, .value = value, .name_span = name_span, .is_raw = name_is_raw } });
}
if (self.current.tag == .equal) {
@@ -629,11 +630,16 @@ pub const Parser = struct {
}
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
// A backtick raw identifier (`` `s2 ``) is a VALUE-name escape; it is
// never a type. Reject it in type position rather than silently
// type-classifying it (issue 0089).
// 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
// classifier and looks up a `` `s2 ``-declared type (issue 0089).
if (self.current.is_raw) {
return self.failFmt("`{s}` is a raw identifier, not a type — the backtick escape names a value, never a type", .{self.tokenSlice(self.current)});
const raw_name = self.tokenSlice(self.current);
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = raw_name, .is_raw = true } });
}
var name = self.tokenSlice(self.current);
self.advance();
@@ -787,20 +793,20 @@ pub const Parser = struct {
}
// Inline struct type in type position: struct { ... }
if (self.current.tag == .kw_struct) {
return try self.parseStructDecl("__anon", start);
return try self.parseStructDecl("__anon", start, false);
}
// Inline C-style union in type position: union { ... }
if (self.current.tag == .kw_union) {
return try self.parseUnionDecl("__anon", start);
return try self.parseUnionDecl("__anon", start, false);
}
// Inline enum type in type position: enum { ... }
if (self.current.tag == .kw_enum) {
return try self.parseEnumDecl("__anon", start);
return try self.parseEnumDecl("__anon", start, false);
}
return self.fail("expected type name");
}
fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
self.advance(); // skip 'enum'
// Check for 'flags' modifier: enum flags { ... }
@@ -874,10 +880,11 @@ pub const Parser = struct {
.is_flags = is_flags,
.variant_values = if (has_any_value) try variant_values.toOwnedSlice(self.allocator) else &.{},
.backing_type = backing_type,
.is_raw = name_is_raw,
} });
}
fn parseErrorSetDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
fn parseErrorSetDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
self.advance(); // skip 'error'
try self.expect(.l_brace);
var tag_names = std.ArrayList([]const u8).empty;
@@ -899,10 +906,11 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .error_set_decl = .{
.name = name,
.tag_names = try tag_names.toOwnedSlice(self.allocator),
.is_raw = name_is_raw,
} });
}
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
self.advance(); // skip 'union'
try self.expect(.l_brace);
var field_names = std.ArrayList([]const u8).empty;
@@ -914,7 +922,7 @@ pub const Parser = struct {
const anon_field = try std.fmt.allocPrint(self.allocator, "__anon_{d}", .{anon_idx});
anon_idx += 1;
const anon_struct_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, anon_field });
const struct_node = try self.parseStructDecl(anon_struct_name, self.current.loc.start);
const struct_node = try self.parseStructDecl(anon_struct_name, self.current.loc.start, false);
try field_names.append(self.allocator, anon_field);
try field_types.append(self.allocator, struct_node);
if (self.current.tag == .semicolon) {
@@ -942,10 +950,11 @@ pub const Parser = struct {
.name = name,
.field_names = try field_names.toOwnedSlice(self.allocator),
.field_types = try field_types.toOwnedSlice(self.allocator),
.is_raw = name_is_raw,
} });
}
fn parseStructDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
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
@@ -1133,10 +1142,11 @@ pub const Parser = struct {
.using_entries = try using_entries.toOwnedSlice(self.allocator),
.methods = try methods.toOwnedSlice(self.allocator),
.constants = try constants.toOwnedSlice(self.allocator),
.is_raw = name_is_raw,
} });
}
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
self.advance(); // skip 'protocol'
// Optional type params: protocol(Target: Type, U: Type) { ... }
@@ -1249,6 +1259,7 @@ pub const Parser = struct {
.methods = try methods.toOwnedSlice(self.allocator),
.is_inline = is_inline,
.type_params = try type_params.toOwnedSlice(self.allocator),
.is_raw = name_is_raw,
} });
}
@@ -1335,7 +1346,7 @@ pub const Parser = struct {
};
}
fn parseForeignClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.ForeignRuntime, is_foreign: bool, is_main: bool) anyerror!*Node {
fn parseForeignClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.ForeignRuntime, is_foreign: bool, is_main: bool, name_is_raw: bool) anyerror!*Node {
self.advance(); // skip directive token
try self.expect(.l_paren);
@@ -1576,6 +1587,7 @@ pub const Parser = struct {
.members = try members.toOwnedSlice(self.allocator),
.is_foreign = is_foreign,
.is_main = is_main,
.is_raw = name_is_raw,
} });
}
@@ -2820,15 +2832,15 @@ pub const Parser = struct {
},
.kw_struct => {
// Anonymous struct expression: struct { value: T; count: u32; }
return try self.parseStructDecl("__anon", start);
return try self.parseStructDecl("__anon", start, false);
},
.kw_enum => {
// Anonymous enum expression: enum { variant: T; other: u32; }
return try self.parseEnumDecl("__anon", start);
return try self.parseEnumDecl("__anon", start, false);
},
.kw_union => {
// Anonymous C-style union expression: union { f: f32; i: s32; }
return try self.parseUnionDecl("__anon", start);
return try self.parseUnionDecl("__anon", start, false);
},
.kw_if => {
return self.parseIfExpr();