test: group examples into per-category folders
Move examples/*.sx and their expected/ snapshots into per-category subfolders (examples/<category>/...). Folder = leading filename token, with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus runner and LSP sweep now discover each category's expected/ dir, while issues/ stays flat. Example 1058's repo-root-relative companion import is made file-relative. Path strings embedded in 164 snapshots were regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
// Accessing an unknown field on a struct produces a clear
|
||||
// `error: field 'X' not found on type 'Y'` diagnostic and exit 1.
|
||||
|
||||
Vec :: struct { x: f32; y: f32; }
|
||||
|
||||
main :: () -> i32 {
|
||||
v := Vec.{ x = 1.0, y = 2.0 };
|
||||
return xx v.bogus;
|
||||
}
|
||||
7
examples/diagnostics/1101-diagnostics-err-tuple-oob.sx
Normal file
7
examples/diagnostics/1101-diagnostics-err-tuple-oob.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
// Out-of-range tuple index produces a clear
|
||||
// `error: field 'N' not found on type 'tuple'` diagnostic and exit 1.
|
||||
|
||||
main :: () -> i32 {
|
||||
t := (10, 20);
|
||||
return xx t.42;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Dot-shorthand `.Variant(args)` without a tagged-union target type produces
|
||||
// `error: cannot infer enum type for '.X'` instead of crashing.
|
||||
|
||||
main :: () -> i32 {
|
||||
x := .Foo(1, 2);
|
||||
return 0;
|
||||
}
|
||||
22
examples/diagnostics/1103-diagnostics-err-bad-variant.sx
Normal file
22
examples/diagnostics/1103-diagnostics-err-bad-variant.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// A match arm with a variant name that doesn't exist on the subject's
|
||||
// enum/tagged-union produces `error: no variant 'X' on type 'Y'` instead of
|
||||
// falling back to the arm index (which used to cause duplicate switch cases
|
||||
// and LLVM verification failures).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Shape :: enum {
|
||||
circle: f32;
|
||||
rect: struct { w, h: f32; };
|
||||
none;
|
||||
}
|
||||
|
||||
main :: () {
|
||||
s :Shape = .circle(3.14);
|
||||
if s == {
|
||||
case .circle: (r) { print("r={}\n", r); }
|
||||
case .Bogus: (x) { print("bogus={}\n", x); }
|
||||
case .none: print("none\n");
|
||||
case .rect: print("rect\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Passing a default-conv sx function as a abi(.c) fn-pointer
|
||||
// silently mismatches ABIs — historically that meant the C-side caller
|
||||
// supplied no `__sx_ctx` slot 0 and the sx-side body read garbage.
|
||||
// The compiler now rejects the coercion outright with a "call-convention
|
||||
// mismatch" diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
sx_handler :: (arg: *void) -> *void { return arg; }
|
||||
|
||||
main :: () -> i32 {
|
||||
fp : (*void) -> *void abi(.c) = sx_handler;
|
||||
return 0;
|
||||
}
|
||||
14
examples/diagnostics/1105-diagnostics-compile-error.sx
Normal file
14
examples/diagnostics/1105-diagnostics-compile-error.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// `compile_error(msg)` raises a build-time diagnostic at the call
|
||||
// site. Used by builder fns (e.g. `#insert build_block_convert(...)`)
|
||||
// to reject malformed pack shapes with a clear message rather than
|
||||
// silently producing wrong code.
|
||||
//
|
||||
// The diagnostic appears at the source position of the
|
||||
// `compile_error` call. Argument must be a string literal — runtime
|
||||
// expressions can't be reported as compile errors.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#run compile_error("intentional compile error from #run");
|
||||
|
||||
main :: () { }
|
||||
@@ -0,0 +1,25 @@
|
||||
// Scalar binary operators check operand-type compatibility. The result
|
||||
// type is otherwise taken from the left operand, so mixing a non-numeric
|
||||
// type (here `string`) would lower as `<op> : i64` and either reinterpret
|
||||
// the string's bytes (arithmetic / bitwise → garbage) or feed mismatched
|
||||
// types to `icmp` (ordering → LLVM verifier failure). All such mismatches
|
||||
// are now rejected at compile time:
|
||||
// - arithmetic `+ - * / %` (numeric / vector / pointer operands)
|
||||
// - ordering `< <= > >=` (numeric / enum / pointer operands)
|
||||
// - bitwise `& | ^` (integer / enum operands)
|
||||
// - shift `<< >>` (integer / enum operands)
|
||||
// Legitimate mixes (int+float promotion, flags-enum bitwise, enum/pointer
|
||||
// comparison) are unaffected — see `examples/50-smoke.sx`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
n : i64 = 40;
|
||||
s : string = "nope";
|
||||
a := n + s; // arithmetic: i64 + string
|
||||
b := s * n; // arithmetic: non-numeric LHS (string * i64)
|
||||
c := n < s; // ordering: i64 < string
|
||||
d := n & s; // bitwise: i64 & string
|
||||
e := n << s; // shift: i64 << string
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// A by-reference loop capture (`for xs (*m)`) binds `m` to a `*T`.
|
||||
// Passing it where a `T` value is expected used to slip through to the
|
||||
// LLVM verifier ("Call parameter type does not match function signature").
|
||||
// The compiler now reports it at the call site with a fix-it: write `m.*`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Move :: struct { flag: i64; }
|
||||
|
||||
take :: (m: Move) -> i64 { return m.flag; }
|
||||
|
||||
main :: () -> i32 {
|
||||
moves : [2]Move = .[ Move.{ flag = 1 }, Move.{ flag = 2 } ];
|
||||
for moves (*m) {
|
||||
take(m);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Passing a `*T` where a `T` value is expected is caught at the call site —
|
||||
// not only for `for xs (*m)` loop captures (see 215) but for any pointer,
|
||||
// here a `*Move` parameter forwarded into a by-value parameter. Without the
|
||||
// check this slipped through to the LLVM verifier as "Call parameter type
|
||||
// does not match function signature".
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Move :: struct { flag: i64; }
|
||||
|
||||
take :: (m: Move) -> i64 { return m.flag; }
|
||||
|
||||
forward :: (m: *Move) -> i64 { return take(m); }
|
||||
|
||||
main :: () -> i32 {
|
||||
mv : Move = .{ flag = 7 };
|
||||
return xx forward(@mv);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// `.*` on a non-pointer must be a clean compile diagnostic, NOT a codegen
|
||||
// panic. Regression: a stale `value.*` (e.g. after a parameter changed from
|
||||
// `*T` to `T` by value) used to lower a `.deref` with an `.unresolved` result
|
||||
// type, which slipped through to emit_llvm's "unresolved type reached LLVM
|
||||
// emission" panic with no source location. `lowerDerefExpr` now diagnoses it.
|
||||
// Expected: a clean error pointing at the deref; exit 1.
|
||||
|
||||
Point :: struct { x: i32; y: i32; }
|
||||
|
||||
main :: () -> i32 {
|
||||
p : Point = .{ x = 3, y = 4 };
|
||||
q := p.*; // ERROR: `p` is a Point value, not a pointer
|
||||
return q.x;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Auto-ref of a COMPOUND lvalue (field access / index / deref) passed to a
|
||||
// `*T` parameter must reference the REAL lvalue, not a copy. Regression: a
|
||||
// field-access argument (e.g. `make_move(self.board, m)`) silently passed the
|
||||
// address of a temporary copy, so mutations through the pointer were lost with
|
||||
// no diagnostic. A plain local (`bump(x)`) already auto-ref'd; now compound
|
||||
// lvalues do too. Expected: w.s.v == 42 (the real field was mutated).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
S :: struct { v: i32; }
|
||||
W :: struct { s: S; }
|
||||
|
||||
bump :: (p: *S) { p.v = p.v + 41; }
|
||||
|
||||
main :: () -> i32 {
|
||||
w : W = .{ s = .{ v = 1 } };
|
||||
bump(w.s); // field access, no `@` — auto-refs &w.s
|
||||
print("w.s.v = {}\n", w.s.v); // 42, not 1
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// A value parameter declared `T: Type` (WITHOUT the `$` generic sigil) used in
|
||||
// a type position is rejected with a hint to write `$T: Type`. Previously the
|
||||
// type resolver silently fabricated a 0-field struct named `T`, so the call
|
||||
// compiled and rendered as `T{}` at runtime with no diagnostic.
|
||||
// Regression (issue 0064). Expected: one error per `T` use site; exit 1.
|
||||
idwrap :: (T: Type, f: Closure() -> T) -> T { return f(); }
|
||||
|
||||
main :: () -> i32 {
|
||||
return idwrap(i32, closure(() -> i32 { return 7; }));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// An identifier used in a type position that names no declared type, builtin,
|
||||
// or in-scope generic parameter is rejected. Previously the type resolver's
|
||||
// empty-struct-stub fallback silently interned a 0-field struct under the typo,
|
||||
// so the program compiled and ran. Regression (issue 0064, broader fix).
|
||||
// Expected: a clean "unknown type" error at the field; exit 1.
|
||||
Point :: struct {
|
||||
x: i32;
|
||||
y: Coordnate; // typo for a non-existent type
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// An identifier used in a LOCAL variable's type annotation that names no
|
||||
// declared type is rejected, exactly like a signature or struct-field use.
|
||||
// Previously a body-level annotation bypassed the check: the type resolver's
|
||||
// empty-struct stub silently gave the local a 0-field type, so `v: Coordnate
|
||||
// = 5` compiled and ran (the `5` dropped) with no diagnostic. Regression
|
||||
// (issue 0064, body-level positions). Expected: error at the annotation; exit 1.
|
||||
main :: () -> i32 {
|
||||
v: Coordnate = 5;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// An unknown type in a NESTED closure body's local annotation is rejected,
|
||||
// with the enclosing function's generic params still in scope. Previously the
|
||||
// body walk stopped at closure / nested-function boundaries, so a typo'd type
|
||||
// inside a closure slipped through and silently became a 0-field struct.
|
||||
// Regression (issue 0064, nested scopes). Expected: error at the annotation; exit 1.
|
||||
main :: () -> i32 {
|
||||
f := closure(() -> i32 {
|
||||
bad: Coordnate = ---;
|
||||
return 0;
|
||||
});
|
||||
return f();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// `cast(T)` where `T` is a value parameter declared `: Type` (without the `$`
|
||||
// generic sigil) is rejected with the generic-param hint. Previously it
|
||||
// silently cast to a fabricated empty struct (an unknown *literal* cast target
|
||||
// already errored via value resolution, but the value-param case was silent).
|
||||
// Regression (issue 0064, cast position). Expected: tailored error; exit 1.
|
||||
conv :: (T: Type, x: i32) -> i32 {
|
||||
return cast(T) x;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
return conv(i32, 5);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// A tuple literal used in a type position (`(i32, i32)` reinterpreted as a tuple
|
||||
// type at a type-demanding site like `size_of`) must list only types. A non-type
|
||||
// element — here the `1` in `(i32, 1)` — is rejected with a user-facing
|
||||
// diagnostic instead of silently fabricating an `i64` field for that slot.
|
||||
// Regression (issue 0067).
|
||||
// Expected: a clean "tuple type element is not a type" error at the `1`; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
print("bad tuple type size = {}\n", size_of((i32, 1)));
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// A top-level VALUE constant name used in a type position is rejected. Without
|
||||
// the fix the unknown-type pass added every `const_decl` name to its declared-
|
||||
// type set, so a value const (`NotAType :: 123`) satisfied the check and the
|
||||
// type resolver's unknown-name fallback then fabricated an empty struct — the
|
||||
// program ran and printed `NotAType{}`. Now only consts whose value introduces a
|
||||
// type (declarations / type-expression aliases) count as type names.
|
||||
// Regression (issue 0068).
|
||||
// Expected: a clean "unknown type 'NotAType'" error at the annotation; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
NotAType :: 123;
|
||||
|
||||
main :: () -> i32 {
|
||||
v: NotAType = ---;
|
||||
print("value = {}\n", v);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// A top-level global initialized from a non-constant expression (here a field
|
||||
// access on a module constant, `K.x`) is rejected with a diagnostic. Without
|
||||
// the fix `registerTopLevelGlobal`'s init_val serializer handled only literals
|
||||
// / array / struct literals / identifiers and let every other shape fall through
|
||||
// to a null payload, so the global silently zero-initialized (`g=0`) — a wrong
|
||||
// value with no error.
|
||||
// Regression (issue 0072).
|
||||
// Expected: "global 'g' must be initialized by a compile-time constant"; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Point :: struct { x: i32; y: i32; }
|
||||
K : Point : Point.{ x = 9, y = 4 };
|
||||
g : i32 = K.x;
|
||||
|
||||
main :: () -> i32 {
|
||||
print("g={}\n", g);
|
||||
return g;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// A value binding (parameter or local `var`) spelled as a reserved/builtin
|
||||
// type name is rejected at the declaration site, across every declaration
|
||||
// form: a parameter name (`u8`), a typed local (`i64`, `bool`), and a `:=`
|
||||
// local (`string`). Such a spelling parses as a `.type_expr` rather than an
|
||||
// `.identifier`, so the address-of family in lowering mis-lowers it (issue
|
||||
// 0076). Expected: one error per offending name; exit 1.
|
||||
#import "modules/std.sx";
|
||||
|
||||
takes_u8 :: (u8: i32) -> i32 { return u8; }
|
||||
|
||||
main :: () -> i32 {
|
||||
i64 : i32 = 3;
|
||||
bool : bool = true;
|
||||
string := "x";
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// A value binding spelled as a reserved type name (`i2`, the `sN` arbitrary-
|
||||
// width int syntax) is rejected at its declaration site even when it lives in
|
||||
// an IMPORTED module — the reserved-name binding diagnostic covers every
|
||||
// compiled module, not just the main file. Without universal coverage the
|
||||
// binding reaches lowering and aborts LLVM verification (a loaded aggregate
|
||||
// passed by value to a `*Box` param).
|
||||
//
|
||||
// Regression (issue 0077): the imported-module facet of issue 0076. Expected:
|
||||
// one clean diagnostic pointing at the imported module's `i2 := ...`, exit 1 —
|
||||
// NOT an LLVM verifier abort.
|
||||
#import "modules/std.sx";
|
||||
mod :: #import "1120-diagnostics-imported-reserved-type-name/mod.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
return mod.run_imported_reserved_name();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct { total: i64 = 0; count: i64 = 0; }
|
||||
|
||||
update :: ufcs (self: *Box, n: i64) {
|
||||
self.total += n;
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
run_imported_reserved_name :: () -> i32 {
|
||||
i2 := Box.{ total = 0, count = 0 };
|
||||
update(@i2, 5);
|
||||
i2.update(7);
|
||||
print("imported i2 total={} count={}\n", i2.total, i2.count);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Reserved/builtin type names are rejected as binding NAMES across every
|
||||
// control-flow and destructuring form, not just plain `var`/param decls: a
|
||||
// destructure name (`i2`), an `if`/`while` optional binding (`u8`/`i16`), a
|
||||
// `for` capture and index name (`bool`/`i32`), and a match-arm capture
|
||||
// (`string`). Each spelling parses as a `.type_expr`, so the address-of family
|
||||
// in lowering mis-lowers it (a loaded aggregate passed by value to a `ptr`
|
||||
// param → LLVM verifier abort). The declaration-site diagnostic comes from one
|
||||
// EXHAUSTIVE binding-name walk, so no syntactic binding form can slip through.
|
||||
//
|
||||
// Regression (issue 0076, attempt-4 coverage). Expected: one error per
|
||||
// offending name; exit 1 — NOT an LLVM verifier abort.
|
||||
#import "modules/std.sx";
|
||||
|
||||
pair :: () -> (i64, i64) { (1, 2) }
|
||||
maybe :: () -> ?i64 { return null; }
|
||||
|
||||
main :: () -> i32 {
|
||||
i2, rest := pair(); // destructure name
|
||||
if u8 := maybe() { } // if optional binding
|
||||
while i16 := maybe() { break; } // while optional binding
|
||||
xs := [3]i64.{ 10, 20, 30 };
|
||||
for xs (bool) { } // for capture name
|
||||
for xs, 0.. (v, i32) { } // for index name
|
||||
opt: ?i64 = 5;
|
||||
r := if opt == { // match-arm capture
|
||||
case .some: (string) { 0 }
|
||||
case .none: { 0 }
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// A reserved/builtin type name is rejected as a binding name inside an `impl`
|
||||
// block's method too — both as a parameter (`u8`) and as a local (`i2`). The
|
||||
// impl method is reached through the exhaustive binding-name walk's
|
||||
// `impl_block` arm (→ each method's `fn_decl`), so an `impl` method is no more
|
||||
// exempt than a free function. Without the diagnostic the reserved local's
|
||||
// `@i2` mis-lowers (a loaded aggregate passed by value to a `*Box` param →
|
||||
// LLVM verifier abort).
|
||||
//
|
||||
// Regression (issue 0076, attempt-4 coverage). Expected: one error for the
|
||||
// param and one for the local; exit 1.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct { total: i64 = 0; count: i64 = 0; }
|
||||
update :: (self: *Box, n: i64) { self.total += n; self.count += 1; }
|
||||
|
||||
Doer :: protocol { go :: (self: *Self, n: i64); }
|
||||
|
||||
impl Doer for Box {
|
||||
go :: (self: *Box, u8: i64) {
|
||||
i2 := Box.{ total = 1 };
|
||||
update(@i2, u8);
|
||||
self.total += i2.total;
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
b := Box.{};
|
||||
b.go(7);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// A reserved/builtin type name is rejected as the error-tag binding of a
|
||||
// `catch` (`u8`) and of an `onfail` (`i64`). Both are reached through the
|
||||
// exhaustive binding-name walk's `catch_expr` / `onfail_stmt` arms. The tag is
|
||||
// a scalar, so before the diagnostic these spellings were silently accepted
|
||||
// (they never reached the address-of mis-lowering) — the binding must still be
|
||||
// rejected at its declaration.
|
||||
//
|
||||
// Regression (issue 0076, attempt-4 coverage). Expected: one error for each
|
||||
// binding; exit 1.
|
||||
#import "modules/std.sx";
|
||||
|
||||
E :: error { Bad }
|
||||
|
||||
must :: (n: i32) -> !E {
|
||||
if n < 0 { raise error.Bad; }
|
||||
return;
|
||||
}
|
||||
|
||||
classify :: (n: i32) -> !E {
|
||||
onfail (i64) { } // onfail tag binding
|
||||
must(n) catch (u8) { return; }; // catch tag binding
|
||||
return;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
classify(-1) catch { };
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// A reserved type name used as a DESTRUCTURE binding name (`i2`) is rejected
|
||||
// even when it lives in an IMPORTED module — the exhaustive binding-name walk
|
||||
// descends the `namespace_decl` an `mod :: #import` wraps and renders the
|
||||
// diagnostic against that module's source (issue 0077's universal-coverage
|
||||
// rule applied to the destructure form). Without it the binding reaches
|
||||
// lowering and aborts LLVM verification.
|
||||
//
|
||||
// Regression (issues 0076 + 0077, attempt-4 coverage). Expected: one clean
|
||||
// diagnostic pointing at the imported module's `i2, rest := ...`, exit 1.
|
||||
#import "modules/std.sx";
|
||||
mod :: #import "1124-diagnostics-imported-reserved-destructure/mod.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
return mod.run();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
pair :: () -> (i64, i64) { (1, 2) }
|
||||
|
||||
run :: () -> i32 {
|
||||
i2, rest := pair(); // destructure name in an IMPORTED module
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// A reserved/builtin type name used as a PARAMETER name is rejected inside the
|
||||
// two method-with-body forms that carry their params as bare name lists rather
|
||||
// than `Param` nodes: a protocol default-body method (`u8`) and a sx-defined
|
||||
// runtime-class (`#objc_class`) method (`i16`). The declaration-site diagnostic
|
||||
// underlines the OFFENDING PARAMETER itself, not the enclosing `protocol` /
|
||||
// `#objc_class` block — each method's `param_name_spans` is threaded from the
|
||||
// parser so the caret lands on the parameter token.
|
||||
//
|
||||
// Regression (issue 0076, attempt-5 span precision). Expected: one error per
|
||||
// offending parameter, each caret on the parameter name; exit 1.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/build.sx";
|
||||
|
||||
Greeter :: protocol {
|
||||
greet :: (self: *Self, u8: i64) -> i64 {
|
||||
return u8;
|
||||
}
|
||||
}
|
||||
|
||||
SxFoo :: #objc_class("SxFoo") {
|
||||
counter: i32;
|
||||
|
||||
bump :: (self: *Self, i16: i32) {
|
||||
self.counter += i16;
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// A module-global aggregate with a NULL pointer field is fine (null is a
|
||||
// compile-time constant), but a sibling field initialized from a NON-constant
|
||||
// expression (here a runtime function call) must still be rejected loudly. The
|
||||
// presence of an accepted `null` must NOT widen the gate to admit the
|
||||
// non-constant neighbor.
|
||||
// Regression (issue 0081): the null-pointer fix must not regress the
|
||||
// reject-loud behavior for genuinely non-constant initializers (issues
|
||||
// 0072/0080). Expected: "global 'boxes' must be initialized by a compile-time
|
||||
// constant"; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
runtime_marker :: () -> i64 { return 7; }
|
||||
|
||||
Box :: struct { p: *i64; marker: i64; }
|
||||
boxes : [1]Box = .[ .{ p = null, marker = runtime_marker() } ];
|
||||
|
||||
main :: () -> i32 {
|
||||
print("marker={}\n", boxes[0].marker);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// A module-global enum-literal initializer naming a variant that does not exist
|
||||
// must be rejected loudly — never silently zero-initialized to the first tag.
|
||||
// Regression (issue 0082): the enum-literal global serializer resolves the tag
|
||||
// against the destination enum type; an unknown variant emits a diagnostic and
|
||||
// fails the build instead of falling back to a null (zero-tag) initializer.
|
||||
// Expected: "'.purple' is not a variant of enum 'Color'"; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: enum u8 { red; green; blue; }
|
||||
bad : Color = .purple;
|
||||
|
||||
main :: () -> i32 {
|
||||
print("{}\n", bad);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// A comptime `#run` global initializer that yields a function reference cannot
|
||||
// be serialized to a static constant: at global-init time (Pass 0) functions
|
||||
// are not yet declared, and the comptime serialization path has no later
|
||||
// re-emit, so the func_ref can never resolve to a real function pointer. The
|
||||
// compiler must reject this with a diagnostic AND a CLEAN non-zero exit — never
|
||||
// print the error and then fall through into an undef initializer that crashes
|
||||
// (pre-fix: the diagnostic printed, emission continued, and the JIT segfaulted
|
||||
// calling through the undef pointer → exit 134).
|
||||
// Regression (issue 0079 follow-up): every global-init serialization bail now
|
||||
// routes through `failGlobalInit`, which sets the halt flag so the driver aborts
|
||||
// after emit() instead of shipping the placeholder.
|
||||
// Expected: "comptime init of 'fp' produced a reference to function 'add'…";
|
||||
// exit 1, no segfault.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
add :: (a: i32, b: i32) -> i32 { a + b }
|
||||
|
||||
pick :: () -> (i32, i32) -> i32 { return add; }
|
||||
|
||||
fp :: #run pick();
|
||||
|
||||
main :: () -> i32 {
|
||||
print("{}\n", fp(3, 4));
|
||||
return 0;
|
||||
}
|
||||
24
examples/diagnostics/1129-diagnostics-array-dim-not-const.sx
Normal file
24
examples/diagnostics/1129-diagnostics-array-dim-not-const.sx
Normal file
@@ -0,0 +1,24 @@
|
||||
// An array dimension that is not a compile-time integer constant is a hard
|
||||
// error, not a silently-fabricated 0-length array. Here a type alias's
|
||||
// dimension is a runtime function call (`get()`), which is genuinely not
|
||||
// compile-time-known — the registration-time resolver cannot evaluate it.
|
||||
//
|
||||
// (A const-FOLDABLE expression dimension such as `[M + 1]` is NOT an error — it
|
||||
// folds; see examples/0144-types-const-expr-array-dim.sx. Only a dimension with
|
||||
// a genuinely runtime operand halts here.)
|
||||
//
|
||||
// Regression (issue 0083): the stateless resolver printed a non-fatal warning
|
||||
// and fabricated length 0, then let compilation continue — producing a 0-byte
|
||||
// alloca and corrupt element access. It now yields the `.unresolved` sentinel,
|
||||
// which the alias registration surfaces as this diagnostic, aborting the build
|
||||
// with a non-zero exit.
|
||||
#import "modules/std.sx";
|
||||
|
||||
get :: () -> i64 { return 5; }
|
||||
BadArr :: [get()]i64;
|
||||
|
||||
main :: () {
|
||||
a : BadArr = ---;
|
||||
a[0] = 7;
|
||||
print("a0={}\n", a[0]);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// An array dimension that folds to a valid compile-time integer but exceeds a
|
||||
// `u32` (`[5_000_000_000]i64`) is a hard error — a clean sx diagnostic with a
|
||||
// non-zero exit, NOT a compiler panic.
|
||||
//
|
||||
// Regression (issue 0087 / F0.4 attempt 6): the dimension folded to a valid i64
|
||||
// (5e9) and was then narrowed with an unchecked `@intCast` to u32, aborting the
|
||||
// COMPILER with "integer does not fit in destination type". Every dim/lane fold
|
||||
// now narrows through the single range-checked `program_index.foldDimU32`, which
|
||||
// reports an out-of-u32-range dimension as this diagnostic and halts the build.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
a : [5000000000]i64 = ---;
|
||||
print("unreachable: {}\n", a.len);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// An atomic op on a non-scalar type is rejected with a clean diagnostic
|
||||
// (not a raw LLVM verifier error). Stream A (atomics) guard.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/atomic.sx";
|
||||
|
||||
main :: () {
|
||||
arr : [8]u8 = .[0, 0, 0, 0, 0, 0, 0, 0];
|
||||
x := atomic_load([8]u8, @arr, .seq_cst);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// An array dimension that folds to a valid compile-time integer but exceeds a
|
||||
// `u32` is a hard error — and it must report the SAME precise diagnostic whether
|
||||
// the array is written directly (`a : [5_000_000_000]i64`, see example 1130) or
|
||||
// behind a type ALIAS (`Big :: [5_000_000_000]i64`, here). Both forms now route
|
||||
// the dimension through one shared folder + one shared message map, so they
|
||||
// cannot diverge.
|
||||
//
|
||||
// Regression (issue 0083 / F0.4 attempt 7): the stateless alias-registration
|
||||
// path collapsed `foldDimU32`'s distinct `.too_large` outcome into `null` and
|
||||
// emitted ONE generic "an array dimension is not a compile-time integer
|
||||
// constant" message — FALSE, since 5_000_000_000 IS a compile-time integer
|
||||
// constant; it merely doesn't fit a `u32`. The alias path now consults the
|
||||
// shared fold and emits the precise "does not fit in u32" message, matching the
|
||||
// direct form. (A genuinely non-const alias dim still gets the generic message —
|
||||
// see example 1129.)
|
||||
#import "modules/std.sx";
|
||||
|
||||
Big :: [5000000000]i64;
|
||||
|
||||
main :: () {
|
||||
a : Big = ---;
|
||||
a[0] = 7;
|
||||
print("unreachable: {}\n", a[0]);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// An atomic load with an ordering LLVM forbids (.release / .acq_rel) is
|
||||
// rejected with a clean diagnostic. Stream A (atomics) guard.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/atomic.sx";
|
||||
|
||||
main :: () {
|
||||
n : i64 = 0;
|
||||
x := atomic_load(i64, @n, .release);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// A NON-integral float constant (`4.5`) used as an array dimension is a hard
|
||||
// error — only an integral float (`4.0`) folds to a count. Clean diagnostic +
|
||||
// non-zero exit, NOT a fabricated length.
|
||||
//
|
||||
// The dimension follows the unified float→int narrowing rule (F0.11 / issue
|
||||
// 0095): an integral float folds, a non-integral one is rejected as "not an
|
||||
// integer" — the SAME `floatToIntExact` judgement the typed local/field/param/
|
||||
// const sites use (the count path is the last of the five sites to unify).
|
||||
//
|
||||
// Regression (F0.4 attempt 8, Agra ruling): the integral-float rule accepts
|
||||
// `4.0` as a dimension but must keep rejecting `4.5` (it is not an integer).
|
||||
#import "modules/std.sx";
|
||||
|
||||
N : f64 : 4.5;
|
||||
|
||||
main :: () {
|
||||
a : [N]i64 = ---;
|
||||
print("unreachable: {}\n", a.len);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// A NEGATIVE integral float (`-2.0`) used as an array dimension is a hard error.
|
||||
// The integral-float rule folds and negates it to `-2`, then the shared u32 dim
|
||||
// gate rejects a below-minimum dimension — a clean diagnostic + non-zero exit.
|
||||
//
|
||||
// Regression (F0.4 attempt 8, Agra ruling): integral floats fold, but a negative
|
||||
// result is still rejected (a dimension must be non-negative).
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
a : [-2.0]i64 = ---;
|
||||
print("unreachable: {}\n", a.len);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// A generic value-param arg that does not fit the param's declared integer type
|
||||
// (`Box(5_000_000_000)` for `$K: u32`) is a hard error — a clean diagnostic +
|
||||
// non-zero exit, NOT a silent truncating bind.
|
||||
//
|
||||
// Regression (F0.4 attempt 8, item 1): `resolveValueParamArg` bound the folded
|
||||
// i64 without range-checking the declared type, so an out-of-u32 arg compiled
|
||||
// and ran. The bind now routes a `u32` count through the shared
|
||||
// `program_index.foldDimU32` gate (the same one array dims / Vector lanes use),
|
||||
// so an oversized value is rejected before instantiation.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct ($K: u32) { value: i64; }
|
||||
|
||||
main :: () {
|
||||
b : Box(5000000000) = ---;
|
||||
print("unreachable\n");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// A generic value-param arg that does not fit the param's declared integer type
|
||||
// is a hard error even when that type is reached through a type ALIAS
|
||||
// (`$K: Count` where `Count :: u32`, `$K: Small` where `Small :: i8`) — a clean
|
||||
// diagnostic + non-zero exit, NOT a silent truncating bind.
|
||||
//
|
||||
// Regression (issue 0083): the value-param range gate matched only BUILTIN
|
||||
// constraint names, so an aliased constraint slipped past `intTypeRange` and
|
||||
// `Box(5_000_000_000)` with `$K: Count` compiled and bound a truncated value.
|
||||
// The constraint now resolves to its underlying builtin (`Count` → u32,
|
||||
// `Small` → i8) before range-checking, so an aliased integer constraint behaves
|
||||
// exactly like the builtin it names — at both the struct and type-fn binders.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Count :: u32;
|
||||
Small :: i8;
|
||||
Box :: struct ($K: Count) { value: i64; }
|
||||
Tiny :: struct ($K: Small) { value: i64; }
|
||||
|
||||
main :: () {
|
||||
b : Box(5000000000) = ---;
|
||||
t : Tiny(300) = ---;
|
||||
print("unreachable {} {}\n", b.value, t.value);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// A DIRECT array dimension that is genuinely not a compile-time integer (a
|
||||
// runtime call) is a hard error — ONE clean diagnostic + non-zero exit. Crucially
|
||||
// it must NOT fabricate a length and must NOT crash later in lowering: the bad
|
||||
// var is used downstream (element store + read, `.len`), and lowering has to bail
|
||||
// gracefully on the `.unresolved` type rather than `@panic` in `sizeOf` or pile
|
||||
// on cascade errors.
|
||||
//
|
||||
// Regression (issue 0083): the stateful `resolveArrayLen` emitted the diagnostic
|
||||
// then `return 0` — fabricating a 0-length array (0-byte alloca, OOB access) to
|
||||
// dodge the `sizeOf` panic. It now returns null → the `.unresolved` sentinel; the
|
||||
// binding's lowering bails on it (a field access on an already-diagnosed
|
||||
// `.unresolved` value stays silent), so the single real diagnostic aborts the
|
||||
// build with no fabrication and no panic.
|
||||
#import "modules/std.sx";
|
||||
|
||||
get :: () -> i64 { return 5; }
|
||||
|
||||
main :: () {
|
||||
a : [get()]i64 = ---;
|
||||
a[0] = 7;
|
||||
print("unreachable: {} {}\n", a.len, a[0]);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// A FAILED value-param bind on a type-RETURNING FUNCTION must emit exactly its
|
||||
// own diagnostic and NOT cascade a bogus `field '…' not found on '<fn>'` when the
|
||||
// binding is later field-accessed. The type-fn binder must poison the binding to
|
||||
// `.unresolved` (the diagnosed-poison sentinel) — exactly like the struct binder —
|
||||
// so the downstream `.len` is suppressed, not reported as a second error.
|
||||
//
|
||||
// Regression (issue 0083): the type-fn path (`instantiateTypeFunction`) fell
|
||||
// through to an empty-struct placeholder named after the function on a failed
|
||||
// value-param bind, so `a.len` produced a second `field 'len' not found on
|
||||
// 'MakeC'` error. The struct binder already returned `.unresolved` here; the
|
||||
// type-fn binder now matches it. Three failure modes, three clean diagnostics,
|
||||
// zero field cascades:
|
||||
// - value-param overflow via an aliased integer constraint (`$K: Count`),
|
||||
// - a non-const value-param arg (`get()`),
|
||||
// - an unknown TYPE arg (`NoSuchType`) — must still report the unknown type.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Count :: u32;
|
||||
MakeC :: ($K: Count, $T: Type) -> Type { return [K]T; }
|
||||
get :: () -> u32 { return 4; }
|
||||
|
||||
main :: () {
|
||||
a : MakeC(5000000000, i64) = ---;
|
||||
b : MakeC(get(), i64) = ---;
|
||||
c : MakeC(3, NoSuchType) = ---;
|
||||
print("unreachable {} {} {}\n", a.len, b.len, c.len);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// A NON-integral float (`4.5`) as an `inline for` range bound is a hard error:
|
||||
// the loop cursor must be a compile-time integer, so only an integral float
|
||||
// (`4.0`, `-2.0`) folds. Clean diagnostic + non-zero exit.
|
||||
//
|
||||
// Regression (F0.4 attempt 11, Agra ruling): range bounds are exempt from the
|
||||
// count negative-rejection (negatives are valid endpoints), but the
|
||||
// must-be-integer requirement still applies — `4.5` has no integer value.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
s := 0;
|
||||
inline for 0..4.5 (i) { s += i; }
|
||||
print("unreachable: {}\n", s);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// A reserved/builtin type-name spelling is rejected as the NAME of a `::`
|
||||
// declaration too — both a constant (`i2 :: 5`) and a function
|
||||
// (`u8 :: (…) {…}`). A function name and a const name are binding sites just
|
||||
// like `i2 := …`; previously the `::` decl forms slipped past the
|
||||
// reserved-name check, so a bare reserved-name function compiled silently and
|
||||
// became callable — bypassing the backtick rule that handwritten sx must use.
|
||||
// The backtick escape (`` `i2 :: … ``, examples/0153) is the only way to spell
|
||||
// these names; `#import c` extern decls remain exempt (examples/1220).
|
||||
//
|
||||
// Regression (issue 0089). Expected: one error per declaration, each caret on
|
||||
// the declared name; exit 1.
|
||||
#import "modules/std.sx";
|
||||
|
||||
i2 :: 5;
|
||||
u8 :: (n: i64) -> i64 { return n + 7; }
|
||||
|
||||
main :: () -> i32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// A reserved/builtin type-name spelling is rejected as the NAME of EVERY
|
||||
// type-introducing `::` declaration too — struct, enum, union, error-set, and
|
||||
// a typed constant — not just `:=` / value-const / function names (those are
|
||||
// examples/1140). Each is a declaration-name binding site: a bare reserved
|
||||
// spelling there mis-classifies and is rejected, exactly like `i2 := …`. The
|
||||
// backtick escape (`` `i2 :: struct{…} ``, examples/0154) is the only way to
|
||||
// spell these names in handwritten sx; `#import c` extern decls stay exempt
|
||||
// (examples/1220).
|
||||
//
|
||||
// Regression (issue 0089 — attempt-4: 0076 holds across every decl kind).
|
||||
// Expected: one error per declaration, each caret ON the declared name; exit 1.
|
||||
#import "modules/std.sx";
|
||||
|
||||
i8 :: struct { v: i64; }
|
||||
i16 :: enum { A; B; }
|
||||
u16 :: union { a: i32; b: f32; }
|
||||
u32 :: error { Bad, Empty }
|
||||
i2 : i64 : 5;
|
||||
|
||||
main :: () -> i32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// A bare reserved/builtin type-name spelling is rejected as the NAME of a
|
||||
// STRUCT-BODY constant too — both the untyped (`i2 :: 5`) and the typed
|
||||
// (`u8 : i64 : 9`) forms — exactly like a top-level const (examples/1140) or a
|
||||
// type decl (examples/1141). A struct member constant is a binding site, so a
|
||||
// bare reserved spelling mis-classifies and is rejected; the caret lands ON the
|
||||
// constant's name (not at 1:1). The backtick escape (examples/0156) is the only
|
||||
// way to spell these names in handwritten sx.
|
||||
//
|
||||
// Regression (issue 0089 — attempt-5: 0076 holds for struct-body consts, with
|
||||
// the caret on the name). Expected: one error per const, caret on the name; exit 1.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Holder :: struct {
|
||||
i2 :: 5;
|
||||
u8 : i64 : 9;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// A typed module-level constant whose initializer does not match its
|
||||
// annotation is a compile-time type error — not a silently-accepted const.
|
||||
// Each declaration below pairs an initializer with an incompatible annotation,
|
||||
// so the compiler must emit a `type mismatch` diagnostic at the initializer and
|
||||
// abort (exit 1) rather than registering a usable const.
|
||||
//
|
||||
// Regression (issue 0088): `N : string : 4` was accepted; `print(N)` then
|
||||
// segfaulted (an integer emitted as a `string` const → a bogus pointer) and
|
||||
// `[N]i64` folded `N` to 4. The fix rejects the declaration at the root. The
|
||||
// validation is type-based, so a const-EXPRESSION initializer (`E : string :
|
||||
// M + 2`, `V : string : -M`) is rejected just like a literal — not skipped
|
||||
// because its node kind isn't a literal (issue 0088, attempt 2).
|
||||
//
|
||||
// The mixed-numeric pair (`i64 : M + 0.5`, `i64 : 0.5 + M`) is rejected in BOTH
|
||||
// operand orders: arithmetic binary-op inference now promotes int+float to the
|
||||
// float result (`Lowering.arithResultType`), so an i64 annotation no longer
|
||||
// matches a float-producing initializer regardless of which operand is the
|
||||
// float (issue 0088, attempt 3 — the inferExprType promotion root fix).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
M :: 2;
|
||||
|
||||
N : string : 4; // integer literal where a string is annotated
|
||||
F : i64 : "x"; // string literal where an integer is annotated
|
||||
B : i64 : true; // boolean literal where an integer is annotated
|
||||
G : i64 : 1.5; // float literal where an integer is annotated
|
||||
E : string : M + 2; // integer EXPRESSION where a string is annotated
|
||||
V : string : -M; // integer (unary) expression where a string is annotated
|
||||
BAD : i64 : M + 0.5; // mixed int+float (int LHS) → f64, rejected vs i64
|
||||
BAD2 : i64 : 0.5 + M; // mixed float+int (float LHS) → f64, rejected vs i64 — order-independent
|
||||
|
||||
main :: () {
|
||||
print("unreachable\n");
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// The 7 type-introspection builtins (size_of, align_of, field_count,
|
||||
// type_name, type_eq, type_is_unsigned, is_flags) take ONLY types. A
|
||||
// value argument is a compile-time error, not a silently-reinterpreted
|
||||
// TypeId index.
|
||||
//
|
||||
// Regression (issue 0090, attempt 2): each builtin used to accept a
|
||||
// non-type value and reinterpret it — `type_is_unsigned(6)` read 6 as a
|
||||
// TypeId and returned the signedness of types[6] (`u8` → true);
|
||||
// `size_of(true)` sized `typeof(true)` (8). The strict `$T: Type` guard
|
||||
// (`Lowering.reflectionTypeArgGuard`) now rejects any argument whose
|
||||
// static type is not `Type` and emits "<builtin> expects a type, got
|
||||
// '<type>'" at the offending argument, aborting (exit 1).
|
||||
//
|
||||
// Reject cases use `true`/`1.5` (whose types — bool/f64 — are stable)
|
||||
// rather than an integer literal, so the pinned diagnostics don't drift
|
||||
// when the int-literal default type changes.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
print("{}\n", size_of(true)); // bool, not a type
|
||||
print("{}\n", align_of(1.5)); // f64, not a type
|
||||
print("{}\n", field_count(true)); // bool, not a type
|
||||
print("{}\n", type_name(1.5)); // f64, not a type
|
||||
print("{}\n", type_eq(true, false)); // both bool — both rejected
|
||||
print("{}\n", type_is_unsigned(true)); // bool, not a type
|
||||
print("{}\n", is_flags(1.5)); // f64, not a type
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Assigning to a field that does not exist on a struct produces the same
|
||||
// `field 'X' not found on type 'Y'` diagnostic as the read path (1100), and
|
||||
// exits 1 — never the `.unresolved` LLVM-emission panic, never a silent store
|
||||
// into a neighbouring field.
|
||||
//
|
||||
// Regression (issue 0094): the lvalue field lookup left `field_ty = .unresolved`
|
||||
// (lowerAssignment's assignment-target path) or silently GEP'd field 0 as `.i64`
|
||||
// (lowerExprAsPtr's fallback / lowerMultiAssign's struct loop), so a missing-field
|
||||
// store either built a pointer-to-`.unresolved` that panicked at LLVM emission or
|
||||
// silently wrote field 0. All three lvalue sites now emit the field-not-found
|
||||
// diagnostic: the assignment-target path (`p.q`), the nested lvalue-pointer path
|
||||
// (`o.missing.a`), and the multi-target store path (`p.r, y`).
|
||||
|
||||
Point :: struct { x: i64; }
|
||||
Inner :: struct { a: i64; }
|
||||
Outer :: struct { inner: Inner; }
|
||||
|
||||
main :: () -> i32 {
|
||||
p := Point.{ x = 1 };
|
||||
p.q = 2; // site 1: lowerAssignment target path
|
||||
|
||||
o := Outer.{ inner = Inner.{ a = 1 } };
|
||||
o.missing.a = 5; // site 2: lowerExprAsPtr fallback
|
||||
|
||||
y : i64 = 0;
|
||||
p.r, y = 3, 4; // site 3: lowerMultiAssign field path
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Unified float→int narrowing rule (F0.11), NEGATIVE side: a NON-INTEGRAL float
|
||||
// implicitly narrowing to an integer-typed binding is a COMPILE ERROR — not a
|
||||
// silent truncation. The rule fires at a typed LOCAL initializer, a function
|
||||
// PARAM default, a struct FIELD default, AND an array DIMENSION; each emits a
|
||||
// narrowing diagnostic at the offending float and aborts (exit 1). It fires
|
||||
// whether the float is a LITERAL (`1.5`), an INT-const-expression (`M + 0.5`,
|
||||
// with `M :: 2`), a FLOAT-const-leaf expression (`F + 0.25`, with `F : f64 : 2.5`,
|
||||
// = 2.75), a builtin FLOAT numeric-limit leaf inside an expression
|
||||
// (`f64.true_min + 0.5` = 0.5), or a float `%` whose remainder is non-integral
|
||||
// (`5.5 % 2.0` = 1.5) — all of these are the core of issue 0095, which previously
|
||||
// slipped through and truncated. The fix is the integral-fold / non-integral-error
|
||||
// rule shared across all five sites (local, field, param, const, and array
|
||||
// dimension), applied to ANY compile-time-constant float expression (literal,
|
||||
// int-const leaf, float-const leaf, numeric-limit leaf, `+ - * / %`, and
|
||||
// combinations) — the compile-time float evaluator is at parity with the integer
|
||||
// one, so no float leaf shape escapes. The array-dimension site phrases the same
|
||||
// rejection as "must be an integer".
|
||||
//
|
||||
// The escape hatch stays open: `y : i64 = xx 1.5` (or `cast(i64) 1.5`)
|
||||
// truncates with no error — exercised on the POSITIVE side (example 0168).
|
||||
//
|
||||
// Regression (issue 0095): `y : i64 = 1.5` silently truncated to 1,
|
||||
// `y : i64 = M + 0.5` to 2, and `y : i64 = F + 0.25` (float-const leaf) to 2.
|
||||
#import "modules/std.sx";
|
||||
|
||||
M :: 2; // int module const, for the INT-const-EXPRESSION cases
|
||||
F : f64 : 2.5; // float module const, for the FLOAT-const-LEAF cases
|
||||
|
||||
Bad :: struct {
|
||||
f : i64 = 3.5; // non-integral float LITERAL field default → error
|
||||
fe : i64 = M + 0.5; // non-integral int-const-EXPR field default → error
|
||||
ff : i64 = F + 0.25; // non-integral float-const-LEAF field default → error
|
||||
}
|
||||
|
||||
badLit :: (x : i64 = 2.5) -> i64 { return x; } // non-integral LITERAL param default → error
|
||||
badExpr :: (x : i64 = M + 0.5) -> i64 { return x; } // non-integral int-const-EXPR param default → error
|
||||
badFlt :: (x : i64 = F + 0.25) -> i64 { return x; } // non-integral float-const-LEAF param default → error
|
||||
|
||||
main :: () {
|
||||
y : i64 = 1.5; // non-integral float LITERAL local → error
|
||||
ye : i64 = M + 0.5; // non-integral int-const-EXPRESSION local → error
|
||||
yf : i64 = F + 0.25; // non-integral float-const-LEAF local → error
|
||||
yn : i64 = f64.true_min + 0.5; // non-integral numeric-limit float expr → error
|
||||
ym : i64 = 5.5 % 2.0; // non-integral float `%` remainder (1.5) → error
|
||||
ad : [F + 0.25]i64 = ---; // non-integral float-const-LEAF array DIMENSION → error
|
||||
b := Bad.{};
|
||||
print("{} {} {}\n", b.f, b.fe, b.ff);
|
||||
print("{} {} {}\n", badLit(), badExpr(), badFlt());
|
||||
print("{} {} {} {} {} {}\n", y, ye, yf, yn, ym, ad.len);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Unified float→int narrowing rule (F0.11), float-DIVISION pin: a compile-time
|
||||
// float division (`5.0 / 2.0` = 2.5) is a NON-INTEGRAL float, so narrowing it
|
||||
// implicitly into an integer-typed binding is a COMPILE ERROR — exactly like any
|
||||
// other non-integral float (example 1146). The division is the subtle case: its
|
||||
// operands (`5.0`, `2.0`) are individually INTEGRAL, so a naive integer fold
|
||||
// would truncate `5.0 / 2.0` to 2 with no diagnostic. The rule fires at all five
|
||||
// sites — a typed module CONST, a struct FIELD default, a function PARAM default,
|
||||
// a typed LOCAL, and an array DIMENSION — because the shared compile-time integer
|
||||
// folder now refuses a division with a float operand, deferring it to the float
|
||||
// evaluator + the unified rule (integral folds, non-integral errors). A float
|
||||
// operand on either side (literal or float-typed const) makes the `/` a float
|
||||
// division.
|
||||
//
|
||||
// The escape hatch stays open: `xx (5.0 / 2.0)` truncates to 2 with no error, and
|
||||
// an INTEGRAL float division (`6.0 / 2.0` → 3) folds — both exercised on the
|
||||
// POSITIVE side (example 0168).
|
||||
//
|
||||
// Regression (issue 0095, F0.11-6): `5.0 / 2.0` at a typed local, field default,
|
||||
// param default, typed const, and array dimension all silently folded to 2 via
|
||||
// integer truncating division; each now rejects the non-integral float.
|
||||
#import "modules/std.sx";
|
||||
|
||||
// An UNTYPED float-EXPRESSION const carries a placeholder `i64` type, yet its
|
||||
// value is float — `ME / 2` is still float division and must reject (judged by
|
||||
// the const's VALUE, not its declared type), at BOTH the typed-binding path and
|
||||
// the count path.
|
||||
ME :: 4.0 + 1.0; // untyped float-EXPRESSION const (= 5.0)
|
||||
|
||||
// Typed CONST: declared but not referenced, so the single narrowing error is not
|
||||
// followed by a downstream "unresolved const" cascade.
|
||||
K : i64 : 5.0 / 2.0; // 2.5 non-integral float-DIVISION const → error
|
||||
|
||||
BadField :: struct {
|
||||
f : i64 = 5.0 / 2.0; // non-integral float-DIVISION field default → error
|
||||
}
|
||||
|
||||
badParam :: (x : i64 = 5.0 / 2.0) -> i64 { return x; } // float-DIVISION param default → error
|
||||
|
||||
main :: () {
|
||||
local : i64 = 5.0 / 2.0; // non-integral float-DIVISION local → error
|
||||
dim : [5.0 / 2.0]i64 = ---; // non-integral float-DIVISION array dimension → error
|
||||
cdiv : i64 = ME / 2; // untyped float-EXPR const division (5.0/2 = 2.5) → error
|
||||
cdim : [ME / 2]i64 = ---; // same, at the count path → error
|
||||
b := BadField.{};
|
||||
print("{} {} {} {} {} {}\n", local, b.f, badParam(), dim.len, cdiv, cdim.len);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// A raw value binding whose spelling shadows a builtin INTEGER type name
|
||||
// (`` `i8 ``) used as an array DIMENSION through one of its fields. Field
|
||||
// access on a raw value is an ORDINARY runtime field read, so `` `i8.max `` is
|
||||
// a runtime value — NOT the builtin `i8.max` (= 127) and NOT a compile-time
|
||||
// constant. An array dimension demands a compile-time integer constant, so the
|
||||
// dimension is rejected with the same diagnostic a plainly-named runtime field
|
||||
// read (`b.max`) earns — the backtick spelling changes nothing.
|
||||
//
|
||||
// Sibling (integer) half of the F0.11-7 fix: the compile-time INTEGER evaluator
|
||||
// (`evalConstIntExpr`) misclassified a raw value-shadow receiver as the builtin
|
||||
// `<IntType>.min`/`.max` accessor, silently folding 127 and fabricating a
|
||||
// 127-element array. The `is_raw` guard now defers it to an ordinary field
|
||||
// read, so it surfaces as a non-constant dimension instead of a silent wrong
|
||||
// length.
|
||||
//
|
||||
// Negative companion to 0169 (the FLOAT-field narrowing half, exit 0).
|
||||
//
|
||||
// Regression (issue 0095 / F0.11-7).
|
||||
#import "modules/std.sx";
|
||||
|
||||
DimBox :: struct { max: i64; }
|
||||
|
||||
main :: () {
|
||||
`i8 := DimBox.{ max = 3 };
|
||||
// Raw value-shadow field read → a runtime value, not the builtin `i8.max`
|
||||
// (127) and not a compile-time constant → rejected as a non-const dim.
|
||||
arr : [`i8.max]f32 = ---;
|
||||
print("len={}\n", arr.len);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// The pre-multi-iterable `for xs: (x)` syntax (colon before the capture) is
|
||||
// rejected with a migration hint.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
xs : [2]i64 = .[1, 2];
|
||||
for xs: (x) { }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// A for capture group is positional: one capture per iterable (or none).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
xs : [2]i64 = .[1, 2];
|
||||
ys : [2]i64 = .[3, 4];
|
||||
for xs, ys (x) { }
|
||||
}
|
||||
8
examples/diagnostics/1151-diagnostics-for-open-first.sx
Normal file
8
examples/diagnostics/1151-diagnostics-for-open-first.sx
Normal file
@@ -0,0 +1,8 @@
|
||||
// The FIRST iterable drives the loop, so it must be bounded; an open range
|
||||
// `a..` may only follow it.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
for 0.. (i) { }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// A range with an explicit end marker (`..=` / `..<`) is the bounded form —
|
||||
// it requires an end expression; the open form is `a..`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
for 0..= (i) { }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Range elements are synthesized values with no storage — `*` capture is
|
||||
// rejected.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
for 0..3 (*i) { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// In a for header the trailing paren group is the CAPTURE; a call iterable
|
||||
// therefore needs one too. `()` cannot be a capture — parse error with the
|
||||
// hint (`for f(n) (x) { }` / `for (f(n)) { }` / bind it first).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
g :: () -> i64 { 1 }
|
||||
|
||||
main :: () {
|
||||
for g() { }
|
||||
}
|
||||
12
examples/diagnostics/1155-diagnostics-for-not-iterable.sx
Normal file
12
examples/diagnostics/1155-diagnostics-for-not-iterable.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// The collision case of the positional capture rule: `for f(n) { }` reads
|
||||
// `(n)` as the capture, making the iterable `f` itself — not iterable, with
|
||||
// the parenthesize/add-capture hint.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
f :: (n: i64) -> i64 { n }
|
||||
|
||||
main :: () {
|
||||
n := 1;
|
||||
for f(n) { }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// An integer literal that does not fit its integer target type is a
|
||||
// compile error (no silent wrap): both faces diagnosed in one run.
|
||||
// Regression (issue 0112): `x : i8 = 300` bound 44, `y : u8 = 256` bound 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
x : i8 = 300;
|
||||
print("x: {}\n", x);
|
||||
y : u8 = 256;
|
||||
print("y: {}\n", y);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// The catch error binding is parenthesized — `catch (e) { }`; a bare
|
||||
// binding is rejected with a migration hint. (Same rule as the for-loop
|
||||
// capture; `onfail (e) { }` follows it too.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
E :: error { Bad };
|
||||
|
||||
f :: () -> (i64, !E) { raise error.Bad; }
|
||||
|
||||
main :: () {
|
||||
v := f() catch e { 0 };
|
||||
print("{}\n", v);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// An extensionless #import that matches BOTH a `.sx` file and a sibling
|
||||
// directory of the same name is ambiguous — the compiler refuses to pick
|
||||
// silently and asks for the explicit `.sx` spelling. `modules/std` is the
|
||||
// canonical collision: `modules/std.sx` (the prelude) sits next to
|
||||
// `modules/std/` (mem/fs/process/...).
|
||||
|
||||
#import "modules/std";
|
||||
|
||||
main :: () -> i32 {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// An untyped array-literal constant infers its element type from the
|
||||
// elements (ints -> i64; a numeric int/float mix promotes to f64). A
|
||||
// NON-NUMERIC mix has no unified element type — diagnosed, with the
|
||||
// annotation as the fix.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
BAD :: .["alpha", 1];
|
||||
|
||||
main :: () {
|
||||
print("{}\n", BAD[0]);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// An array constant's elements must be compile-time constants — a call
|
||||
// (or any runtime expression) in an element slot is diagnosed. `#run`
|
||||
// is the tool for computed constants.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
f :: () -> i64 { 7 }
|
||||
BAD : [2]i64 : .[1, f()];
|
||||
|
||||
main :: () {
|
||||
print("{}\n", BAD[0]);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// A typed array constant's annotation dimension must match the
|
||||
// initializer's element count.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
BAD : [3]i64 : .[1, 2];
|
||||
|
||||
main :: () {
|
||||
print("{}\n", BAD[0]);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Writes through a module constant are compile errors — for every target
|
||||
// chain rooted at a const: a struct const's field (this used to compile
|
||||
// and BUS-ERROR at runtime — issue 0116), an array const's element,
|
||||
// a compound assignment, and a bare scalar const. A local that shadows
|
||||
// the const name stays writable (see 0177/0178 for the value semantics).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: struct { r, g, b: i64; }
|
||||
WHITE :: Color.{ r = 255, g = 255, b = 255 };
|
||||
K : [4]i64 : .[11, 22, 33, 44];
|
||||
N : i64 : 4;
|
||||
|
||||
main :: () {
|
||||
WHITE.r = 0;
|
||||
K[0] = 5;
|
||||
K[1] += 2;
|
||||
N = 9;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// A compile-time index into an array constant is bounds-checked at fold
|
||||
// time — out of range is a diagnostic, never a wrap or a silent
|
||||
// runtime read.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
K : [4]i64 : .[11, 22, 33, 44];
|
||||
|
||||
main :: () {
|
||||
b : [K[9]]u8 = ---;
|
||||
print("{}\n", b.len);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// `inline for` pack rejections: (1) a pack-element capture exposes only the
|
||||
// constraint protocol's interface (same rule as `xs[i]`, example 0530);
|
||||
// (2) a pack element cannot be captured by reference (`(*x)` — an element is
|
||||
// an AST-substituted call arg, no storage); (3) a trailing pack shorter than
|
||||
// the driving iterable; (4) a non-pack, non-range iterable.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Show :: protocol { show :: (self: *Self) -> string; }
|
||||
IntBox :: struct { v: i64; }
|
||||
impl Show for IntBox { show :: (self: *IntBox) -> string { int_to_string(self.v) } }
|
||||
|
||||
leak :: (..xs: Show) {
|
||||
inline for xs (x) {
|
||||
print("{}\n", x.v);
|
||||
}
|
||||
}
|
||||
borrow :: (..xs: Show) {
|
||||
inline for xs (*x) { }
|
||||
}
|
||||
short :: (..xs: Show) {
|
||||
inline for 0..5, xs (i, x) { }
|
||||
}
|
||||
|
||||
main :: () {
|
||||
leak(IntBox.{ v = 5 });
|
||||
borrow(IntBox.{ v = 5 });
|
||||
short(IntBox.{ v = 1 }, IntBox.{ v = 2 });
|
||||
arr := .[1, 2, 3];
|
||||
inline for arr (x) { }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// A `$T`-generic RETURN type with no parameter mentioning `$T` is rejected
|
||||
// at the declaration: the fn isn't a template (type params derive from
|
||||
// params), and no call site could ever bind the return. All three declare
|
||||
// surfaces diagnose: a top-level fn, a struct-body method, and a
|
||||
// (non-parameterised) impl method. Each used to PANIC the compiler at LLVM
|
||||
// emission via the `.unresolved` tripwire — even when never called.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
make :: () -> $T { 0 }
|
||||
|
||||
Foo :: struct {
|
||||
x: i64;
|
||||
weird :: (self: *Foo) -> $T { 0 }
|
||||
}
|
||||
|
||||
Show2 :: protocol { show2 :: (self: *Self) -> string; }
|
||||
IntBox :: struct { v: i64; }
|
||||
impl Show2 for IntBox {
|
||||
show2 :: (self: *IntBox) -> string { "x" }
|
||||
leak :: (self: *IntBox) -> $T { 0 }
|
||||
}
|
||||
|
||||
main :: () { print("ok\n"); }
|
||||
11
examples/diagnostics/1166-diagnostics-ufcs-not-opted-in.sx
Normal file
11
examples/diagnostics/1166-diagnostics-ufcs-not-opted-in.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// A dot-call on a PLAIN free function (no `ufcs` marker, no alias) is
|
||||
// rejected with a tailored help: direct call, pipe, or declare it ufcs.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
bump :: (x: i64) -> i64 { x + 1 }
|
||||
|
||||
main :: () {
|
||||
f : i64 = 40;
|
||||
print("{}\n", f.bump());
|
||||
}
|
||||
26
examples/diagnostics/1167-diagnostics-call-arity-mismatch.sx
Normal file
26
examples/diagnostics/1167-diagnostics-call-arity-mismatch.sx
Normal file
@@ -0,0 +1,26 @@
|
||||
// Wrong argument counts to fixed-arity functions are rejected at the call
|
||||
// site — bare calls, flat-imported stdlib fns, method dot-calls, and ufcs
|
||||
// dot-calls — instead of reaching LLVM verification ("Incorrect number of
|
||||
// arguments passed to called function!").
|
||||
// Regression (issue 0123).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
add2 :: (a: i64, b: i64) -> i64 { return a + b; }
|
||||
|
||||
Point :: struct {
|
||||
x: i64;
|
||||
scaled :: (self: Point, k: i64) -> i64 { return self.x * k; }
|
||||
}
|
||||
|
||||
bump :: ufcs (p: Point, by: i64) -> i64 { return p.x + by; }
|
||||
|
||||
main :: () -> i32 {
|
||||
_ = add2(1, 2, 3); // plain bare call, too many
|
||||
_ = add2(1); // plain bare call, too few
|
||||
_ = concat("a", "b", "c"); // flat-imported stdlib fn, too many
|
||||
p := Point.{ x = 5 };
|
||||
_ = p.scaled(2, 9); // method dot-call, too many
|
||||
_ = p.bump(1, 2); // ufcs dot-call, too many
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// A direct call to a generic fn whose arguments cannot bind a TYPE
|
||||
// param diagnoses at the call site instead of monomorphizing with the
|
||||
// param unbound. A `string` arg at a `[]$T` param is the canonical
|
||||
// uninferrable shape (string deliberately does not bind `[]$T`); it
|
||||
// used to stamp `.unresolved` through the body and PANIC the compiler
|
||||
// at LLVM emission via the sentinel tripwire.
|
||||
//
|
||||
// Regression (issue 0126, diagnostic half).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
first :: (xs: []$T) -> T {
|
||||
return xs[0];
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
print("{}\n", first("abc"));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Enum literals against unusable destinations are DIAGNOSED, never a
|
||||
// silent variant 0 (issue 0098's sibling holes): an unknown variant of a
|
||||
// real enum, a non-enum destination type, and a destination-less literal
|
||||
// each get their own error.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Platform :: enum u8 { ios; android_apk; }
|
||||
|
||||
main :: () -> i32 {
|
||||
a : Platform = .nonexistent; // unknown variant: lists the real ones
|
||||
b : i64 = .foo; // non-enum destination
|
||||
print("{}{}\n", a, b);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// A destination-less enum literal is diagnosed (issue 0098's third hole —
|
||||
// it previously panicked the LLVM backend with an unresolved type). Kept
|
||||
// as the ONLY error in this file: the diagnostic is cascade-guarded, so it
|
||||
// stays silent when the destination type itself already failed to resolve.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
c := .ios;
|
||||
print("{}\n", c);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// `!` on an operand that has no truthiness (neither bool, integer, nor
|
||||
// an error binding) is diagnosed instead of silently bit-flipped
|
||||
// (issue 0129's diagnostic half).
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
s := "text";
|
||||
if !s { print("unreachable\n"); }
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// One C symbol bound twice with DIFFERENT sx signatures is diagnosed
|
||||
// (issue 0128): the first registration used to silently win, mis-typing
|
||||
// every call through the second declaration. Equal signatures share one
|
||||
// registration silently (see std's read/write bound by several modules).
|
||||
#import "modules/std.sx";
|
||||
|
||||
libc :: #library "c";
|
||||
// std/process.sx already binds getenv as `-> *u8`; this view disagrees.
|
||||
getenv_opt :: (name: [:0]u8) -> ?[:0]u8 extern libc "getenv";
|
||||
|
||||
main :: () -> i32 {
|
||||
p := getenv_opt("PATH");
|
||||
if p == null { return 1; }
|
||||
return 0;
|
||||
}
|
||||
13
examples/diagnostics/1173-diagnostics-cstring-coercions.sx
Normal file
13
examples/diagnostics/1173-diagnostics-cstring-coercions.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// cstring's coercion discipline (Odin-style): only a string LITERAL
|
||||
// converts implicitly — an arbitrary string may be an unterminated view
|
||||
// (use to_cstring); and cstring never converts to string implicitly —
|
||||
// the length is an O(n) strlen the code must ask for (from_cstring).
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
s := concat("a", "b");
|
||||
c : cstring = s; // error: non-literal string -> cstring
|
||||
t : string = c; // error: cstring -> string
|
||||
print("{}{}\n", t, cstring_len(c));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Phase 4 (FFI-linkage) interplay diagnostic: `extern` and `export` are the two
|
||||
// values of the same linkage axis — a declaration is either an import (`extern`)
|
||||
// or a definition (`export`), never both. The parser rejects the redundant
|
||||
// second keyword with a clear message (instead of the bare "expected ';'" the
|
||||
// body parser would otherwise emit).
|
||||
//
|
||||
// Expected: one error caret on the second keyword; exit 1.
|
||||
|
||||
f :: (a: i32) -> i32 extern export;
|
||||
|
||||
main :: () -> i32 { 0 }
|
||||
@@ -0,0 +1,13 @@
|
||||
// A parse error in an IMPORTED file must be located in THAT file, not the
|
||||
// importer. Regression: `import_sources` was wired to the diagnostics only
|
||||
// AFTER import resolution finished, so a parse error raised MID-resolution
|
||||
// (which aborts before that wiring) could not resolve the imported file's
|
||||
// source — the caret fell back to the root file and landed on an unrelated
|
||||
// line. The fix wires `import_sources` before resolving and pins the
|
||||
// diagnostic's `source_file` + offset to the imported file.
|
||||
//
|
||||
// The companion's error sits several lines down (after comments) so a caret
|
||||
// mislocated against THIS importer would be unmistakable.
|
||||
#import "1176-diagnostics-import-parse-error-location/broken.sx";
|
||||
|
||||
main :: () {}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Deliberately broken: exercises import parse-error LOCATION reporting.
|
||||
// These leading comment lines push the parse error down so its line number
|
||||
// differs from the importer's — a mislocated caret would point here-or-wrong
|
||||
// instead of at the real offending token below.
|
||||
//
|
||||
broken :: 1 2;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Taking the address of a scalar `::` constant is a compile error: a scalar
|
||||
// constant folds to its value and has NO storage (only array/struct constants
|
||||
// are immutable globals with a real address — see 0177). Covers a module-scope
|
||||
// const, a local const, and an inline-asm `-> @const` write-through (the path
|
||||
// that surfaced the bug). Before the fix, `@N` lowered to `inttoptr (i64 40 to
|
||||
// ptr)` — a wild pointer that segfaulted on deref and emitted invalid stores
|
||||
// for asm `-> @const`. Regression (issue 0138).
|
||||
|
||||
takes :: (p: *i64) {}
|
||||
|
||||
N :: 40;
|
||||
|
||||
main :: () {
|
||||
takes(@N); // module scalar const — no storage
|
||||
x :: 7;
|
||||
takes(@x); // local scalar const — no storage
|
||||
asm volatile { "mov %[c], #99", [c] "=r" -> @N }; // write-through to a const
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Diagnostic: a type that contains ITSELF by value has no finite size and must
|
||||
// be rejected loudly (not infinite-loop the size computation into a crash). A
|
||||
// pointer payload (`*Tree`) would break the cycle and is the fix the message
|
||||
// suggests. Covers both source decls and comptime-constructed types — this is
|
||||
// the source form (regression for issue 0139).
|
||||
#import "modules/std.sx";
|
||||
|
||||
Tree :: enum {
|
||||
node: Tree; // by-VALUE self-reference → infinitely sized
|
||||
leaf;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
t : Tree = .leaf;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// A comptime type construction (declare/define, reflection) that leaves a type
|
||||
// INCOMPLETE must surface a build-gating DIAGNOSTIC naming the reason — not
|
||||
// poison the decl to `.unresolved` silently and let that crash at LLVM emission
|
||||
// or hide behind a downstream cascade. Here `declare("Undefined")` mints a
|
||||
// forward nominal slot that is NEVER completed by a matching `define(handle, …)`;
|
||||
// the compiler rejects the incomplete type at its construction site (exit 1, no
|
||||
// panic).
|
||||
//
|
||||
// NOTE: an EXPLICITLY-defined empty type (empty struct/tuple/enum/tagged_union)
|
||||
// is VALID — see examples/0641. The remaining rejection is purely the
|
||||
// never-defined `declare` placeholder, which would otherwise panic codegen
|
||||
// (`verifySizes`: llvm_size != ir_size on an unsized forward slot).
|
||||
//
|
||||
// Regression (issue 0140): before the fix this panicked with "unresolved type
|
||||
// reached LLVM emission" (exit 134), because the interp's bail detail was
|
||||
// dropped (`catch return null`) and `.unresolved` reached codegen unannounced.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
// Declared but never `define`d — an incomplete forward slot.
|
||||
mk_undefined :: () -> Type {
|
||||
return declare("Undefined");
|
||||
}
|
||||
|
||||
Undefined :: mk_undefined();
|
||||
|
||||
main :: () -> i32 {
|
||||
u : Undefined = ---;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// A comptime-constructed enum (declare/define) with two same-named variants is
|
||||
// rejected loudly. Two `value` variants would make construction (`.value`) and
|
||||
// matching ambiguous — `define` bails naming the duplicate, instead of silently
|
||||
// minting a malformed enum that picks one arbitrarily.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
Bad :: define(declare("Bad"), .enum(.{ variants = .[
|
||||
EnumVariant.{ name = "value", payload = i64 },
|
||||
EnumVariant.{ name = "closed", payload = void },
|
||||
EnumVariant.{ name = "value", payload = f64 }, // duplicate name
|
||||
] }));
|
||||
|
||||
main :: () -> i32 {
|
||||
b : Bad = ---;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// A `declare("X")` that is never completed by `define(handle, info)` is rejected
|
||||
// loudly. `declare` mints an empty (undefined) nominal slot; without a matching
|
||||
// `define` it has zero variants, which is never a usable type — sizing /
|
||||
// constructing it would otherwise panic at codegen. The diagnostic names the
|
||||
// type and points at the bare `declare` so the missing `define` is obvious.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
Undef :: declare("Undef"); // never define()d
|
||||
|
||||
main :: () -> i32 {
|
||||
x : Undef = ---;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Diagnostic: a comptime-CONSTRUCTED enum (declare/define) that contains itself
|
||||
// BY VALUE is infinitely sized and rejected loudly — the same `checkInfiniteSize`
|
||||
// guard that covers source decls (examples/1178) also covers minted types. A
|
||||
// pointer payload (`*L`) breaks the cycle and is the fix the message suggests
|
||||
// (see examples/0618 for the working recursive `*List`).
|
||||
//
|
||||
// This is the constructed-type companion to 1178, and pins the "use-before-
|
||||
// define by value" corner of the metatype validation story: referencing a
|
||||
// declared slot by VALUE in its own definition is the one self-reference shape
|
||||
// that isn't legal (a `*L` pointer needs no layout, so it is).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
make :: () -> Type {
|
||||
h := declare("L");
|
||||
return define(h, .enum(.{ variants = .[
|
||||
EnumVariant.{ name = "cons", payload = L }, // by VALUE, not *L
|
||||
EnumVariant.{ name = "nil", payload = void },
|
||||
] }));
|
||||
}
|
||||
|
||||
L :: make();
|
||||
|
||||
main :: () -> i32 {
|
||||
x : L = .nil;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// A many-pointer `[*]T` carries NO length, so it cannot coerce to a slice `[]T`
|
||||
// implicitly — doing so would pass a bare 8-byte pointer where a 16-byte
|
||||
// `{ptr,len}` fat pointer is expected, silently corrupting the callee's view of
|
||||
// the data (garbage length, mis-aligned element reads). The compiler rejects it
|
||||
// loudly and tells the user to supply the length via `ptr[0..len]`.
|
||||
//
|
||||
// Regression (issue 0141): this silent mis-coercion segfaulted the comptime VM
|
||||
// and failed LLVM verification at runtime; it now produces a clean diagnostic.
|
||||
#import "modules/std.sx";
|
||||
|
||||
sum :: (s: []i64) -> i64 {
|
||||
total := 0;
|
||||
for s (x) { total += x; }
|
||||
return total;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
xs : List(i64) = .{};
|
||||
xs.append(10);
|
||||
xs.append(20);
|
||||
r := sum(xs.items); // [*]i64 → []i64 — needs xs.items[0..xs.len]
|
||||
print("{}\n", r);
|
||||
return 0;
|
||||
}
|
||||
10
examples/diagnostics/1184-diagnostics-weld-fn-unexported.sx
Normal file
10
examples/diagnostics/1184-diagnostics-weld-fn-unexported.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// Diagnostic: a `fn abi(.compiler)` whose name is NOT on the compiler
|
||||
// library's function-export list is a build error — the export list is the
|
||||
// safety boundary, so an unbound name can't silently fall through to dlsym.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
|
||||
not_a_real_compiler_fn :: (x: i64) -> i64 abi(.compiler);
|
||||
|
||||
main :: () { print("unreached\n"); }
|
||||
@@ -0,0 +1,16 @@
|
||||
// Diagnostic: a welded `compiler`-library function is comptime-only — it has no
|
||||
// runtime symbol (the comptime interpreter dispatches it to a Zig handler).
|
||||
// Calling one from runtime code is a build error with a clear message, NOT an
|
||||
// undefined-symbol link failure. (A comptime use — inside `#run` or a `::` —
|
||||
// is fine; see examples/0626.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
|
||||
StringId :: u32;
|
||||
intern :: (s: string) -> StringId abi(.compiler);
|
||||
|
||||
main :: () {
|
||||
id := intern("called at runtime");
|
||||
print("{}\n", id);
|
||||
}
|
||||
12
examples/diagnostics/1186-diagnostics-atomic-cas-ordering.sx
Normal file
12
examples/diagnostics/1186-diagnostics-atomic-cas-ordering.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// Atomic compare-exchange dual-ordering validation: the FAILURE ordering may not
|
||||
// be stronger than the SUCCESS ordering (LLVM rule). Here failure=.seq_cst (rank
|
||||
// 3) is stronger than success=.relaxed (rank 0) → loud diagnostic, not invalid IR.
|
||||
// Calls the intrinsic directly so the diagnostic span is stable (user file, not
|
||||
// the lib forward site). Stream A (atomics) A.2.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/atomic.sx";
|
||||
|
||||
main :: () {
|
||||
n : i64 = 0;
|
||||
_ := atomic_cmpxchg(i64, @n, 0, 1, .relaxed, .seq_cst);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// A fence with .relaxed ordering is rejected (LLVM has no monotonic/unordered
|
||||
// fence). Stream A (atomics) guard.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/atomic.sx";
|
||||
|
||||
main :: () {
|
||||
atomic_fence(.relaxed);
|
||||
}
|
||||
12
examples/diagnostics/1188-diagnostics-run-no-main.sx
Normal file
12
examples/diagnostics/1188-diagnostics-run-no-main.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// `sx run` on a program with no `main` must emit a clean diagnostic and exit
|
||||
// non-zero — never call into a garbage JIT address and segfault. A pre-JIT
|
||||
// entry-point check in main.zig (plus a defensive `main_addr == 0` backstop in
|
||||
// target.zig's runJITFromObject) replaces the old silent garbage-pointer call.
|
||||
//
|
||||
// Regression (issue 0137).
|
||||
#import "modules/std.sx";
|
||||
|
||||
// Intentionally no `main` — only a helper.
|
||||
greet :: () {
|
||||
print("unreachable\n");
|
||||
}
|
||||
16
examples/diagnostics/1189-diagnostics-unknown-builtin.sx
Normal file
16
examples/diagnostics/1189-diagnostics-unknown-builtin.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// A bodiless `#builtin` carrying a `$T: Type` parameter, whose name the
|
||||
// compiler does not recognize, must fail LOUDLY with an "unknown #builtin"
|
||||
// diagnostic — not silently evaluate to 0 (the CLAUDE.md silent-fallback
|
||||
// pattern). The generic monomorphization path (monomorphizeFunction's
|
||||
// builtin-body branch) now diagnoses an unresolved builtin name instead of
|
||||
// falling through to `ensureTerminator`'s `constInt(0)`.
|
||||
//
|
||||
// Regression (issue 0144).
|
||||
#import "modules/std.sx";
|
||||
|
||||
// `mystery` is not a recognized builtin.
|
||||
mystery :: ($T: Type, x: T) -> T #builtin;
|
||||
|
||||
main :: () {
|
||||
print("mystery(42) = {}\n", mystery(i64, 42));
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// A protocol method that omits the explicit receiver (the old implicit form)
|
||||
// is now a parse error — the receiver `self: *Self`/`self: Self` is required as
|
||||
// the first parameter. This guards the diagnostic.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Show :: protocol {
|
||||
show :: () -> string; // ERROR: missing `self` receiver
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: field 'bogus' not found on type 'Vec'
|
||||
--> examples/diagnostics/1100-diagnostics-err-field-not-found.sx:8:15
|
||||
|
|
||||
8 | return xx v.bogus;
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: field '42' not found on type 'tuple'
|
||||
--> examples/diagnostics/1101-diagnostics-err-tuple-oob.sx:6:15
|
||||
|
|
||||
6 | return xx t.42;
|
||||
| ^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user