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:
@@ -35,7 +35,15 @@
|
|||||||
> builtin int. The SECOND (editor/LSP) classifier in [src/sema.zig]
|
> builtin int. The SECOND (editor/LSP) classifier in [src/sema.zig]
|
||||||
> (`Type.fromTypeExpr` / `resolveTypeNode` / `resolveTypeNameStr`) honors
|
> (`Type.fromTypeExpr` / `resolveTypeNode` / `resolveTypeNameStr`) honors
|
||||||
> `is_raw` too, so a backtick reserved-name annotation resolves to the user type
|
> `is_raw` too, so a backtick reserved-name annotation resolves to the user type
|
||||||
> in hover/completion, not the builtin (no two-resolver divergence).
|
> in hover/completion, not the builtin (no two-resolver divergence). The raw bit
|
||||||
|
> is carried STRUCTURALLY through every COMPOUND shape's inner-name metadata —
|
||||||
|
> `PointerTypeInfo` / `OptionalTypeInfo` / `SliceTypeInfo` / `ManyPointerTypeInfo`
|
||||||
|
> / `ArrayTypeInfo` each store a REQUIRED `is_raw` ([src/types.zig], no default,
|
||||||
|
> so a future construction site cannot drop it) that every `resolveTypeNameStr`
|
||||||
|
> call passes as its `skip_builtin` — so `` *`s2 ``, `` ?`s2 ``, `` [N]`s2 ``,
|
||||||
|
> `` []`s2 ``, `` [*]`s2 `` field-access / unwrap / index / deref in the editor
|
||||||
|
> index all reach the user type instead of reclassifying the inner `s2` to the
|
||||||
|
> builtin (the divergence the DIRECT-only attempt left for compound forms).
|
||||||
> - **Declaration position.** A bare reserved-name declaration of EVERY kind
|
> - **Declaration position.** A bare reserved-name declaration of EVERY kind
|
||||||
> still errors (issue 0076 preserved); the backtick form is exempt. The check
|
> still errors (issue 0076 preserved); the backtick form is exempt. The check
|
||||||
> and the exemption are made structurally symmetric:
|
> and the exemption are made structurally symmetric:
|
||||||
@@ -82,7 +90,10 @@
|
|||||||
> `examples/1142-diagnostics-reserved-name-struct-const.sx` (bare struct-body const,
|
> `examples/1142-diagnostics-reserved-name-struct-const.sx` (bare struct-body const,
|
||||||
> caret on the name). Backtick lexer + `resolveNamed(skip_builtin)` unit tests in
|
> caret on the name). Backtick lexer + `resolveNamed(skip_builtin)` unit tests in
|
||||||
> `src/lexer.zig` / `src/ir/type_resolver.test.zig`; the editor/LSP raw-type
|
> `src/lexer.zig` / `src/ir/type_resolver.test.zig`; the editor/LSP raw-type
|
||||||
> resolution (the second classifier) is pinned in `src/sema.test.zig`.
|
> resolution (the second classifier) is pinned in `src/sema.test.zig` — the direct
|
||||||
|
> case plus raw provenance through every compound shape (`` *`s2 `` field access,
|
||||||
|
> `` ?`s2 `` unwrap, `` [N]`s2 `` index, parameterized `` `s2(s64) ``), each with a
|
||||||
|
> bare-spelling control that stays the builtin (fail-before verified).
|
||||||
>
|
>
|
||||||
> The original report is preserved below.
|
> The original report is preserved below.
|
||||||
|
|
||||||
|
|||||||
@@ -84,3 +84,132 @@ test "sema: a raw struct-field annotation resolves to the user type; bare stays
|
|||||||
try std.testing.expect(b_ty.? == .unsigned);
|
try std.testing.expect(b_ty.? == .unsigned);
|
||||||
try std.testing.expectEqual(@as(u8, 8), b_ty.?.unsigned);
|
try std.testing.expectEqual(@as(u8, 8), b_ty.?.unsigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── issue 0089: raw provenance through sema's COMPOUND type metadata ────────
|
||||||
|
//
|
||||||
|
// The direct-case fix (above) only covered a bare `` `s2 `` reference. A
|
||||||
|
// COMPOUND raw type (`*`s2`, `?`s2`, `[N]`s2`, …) stores its inner name as a
|
||||||
|
// bare string on the Type's info struct; the resolver re-reads that name via
|
||||||
|
// `resolveTypeNameStr`. Before threading `is_raw` ALONGSIDE the stored name,
|
||||||
|
// the resolver passed `skip_builtin = false`, so the LSP index reclassified a
|
||||||
|
// user type named `s2` as the builtin int — diverging from codegen. These
|
||||||
|
// pin every compound form: the raw inner resolves to the user type (FAILS on
|
||||||
|
// pre-fix sema), the bare inner stays the builtin (control, preserved).
|
||||||
|
|
||||||
|
fn symType(res: sema.SemaResult, name: []const u8) ?Type {
|
||||||
|
for (res.symbols) |sym| {
|
||||||
|
if (std.mem.eql(u8, sym.name, name)) return sym.ty;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "sema: field access through a raw `*`s2` pointer resolves the user field; bare `*s2` stays builtin" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
const src =
|
||||||
|
\\`s2 :: struct { x: s64; }
|
||||||
|
\\f :: (p: *`s2) { y := p.x; }
|
||||||
|
\\g :: (q: *s2) { w := q.*; }
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
var parser = Parser.init(alloc, src);
|
||||||
|
const root = try parser.parse();
|
||||||
|
var analyzer = sema.Analyzer.init(alloc);
|
||||||
|
const res = try analyzer.analyze(root);
|
||||||
|
|
||||||
|
// RAW: `p: *`s2` → field `x` on the user struct → s64. (Pre-fix: the
|
||||||
|
// pointee `s2` reclassified to the 2-bit int, `.x` not found → unresolved.)
|
||||||
|
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(y == .signed);
|
||||||
|
try std.testing.expectEqual(@as(u8, 64), y.signed);
|
||||||
|
|
||||||
|
// CONTROL: `q: *s2` (bare) → deref yields the builtin 2-bit signed int.
|
||||||
|
const w = symType(res, "w") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(w == .signed);
|
||||||
|
try std.testing.expectEqual(@as(u8, 2), w.signed);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "sema: unwrapping a raw `?`s2` optional resolves the user field; bare `?s2` stays builtin" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
const src =
|
||||||
|
\\`s2 :: struct { x: s64; }
|
||||||
|
\\f :: (o: ?`s2) { if val := o { y := val.x; } }
|
||||||
|
\\g :: (b: ?s2) { if v := b { w := v; } }
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
var parser = Parser.init(alloc, src);
|
||||||
|
const root = try parser.parse();
|
||||||
|
var analyzer = sema.Analyzer.init(alloc);
|
||||||
|
const res = try analyzer.analyze(root);
|
||||||
|
|
||||||
|
// RAW: `o: ?`s2` → `if val := o` unwraps to the user struct → `val.x` is s64.
|
||||||
|
// (Pre-fix: the optional child `s2` reclassified to the 2-bit int.)
|
||||||
|
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(y == .signed);
|
||||||
|
try std.testing.expectEqual(@as(u8, 64), y.signed);
|
||||||
|
|
||||||
|
// CONTROL: `b: ?s2` (bare) unwraps to the builtin 2-bit signed int.
|
||||||
|
const w = symType(res, "w") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(w == .signed);
|
||||||
|
try std.testing.expectEqual(@as(u8, 2), w.signed);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "sema: indexing a raw `[N]`s2` array resolves the user element; bare `[N]s2` stays builtin" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
const src =
|
||||||
|
\\`s2 :: struct { x: s64; }
|
||||||
|
\\f :: (a: [4]`s2, b: [4]s2) { y := a[0]; w := b[0]; }
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
var parser = Parser.init(alloc, src);
|
||||||
|
const root = try parser.parse();
|
||||||
|
var analyzer = sema.Analyzer.init(alloc);
|
||||||
|
const res = try analyzer.analyze(root);
|
||||||
|
|
||||||
|
// RAW: `a: [4]`s2` → element is the user struct. (Pre-fix: reclassified to
|
||||||
|
// the 2-bit int.)
|
||||||
|
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(y == .struct_type);
|
||||||
|
try std.testing.expectEqualStrings("s2", y.struct_type);
|
||||||
|
|
||||||
|
// CONTROL: `b: [4]s2` (bare) → element is the builtin 2-bit signed int.
|
||||||
|
const w = symType(res, "w") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(w == .signed);
|
||||||
|
try std.testing.expectEqual(@as(u8, 2), w.signed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameterized raw type (`` `s2(s64) ``). Unlike the shapes above this never
|
||||||
|
// had the divergence — instantiation resolves the base name straight against
|
||||||
|
// `struct_types` (no builtin classifier in the path), so it passes before AND
|
||||||
|
// after. Included as coverage that the universal model holds for the
|
||||||
|
// parameterized form too: a `` `s2 ``-declared generic instantiates and its
|
||||||
|
// field resolves.
|
||||||
|
test "sema: a raw parameterized type `` `s2(s64) `` instantiates the user generic" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
const src =
|
||||||
|
\\`s2 :: struct ($T: Type) { items: [*]T = null; n: s64 = 0; }
|
||||||
|
\\f :: (v: `s2(s64)) { y := v.n; }
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
var parser = Parser.init(alloc, src);
|
||||||
|
const root = try parser.parse();
|
||||||
|
var analyzer = sema.Analyzer.init(alloc);
|
||||||
|
const res = try analyzer.analyze(root);
|
||||||
|
|
||||||
|
// `v: `s2(s64)` instantiates the `` `s2 ``-declared generic; its concrete
|
||||||
|
// field `n` resolves to s64 (the raw base name was not misread as a builtin).
|
||||||
|
const y = symType(res, "y") orelse return error.MissingSymbol;
|
||||||
|
try std.testing.expect(y == .signed);
|
||||||
|
try std.testing.expectEqual(@as(u8, 64), y.signed);
|
||||||
|
}
|
||||||
|
|||||||
102
src/sema.zig
102
src/sema.zig
@@ -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>",
|
.slice_type_expr => |st| if (st.element_type.data == .type_expr) st.element_type.data.type_expr.name else "<unresolved>",
|
||||||
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 {
|
} else {
|
||||||
try param_types.append(self.allocator, pt);
|
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 length: u32 = @intCast(ate.length.data.int_literal.value);
|
||||||
const elem_type = self.resolveTypeNode(ate.element_type);
|
const elem_type = self.resolveTypeNode(ate.element_type);
|
||||||
const elem_name = elem_type.displayName(self.allocator) catch return .void_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
|
// Slice type: []T
|
||||||
if (tn.data == .slice_type_expr) {
|
if (tn.data == .slice_type_expr) {
|
||||||
const ste = tn.data.slice_type_expr;
|
const ste = tn.data.slice_type_expr;
|
||||||
const elem_type = self.resolveTypeNode(ste.element_type);
|
const elem_type = self.resolveTypeNode(ste.element_type);
|
||||||
const elem_name = elem_type.displayName(self.allocator) catch return .void_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
|
// Optional type: ?T
|
||||||
if (tn.data == .optional_type_expr) {
|
if (tn.data == .optional_type_expr) {
|
||||||
const ote = tn.data.optional_type_expr;
|
const ote = tn.data.optional_type_expr;
|
||||||
const inner_type = self.resolveTypeNode(ote.inner_type);
|
const inner_type = self.resolveTypeNode(ote.inner_type);
|
||||||
const inner_name = inner_type.displayName(self.allocator) catch return .void_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
|
// Pointer type: *T
|
||||||
if (tn.data == .pointer_type_expr) {
|
if (tn.data == .pointer_type_expr) {
|
||||||
const pte = tn.data.pointer_type_expr;
|
const pte = tn.data.pointer_type_expr;
|
||||||
const pointee_type = self.resolveTypeNode(pte.pointee_type);
|
const pointee_type = self.resolveTypeNode(pte.pointee_type);
|
||||||
const pointee_name = pointee_type.displayName(self.allocator) catch return .void_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
|
// Many-pointer type: [*]T
|
||||||
if (tn.data == .many_pointer_type_expr) {
|
if (tn.data == .many_pointer_type_expr) {
|
||||||
const mpte = tn.data.many_pointer_type_expr;
|
const mpte = tn.data.many_pointer_type_expr;
|
||||||
const elem_type = self.resolveTypeNode(mpte.element_type);
|
const elem_type = self.resolveTypeNode(mpte.element_type);
|
||||||
const elem_name = elem_type.displayName(self.allocator) catch return .void_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
|
// Function pointer type: (ParamTypes) -> ReturnType
|
||||||
if (tn.data == .function_type_expr) {
|
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/
|
/// Resolve a struct field's declared type, preserving the raw element/
|
||||||
/// pointee name of pointer/slice shapes so generic params (`T`) survive
|
/// pointee name of pointer/slice shapes so generic params (`T`) survive
|
||||||
/// into `instantiateGeneric`'s substitution. Bare names resolve through the
|
/// into `instantiateGeneric`'s substitution. Bare names resolve through the
|
||||||
@@ -474,9 +504,9 @@ pub const Analyzer = struct {
|
|||||||
return switch (node.data) {
|
return switch (node.data) {
|
||||||
.type_expr => |te| self.resolveTypeNameStr(te.name, te.is_raw),
|
.type_expr => |te| self.resolveTypeNameStr(te.name, te.is_raw),
|
||||||
.identifier => |id| self.resolveTypeNameStr(id.name, id.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) } },
|
.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) } },
|
.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) } },
|
.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),
|
.parameterized_type_expr => |pte| self.instantiateGeneric(pte.name, pte.args) orelse self.resolveTypeNode(node),
|
||||||
else => self.resolveTypeNode(node),
|
else => self.resolveTypeNode(node),
|
||||||
};
|
};
|
||||||
@@ -488,15 +518,15 @@ pub const Analyzer = struct {
|
|||||||
/// pointee first (so `*List(Move)` still iterates `Move`).
|
/// pointee first (so `*List(Move)` still iterates `Move`).
|
||||||
fn elementTypeOf(self: *Analyzer, ty: Type) ?Type {
|
fn elementTypeOf(self: *Analyzer, ty: Type) ?Type {
|
||||||
return switch (ty) {
|
return switch (ty) {
|
||||||
.array_type => |i| self.resolveTypeNameStr(i.element_name, false),
|
.array_type => |i| self.resolveTypeNameStr(i.element_name, i.is_raw),
|
||||||
.slice_type => |i| self.resolveTypeNameStr(i.element_name, false),
|
.slice_type => |i| self.resolveTypeNameStr(i.element_name, i.is_raw),
|
||||||
.many_pointer_type => |i| self.resolveTypeNameStr(i.element_name, false),
|
.many_pointer_type => |i| self.resolveTypeNameStr(i.element_name, i.is_raw),
|
||||||
.pointer_type => |i| self.elementTypeOf(self.resolveTypeNameStr(i.pointee_name, false)),
|
.pointer_type => |i| self.elementTypeOf(self.resolveTypeNameStr(i.pointee_name, i.is_raw)),
|
||||||
.struct_type => |name| blk: {
|
.struct_type => |name| blk: {
|
||||||
const info = self.struct_types.get(name) orelse break :blk null;
|
const info = self.struct_types.get(name) orelse break :blk null;
|
||||||
for (info.field_names, info.field_types) |fname, fty| {
|
for (info.field_names, info.field_types) |fname, fty| {
|
||||||
if (std.mem.eql(u8, fname, "items") and fty == .many_pointer_type) {
|
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;
|
break :blk null;
|
||||||
@@ -527,10 +557,10 @@ pub const Analyzer = struct {
|
|||||||
/// name-carrying shapes need rewriting; the rest pass through.
|
/// name-carrying shapes need rewriting; the rest pass through.
|
||||||
fn substType(ty: Type, params: []const []const u8, args: []const []const u8) Type {
|
fn substType(ty: Type, params: []const []const u8, args: []const []const u8) Type {
|
||||||
return switch (ty) {
|
return switch (ty) {
|
||||||
.many_pointer_type => |i| .{ .many_pointer_type = .{ .element_name = substName(i.element_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) } },
|
.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) } },
|
.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) } },
|
.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) },
|
.struct_type => |n| .{ .struct_type = substName(n, params, args) },
|
||||||
else => ty,
|
else => ty,
|
||||||
};
|
};
|
||||||
@@ -654,16 +684,16 @@ pub const Analyzer = struct {
|
|||||||
var obj_ty = self.inferExprType(fa.object);
|
var obj_ty = self.inferExprType(fa.object);
|
||||||
// `p.field` where `p` is `*T` resolves on the pointee `T`.
|
// `p.field` where `p` is `*T` resolves on the pointee `T`.
|
||||||
if (obj_ty.isPointer()) {
|
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).
|
// `.len` / `.ptr` on the built-in containers (string, slice, array).
|
||||||
if (std.mem.eql(u8, fa.field, "len")) {
|
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 (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 (std.mem.eql(u8, fa.field, "ptr")) {
|
||||||
if (obj_ty == .string_type) return .{ .many_pointer_type = .{ .element_name = "u8" } };
|
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 } };
|
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 } };
|
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 (obj_ty.isStruct()) {
|
||||||
if (self.struct_types.get(obj_ty.struct_type)) |info| {
|
if (self.struct_types.get(obj_ty.struct_type)) |info| {
|
||||||
@@ -675,23 +705,23 @@ pub const Analyzer = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (obj_ty.isArray()) {
|
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;
|
return Type.unresolved;
|
||||||
},
|
},
|
||||||
.index_expr => |ie| {
|
.index_expr => |ie| {
|
||||||
const obj_ty = self.inferExprType(ie.object);
|
const obj_ty = self.inferExprType(ie.object);
|
||||||
if (obj_ty == .string_type) return Type.u(8);
|
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.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, false);
|
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, false);
|
if (obj_ty.isSlice()) return self.resolveTypeNameStr(obj_ty.slice_type.element_name, obj_ty.slice_type.is_raw);
|
||||||
return Type.unresolved;
|
return Type.unresolved;
|
||||||
},
|
},
|
||||||
.slice_expr => |se| {
|
.slice_expr => |se| {
|
||||||
const obj_ty = self.inferExprType(se.object);
|
const obj_ty = self.inferExprType(se.object);
|
||||||
if (obj_ty == .string_type) return .string_type;
|
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.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 } };
|
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;
|
if (obj_ty.isSlice()) return obj_ty;
|
||||||
return .void_type;
|
return .void_type;
|
||||||
},
|
},
|
||||||
@@ -721,17 +751,17 @@ pub const Analyzer = struct {
|
|||||||
},
|
},
|
||||||
.force_unwrap => |fu| {
|
.force_unwrap => |fu| {
|
||||||
const opt_ty = self.inferExprType(fu.operand);
|
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;
|
return .void_type;
|
||||||
},
|
},
|
||||||
.null_coalesce => |nc| {
|
.null_coalesce => |nc| {
|
||||||
const opt_ty = self.inferExprType(nc.lhs);
|
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);
|
return self.inferExprType(nc.rhs);
|
||||||
},
|
},
|
||||||
.deref_expr => |de| {
|
.deref_expr => |de| {
|
||||||
const ptr_ty = self.inferExprType(de.operand);
|
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;
|
return .void_type;
|
||||||
},
|
},
|
||||||
.null_literal => .void_type,
|
.null_literal => .void_type,
|
||||||
@@ -1066,7 +1096,7 @@ pub const Analyzer = struct {
|
|||||||
.field_access => |fa| {
|
.field_access => |fa| {
|
||||||
try self.analyzeNode(fa.object);
|
try self.analyzeNode(fa.object);
|
||||||
var owner_ty = self.inferExprType(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);
|
self.recordMemberRef(fa.field, owner_ty.toName() orelse "", false);
|
||||||
},
|
},
|
||||||
.enum_literal => |el| {
|
.enum_literal => |el| {
|
||||||
@@ -1078,7 +1108,7 @@ pub const Analyzer = struct {
|
|||||||
// `if val := expr { ... }` — val is the unwrapped optional
|
// `if val := expr { ... }` — val is the unwrapped optional
|
||||||
const cond_ty = self.inferExprType(ie.condition);
|
const cond_ty = self.inferExprType(ie.condition);
|
||||||
const inner_ty: ?Type = if (cond_ty.isOptional())
|
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
|
else
|
||||||
null;
|
null;
|
||||||
try self.pushScope();
|
try self.pushScope();
|
||||||
@@ -1095,7 +1125,7 @@ pub const Analyzer = struct {
|
|||||||
.match_expr => |me| {
|
.match_expr => |me| {
|
||||||
try self.analyzeNode(me.subject);
|
try self.analyzeNode(me.subject);
|
||||||
var subj_ty = self.inferExprType(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 "";
|
const subj_owner = subj_ty.toName() orelse "";
|
||||||
for (me.arms) |arm| {
|
for (me.arms) |arm| {
|
||||||
if (arm.pattern) |pat| {
|
if (arm.pattern) |pat| {
|
||||||
@@ -1114,7 +1144,7 @@ pub const Analyzer = struct {
|
|||||||
if (we.binding_name) |bname| {
|
if (we.binding_name) |bname| {
|
||||||
const cond_ty = self.inferExprType(we.condition);
|
const cond_ty = self.inferExprType(we.condition);
|
||||||
const inner_ty: ?Type = if (cond_ty.isOptional())
|
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
|
else
|
||||||
null;
|
null;
|
||||||
try self.pushScope();
|
try self.pushScope();
|
||||||
@@ -1134,7 +1164,7 @@ pub const Analyzer = struct {
|
|||||||
cap_ty = .{ .signed = 64 };
|
cap_ty = .{ .signed = 64 };
|
||||||
} else if (self.elementTypeOf(self.inferExprType(fe.iterable))) |elem| {
|
} else if (self.elementTypeOf(self.inferExprType(fe.iterable))) |elem| {
|
||||||
cap_ty = if (fe.capture_by_ref)
|
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
|
else
|
||||||
elem;
|
elem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,16 +42,26 @@ pub const Type = union(enum) {
|
|||||||
/// `ir.TypeId.unresolved`.
|
/// `ir.TypeId.unresolved`.
|
||||||
unresolved,
|
unresolved,
|
||||||
|
|
||||||
|
/// `is_raw` records whether the inner type-name came from a backtick raw
|
||||||
|
/// reference (`` `s2 ``) or an already-resolved user type. It is the
|
||||||
|
/// `skip_builtin` the resolver MUST pass when re-resolving the stored inner
|
||||||
|
/// name (issue 0089) — without it `resolveTypeNameStr` would reclassify a
|
||||||
|
/// user type named `s2` as the builtin int, diverging from codegen. The
|
||||||
|
/// field is REQUIRED (no default) so a future construction site cannot
|
||||||
|
/// silently drop the bit, the way the LSP index did for compound shapes.
|
||||||
pub const SliceTypeInfo = struct {
|
pub const SliceTypeInfo = struct {
|
||||||
element_name: []const u8,
|
element_name: []const u8,
|
||||||
|
is_raw: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PointerTypeInfo = struct {
|
pub const PointerTypeInfo = struct {
|
||||||
pointee_name: []const u8,
|
pointee_name: []const u8,
|
||||||
|
is_raw: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ManyPointerTypeInfo = struct {
|
pub const ManyPointerTypeInfo = struct {
|
||||||
element_name: []const u8,
|
element_name: []const u8,
|
||||||
|
is_raw: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FunctionTypeInfo = struct {
|
pub const FunctionTypeInfo = struct {
|
||||||
@@ -67,6 +77,7 @@ pub const Type = union(enum) {
|
|||||||
pub const ArrayTypeInfo = struct {
|
pub const ArrayTypeInfo = struct {
|
||||||
element_name: []const u8,
|
element_name: []const u8,
|
||||||
length: u32,
|
length: u32,
|
||||||
|
is_raw: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const VectorTypeInfo = struct {
|
pub const VectorTypeInfo = struct {
|
||||||
@@ -76,6 +87,7 @@ pub const Type = union(enum) {
|
|||||||
|
|
||||||
pub const OptionalTypeInfo = struct {
|
pub const OptionalTypeInfo = struct {
|
||||||
child_name: []const u8,
|
child_name: []const u8,
|
||||||
|
is_raw: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MetaTypeInfo = struct {
|
pub const MetaTypeInfo = struct {
|
||||||
@@ -125,7 +137,7 @@ pub const Type = union(enum) {
|
|||||||
if (std.mem.eql(u8, name, "f64")) return .f64;
|
if (std.mem.eql(u8, name, "f64")) return .f64;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
'?' => if (name.len >= 2) .{ .optional_type = .{ .child_name = name[1..] } } else null,
|
'?' => if (name.len >= 2) .{ .optional_type = .{ .child_name = name[1..], .is_raw = false } } else null,
|
||||||
'A' => if (std.mem.eql(u8, name, "Any")) .any_type else null,
|
'A' => if (std.mem.eql(u8, name, "Any")) .any_type else null,
|
||||||
'v' => if (std.mem.eql(u8, name, "void")) .void_type else null,
|
'v' => if (std.mem.eql(u8, name, "void")) .void_type else null,
|
||||||
'[' => {
|
'[' => {
|
||||||
@@ -141,11 +153,11 @@ pub const Type = union(enum) {
|
|||||||
}
|
}
|
||||||
// Many-pointer: [*]T
|
// Many-pointer: [*]T
|
||||||
if (name.len >= 4 and name[1] == '*' and name[2] == ']') {
|
if (name.len >= 4 and name[1] == '*' and name[2] == ']') {
|
||||||
return .{ .many_pointer_type = .{ .element_name = name[3..] } };
|
return .{ .many_pointer_type = .{ .element_name = name[3..], .is_raw = false } };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
'*' => if (name.len >= 2) .{ .pointer_type = .{ .pointee_name = name[1..] } } else null,
|
'*' => if (name.len >= 2) .{ .pointer_type = .{ .pointee_name = name[1..], .is_raw = false } } else null,
|
||||||
'V' => {
|
'V' => {
|
||||||
// Vector(N,T)
|
// Vector(N,T)
|
||||||
if (name.len >= 10 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') {
|
if (name.len >= 10 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') {
|
||||||
@@ -235,13 +247,6 @@ pub const Type = union(enum) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pointerPointeeType(self: Type) ?Type {
|
|
||||||
return switch (self) {
|
|
||||||
.pointer_type => |info| fromName(info.pointee_name),
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isManyPointer(self: Type) bool {
|
pub fn isManyPointer(self: Type) bool {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.many_pointer_type => true,
|
.many_pointer_type => true,
|
||||||
|
|||||||
Reference in New Issue
Block a user