Add tests for traits feature
4fd4c947c18fd1be2f9fdfe2df2ccbd057f168bc83f7881b58874416eb7f3c83
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 |