feat(lang): reserved-name check covers :: const/fn/type decls + scope call rewrite to raw provenance [F0.6]
A bare reserved-type-name `::` declaration was silently accepted, and the
attempt-2 lowerCall rewrite then made a bare `s2 :: (…) {…}` function callable —
bypassing the backtick rule for handwritten sx. The reserved-name binding check
covered `:=` / typed-local / param / captures but NOT the `::` declaration form.
- ast: `ConstDecl`/`FnDecl` carry `is_raw` + `name_span` threaded from the parser
(parseConstBinding / parseFnDecl, all call sites incl. struct/impl methods).
- semantic_diagnostics: reject a bare reserved spelling at EVERY declaration-name
site — const, function (incl. struct/impl methods), struct/enum/union/error-set,
protocol, foreign-class, ufcs alias, namespaced/library/c-import name. Backtick
(`is_raw`) and the compiler's `#builtin` definition (`string :: []u8 #builtin`)
are the only exemptions; a value whose node is itself a named decl defers to
that node's own check.
- c_import: synthesized foreign fn_decls are `is_raw = true`, so a C function
whose own name collides with a reserved spelling (`int s2(int);`) imports and
bare-calls unedited.
- lower: scope the `.type_expr`→`.identifier` call rewrite to a callee FnDecl of
RAW provenance (`is_raw`) — only a backtick / `#import c` foreign fn can carry a
reserved-name spelling, so a non-raw match never gets rewritten.
- examples: 0153 (positive — backtick `::` const + fn, bare + tick call), 1140
(negative — bare `::` const + fn rejected).
- docs: specs.md + readme.md state the backtick is required at every binding site
including `::` const / function / type declarations; issue 0089 banner updated.
This commit is contained in:
23
examples/0153-types-backtick-const-fn-decl.sx
Normal file
23
examples/0153-types-backtick-const-fn-decl.sx
Normal file
@@ -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;
|
||||
}
|
||||
19
examples/1140-diagnostics-reserved-name-const-fn-decl.sx
Normal file
19
examples/1140-diagnostics-reserved-name-const-fn-decl.sx
Normal file
@@ -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;
|
||||
}
|
||||
1
examples/expected/0153-types-backtick-const-fn-decl.exit
Normal file
1
examples/expected/0153-types-backtick-const-fn-decl.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
const = 2.500000
|
||||
fn tick = 12
|
||||
fn bare = 12
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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; }
|
||||
| ^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
21
readme.md
21
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
|
||||
|
||||
22
specs.md
22
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
|
||||
|
||||
15
src/ast.zig
15
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 {
|
||||
|
||||
@@ -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,
|
||||
} },
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user