feat(lang): universal raw identifier — parser exhaustiveness + raw type continuations + sema/LSP [F0.6]
Closes the remaining three F0.6 findings so the universal backtick raw identifier holds in BOTH classifiers and at EVERY parser construction site. 1. Struct-body constants thread is_raw + name_span. The struct-body const forms (untyped `` `s2 :: 5 `` and typed `` `s2 : T : v ``) built the const_decl node without name_span/is_raw, so a backtick const was falsely rejected and a bare reserved-name const caretted at 1:1. They now capture both. Structural cure: `ast.ConstDecl`'s name_span + is_raw carry NO default, so the compiler rejects any construction site that omits them (mirrors checkBindingName's required `is_raw` arg). FnDecl keeps its defaults — every parser fn_decl routes through parseFnDecl whose `name_is_raw` is a required parameter (equivalent guarantee). 2. Raw identifier in TYPE position flows through the normal continuations. parseTypeExpr no longer returns a terminal type_expr for a raw atom; the raw flag rides the atom through the qualified-path / Closure / parameterized continuations, so `` `s2(s64) ``, `` *`s2 ``, `` ?`s2 `` all parse. ParameterizedTypeExpr carries is_raw; resolveParameterizedWithBindings skips the `Vector` intrinsic when raw. 3. sema/LSP (the second classifier) honors is_raw. Type.fromTypeExpr returns null for a raw type_expr; resolveTypeNode skips the builtin classifier when raw; resolveTypeNameStr takes a skip_builtin arg threaded from te/id.is_raw (compound inner names pass false). A backtick reserved-name annotation now resolves to the user type in the editor index, not the builtin. Tests: examples/0156 (struct-body const), 0157 (parameterized raw type + wrappers), 1142 (bare struct-body const errors, caret on name); src/sema.test.zig pins the LSP raw-type resolution (fail-before verified). Gate: 365 unit tests, 429 examples, 0 failed.
This commit is contained in:
48
src/sema.zig
48
src/sema.zig
@@ -411,10 +411,15 @@ pub const Analyzer = struct {
|
||||
if (tn.data == .parameterized_type_expr) {
|
||||
return .void_type;
|
||||
}
|
||||
// type_expr or identifier — check aliases, enums, structs
|
||||
// type_expr or identifier — check aliases, enums, structs. A raw
|
||||
// reference (`` `s2 ``) skips the builtin classifier and resolves
|
||||
// through user-defined types only (issue 0089).
|
||||
if (tn.data == .type_expr or tn.data == .identifier) {
|
||||
const name = if (tn.data == .type_expr) tn.data.type_expr.name else tn.data.identifier.name;
|
||||
if (Type.fromName(name)) |t| return t;
|
||||
const is_raw = if (tn.data == .type_expr) tn.data.type_expr.is_raw else tn.data.identifier.is_raw;
|
||||
if (!is_raw) {
|
||||
if (Type.fromName(name)) |t| return t;
|
||||
}
|
||||
if (self.type_aliases.get(name)) |target| {
|
||||
if (Type.fromName(target)) |t| return t;
|
||||
if (self.struct_types.contains(target)) return .{ .struct_type = target };
|
||||
@@ -430,9 +435,16 @@ pub const Analyzer = struct {
|
||||
/// Resolve a bare type-name string against the registry (aliases, enums,
|
||||
/// structs), falling back to primitive spellings. Unlike `Type.fromName`,
|
||||
/// this knows user-defined types; returns `unresolved` when it can't place
|
||||
/// the name.
|
||||
fn resolveTypeNameStr(self: *Analyzer, name: []const u8) Type {
|
||||
if (Type.fromName(name)) |t| return t;
|
||||
/// the name. `skip_builtin` is the backtick raw escape (issue 0089) — a raw
|
||||
/// reference (`` `s2 ``) bypasses the builtin/reserved classifier and
|
||||
/// resolves only through user-defined types, mirroring the codegen-side
|
||||
/// `TypeResolver.resolveNamed`. Inner names of compound shapes
|
||||
/// (pointer/slice element/pointee) are always bare, so their callers pass
|
||||
/// `false`.
|
||||
fn resolveTypeNameStr(self: *Analyzer, name: []const u8, skip_builtin: bool) Type {
|
||||
if (!skip_builtin) {
|
||||
if (Type.fromName(name)) |t| return t;
|
||||
}
|
||||
if (self.type_aliases.get(name)) |target| {
|
||||
if (Type.fromName(target)) |t| return t;
|
||||
if (self.struct_types.contains(target)) return .{ .struct_type = target };
|
||||
@@ -460,8 +472,8 @@ pub const Analyzer = struct {
|
||||
/// registry; the element name is resolved lazily at index/field time.
|
||||
fn fieldType(self: *Analyzer, node: *Node) Type {
|
||||
return switch (node.data) {
|
||||
.type_expr => |te| self.resolveTypeNameStr(te.name),
|
||||
.identifier => |id| self.resolveTypeNameStr(id.name),
|
||||
.type_expr => |te| self.resolveTypeNameStr(te.name, te.is_raw),
|
||||
.identifier => |id| self.resolveTypeNameStr(id.name, id.is_raw),
|
||||
.many_pointer_type_expr => |mp| .{ .many_pointer_type = .{ .element_name = self.typeExprName(mp.element_type) } },
|
||||
.pointer_type_expr => |p| .{ .pointer_type = .{ .pointee_name = self.typeExprName(p.pointee_type) } },
|
||||
.slice_type_expr => |s| .{ .slice_type = .{ .element_name = self.typeExprName(s.element_type) } },
|
||||
@@ -476,15 +488,15 @@ pub const Analyzer = struct {
|
||||
/// pointee first (so `*List(Move)` still iterates `Move`).
|
||||
fn elementTypeOf(self: *Analyzer, ty: Type) ?Type {
|
||||
return switch (ty) {
|
||||
.array_type => |i| self.resolveTypeNameStr(i.element_name),
|
||||
.slice_type => |i| self.resolveTypeNameStr(i.element_name),
|
||||
.many_pointer_type => |i| self.resolveTypeNameStr(i.element_name),
|
||||
.pointer_type => |i| self.elementTypeOf(self.resolveTypeNameStr(i.pointee_name)),
|
||||
.array_type => |i| self.resolveTypeNameStr(i.element_name, false),
|
||||
.slice_type => |i| self.resolveTypeNameStr(i.element_name, false),
|
||||
.many_pointer_type => |i| self.resolveTypeNameStr(i.element_name, false),
|
||||
.pointer_type => |i| self.elementTypeOf(self.resolveTypeNameStr(i.pointee_name, false)),
|
||||
.struct_type => |name| blk: {
|
||||
const info = self.struct_types.get(name) orelse break :blk null;
|
||||
for (info.field_names, info.field_types) |fname, fty| {
|
||||
if (std.mem.eql(u8, fname, "items") and fty == .many_pointer_type) {
|
||||
break :blk self.resolveTypeNameStr(fty.many_pointer_type.element_name);
|
||||
break :blk self.resolveTypeNameStr(fty.many_pointer_type.element_name, false);
|
||||
}
|
||||
}
|
||||
break :blk null;
|
||||
@@ -642,7 +654,7 @@ pub const Analyzer = struct {
|
||||
var obj_ty = self.inferExprType(fa.object);
|
||||
// `p.field` where `p` is `*T` resolves on the pointee `T`.
|
||||
if (obj_ty.isPointer()) {
|
||||
obj_ty = self.resolveTypeNameStr(obj_ty.pointer_type.pointee_name);
|
||||
obj_ty = self.resolveTypeNameStr(obj_ty.pointer_type.pointee_name, false);
|
||||
}
|
||||
// `.len` / `.ptr` on the built-in containers (string, slice, array).
|
||||
if (std.mem.eql(u8, fa.field, "len")) {
|
||||
@@ -670,9 +682,9 @@ pub const Analyzer = struct {
|
||||
.index_expr => |ie| {
|
||||
const obj_ty = self.inferExprType(ie.object);
|
||||
if (obj_ty == .string_type) return Type.u(8);
|
||||
if (obj_ty.isArray()) return self.resolveTypeNameStr(obj_ty.array_type.element_name);
|
||||
if (obj_ty.isManyPointer()) return self.resolveTypeNameStr(obj_ty.many_pointer_type.element_name);
|
||||
if (obj_ty.isSlice()) return self.resolveTypeNameStr(obj_ty.slice_type.element_name);
|
||||
if (obj_ty.isArray()) return self.resolveTypeNameStr(obj_ty.array_type.element_name, false);
|
||||
if (obj_ty.isManyPointer()) return self.resolveTypeNameStr(obj_ty.many_pointer_type.element_name, false);
|
||||
if (obj_ty.isSlice()) return self.resolveTypeNameStr(obj_ty.slice_type.element_name, false);
|
||||
return Type.unresolved;
|
||||
},
|
||||
.slice_expr => |se| {
|
||||
@@ -1054,7 +1066,7 @@ pub const Analyzer = struct {
|
||||
.field_access => |fa| {
|
||||
try self.analyzeNode(fa.object);
|
||||
var owner_ty = self.inferExprType(fa.object);
|
||||
if (owner_ty.isPointer()) owner_ty = self.resolveTypeNameStr(owner_ty.pointer_type.pointee_name);
|
||||
if (owner_ty.isPointer()) owner_ty = self.resolveTypeNameStr(owner_ty.pointer_type.pointee_name, false);
|
||||
self.recordMemberRef(fa.field, owner_ty.toName() orelse "", false);
|
||||
},
|
||||
.enum_literal => |el| {
|
||||
@@ -1083,7 +1095,7 @@ pub const Analyzer = struct {
|
||||
.match_expr => |me| {
|
||||
try self.analyzeNode(me.subject);
|
||||
var subj_ty = self.inferExprType(me.subject);
|
||||
if (subj_ty.isPointer()) subj_ty = self.resolveTypeNameStr(subj_ty.pointer_type.pointee_name);
|
||||
if (subj_ty.isPointer()) subj_ty = self.resolveTypeNameStr(subj_ty.pointer_type.pointee_name, false);
|
||||
const subj_owner = subj_ty.toName() orelse "";
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern) |pat| {
|
||||
|
||||
Reference in New Issue
Block a user