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:
@@ -630,6 +630,30 @@ pub fn coerceMode(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId, mod
|
||||
const unwrapped = self.builder.emit(.{ .optional_unwrap = .{ .operand = val } }, child_ty);
|
||||
return self.coerceMode(unwrapped, child_ty, dst_ty, mode);
|
||||
},
|
||||
// string → cstring: ONLY a string LITERAL coerces implicitly — its
|
||||
// bytes are a terminated constant (Odin's literal blessing). Any
|
||||
// other string may be an unterminated view, so it must materialize
|
||||
// through `to_cstring`.
|
||||
.string_to_cstring => {
|
||||
if (self.builder.isConstString(val)) {
|
||||
return self.builder.emit(.{ .data_ptr = .{ .operand = val } }, .cstring);
|
||||
}
|
||||
if (self.diagnostics) |d| {
|
||||
const cs = self.builder.current_span;
|
||||
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "only a string LITERAL coerces to 'cstring' implicitly; an arbitrary string may be an unterminated view — materialize it with to_cstring(s)", .{});
|
||||
}
|
||||
return val;
|
||||
},
|
||||
// cstring → string: the length is implicit (strlen), so the
|
||||
// conversion is never silent — `from_cstring(c)` is the zero-copy
|
||||
// view, `substr(from_cstring(c), 0, ...)` the owned copy.
|
||||
.cstring_to_string_reject => {
|
||||
if (self.diagnostics) |d| {
|
||||
const cs = self.builder.current_span;
|
||||
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "'cstring' does not coerce to 'string' implicitly (the length is implicit); convert with from_cstring(c)", .{});
|
||||
}
|
||||
return val;
|
||||
},
|
||||
// void → Optional: produce null (void is the type of null_literal)
|
||||
.void_to_optional => return self.builder.constNull(dst_ty),
|
||||
// Concrete → Optional wrapping (coerce to the inner type first)
|
||||
|
||||
Reference in New Issue
Block a user