feat(lang): backtick raw-identifier escape + #import c foreign-name exemption [F0.6]

Reserved type-name spellings (s1, s2, u8, …) can now be used as value
identifiers two ways, resolving issue 0089:

1. Backtick raw identifier: a leading backtick (`s2) lexes to an
   .identifier token carrying a new Token.is_raw flag, with the backtick
   excluded from the text. A raw identifier is never type-classified — the
   parser skips Type.fromName for it — so it is always a value identifier.
   The flag threads to VarDecl.is_raw / Param.is_raw at binding sites, and
   the reserved-type-name check (UnknownTypeChecker) skips raw bindings.
   Because the token tag stays .identifier, the escape works in every
   position (local, global, param, field, fn name, struct member, later
   reference) with no per-site parser change.

2. #import c exemption: c_import.zig synthesizes foreign decls with
   Param.is_raw = true, so generated C param names that collide with
   reserved type names (s1, s2) import unedited.

A bare reserved-name binding in sx still errors (issue 0076 preserved):
the is_raw-gated skip only fires for backtick / foreign names, and a raw
binding's address-of / autoref lowering stays correct because every
occurrence is an .identifier, never a .type_expr.

Tests: examples/0151 (backtick, every position),
examples/1220 (foreign exemption, compiled+run), lexer unit tests.
1119 (bare-binding rejection) stays green. specs.md + readme.md updated.
This commit is contained in:
agra
2026-06-04 17:40:42 +03:00
parent 7911494809
commit 0dbdc530ba
19 changed files with 317 additions and 14 deletions

View File

@@ -146,6 +146,7 @@ pub const Parser = struct {
}
const name = self.tokenSlice(self.current);
const name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
const name_is_raw = self.current.is_raw;
self.advance();
// IDENT :: ...
@@ -158,7 +159,7 @@ pub const Parser = struct {
// IDENT : type = value; (typed variable)
if (self.current.tag == .colon) {
self.advance();
return self.parseTypedBinding(name, name_span, start);
return self.parseTypedBinding(name, name_span, start, name_is_raw);
}
// IDENT := value; (variable)
@@ -166,7 +167,7 @@ pub const Parser = struct {
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = null, .value = value } });
return try self.createNode(start, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = null, .value = value, .is_raw = name_is_raw } });
}
return self.fail("expected '::', ':=', or ':' after identifier");
@@ -383,7 +384,7 @@ pub const Parser = struct {
} });
}
fn parseTypedBinding(self: *Parser, name: []const u8, name_span: ast.Span, start_pos: u32) anyerror!*Node {
fn parseTypedBinding(self: *Parser, name: []const u8, name_span: ast.Span, start_pos: u32, name_is_raw: bool) anyerror!*Node {
// After `name :`
// Parse type
const type_node = try self.parseTypeExpr();
@@ -401,13 +402,13 @@ pub const Parser = struct {
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = value } });
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = value, .is_raw = name_is_raw } });
}
if (self.current.tag == .semicolon) {
// name : type; (default-initialized variable)
self.advance();
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = null } });
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = null, .is_raw = name_is_raw } });
}
if (self.current.tag == .hash_foreign) {
@@ -433,6 +434,7 @@ pub const Parser = struct {
.is_foreign = true,
.foreign_lib = lib_ref,
.foreign_name = c_name,
.is_raw = name_is_raw,
} });
}
@@ -1778,11 +1780,12 @@ pub const Parser = struct {
}
const param_name = self.tokenSlice(self.current);
const param_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
const param_is_raw = self.current.is_raw;
self.advance();
// Optional type annotation: if no ':', infer type from context
if (self.current.tag != .colon) {
const inferred_node = try self.createNode(param_name_span.start, .{ .inferred_type = {} });
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = inferred_node, .is_variadic = is_variadic, .is_comptime = is_ct_param });
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = inferred_node, .is_variadic = is_variadic, .is_comptime = is_ct_param, .is_raw = param_is_raw });
continue;
}
self.advance(); // consume ':'
@@ -1822,7 +1825,7 @@ pub const Parser = struct {
.type_expr, .parameterized_type_expr => true,
else => false,
};
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = param_type, .is_variadic = is_variadic, .is_comptime = is_comptime_param, .is_pack = is_pack, .default_expr = default_expr });
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = param_type, .is_variadic = is_variadic, .is_comptime = is_comptime_param, .is_pack = is_pack, .default_expr = default_expr, .is_raw = param_is_raw });
}
for (params.items, 0..) |param, i| {
if (param.is_variadic and i != params.items.len - 1) {
@@ -2023,6 +2026,7 @@ pub const Parser = struct {
const start = self.current.loc.start;
const name = self.tokenSlice(self.current);
const name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
const name_is_raw = self.current.is_raw;
self.advance();
if (self.current.tag == .colon_colon) {
@@ -2033,11 +2037,11 @@ pub const Parser = struct {
self.advance();
const value = try self.parseExpr();
try self.expectSemicolonAfter(value);
return try self.createNode(start, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = null, .value = value } });
return try self.createNode(start, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = null, .value = value, .is_raw = name_is_raw } });
}
if (self.current.tag == .colon) {
self.advance();
return self.parseTypedBinding(name, name_span, start);
return self.parseTypedBinding(name, name_span, start, name_is_raw);
}
// Multi-target assignment: ident, expr, ... = expr, expr, ...;
@@ -2686,8 +2690,11 @@ pub const Parser = struct {
},
.identifier => {
const name = self.tokenSlice(self.current);
// Check if this identifier is a type name (e.g. s32, u8, s128)
if (Type.fromName(name) != null) {
// A backtick raw identifier (`` `s2 ``) is NEVER type-classified —
// it is always a value identifier, bypassing the reserved-type-name
// rule (issue 0089). Only a bare spelling is checked for a type name
// (e.g. s32, u8, s128).
if (!self.current.is_raw and Type.fromName(name) != null) {
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
}