feat(lang): raw provenance through ALL sema compound type metadata — finish universal raw identifier in the LSP classifier [F0.6]

The codegen-side resolver was already raw-aware for the universal model;
the sema/LSP editor index (the second classifier) only honored the DIRECT
raw type. A COMPOUND raw type (`*`s2`, `?`s2`, `[N]`s2`, `[]`s2`, `[*]`s2`)
stores its inner type-name as a bare string on the Type info struct, and
every resolution site re-read it with skip_builtin=false — so the index
reclassified a user type named `s2` as the builtin int, diverging from
codegen (issue-0083 class, LSP surface only; codegen unchanged).

Structural cure: every compound info struct (Pointer/Optional/Slice/
ManyPointer/Array) carries a REQUIRED is_raw bit (no default — a future
construction site cannot drop it). is_raw is set at every construction
site (resolveTypeNode arms, fieldType arms, variadic slice, .ptr/slice_expr
derivation, for-loop by-ref, substType) and passed as skip_builtin at every
resolution site (elementTypeOf, field-access pointer unwrap, index, deref,
optional unwrap/null-coalesce, if/while optional binding, match subject).
Optional-unwrap + deref sites converted from Type.fromName/pointerPointeeType
(builtin-only, divergent) to resolveTypeNameStr(name, is_raw); the now-dead
pointerPointeeType removed.

Tests: src/sema.test.zig gains pointer/optional/array raw-vs-bare
regressions (raw → user type, bare → builtin control) — each FAILS on
pre-fix sema, PASSES after — plus a parameterized-raw coverage test.
This commit is contained in:
agra
2026-06-04 21:46:31 +03:00
parent ef8f021c01
commit 724a919fc1
4 changed files with 223 additions and 48 deletions

View File

@@ -193,7 +193,12 @@ pub const Analyzer = struct {
.slice_type_expr => |st| if (st.element_type.data == .type_expr) st.element_type.data.type_expr.name else "<unresolved>",
else => "<unresolved>",
};
try param_types.append(self.allocator, .{ .slice_type = .{ .element_name = elem_name } });
const elem_raw = switch (param.type_expr.data) {
.type_expr => |te| te.is_raw,
.slice_type_expr => |st| typeExprIsRaw(st.element_type),
else => false,
};
try param_types.append(self.allocator, .{ .slice_type = .{ .element_name = elem_name, .is_raw = elem_raw } });
} else {
try param_types.append(self.allocator, pt);
}
@@ -362,35 +367,35 @@ pub const Analyzer = struct {
const length: u32 = @intCast(ate.length.data.int_literal.value);
const elem_type = self.resolveTypeNode(ate.element_type);
const elem_name = elem_type.displayName(self.allocator) catch return .void_type;
return .{ .array_type = .{ .element_name = elem_name, .length = length } };
return .{ .array_type = .{ .element_name = elem_name, .length = length, .is_raw = typeExprIsRaw(ate.element_type) } };
}
// Slice type: []T
if (tn.data == .slice_type_expr) {
const ste = tn.data.slice_type_expr;
const elem_type = self.resolveTypeNode(ste.element_type);
const elem_name = elem_type.displayName(self.allocator) catch return .void_type;
return .{ .slice_type = .{ .element_name = elem_name } };
return .{ .slice_type = .{ .element_name = elem_name, .is_raw = typeExprIsRaw(ste.element_type) } };
}
// Optional type: ?T
if (tn.data == .optional_type_expr) {
const ote = tn.data.optional_type_expr;
const inner_type = self.resolveTypeNode(ote.inner_type);
const inner_name = inner_type.displayName(self.allocator) catch return .void_type;
return .{ .optional_type = .{ .child_name = inner_name } };
return .{ .optional_type = .{ .child_name = inner_name, .is_raw = typeExprIsRaw(ote.inner_type) } };
}
// Pointer type: *T
if (tn.data == .pointer_type_expr) {
const pte = tn.data.pointer_type_expr;
const pointee_type = self.resolveTypeNode(pte.pointee_type);
const pointee_name = pointee_type.displayName(self.allocator) catch return .void_type;
return .{ .pointer_type = .{ .pointee_name = pointee_name } };
return .{ .pointer_type = .{ .pointee_name = pointee_name, .is_raw = typeExprIsRaw(pte.pointee_type) } };
}
// Many-pointer type: [*]T
if (tn.data == .many_pointer_type_expr) {
const mpte = tn.data.many_pointer_type_expr;
const elem_type = self.resolveTypeNode(mpte.element_type);
const elem_name = elem_type.displayName(self.allocator) catch return .void_type;
return .{ .many_pointer_type = .{ .element_name = elem_name } };
return .{ .many_pointer_type = .{ .element_name = elem_name, .is_raw = typeExprIsRaw(mpte.element_type) } };
}
// Function pointer type: (ParamTypes) -> ReturnType
if (tn.data == .function_type_expr) {
@@ -466,6 +471,31 @@ pub const Analyzer = struct {
};
}
/// The backtick raw bit of an inner type-name node (`` `s2 ``). A compound
/// shape (`*T`, `?T`, `[]T`, …) stores its inner name as a bare string, so
/// this bit must travel ALONGSIDE that name (issue 0089) — otherwise the
/// resolver re-reads `s2` as the builtin int. Non-leaf nodes are never raw.
fn typeExprIsRaw(node: *Node) bool {
return switch (node.data) {
.type_expr => |te| te.is_raw,
.identifier => |id| id.is_raw,
else => false,
};
}
/// When a compound shape stores the NAME of an ALREADY-resolved inner type
/// (no syntactic node to read `is_raw` from — e.g. a for-loop element), a
/// user nominal type must be re-resolved with `skip_builtin` so a struct/
/// enum/union named `s2` is not reclassified as the builtin. Builtins keep
/// `false`. Harmless for non-colliding names (the registry lookup is the
/// same either way).
fn innerNameIsRaw(inner: Type) bool {
return switch (inner) {
.struct_type, .enum_type, .union_type => true,
else => false,
};
}
/// Resolve a struct field's declared type, preserving the raw element/
/// pointee name of pointer/slice shapes so generic params (`T`) survive
/// into `instantiateGeneric`'s substitution. Bare names resolve through the
@@ -474,9 +504,9 @@ pub const Analyzer = struct {
return switch (node.data) {
.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) } },
.many_pointer_type_expr => |mp| .{ .many_pointer_type = .{ .element_name = self.typeExprName(mp.element_type), .is_raw = typeExprIsRaw(mp.element_type) } },
.pointer_type_expr => |p| .{ .pointer_type = .{ .pointee_name = self.typeExprName(p.pointee_type), .is_raw = typeExprIsRaw(p.pointee_type) } },
.slice_type_expr => |s| .{ .slice_type = .{ .element_name = self.typeExprName(s.element_type), .is_raw = typeExprIsRaw(s.element_type) } },
.parameterized_type_expr => |pte| self.instantiateGeneric(pte.name, pte.args) orelse self.resolveTypeNode(node),
else => self.resolveTypeNode(node),
};
@@ -488,15 +518,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, 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)),
.array_type => |i| self.resolveTypeNameStr(i.element_name, i.is_raw),
.slice_type => |i| self.resolveTypeNameStr(i.element_name, i.is_raw),
.many_pointer_type => |i| self.resolveTypeNameStr(i.element_name, i.is_raw),
.pointer_type => |i| self.elementTypeOf(self.resolveTypeNameStr(i.pointee_name, i.is_raw)),
.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, false);
break :blk self.resolveTypeNameStr(fty.many_pointer_type.element_name, fty.many_pointer_type.is_raw);
}
}
break :blk null;
@@ -527,10 +557,10 @@ pub const Analyzer = struct {
/// name-carrying shapes need rewriting; the rest pass through.
fn substType(ty: Type, params: []const []const u8, args: []const []const u8) Type {
return switch (ty) {
.many_pointer_type => |i| .{ .many_pointer_type = .{ .element_name = substName(i.element_name, params, args) } },
.slice_type => |i| .{ .slice_type = .{ .element_name = substName(i.element_name, params, args) } },
.array_type => |i| .{ .array_type = .{ .length = i.length, .element_name = substName(i.element_name, params, args) } },
.pointer_type => |i| .{ .pointer_type = .{ .pointee_name = substName(i.pointee_name, params, args) } },
.many_pointer_type => |i| .{ .many_pointer_type = .{ .element_name = substName(i.element_name, params, args), .is_raw = i.is_raw } },
.slice_type => |i| .{ .slice_type = .{ .element_name = substName(i.element_name, params, args), .is_raw = i.is_raw } },
.array_type => |i| .{ .array_type = .{ .length = i.length, .element_name = substName(i.element_name, params, args), .is_raw = i.is_raw } },
.pointer_type => |i| .{ .pointer_type = .{ .pointee_name = substName(i.pointee_name, params, args), .is_raw = i.is_raw } },
.struct_type => |n| .{ .struct_type = substName(n, params, args) },
else => ty,
};
@@ -654,16 +684,16 @@ 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, false);
obj_ty = self.resolveTypeNameStr(obj_ty.pointer_type.pointee_name, obj_ty.pointer_type.is_raw);
}
// `.len` / `.ptr` on the built-in containers (string, slice, array).
if (std.mem.eql(u8, fa.field, "len")) {
if (obj_ty == .string_type or obj_ty.isSlice() or obj_ty.isArray()) return Type.s(64);
}
if (std.mem.eql(u8, fa.field, "ptr")) {
if (obj_ty == .string_type) return .{ .many_pointer_type = .{ .element_name = "u8" } };
if (obj_ty.isSlice()) return .{ .many_pointer_type = .{ .element_name = obj_ty.slice_type.element_name } };
if (obj_ty.isArray()) return .{ .many_pointer_type = .{ .element_name = obj_ty.array_type.element_name } };
if (obj_ty == .string_type) return .{ .many_pointer_type = .{ .element_name = "u8", .is_raw = false } };
if (obj_ty.isSlice()) return .{ .many_pointer_type = .{ .element_name = obj_ty.slice_type.element_name, .is_raw = obj_ty.slice_type.is_raw } };
if (obj_ty.isArray()) return .{ .many_pointer_type = .{ .element_name = obj_ty.array_type.element_name, .is_raw = obj_ty.array_type.is_raw } };
}
if (obj_ty.isStruct()) {
if (self.struct_types.get(obj_ty.struct_type)) |info| {
@@ -675,23 +705,23 @@ pub const Analyzer = struct {
}
}
if (obj_ty.isArray()) {
return Type.fromName(obj_ty.array_type.element_name) orelse Type.unresolved;
return self.resolveTypeNameStr(obj_ty.array_type.element_name, obj_ty.array_type.is_raw);
}
return Type.unresolved;
},
.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, 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);
if (obj_ty.isArray()) return self.resolveTypeNameStr(obj_ty.array_type.element_name, obj_ty.array_type.is_raw);
if (obj_ty.isManyPointer()) return self.resolveTypeNameStr(obj_ty.many_pointer_type.element_name, obj_ty.many_pointer_type.is_raw);
if (obj_ty.isSlice()) return self.resolveTypeNameStr(obj_ty.slice_type.element_name, obj_ty.slice_type.is_raw);
return Type.unresolved;
},
.slice_expr => |se| {
const obj_ty = self.inferExprType(se.object);
if (obj_ty == .string_type) return .string_type;
if (obj_ty.isArray()) return .{ .slice_type = .{ .element_name = obj_ty.array_type.element_name } };
if (obj_ty.isManyPointer()) return .{ .slice_type = .{ .element_name = obj_ty.many_pointer_type.element_name } };
if (obj_ty.isArray()) return .{ .slice_type = .{ .element_name = obj_ty.array_type.element_name, .is_raw = obj_ty.array_type.is_raw } };
if (obj_ty.isManyPointer()) return .{ .slice_type = .{ .element_name = obj_ty.many_pointer_type.element_name, .is_raw = obj_ty.many_pointer_type.is_raw } };
if (obj_ty.isSlice()) return obj_ty;
return .void_type;
},
@@ -721,17 +751,17 @@ pub const Analyzer = struct {
},
.force_unwrap => |fu| {
const opt_ty = self.inferExprType(fu.operand);
if (opt_ty.isOptional()) return Type.fromName(opt_ty.optional_type.child_name) orelse .void_type;
if (opt_ty.isOptional()) return self.resolveTypeNameStr(opt_ty.optional_type.child_name, opt_ty.optional_type.is_raw);
return .void_type;
},
.null_coalesce => |nc| {
const opt_ty = self.inferExprType(nc.lhs);
if (opt_ty.isOptional()) return Type.fromName(opt_ty.optional_type.child_name) orelse .void_type;
if (opt_ty.isOptional()) return self.resolveTypeNameStr(opt_ty.optional_type.child_name, opt_ty.optional_type.is_raw);
return self.inferExprType(nc.rhs);
},
.deref_expr => |de| {
const ptr_ty = self.inferExprType(de.operand);
if (ptr_ty.isPointer()) return ptr_ty.pointerPointeeType() orelse .void_type;
if (ptr_ty.isPointer()) return self.resolveTypeNameStr(ptr_ty.pointer_type.pointee_name, ptr_ty.pointer_type.is_raw);
return .void_type;
},
.null_literal => .void_type,
@@ -1066,7 +1096,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, false);
if (owner_ty.isPointer()) owner_ty = self.resolveTypeNameStr(owner_ty.pointer_type.pointee_name, owner_ty.pointer_type.is_raw);
self.recordMemberRef(fa.field, owner_ty.toName() orelse "", false);
},
.enum_literal => |el| {
@@ -1078,7 +1108,7 @@ pub const Analyzer = struct {
// `if val := expr { ... }` — val is the unwrapped optional
const cond_ty = self.inferExprType(ie.condition);
const inner_ty: ?Type = if (cond_ty.isOptional())
Type.fromName(cond_ty.optional_type.child_name)
self.resolveTypeNameStr(cond_ty.optional_type.child_name, cond_ty.optional_type.is_raw)
else
null;
try self.pushScope();
@@ -1095,7 +1125,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, false);
if (subj_ty.isPointer()) subj_ty = self.resolveTypeNameStr(subj_ty.pointer_type.pointee_name, subj_ty.pointer_type.is_raw);
const subj_owner = subj_ty.toName() orelse "";
for (me.arms) |arm| {
if (arm.pattern) |pat| {
@@ -1114,7 +1144,7 @@ pub const Analyzer = struct {
if (we.binding_name) |bname| {
const cond_ty = self.inferExprType(we.condition);
const inner_ty: ?Type = if (cond_ty.isOptional())
Type.fromName(cond_ty.optional_type.child_name)
self.resolveTypeNameStr(cond_ty.optional_type.child_name, cond_ty.optional_type.is_raw)
else
null;
try self.pushScope();
@@ -1134,7 +1164,7 @@ pub const Analyzer = struct {
cap_ty = .{ .signed = 64 };
} else if (self.elementTypeOf(self.inferExprType(fe.iterable))) |elem| {
cap_ty = if (fe.capture_by_ref)
(if (elem.toName()) |en| Type{ .pointer_type = .{ .pointee_name = en } } else elem)
(if (elem.toName()) |en| Type{ .pointer_type = .{ .pointee_name = en, .is_raw = innerNameIsRaw(elem) } } else elem)
else
elem;
}