Merge branch 'dist-foundation' into flow/sx-foundation/NL.2

This commit is contained in:
agra
2026-06-04 23:13:50 +03:00
78 changed files with 1861 additions and 177 deletions

View File

@@ -6633,10 +6633,38 @@ pub const Lowering = struct {
// ── Calls ───────────────────────────────────────────────────────
fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
var c = c_in;
// A bare reserved-type-name spelling in call position parses as a
// `.type_expr` (e.g. `s2(4)`), but if a function of that name is in
// scope — a backtick-declared sx fn or a `#import c` foreign fn whose C
// name collides with a reserved type spelling — it is a CALL to that
// function. `TypeName(val)` is not a cast (casts are `cast(T, val)`), so
// 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 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
// from the callee's `param: T = expr` declarations.
var c = c_in;
if (self.expandCallDefaults(c)) |expanded| c = expanded;
// Check reflection builtins first (before lowering args — some args are type names, not values)
if (c.callee.data == .identifier) {
@@ -11876,8 +11904,8 @@ pub const Lowering = struct {
// type_bridge, which now takes the alias map as an explicit argument
// (the `TypeTable.aliases` borrow is gone, A2.3).
switch (node.data) {
.type_expr => |te| return self.typeResolver().resolveName(te.name),
.identifier => |id| return self.typeResolver().resolveName(id.name),
.type_expr => |te| return self.typeResolver().resolveName(te.name, te.is_raw),
.identifier => |id| return self.typeResolver().resolveName(id.name, id.is_raw),
// A non-spread tuple literal in a type position is a tuple-type
// literal (`(s32, s32)`); validate its elements are types and reject
// non-type elements loudly (issue 0067).
@@ -12041,8 +12069,10 @@ pub const Lowering = struct {
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
const table = &self.module.types;
// Vector(N, T) — built-in parameterized type
if (std.mem.eql(u8, base_name, "Vector")) {
// Vector(N, T) — built-in parameterized type. A backtick raw base
// (`` `Vector(…) ``) is the LITERAL user type named `Vector`, so it
// skips this intrinsic and resolves through the template map (0089).
if (!pt.is_raw and std.mem.eql(u8, base_name, "Vector")) {
if (pt.args.len == 2) {
const length = self.resolveVectorLane(pt.args[0]) orelse return .unresolved;
const elem = self.resolveTypeWithBindings(pt.args[1]);

View File

@@ -116,15 +116,25 @@ pub const UnknownTypeChecker = struct {
if (node.source_file) |sf| self.diagnostics.current_source_file = sf;
switch (node.data) {
// ── Binding-introducing nodes: check the name(s), then recurse. ──
// Every site passes the node's own `is_raw` straight to the check —
// never an `if (!is_raw)` call-site guard — so the check and its
// exemption are one operation that cannot be threaded apart (0089).
.var_decl => |vd| {
self.checkBindingName(vd.name, vd.name_span);
self.checkBindingName(vd.name, vd.name_span, vd.is_raw);
if (vd.value) |v| self.checkBindingNames(v);
},
.destructure_decl => |dd| {
for (dd.names, dd.name_spans) |n, sp| self.checkBindingName(n, sp);
for (dd.names, dd.name_spans, dd.name_is_raw) |n, sp, raw| {
self.checkBindingName(n, sp, raw);
}
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).
self.checkBindingName(fd.name, fd.name_span, fd.is_raw);
self.checkParamNames(fd.params);
self.checkBindingNames(fd.body);
},
@@ -133,23 +143,23 @@ pub const UnknownTypeChecker = struct {
self.checkBindingNames(lm.body);
},
.param => |p| {
self.checkBindingName(p.name, p.name_span);
self.checkBindingName(p.name, p.name_span, p.is_raw);
if (p.default_expr) |de| self.checkBindingNames(de);
},
.if_expr => |ie| {
if (ie.binding_name) |bn| self.checkBindingName(bn, ie.binding_span);
if (ie.binding_name) |bn| self.checkBindingName(bn, ie.binding_span, ie.binding_is_raw);
self.checkBindingNames(ie.condition);
self.checkBindingNames(ie.then_branch);
if (ie.else_branch) |e| self.checkBindingNames(e);
},
.while_expr => |we| {
if (we.binding_name) |bn| self.checkBindingName(bn, we.binding_span);
if (we.binding_name) |bn| self.checkBindingName(bn, we.binding_span, we.binding_is_raw);
self.checkBindingNames(we.condition);
self.checkBindingNames(we.body);
},
.for_expr => |fe| {
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, fe.capture_span);
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span);
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, fe.capture_span, fe.capture_is_raw);
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span, fe.index_is_raw);
self.checkBindingNames(fe.iterable);
if (fe.range_end) |re| self.checkBindingNames(re);
self.checkBindingNames(fe.body);
@@ -157,23 +167,23 @@ pub const UnknownTypeChecker = struct {
.match_expr => |me| {
self.checkBindingNames(me.subject);
for (me.arms) |arm| {
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span, arm.capture_is_raw);
if (arm.pattern) |p| self.checkBindingNames(p);
self.checkBindingNames(arm.body);
}
},
.match_arm => |arm| {
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span, arm.capture_is_raw);
if (arm.pattern) |p| self.checkBindingNames(p);
self.checkBindingNames(arm.body);
},
.catch_expr => |ce| {
if (ce.binding) |b| self.checkBindingName(b, ce.binding_span);
if (ce.binding) |b| self.checkBindingName(b, ce.binding_span, ce.binding_is_raw);
self.checkBindingNames(ce.operand);
self.checkBindingNames(ce.body);
},
.onfail_stmt => |os| {
if (os.binding) |b| self.checkBindingName(b, os.binding_span);
if (os.binding) |b| self.checkBindingName(b, os.binding_span, os.binding_is_raw);
self.checkBindingNames(os.body);
},
// impl / protocol-default / foreign-class method bodies: each
@@ -181,27 +191,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) |pn, sp| self.checkBindingName(pn, sp);
self.checkBindingNames(body);
.protocol_decl => |pd| {
self.checkDeclName(node, pd.name, pd.is_raw);
for (pd.methods) |m| {
if (m.default_body) |body| {
for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| {
const raw = i < m.param_name_is_raw.len and m.param_name_is_raw[i];
self.checkBindingName(pn, sp, raw);
}
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) |pn, sp| 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, fcd.is_raw);
for (fcd.members) |member| switch (member) {
.method => |m| if (m.body) |body| {
for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| {
const raw = i < m.param_name_is_raw.len and m.param_name_is_raw[i];
self.checkBindingName(pn, sp, raw);
}
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, nd.is_raw);
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 => self.checkBindingName(cd.name, cd.name_span, cd.is_raw),
}
self.checkBindingNames(cd.value);
},
.struct_decl => |sd| {
self.checkDeclName(node, sd.name, sd.is_raw);
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);
@@ -264,12 +304,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, ed.is_raw),
.union_decl => |ud| self.checkDeclName(node, ud.name, ud.is_raw),
.error_set_decl => |esd| self.checkDeclName(node, esd.name, esd.is_raw),
.ufcs_alias => |ua| self.checkDeclName(node, ua.name, ua.is_raw),
.library_decl => |ld| self.checkDeclName(node, ld.name, ld.is_raw),
.import_decl => |imp| if (imp.name) |n| self.checkDeclName(node, n, imp.is_raw),
.c_import_decl => |cid| if (cid.name) |n| self.checkDeclName(node, n, cid.is_raw),
// ── 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,
@@ -277,10 +326,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,
@@ -299,13 +344,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,
=> {},
}
}
@@ -316,7 +358,10 @@ pub const UnknownTypeChecker = struct {
/// (a lambda default), so recurse into it.
fn checkParamNames(self: UnknownTypeChecker, params: []const ast.Param) void {
for (params) |p| {
self.checkBindingName(p.name, p.name_span);
// A backtick raw param (`` (`s2: T) ``) or a `#import c` foreign
// param is exempt from the reserved-type-name rule (issue 0089) —
// the exemption is honored inside `checkBindingName` via `p.is_raw`.
self.checkBindingName(p.name, p.name_span, p.is_raw);
if (p.default_expr) |de| self.checkBindingNames(de);
}
}
@@ -653,8 +698,8 @@ pub const UnknownTypeChecker = struct {
switch (node.data) {
// A `$`-prefixed name (`-> $R`) introduces/references a generic type
// param inline — always valid in a type position.
.type_expr => |te| if (!te.is_generic) self.reportIfUnknownType(te.name, node.span, declared, in_scope, type_vals),
.identifier => |id| self.reportIfUnknownType(id.name, node.span, declared, in_scope, type_vals),
.type_expr => |te| if (!te.is_generic) self.reportIfUnknownType(te.name, node.span, declared, in_scope, type_vals, te.is_raw),
.identifier => |id| self.reportIfUnknownType(id.name, node.span, declared, in_scope, type_vals, id.is_raw),
.pointer_type_expr => |pt| self.checkTypeNodeForUnknown(pt.pointee_type, declared, in_scope, type_vals),
.many_pointer_type_expr => |mp| self.checkTypeNodeForUnknown(mp.element_type, declared, in_scope, type_vals),
.slice_type_expr => |st| self.checkTypeNodeForUnknown(st.element_type, declared, in_scope, type_vals),
@@ -698,11 +743,17 @@ pub const UnknownTypeChecker = struct {
declared: *std.StringHashMap(void),
in_scope: []const ast.StructTypeParam,
type_vals: []const []const u8,
is_raw: bool,
) void {
// Only bare identifiers are validated. Inline-spelled compound types
// (`[:0]u8`, `mod.Type`, …) carry non-identifier characters — trust them.
if (!isIdentLike(name)) return;
if (isBuiltinTypeName(name)) return;
// A backtick raw reference (`` `s2 ``) is the LITERAL name used as a
// type — explicitly NOT the builtin/reserved spelling — so it must
// resolve to a `` `s2 ``-declared type, else a normal "unknown type"
// error. Skip the builtin-name exemption that would otherwise wave a
// bare `s2` through (issue 0089).
if (!is_raw and isBuiltinTypeName(name)) return;
for (in_scope) |tp| if (std.mem.eql(u8, tp.name, name)) return;
if (declared.contains(name)) return;
// Registered as a real (non-stub) type — covers imported concrete
@@ -734,10 +785,31 @@ pub const UnknownTypeChecker = struct {
/// (LLVM verifier abort, or a silent mutation-losing copy). Rejecting the
/// name here, before lowering, keeps the `.identifier`-only address-of paths
/// correct without any lowering special-case.
fn checkBindingName(self: UnknownTypeChecker, name: []const u8, span: ?ast.Span) void {
/// `is_raw` is a REQUIRED argument, not a call-site guard: the exemption
/// lives INSIDE the check so no caller can validate a name without also
/// honoring the backtick / `#import c` foreign exemption. This is what keeps
/// the check and the exemption from desyncing — the recurring failure of the
/// earlier attempts, where each site threaded an `if (!is_raw)` guard
/// separately and one was forgotten (issue 0089).
fn checkBindingName(self: UnknownTypeChecker, name: []const u8, span: ?ast.Span, is_raw: bool) void {
if (is_raw) return;
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 (`createNode(name_start, …)`), so the name's length isolates the
/// caret onto the name — a single source for the span, no separate stored
/// field to drift from `node.span`. `is_raw` is REQUIRED, exactly as in
/// `checkBindingName`: a backtick raw / `#import c` foreign name is exempt
/// by construction.
fn checkDeclName(self: UnknownTypeChecker, node: *const Node, name: []const u8, is_raw: bool) void {
const span = ast.Span{ .start = node.span.start, .end = node.span.start + @as(u32, @intCast(name.len)) };
self.checkBindingName(name, span, is_raw);
}
};
/// A binding name collides with a reserved/builtin type name exactly when the

View File

@@ -107,8 +107,8 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
const n = node orelse return .unresolved;
const si = StatelessInner{ .table = table, .alias_map = alias_map, .consts = consts };
return switch (n.data) {
.type_expr => |te| resolveTypeName(te.name, table, alias_map),
.identifier => |id| resolveTypeName(id.name, table, alias_map),
.type_expr => |te| resolveTypeName(te.name, table, alias_map, te.is_raw),
.identifier => |id| resolveTypeName(id.name, table, alias_map, id.is_raw),
// Structural shapes (`*T`/`[*]T`/`[]T`/`?T`/`[N]T`, functions, plain
// closures, plain tuples) are owned by the single canonical
// `TypeResolver.resolveCompound` — no independent compound algorithm
@@ -174,8 +174,9 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
/// Resolve a bare type name. The algorithm lives in `type_resolver.zig`
/// (`TypeResolver.resolveNamed`, the single source); `type_bridge` forwards the
/// caller-threaded `alias_map` (the single-source `ProgramIndex.type_alias_map`).
fn resolveTypeName(name: []const u8, table: *TypeTable, alias_map: AliasMap) TypeId {
return type_resolver.TypeResolver.resolveNamed(name, table, alias_map);
/// `skip_builtin` carries the backtick raw escape (issue 0089).
fn resolveTypeName(name: []const u8, table: *TypeTable, alias_map: AliasMap, skip_builtin: bool) TypeId {
return type_resolver.TypeResolver.resolveNamed(name, table, alias_map, skip_builtin);
}
/// Builtin primitive keyword → TypeId. The keyword table now lives in
@@ -535,7 +536,7 @@ fn resolveInlineErrorSet(esd: *const ast.ErrorSetDecl, table: *TypeTable) TypeId
/// resolves to the same empty inferred set, which is correct while no
/// function raises (E1.3+).
fn resolveErrorType(ete: *const ast.ErrorTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
if (ete.name) |name| return resolveTypeName(name, table, alias_map);
if (ete.name) |name| return resolveTypeName(name, table, alias_map, false);
// `!` is not a legal type/identifier name, so this reserved StringId can
// never collide with a user-declared set.
const name_id = table.internString("!");

View File

@@ -144,21 +144,33 @@ test "TypeResolver.resolveName resolves aliases via ProgramIndex (not the TypeTa
try index.type_alias_map.put("NodeRef", ptr_s64); // alias → pointer
const tr = TypeResolver{ .alloc = alloc, .types = &table, .diagnostics = null, .index = &index };
try std.testing.expectEqual(@as(TypeId, .u32), tr.resolveName("ShaderHandle"));
try std.testing.expectEqual(ptr_s64, tr.resolveName("NodeRef"));
try std.testing.expectEqual(@as(TypeId, .u32), tr.resolveName("ShaderHandle", false));
try std.testing.expectEqual(ptr_s64, tr.resolveName("NodeRef", false));
// Primitive is checked before alias.
try std.testing.expectEqual(@as(TypeId, .s64), tr.resolveName("s64"));
try std.testing.expectEqual(@as(TypeId, .s64), tr.resolveName("s64", false));
}
test "TypeResolver.resolveNamed: width-int, string-prefix, unknown→stub" {
const alloc = std.testing.allocator;
var table = TypeTable.init(alloc);
defer table.deinit();
try std.testing.expectEqual(table.intern(.{ .signed = 7 }), TypeResolver.resolveNamed("s7", &table, null));
try std.testing.expectEqual(table.ptrTo(.s64), TypeResolver.resolveNamed("*s64", &table, null));
try std.testing.expectEqual(table.intern(.{ .signed = 7 }), TypeResolver.resolveNamed("s7", &table, null, false));
try std.testing.expectEqual(table.ptrTo(.s64), TypeResolver.resolveNamed("*s64", &table, null, false));
// Unknown name, no alias map → empty-struct stub (preserved behavior;
// never `.unresolved`, which is reserved for failed *generic* resolution).
try std.testing.expect(TypeResolver.resolveNamed("Unknown", &table, null) != .unresolved);
try std.testing.expect(TypeResolver.resolveNamed("Unknown", &table, null, false) != .unresolved);
}
test "TypeResolver.resolveNamed: skip_builtin resolves a raw reserved-name type, not the builtin" {
const alloc = std.testing.allocator;
var table = TypeTable.init(alloc);
defer table.deinit();
// A registered user type named "s2" (a reserved int spelling).
const name_id = table.internString("s2");
const user_s2 = table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
// Bare lookup → the builtin 2-bit signed int; raw lookup → the user type.
try std.testing.expectEqual(table.intern(.{ .signed = 2 }), TypeResolver.resolveNamed("s2", &table, null, false));
try std.testing.expectEqual(user_s2, TypeResolver.resolveNamed("s2", &table, null, true));
}
test "TypeResolver.parseWidthInt: every width 1..64, both signs; rejects out-of-range / non-int" {

View File

@@ -287,11 +287,21 @@ pub const TypeResolver = struct {
/// `type_bridge` via the alias map threaded through `resolveAstType`. The
/// stub fall-through preserves long-standing behavior for as-yet-
/// unregistered names.
pub fn resolveNamed(name: []const u8, table: *TypeTable, alias_map: ?*const std.StringHashMap(TypeId)) TypeId {
///
/// `skip_builtin` is the backtick raw-identifier escape (`` `s2 `` in type
/// position, issue 0089): a raw reference is the LITERAL name used as a
/// type, so it bypasses the builtin/reserved classifier and resolves only
/// through registered-type → alias → stub. A bare `s2` keeps the default
/// (`false`) and resolves to the builtin int type. The string-prefix
/// recursion always passes `false`: the inner names (`*T`/`?T`) are bare,
/// never raw.
pub fn resolveNamed(name: []const u8, table: *TypeTable, alias_map: ?*const std.StringHashMap(TypeId), skip_builtin: bool) TypeId {
// Builtin primitive keyword or arbitrary-width integer (`s1`-`s64`,
// `u1`-`u64`) — the single builtin classifier, also reused by the
// numeric-limit accessor intercept.
if (resolveBuiltinName(name, table)) |id| return id;
if (!skip_builtin) {
if (resolveBuiltinName(name, table)) |id| return id;
}
// Sentinel-terminated slice: [:0]u8 → string.
if (name.len >= 5 and name[0] == '[' and name[1] == ':') {
if (std.mem.indexOfScalar(u8, name, ']')) |close| {
@@ -302,15 +312,15 @@ pub const TypeResolver = struct {
}
// Many-pointer: [*]T.
if (name.len >= 4 and name[0] == '[' and name[1] == '*' and name[2] == ']') {
return table.manyPtrTo(resolveNamed(name[3..], table, alias_map));
return table.manyPtrTo(resolveNamed(name[3..], table, alias_map, false));
}
// Pointer: *T.
if (name.len >= 2 and name[0] == '*') {
return table.ptrTo(resolveNamed(name[1..], table, alias_map));
return table.ptrTo(resolveNamed(name[1..], table, alias_map, false));
}
// Optional: ?T.
if (name.len >= 2 and name[0] == '?') {
return table.optionalOf(resolveNamed(name[1..], table, alias_map));
return table.optionalOf(resolveNamed(name[1..], table, alias_map, false));
}
// Named struct/enum/union — already-registered wins, then alias, then
// a fresh empty-struct stub for an as-yet-unregistered name.
@@ -323,8 +333,9 @@ pub const TypeResolver = struct {
}
/// Resolve a bare type name through the canonical alias source
/// (`ProgramIndex.type_alias_map`).
pub fn resolveName(self: TypeResolver, name: []const u8) TypeId {
return resolveNamed(name, self.types, &self.index.type_alias_map);
/// (`ProgramIndex.type_alias_map`). `skip_builtin` carries the backtick raw
/// escape (issue 0089) — see `resolveNamed`.
pub fn resolveName(self: TypeResolver, name: []const u8, skip_builtin: bool) TypeId {
return resolveNamed(name, self.types, &self.index.type_alias_map, skip_builtin);
}
};