Implement `assert` statements

66464ff6dada83798cc74ca7af758148e3cececd429291b16d62c172ef3b90be
Alexis Sellier committed ago 1 parent d21f24aa
lib/std/arch/rv64.rad +1 -1
66 66
pub const T5:   Reg = { n: 30 };  /// Temporary.
67 67
pub const T6:   Reg = { n: 31 };  /// Temporary.
68 68
69 69
/// Create a register from a number. Panics if `n > 31`.
70 70
pub fn reg(n: u8) -> Reg {
71 -
    debug::assert(n < 32);
71 +
    debug::check(n < 32);
72 72
    return Reg { n };
73 73
}
74 74
75 75
////////////////////////////
76 76
// Architecture constants //
lib/std/arch/rv64/emit.rad +2 -2
232 232
    e.code[index] = instr;
233 233
}
234 234
235 235
/// Record a block's address for branch resolution.
236 236
pub fn recordBlock(e: *mut Emitter, blockIdx: u32) {
237 -
    debug::assert(e.codeLen <= MAX_CODE_LEN);
237 +
    debug::check(e.codeLen <= MAX_CODE_LEN);
238 238
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
239 239
}
240 240
241 241
/// Record a function's code offset for call resolution.
242 242
pub fn recordFuncOffset(e: *mut Emitter, name: *[u8]) {
243 -
    debug::assert(e.codeLen <= MAX_CODE_LEN);
243 +
    debug::check(e.codeLen <= MAX_CODE_LEN);
244 244
    dict::insert(&mut e.labels.funcs, name, e.codeLen as i32 * super::INSTR_SIZE);
245 245
}
246 246
247 247
/// Record a function's start position for printing.
248 248
pub fn recordFunc(e: *mut Emitter, name: *[u8]) {
lib/std/arch/rv64/encode.rad +10 -10
104 104
         | ((funct7       & 0x7F) << 25);
105 105
}
106 106
107 107
/// Encode an I-type instruction.
108 108
fn encodeI(opcode: u32, rd: super::Reg, rs1: super::Reg, funct3: u32, imm: i32) -> u32 {
109 -
    debug::assert(isSmallImm(imm));
109 +
    debug::check(isSmallImm(imm));
110 110
111 111
    return (opcode        & 0x7F)
112 112
         | ((rd.n  as u32 & 0x1F)  << 7)
113 113
         | ((funct3       & 0x07)  << 12)
114 114
         | ((rs1.n as u32 & 0x1F)  << 15)
115 115
         | ((imm as u32   & 0xFFF) << 20);
116 116
}
117 117
118 118
/// Encode an S-type instruction.
119 119
fn encodeS(opcode: u32, rs1: super::Reg, rs2: super::Reg, funct3: u32, imm: i32) -> u32 {
120 -
    debug::assert(isSmallImm(imm));
120 +
    debug::check(isSmallImm(imm));
121 121
122 122
    return (opcode  & 0x7F)
123 123
         | ((imm as u32        & 0x1F) << 7)
124 124
         | ((funct3            & 0x07) << 12)
125 125
         | ((rs1.n as u32      & 0x1F) << 15)
127 127
         | ((imm as u32 >> 5   & 0x7F) << 25);
128 128
}
129 129
130 130
/// Encode a B-type (branch) instruction.
131 131
fn encodeB(opcode: u32, rs1: super::Reg, rs2: super::Reg, funct3: u32, imm: i32) -> u32 {
132 -
    debug::assert(isBranchImm(imm));
132 +
    debug::check(isBranchImm(imm));
133 133
134 134
    let imm11   = (imm as u32 >> 11) & 0x1;
135 135
    let imm4_1  = (imm as u32 >> 1)  & 0xF;
136 136
    let imm10_5 = (imm as u32 >> 5)  & 0x3F;
137 137
    let imm12   = (imm as u32 >> 12) & 0x1;
153 153
         | ((imm as u32  & 0xFFFFF) << 12);
154 154
}
155 155
156 156
/// Encode a J-type (jump) instruction.
157 157
fn encodeJ(opcode: u32, rd: super::Reg, imm: i32) -> u32 {
158 -
    debug::assert(isJumpImm(imm));
158 +
    debug::check(isJumpImm(imm));
159 159
160 160
    let imm20    = (imm as u32 >> 20) & 0x1;
161 161
    let imm10_1  = (imm as u32 >> 1)  & 0x3FF;
162 162
    let imm11    = (imm as u32 >> 11) & 0x1;
163 163
    let imm19_12 = (imm as u32 >> 12) & 0xFF;
204 204
    return encodeI(OP_IMM, rd, rs1, F3_AND, imm);
205 205
}
206 206
207 207
/// Shift left logical immediate: `rd = rs1 << shamt`.
208 208
pub fn slli(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
209 -
    debug::assert(shamt >= 0 and shamt < 64);
209 +
    debug::check(shamt >= 0 and shamt < 64);
210 210
    return encodeI(OP_IMM, rd, rs1, F3_SLL, shamt & 0x3F);
211 211
}
212 212
213 213
/// Shift right logical immediate: `rd = rs1 >> shamt` (zero-extend).
214 214
pub fn srli(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
215 -
    debug::assert(shamt >= 0 and shamt < 64);
215 +
    debug::check(shamt >= 0 and shamt < 64);
216 216
    return encodeI(OP_IMM, rd, rs1, F3_SRL, shamt & 0x3F);
217 217
}
218 218
219 219
/// Shift right arithmetic immediate: `rd = rs1 >> shamt` (sign-extend).
220 220
pub fn srai(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
221 -
    debug::assert(shamt >= 0 and shamt < 64);
221 +
    debug::check(shamt >= 0 and shamt < 64);
222 222
    // SRAI has bit 10 set in immediate field (becomes bit 30 in instruction)
223 223
    return encodeI(OP_IMM, rd, rs1, F3_SRL, (shamt & 0x3F) | 0b10000000000);
224 224
}
225 225
226 226
///////////////////////////
330 330
    return encodeI(OP_IMM32, rd, rs1, F3_ADD, imm);
331 331
}
332 332
333 333
/// Shift left logical immediate word: `rd = sign_ext((rs1 << shamt)[31:0])`.
334 334
pub fn slliw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
335 -
    debug::assert(shamt >= 0 and shamt < 32);
335 +
    debug::check(shamt >= 0 and shamt < 32);
336 336
    return encodeI(OP_IMM32, rd, rs1, F3_SLL, shamt & 0x1F);
337 337
}
338 338
339 339
/// Shift right logical immediate word: `rd = sign_ext((rs1[31:0] >> shamt))`.
340 340
pub fn srliw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
341 -
    debug::assert(shamt >= 0 and shamt < 32);
341 +
    debug::check(shamt >= 0 and shamt < 32);
342 342
    return encodeI(OP_IMM32, rd, rs1, F3_SRL, shamt & 0x1F);
343 343
}
344 344
345 345
/// Shift right arithmetic immediate word: `rd = sign_ext((rs1[31:0] >> shamt))` (sign-extended).
346 346
pub fn sraiw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
347 -
    debug::assert(shamt >= 0 and shamt < 32);
347 +
    debug::check(shamt >= 0 and shamt < 32);
348 348
    return encodeI(OP_IMM32, rd, rs1, F3_SRL, (shamt & 0x1F) | 0b10000000000);
349 349
}
350 350
351 351
/// Add word: `rd = sign_ext((rs1 + rs2)[31:0])`.
352 352
pub fn addw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 {
lib/std/arch/rv64/isel.rad +1 -1
389 389
390 390
                    emit::emit(s.e, encode::sub(super::SP, super::SP, rs));
391 391
392 392
                    if alignment > 1 {
393 393
                        let mask = 0 - alignment as i32;
394 -
                        debug::assert(encode::isSmallImm(mask));
394 +
                        debug::check(encode::isSmallImm(mask));
395 395
396 396
                        emit::emit(s.e, encode::andi(super::SP, super::SP, mask));
397 397
                    }
398 398
                    emit::emit(s.e, encode::mv(rd, super::SP));
399 399
                },
lib/std/arch/rv64/tests/assert.basic.rad added +19 -0
1 +
/// Test assert keyword.
2 +
@default fn main() -> i32 {
3 +
    // Assert with true condition should pass.
4 +
    assert true;
5 +
6 +
    // Assert with comparison.
7 +
    let x: i32 = 42;
8 +
    assert x == 42;
9 +
    assert x > 0;
10 +
    assert x != 0;
11 +
12 +
    // Assert with message.
13 +
    assert x == 42, "x should be 42";
14 +
15 +
    // Assert with block form.
16 +
    assert { x > 0 }, "x must be positive";
17 +
18 +
    return 0;
19 +
}
lib/std/arch/rv64/tests/assert.fail.rad added +6 -0
1 +
/// Test that assert with false condition triggers ebreak.
2 +
//! returns: 133
3 +
@default fn main() -> i32 {
4 +
    assert false;
5 +
    return 0;
6 +
}
lib/std/arch/rv64/tests/bool.comparison.slice.rad +15 -21
1 -
fn assert(cond: bool) {
2 -
    if not cond {
3 -
        panic;
4 -
    }
5 -
}
6 -
7 1
fn memEq(a: *[u8], b: *[u8]) -> bool {
8 2
    if a.len != b.len {
9 3
        return false;
10 4
    }
11 5
    for i in 0..a.len {
181 175
    return memEq(sliceU8("ABC"), "ABC")
182 176
       and sliceEqualI32(sliceI32(&[1, 2, 3]), &[1, 2, 3]);
183 177
}
184 178
185 179
@default fn main() -> i32 {
186 -
    assert(sliceEqual1());
187 -
    assert(sliceEqual2());
188 -
    assert(sliceEqual3());
189 -
    assert(sliceEqual4());
190 -
    assert(sliceEqualString1());
191 -
    assert(sliceEqualString2());
192 -
    assert(sliceEqualString3());
193 -
    assert(sliceEqualString4());
194 -
    assert(sliceEqualString5());
195 -
    assert(sliceNotEqualSameArray1());
196 -
    assert(sliceNotEqualSameArray2());
197 -
    assert(sliceEqualDifferentArray());
198 -
    assert(sliceNotEqualU8());
199 -
    assert(sliceNotEqualU16());
200 -
    assert(sliceReturnEqual());
180 +
    assert sliceEqual1();
181 +
    assert sliceEqual2();
182 +
    assert sliceEqual3();
183 +
    assert sliceEqual4();
184 +
    assert sliceEqualString1();
185 +
    assert sliceEqualString2();
186 +
    assert sliceEqualString3();
187 +
    assert sliceEqualString4();
188 +
    assert sliceEqualString5();
189 +
    assert sliceNotEqualSameArray1();
190 +
    assert sliceNotEqualSameArray2();
191 +
    assert sliceEqualDifferentArray();
192 +
    assert sliceNotEqualU8();
193 +
    assert sliceNotEqualU16();
194 +
    assert sliceReturnEqual();
201 195
202 196
    return 0;
203 197
}
lib/std/arch/rv64/tests/builtin.size.align.rad +36 -42
1 1
record Foo {
2 2
    x: u8,
3 3
    y: u32,
4 4
}
5 5
6 -
fn assert(cond: bool) {
7 -
    if not cond {
8 -
        panic;
9 -
    }
10 -
}
11 -
12 6
@default fn main() -> u8 {
13 -
    assert(@sizeOf(u8) == 1);
14 -
    assert(@alignOf(u8) == 1);
7 +
    assert @sizeOf(u8) == 1;
8 +
    assert @alignOf(u8) == 1;
15 9
16 -
    assert(@sizeOf(u16) == 2);
17 -
    assert(@alignOf(u16) == 2);
10 +
    assert @sizeOf(u16) == 2;
11 +
    assert @alignOf(u16) == 2;
18 12
19 -
    assert(@sizeOf(u32) == 4);
20 -
    assert(@alignOf(u32) == 4);
13 +
    assert @sizeOf(u32) == 4;
14 +
    assert @alignOf(u32) == 4;
21 15
22 -
    assert(@sizeOf(i32) == 4);
23 -
    assert(@alignOf(i32) == 4);
16 +
    assert @sizeOf(i32) == 4;
17 +
    assert @alignOf(i32) == 4;
24 18
25 -
    assert(@sizeOf(Foo) == 8);
26 -
    assert(@alignOf(Foo) == 4);
19 +
    assert @sizeOf(Foo) == 8;
20 +
    assert @alignOf(Foo) == 4;
27 21
28 -
    assert(@sizeOf([u16; 3]) == 6);
29 -
    assert(@alignOf([u16; 3]) == 2);
22 +
    assert @sizeOf([u16; 3]) == 6;
23 +
    assert @alignOf([u16; 3]) == 2;
30 24
31 -
    assert(@sizeOf(?u8) == 2);
32 -
    assert(@alignOf(?u8) == 1);
25 +
    assert @sizeOf(?u8) == 2;
26 +
    assert @alignOf(?u8) == 1;
33 27
34 -
    assert(@sizeOf(?*u32) == 8);
35 -
    assert(@alignOf(?*u32) == 8);
28 +
    assert @sizeOf(?*u32) == 8;
29 +
    assert @alignOf(?*u32) == 8;
36 30
37 -
    assert(@sizeOf(?*mut u32) == 8);
38 -
    assert(@alignOf(?*mut u32) == 8);
31 +
    assert @sizeOf(?*mut u32) == 8;
32 +
    assert @alignOf(?*mut u32) == 8;
39 33
40 -
    assert(@sizeOf(?*[u16]) == 16);
41 -
    assert(@alignOf(?*[u16]) == 8);
34 +
    assert @sizeOf(?*[u16]) == 16;
35 +
    assert @alignOf(?*[u16]) == 8;
42 36
43 -
    assert(@sizeOf(?*mut [u8]) == 16);
44 -
    assert(@alignOf(?*mut [u8]) == 8);
37 +
    assert @sizeOf(?*mut [u8]) == 16;
38 +
    assert @alignOf(?*mut [u8]) == 8;
45 39
46 -
    assert(@sizeOf(?Foo) == 12);
47 -
    assert(@alignOf(?Foo) == 4);
40 +
    assert @sizeOf(?Foo) == 12;
41 +
    assert @alignOf(?Foo) == 4;
48 42
49 -
    assert(@sizeOf(?[u16; 3]) == 8);
50 -
    assert(@alignOf(?[u16; 3]) == 2);
43 +
    assert @sizeOf(?[u16; 3]) == 8;
44 +
    assert @alignOf(?[u16; 3]) == 2;
51 45
52 -
    assert(@sizeOf(*u32) == 8);
53 -
    assert(@alignOf(*u32) == 8);
46 +
    assert @sizeOf(*u32) == 8;
47 +
    assert @alignOf(*u32) == 8;
54 48
55 -
    assert(@sizeOf(*mut u32) == 8);
56 -
    assert(@alignOf(*mut u32) == 8);
49 +
    assert @sizeOf(*mut u32) == 8;
50 +
    assert @alignOf(*mut u32) == 8;
57 51
58 -
    assert(@sizeOf(*[u16]) == 16);
59 -
    assert(@alignOf(*[u16]) == 8);
52 +
    assert @sizeOf(*[u16]) == 16;
53 +
    assert @alignOf(*[u16]) == 8;
60 54
61 -
    assert(@sizeOf(*mut [u8]) == 16);
62 -
    assert(@alignOf(*mut [u8]) == 8);
55 +
    assert @sizeOf(*mut [u8]) == 16;
56 +
    assert @alignOf(*mut [u8]) == 8;
63 57
64 -
    assert(@sizeOf(void) == 0);
65 -
    assert(@alignOf(void) == 0);
58 +
    assert @sizeOf(void) == 0;
59 +
    assert @alignOf(void) == 0;
66 60
67 61
    return 0;
68 62
}
lib/std/arch/rv64/tests/error.catch.rad +10 -16
1 1
//! returns: 0
2 2
3 -
fn assert(cond: bool) {
4 -
    if not cond {
5 -
        panic;
6 -
    }
7 -
}
8 -
9 3
union TestError { Fail }
10 4
static PTR_VALUE: i32 = 7;
11 5
12 6
@default fn main() -> u32 {
13 7
    // Catch block with early return
14 8
    let val1: u32 = catchWithReturn(true);
15 -
    assert(val1 == 42);
9 +
    assert val1 == 42;
16 10
17 11
    // Catch block with early return (success case)
18 12
    let val2: u32 = catchWithReturn(false);
19 -
    assert(val2 == 21);
13 +
    assert val2 == 21;
20 14
21 15
    // Catch block that discards error
22 16
    let mut flag: u32 = 0;
23 17
    try returnsErr() catch {};
24 18
    flag = 1;
25 -
    assert(flag == 1);
19 +
    assert flag == 1;
26 20
27 21
    // Catch block with return in function
28 22
    let val3: u32 = returnsEarly();
29 -
    assert(val3 == 42);
23 +
    assert val3 == 42;
30 24
31 25
    // try? converts to optional on error
32 26
    let ptr_ok: ?*i32 = try? returnsPtrOk();
33 27
    if let p = ptr_ok {
34 -
        assert(*p == 7);
28 +
        assert *p == 7;
35 29
    } else {
36 -
        assert(false);
30 +
        assert false;
37 31
    }
38 32
39 33
    // try? converts to nil on error
40 34
    let ptr_err: ?*i32 = try? returnsPtrErr();
41 -
    assert(ptr_err == nil);
35 +
    assert ptr_err == nil;
42 36
43 37
    // try? on success
44 38
    let opt_ok: ?u32 = try? returnsOk();
45 -
    assert(opt_ok != nil);
39 +
    assert opt_ok != nil;
46 40
    if let v = opt_ok {
47 -
        assert(v == 21);
41 +
        assert v == 21;
48 42
    }
49 43
50 44
    // try? on error
51 45
    let opt_err: ?u32 = try? returnsErr();
52 -
    assert(opt_err == nil);
46 +
    assert opt_err == nil;
53 47
54 48
    return 0;
55 49
}
56 50
57 51
fn catchWithReturn(fail: bool) -> u32 {
lib/std/arch/rv64/tests/error.try.bang.success.rad +3 -9
1 1
//! returns: 0
2 2
3 -
fn assert(cond: bool) {
4 -
    if not cond {
5 -
        panic;
6 -
    }
7 -
}
8 -
9 3
union PanicError { Boom }
10 4
11 5
fn makeOk(value: u32) -> u32 throws (PanicError) {
12 6
    return value;
13 7
}
14 8
15 9
@default fn main() -> u32 {
16 10
    let first: u32 = try! makeOk(7);
17 11
    let second: u32 = 2 * try! makeOk(11);
18 12
19 -
    assert(first == 7);
20 -
    assert(second == 22);
13 +
    assert first == 7;
14 +
    assert second == 22;
21 15
22 16
    let combined: u32 = try! makeOk(first + second);
23 -
    assert(combined == 29);
17 +
    assert combined == 29;
24 18
25 19
    return 0;
26 20
}
lib/std/arch/rv64/tests/opt.slice.npo.rad +0 -6
1 1
//! Test null pointer optimization for optional slices (?*[T]).
2 2
//! Optional slices should have the same size as slices (16 bytes),
3 3
//! using a null data pointer to represent `nil`.
4 4
5 -
fn assert(cond: bool) {
6 -
    if not cond {
7 -
        panic;
8 -
    }
9 -
}
10 -
11 5
fn checkSizes() -> u8 {
12 6
    // ?*[T] should be the same size as *[T] (16 bytes, not 24).
13 7
    if @sizeOf(?*[u8]) != 16 {
14 8
        return 1;
15 9
    }
lib/std/arch/rv64/tests/regalloc.spill.reuse.rad +1 -7
1 1
fn callPressure(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, h: u32) -> u32 {
2 2
    return a + b + c + d + e + f + g + h;
3 3
}
4 4
5 -
fn assert(cond: bool) {
6 -
    if not cond {
7 -
        panic "assert";
8 -
    }
9 -
}
10 -
11 5
fn bump(old: u32) -> u32 {
12 6
    let saved: u32 = old + 1;
13 7
14 8
    let first: u32 = callPressure(1, 2, 3, 4, 5, 6, 7, 8);
15 9
    let second: u32 = callPressure(8, 7, 6, 5, 4, 3, 2, 1);
21 15
    return saved;
22 16
}
23 17
24 18
@default fn main() -> i32 {
25 19
    let result: u32 = bump(0);
26 -
    assert(result == 1);
20 +
    assert result == 1;
27 21
28 22
    return 0;
29 23
}
lib/std/arch/rv64/tests/var.align.rad +8 -14
1 -
fn assert(cond: bool) {
2 -
    if not cond {
3 -
        panic;
4 -
    }
5 -
}
6 -
7 1
fn testDefaultAlignment() {
8 2
    let p1: u8 = 1;
9 3
    let p2: u8 = 2;
10 4
11 5
    let p3: u16 = 3;
15 9
    let p6: u32 = 6;
16 10
17 11
    // Each variable must be naturally aligned.
18 12
    let a1: u32 = (&p1) as u32;
19 13
    let a2: u32 = (&p2) as u32;
20 -
    assert((a1 % @alignOf(u8)) == 0);
21 -
    assert((a2 % @alignOf(u8)) == 0);
14 +
    assert (a1 % @alignOf(u8)) == 0;
15 +
    assert (a2 % @alignOf(u8)) == 0;
22 16
23 17
    let a3: u32 = (&p3) as u32;
24 18
    let a4: u32 = (&p4) as u32;
25 -
    assert((a3 % @alignOf(u16)) == 0);
26 -
    assert((a4 % @alignOf(u16)) == 0);
19 +
    assert (a3 % @alignOf(u16)) == 0;
20 +
    assert (a4 % @alignOf(u16)) == 0;
27 21
28 22
    let a5: u32 = (&p5) as u32;
29 23
    let a6: u32 = (&p6) as u32;
30 -
    assert((a5 % @alignOf(u32)) == 0);
31 -
    assert((a6 % @alignOf(u32)) == 0);
24 +
    assert (a5 % @alignOf(u32)) == 0;
25 +
    assert (a6 % @alignOf(u32)) == 0;
32 26
}
33 27
34 28
fn testAlignAnnotation() {
35 29
    let pad: [u8; 3] = [0; 3];
36 30
    let plain: u32 = 1;
37 31
    let custom: u32 align(16) = 2;
38 32
39 33
    let plainAddr: u32 = (&plain) as u32;
40 34
    let customAddr: u32 = (&custom) as u32;
41 35
42 -
    assert((customAddr % 16) == 0);
43 -
    assert((plainAddr % @alignOf(u32)) == 0);
36 +
    assert (customAddr % 16) == 0;
37 +
    assert (plainAddr % @alignOf(u32)) == 0;
44 38
}
45 39
46 40
@default fn main() -> i32 {
47 41
    testDefaultAlignment();
48 42
    testAlignAnnotation();
lib/std/debug.rad +5 -3
1 1
// TODO: Re-export `core::debug::*`.
2 2
use super::intrinsics;
3 3
4 -
pub fn assert(cond: bool) {
4 +
/// Check that a condition is true, otherwise trigger a breakpoint.
5 +
pub fn check(cond: bool) {
5 6
    if not cond {
6 7
        intrinsics::ebreak();
7 8
    }
8 9
}
9 10
10 -
pub fn assertMsg(cond: bool, msg: *[u8]) {
11 -
    assert(cond);
11 +
/// Check that a condition is true with a message, otherwise trigger a breakpoint.
12 +
pub fn checkMsg(cond: bool, msg: *[u8]) {
13 +
    check(cond);
12 14
}
lib/std/fmt.rad +4 -4
13 13
/// Maximum string length for a formatted bool (eg. "false").
14 14
pub const BOOL_STR_LEN: u32 = 5;
15 15
16 16
/// Format a u32 by writing it to the provided buffer.
17 17
pub fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
18 -
    debug::assert(buffer.len >= U32_STR_LEN);
18 +
    debug::check(buffer.len >= U32_STR_LEN);
19 19
20 20
    let mut x: u32 = val;
21 21
    let mut i: u32 = buffer.len;
22 22
23 23
    // Handle the zero case separately to ensure a single '0' is written.
37 37
    return &buffer[i..];
38 38
}
39 39
40 40
/// Format a i32 by writing it to the provided buffer.
41 41
pub fn formatI32(val: i32, buffer: *mut [u8]) -> *[u8] {
42 -
    debug::assert(buffer.len >= I32_STR_LEN);
42 +
    debug::check(buffer.len >= I32_STR_LEN);
43 43
44 44
    let neg: bool = val < 0;
45 45
    let mut x: u32 = -val as u32 if neg else val as u32;
46 46
    let mut i: u32 = buffer.len;
47 47
    // Handle the zero case separately to ensure a single '0' is written.
64 64
    return &buffer[i..];
65 65
}
66 66
67 67
/// Format a u64 by writing it to the provided buffer.
68 68
pub fn formatU64(val: u64, buffer: *mut [u8]) -> *[u8] {
69 -
    debug::assert(buffer.len >= U64_STR_LEN);
69 +
    debug::check(buffer.len >= U64_STR_LEN);
70 70
71 71
    let mut x: u64 = val;
72 72
    let mut i: u32 = buffer.len;
73 73
74 74
    if x == 0 {
84 84
    return &buffer[i..];
85 85
}
86 86
87 87
/// Format a i64 by writing it to the provided buffer.
88 88
pub fn formatI64(val: i64, buffer: *mut [u8]) -> *[u8] {
89 -
    debug::assert(buffer.len >= I64_STR_LEN);
89 +
    debug::check(buffer.len >= I64_STR_LEN);
90 90
91 91
    let neg: bool = val < 0;
92 92
    let mut x: u64 = -val as u64 if neg else val as u64;
93 93
    let mut i: u32 = buffer.len;
94 94
    if x == 0 {
lib/std/lang/alloc.rad +2 -2
35 35
///
36 36
/// Returns an opaque pointer to the allocated memory. Throws `AllocError` if
37 37
/// the arena is exhausted. The caller is responsible for casting to the
38 38
/// appropriate type and initializing the memory.
39 39
pub fn alloc(arena: *mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
40 -
    debug::assert(alignment > 0);
41 -
    debug::assert(size > 0);
40 +
    debug::check(alignment > 0);
41 +
    debug::check(size > 0);
42 42
43 43
    let aligned = mem::alignUp(arena.offset, alignment);
44 44
    let newOffset = aligned + size;
45 45
46 46
    if newOffset > arena.data.len as u32 {
lib/std/lang/ast.rad +8 -1
101 101
    return self.list.len;
102 102
}
103 103
104 104
/// Fetch the attribute node at the specified index.
105 105
pub fn attributesGet(self: *Attributes, index: u32) -> *Node {
106 -
    debug::assertMsg(index < self.list.len, "attributesGet: invalid index");
106 +
    debug::checkMsg(index < self.list.len, "attributesGet: invalid index");
107 107
    return self.list.list[index];
108 108
}
109 109
110 110
/// Check if an attributes list contains an attribute.
111 111
pub fn attributesContains(self: *Attributes, attr: Attribute) -> bool {
730 730
    /// Panic statement.
731 731
    Panic {
732 732
        /// Optional panic message expression.
733 733
        message: ?*Node,
734 734
    },
735 +
    /// Assert statement.
736 +
    Assert {
737 +
        /// Condition expression that must be true.
738 +
        condition: *Node,
739 +
        /// Optional assertion failure message.
740 +
        message: ?*Node,
741 +
    },
735 742
    /// Conditional statement.
736 743
    If(If),
737 744
    /// Conditional expression.
738 745
    CondExpr(CondExpr),
739 746
    /// `if let` conditional binding.
lib/std/lang/ast/printer.rad +2 -0
357 357
            return sexpr::list(a, "return", &[toExprOrNull(a, value)]),
358 358
        case super::NodeValue::Throw { expr } =>
359 359
            return sexpr::list(a, "throw", &[toExpr(a, expr)]),
360 360
        case super::NodeValue::Panic { message } =>
361 361
            return sexpr::list(a, "panic", &[toExprOrNull(a, message)]),
362 +
        case super::NodeValue::Assert { condition, message } =>
363 +
            return sexpr::list(a, "assert", &[toExpr(a, condition), toExprOrNull(a, message)]),
362 364
        case super::NodeValue::If(c) =>
363 365
            return sexpr::block(a, "if", &[toExpr(a, c.condition)],
364 366
                &[toExpr(a, c.thenBranch), toExprOrNull(a, c.elseBranch)]),
365 367
        case super::NodeValue::IfLet(c) =>
366 368
            return sexpr::block(a, "if-let", &[
lib/std/lang/lower.rad +34 -13
1017 1017
    } else {
1018 1018
        func.returnType = ilType(self, *fnType.returnType);
1019 1019
    }
1020 1020
    let body = decl.body else {
1021 1021
        // Extern functions have no body.
1022 -
        debug::assert(isExtern);
1022 +
        debug::check(isExtern);
1023 1023
        return func;
1024 1024
    };
1025 1025
    func.blocks = try lowerFnBody(&mut fnLow, body);
1026 1026
1027 1027
    return func;
1942 1942
    switchToBlock(self, target);
1943 1943
}
1944 1944
1945 1945
/// Emit a conditional branch based on `cond`.
1946 1946
fn emitBr(self: *mut FnLowerer, cond: il::Reg, thenBlock: BlockId, elseBlock: BlockId) throws (LowerError) {
1947 -
    debug::assert(thenBlock != elseBlock);
1947 +
    debug::check(thenBlock != elseBlock);
1948 1948
    emit(self, il::Instr::Br {
1949 1949
        op: il::CmpOp::Ne,
1950 1950
        typ: il::Type::W32,
1951 1951
        a: il::Val::Reg(cond),
1952 1952
        b: il::Val::Imm(0),
1967 1967
    a: il::Val,
1968 1968
    b: il::Val,
1969 1969
    thenBlock: BlockId,
1970 1970
    elseBlock: BlockId
1971 1971
) throws (LowerError) {
1972 -
    debug::assert(thenBlock != elseBlock);
1972 +
    debug::check(thenBlock != elseBlock);
1973 1973
    emit(self, il::Instr::Br {
1974 1974
        op, typ, a, b,
1975 1975
        thenTarget: thenBlock.n, thenArgs: &mut [],
1976 1976
        elseTarget: elseBlock.n, elseArgs: &mut [],
1977 1977
    });
2210 2210
2211 2211
                try emitBr(self, eqReg, matchBlock, fallthrough);
2212 2212
            }
2213 2213
        }
2214 2214
        case MatchSubjectKind::Union(unionInfo) => {
2215 -
            debug::assert(not isNil);
2215 +
            debug::check(not isNil);
2216 2216
2217 2217
            let case resolver::NodeExtra::UnionVariant { tag: variantTag, .. } =
2218 2218
                resolver::nodeData(self.low.resolver, pattern).extra
2219 2219
            else {
2220 2220
                throw LowerError::ExpectedVariant;
2238 2238
2239 2239
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2240 2240
            }
2241 2241
        }
2242 2242
        else => { // Scalar comparison.
2243 -
            debug::assert(not isNil);
2243 +
            debug::check(not isNil);
2244 2244
            let pattVal = try lowerExpr(self, pattern);
2245 2245
            try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough);
2246 2246
        }
2247 2247
    }
2248 2248
}
2255 2255
    subject: *MatchSubject,
2256 2256
    patterns: ast::NodeList,
2257 2257
    matchBlock: BlockId,
2258 2258
    fallthrough: BlockId
2259 2259
) throws (LowerError) {
2260 -
    debug::assert(patterns.len > 0);
2260 +
    debug::check(patterns.len > 0);
2261 2261
2262 2262
    for i in 0..(patterns.len - 1) {
2263 2263
        let pattern = patterns.list[i];
2264 2264
        let nextArm = try createBlock(self, "arm");
2265 2265
        try emitPatternMatch(self, subject, pattern, matchBlock, nextArm);
2565 2565
/// Define (write) a variable. Record the SSA value of a variable in the
2566 2566
/// current block. Called when a variable is assigned or initialized (`let`
2567 2567
/// bindings, assignments, loop updates). When [`useVar`] is later called,
2568 2568
/// it will retrieve this value.
2569 2569
fn defVar(self: *mut FnLowerer, v: Var, val: il::Val) {
2570 -
    debug::assert(v.id < self.varsLen);
2570 +
    debug::check(v.id < self.varsLen);
2571 2571
    getBlockMut(self, currentBlock(self)).vars[v.id] = val;
2572 2572
}
2573 2573
2574 2574
/// Use (read) the current value of a variable in the current block.
2575 2575
/// May insert block parameters if the value must come from predecessors.
2582 2582
/// Given a variable and a block where it's used, this function finds the
2583 2583
/// correct [`il::Val`] that holds the variable's value at that program point.
2584 2584
/// When control flow merges from multiple predecessors with different
2585 2585
/// definitions, it creates a block parameter to unify them.
2586 2586
fn useVarInBlock(self: *mut FnLowerer, block: BlockId, v: Var) -> il::Val throws (LowerError) {
2587 -
    debug::assert(v.id < self.varsLen);
2587 +
    debug::check(v.id < self.varsLen);
2588 2588
2589 2589
    let blk = getBlockMut(self, block);
2590 2590
    if let val = blk.vars[v.id] {
2591 2591
        return val;
2592 2592
    }
2649 2649
    self.varsLen = savedVarsLen;
2650 2650
}
2651 2651
2652 2652
/// Get the metadata for a variable.
2653 2653
fn getVar(self: *FnLowerer, v: Var) -> *VarData {
2654 -
    debug::assert(v.id < self.varsLen);
2654 +
    debug::check(v.id < self.varsLen);
2655 2655
    return &self.vars[v.id];
2656 2656
}
2657 2657
2658 2658
/// Create a block parameter to merge a variable's value from multiple
2659 2659
/// control flow paths.
2869 2869
    let totalLen = fnType.paramTypesLen + offset;
2870 2870
2871 2871
    if totalLen == 0 {
2872 2872
        return &[];
2873 2873
    }
2874 -
    debug::assert(fnType.paramTypesLen <= resolver::MAX_FN_PARAMS);
2874 +
    debug::check(fnType.paramTypesLen <= resolver::MAX_FN_PARAMS);
2875 2875
2876 2876
    let params = try! alloc::allocSlice(
2877 2877
        self.low.arena, @sizeOf(il::Param), @alignOf(il::Param), totalLen
2878 2878
    ) as *mut [il::Param];
2879 2879
3297 3297
///       jmp else#0;                     // guard failed, fallthrough to `else`
3298 3298
///   else#0:
3299 3299
///       ret 0;                          // `else` body
3300 3300
///
3301 3301
fn lowerMatch(self: *mut FnLowerer, node: *ast::Node, m: ast::Match) throws (LowerError) {
3302 -
    debug::assert(m.prongs.len > 0);
3302 +
    debug::check(m.prongs.len > 0);
3303 3303
3304 3304
    let prongs = &m.prongs;
3305 3305
    // Lower the subject expression once; reused across all arms.
3306 3306
    let subject = try lowerMatchSubject(self, m.subject);
3307 3307
    // Merge block created lazily if any arm needs it (i.e., doesn't diverge).
3589 3589
            let _ = try lowerExpr(self, expr);
3590 3590
        }
3591 3591
        case ast::NodeValue::Panic { .. } => {
3592 3592
            emit(self, il::Instr::Unreachable);
3593 3593
        }
3594 +
        case ast::NodeValue::Assert { condition, .. } => {
3595 +
            // Lower `assert <cond>` as: `if not cond { unreachable; }`.
3596 +
            let thenBlock = try createBlock(self, "assert.fail");
3597 +
            let endBlock = try createBlock(self, "assert.ok");
3598 +
3599 +
            // Branch: if condition is `true`, go to `endBlock`; if `false`, go to `thenBlock`.
3600 +
            try emitCondBranch(self, condition, endBlock, thenBlock);
3601 +
            try sealBlock(self, thenBlock);
3602 +
3603 +
            // Emit `unreachable` in the failure block.
3604 +
            switchToBlock(self, thenBlock);
3605 +
            emit(self, il::Instr::Unreachable);
3606 +
3607 +
            // Continue after the assert.
3608 +
            try switchToAndSeal(self, endBlock);
3609 +
        }
3594 3610
        else => {
3595 3611
            // Treat as expression statement, discard result.
3596 3612
            let _ = try lowerExpr(self, node);
3597 3613
        }
3598 3614
    }
4155 4171
    let tagBlock = try createBlock(self, "eq#tag");
4156 4172
4157 4173
    // Compare tags: if they differ, jump to merge with `false`; otherwise check payloads.
4158 4174
    let falseArgs = try allocVal(self, il::Val::Imm(0));
4159 4175
4160 -
    debug::assert(tagBlock != mergeBlock);
4176 +
    debug::check(tagBlock != mergeBlock);
4161 4177
4162 4178
    // TODO: Use the helper once the compiler supports more than eight function params.
4163 4179
    emit(self, il::Instr::Br {
4164 4180
        op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB,
4165 4181
        thenTarget: tagBlock.n, thenArgs: &mut [],
5223 5239
    try emitRetVal(self, val);
5224 5240
}
5225 5241
5226 5242
/// Lower a throw statement.
5227 5243
fn lowerThrowStmt(self: *mut FnLowerer, expr: *ast::Node) throws (LowerError) {
5228 -
    debug::assert(self.fnType.throwListLen > 0);
5244 +
    debug::check(self.fnType.throwListLen > 0);
5229 5245
5230 5246
    let errType = resolver::typeFor(self.low.resolver, expr) else {
5231 5247
        throw LowerError::MissingType(expr);
5232 5248
    };
5233 5249
    let tag = getOrAssignErrorTag(self.low, errType) as i64;
6373 6389
            // Panic in expression context (e.g. match arm). Emit unreachable
6374 6390
            // and return a dummy value since control won't continue.
6375 6391
            emit(self, il::Instr::Unreachable);
6376 6392
            val = il::Val::Undef;
6377 6393
        }
6394 +
        case ast::NodeValue::Assert { .. } => {
6395 +
            // Assert in expression context. Lower as statement, return `void`.
6396 +
            try lowerNode(self, node);
6397 +
            val = il::Val::Undef;
6398 +
        }
6378 6399
        case ast::NodeValue::Block(_) => {
6379 6400
            try lowerBlock(self, node);
6380 6401
            val = il::Val::Undef;
6381 6402
        }
6382 6403
        case ast::NodeValue::ExprStmt(expr) => {
lib/std/lang/lower/tests/assert.basic.rad added +5 -0
1 +
/// Test lowering of assert statement.
2 +
fn test(x: bool) -> i32 {
3 +
    assert x;
4 +
    return 0;
5 +
}
lib/std/lang/lower/tests/assert.basic.ril added +8 -0
1 +
fn w32 $test(w8 %0) {
2 +
  @entry0
3 +
    br.ne w32 %0 0 @assert.ok2 @assert.fail1;
4 +
  @assert.fail1
5 +
    unreachable;
6 +
  @assert.ok2
7 +
    ret 0;
8 +
}
lib/std/lang/lower/tests/assert.message.rad added +5 -0
1 +
/// Test lowering of assert with message.
2 +
fn test(x: i32) -> i32 {
3 +
    assert x > 0, "x must be positive";
4 +
    return x;
5 +
}
lib/std/lang/lower/tests/assert.message.ril added +8 -0
1 +
fn w32 $test(w32 %0) {
2 +
  @entry0
3 +
    br.slt w32 0 %0 @assert.ok2 @assert.fail1;
4 +
  @assert.fail1
5 +
    unreachable;
6 +
  @assert.ok2
7 +
    ret %0;
8 +
}
lib/std/lang/module.rad +7 -7
149 149
    graph: *mut ModuleGraph,
150 150
    parentId: u16,
151 151
    name: *[u8],
152 152
    filePath: *[u8]
153 153
) -> u16 throws (ModuleError) {
154 -
    debug::assertMsg(name.len > 0, "registerChild: name must not be empty");
155 -
    debug::assertMsg(filePath.len > 0, "registerChild: file path must not be empty");
154 +
    debug::checkMsg(name.len > 0, "registerChild: name must not be empty");
155 +
    debug::checkMsg(filePath.len > 0, "registerChild: file path must not be empty");
156 156
157 157
    let parent = getMut(graph, parentId)
158 158
        else throw ModuleError::NotFound(parentId);
159 159
160 160
    // If the child already exists under this parent, return it.
193 193
    return &mut graph.entries[id as u32];
194 194
}
195 195
196 196
/// Return the identifier of the child stored at `index`.
197 197
pub fn childAt(m: *ModuleEntry, index: u32) -> u16 {
198 -
    debug::assertMsg(index < m.childrenLen, "childAt: index must be valid");
198 +
    debug::checkMsg(index < m.childrenLen, "childAt: index must be valid");
199 199
    return m.children[index];
200 200
}
201 201
202 202
/// Public accessor for a module's directory prefix.
203 203
pub fn moduleDir(m: *ModuleEntry) -> *[u8] {
204 204
    return &m.filePath[..m.dirLen];
205 205
}
206 206
207 207
/// Public accessor to the logical module path segments.
208 208
pub fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
209 -
    debug::assertMsg(m.pathDepth > 0, "moduleQualifiedPath: path must not be empty");
209 +
    debug::checkMsg(m.pathDepth > 0, "moduleQualifiedPath: path must not be empty");
210 210
    return &m.path[..m.pathDepth];
211 211
}
212 212
213 213
/// Update the recorded lifecycle state for `id`.
214 214
pub fn setState(graph: *mut ModuleGraph, id: u16, state: ModuleState) throws (ModuleError) {
237 237
    m.source = source;
238 238
}
239 239
240 240
/// Look up a child module by name under the given parent.
241 241
pub fn findChild(graph: *ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
242 -
    debug::assertMsg(isValidId(graph, parentId), "findChild: parent identifier is valid");
242 +
    debug::checkMsg(isValidId(graph, parentId), "findChild: parent identifier is valid");
243 243
244 244
    let parent = &graph.entries[parentId as u32];
245 245
    for i in 0..parent.childrenLen {
246 246
        let childId = parent.children[i];
247 247
        let child = &graph.entries[childId as u32];
261 261
262 262
    // Split on '/' to extract all but the last component.
263 263
    for i in 0..filePath.len {
264 264
        if filePath[i] == PATH_SEP {
265 265
            if i > last {
266 -
                debug::assertMsg(count < components.len, "parsePath: output slice is large enough");
266 +
                debug::checkMsg(count < components.len, "parsePath: output slice is large enough");
267 267
                components[count] = &filePath[last..i];
268 268
                count += 1;
269 269
            }
270 270
            last = i + 1;
271 271
        }
273 273
    if last >= filePath.len {
274 274
        return nil; // Path ends with separator or is empty.
275 275
    }
276 276
277 277
    // Handle the last component by removing extension.
278 -
    debug::assertMsg(count < components.len, "parsePath: output slice is large enough");
278 +
    debug::checkMsg(count < components.len, "parsePath: output slice is large enough");
279 279
    if let name = trimExtension(&filePath[last..]) {
280 280
        components[count] = name;
281 281
    } else {
282 282
        return nil;
283 283
    };
lib/std/lang/parser.rad +31 -2
162 162
    return node(p, ast::NodeValue::Bool(value));
163 163
}
164 164
165 165
/// Convert a single ASCII digit into its numeric value for the given radix.
166 166
pub fn digitFromAscii(ch: u8, radix: u32) -> ?u32 {
167 -
    debug::assert(radix >= 2 and radix <= 36);
167 +
    debug::check(radix >= 2 and radix <= 36);
168 168
169 169
    // Default to an out-of-range value so non-digits fall through to `nil`.
170 170
    let mut value: u32 = 36;
171 171
172 172
    if ch >= '0' and ch <= '9' {
987 987
            return try parseThrow(p);
988 988
        }
989 989
        case scanner::TokenKind::Panic => {
990 990
            return try parsePanic(p);
991 991
        }
992 +
        case scanner::TokenKind::Assert => {
993 +
            return try parseAssert(p);
994 +
        }
992 995
        case scanner::TokenKind::Break => {
993 996
            advance(p);
994 997
            return node(p, ast::NodeValue::Break);
995 998
        }
996 999
        case scanner::TokenKind::Continue => {
1130 1133
    }
1131 1134
}
1132 1135
1133 1136
/// Report a parser error.
1134 1137
fn reportError(p: *mut Parser, token: scanner::Token, message: *[u8]) {
1135 -
    debug::assert(message.len > 0);
1138 +
    debug::check(message.len > 0);
1136 1139
1137 1140
    // Ignore errors once the error list is full.
1138 1141
    if p.errors.count < p.errors.list.len {
1139 1142
        p.errors.list[p.errors.count] = Error { message, token };
1140 1143
        p.errors.count += 1;
1351 1354
    // `panic` or `panic "message"`.
1352 1355
    let message: ?*ast::Node = try? parseExpr(p);
1353 1356
    return node(p, ast::NodeValue::Panic { message });
1354 1357
}
1355 1358
1359 +
/// Parse an `assert` statement.
1360 +
///
1361 +
/// Forms:
1362 +
///   `assert <expr>`
1363 +
///   `assert <expr>, "message"`
1364 +
///   `assert { <expr> }, "message"`
1365 +
fn parseAssert(p: *mut Parser) -> *ast::Node
1366 +
    throws (ParseError)
1367 +
{
1368 +
    try expect(p, scanner::TokenKind::Assert, "expected `assert`");
1369 +
1370 +
    // `assert { expr }` block form or `assert <expr>`.
1371 +
    let mut condition: *ast::Node = undefined;
1372 +
    if consume(p, scanner::TokenKind::LBrace) {
1373 +
        condition = try parseExpr(p);
1374 +
        try expect(p, scanner::TokenKind::RBrace, "expected closing `}` after expression");
1375 +
    } else {
1376 +
        condition = try parseExpr(p);
1377 +
    }
1378 +
    let mut message: ?*ast::Node = nil;
1379 +
    if consume(p, scanner::TokenKind::Comma) {
1380 +
        message = try parseExpr(p);
1381 +
    }
1382 +
    return node(p, ast::NodeValue::Assert { condition, message });
1383 +
}
1384 +
1356 1385
/// Parse a `break` statement.
1357 1386
fn parseBreak(p: *mut Parser) -> *ast::Node
1358 1387
    throws (ParseError)
1359 1388
{
1360 1389
    try expect(p, scanner::TokenKind::Break, "expected `break`");
lib/std/lang/resolver.rad +19 -11
2524 2524
                item: allocType(self, Type::U8),
2525 2525
                mutable: false
2526 2526
            });
2527 2527
            return try setNodeType(self, node, Type::Never);
2528 2528
        },
2529 +
        case ast::NodeValue::Assert { condition, message } => {
2530 +
            try visit(self, condition, Type::Bool);
2531 +
            try visitOptional(self, message, Type::Slice { // TODO: Have easy access to string type.
2532 +
                item: allocType(self, Type::U8),
2533 +
                mutable: false
2534 +
            });
2535 +
            return try setNodeType(self, node, Type::Void);
2536 +
        },
2529 2537
        case ast::NodeValue::BinOp(binop) => return try resolveBinOp(self, node, binop),
2530 2538
        case ast::NodeValue::UnOp(unop) => return try resolveUnOp(self, node, unop),
2531 2539
        case ast::NodeValue::ExprStmt(expr) => {
2532 2540
            // Pass `Void` as expected type to indicate value is discarded.
2533 2541
            let exprTy = try visit(self, expr, Type::Void);
2713 2721
    if let a = decl.alignment {
2714 2722
        let case ast::NodeValue::Align { value } = a.value
2715 2723
            else panic "resolveLet: expected Align node";
2716 2724
        alignment = try checkSizeInt(self, value);
2717 2725
    }
2718 -
    debug::assert(bindingTy != Type::Unknown);
2726 +
    debug::check(bindingTy != Type::Unknown);
2719 2727
2720 2728
    // Alignment must be zero or a power of two.
2721 2729
    if alignment != 0 and (alignment & (alignment - 1)) != 0 {
2722 2730
        throw emitError(self, decl.value, ErrorKind::InvalidAlignmentValue(alignment));
2723 2731
    }
2874 2882
2875 2883
    // Validate it fits within u32 range.
2876 2884
    if not validateConstIntRange(value, Type::U32) {
2877 2885
        throw emitError(self, node, ErrorKind::NumericLiteralOverflow);
2878 2886
    }
2879 -
    debug::assert(not int.negative);
2887 +
    debug::check(not int.negative);
2880 2888
    try setNodeType(self, node, Type::U32);
2881 2889
2882 2890
    return int.magnitude as u32;
2883 2891
}
2884 2892
4175 4183
            actual: call.args.len,
4176 4184
        }));
4177 4185
    }
4178 4186
    // TODO: Check what happens when we exceed `MAX_FN_PARAMS`.
4179 4187
    for i in 0..call.args.len {
4180 -
        debug::assert(i < MAX_FN_PARAMS);
4188 +
        debug::check(i < MAX_FN_PARAMS);
4181 4189
4182 4190
        let argNode = call.args.list[i];
4183 4191
        let expectedTy = *info.paramTypes[i];
4184 4192
        try checkAssignable(self, argNode, expectedTy);
4185 4193
    }
4503 4511
        expectedTy = *ary.item;
4504 4512
    };
4505 4513
    for i in 0..length {
4506 4514
        let itemNode = items.list[i];
4507 4515
        let itemTy = try visit(self, itemNode, expectedTy);
4508 -
        debug::assert(itemTy != Type::Unknown);
4516 +
        debug::check(itemTy != Type::Unknown);
4509 4517
4510 4518
        // Set the expected type to the first type we encounter.
4511 4519
        if expectedTy == Type::Unknown {
4512 4520
            expectedTy = itemTy;
4513 4521
        } else {
4889 4897
    throws (ResolveError)
4890 4898
{
4891 4899
    let targetTy = try infer(self, expr.type);
4892 4900
    let sourceTy = try visit(self, expr.value, targetTy);
4893 4901
4894 -
    debug::assert(sourceTy != Type::Unknown);
4895 -
    debug::assert(targetTy != Type::Unknown);
4902 +
    debug::check(sourceTy != Type::Unknown);
4903 +
    debug::check(targetTy != Type::Unknown);
4896 4904
4897 4905
    if isValidCast(sourceTy, targetTy) {
4898 4906
        return try setNodeType(self, node, targetTy);
4899 4907
    }
4900 4908
    throw emitError(self, node, ErrorKind::InvalidAsCast(InvalidAsCast {
5367 5375
                returnType: allocType(self, Type::Void),
5368 5376
                throwList: undefined,
5369 5377
                throwListLen: 0,
5370 5378
                localCount: 0,
5371 5379
            };
5372 -
            debug::assert(t.params.len <= fnType.paramTypes.len);
5373 -
            debug::assert(t.throwList.len <= fnType.throwList.len);
5380 +
            debug::check(t.params.len <= fnType.paramTypes.len);
5381 +
            debug::check(t.throwList.len <= fnType.throwList.len);
5374 5382
5375 5383
            for i in 0..t.params.len {
5376 5384
                let paramTy = try infer(self, t.params.list[i]);
5377 5385
5378 5386
                fnType.paramTypes[fnType.paramTypesLen] = allocType(self, paramTy);
5623 5631
    let case ast::NodeValue::Block(block) = node.value
5624 5632
        else panic "resolvePackage: expected block for module root";
5625 5633
5626 5634
    // Module graph analysis phase: bind all module name symbols and scopes.
5627 5635
    try resolveModuleGraph(self, &block) catch {
5628 -
        debug::assertMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
5636 +
        debug::checkMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
5629 5637
        return Diagnostics { errors: self.errors };
5630 5638
    };
5631 5639
5632 5640
    // Declaration phase: bind all names and analyze top-level declarations.
5633 5641
    try resolveModuleDecls(self, &block) catch {
5634 -
        debug::assertMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
5642 +
        debug::checkMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
5635 5643
    };
5636 5644
    if self.errors.listLen > 0 {
5637 5645
        return Diagnostics { errors: self.errors };
5638 5646
    }
5639 5647
5640 5648
    // Definition phase: analyze function bodies and sub-module definitions.
5641 5649
    try resolveModuleDefs(self, &block) catch {
5642 -
        debug::assertMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
5650 +
        debug::checkMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
5643 5651
    };
5644 5652
    try setNodeType(self, node, Type::Void);
5645 5653
5646 5654
    return Diagnostics { errors: self.errors };
5647 5655
}
lib/std/lang/scanner.rad +4 -3
89 89
90 90
    // Control flow tokens.
91 91
    If, Else, Return, Break,
92 92
    Continue, While, For, In,
93 93
    Loop, Match, Case, Try, Catch,
94 -
    Throw, Throws, Panic,
94 +
    Throw, Throws, Panic, Assert,
95 95
96 96
    // Variable binding tokens.
97 97
    Let, Mut, Const, Align,
98 98
99 99
    // Module-related tokens.
185 185
        case TokenKind::Try => return "Try",
186 186
        case TokenKind::Catch => return "Catch",
187 187
        case TokenKind::Throw => return "Throw",
188 188
        case TokenKind::Throws => return "Throws",
189 189
        case TokenKind::Panic => return "Panic",
190 +
        case TokenKind::Assert => return "Assert",
190 191
        case TokenKind::Let => return "Let",
191 192
        case TokenKind::Mut => return "Mut",
192 193
        case TokenKind::Const => return "Const",
193 194
        case TokenKind::Align => return "Align",
194 195
        case TokenKind::Mod => return "Mod",
222 223
    /// Corresponding token.
223 224
    tok: TokenKind,
224 225
}
225 226
226 227
/// Sorted keyword table for binary search.
227 -
const KEYWORDS: [Keyword; 49] = [
228 +
const KEYWORDS: [Keyword; 50] = [
228 229
    { name: "align", tok: TokenKind::Align },
229 230
    { name: "and", tok: TokenKind::And },
230 231
    { name: "as", tok: TokenKind::As },
232 +
    { name: "assert", tok: TokenKind::Assert },
231 233
    { name: "bool", tok: TokenKind::Bool },
232 234
    { name: "break", tok: TokenKind::Break },
233 235
    { name: "case", tok: TokenKind::Case },
234 236
    { name: "catch", tok: TokenKind::Catch },
235 237
    { name: "const", tok: TokenKind::Const },
274 276
    { name: "use", tok: TokenKind::Use },
275 277
    { name: "void", tok: TokenKind::Void },
276 278
    { name: "while", tok: TokenKind::While },
277 279
];
278 280
279 -
280 281
/// Lexical scanner state for tokenizing Radiance source code.
281 282
///
282 283
/// Maintains position information and source buffer reference.
283 284
pub record Scanner {
284 285
    /// File path.
lib/std/vec.rad +4 -4
24 24
///
25 25
/// * `arena` is a pointer to static array backing storage.
26 26
/// * `stride` is the size of each element.
27 27
/// * `alignment` is the required alignment for elements.
28 28
pub fn new(arena: *mut [u8], stride: u32, alignment: u32) -> RawVec {
29 -
    debug::assert(stride > 0);
30 -
    debug::assert(alignment > 0);
31 -
    debug::assert((arena.ptr as u32) % alignment == 0);
32 -
    debug::assert((arena.len % stride) == 0);
29 +
    debug::check(stride > 0);
30 +
    debug::check(alignment > 0);
31 +
    debug::check((arena.ptr as u32) % alignment == 0);
32 +
    debug::check((arena.len % stride) == 0);
33 33
34 34
    return RawVec { data: arena, len: 0, stride, alignment };
35 35
}
36 36
37 37
/// Get the current number of elements in the vector.
vim/radiance.vim +1 -1
16 16
17 17
" Keywords
18 18
syntax keyword radianceKeyword mod fn return if else while true false and or not case align static
19 19
syntax keyword radianceKeyword pub extern break continue use loop in for match nil undefined
20 20
syntax keyword radianceKeyword let mut as register device const log record union
21 -
syntax keyword radianceKeyword throws throw try catch panic super
21 +
syntax keyword radianceKeyword throws throw try catch panic assert super
22 22
syntax keyword radianceType i8 i16 i32 i64 u8 u16 u32 u64 f32 void bool bit opaque
23 23
24 24
" Double-quoted strings
25 25
syntax region radianceString start=/"/ skip=/\\"/ end=/"/ contains=radianceEscape
26 26
" Characters