Add tests for traits feature

4fd4c947c18fd1be2f9fdfe2df2ccbd057f168bc83f7881b58874416eb7f3c83
Alexis Sellier committed ago 1 parent a521693e
lib/std/arch/rv64/tests/trait.control.flow.rad added +63 -0
1 +
//! Test trait dispatch inside control flow.
2 +
//!
3 +
//! Exercises: trait method calls inside loops and conditionals,
4 +
//! verifying that vtable dispatch works correctly when interleaved
5 +
//! with branching and iteration.
6 +
7 +
record Counter {
8 +
    value: i32,
9 +
}
10 +
11 +
trait Stepper {
12 +
    fn (*mut Stepper) step() -> i32;
13 +
    fn (*Stepper) current() -> i32;
14 +
}
15 +
16 +
instance Stepper for Counter {
17 +
    fn (c: *mut Counter) step() -> i32 {
18 +
        c.value = c.value + 1;
19 +
        return c.value;
20 +
    }
21 +
22 +
    fn (c: *Counter) current() -> i32 {
23 +
        return c.value;
24 +
    }
25 +
}
26 +
27 +
@default fn main() -> i32 {
28 +
    let mut c = Counter { value: 0 };
29 +
    let s: *mut opaque Stepper = &mut c;
30 +
31 +
    // Dispatch in a while loop.
32 +
    let mut i: i32 = 0;
33 +
    while i < 5 {
34 +
        s.step();
35 +
        i = i + 1;
36 +
    }
37 +
    if s.current() != 5 {
38 +
        return 1;
39 +
    }
40 +
41 +
    // Dispatch in a conditional.
42 +
    if s.current() > 3 {
43 +
        s.step();
44 +
    }
45 +
    if s.current() != 6 {
46 +
        return 2;
47 +
    }
48 +
49 +
    // Dispatch result used as loop condition.
50 +
    while s.current() < 10 {
51 +
        s.step();
52 +
    }
53 +
    if s.current() != 10 {
54 +
        return 3;
55 +
    }
56 +
57 +
    // Dispatch result used in conditional expression.
58 +
    let v = s.current();
59 +
    if v != 10 {
60 +
        return 4;
61 +
    }
62 +
    return 0;
63 +
}
lib/std/arch/rv64/tests/trait.fn.param.rad added +86 -0
1 +
//! Test passing trait objects as function parameters.
2 +
//!
3 +
//! Exercises: trait objects passed to non-method functions, including
4 +
//! both mutable and immutable trait object pointers as arguments.
5 +
6 +
record Circle {
7 +
    radius: i32,
8 +
}
9 +
10 +
record Square {
11 +
    side: i32,
12 +
}
13 +
14 +
trait Shape {
15 +
    fn (*Shape) area() -> i32;
16 +
}
17 +
18 +
trait Scalable {
19 +
    fn (*mut Scalable) scale(factor: i32);
20 +
}
21 +
22 +
instance Shape for Circle {
23 +
    fn (c: *Circle) area() -> i32 {
24 +
        return c.radius * c.radius * 3;
25 +
    }
26 +
}
27 +
28 +
instance Shape for Square {
29 +
    fn (s: *Square) area() -> i32 {
30 +
        return s.side * s.side;
31 +
    }
32 +
}
33 +
34 +
instance Scalable for Circle {
35 +
    fn (c: *mut Circle) scale(factor: i32) {
36 +
        c.radius = c.radius * factor;
37 +
    }
38 +
}
39 +
40 +
/// Accept an immutable trait object parameter.
41 +
fn getArea(s: *opaque Shape) -> i32 {
42 +
    return s.area();
43 +
}
44 +
45 +
/// Accept a mutable trait object parameter.
46 +
fn doubleSize(s: *mut opaque Scalable) {
47 +
    s.scale(2);
48 +
}
49 +
50 +
/// Accept two trait object parameters.
51 +
fn totalArea(a: *opaque Shape, b: *opaque Shape) -> i32 {
52 +
    return a.area() + b.area();
53 +
}
54 +
55 +
@default fn main() -> i32 {
56 +
    let c = Circle { radius: 5 };
57 +
    let s = Square { side: 4 };
58 +
59 +
    // Pass immutable trait objects to function.
60 +
    let cs: *opaque Shape = &c;
61 +
    let ca = getArea(cs);
62 +
    if ca != 75 {
63 +
        return 1;
64 +
    }
65 +
66 +
    let ss: *opaque Shape = &s;
67 +
    let sa = getArea(ss);
68 +
    if sa != 16 {
69 +
        return 2;
70 +
    }
71 +
72 +
    // Pass two different trait objects to same function.
73 +
    let total = totalArea(cs, ss);
74 +
    if total != 91 {
75 +
        return 3;
76 +
    }
77 +
78 +
    // Pass mutable trait object to function.
79 +
    let mut c2 = Circle { radius: 3 };
80 +
    let sc: *mut opaque Scalable = &mut c2;
81 +
    doubleSize(sc);
82 +
    if c2.radius != 6 {
83 +
        return 4;
84 +
    }
85 +
    return 0;
86 +
}
lib/std/arch/rv64/tests/trait.multiple.methods.rad added +66 -0
1 +
//! Test trait with multiple methods and mixed return types.
2 +
//!
3 +
//! Exercises: a trait declaring multiple methods with void, bool, and
4 +
//! integer return types. Verifies vtable layout with more than one
5 +
//! entry and correct dispatch for each method.
6 +
7 +
record Accumulator {
8 +
    total: i32,
9 +
}
10 +
11 +
trait Collector {
12 +
    fn (*mut Collector) add(n: i32) -> i32;
13 +
    fn (*mut Collector) clear();
14 +
    fn (*mut Collector) isEmpty() -> bool;
15 +
}
16 +
17 +
instance Collector for Accumulator {
18 +
    fn (a: *mut Accumulator) add(n: i32) -> i32 {
19 +
        a.total = a.total + n;
20 +
        return a.total;
21 +
    }
22 +
23 +
    fn (a: *mut Accumulator) clear() {
24 +
        a.total = 0;
25 +
    }
26 +
27 +
    fn (a: *mut Accumulator) isEmpty() -> bool {
28 +
        return a.total == 0;
29 +
    }
30 +
}
31 +
32 +
@default fn main() -> i32 {
33 +
    let mut acc = Accumulator { total: 0 };
34 +
    let c: *mut opaque Collector = &mut acc;
35 +
36 +
    // Initially empty.
37 +
    if not c.isEmpty() {
38 +
        return 1;
39 +
    }
40 +
41 +
    // Add returns running total.
42 +
    let v1 = c.add(10);
43 +
    if v1 != 10 {
44 +
        return 2;
45 +
    }
46 +
47 +
    let v2 = c.add(20);
48 +
    if v2 != 30 {
49 +
        return 3;
50 +
    }
51 +
52 +
    // No longer empty.
53 +
    if c.isEmpty() {
54 +
        return 4;
55 +
    }
56 +
57 +
    // Void return: clear.
58 +
    c.clear();
59 +
    if acc.total != 0 {
60 +
        return 5;
61 +
    }
62 +
    if not c.isEmpty() {
63 +
        return 6;
64 +
    }
65 +
    return 0;
66 +
}
lib/std/arch/rv64/tests/trait.multiple.traits.rad added +66 -0
1 +
//! Test one type implementing multiple traits.
2 +
//!
3 +
//! Exercises: a single concrete type that provides instances for
4 +
//! two different traits. Each trait object dispatches independently
5 +
//! through its own vtable.
6 +
7 +
record Counter {
8 +
    value: i32,
9 +
}
10 +
11 +
trait Incrementable {
12 +
    fn (*mut Incrementable) inc() -> i32;
13 +
}
14 +
15 +
trait Resettable {
16 +
    fn (*mut Resettable) reset();
17 +
    fn (*mut Resettable) isZero() -> bool;
18 +
}
19 +
20 +
instance Incrementable for Counter {
21 +
    fn (c: *mut Counter) inc() -> i32 {
22 +
        c.value = c.value + 1;
23 +
        return c.value;
24 +
    }
25 +
}
26 +
27 +
instance Resettable for Counter {
28 +
    fn (c: *mut Counter) reset() {
29 +
        c.value = 0;
30 +
    }
31 +
32 +
    fn (c: *mut Counter) isZero() -> bool {
33 +
        return c.value == 0;
34 +
    }
35 +
}
36 +
37 +
@default fn main() -> i32 {
38 +
    let mut c = Counter { value: 10 };
39 +
40 +
    // Dispatch through Incrementable.
41 +
    let i: *mut opaque Incrementable = &mut c;
42 +
    let v1 = i.inc();
43 +
    if v1 != 11 {
44 +
        return 1;
45 +
    }
46 +
47 +
    // Dispatch through Resettable.
48 +
    let r: *mut opaque Resettable = &mut c;
49 +
    if r.isZero() {
50 +
        return 2;
51 +
    }
52 +
    r.reset();
53 +
    if not r.isZero() {
54 +
        return 3;
55 +
    }
56 +
    if c.value != 0 {
57 +
        return 4;
58 +
    }
59 +
60 +
    // Increment again after reset.
61 +
    let v2 = i.inc();
62 +
    if v2 != 1 {
63 +
        return 5;
64 +
    }
65 +
    return 0;
66 +
}
lib/std/arch/rv64/tests/trait.multiple.types.rad added +71 -0
1 +
//! Test multiple types implementing the same trait, and immutable receivers.
2 +
//!
3 +
//! Exercises: two different concrete types implementing the same trait,
4 +
//! coerced to the same trait object type. Also tests immutable `*Trait`
5 +
//! receivers. Verifies each type dispatches through its own vtable.
6 +
7 +
record Dog {
8 +
    age: i32,
9 +
}
10 +
11 +
record Cat {
12 +
    lives: i32,
13 +
}
14 +
15 +
trait Speaker {
16 +
    fn (*Speaker) speak() -> i32;
17 +
    fn (*Speaker) isOld() -> bool;
18 +
}
19 +
20 +
instance Speaker for Dog {
21 +
    fn (d: *Dog) speak() -> i32 {
22 +
        return d.age;
23 +
    }
24 +
25 +
    fn (d: *Dog) isOld() -> bool {
26 +
        return d.age > 10;
27 +
    }
28 +
}
29 +
30 +
instance Speaker for Cat {
31 +
    fn (c: *Cat) speak() -> i32 {
32 +
        return c.lives * 10;
33 +
    }
34 +
35 +
    fn (c: *Cat) isOld() -> bool {
36 +
        return c.lives < 5;
37 +
    }
38 +
}
39 +
40 +
@default fn main() -> i32 {
41 +
    let d = Dog { age: 5 };
42 +
    let c = Cat { lives: 9 };
43 +
44 +
    // Immutable trait object from Dog.
45 +
    let sd: *opaque Speaker = &d;
46 +
    let v1 = sd.speak();
47 +
    if v1 != 5 {
48 +
        return 1;
49 +
    }
50 +
    if sd.isOld() {
51 +
        return 2;
52 +
    }
53 +
54 +
    // Immutable trait object from Cat.
55 +
    let sc: *opaque Speaker = &c;
56 +
    let v2 = sc.speak();
57 +
    if v2 != 90 {
58 +
        return 3;
59 +
    }
60 +
    if sc.isOld() {
61 +
        return 4;
62 +
    }
63 +
64 +
    // Old dog.
65 +
    let oldDog = Dog { age: 15 };
66 +
    let so: *opaque Speaker = &oldDog;
67 +
    if not so.isOld() {
68 +
        return 5;
69 +
    }
70 +
    return 0;
71 +
}
lib/std/arch/rv64/tests/trait.writer.rad added +121 -0
1 +
//! Test realistic trait usage: a Writer abstraction with multiple backends.
2 +
//!
3 +
//! Exercises: a Writer trait used to abstract over different output targets.
4 +
//! A BufferWriter writes to an in-memory buffer; a CountingWriter wraps any
5 +
//! Writer and tracks how many bytes flow through it. This tests trait objects
6 +
//! as struct fields, dispatch chains, and a real-world composition pattern.
7 +
8 +
trait Writer {
9 +
    fn (*mut Writer) write(data: *[u8]) -> i32;
10 +
    fn (*Writer) total() -> i32;
11 +
}
12 +
13 +
/// Writes bytes into a fixed-size buffer.
14 +
record BufferWriter {
15 +
    buf: [u8; 64],
16 +
    pos: i32,
17 +
}
18 +
19 +
instance Writer for BufferWriter {
20 +
    fn (w: *mut BufferWriter) write(data: *[u8]) -> i32 {
21 +
        let mut i: u32 = 0;
22 +
        while i < data.len {
23 +
            if w.pos >= 64 {
24 +
                return w.pos;
25 +
            }
26 +
            w.buf[w.pos as u32] = data[i];
27 +
            w.pos = w.pos + 1;
28 +
            i = i + 1;
29 +
        }
30 +
        return w.pos;
31 +
    }
32 +
33 +
    fn (w: *BufferWriter) total() -> i32 {
34 +
        return w.pos;
35 +
    }
36 +
}
37 +
38 +
/// Counts bytes written through it, forwarding to an inner writer.
39 +
record CountingWriter {
40 +
    inner: *mut opaque Writer,
41 +
    count: i32,
42 +
}
43 +
44 +
instance Writer for CountingWriter {
45 +
    fn (w: *mut CountingWriter) write(data: *[u8]) -> i32 {
46 +
        w.count = w.count + data.len as i32;
47 +
        return w.inner.write(data);
48 +
    }
49 +
50 +
    fn (w: *CountingWriter) total() -> i32 {
51 +
        return w.count;
52 +
    }
53 +
}
54 +
55 +
/// Write a slice through any Writer.
56 +
fn emit(w: *mut opaque Writer, data: *[u8]) -> i32 {
57 +
    return w.write(data);
58 +
}
59 +
60 +
@default fn main() -> i32 {
61 +
    // Direct BufferWriter usage through trait.
62 +
    let mut buf = BufferWriter { buf: undefined, pos: 0 };
63 +
    let w: *mut opaque Writer = &mut buf;
64 +
    emit(w, "hello");
65 +
    if w.total() != 5 {
66 +
        return 1;
67 +
    }
68 +
    emit(w, " world");
69 +
    if w.total() != 11 {
70 +
        return 2;
71 +
    }
72 +
73 +
    // Verify buffer contents.
74 +
    if buf.buf[0] != 'h' as u8 {
75 +
        return 3;
76 +
    }
77 +
    if buf.buf[4] != 'o' as u8 {
78 +
        return 4;
79 +
    }
80 +
    if buf.buf[5] != ' ' as u8 {
81 +
        return 5;
82 +
    }
83 +
    if buf.buf[10] != 'd' as u8 {
84 +
        return 6;
85 +
    }
86 +
87 +
    // Counting writer wrapping a buffer writer.
88 +
    let mut buf2 = BufferWriter { buf: undefined, pos: 0 };
89 +
    let bw2: *mut opaque Writer = &mut buf2;
90 +
    let mut cw = CountingWriter { inner: bw2, count: 0 };
91 +
    let w2: *mut opaque Writer = &mut cw;
92 +
93 +
    emit(w2, "abc");
94 +
    if cw.count != 3 {
95 +
        return 7;
96 +
    }
97 +
    // Underlying buffer also received the bytes.
98 +
    if buf2.pos != 3 {
99 +
        return 8;
100 +
    }
101 +
102 +
    emit(w2, "defgh");
103 +
    if cw.count != 8 {
104 +
        return 9;
105 +
    }
106 +
    if buf2.pos != 8 {
107 +
        return 10;
108 +
    }
109 +
110 +
    // Check buf2 contents were forwarded correctly.
111 +
    if buf2.buf[0] != 'a' as u8 {
112 +
        return 11;
113 +
    }
114 +
    if buf2.buf[3] != 'd' as u8 {
115 +
        return 12;
116 +
    }
117 +
    if buf2.buf[7] != 'h' as u8 {
118 +
        return 13;
119 +
    }
120 +
    return 0;
121 +
}
lib/std/lang/resolver/tests.rad +99 -2
4502 4502
    let program = "union ErrA { A } union ErrB { B } fn f() -> i32 throws (ErrA, ErrB) { throw ErrA::A(); return 0; } fn g() -> i32 { return try f() catch e { return 0; }; }";
4503 4503
    let result = try resolveProgramStr(&mut a, program);
4504 4504
    try expectErrorKind(&result, super::ErrorKind::TryCatchMultiError);
4505 4505
}
4506 4506
4507 -
@test fn testInstanceMissingMethod() throws (testing::TestError) {
4507 +
@test fn testResolveInstanceMissingMethod() throws (testing::TestError) {
4508 4508
    let mut a = testResolver();
4509 4509
    let program = "trait S { fn (*S) f() -> i32; } record R { x: i32 } instance S for R {}";
4510 4510
    let result = try resolveProgramStr(&mut a, program);
4511 4511
    try expectErrorKind(&result, super::ErrorKind::MissingTraitMethod("f"));
4512 4512
}
4513 4513
4514 -
@test fn testInstanceUnknownMethod() throws (testing::TestError) {
4514 +
@test fn testResolveInstanceUnknownMethod() throws (testing::TestError) {
4515 4515
    let mut a = testResolver();
4516 4516
    let program = "trait S { fn (*S) f() -> i32; } record R { x: i32 } instance S for R { fn (self: *R) x() -> i32 { return 0; } }";
4517 4517
    let result = try resolveProgramStr(&mut a, program);
4518 4518
    try expectErrorKind(&result, super::ErrorKind::UnresolvedSymbol("x"));
4519 4519
}
4520 +
4521 +
@test fn testResolveTraitDuplicateMethodRejected() throws (testing::TestError) {
4522 +
    let mut a = testResolver();
4523 +
    let program = "trait Adder { fn (*mut Adder) add(n: i32) -> i32; fn (*mut Adder) add(n: i32) -> i32; }";
4524 +
    let result = try resolveProgramStr(&mut a, program);
4525 +
    try expectErrorKind(&result, super::ErrorKind::DuplicateBinding("add"));
4526 +
}
4527 +
4528 +
@test fn testResolveInstanceReceiverTypeMustMatchTarget() throws (testing::TestError) {
4529 +
    let mut a = testResolver();
4530 +
    let program = "record Counter { value: i32 } record Wrong { value: i32 } trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Wrong) add(n: i32) -> i32 { return n; } }";
4531 +
    let result = try resolveProgramStr(&mut a, program);
4532 +
    let err = try expectError(&result);
4533 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4534 +
        else throw testing::TestError::Failed;
4535 +
}
4536 +
4537 +
@test fn testResolveTraitMethodThrowsRequireTry() throws (testing::TestError) {
4538 +
    let mut a = testResolver();
4539 +
    let program = "union Error { Fail } record Counter { value: i32 } trait Adder { fn (*mut Adder) add(n: i32) -> i32 throws (Error); } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 throws (Error) { throw Error::Fail; return n; } } fn caller(a: *mut opaque Adder) -> i32 { return a.add(1); }";
4540 +
    let result = try resolveProgramStr(&mut a, program);
4541 +
    try expectErrorKind(&result, super::ErrorKind::MissingTry);
4542 +
}
4543 +
4544 +
/// Trait declares immutable receiver (*Trait) but instance uses mutable (*mut Type).
4545 +
/// The instance method could mutate through what was originally an immutable pointer.
4546 +
@test fn testResolveInstanceMutReceiverOnImmutableTrait() throws (testing::TestError) {
4547 +
    let mut a = testResolver();
4548 +
    let program = "record Counter { value: i32 } trait Reader { fn (*Reader) read() -> i32; } instance Reader for Counter { fn (c: *mut Counter) read() -> i32 { c.value = c.value + 1; return c.value; } }";
4549 +
    let result = try resolveProgramStr(&mut a, program);
4550 +
    // Should reject: instance declares *mut receiver but trait only requires immutable.
4551 +
    try expectErrorKind(&result, super::ErrorKind::ReceiverMutabilityMismatch);
4552 +
}
4553 +
4554 +
/// Instance method declares different parameter types than the trait.
4555 +
/// The resolver should reject the mismatch rather than silently using the trait's types.
4556 +
@test fn testResolveInstanceParamTypeMismatch() throws (testing::TestError) {
4557 +
    let mut a = testResolver();
4558 +
    let program = "record Acc { value: i32 } trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Acc { fn (a: *mut Acc) add(n: u8) -> i32 { a.value = a.value + n as i32; return a.value; } }";
4559 +
    let result = try resolveProgramStr(&mut a, program);
4560 +
    // Should reject: instance param type u8 doesn't match trait param type i32.
4561 +
    let err = try expectError(&result);
4562 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4563 +
        else throw testing::TestError::Failed;
4564 +
}
4565 +
4566 +
/// Duplicate instance declarations for the same (trait, type) pair should be rejected.
4567 +
@test fn testResolveInstanceDuplicateRejected() throws (testing::TestError) {
4568 +
    let mut a = testResolver();
4569 +
    let program = "record Counter { value: i32 } trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { c.value = c.value + n; return c.value; } } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { c.value = c.value + n + 100; return c.value; } }";
4570 +
    let result = try resolveProgramStr(&mut a, program);
4571 +
    // Should reject: duplicate instance for (Adder, Counter).
4572 +
    try expectErrorKind(&result, super::ErrorKind::DuplicateInstance);
4573 +
}
4574 +
4575 +
/// Trait method receiver must point to the declaring trait type.
4576 +
@test fn testResolveTraitReceiverMismatch() throws (testing::TestError) {
4577 +
    let mut a = testResolver();
4578 +
    let program = "record Other { x: i32 } trait Foo { fn (*mut Other) bar() -> i32; }";
4579 +
    let result = try resolveProgramStr(&mut a, program);
4580 +
    try expectErrorKind(&result, super::ErrorKind::TraitReceiverMismatch);
4581 +
}
4582 +
4583 +
/// Using a trait name as a value expression should be rejected.
4584 +
@test fn testResolveTraitNameAsValueRejected() throws (testing::TestError) {
4585 +
    let mut a = testResolver();
4586 +
    let program = "trait Foo { fn (*Foo) bar() -> i32; } fn test() -> i32 { let x = Foo; return 0; }";
4587 +
    let result = try resolveProgramStr(&mut a, program);
4588 +
    try expectErrorKind(&result, super::ErrorKind::UnexpectedTraitName);
4589 +
}
4590 +
4591 +
/// Cross-module trait: coerce to trait object and dispatch from a different module.
4592 +
@test fn testResolveTraitCrossModuleCoercion() throws (testing::TestError) {
4593 +
    let mut a = testResolver();
4594 +
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4595 +
4596 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod defs; mod app;", &mut arena);
4597 +
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "pub record Counter { value: i32 } pub trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { c.value = c.value + n; return c.value; } }", &mut arena);
4598 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn test() -> i32 { let mut c = defs::Counter { value: 10 }; let a: *mut opaque defs::Adder = &mut c; return a.add(5); }", &mut arena);
4599 +
4600 +
    let result = try resolveModuleTree(&mut a, rootId);
4601 +
    try expectNoErrors(&result);
4602 +
}
4603 +
4604 +
/// Instance in a different module from trait and type.
4605 +
@test fn testResolveInstanceCrossModule() throws (testing::TestError) {
4606 +
    let mut a = testResolver();
4607 +
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4608 +
4609 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod defs; pub mod impls; mod app;", &mut arena);
4610 +
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "pub record Counter { value: i32 } pub trait Adder { fn (*mut Adder) add(n: i32) -> i32; }", &mut arena);
4611 +
    let implsId = try registerModule(&mut MODULE_GRAPH, rootId, "impls", "use root::defs; instance defs::Adder for defs::Counter { fn (c: *mut defs::Counter) add(n: i32) -> i32 { c.value = c.value + n; return c.value; } }", &mut arena);
4612 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn test() -> i32 { let mut c = defs::Counter { value: 10 }; let a: *mut opaque defs::Adder = &mut c; return a.add(5); }", &mut arena);
4613 +
4614 +
    let result = try resolveModuleTree(&mut a, rootId);
4615 +
    try expectNoErrors(&result);
4616 +
}
vim/radiance.vim +1 -1
15 15
syntax keyword radianceTodo TODO FIXME contained containedin=radianceComment
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 -
syntax keyword radianceKeyword let mut as register device const log record union
20 +
syntax keyword radianceKeyword let mut as register device const log record union trait instance
21 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