lang: introduce cstring — the C-boundary string (Odin model)
cstring is ONE pointer to a null-terminated u8 buffer, C's char*: thin (8 bytes, no length; cstring_len walks to the terminator), crossing #foreign boundaries verbatim in both directions, with ?cstring as the nullable case lowering to the same bare pointer (null = absent). Conversion discipline mirrors Odin: a string LITERAL coerces implicitly (its bytes are terminated constants); any other string is rejected with a diagnostic naming to_cstring (it may be an unterminated view); and cstring never coerces to string implicitly — from_cstring(c) is the explicit zero-copy view, pricing the strlen. Plumbing: TypeId/TypeInfo builtin slot 18 (first_user 19), name classifiers, size/align/name tables, LLVM ptr lowering, the ?T pointer niche, the xx pointer ladder, the literal-gated coercion plan (isConstString + data_ptr), and the reserved-spelling set. std gains cstring_len/from_cstring/to_cstring (fmt.sx, re-exported); the old cstring(size) allocator helper is renamed alloc_string everywhere; getenv migrates to (name: cstring) -> ?cstring as the canonical user and env() drops its manual strlen/memcpy. Pinned: examples/1222 (FFI both directions, literal coercion, ?cstring null paths, round trip) and examples/1173 (both coercion diagnostics); FAIL pre-feature. The alloc_string rename + getenv signature shift the .ir snapshots — regenerated. zig build test 426/426; run_examples 604/604. Spec: reserved spelling + cstring section + C-interop rows.
This commit is contained in:
@@ -33,9 +33,10 @@ pub const TypeId = enum(u32) {
|
||||
isize = 15,
|
||||
usize = 16,
|
||||
void = 17,
|
||||
_, // user-defined types start at 18
|
||||
cstring = 18, // thin null-terminated char* (see TypeInfo.cstring)
|
||||
_, // user-defined types start at 19
|
||||
|
||||
pub const first_user: u32 = 18;
|
||||
pub const first_user: u32 = 19;
|
||||
|
||||
pub fn index(self: TypeId) u32 {
|
||||
return @intFromEnum(self);
|
||||
@@ -63,6 +64,7 @@ pub const TypeInfo = union(enum) {
|
||||
void,
|
||||
bool,
|
||||
string, // [:0]u8 — fat pointer {ptr, len}
|
||||
cstring, // thin null-terminated char* — ONE pointer, length implicit (strlen)
|
||||
|
||||
@"struct": StructInfo,
|
||||
@"enum": EnumInfo,
|
||||
@@ -382,6 +384,7 @@ pub const TypeTable = struct {
|
||||
.isize, // 15: isize (pointer-sized signed)
|
||||
.usize, // 16: usize (pointer-sized unsigned)
|
||||
.void, // 17
|
||||
.cstring, // 18: thin null-terminated char*
|
||||
};
|
||||
for (&builtins) |info| {
|
||||
table.infos.append(alloc, info) catch unreachable;
|
||||
@@ -587,13 +590,14 @@ pub const TypeTable = struct {
|
||||
.f32 => 4,
|
||||
.f64 => 8,
|
||||
.string => 16, // {ptr, len}
|
||||
.cstring => 8, // one pointer
|
||||
.pointer, .many_pointer, .function => 8,
|
||||
.closure => 16, // {fn_ptr, env}
|
||||
.optional => |opt| blk: {
|
||||
// Sentinel-shaped optionals (pointer/closure/protocol) cost
|
||||
// no extra storage — null reuses the payload's null state.
|
||||
const child_info = self.get(opt.child);
|
||||
if (child_info == .pointer or child_info == .many_pointer or child_info == .function) break :blk 8;
|
||||
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .cstring) break :blk 8;
|
||||
if (child_info == .closure) break :blk 16;
|
||||
if (child_info == .@"struct" and child_info.@"struct".is_protocol) break :blk self.sizeOf(opt.child);
|
||||
// Discriminated form: payload + has_value flag (8-aligned).
|
||||
@@ -682,6 +686,7 @@ pub const TypeTable = struct {
|
||||
if (ty == .i32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .i64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_size;
|
||||
if (ty == .cstring) return ptr_size;
|
||||
if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
|
||||
if (ty == .any) return 16; // {i64 tag, i64 value} — Any boxed layout
|
||||
if (ty.isBuiltin()) return ptr_size; // default for unknown builtins
|
||||
@@ -783,6 +788,7 @@ pub const TypeTable = struct {
|
||||
if (ty == .i64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_align;
|
||||
if (ty == .string) return 8; // i64 drives alignment
|
||||
if (ty == .cstring) return ptr_align;
|
||||
if (ty == .any) return 8; // {i64, i64} aligns to 8
|
||||
if (ty.isBuiltin()) return ptr_align;
|
||||
const info = self.get(ty);
|
||||
@@ -853,6 +859,7 @@ pub const TypeTable = struct {
|
||||
.f32 => "f32",
|
||||
.f64 => "f64",
|
||||
.string => "string",
|
||||
.cstring => "cstring",
|
||||
.any => "Any",
|
||||
.noreturn => "noreturn",
|
||||
.isize => "isize",
|
||||
@@ -999,7 +1006,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
||||
switch (info) {
|
||||
.signed => |w| h.update(&.{w}),
|
||||
.unsigned => |w| h.update(&.{w}),
|
||||
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize, .unresolved => {},
|
||||
.f32, .f64, .void, .bool, .string, .cstring, .any, .noreturn, .usize, .isize, .unresolved => {},
|
||||
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
|
||||
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
|
||||
.slice => |s| h.update(std.mem.asBytes(&s.element)),
|
||||
@@ -1070,7 +1077,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
||||
return switch (a) {
|
||||
.signed => |w| w == b.signed,
|
||||
.unsigned => |w| w == b.unsigned,
|
||||
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize, .unresolved => true,
|
||||
.f32, .f64, .void, .bool, .string, .cstring, .any, .noreturn, .usize, .isize, .unresolved => true,
|
||||
.pointer => |p| p.pointee == b.pointer.pointee,
|
||||
.many_pointer => |p| p.element == b.many_pointer.element,
|
||||
.slice => |s| s.element == b.slice.element,
|
||||
|
||||
Reference in New Issue
Block a user