fix: reject implicit ?T -> bool coercion instead of silent false (issue 0169)
The Optional->Concrete unwrap classify rule treated ?i64 -> bool as unwrap+narrow (both builtin), silently yielding false for every optional (present or null). specs.md defines no implicit optional->bool conversion. Reject it: conversions.zig adds an optional_to_bool_reject plan (dst == bool, child != bool); coerce.zig emits a located diagnostic suggesting '!= null'. Covers arg/field-init/return via the shared coerceMode. The if-opt presence test (issue 0164) is a separate path, untouched. Regression: examples/diagnostics/1199-diagnostics-optional-to-bool.sx + conversions.test.zig unit test. Verified by 3 adversarial reviews, suite 789/0. Filed adjacent issue 0179 (whole implicit ?T->concrete unwrap family silently miscompiles a null optional; design-touching).
This commit is contained in:
@@ -647,6 +647,16 @@ 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);
|
||||
},
|
||||
// Optional → bool: there is no implicit presence-test coercion. The
|
||||
// old unwrap-then-narrow ladder silently produced `false` for every
|
||||
// optional (issue 0169). Reject with a fix-it pointing at `!= null`.
|
||||
.optional_to_bool_reject => {
|
||||
if (self.diagnostics) |d| {
|
||||
const cs = self.builder.current_span;
|
||||
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "cannot use a value of type '{s}' where 'bool' is expected; test presence explicitly with '!= null'", .{self.formatTypeName(src_ty)});
|
||||
}
|
||||
return val;
|
||||
},
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user