diff --git a/examples/0153-types-backtick-const-fn-decl.sx b/examples/0153-types-backtick-const-fn-decl.sx new file mode 100644 index 0000000..4b1c5ba --- /dev/null +++ b/examples/0153-types-backtick-const-fn-decl.sx @@ -0,0 +1,23 @@ +// Backtick raw-identifier escape at the `::` declaration sites: a leading +// backtick makes a CONSTANT name and a FUNCTION name raw, so a reserved type +// spelling (`s2`, `u8`) can be declared and used. Complements examples/0151 +// (var / param / field / global). The backtick fn is callable both via the +// backtick (`` `u8(5) ``) and bare (`u8(5)`) — the bare reserved-name callee +// resolves to the raw fn because its declaration is raw (issue 0089). A *bare* +// `s2 :: …` / `u8 :: …` declaration is still the reserved-name error (see +// examples/1140). +// Regression (issue 0089). +#import "modules/std.sx"; + +// Constant whose name is a reserved type spelling. +`s2 :: 2.5; + +// Function whose name is a reserved type spelling. +`u8 :: (n: s64) -> s64 { return n + 7; } + +main :: () -> s32 { + print("const = {}\n", `s2); + print("fn tick = {}\n", `u8(5)); + print("fn bare = {}\n", u8(5)); + return 0; +} diff --git a/examples/1140-diagnostics-reserved-name-const-fn-decl.sx b/examples/1140-diagnostics-reserved-name-const-fn-decl.sx new file mode 100644 index 0000000..270a594 --- /dev/null +++ b/examples/1140-diagnostics-reserved-name-const-fn-decl.sx @@ -0,0 +1,19 @@ +// A reserved/builtin type-name spelling is rejected as the NAME of a `::` +// declaration too — both a constant (`s2 :: 5`) and a function +// (`u8 :: (…) {…}`). A function name and a const name are binding sites just +// like `s2 := …`; previously the `::` decl forms slipped past the +// reserved-name check, so a bare reserved-name function compiled silently and +// became callable — bypassing the backtick rule that handwritten sx must use. +// The backtick escape (`` `s2 :: … ``, examples/0153) is the only way to spell +// these names; `#import c` foreign decls remain exempt (examples/1220). +// +// Regression (issue 0089). Expected: one error per declaration, each caret on +// the declared name; exit 1. +#import "modules/std.sx"; + +s2 :: 5; +u8 :: (n: s64) -> s64 { return n + 7; } + +main :: () -> s32 { + return 0; +} diff --git a/examples/expected/0153-types-backtick-const-fn-decl.exit b/examples/expected/0153-types-backtick-const-fn-decl.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0153-types-backtick-const-fn-decl.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0153-types-backtick-const-fn-decl.stderr b/examples/expected/0153-types-backtick-const-fn-decl.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0153-types-backtick-const-fn-decl.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0153-types-backtick-const-fn-decl.stdout b/examples/expected/0153-types-backtick-const-fn-decl.stdout new file mode 100644 index 0000000..c0c4c51 --- /dev/null +++ b/examples/expected/0153-types-backtick-const-fn-decl.stdout @@ -0,0 +1,3 @@ +const = 2.500000 +fn tick = 12 +fn bare = 12 diff --git a/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.exit b/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.stderr b/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.stderr new file mode 100644 index 0000000..a1595fa --- /dev/null +++ b/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.stderr @@ -0,0 +1,11 @@ +error: 's2' is a reserved type name and cannot be used as an identifier + --> examples/1140-diagnostics-reserved-name-const-fn-decl.sx:14:1 + | +14 | s2 :: 5; + | ^^ + +error: 'u8' is a reserved type name and cannot be used as an identifier + --> examples/1140-diagnostics-reserved-name-const-fn-decl.sx:15:1 + | +15 | u8 :: (n: s64) -> s64 { return n + 7; } + | ^^ diff --git a/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.stdout b/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.stdout new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/1140-diagnostics-reserved-name-const-fn-decl.stdout @@ -0,0 +1 @@ + diff --git a/issues/0089-backtick-raw-identifier.md b/issues/0089-backtick-raw-identifier.md index ff0e275..c5caafd 100644 --- a/issues/0089-backtick-raw-identifier.md +++ b/issues/0089-backtick-raw-identifier.md @@ -16,6 +16,17 @@ > ([src/ir/semantic_diagnostics.zig]). The backtick works in every identifier > position (local, global, param, field, function name, struct member, later > reference, and all the control-flow/capture/binding forms). +> +> The `::` DECLARATION forms are binding sites too and are equally covered +> (F0.6 attempt-3): a bare reserved-name **constant** (`s2 :: 5`), **function** +> (`s2 :: (…) {…}`, incl. struct/impl methods), or **type** declaration +> (`struct`/`enum`/`union`/`error`/alias/`protocol`/foreign-class/ufcs/namespace) +> is rejected, exactly like `s2 := …`. `ConstDecl`/`FnDecl` carry `is_raw` + +> `name_span` threaded from the parser (`parseConstBinding`/`parseFnDecl`), so the +> backtick form (`` `s2 :: … ``) is exempt; the compiler's own builtin definition +> (`string :: []u8 #builtin`) is the sole non-backtick exemption (a `#builtin` +> constant defines the reserved type). This closed the attempt-2 hole where a +> bare `s2 :: (…) {…}` compiled silently and the call rewrite made it callable. > 2. **`#import c` foreign-name exemption.** `c_import.zig` synthesizes foreign > `#foreign` decls with `Param.is_raw = true`, so generated C param names that > collide with reserved type names (`s1`, `s2`) import unedited. @@ -24,8 +35,11 @@ > one in type position (`x : `s2 = 1`) is a clean parse error ([src/parser.zig] > `parseTypeExpr` atom). A reserved-spelled FUNCTION (backtick-declared or > `#import c` foreign) is bare-callable: `lowerCall` rewrites a `.type_expr` callee -> to an identifier when a function of that name is in scope ([src/ir/lower.zig]), -> so `s2(4)` resolves to the function (`TypeName(val)` is not a cast). A later BARE +> to an identifier when a function **of RAW provenance** of that name is in scope +> ([src/ir/lower.zig]) — the rewrite is scoped to the callee `FnDecl`'s `is_raw` +> flag (F0.6 attempt-3), so it only ever fires for a backtick / `#import c` foreign +> fn (the decl check guarantees no bare reserved-name fn exists), so `s2(4)` +> resolves to the function (`TypeName(val)` is not a cast). A later BARE > reference in value position resolves to the binding; a bare `s2` in type position > is still the type. > @@ -38,7 +52,11 @@ > (foreign param + function-name exemption, bare-callable foreign fn), > `examples/1139-diagnostics-backtick-raw-not-a-type.sx` (negative — raw in type > position), `examples/1119`/`1121`/`1123` (negative — bare reserved binding still -> rejected across all forms). Backtick lexer unit tests in `src/lexer.zig`. +> rejected across all forms), +> `examples/0153-types-backtick-const-fn-decl.sx` (positive — backtick `::` const + +> function decl, bare + backtick call), and +> `examples/1140-diagnostics-reserved-name-const-fn-decl.sx` (negative — bare `::` +> const + function decl rejected). Backtick lexer unit tests in `src/lexer.zig`. > > The original report is preserved below. diff --git a/readme.md b/readme.md index 79b91d8..5473311 100644 --- a/readme.md +++ b/readme.md @@ -106,15 +106,18 @@ z : s32 = ---; // uninitialized ``` Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and can't be used -as bare value identifiers. A leading backtick at the **binding site** escapes one -into a raw identifier — its text drops the backtick and it's never read as a type — -so reserved spellings (and keywords) work as ordinary names. The backtick is needed -only where the name is declared; a later bare reference in value position resolves -to the binding, while a bare `s2` in type position is still the type. It works in -every identifier position (local, global, parameter, field, function name, and the -control-flow / capture / binding forms — destructure, `if`/`while` binding, `for` -capture, match capture, `catch`/`onfail` tag), and a reserved-spelled function is -bare-callable: +as bare identifiers at **any** binding site — a value binding (`:=` / typed local / +parameter), a `::` constant or function declaration, or a `::` type declaration +(`struct` / `enum` / `union` / alias / `protocol` / …) — each is an error +(`s2 :: 5` and `s2 :: (n) { … }` are rejected just like `s2 := 5`). A leading +backtick at the binding site escapes one into a raw identifier — its text drops the +backtick and it's never read as a type — so reserved spellings (and keywords) work +as ordinary names. The backtick is needed only where the name is declared; a later +bare reference in value position resolves to the binding, while a bare `s2` in type +position is still the type. It works in every identifier position (local, global, +parameter, field, function name, constant, and the control-flow / capture / binding +forms — destructure, `if`/`while` binding, `for` capture, match capture, +`catch`/`onfail` tag), and a reserved-spelled function is bare-callable: ```sx `s2 := 2.5; // value identifier "s2", distinct from the s2 type diff --git a/specs.md b/specs.md index 864b0e2..fe69040 100644 --- a/specs.md +++ b/specs.md @@ -17,14 +17,25 @@ Line comments start with `//` and extend to end of line. A spelling that names a builtin type — the arbitrary-width integers `s1`..`s64` / `u1`..`u64`, plus `bool`, `string`, `void`, `f32`, `f64`, `usize`, `isize`, `Any` — -is reserved. A bare value binding (`:=` / typed local / parameter name) spelled as -one of these is rejected: such a spelling parses as a *type*, not a value, so the -address-of / autoref paths would mis-lower it. +is reserved. A bare reserved spelling is rejected at **every binding site** — +anywhere handwritten sx introduces a name: a value binding (`:=` / typed local / +parameter), a `::` **constant** or **function** declaration, and a `::` **type** +declaration (`struct` / `enum` / `union` / `error` / type alias / `protocol` / +foreign class / ufcs alias / namespaced import). A value-spelled-as-type parses as +a *type*, not a value, so its address-of / autoref paths would mis-lower; a +type/const/function name spelled as a builtin would shadow the builtin. The only +exemptions are the backtick escape (below) and `#import c` foreign decls. ```sx -s2 := 2.5; // ERROR: 's2' is a reserved type name and cannot be used as an identifier +s2 := 2.5; // ERROR: 's2' is a reserved type name and cannot be used as an identifier +s2 :: 5; // ERROR — a `::` constant name is a binding site too +s2 :: (n: s64) -> s64 { n } // ERROR — so is a function name +s2 :: struct { x: s64; } // ERROR — and a type-declaration name ``` +(The stdlib's own builtin definitions — e.g. `string :: []u8 #builtin;` — are the +sole exception: a `#builtin` constant defines the reserved type and is allowed.) + #### Backtick raw-identifier escape A leading backtick makes the following identifier **raw**: its text excludes the @@ -52,7 +63,8 @@ capture and index, a match-arm capture, and a `catch` / `onfail` tag binding: ```sx `u8 := 100; // global -`s2 :: (`s1: s64) -> s64 { `s1 } // function name + parameter +`s2 :: 2.5; // constant declaration +`u8 :: (`s1: s64) -> s64 { `s1 } // function name + parameter P :: struct { `s2: f64; } // struct field `u8, rest := pair(); // destructure name if `s16 := maybe() { } // optional binding diff --git a/src/ast.zig b/src/ast.zig index 23b7213..d3b49de 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -131,6 +131,14 @@ pub const FnDecl = struct { type_params: []const StructTypeParam = &.{}, is_arrow: bool = false, call_conv: CallingConvention = .default, + /// Span of the function's name token, for the reserved-type-name decl + /// diagnostic (issue 0089). Synthesized decls (e.g. `#import c` foreign + /// functions) leave it zero. + name_span: Span = .{ .start = 0, .end = 0 }, + /// True when the function NAME was written as a backtick raw identifier + /// (`` `s2 :: … ``) or synthesized by a `#import c` foreign decl. A raw + /// name is exempt from the reserved-type-name binding check (issue 0089). + is_raw: bool = false, }; pub const Param = struct { @@ -307,6 +315,13 @@ pub const ConstDecl = struct { name: []const u8, type_annotation: ?*Node, value: *Node, + /// Span of the constant's name token, for the reserved-type-name decl + /// diagnostic (issue 0089). + name_span: Span = .{ .start = 0, .end = 0 }, + /// True when the constant NAME was written as a backtick raw identifier + /// (`` `s2 :: … ``). A raw name is exempt from the reserved-type-name + /// binding check (issue 0089). + is_raw: bool = false, }; pub const VarDecl = struct { diff --git a/src/c_import.zig b/src/c_import.zig index 29e21a2..f0c09b3 100644 --- a/src/c_import.zig +++ b/src/c_import.zig @@ -156,6 +156,11 @@ pub fn processCImport( .params = try params.toOwnedSlice(allocator), .return_type = ret_node, .body = foreign_body, + // A foreign C function whose own NAME collides with a reserved + // type spelling (`int s2(int);`) is RAW — exempt from the + // reserved-type-name decl check so generated bindings import + // without hand-edits (issue 0089). + .is_raw = true, } }, }; diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 56c0b05..aa536b9 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -6630,17 +6630,25 @@ pub const Lowering = struct { // there is no ambiguity. Rewrite the callee to an identifier so the // normal call machinery resolves it, symmetric to the bare-value // reference that already resolves via scope/globals (issue 0089). + // + // Scoped to RAW provenance: only a backtick (`is_raw`) or `#import c` + // foreign fn declaration may legally carry a reserved-name spelling + // (the decl check rejects every bare reserved-name sx fn). Refusing the + // rewrite for a non-raw match keeps a genuine reserved type spelling a + // type — belt-and-suspenders should any future path ever reintroduce a + // non-raw reserved-name callee. if (c.callee.data == .type_expr) { const tname = c.callee.data.type_expr.name; - const is_fn = self.program_index.fn_ast_map.contains(tname) or - (if (self.scope) |scope| scope.lookupFn(tname) != null else false); - if (is_fn) { + const eff = if (self.scope) |scope| scope.lookupFn(tname) orelse tname else tname; + const fd: ?*const ast.FnDecl = self.program_index.fn_ast_map.get(eff) orelse + self.program_index.fn_ast_map.get(tname); + if (fd) |decl| if (decl.is_raw) { const id_node = self.alloc.create(Node) catch unreachable; id_node.* = .{ .span = c.callee.span, .data = .{ .identifier = .{ .name = tname, .is_raw = true } } }; const rewritten = self.alloc.create(ast.Call) catch unreachable; rewritten.* = .{ .callee = id_node, .args = c.args }; c = rewritten; - } + }; } // Expand default parameter values for bare identifier callees: // when the caller omits trailing positional args, fill them in diff --git a/src/ir/semantic_diagnostics.zig b/src/ir/semantic_diagnostics.zig index 33bee66..9918099 100644 --- a/src/ir/semantic_diagnostics.zig +++ b/src/ir/semantic_diagnostics.zig @@ -127,6 +127,11 @@ pub const UnknownTypeChecker = struct { self.checkBindingNames(dd.value); }, .fn_decl => |fd| { + // A function NAME is a binding site too: a bare reserved-name + // `s2 :: (…) {…}` (free fn or struct/impl method) is rejected, + // exactly like `s2 := …`. Backtick (`` `s2 :: … ``) and + // `#import c` foreign fns set `is_raw` and are exempt (0089). + if (!fd.is_raw) self.checkBindingName(fd.name, fd.name_span); self.checkParamNames(fd.params); self.checkBindingNames(fd.body); }, @@ -197,33 +202,57 @@ pub const UnknownTypeChecker = struct { // `#objc_class` bodied method is lowered (M1.2), so its reserved // param/local names mis-lower the same as any other. .impl_block => |ib| for (ib.methods) |m| self.checkBindingNames(m), - .protocol_decl => |pd| for (pd.methods) |m| { - if (m.default_body) |body| { - for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| { - if (i < m.param_name_is_raw.len and m.param_name_is_raw[i]) continue; - self.checkBindingName(pn, sp); + .protocol_decl => |pd| { + self.checkDeclName(node, pd.name); + for (pd.methods) |m| { + if (m.default_body) |body| { + for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| { + if (i < m.param_name_is_raw.len and m.param_name_is_raw[i]) continue; + self.checkBindingName(pn, sp); + } + self.checkBindingNames(body); } - self.checkBindingNames(body); } }, - .foreign_class_decl => |fcd| for (fcd.members) |member| switch (member) { - .method => |m| if (m.body) |body| { - for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| { - if (i < m.param_name_is_raw.len and m.param_name_is_raw[i]) continue; - self.checkBindingName(pn, sp); - } - self.checkBindingNames(body); - }, - .field, .extends, .implements => {}, + .foreign_class_decl => |fcd| { + // The sx-side alias (left of `::`) is a user-chosen name, so a + // reserved spelling is rejected like any other type decl (0089). + self.checkDeclName(node, fcd.name); + for (fcd.members) |member| switch (member) { + .method => |m| if (m.body) |body| { + for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| { + if (i < m.param_name_is_raw.len and m.param_name_is_raw[i]) continue; + self.checkBindingName(pn, sp); + } + self.checkBindingNames(body); + }, + .field, .extends, .implements => {}, + }; }, // ── Container / control-flow / expression nodes: recurse children // so a binding nested anywhere below is still reached. ── // A namespaced import (`mod :: #import "..."`) is wrapped here, its // module decls held inline; descend so an imported module's // reserved-name binding is rejected too (issue 0077). - .namespace_decl => |nd| for (nd.decls) |d| self.checkBindingNames(d), - .const_decl => |cd| self.checkBindingNames(cd.value), + .namespace_decl => |nd| { + self.checkDeclName(node, nd.name); + for (nd.decls) |d| self.checkBindingNames(d); + }, + .const_decl => |cd| { + // A const BINDS `cd.name`. Reject a bare reserved spelling + // unless it is backtick-raw (`cd.is_raw`) or the compiler's + // blessed builtin definition (`string :: []u8 #builtin`, value + // `.builtin_expr`). When the value node is itself a named decl + // (struct/enum/union/error/fn), that node carries & checks its + // own name on recursion — don't double-check it here (0089). + switch (cd.value.data) { + .builtin_expr, .struct_decl, .enum_decl, .union_decl, .error_set_decl, .fn_decl => {}, + else => if (!cd.is_raw) self.checkBindingName(cd.name, cd.name_span), + } + self.checkBindingNames(cd.value); + }, .struct_decl => |sd| { + self.checkDeclName(node, sd.name); for (sd.methods) |m| self.checkBindingNames(m); for (sd.constants) |c| self.checkBindingNames(c); for (sd.field_defaults) |fdef| if (fdef) |d| self.checkBindingNames(d); @@ -286,12 +315,21 @@ pub const UnknownTypeChecker = struct { .comptime_expr => |ce| self.checkBindingNames(ce.expr), .insert_expr => |ins| self.checkBindingNames(ins.expr), .spread_expr => |se| self.checkBindingNames(se.operand), + // ── Named type / alias / import declarations: a bare reserved + // spelling as the declared name is rejected (issue 0089). These + // have no nested binding sites, so only the name is checked. A + // flat `#import`/`#import c` (name == null) binds nothing. ── + .enum_decl => |ed| self.checkDeclName(node, ed.name), + .union_decl => |ud| self.checkDeclName(node, ud.name), + .error_set_decl => |esd| self.checkDeclName(node, esd.name), + .ufcs_alias => |ua| self.checkDeclName(node, ua.name), + .library_decl => |ld| self.checkDeclName(node, ld.name), + .import_decl => |imp| if (imp.name) |n| self.checkDeclName(node, n), + .c_import_decl => |cid| if (cid.name) |n| self.checkDeclName(node, n), // ── Leaves & pure type-expression nodes: no binding sites below. ── // Type-expression subtrees carry only type names (no value - // bindings); enum / union / error-set declarations carry only field - // types + comptime constants. Listing each tag explicitly (rather - // than an `else`) is what forces a future binding-bearing node to be - // reconsidered here. + // bindings). Listing each tag explicitly (rather than an `else`) is + // what forces a future binding-bearing node to be reconsidered here. .int_literal, .float_literal, .bool_literal, @@ -299,10 +337,6 @@ pub const UnknownTypeChecker = struct { .identifier, .enum_literal, .type_expr, - .enum_decl, - .union_decl, - .error_set_decl, - .import_decl, .array_type_expr, .slice_type_expr, .parameterized_type_expr, @@ -321,13 +355,10 @@ pub const UnknownTypeChecker = struct { .builtin_expr, .compiler_expr, .foreign_expr, - .library_decl, .framework_decl, .function_type_expr, .closure_type_expr, .tuple_type_expr, - .ufcs_alias, - .c_import_decl, => {}, } } @@ -762,6 +793,18 @@ pub const UnknownTypeChecker = struct { if (isReservedTypeName(name)) self.diagnostics.addFmt(.err, span, "'{s}' is a reserved type name and cannot be used as an identifier", .{name}); } + + /// Reserved-name check for a `::` declaration whose own name binds an + /// identifier but carries no dedicated `name_span` field — struct / enum / + /// union / error-set / protocol / foreign-class type decls, ufcs aliases, + /// and namespaced imports (issue 0089). Each such node begins at its name + /// token, so the name's length isolates the caret onto the name. A + /// backtick raw / `#import c` foreign name never reaches here (those forms + /// are exempt at their own decl path). + fn checkDeclName(self: UnknownTypeChecker, node: *const Node, name: []const u8) void { + const span = ast.Span{ .start = node.span.start, .end = node.span.start + @as(u32, @intCast(name.len)) }; + self.checkBindingName(name, span); + } }; /// A binding name collides with a reserved/builtin type name exactly when the diff --git a/src/parser.zig b/src/parser.zig index ef4d279..0ef7bc7 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -152,7 +152,7 @@ pub const Parser = struct { // IDENT :: ... if (self.current.tag == .colon_colon) { self.advance(); - return self.parseConstBinding(name, start); + return self.parseConstBinding(name, name_span, start, name_is_raw); } // IDENT : type : value; (typed constant) @@ -173,7 +173,7 @@ pub const Parser = struct { return self.fail("expected '::', ':=', or ':' after identifier"); } - fn parseConstBinding(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + fn parseConstBinding(self: *Parser, name: []const u8, name_span: ast.Span, start_pos: u32, name_is_raw: bool) anyerror!*Node { // After `::` // Could be: #run expr, enum { ... }, (params) -> type { body }, or expr; @@ -215,7 +215,7 @@ pub const Parser = struct { const inner = try self.parseExpr(); try self.expect(.semicolon); const ct = try self.createNode(run_start, .{ .comptime_expr = .{ .expr = inner } }); - return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = ct } }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = ct, .name_span = name_span, .is_raw = name_is_raw } }); } // Built-in declaration: name :: #builtin; @@ -224,7 +224,7 @@ pub const Parser = struct { self.advance(); try self.expect(.semicolon); const bi = try self.createNode(bi_start, .{ .builtin_expr = {} }); - return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = bi } }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = bi, .name_span = name_span, .is_raw = name_is_raw } }); } // Enum declaration @@ -280,14 +280,14 @@ pub const Parser = struct { // Look ahead: is this a function or an expression starting with `(`? // Heuristic: if after matching parens we see `{` or `->`, it's a function. if (self.isFunctionDef()) { - return self.parseFnDecl(name, start_pos); + return self.parseFnDecl(name, name_span, name_is_raw, start_pos); } } // Bare block shorthand: name :: { body } is equivalent to name :: () { body } if (self.current.tag == .l_brace) { const body = try self.parseBlock(); - return try self.createNode(start_pos, .{ .fn_decl = .{ .name = name, .params = &.{}, .return_type = null, .body = body } }); + return try self.createNode(start_pos, .{ .fn_decl = .{ .name = name, .params = &.{}, .return_type = null, .body = body, .name_span = name_span, .is_raw = name_is_raw } }); } // Otherwise it's a constant expression @@ -299,7 +299,7 @@ pub const Parser = struct { self.advance(); try self.expect(.semicolon); const bi = try self.createNode(bi_start, .{ .builtin_expr = {} }); - return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi, .name_span = name_span, .is_raw = name_is_raw } }); } // name :: type_expr #foreign [lib] ["c_name"]; — foreign with type annotation @@ -325,11 +325,11 @@ pub const Parser = struct { .library_ref = lib_ref, .c_name = c_name, } }); - return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi } }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi, .name_span = name_span, .is_raw = name_is_raw } }); } try self.expect(.semicolon); - return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value } }); + 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 { @@ -1044,10 +1044,12 @@ pub const Parser = struct { if (self.current.tag == .identifier and self.peekNext() == .colon_colon) { const method_start = self.current.loc.start; const method_name = self.tokenSlice(self.current); + const method_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end }; + const method_is_raw = self.current.is_raw; self.advance(); // skip name self.advance(); // skip :: if (self.current.tag == .l_paren and self.isFunctionDef()) { - try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start)); + try methods.append(self.allocator, try self.parseFnDecl(method_name, method_name_span, method_is_raw, method_start)); } else { // Non-function constant: name :: value; const value = try self.parseExpr(); @@ -1672,11 +1674,13 @@ pub const Parser = struct { } const method_start = self.current.loc.start; const method_name = self.tokenSlice(self.current); + const method_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end }; + const method_is_raw = self.current.is_raw; self.advance(); try self.expect(.colon_colon); if (self.current.tag == .l_paren and self.isFunctionDef()) { - try methods.append(self.allocator, try self.parseFnDecl(method_name, method_start)); + try methods.append(self.allocator, try self.parseFnDecl(method_name, method_name_span, method_is_raw, method_start)); } else { return self.fail("expected function declaration in impl block"); } @@ -1900,7 +1904,7 @@ pub const Parser = struct { return try type_params.toOwnedSlice(self.allocator); } - fn parseFnDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + fn parseFnDecl(self: *Parser, name: []const u8, name_span: ast.Span, name_is_raw: bool, start_pos: u32) anyerror!*Node { const params = try self.parseParams(); // Optional return type @@ -1975,6 +1979,8 @@ pub const Parser = struct { .type_params = type_params, .is_arrow = is_arrow, .call_conv = call_conv, + .name_span = name_span, + .is_raw = name_is_raw, } }); } @@ -2043,7 +2049,7 @@ pub const Parser = struct { if (self.current.tag == .colon_colon) { self.advance(); - return self.parseConstBinding(name, start); + return self.parseConstBinding(name, name_span, start, name_is_raw); } if (self.current.tag == .colon_equal) { self.advance();