Simple pattern matching
19abdd4f658d98283b6b38c8d23ba644ea4022af984daf2cd46488063422cbc0
1 parent
40bf87cb
lib/std/arch/rv64/tests/match.array.rad
added
+160 -0
| 1 | + | //! Test array literal patterns in match statements. |
|
| 2 | + | ||
| 3 | + | /// All-binding array pattern: destructure into variables. |
|
| 4 | + | fn sum3(arr: [i32; 3]) -> i32 { |
|
| 5 | + | match arr { |
|
| 6 | + | case [a, b, c] => { |
|
| 7 | + | return a + b + c; |
|
| 8 | + | } |
|
| 9 | + | } |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Mixed literal and binding: test first element, bind rest. |
|
| 13 | + | fn classify(arr: [i32; 3]) -> i32 { |
|
| 14 | + | match arr { |
|
| 15 | + | case [1, b, c] => { |
|
| 16 | + | return b + c; |
|
| 17 | + | } |
|
| 18 | + | case [2, b, c] => { |
|
| 19 | + | return (b + c) * 2; |
|
| 20 | + | } |
|
| 21 | + | else => { |
|
| 22 | + | return 0; |
|
| 23 | + | } |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// All-literal array pattern. |
|
| 28 | + | fn isOrigin(p: [i32; 2]) -> bool { |
|
| 29 | + | match p { |
|
| 30 | + | case [0, 0] => { |
|
| 31 | + | return true; |
|
| 32 | + | } |
|
| 33 | + | else => { |
|
| 34 | + | return false; |
|
| 35 | + | } |
|
| 36 | + | } |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Placeholder inside array pattern. |
|
| 40 | + | fn firstOf3(arr: [i32; 3]) -> i32 { |
|
| 41 | + | match arr { |
|
| 42 | + | case [x, _, _] => { |
|
| 43 | + | return x; |
|
| 44 | + | } |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Array pattern nested inside union variant. |
|
| 49 | + | union Shape { |
|
| 50 | + | Point { coords: [i32; 2] }, |
|
| 51 | + | Line { start: [i32; 2], end: [i32; 2] }, |
|
| 52 | + | None, |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | fn shapeSum(s: Shape) -> i32 { |
|
| 56 | + | match s { |
|
| 57 | + | case Shape::Point { coords: [x, y] } => { |
|
| 58 | + | return x + y; |
|
| 59 | + | } |
|
| 60 | + | case Shape::Line { start: [x1, y1], end: [x2, y2] } => { |
|
| 61 | + | return x1 + y1 + x2 + y2; |
|
| 62 | + | } |
|
| 63 | + | case Shape::None => { |
|
| 64 | + | return 0; |
|
| 65 | + | } |
|
| 66 | + | } |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// Array pattern with literal test inside union. |
|
| 70 | + | fn isAtOrigin(s: Shape) -> bool { |
|
| 71 | + | match s { |
|
| 72 | + | case Shape::Point { coords: [0, 0] } => { |
|
| 73 | + | return true; |
|
| 74 | + | } |
|
| 75 | + | else => { |
|
| 76 | + | return false; |
|
| 77 | + | } |
|
| 78 | + | } |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Array pattern in if-let-case. |
|
| 82 | + | fn extractX(s: Shape) -> i32 { |
|
| 83 | + | if let case Shape::Point { coords: [x, _] } = s { |
|
| 84 | + | return x; |
|
| 85 | + | } |
|
| 86 | + | return -1; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | /// Array pattern in let-else. |
|
| 90 | + | fn extractLine(s: Shape) -> i32 { |
|
| 91 | + | let case Shape::Line { start: [x1, _], end: [x2, _] } = s |
|
| 92 | + | else { return -1; }; |
|
| 93 | + | return x2 - x1; |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | /// Nested array of arrays. |
|
| 97 | + | fn sumMatrix(m: [[i32; 2]; 2]) -> i32 { |
|
| 98 | + | match m { |
|
| 99 | + | case [[a, b], [c, d]] => { |
|
| 100 | + | return a + b + c + d; |
|
| 101 | + | } |
|
| 102 | + | } |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Nested array with literal test. |
|
| 106 | + | fn isIdentity(m: [[i32; 2]; 2]) -> bool { |
|
| 107 | + | match m { |
|
| 108 | + | case [[1, 0], [0, 1]] => { |
|
| 109 | + | return true; |
|
| 110 | + | } |
|
| 111 | + | else => { |
|
| 112 | + | return false; |
|
| 113 | + | } |
|
| 114 | + | } |
|
| 115 | + | } |
|
| 116 | + | ||
| 117 | + | @default fn main() -> i32 { |
|
| 118 | + | // sum3 |
|
| 119 | + | assert sum3([10, 20, 30]) == 60; |
|
| 120 | + | ||
| 121 | + | // classify |
|
| 122 | + | assert classify([1, 5, 6]) == 11; |
|
| 123 | + | assert classify([2, 3, 4]) == 14; |
|
| 124 | + | assert classify([9, 1, 1]) == 0; |
|
| 125 | + | ||
| 126 | + | // isOrigin |
|
| 127 | + | assert isOrigin([0, 0]); |
|
| 128 | + | assert not isOrigin([1, 0]); |
|
| 129 | + | assert not isOrigin([0, 1]); |
|
| 130 | + | ||
| 131 | + | // firstOf3 |
|
| 132 | + | assert firstOf3([42, 0, 0]) == 42; |
|
| 133 | + | ||
| 134 | + | // shapeSum |
|
| 135 | + | assert shapeSum(Shape::Point { coords: [3, 4] }) == 7; |
|
| 136 | + | assert shapeSum(Shape::Line { start: [1, 2], end: [3, 4] }) == 10; |
|
| 137 | + | assert shapeSum(Shape::None) == 0; |
|
| 138 | + | ||
| 139 | + | // isAtOrigin |
|
| 140 | + | assert isAtOrigin(Shape::Point { coords: [0, 0] }); |
|
| 141 | + | assert not isAtOrigin(Shape::Point { coords: [1, 0] }); |
|
| 142 | + | assert not isAtOrigin(Shape::None); |
|
| 143 | + | ||
| 144 | + | // extractX |
|
| 145 | + | assert extractX(Shape::Point { coords: [5, 9] }) == 5; |
|
| 146 | + | assert extractX(Shape::None) == -1; |
|
| 147 | + | ||
| 148 | + | // extractLine |
|
| 149 | + | assert extractLine(Shape::Line { start: [2, 0], end: [8, 0] }) == 6; |
|
| 150 | + | assert extractLine(Shape::None) == -1; |
|
| 151 | + | ||
| 152 | + | // sumMatrix |
|
| 153 | + | assert sumMatrix([[1, 2], [3, 4]]) == 10; |
|
| 154 | + | ||
| 155 | + | // isIdentity |
|
| 156 | + | assert isIdentity([[1, 0], [0, 1]]); |
|
| 157 | + | assert not isIdentity([[1, 1], [0, 1]]); |
|
| 158 | + | ||
| 159 | + | return 0; |
|
| 160 | + | } |
lib/std/arch/rv64/tests/match.char.rad
added
+66 -0
| 1 | + | //! Test character literal patterns in match statements. |
|
| 2 | + | ||
| 3 | + | /// Top-level character match. |
|
| 4 | + | fn charClass(c: u8) -> i32 { |
|
| 5 | + | match c { |
|
| 6 | + | case 'a' => { return 1; } |
|
| 7 | + | case 'b' => { return 2; } |
|
| 8 | + | case 'z' => { return 26; } |
|
| 9 | + | case '0' => { return 100; } |
|
| 10 | + | else => { return 0; } |
|
| 11 | + | } |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /// Character pattern nested inside a union variant field. |
|
| 15 | + | union Token { |
|
| 16 | + | Punct { ch: u8, pos: i32 }, |
|
| 17 | + | Eof, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | fn tokenVal(t: Token) -> i32 { |
|
| 21 | + | match t { |
|
| 22 | + | case Token::Punct { ch: '(', pos } => { |
|
| 23 | + | return pos; |
|
| 24 | + | } |
|
| 25 | + | case Token::Punct { ch: ')', pos } => { |
|
| 26 | + | return 0 - pos; |
|
| 27 | + | } |
|
| 28 | + | case Token::Punct { ch: '+', .. } => { |
|
| 29 | + | return 100; |
|
| 30 | + | } |
|
| 31 | + | else => { |
|
| 32 | + | return 0; |
|
| 33 | + | } |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Character pattern in if-let-case. |
|
| 38 | + | fn isOpen(t: Token) -> bool { |
|
| 39 | + | if let case Token::Punct { ch: '(', .. } = t { |
|
| 40 | + | return true; |
|
| 41 | + | } |
|
| 42 | + | return false; |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | @default fn main() -> i32 { |
|
| 46 | + | // charClass |
|
| 47 | + | assert charClass('a') == 1; |
|
| 48 | + | assert charClass('b') == 2; |
|
| 49 | + | assert charClass('z') == 26; |
|
| 50 | + | assert charClass('0') == 100; |
|
| 51 | + | assert charClass('x') == 0; |
|
| 52 | + | ||
| 53 | + | // tokenVal |
|
| 54 | + | assert tokenVal(Token::Punct { ch: '(', pos: 5 }) == 5; |
|
| 55 | + | assert tokenVal(Token::Punct { ch: ')', pos: 10 }) == -10; |
|
| 56 | + | assert tokenVal(Token::Punct { ch: '+', pos: 0 }) == 100; |
|
| 57 | + | assert tokenVal(Token::Punct { ch: '-', pos: 0 }) == 0; |
|
| 58 | + | assert tokenVal(Token::Eof) == 0; |
|
| 59 | + | ||
| 60 | + | // isOpen |
|
| 61 | + | assert isOpen(Token::Punct { ch: '(', pos: 0 }); |
|
| 62 | + | assert not isOpen(Token::Punct { ch: ')', pos: 0 }); |
|
| 63 | + | assert not isOpen(Token::Eof); |
|
| 64 | + | ||
| 65 | + | return 0; |
|
| 66 | + | } |
lib/std/arch/rv64/tests/match.nested.call.rad
added
+76 -0
| 1 | + | //! Test nested patterns with tuple-style (Call) union variants. |
|
| 2 | + | ||
| 3 | + | union Dir { North, South } |
|
| 4 | + | union Opt { Some(i32), None } |
|
| 5 | + | ||
| 6 | + | /// Tuple-style variant with integer literal nested pattern. |
|
| 7 | + | fn checkOpt(o: Opt) -> i32 { |
|
| 8 | + | match o { |
|
| 9 | + | case Opt::Some(42) => { |
|
| 10 | + | return 1; |
|
| 11 | + | } |
|
| 12 | + | case Opt::Some(x) => { |
|
| 13 | + | return x; |
|
| 14 | + | } |
|
| 15 | + | case Opt::None => { |
|
| 16 | + | return 0; |
|
| 17 | + | } |
|
| 18 | + | } |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Tuple-style variant in if-let-case. |
|
| 22 | + | fn extractSome(o: Opt) -> i32 { |
|
| 23 | + | if let case Opt::Some(x) = o { |
|
| 24 | + | return x; |
|
| 25 | + | } |
|
| 26 | + | return -1; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Tuple-style variant in let-else. |
|
| 30 | + | fn unwrapSome(o: Opt) -> i32 { |
|
| 31 | + | let case Opt::Some(x) = o |
|
| 32 | + | else { return -1; }; |
|
| 33 | + | return x; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Nested union inside tuple-style variant. |
|
| 37 | + | union Wrapper { Wrap(Dir), Empty } |
|
| 38 | + | ||
| 39 | + | fn extractDir(w: Wrapper) -> i32 { |
|
| 40 | + | match w { |
|
| 41 | + | case Wrapper::Wrap(Dir::North) => { |
|
| 42 | + | return 10; |
|
| 43 | + | } |
|
| 44 | + | case Wrapper::Wrap(Dir::South) => { |
|
| 45 | + | return 20; |
|
| 46 | + | } |
|
| 47 | + | else => { |
|
| 48 | + | return 0; |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | @default fn main() -> i32 { |
|
| 54 | + | // checkOpt: literal 42 matches first arm |
|
| 55 | + | assert checkOpt(Opt::Some(42)) == 1; |
|
| 56 | + | // checkOpt: non-42 matches second arm |
|
| 57 | + | assert checkOpt(Opt::Some(7)) == 7; |
|
| 58 | + | assert checkOpt(Opt::Some(0)) == 0; |
|
| 59 | + | // checkOpt: None |
|
| 60 | + | assert checkOpt(Opt::None) == 0; |
|
| 61 | + | ||
| 62 | + | // extractSome |
|
| 63 | + | assert extractSome(Opt::Some(99)) == 99; |
|
| 64 | + | assert extractSome(Opt::None) == -1; |
|
| 65 | + | ||
| 66 | + | // unwrapSome |
|
| 67 | + | assert unwrapSome(Opt::Some(55)) == 55; |
|
| 68 | + | assert unwrapSome(Opt::None) == -1; |
|
| 69 | + | ||
| 70 | + | // extractDir |
|
| 71 | + | assert extractDir(Wrapper::Wrap(Dir::North)) == 10; |
|
| 72 | + | assert extractDir(Wrapper::Wrap(Dir::South)) == 20; |
|
| 73 | + | assert extractDir(Wrapper::Empty) == 0; |
|
| 74 | + | ||
| 75 | + | return 0; |
|
| 76 | + | } |
lib/std/arch/rv64/tests/match.nested.deep.rad
added
+88 -0
| 1 | + | //! Test deeply nested patterns: three levels of record nesting, |
|
| 2 | + | //! and nested unions inside records inside unions. |
|
| 3 | + | ||
| 4 | + | record Leaf { |
|
| 5 | + | val: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Branch { |
|
| 9 | + | leaf: Leaf, |
|
| 10 | + | extra: i32, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | union Tree { |
|
| 14 | + | Node { branch: Branch, tag: i32 }, |
|
| 15 | + | Empty, |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | /// Three levels deep: union -> record -> record -> record. |
|
| 19 | + | fn sumTree(t: Tree) -> i32 { |
|
| 20 | + | match t { |
|
| 21 | + | case Tree::Node { branch: Branch { leaf: Leaf { val }, extra }, tag } => { |
|
| 22 | + | return val + extra + tag; |
|
| 23 | + | } |
|
| 24 | + | case Tree::Empty => { |
|
| 25 | + | return 0; |
|
| 26 | + | } |
|
| 27 | + | } |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Nested union inside a record inside a union. |
|
| 31 | + | union Color { Red, Green, Blue } |
|
| 32 | + | ||
| 33 | + | record Pixel { |
|
| 34 | + | color: Color, |
|
| 35 | + | brightness: i32, |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | union Canvas { |
|
| 39 | + | Filled { pixel: Pixel }, |
|
| 40 | + | Blank, |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | fn getColor(cv: Canvas) -> i32 { |
|
| 44 | + | match cv { |
|
| 45 | + | case Canvas::Filled { pixel: Pixel { color: Color::Red, brightness } } => { |
|
| 46 | + | return brightness; |
|
| 47 | + | } |
|
| 48 | + | case Canvas::Filled { pixel: Pixel { color: Color::Green, brightness } } => { |
|
| 49 | + | return brightness * 2; |
|
| 50 | + | } |
|
| 51 | + | case Canvas::Filled { pixel: Pixel { color: Color::Blue, brightness } } => { |
|
| 52 | + | return brightness * 3; |
|
| 53 | + | } |
|
| 54 | + | else => { |
|
| 55 | + | return 0; |
|
| 56 | + | } |
|
| 57 | + | } |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | /// If-let-case with nested patterns. |
|
| 61 | + | fn ifLetNested(t: Tree) -> i32 { |
|
| 62 | + | if let case Tree::Node { branch: Branch { leaf: Leaf { val }, extra }, tag } = t { |
|
| 63 | + | return val + extra + tag; |
|
| 64 | + | } |
|
| 65 | + | return -1; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | @default fn main() -> i32 { |
|
| 69 | + | // sumTree |
|
| 70 | + | let t = Tree::Node { branch: Branch { leaf: Leaf { val: 10 }, extra: 20 }, tag: 30 }; |
|
| 71 | + | assert sumTree(t) == 60; |
|
| 72 | + | assert sumTree(Tree::Empty) == 0; |
|
| 73 | + | ||
| 74 | + | // getColor |
|
| 75 | + | let red = Canvas::Filled { pixel: Pixel { color: Color::Red, brightness: 5 } }; |
|
| 76 | + | assert getColor(red) == 5; |
|
| 77 | + | let green = Canvas::Filled { pixel: Pixel { color: Color::Green, brightness: 7 } }; |
|
| 78 | + | assert getColor(green) == 14; |
|
| 79 | + | let blue = Canvas::Filled { pixel: Pixel { color: Color::Blue, brightness: 3 } }; |
|
| 80 | + | assert getColor(blue) == 9; |
|
| 81 | + | assert getColor(Canvas::Blank) == 0; |
|
| 82 | + | ||
| 83 | + | // ifLetNested |
|
| 84 | + | assert ifLetNested(t) == 60; |
|
| 85 | + | assert ifLetNested(Tree::Empty) == -1; |
|
| 86 | + | ||
| 87 | + | return 0; |
|
| 88 | + | } |
lib/std/arch/rv64/tests/match.nested.guard.rad
added
+71 -0
| 1 | + | //! Test nested patterns combined with guards. |
|
| 2 | + | ||
| 3 | + | record Pair { |
|
| 4 | + | a: i32, |
|
| 5 | + | b: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Opt { |
|
| 9 | + | Some { pair: Pair }, |
|
| 10 | + | None, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Nested pattern with guard on bound variable. |
|
| 14 | + | fn filtered(o: Opt) -> i32 { |
|
| 15 | + | match o { |
|
| 16 | + | case Opt::Some { pair: Pair { a, b } } if a > 0 => { |
|
| 17 | + | return a + b; |
|
| 18 | + | } |
|
| 19 | + | case Opt::Some { pair: Pair { a, b } } => { |
|
| 20 | + | return 0 - a - b; |
|
| 21 | + | } |
|
| 22 | + | case Opt::None => { |
|
| 23 | + | return -1; |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Nested union with guard. |
|
| 29 | + | union Tag { X, Y } |
|
| 30 | + | ||
| 31 | + | union Box { |
|
| 32 | + | Item { tag: Tag, val: i32 }, |
|
| 33 | + | Empty, |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | fn guardedNested(bx: Box) -> i32 { |
|
| 37 | + | match bx { |
|
| 38 | + | case Box::Item { tag: Tag::X, val } if val > 10 => { |
|
| 39 | + | return val; |
|
| 40 | + | } |
|
| 41 | + | case Box::Item { tag: Tag::X, val } => { |
|
| 42 | + | return 0 - val; |
|
| 43 | + | } |
|
| 44 | + | case Box::Item { tag: Tag::Y, val } => { |
|
| 45 | + | return val * 2; |
|
| 46 | + | } |
|
| 47 | + | else => { |
|
| 48 | + | return 0; |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | @default fn main() -> i32 { |
|
| 54 | + | // filtered tests |
|
| 55 | + | let pos = Opt::Some { pair: Pair { a: 3, b: 4 } }; |
|
| 56 | + | assert filtered(pos) == 7; |
|
| 57 | + | let neg = Opt::Some { pair: Pair { a: -2, b: 5 } }; |
|
| 58 | + | assert filtered(neg) == -3; |
|
| 59 | + | assert filtered(Opt::None) == -1; |
|
| 60 | + | ||
| 61 | + | // guardedNested tests |
|
| 62 | + | let x_big = Box::Item { tag: Tag::X, val: 20 }; |
|
| 63 | + | assert guardedNested(x_big) == 20; |
|
| 64 | + | let x_small = Box::Item { tag: Tag::X, val: 5 }; |
|
| 65 | + | assert guardedNested(x_small) == -5; |
|
| 66 | + | let y = Box::Item { tag: Tag::Y, val: 8 }; |
|
| 67 | + | assert guardedNested(y) == 16; |
|
| 68 | + | assert guardedNested(Box::Empty) == 0; |
|
| 69 | + | ||
| 70 | + | return 0; |
|
| 71 | + | } |
lib/std/arch/rv64/tests/match.nested.iflet.guard.rad
added
+68 -0
| 1 | + | //! Test nested patterns combined with guards in if-let-case. |
|
| 2 | + | ||
| 3 | + | record Pair { |
|
| 4 | + | a: i32, |
|
| 5 | + | b: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Opt { |
|
| 9 | + | Some { pair: Pair }, |
|
| 10 | + | None, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Nested record pattern with guard on bound variable. |
|
| 14 | + | fn filteredRecord(o: Opt) -> i32 { |
|
| 15 | + | if let case Opt::Some { pair: Pair { a, b } } = o; a > 0 { |
|
| 16 | + | return a + b; |
|
| 17 | + | } |
|
| 18 | + | return -1; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Nested union variant with guard. |
|
| 22 | + | union Dir { North, South } |
|
| 23 | + | ||
| 24 | + | union Command { |
|
| 25 | + | Move { dir: Dir, speed: i32 }, |
|
| 26 | + | Stop, |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | fn fastNorth(c: Command) -> i32 { |
|
| 30 | + | if let case Command::Move { dir: Dir::North, speed } = c; speed > 10 { |
|
| 31 | + | return speed; |
|
| 32 | + | } |
|
| 33 | + | return -1; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Nested record with guard and else branch. |
|
| 37 | + | fn filteredElse(o: Opt) -> i32 { |
|
| 38 | + | if let case Opt::Some { pair: Pair { a, b } } = o; a > 0 { |
|
| 39 | + | return a + b; |
|
| 40 | + | } else { |
|
| 41 | + | return 0; |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | @default fn main() -> i32 { |
|
| 46 | + | // filteredRecord |
|
| 47 | + | let pos = Opt::Some { pair: Pair { a: 3, b: 4 } }; |
|
| 48 | + | assert filteredRecord(pos) == 7; |
|
| 49 | + | let neg = Opt::Some { pair: Pair { a: -2, b: 5 } }; |
|
| 50 | + | assert filteredRecord(neg) == -1; |
|
| 51 | + | assert filteredRecord(Opt::None) == -1; |
|
| 52 | + | ||
| 53 | + | // fastNorth |
|
| 54 | + | let fast = Command::Move { dir: Dir::North, speed: 20 }; |
|
| 55 | + | assert fastNorth(fast) == 20; |
|
| 56 | + | let slow = Command::Move { dir: Dir::North, speed: 5 }; |
|
| 57 | + | assert fastNorth(slow) == -1; |
|
| 58 | + | let south = Command::Move { dir: Dir::South, speed: 15 }; |
|
| 59 | + | assert fastNorth(south) == -1; |
|
| 60 | + | assert fastNorth(Command::Stop) == -1; |
|
| 61 | + | ||
| 62 | + | // filteredElse |
|
| 63 | + | assert filteredElse(pos) == 7; |
|
| 64 | + | assert filteredElse(neg) == 0; |
|
| 65 | + | assert filteredElse(Opt::None) == 0; |
|
| 66 | + | ||
| 67 | + | return 0; |
|
| 68 | + | } |
lib/std/arch/rv64/tests/match.nested.iflet.rad
added
+63 -0
| 1 | + | //! Test nested patterns in if-let-case and let-else contexts. |
|
| 2 | + | ||
| 3 | + | record Inner { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Wrapper { |
|
| 9 | + | Some { inner: Inner, z: i32 }, |
|
| 10 | + | None, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Nested if-let-case. |
|
| 14 | + | fn ifLetNested(w: Wrapper) -> i32 { |
|
| 15 | + | if let case Wrapper::Some { inner: Inner { x, y }, z } = w { |
|
| 16 | + | return x + y + z; |
|
| 17 | + | } |
|
| 18 | + | return -1; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Nested union variant in if-let-case. |
|
| 22 | + | union Kind { A, B } |
|
| 23 | + | ||
| 24 | + | union Tagged { |
|
| 25 | + | Item { kind: Kind, val: i32 }, |
|
| 26 | + | Empty, |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | fn ifLetNestedUnion(t: Tagged) -> i32 { |
|
| 30 | + | if let case Tagged::Item { kind: Kind::A, val } = t { |
|
| 31 | + | return val; |
|
| 32 | + | } |
|
| 33 | + | return 0; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Nested patterns with placeholder in if-let-case. |
|
| 37 | + | fn ifLetPlaceholder(w: Wrapper) -> i32 { |
|
| 38 | + | if let case Wrapper::Some { inner: Inner { x, .. }, .. } = w { |
|
| 39 | + | return x; |
|
| 40 | + | } |
|
| 41 | + | return -1; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | @default fn main() -> i32 { |
|
| 45 | + | let w = Wrapper::Some { inner: Inner { x: 10, y: 20 }, z: 30 }; |
|
| 46 | + | ||
| 47 | + | // ifLetNested |
|
| 48 | + | assert ifLetNested(w) == 60; |
|
| 49 | + | assert ifLetNested(Wrapper::None) == -1; |
|
| 50 | + | ||
| 51 | + | // ifLetNestedUnion |
|
| 52 | + | let ta = Tagged::Item { kind: Kind::A, val: 42 }; |
|
| 53 | + | assert ifLetNestedUnion(ta) == 42; |
|
| 54 | + | let tb = Tagged::Item { kind: Kind::B, val: 99 }; |
|
| 55 | + | assert ifLetNestedUnion(tb) == 0; |
|
| 56 | + | assert ifLetNestedUnion(Tagged::Empty) == 0; |
|
| 57 | + | ||
| 58 | + | // ifLetPlaceholder |
|
| 59 | + | assert ifLetPlaceholder(w) == 10; |
|
| 60 | + | assert ifLetPlaceholder(Wrapper::None) == -1; |
|
| 61 | + | ||
| 62 | + | return 0; |
|
| 63 | + | } |
lib/std/arch/rv64/tests/match.nested.letelse.rad
added
+37 -0
| 1 | + | //! Test nested patterns in let-else statements. |
|
| 2 | + | ||
| 3 | + | record Inner { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Wrapper { |
|
| 9 | + | Some { inner: Inner, z: i32 }, |
|
| 10 | + | None, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Nested record destructuring in let-else. |
|
| 14 | + | fn extractSum(w: Wrapper) -> i32 { |
|
| 15 | + | let case Wrapper::Some { inner: Inner { x, y }, z } = w |
|
| 16 | + | else { return -1; }; |
|
| 17 | + | return x + y + z; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Nested with placeholder in let-else. |
|
| 21 | + | fn extractPartial(w: Wrapper) -> i32 { |
|
| 22 | + | let case Wrapper::Some { inner: Inner { x, .. }, .. } = w |
|
| 23 | + | else { return -1; }; |
|
| 24 | + | return x; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | @default fn main() -> i32 { |
|
| 28 | + | let w = Wrapper::Some { inner: Inner { x: 10, y: 20 }, z: 30 }; |
|
| 29 | + | ||
| 30 | + | assert extractSum(w) == 60; |
|
| 31 | + | assert extractSum(Wrapper::None) == -1; |
|
| 32 | + | ||
| 33 | + | assert extractPartial(w) == 10; |
|
| 34 | + | assert extractPartial(Wrapper::None) == -1; |
|
| 35 | + | ||
| 36 | + | return 0; |
|
| 37 | + | } |
lib/std/arch/rv64/tests/match.nested.letelse.union.rad
added
+58 -0
| 1 | + | //! Test nested union variant patterns in let-else statements. |
|
| 2 | + | ||
| 3 | + | union Dir { North, South } |
|
| 4 | + | ||
| 5 | + | union Command { |
|
| 6 | + | Move { dir: Dir, speed: i32 }, |
|
| 7 | + | Stop, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | /// Nested union variant in let-else. |
|
| 11 | + | fn extractNorth(c: Command) -> i32 { |
|
| 12 | + | let case Command::Move { dir: Dir::North, speed } = c |
|
| 13 | + | else { return -1; }; |
|
| 14 | + | return speed; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | /// Nested union variant in let-else, deeper nesting. |
|
| 18 | + | record Inner { |
|
| 19 | + | x: i32, |
|
| 20 | + | y: i32, |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | union Wrapper { |
|
| 24 | + | Some { inner: Inner, z: i32 }, |
|
| 25 | + | None, |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | union Box { |
|
| 29 | + | Filled { w: Wrapper, tag: i32 }, |
|
| 30 | + | Empty, |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn extractInner(b: Box) -> i32 { |
|
| 34 | + | let case Box::Filled { w: Wrapper::Some { inner: Inner { x, y }, z }, tag } = b |
|
| 35 | + | else { return -1; }; |
|
| 36 | + | return x + y + z + tag; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | @default fn main() -> i32 { |
|
| 40 | + | // extractNorth |
|
| 41 | + | let mn = Command::Move { dir: Dir::North, speed: 5 }; |
|
| 42 | + | assert extractNorth(mn) == 5; |
|
| 43 | + | let ms = Command::Move { dir: Dir::South, speed: 3 }; |
|
| 44 | + | assert extractNorth(ms) == -1; |
|
| 45 | + | assert extractNorth(Command::Stop) == -1; |
|
| 46 | + | ||
| 47 | + | // extractInner |
|
| 48 | + | let b = Box::Filled { |
|
| 49 | + | w: Wrapper::Some { inner: Inner { x: 10, y: 20 }, z: 30 }, |
|
| 50 | + | tag: 40, |
|
| 51 | + | }; |
|
| 52 | + | assert extractInner(b) == 100; |
|
| 53 | + | let b2 = Box::Filled { w: Wrapper::None, tag: 50 }; |
|
| 54 | + | assert extractInner(b2) == -1; |
|
| 55 | + | assert extractInner(Box::Empty) == -1; |
|
| 56 | + | ||
| 57 | + | return 0; |
|
| 58 | + | } |
lib/std/arch/rv64/tests/match.nested.literal.rad
added
+115 -0
| 1 | + | //! Test literal patterns (integers, booleans) nested inside union variant fields. |
|
| 2 | + | ||
| 3 | + | union Action { |
|
| 4 | + | Set { code: i32, value: i32 }, |
|
| 5 | + | Toggle { flag: bool }, |
|
| 6 | + | None, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | /// Match integer literals inside a union variant field. |
|
| 10 | + | fn handleCode(a: Action) -> i32 { |
|
| 11 | + | match a { |
|
| 12 | + | case Action::Set { code: 1, value } => { |
|
| 13 | + | return value * 10; |
|
| 14 | + | } |
|
| 15 | + | case Action::Set { code: 2, value } => { |
|
| 16 | + | return value * 20; |
|
| 17 | + | } |
|
| 18 | + | case Action::Set { code, value } => { |
|
| 19 | + | return code + value; |
|
| 20 | + | } |
|
| 21 | + | case Action::Toggle { flag: true } => { |
|
| 22 | + | return 100; |
|
| 23 | + | } |
|
| 24 | + | case Action::Toggle { flag: false } => { |
|
| 25 | + | return 200; |
|
| 26 | + | } |
|
| 27 | + | else => { |
|
| 28 | + | return 0; |
|
| 29 | + | } |
|
| 30 | + | } |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// Integer literal nested pattern in if-let-case. |
|
| 34 | + | fn isCodeOne(a: Action) -> bool { |
|
| 35 | + | if let case Action::Set { code: 1, .. } = a { |
|
| 36 | + | return true; |
|
| 37 | + | } |
|
| 38 | + | return false; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// Integer literal nested pattern in let-else. |
|
| 42 | + | fn extractCodeTwo(a: Action) -> i32 { |
|
| 43 | + | let case Action::Set { code: 2, value } = a |
|
| 44 | + | else { return -1; }; |
|
| 45 | + | return value; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Boolean literal nested in if-let-case. |
|
| 49 | + | fn isToggleOn(a: Action) -> bool { |
|
| 50 | + | if let case Action::Toggle { flag: true } = a { |
|
| 51 | + | return true; |
|
| 52 | + | } |
|
| 53 | + | return false; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Multiple literal fields in the same pattern. |
|
| 57 | + | union Pair { |
|
| 58 | + | Both { a: i32, b: i32 }, |
|
| 59 | + | Empty, |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | fn matchBothLiterals(p: Pair) -> i32 { |
|
| 63 | + | match p { |
|
| 64 | + | case Pair::Both { a: 1, b: 2 } => { |
|
| 65 | + | return 12; |
|
| 66 | + | } |
|
| 67 | + | case Pair::Both { a: 1, b } => { |
|
| 68 | + | return 10 + b; |
|
| 69 | + | } |
|
| 70 | + | case Pair::Both { a, b: 2 } => { |
|
| 71 | + | return a + 20; |
|
| 72 | + | } |
|
| 73 | + | else => { |
|
| 74 | + | return 0; |
|
| 75 | + | } |
|
| 76 | + | } |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | @default fn main() -> i32 { |
|
| 80 | + | // handleCode: integer literal patterns |
|
| 81 | + | assert handleCode(Action::Set { code: 1, value: 5 }) == 50; |
|
| 82 | + | assert handleCode(Action::Set { code: 2, value: 3 }) == 60; |
|
| 83 | + | assert handleCode(Action::Set { code: 99, value: 1 }) == 100; |
|
| 84 | + | ||
| 85 | + | // handleCode: boolean literal patterns |
|
| 86 | + | assert handleCode(Action::Toggle { flag: true }) == 100; |
|
| 87 | + | assert handleCode(Action::Toggle { flag: false }) == 200; |
|
| 88 | + | ||
| 89 | + | // handleCode: void variant |
|
| 90 | + | assert handleCode(Action::None) == 0; |
|
| 91 | + | ||
| 92 | + | // isCodeOne |
|
| 93 | + | assert isCodeOne(Action::Set { code: 1, value: 42 }); |
|
| 94 | + | assert not isCodeOne(Action::Set { code: 2, value: 42 }); |
|
| 95 | + | assert not isCodeOne(Action::None); |
|
| 96 | + | ||
| 97 | + | // extractCodeTwo |
|
| 98 | + | assert extractCodeTwo(Action::Set { code: 2, value: 77 }) == 77; |
|
| 99 | + | assert extractCodeTwo(Action::Set { code: 1, value: 77 }) == -1; |
|
| 100 | + | assert extractCodeTwo(Action::None) == -1; |
|
| 101 | + | ||
| 102 | + | // isToggleOn |
|
| 103 | + | assert isToggleOn(Action::Toggle { flag: true }); |
|
| 104 | + | assert not isToggleOn(Action::Toggle { flag: false }); |
|
| 105 | + | assert not isToggleOn(Action::None); |
|
| 106 | + | ||
| 107 | + | // matchBothLiterals |
|
| 108 | + | assert matchBothLiterals(Pair::Both { a: 1, b: 2 }) == 12; |
|
| 109 | + | assert matchBothLiterals(Pair::Both { a: 1, b: 5 }) == 15; |
|
| 110 | + | assert matchBothLiterals(Pair::Both { a: 3, b: 2 }) == 23; |
|
| 111 | + | assert matchBothLiterals(Pair::Both { a: 3, b: 5 }) == 0; |
|
| 112 | + | assert matchBothLiterals(Pair::Empty) == 0; |
|
| 113 | + | ||
| 114 | + | return 0; |
|
| 115 | + | } |
lib/std/arch/rv64/tests/match.nested.multi.rad
added
+75 -0
| 1 | + | //! Test multiple nested refining fields in the same record pattern. |
|
| 2 | + | ||
| 3 | + | union Dir { North, South, East } |
|
| 4 | + | union Mode { Fast, Slow } |
|
| 5 | + | ||
| 6 | + | union Command { |
|
| 7 | + | Move { dir: Dir, mode: Mode, speed: i32 }, |
|
| 8 | + | Stop, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Two union-typed fields refined simultaneously. |
|
| 12 | + | fn dispatch(c: Command) -> i32 { |
|
| 13 | + | match c { |
|
| 14 | + | case Command::Move { dir: Dir::North, mode: Mode::Fast, speed } => { |
|
| 15 | + | return speed * 4; |
|
| 16 | + | } |
|
| 17 | + | case Command::Move { dir: Dir::North, mode: Mode::Slow, speed } => { |
|
| 18 | + | return speed; |
|
| 19 | + | } |
|
| 20 | + | case Command::Move { dir: Dir::South, mode: Mode::Fast, speed } => { |
|
| 21 | + | return 0 - speed * 4; |
|
| 22 | + | } |
|
| 23 | + | case Command::Move { dir: Dir::South, mode: Mode::Slow, speed } => { |
|
| 24 | + | return 0 - speed; |
|
| 25 | + | } |
|
| 26 | + | else => { |
|
| 27 | + | return 0; |
|
| 28 | + | } |
|
| 29 | + | } |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Multiple refining fields with guard. |
|
| 33 | + | fn dispatchGuarded(c: Command) -> i32 { |
|
| 34 | + | match c { |
|
| 35 | + | case Command::Move { dir: Dir::North, mode: Mode::Fast, speed } if speed > 10 => { |
|
| 36 | + | return speed * 3; |
|
| 37 | + | } |
|
| 38 | + | case Command::Move { dir: Dir::North, mode: Mode::Fast, speed } => { |
|
| 39 | + | return speed * 2; |
|
| 40 | + | } |
|
| 41 | + | case Command::Move { dir: Dir::South, mode: Mode::Slow, speed } => { |
|
| 42 | + | return 0 - speed; |
|
| 43 | + | } |
|
| 44 | + | else => { |
|
| 45 | + | return 0; |
|
| 46 | + | } |
|
| 47 | + | } |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | @default fn main() -> i32 { |
|
| 51 | + | // dispatch |
|
| 52 | + | let nf = Command::Move { dir: Dir::North, mode: Mode::Fast, speed: 5 }; |
|
| 53 | + | assert dispatch(nf) == 20; |
|
| 54 | + | let ns = Command::Move { dir: Dir::North, mode: Mode::Slow, speed: 5 }; |
|
| 55 | + | assert dispatch(ns) == 5; |
|
| 56 | + | let sf = Command::Move { dir: Dir::South, mode: Mode::Fast, speed: 3 }; |
|
| 57 | + | assert dispatch(sf) == -12; |
|
| 58 | + | let ss = Command::Move { dir: Dir::South, mode: Mode::Slow, speed: 3 }; |
|
| 59 | + | assert dispatch(ss) == -3; |
|
| 60 | + | // East falls to else. |
|
| 61 | + | let ef = Command::Move { dir: Dir::East, mode: Mode::Fast, speed: 7 }; |
|
| 62 | + | assert dispatch(ef) == 0; |
|
| 63 | + | assert dispatch(Command::Stop) == 0; |
|
| 64 | + | ||
| 65 | + | // dispatchGuarded |
|
| 66 | + | let fast_big = Command::Move { dir: Dir::North, mode: Mode::Fast, speed: 20 }; |
|
| 67 | + | assert dispatchGuarded(fast_big) == 60; |
|
| 68 | + | let fast_small = Command::Move { dir: Dir::North, mode: Mode::Fast, speed: 5 }; |
|
| 69 | + | assert dispatchGuarded(fast_small) == 10; |
|
| 70 | + | let south_slow = Command::Move { dir: Dir::South, mode: Mode::Slow, speed: 4 }; |
|
| 71 | + | assert dispatchGuarded(south_slow) == -4; |
|
| 72 | + | assert dispatchGuarded(Command::Stop) == 0; |
|
| 73 | + | ||
| 74 | + | return 0; |
|
| 75 | + | } |
lib/std/arch/rv64/tests/match.nested.pattern.rad
added
+212 -0
| 1 | + | //! Test nested patterns in match/case statements. |
|
| 2 | + | ||
| 3 | + | record Inner { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Outer { |
|
| 9 | + | A { inner: Inner, z: i32 }, |
|
| 10 | + | B, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Match a union variant and destructure its nested record field. |
|
| 14 | + | fn testNestedMatch(o: Outer) -> i32 { |
|
| 15 | + | match o { |
|
| 16 | + | case Outer::A { inner: Inner { x, y }, z } => { |
|
| 17 | + | return x + y + z; |
|
| 18 | + | } |
|
| 19 | + | case Outer::B => { |
|
| 20 | + | return 0; |
|
| 21 | + | } |
|
| 22 | + | } |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Nested pattern in if-let-case. |
|
| 26 | + | fn testNestedIfLetCase(o: Outer) -> i32 { |
|
| 27 | + | if let case Outer::A { inner: Inner { x, y }, z } = o { |
|
| 28 | + | return x + y + z; |
|
| 29 | + | } |
|
| 30 | + | return 0; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// Nested pattern with placeholder for unused fields. |
|
| 34 | + | fn testNestedPlaceholder(o: Outer) -> i32 { |
|
| 35 | + | match o { |
|
| 36 | + | case Outer::A { inner: Inner { x, .. }, z } => { |
|
| 37 | + | return x + z; |
|
| 38 | + | } |
|
| 39 | + | case Outer::B => { |
|
| 40 | + | return 0; |
|
| 41 | + | } |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Multi-level nesting: three records deep. |
|
| 46 | + | record Deep { |
|
| 47 | + | val: i32, |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | record Mid { |
|
| 51 | + | deep: Deep, |
|
| 52 | + | extra: i32, |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | union Wrapper { |
|
| 56 | + | W { mid: Mid, tag: i32 }, |
|
| 57 | + | Empty, |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | fn testDeepNesting(w: Wrapper) -> i32 { |
|
| 61 | + | match w { |
|
| 62 | + | case Wrapper::W { mid: Mid { deep: Deep { val }, extra }, tag } => { |
|
| 63 | + | return val + extra + tag; |
|
| 64 | + | } |
|
| 65 | + | case Wrapper::Empty => { |
|
| 66 | + | return 0; |
|
| 67 | + | } |
|
| 68 | + | } |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// Nested union inside a union variant. |
|
| 72 | + | union Direction { |
|
| 73 | + | North, |
|
| 74 | + | South, |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | union Command { |
|
| 78 | + | Move { dir: Direction, speed: i32 }, |
|
| 79 | + | Stop, |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | fn testNestedUnionLiteral(c: Command) -> i32 { |
|
| 83 | + | match c { |
|
| 84 | + | case Command::Move { dir: Direction::North, speed } => { |
|
| 85 | + | return speed; |
|
| 86 | + | } |
|
| 87 | + | case Command::Move { dir: Direction::South, speed } => { |
|
| 88 | + | return 0 - speed; |
|
| 89 | + | } |
|
| 90 | + | else => { |
|
| 91 | + | return 0; |
|
| 92 | + | } |
|
| 93 | + | } |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | /// Mixed record and union nesting with multiple fields. |
|
| 97 | + | record Point { |
|
| 98 | + | x: i32, |
|
| 99 | + | y: i32, |
|
| 100 | + | } |
|
| 101 | + | ||
| 102 | + | union Shape { |
|
| 103 | + | Circle { center: Point, radius: i32 }, |
|
| 104 | + | Rect { origin: Point, size: Point }, |
|
| 105 | + | None, |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | fn testMixedNesting(s: Shape) -> i32 { |
|
| 109 | + | match s { |
|
| 110 | + | case Shape::Circle { center: Point { x, y }, radius } => { |
|
| 111 | + | return x + y + radius; |
|
| 112 | + | } |
|
| 113 | + | case Shape::Rect { origin: Point { x: ox, y: oy }, size: Point { x: sx, y: sy } } => { |
|
| 114 | + | return ox + oy + sx + sy; |
|
| 115 | + | } |
|
| 116 | + | case Shape::None => { |
|
| 117 | + | return 0; |
|
| 118 | + | } |
|
| 119 | + | } |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | /// Nested union variant pattern inside record field. |
|
| 123 | + | union Pair { |
|
| 124 | + | Val { a: i32, b: i32 }, |
|
| 125 | + | Empty, |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | record Container { |
|
| 129 | + | pair: Pair, |
|
| 130 | + | label: i32, |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | union Boxed { |
|
| 134 | + | Some { c: Container }, |
|
| 135 | + | None, |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | fn testNestedUnionInRecord(bx: Boxed) -> i32 { |
|
| 139 | + | match bx { |
|
| 140 | + | case Boxed::Some { c: Container { pair: Pair::Val { a, b }, label } } => { |
|
| 141 | + | return a + b + label; |
|
| 142 | + | } |
|
| 143 | + | case Boxed::Some { c: Container { pair: Pair::Empty, .. } } => { |
|
| 144 | + | return -1; |
|
| 145 | + | } |
|
| 146 | + | else => { |
|
| 147 | + | return 0; |
|
| 148 | + | } |
|
| 149 | + | } |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | /// Nested pattern binding -- bind intermediate record as a whole. |
|
| 153 | + | fn testPartialNesting(s: Shape) -> i32 { |
|
| 154 | + | match s { |
|
| 155 | + | case Shape::Circle { center, radius } => { |
|
| 156 | + | return center.x + center.y + radius; |
|
| 157 | + | } |
|
| 158 | + | case Shape::Rect { origin, size: Point { x, y } } => { |
|
| 159 | + | return origin.x + origin.y + x + y; |
|
| 160 | + | } |
|
| 161 | + | case Shape::None => { |
|
| 162 | + | return 0; |
|
| 163 | + | } |
|
| 164 | + | } |
|
| 165 | + | } |
|
| 166 | + | ||
| 167 | + | @default fn main() -> i32 { |
|
| 168 | + | // Basic nested match. |
|
| 169 | + | let o1 = Outer::A { inner: Inner { x: 10, y: 20 }, z: 30 }; |
|
| 170 | + | assert testNestedMatch(o1) == 60; |
|
| 171 | + | assert testNestedMatch(Outer::B) == 0; |
|
| 172 | + | ||
| 173 | + | // If-let-case nested. |
|
| 174 | + | assert testNestedIfLetCase(o1) == 60; |
|
| 175 | + | assert testNestedIfLetCase(Outer::B) == 0; |
|
| 176 | + | ||
| 177 | + | // Placeholder in nested pattern. |
|
| 178 | + | assert testNestedPlaceholder(o1) == 40; |
|
| 179 | + | ||
| 180 | + | // Three-level deep nesting. |
|
| 181 | + | let w = Wrapper::W { mid: Mid { deep: Deep { val: 100 }, extra: 200 }, tag: 300 }; |
|
| 182 | + | assert testDeepNesting(w) == 600; |
|
| 183 | + | assert testDeepNesting(Wrapper::Empty) == 0; |
|
| 184 | + | ||
| 185 | + | // Nested union literal inside union variant. |
|
| 186 | + | let north = Command::Move { dir: Direction::North, speed: 5 }; |
|
| 187 | + | assert testNestedUnionLiteral(north) == 5; |
|
| 188 | + | let south = Command::Move { dir: Direction::South, speed: 3 }; |
|
| 189 | + | assert testNestedUnionLiteral(south) == -3; |
|
| 190 | + | assert testNestedUnionLiteral(Command::Stop) == 0; |
|
| 191 | + | ||
| 192 | + | // Mixed nesting: record fields destructured in multiple arms. |
|
| 193 | + | let circ = Shape::Circle { center: Point { x: 1, y: 2 }, radius: 10 }; |
|
| 194 | + | assert testMixedNesting(circ) == 13; |
|
| 195 | + | let rect = Shape::Rect { origin: Point { x: 3, y: 4 }, size: Point { x: 5, y: 6 } }; |
|
| 196 | + | assert testMixedNesting(rect) == 18; |
|
| 197 | + | assert testMixedNesting(Shape::None) == 0; |
|
| 198 | + | ||
| 199 | + | // Nested union variant pattern inside record inside union. |
|
| 200 | + | let bx = Boxed::Some { c: Container { pair: Pair::Val { a: 7, b: 8 }, label: 100 } }; |
|
| 201 | + | assert testNestedUnionInRecord(bx) == 115; |
|
| 202 | + | let bx2 = Boxed::Some { c: Container { pair: Pair::Empty, label: 99 } }; |
|
| 203 | + | assert testNestedUnionInRecord(bx2) == -1; |
|
| 204 | + | assert testNestedUnionInRecord(Boxed::None) == 0; |
|
| 205 | + | ||
| 206 | + | // Partial nesting: bind one field whole, destructure another. |
|
| 207 | + | assert testPartialNesting(circ) == 13; |
|
| 208 | + | assert testPartialNesting(rect) == 18; |
|
| 209 | + | assert testPartialNesting(Shape::None) == 0; |
|
| 210 | + | ||
| 211 | + | return 0; |
|
| 212 | + | } |
lib/std/arch/rv64/tests/match.nested.record.rad
added
+84 -0
| 1 | + | //! Test nested record destructuring inside union variant patterns. |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Rect { |
|
| 9 | + | origin: Point, |
|
| 10 | + | size: Point, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | union Shape { |
|
| 14 | + | Circle { center: Point, radius: i32 }, |
|
| 15 | + | Rectangle { rect: Rect }, |
|
| 16 | + | None, |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Nested record destructuring in multiple arms. |
|
| 20 | + | fn area(s: Shape) -> i32 { |
|
| 21 | + | match s { |
|
| 22 | + | case Shape::Circle { center: Point { x, y }, radius } => { |
|
| 23 | + | return x + y + radius; |
|
| 24 | + | } |
|
| 25 | + | case Shape::Rectangle { rect: Rect { origin: Point { x: ox, y: oy }, size: Point { x: w, y: h } } } => { |
|
| 26 | + | return ox + oy + w * h; |
|
| 27 | + | } |
|
| 28 | + | case Shape::None => { |
|
| 29 | + | return 0; |
|
| 30 | + | } |
|
| 31 | + | } |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Partial nesting: bind intermediate record as a whole. |
|
| 35 | + | fn partial(s: Shape) -> i32 { |
|
| 36 | + | match s { |
|
| 37 | + | case Shape::Circle { center, radius } => { |
|
| 38 | + | return center.x + center.y + radius; |
|
| 39 | + | } |
|
| 40 | + | case Shape::Rectangle { rect } => { |
|
| 41 | + | return rect.origin.x + rect.size.x * rect.size.y; |
|
| 42 | + | } |
|
| 43 | + | case Shape::None => { |
|
| 44 | + | return -1; |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Nested with placeholder to ignore fields. |
|
| 50 | + | fn withPlaceholder(s: Shape) -> i32 { |
|
| 51 | + | match s { |
|
| 52 | + | case Shape::Circle { center: Point { x, .. }, radius } => { |
|
| 53 | + | return x + radius; |
|
| 54 | + | } |
|
| 55 | + | case Shape::Rectangle { rect: Rect { size: Point { x: w, y: h }, .. } } => { |
|
| 56 | + | return w * h; |
|
| 57 | + | } |
|
| 58 | + | case Shape::None => { |
|
| 59 | + | return 0; |
|
| 60 | + | } |
|
| 61 | + | } |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | @default fn main() -> i32 { |
|
| 65 | + | let c = Shape::Circle { center: Point { x: 3, y: 4 }, radius: 5 }; |
|
| 66 | + | let r = Shape::Rectangle { rect: Rect { origin: Point { x: 1, y: 2 }, size: Point { x: 10, y: 20 } } }; |
|
| 67 | + | ||
| 68 | + | // area tests |
|
| 69 | + | assert area(c) == 12; |
|
| 70 | + | assert area(r) == 203; |
|
| 71 | + | assert area(Shape::None) == 0; |
|
| 72 | + | ||
| 73 | + | // partial tests |
|
| 74 | + | assert partial(c) == 12; |
|
| 75 | + | assert partial(r) == 201; |
|
| 76 | + | assert partial(Shape::None) == -1; |
|
| 77 | + | ||
| 78 | + | // withPlaceholder tests |
|
| 79 | + | assert withPlaceholder(c) == 8; |
|
| 80 | + | assert withPlaceholder(r) == 200; |
|
| 81 | + | assert withPlaceholder(Shape::None) == 0; |
|
| 82 | + | ||
| 83 | + | return 0; |
|
| 84 | + | } |
lib/std/arch/rv64/tests/match.nested.union.rad
added
+81 -0
| 1 | + | //! Test nested union variant patterns inside record fields. |
|
| 2 | + | ||
| 3 | + | union Dir { North, South, East, West } |
|
| 4 | + | ||
| 5 | + | union Command { |
|
| 6 | + | Move { dir: Dir, speed: i32 }, |
|
| 7 | + | Turn { dir: Dir }, |
|
| 8 | + | Stop, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Match nested union variants within record field patterns. |
|
| 12 | + | fn dispatch(c: Command) -> i32 { |
|
| 13 | + | match c { |
|
| 14 | + | case Command::Move { dir: Dir::North, speed } => { |
|
| 15 | + | return speed; |
|
| 16 | + | } |
|
| 17 | + | case Command::Move { dir: Dir::South, speed } => { |
|
| 18 | + | return 0 - speed; |
|
| 19 | + | } |
|
| 20 | + | case Command::Move { dir: Dir::East, speed } => { |
|
| 21 | + | return speed * 2; |
|
| 22 | + | } |
|
| 23 | + | case Command::Move { dir: Dir::West, speed } => { |
|
| 24 | + | return speed * 3; |
|
| 25 | + | } |
|
| 26 | + | case Command::Turn { dir: Dir::North } => { |
|
| 27 | + | return 100; |
|
| 28 | + | } |
|
| 29 | + | case Command::Turn { dir: Dir::South } => { |
|
| 30 | + | return 200; |
|
| 31 | + | } |
|
| 32 | + | else => { |
|
| 33 | + | return 0; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// Same outer variant appears multiple times with different nested patterns. |
|
| 39 | + | fn moveOnly(c: Command) -> i32 { |
|
| 40 | + | match c { |
|
| 41 | + | case Command::Move { dir: Dir::North, speed } => { |
|
| 42 | + | return speed + 10; |
|
| 43 | + | } |
|
| 44 | + | case Command::Move { dir: Dir::South, speed } => { |
|
| 45 | + | return speed + 20; |
|
| 46 | + | } |
|
| 47 | + | else => { |
|
| 48 | + | return -1; |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | @default fn main() -> i32 { |
|
| 54 | + | // dispatch tests |
|
| 55 | + | let mn = Command::Move { dir: Dir::North, speed: 5 }; |
|
| 56 | + | assert dispatch(mn) == 5; |
|
| 57 | + | let ms = Command::Move { dir: Dir::South, speed: 3 }; |
|
| 58 | + | assert dispatch(ms) == -3; |
|
| 59 | + | let me = Command::Move { dir: Dir::East, speed: 7 }; |
|
| 60 | + | assert dispatch(me) == 14; |
|
| 61 | + | let mw = Command::Move { dir: Dir::West, speed: 4 }; |
|
| 62 | + | assert dispatch(mw) == 12; |
|
| 63 | + | let tn = Command::Turn { dir: Dir::North }; |
|
| 64 | + | assert dispatch(tn) == 100; |
|
| 65 | + | let ts = Command::Turn { dir: Dir::South }; |
|
| 66 | + | assert dispatch(ts) == 200; |
|
| 67 | + | let te = Command::Turn { dir: Dir::East }; |
|
| 68 | + | assert dispatch(te) == 0; |
|
| 69 | + | assert dispatch(Command::Stop) == 0; |
|
| 70 | + | ||
| 71 | + | // moveOnly tests |
|
| 72 | + | let mn2 = Command::Move { dir: Dir::North, speed: 5 }; |
|
| 73 | + | assert moveOnly(mn2) == 15; |
|
| 74 | + | let ms2 = Command::Move { dir: Dir::South, speed: 3 }; |
|
| 75 | + | assert moveOnly(ms2) == 23; |
|
| 76 | + | let me2 = Command::Move { dir: Dir::East, speed: 1 }; |
|
| 77 | + | assert moveOnly(me2) == -1; |
|
| 78 | + | assert moveOnly(Command::Stop) == -1; |
|
| 79 | + | ||
| 80 | + | return 0; |
|
| 81 | + | } |
lib/std/arch/rv64/tests/match.nested.whilelet.rad
added
+103 -0
| 1 | + | //! Test nested patterns in while-let loops. |
|
| 2 | + | ||
| 3 | + | record Pair { |
|
| 4 | + | a: i32, |
|
| 5 | + | b: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Opt { |
|
| 9 | + | Some { pair: Pair }, |
|
| 10 | + | None, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn get(items: *[Opt], idx: u32) -> Opt { |
|
| 14 | + | if idx < items.len { |
|
| 15 | + | return items[idx]; |
|
| 16 | + | } |
|
| 17 | + | return Opt::None; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// While-let with nested record destructuring. |
|
| 21 | + | fn sumPairs(items: *[Opt]) -> i32 { |
|
| 22 | + | let mut sum: i32 = 0; |
|
| 23 | + | let mut i: u32 = 0; |
|
| 24 | + | while let case Opt::Some { pair: Pair { a, b } } = get(items, i) { |
|
| 25 | + | sum = sum + a + b; |
|
| 26 | + | i = i + 1; |
|
| 27 | + | } |
|
| 28 | + | return sum; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// While-let with nested record and guard. |
|
| 32 | + | fn sumPositive(items: *[Opt]) -> i32 { |
|
| 33 | + | let mut sum: i32 = 0; |
|
| 34 | + | let mut i: u32 = 0; |
|
| 35 | + | while let case Opt::Some { pair: Pair { a, b } } = get(items, i); a > 0 { |
|
| 36 | + | sum = sum + a + b; |
|
| 37 | + | i = i + 1; |
|
| 38 | + | } |
|
| 39 | + | return sum; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | union Dir { North, South } |
|
| 43 | + | ||
| 44 | + | union Command { |
|
| 45 | + | Move { dir: Dir, speed: i32 }, |
|
| 46 | + | Stop, |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | fn getCmd(items: *[Command], idx: u32) -> Command { |
|
| 50 | + | if idx < items.len { |
|
| 51 | + | return items[idx]; |
|
| 52 | + | } |
|
| 53 | + | return Command::Stop; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// While-let with nested union variant pattern. |
|
| 57 | + | fn sumNorthSpeeds(items: *[Command]) -> i32 { |
|
| 58 | + | let mut sum: i32 = 0; |
|
| 59 | + | let mut i: u32 = 0; |
|
| 60 | + | while let case Command::Move { dir: Dir::North, speed } = getCmd(items, i) { |
|
| 61 | + | sum = sum + speed; |
|
| 62 | + | i = i + 1; |
|
| 63 | + | } |
|
| 64 | + | return sum; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | @default fn main() -> i32 { |
|
| 68 | + | // sumPairs |
|
| 69 | + | let items: *[Opt] = &[ |
|
| 70 | + | Opt::Some { pair: Pair { a: 1, b: 2 } }, |
|
| 71 | + | Opt::Some { pair: Pair { a: 3, b: 4 } }, |
|
| 72 | + | ]; |
|
| 73 | + | assert sumPairs(items) == 10; |
|
| 74 | + | ||
| 75 | + | // sumPairs: empty |
|
| 76 | + | let empty: *[Opt] = &[]; |
|
| 77 | + | assert sumPairs(empty) == 0; |
|
| 78 | + | ||
| 79 | + | // sumPositive: stops at first non-positive a. |
|
| 80 | + | let mixed: *[Opt] = &[ |
|
| 81 | + | Opt::Some { pair: Pair { a: 3, b: 4 } }, |
|
| 82 | + | Opt::Some { pair: Pair { a: -1, b: 5 } }, |
|
| 83 | + | Opt::Some { pair: Pair { a: 2, b: 6 } }, |
|
| 84 | + | ]; |
|
| 85 | + | assert sumPositive(mixed) == 7; |
|
| 86 | + | ||
| 87 | + | // sumNorthSpeeds |
|
| 88 | + | let cmds: *[Command] = &[ |
|
| 89 | + | Command::Move { dir: Dir::North, speed: 5 }, |
|
| 90 | + | Command::Move { dir: Dir::North, speed: 10 }, |
|
| 91 | + | ]; |
|
| 92 | + | assert sumNorthSpeeds(cmds) == 15; |
|
| 93 | + | ||
| 94 | + | // sumNorthSpeeds: stops at South. |
|
| 95 | + | let cmds2: *[Command] = &[ |
|
| 96 | + | Command::Move { dir: Dir::North, speed: 3 }, |
|
| 97 | + | Command::Move { dir: Dir::South, speed: 7 }, |
|
| 98 | + | Command::Move { dir: Dir::North, speed: 9 }, |
|
| 99 | + | ]; |
|
| 100 | + | assert sumNorthSpeeds(cmds2) == 3; |
|
| 101 | + | ||
| 102 | + | return 0; |
|
| 103 | + | } |
lib/std/arch/rv64/tests/match.string.rad
added
+80 -0
| 1 | + | //! Test string literal patterns in match statements. |
|
| 2 | + | ||
| 3 | + | /// Top-level string match. |
|
| 4 | + | fn classify(s: *[u8]) -> i32 { |
|
| 5 | + | match s { |
|
| 6 | + | case "red" => { |
|
| 7 | + | return 1; |
|
| 8 | + | } |
|
| 9 | + | case "green" => { |
|
| 10 | + | return 2; |
|
| 11 | + | } |
|
| 12 | + | case "blue" => { |
|
| 13 | + | return 3; |
|
| 14 | + | } |
|
| 15 | + | else => { |
|
| 16 | + | return 0; |
|
| 17 | + | } |
|
| 18 | + | } |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// String pattern nested inside a union variant field. |
|
| 22 | + | union Cmd { |
|
| 23 | + | Say { msg: *[u8], count: i32 }, |
|
| 24 | + | Quit, |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn dispatch(c: Cmd) -> i32 { |
|
| 28 | + | match c { |
|
| 29 | + | case Cmd::Say { msg: "hello", count } => { |
|
| 30 | + | return count; |
|
| 31 | + | } |
|
| 32 | + | case Cmd::Say { msg: "bye", count } => { |
|
| 33 | + | return 0 - count; |
|
| 34 | + | } |
|
| 35 | + | else => { |
|
| 36 | + | return 0; |
|
| 37 | + | } |
|
| 38 | + | } |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// String pattern in if-let-case. |
|
| 42 | + | fn isHello(c: Cmd) -> bool { |
|
| 43 | + | if let case Cmd::Say { msg: "hello", .. } = c { |
|
| 44 | + | return true; |
|
| 45 | + | } |
|
| 46 | + | return false; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// String pattern in let-else. |
|
| 50 | + | fn extractBye(c: Cmd) -> i32 { |
|
| 51 | + | let case Cmd::Say { msg: "bye", count } = c |
|
| 52 | + | else { return -1; }; |
|
| 53 | + | return count; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | @default fn main() -> i32 { |
|
| 57 | + | // classify |
|
| 58 | + | assert classify("red") == 1; |
|
| 59 | + | assert classify("green") == 2; |
|
| 60 | + | assert classify("blue") == 3; |
|
| 61 | + | assert classify("other") == 0; |
|
| 62 | + | ||
| 63 | + | // dispatch |
|
| 64 | + | assert dispatch(Cmd::Say { msg: "hello", count: 3 }) == 3; |
|
| 65 | + | assert dispatch(Cmd::Say { msg: "bye", count: 5 }) == -5; |
|
| 66 | + | assert dispatch(Cmd::Say { msg: "other", count: 1 }) == 0; |
|
| 67 | + | assert dispatch(Cmd::Quit) == 0; |
|
| 68 | + | ||
| 69 | + | // isHello |
|
| 70 | + | assert isHello(Cmd::Say { msg: "hello", count: 1 }); |
|
| 71 | + | assert not isHello(Cmd::Say { msg: "bye", count: 1 }); |
|
| 72 | + | assert not isHello(Cmd::Quit); |
|
| 73 | + | ||
| 74 | + | // extractBye |
|
| 75 | + | assert extractBye(Cmd::Say { msg: "bye", count: 7 }) == 7; |
|
| 76 | + | assert extractBye(Cmd::Say { msg: "hello", count: 7 }) == -1; |
|
| 77 | + | assert extractBye(Cmd::Quit) == -1; |
|
| 78 | + | ||
| 79 | + | return 0; |
|
| 80 | + | } |
lib/std/lang/lower.rad
+198 -49
| 2158 | 2158 | subject: *MatchSubject, |
|
| 2159 | 2159 | pattern: *ast::Node, |
|
| 2160 | 2160 | matchBlock: BlockId, |
|
| 2161 | 2161 | fallthrough: BlockId |
|
| 2162 | 2162 | ) throws (LowerError) { |
|
| 2163 | - | // Wildcard `_` always matches. |
|
| 2163 | + | // Wildcards always match; array patterns are tested element-by-element |
|
| 2164 | + | // during binding, so they also unconditionally enter the match block. |
|
| 2164 | 2165 | if isWildcardPattern(pattern) { |
|
| 2165 | 2166 | try emitJmp(self, matchBlock); |
|
| 2166 | 2167 | return; |
|
| 2167 | 2168 | } |
|
| 2169 | + | if let case ast::NodeValue::ArrayLit(_) = pattern.value { |
|
| 2170 | + | try emitJmp(self, matchBlock); |
|
| 2171 | + | return; |
|
| 2172 | + | } |
|
| 2168 | 2173 | let isNil = pattern.value == ast::NodeValue::Nil; |
|
| 2169 | 2174 | ||
| 2170 | 2175 | match subject.kind { |
|
| 2171 | 2176 | case MatchSubjectKind::OptionalPtr if isNil => { |
|
| 2172 | 2177 | // Null pointer optimization: branch on the data pointer being null. |
| 2218 | 2223 | let tagReg = tvalTagReg(self, base); |
|
| 2219 | 2224 | ||
| 2220 | 2225 | try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough); |
|
| 2221 | 2226 | } |
|
| 2222 | 2227 | } |
|
| 2223 | - | else => { // Scalar comparison. |
|
| 2228 | + | else => { // Value comparison. |
|
| 2224 | 2229 | assert not isNil; |
|
| 2225 | 2230 | let pattVal = try lowerExpr(self, pattern); |
|
| 2226 | - | try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough); |
|
| 2231 | + | if isAggregateType(subject.type) { |
|
| 2232 | + | // Aggregate types (slices, records, etc.) need structural |
|
| 2233 | + | // comparison via lowerAggregateEq rather than scalar compare. |
|
| 2234 | + | let subjectReg = emitValToReg(self, subject.val); |
|
| 2235 | + | let pattReg = emitValToReg(self, pattVal); |
|
| 2236 | + | let eq = try lowerAggregateEq(self, subject.type, subjectReg, pattReg, 0); |
|
| 2237 | + | let eqReg = emitValToReg(self, eq); |
|
| 2238 | + | try emitBr(self, eqReg, matchBlock, fallthrough); |
|
| 2239 | + | } else { |
|
| 2240 | + | try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough); |
|
| 2241 | + | } |
|
| 2227 | 2242 | } |
|
| 2228 | 2243 | } |
|
| 2229 | 2244 | } |
|
| 2230 | 2245 | ||
| 2231 | 2246 | /// Emit branches for multiple patterns. The first pattern that matches |
| 3042 | 3057 | // Declare the variable in the current block's scope. |
|
| 3043 | 3058 | return newVar(self, name, ilType(self.low, subject.bindType), mutable, subject.val); |
|
| 3044 | 3059 | } |
|
| 3045 | 3060 | ||
| 3046 | 3061 | /// Bind variables from inside case patterns (union variants, records, slices). |
|
| 3047 | - | fn bindPatternVariables(self: *mut FnLowerer, subject: *MatchSubject, patterns: *mut [*ast::Node]) throws (LowerError) { |
|
| 3062 | + | /// `failBlock` is passed when nested patterns may require additional tests |
|
| 3063 | + | /// that branch on mismatch (e.g. nested union variant tests). |
|
| 3064 | + | fn bindPatternVariables(self: *mut FnLowerer, subject: *MatchSubject, patterns: *mut [*ast::Node], failBlock: BlockId) throws (LowerError) { |
|
| 3048 | 3065 | for pattern in patterns { |
|
| 3049 | 3066 | ||
| 3050 | 3067 | // Handle simple variant patterns like `Variant(x)`. |
|
| 3051 | - | // TODO: Why is this handled differently from the cases below? |
|
| 3052 | 3068 | if let arg = resolver::variantPatternBinding(self.low.resolver, pattern) { |
|
| 3053 | - | if let case ast::NodeValue::Ident(name) = arg.value { |
|
| 3054 | - | if let bindType = resolver::typeFor(self.low.resolver, arg) { |
|
| 3055 | - | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
|
| 3056 | - | else panic "bindPatternVariables: expected union subject"; |
|
| 3057 | - | let valOffset = unionInfo.valOffset as i32; |
|
| 3058 | - | ||
| 3059 | - | try bindPayloadVariable(self, name, subject.val, bindType, subject.by, valOffset, false); |
|
| 3069 | + | if let bindType = resolver::typeFor(self.low.resolver, arg) { |
|
| 3070 | + | let case MatchSubjectKind::Union(unionInfo) = subject.kind |
|
| 3071 | + | else panic "bindPatternVariables: expected union subject"; |
|
| 3072 | + | let valOffset = unionInfo.valOffset as i32; |
|
| 3073 | + | ||
| 3074 | + | match arg.value { |
|
| 3075 | + | case ast::NodeValue::Ident(name) => { |
|
| 3076 | + | try bindPayloadVariable(self, name, subject.val, bindType, subject.by, valOffset, false); |
|
| 3077 | + | } |
|
| 3078 | + | case ast::NodeValue::Placeholder => {} |
|
| 3079 | + | else => { |
|
| 3080 | + | // Nested pattern inside a variant call, e.g. `Variant(Inner { x, y })`. |
|
| 3081 | + | let base = emitValToReg(self, subject.val); |
|
| 3082 | + | let payloadBase = emitPtrOffset(self, base, valOffset); |
|
| 3083 | + | let fieldInfo = resolver::RecordField { |
|
| 3084 | + | name: nil, |
|
| 3085 | + | fieldType: bindType, |
|
| 3086 | + | offset: 0, |
|
| 3087 | + | }; |
|
| 3088 | + | try bindFieldVariable(self, arg, payloadBase, fieldInfo, subject.by, failBlock); |
|
| 3089 | + | } |
|
| 3060 | 3090 | } |
|
| 3061 | 3091 | } |
|
| 3062 | 3092 | } |
|
| 3063 | 3093 | match pattern.value { |
|
| 3064 | 3094 | // Compound variant patterns like `Variant { a, b }`. |
|
| 3065 | 3095 | case ast::NodeValue::RecordLit(lit) => |
|
| 3066 | - | try bindRecordPatternFields(self, subject, pattern, lit), |
|
| 3096 | + | try bindRecordPatternFields(self, subject, pattern, lit, failBlock), |
|
| 3067 | 3097 | // Array patterns like `[a, b, c]`. |
|
| 3068 | - | case ast::NodeValue::ArrayLit(_) => |
|
| 3069 | - | panic "bindPatternVariables: array patterns not yet implemented", |
|
| 3098 | + | case ast::NodeValue::ArrayLit(items) => |
|
| 3099 | + | try bindArrayPatternElements(self, subject, items, failBlock), |
|
| 3070 | 3100 | // Literals, wildcards, identifiers: no bindings needed. |
|
| 3071 | 3101 | else => {}, |
|
| 3072 | 3102 | } |
|
| 3073 | 3103 | } |
|
| 3074 | 3104 | } |
|
| 3075 | 3105 | ||
| 3076 | - | /// Bind variables from a record literal pattern (e.g., `Variant { op, a, b }`). |
|
| 3077 | - | // TODO: Currently only handles union variant payloads. Standalone record patterns are not |
|
| 3078 | - | // yet supported. |
|
| 3079 | - | fn bindRecordPatternFields(self: *mut FnLowerer, subject: *MatchSubject, pattern: *ast::Node, lit: ast::RecordLit) throws (LowerError) { |
|
| 3106 | + | /// Bind variables from an array literal pattern (e.g., `[a, 1, c]`). |
|
| 3107 | + | /// Each element is either bound as a variable, skipped (placeholder), or |
|
| 3108 | + | /// tested against the subject element, branching to `failBlock` on mismatch. |
|
| 3109 | + | fn bindArrayPatternElements( |
|
| 3110 | + | self: *mut FnLowerer, |
|
| 3111 | + | subject: *MatchSubject, |
|
| 3112 | + | items: *mut [*ast::Node], |
|
| 3113 | + | failBlock: BlockId |
|
| 3114 | + | ) throws (LowerError) { |
|
| 3115 | + | let case resolver::Type::Array(arrInfo) = subject.type |
|
| 3116 | + | else throw LowerError::ExpectedSliceOrArray; |
|
| 3117 | + | ||
| 3118 | + | let elemTy = *arrInfo.item; |
|
| 3119 | + | let elemLayout = resolver::getTypeLayout(elemTy); |
|
| 3120 | + | let stride = elemLayout.size as i32; |
|
| 3121 | + | let base = emitValToReg(self, subject.val); |
|
| 3122 | + | ||
| 3123 | + | for elem, i in items { |
|
| 3124 | + | let fieldInfo = resolver::RecordField { |
|
| 3125 | + | name: nil, |
|
| 3126 | + | fieldType: elemTy, |
|
| 3127 | + | offset: (i as i32) * stride, |
|
| 3128 | + | }; |
|
| 3129 | + | try bindFieldVariable(self, elem, base, fieldInfo, subject.by, failBlock); |
|
| 3130 | + | } |
|
| 3131 | + | } |
|
| 3132 | + | ||
| 3133 | + | fn bindRecordPatternFields(self: *mut FnLowerer, subject: *MatchSubject, pattern: *ast::Node, lit: ast::RecordLit, failBlock: BlockId) throws (LowerError) { |
|
| 3080 | 3134 | // No fields to bind (e.g., `{ .. }`). |
|
| 3081 | 3135 | if lit.fields.len == 0 { |
|
| 3082 | 3136 | return; |
|
| 3083 | 3137 | } |
|
| 3084 | 3138 | // Get the union type info from the subject. |
| 3098 | 3152 | // Get the payload base pointer which points to the record within the tagged union. |
|
| 3099 | 3153 | let base = emitValToReg(self, subject.val); |
|
| 3100 | 3154 | let valOffset = unionInfo.valOffset as i32; |
|
| 3101 | 3155 | let payloadBase = emitPtrOffset(self, base, valOffset); |
|
| 3102 | 3156 | ||
| 3103 | - | // Bind each field. |
|
| 3157 | + | try bindNestedRecordFields(self, payloadBase, lit, recInfo, subject.by, failBlock); |
|
| 3158 | + | } |
|
| 3159 | + | ||
| 3160 | + | /// Emit a field read from a base pointer. |
|
| 3161 | + | fn emitFieldRead(self: *mut FnLowerer, base: il::Reg, fieldInfo: resolver::RecordField, matchBy: resolver::MatchBy) -> il::Val { |
|
| 3162 | + | match matchBy { |
|
| 3163 | + | case resolver::MatchBy::Value => { |
|
| 3164 | + | return emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType); |
|
| 3165 | + | } |
|
| 3166 | + | case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => { |
|
| 3167 | + | return il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset)); |
|
| 3168 | + | } |
|
| 3169 | + | } |
|
| 3170 | + | } |
|
| 3171 | + | ||
| 3172 | + | /// Bind a single record field to a pattern variable, with support for nested |
|
| 3173 | + | /// pattern tests that branch to `failBlock` on mismatch. |
|
| 3174 | + | fn bindFieldVariable( |
|
| 3175 | + | self: *mut FnLowerer, |
|
| 3176 | + | binding: *ast::Node, |
|
| 3177 | + | base: il::Reg, |
|
| 3178 | + | fieldInfo: resolver::RecordField, |
|
| 3179 | + | matchBy: resolver::MatchBy, |
|
| 3180 | + | failBlock: BlockId |
|
| 3181 | + | ) throws (LowerError) { |
|
| 3182 | + | match binding.value { |
|
| 3183 | + | case ast::NodeValue::Ident(name) => { |
|
| 3184 | + | let val = emitFieldRead(self, base, fieldInfo, matchBy); |
|
| 3185 | + | let _ = newVar(self, name, ilType(self.low, fieldInfo.fieldType), false, val); |
|
| 3186 | + | } |
|
| 3187 | + | case ast::NodeValue::Placeholder => {} |
|
| 3188 | + | case ast::NodeValue::RecordLit(lit) => { |
|
| 3189 | + | // Check if this record literal is a union variant pattern. |
|
| 3190 | + | if let keyNode = resolver::patternVariantKeyNode(binding) { |
|
| 3191 | + | if let case resolver::NodeExtra::UnionVariant { .. } = resolver::nodeData(self.low.resolver, keyNode).extra { |
|
| 3192 | + | try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock); |
|
| 3193 | + | return; |
|
| 3194 | + | } |
|
| 3195 | + | } |
|
| 3196 | + | // Plain nested record destructuring pattern. |
|
| 3197 | + | let recInfo = resolver::getRecord(fieldInfo.fieldType) |
|
| 3198 | + | else throw LowerError::ExpectedRecord; |
|
| 3199 | + | let nestedBase = emitPtrOffset(self, base, fieldInfo.offset); |
|
| 3200 | + | try bindNestedRecordFields(self, nestedBase, lit, recInfo, matchBy, failBlock); |
|
| 3201 | + | } |
|
| 3202 | + | else => { |
|
| 3203 | + | // Nested pattern requiring a test (union variant scope access, literal, etc). |
|
| 3204 | + | try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock); |
|
| 3205 | + | } |
|
| 3206 | + | } |
|
| 3207 | + | } |
|
| 3208 | + | ||
| 3209 | + | /// Emit a nested pattern test for a record field value, branching to |
|
| 3210 | + | /// `failBlock` if the pattern does not match. On success, continues in |
|
| 3211 | + | /// a fresh block and binds any nested variables. |
|
| 3212 | + | fn emitNestedFieldTest( |
|
| 3213 | + | self: *mut FnLowerer, |
|
| 3214 | + | pattern: *ast::Node, |
|
| 3215 | + | base: il::Reg, |
|
| 3216 | + | fieldInfo: resolver::RecordField, |
|
| 3217 | + | matchBy: resolver::MatchBy, |
|
| 3218 | + | failBlock: BlockId |
|
| 3219 | + | ) throws (LowerError) { |
|
| 3220 | + | let fieldType = fieldInfo.fieldType; |
|
| 3221 | + | let fieldPtr = emitPtrOffset(self, base, fieldInfo.offset); |
|
| 3222 | + | ||
| 3223 | + | // Build a MatchSubject for the nested field. |
|
| 3224 | + | let ilTy = ilType(self.low, fieldType); |
|
| 3225 | + | let kind = matchSubjectKind(fieldType); |
|
| 3226 | + | ||
| 3227 | + | // For aggregate subjects, the val must be a pointer (Reg). |
|
| 3228 | + | let mut val = il::Val::Reg(fieldPtr); |
|
| 3229 | + | if not isAggregateType(fieldType) { |
|
| 3230 | + | val = emitRead(self, base, fieldInfo.offset, fieldType); |
|
| 3231 | + | } |
|
| 3232 | + | ||
| 3233 | + | let nestedSubject = MatchSubject { |
|
| 3234 | + | val, |
|
| 3235 | + | type: fieldType, |
|
| 3236 | + | ilType: ilTy, |
|
| 3237 | + | bindType: fieldType, |
|
| 3238 | + | kind, |
|
| 3239 | + | by: matchBy, |
|
| 3240 | + | }; |
|
| 3241 | + | ||
| 3242 | + | // Emit the pattern test: on success jump to continueBlock, on fail to failBlock. |
|
| 3243 | + | let continueBlock = try createBlock(self, "nest"); |
|
| 3244 | + | try emitPatternMatch(self, &nestedSubject, pattern, continueBlock, failBlock); |
|
| 3245 | + | try switchToAndSeal(self, continueBlock); |
|
| 3246 | + | ||
| 3247 | + | // After the test succeeds, bind any nested variables. |
|
| 3248 | + | let patterns: *mut [*ast::Node] = &mut [pattern]; |
|
| 3249 | + | try bindPatternVariables(self, &nestedSubject, patterns, failBlock); |
|
| 3250 | + | } |
|
| 3251 | + | ||
| 3252 | + | /// Bind variables from a nested record literal pattern. |
|
| 3253 | + | fn bindNestedRecordFields( |
|
| 3254 | + | self: *mut FnLowerer, |
|
| 3255 | + | base: il::Reg, |
|
| 3256 | + | lit: ast::RecordLit, |
|
| 3257 | + | recInfo: resolver::RecordType, |
|
| 3258 | + | matchBy: resolver::MatchBy, |
|
| 3259 | + | failBlock: BlockId |
|
| 3260 | + | ) throws (LowerError) { |
|
| 3104 | 3261 | for fieldNode in lit.fields { |
|
| 3105 | 3262 | let case ast::NodeValue::RecordLitField(field) = fieldNode.value else { |
|
| 3106 | 3263 | throw LowerError::UnexpectedNodeValue(fieldNode); |
|
| 3107 | 3264 | }; |
|
| 3108 | 3265 | let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, fieldNode) |
| 3110 | 3267 | if fieldIdx >= recInfo.fields.len { |
|
| 3111 | 3268 | throw LowerError::MissingMetadata; |
|
| 3112 | 3269 | } |
|
| 3113 | 3270 | let fieldInfo = recInfo.fields[fieldIdx]; |
|
| 3114 | 3271 | ||
| 3115 | - | bindFieldVariable(self, field.value, payloadBase, fieldInfo, subject.by); |
|
| 3272 | + | try bindFieldVariable(self, field.value, base, fieldInfo, matchBy, failBlock); |
|
| 3116 | 3273 | } |
|
| 3117 | 3274 | } |
|
| 3118 | 3275 | ||
| 3119 | - | /// Bind a single record field to a pattern variable. |
|
| 3120 | - | fn bindFieldVariable( |
|
| 3121 | - | self: *mut FnLowerer, |
|
| 3122 | - | binding: *ast::Node, |
|
| 3123 | - | base: il::Reg, |
|
| 3124 | - | fieldInfo: resolver::RecordField, |
|
| 3125 | - | matchBy: resolver::MatchBy |
|
| 3126 | - | ) { |
|
| 3127 | - | let case ast::NodeValue::Ident(name) = binding.value else { |
|
| 3128 | - | return; // Not an identifier binding. |
|
| 3129 | - | }; |
|
| 3130 | - | let mut val: il::Val = undefined; |
|
| 3131 | - | match matchBy { |
|
| 3132 | - | case resolver::MatchBy::Value => { |
|
| 3133 | - | val = emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType); |
|
| 3134 | - | }, |
|
| 3135 | - | case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => { |
|
| 3136 | - | val = il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset)); |
|
| 3137 | - | }, |
|
| 3138 | - | } |
|
| 3139 | - | let _ = newVar(self, name, ilType(self.low, fieldInfo.fieldType), false, val); |
|
| 3140 | - | } |
|
| 3141 | - | ||
| 3142 | 3276 | /// Lower function body to a list of basic blocks. |
|
| 3143 | 3277 | fn lowerFnBody(self: *mut FnLowerer, body: *ast::Node) -> *[il::Block] throws (LowerError) { |
|
| 3144 | 3278 | // Create and switch to entry block. |
|
| 3145 | 3279 | let entry = try createBlock(self, "entry"); |
|
| 3146 | 3280 | self.entryBlock = entry; |
| 3315 | 3449 | // Body block: where the case body lives. |
|
| 3316 | 3450 | let mut bodyLabel = "case"; |
|
| 3317 | 3451 | if prong.arm == ast::ProngArm::Else { |
|
| 3318 | 3452 | bodyLabel = "else"; |
|
| 3319 | 3453 | } |
|
| 3320 | - | let bodyBlock = try createBlock(self, bodyLabel); |
|
| 3454 | + | let mut bodyBlock = try createBlock(self, bodyLabel); |
|
| 3321 | 3455 | if not hasGuard { |
|
| 3322 | 3456 | entryBlock = bodyBlock; |
|
| 3323 | 3457 | } |
|
| 3324 | 3458 | // Fallthrough block: jumped to when pattern or guard fails. |
|
| 3325 | 3459 | let nextArm = try createBlock(self, "arm"); |
| 3335 | 3469 | } |
|
| 3336 | 3470 | // Switch to entry block, where any variable bindings need to be created. |
|
| 3337 | 3471 | try switchToAndSeal(self, entryBlock); |
|
| 3338 | 3472 | ||
| 3339 | 3473 | // Bind pattern variables after successful match. Note that the guard |
|
| 3340 | - | // has not been evaluated yet. |
|
| 3474 | + | // has not been evaluated yet. Nested patterns may emit additional |
|
| 3475 | + | // tests that branch to `nextArm` on failure, switching the current |
|
| 3476 | + | // block. |
|
| 3341 | 3477 | match prong.arm { |
|
| 3342 | 3478 | case ast::ProngArm::Binding(pat) => |
|
| 3343 | 3479 | try bindMatchVariable(self, &subject, pat, false), |
|
| 3344 | 3480 | case ast::ProngArm::Case(patterns) => |
|
| 3345 | - | try bindPatternVariables(self, &subject, patterns), |
|
| 3481 | + | try bindPatternVariables(self, &subject, patterns, nextArm), |
|
| 3346 | 3482 | else => {}, |
|
| 3347 | 3483 | } |
|
| 3348 | 3484 | ||
| 3349 | 3485 | // Evaluate guard if present; can still fail to next arm. |
|
| 3350 | 3486 | if let g = prong.guard { |
|
| 3351 | 3487 | try emitCondBranch(self, g, bodyBlock, nextArm); |
|
| 3488 | + | } else if currentBlock(self).n != bodyBlock.n { |
|
| 3489 | + | // Nested tests changed the current block. Create a new body block |
|
| 3490 | + | // after the nest blocks to maintain RPO ordering, and jump to it. |
|
| 3491 | + | bodyBlock = try createBlock(self, bodyLabel); |
|
| 3492 | + | try emitJmp(self, bodyBlock); |
|
| 3352 | 3493 | } |
|
| 3353 | 3494 | // Lower prong body and jump to merge if unterminated. |
|
| 3354 | 3495 | try switchToAndSeal(self, bodyBlock); |
|
| 3355 | 3496 | try lowerNode(self, prong.body); |
|
| 3356 | 3497 | try emitMergeIfUnterminated(self, &mut mergeBlock); |
| 3430 | 3571 | case ast::PatternKind::Case => { |
|
| 3431 | 3572 | let patterns: *mut [*ast::Node] = &mut [pat.pattern]; |
|
| 3432 | 3573 | // Jump to `targetBlock` if the pattern matches, `failBlock` otherwise. |
|
| 3433 | 3574 | try emitPatternMatches(self, subject, patterns, targetBlock, failBlock); |
|
| 3434 | 3575 | try switchToAndSeal(self, targetBlock); |
|
| 3435 | - | // Bind any variables inside the pattern. |
|
| 3436 | - | try bindPatternVariables(self, subject, patterns); |
|
| 3576 | + | // Bind any variables inside the pattern. Nested patterns may |
|
| 3577 | + | // emit additional tests that branch to `failBlock`, switching |
|
| 3578 | + | // the current block. |
|
| 3579 | + | try bindPatternVariables(self, subject, patterns, failBlock); |
|
| 3437 | 3580 | } |
|
| 3438 | 3581 | case ast::PatternKind::Binding => { |
|
| 3439 | 3582 | // Jump to `targetBlock` if there is a value present, `failBlock` otherwise. |
|
| 3440 | 3583 | try emitBindingTest(self, subject, targetBlock, failBlock); |
|
| 3441 | 3584 | try switchToAndSeal(self, targetBlock); |
| 3445 | 3588 | } |
|
| 3446 | 3589 | // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`. |
|
| 3447 | 3590 | if let g = pat.guard { |
|
| 3448 | 3591 | try emitCondBranch(self, g, *successBlock, failBlock); |
|
| 3449 | 3592 | try switchToAndSeal(self, *successBlock); |
|
| 3593 | + | } else if currentBlock(self).n != targetBlock.n { |
|
| 3594 | + | // Nested tests changed the current block. Create a new success block |
|
| 3595 | + | // after the nest blocks to maintain RPO ordering, and jump to it. |
|
| 3596 | + | *successBlock = try createBlock(self, successLabel); |
|
| 3597 | + | try emitJmp(self, *successBlock); |
|
| 3598 | + | try switchToAndSeal(self, *successBlock); |
|
| 3450 | 3599 | } |
|
| 3451 | 3600 | } |
|
| 3452 | 3601 | ||
| 3453 | 3602 | /// Lower a `let-else` statement. |
|
| 3454 | 3603 | fn lowerLetElse(self: *mut FnLowerer, letElse: ast::LetElse) throws (LowerError) { |
lib/std/lang/lower/tests/match.nested.iflet.rad
added
+18 -0
| 1 | + | /// Nested record pattern in if-let-case. |
|
| 2 | + | record Inner { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | union Outer { |
|
| 8 | + | A { inner: Inner, z: i32 }, |
|
| 9 | + | B, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// If-let-case with nested record destructuring. |
|
| 13 | + | fn extract(o: Outer) -> i32 { |
|
| 14 | + | if let case Outer::A { inner: Inner { x, y }, z } = o { |
|
| 15 | + | return x + y + z; |
|
| 16 | + | } |
|
| 17 | + | return 0; |
|
| 18 | + | } |
lib/std/lang/lower/tests/match.nested.iflet.ril
added
+19 -0
| 1 | + | fn w32 $extract(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 16 4; |
|
| 4 | + | blit %1 %0 16; |
|
| 5 | + | load w8 %2 %1 0; |
|
| 6 | + | br.eq w8 %2 0 @then1 @else2; |
|
| 7 | + | @then1 |
|
| 8 | + | add w64 %3 %1 4; |
|
| 9 | + | sload w32 %4 %3 0; |
|
| 10 | + | sload w32 %5 %3 4; |
|
| 11 | + | sload w32 %6 %3 8; |
|
| 12 | + | add w32 %7 %4 %5; |
|
| 13 | + | add w32 %8 %7 %6; |
|
| 14 | + | ret %8; |
|
| 15 | + | @else2 |
|
| 16 | + | jmp @merge3; |
|
| 17 | + | @merge3 |
|
| 18 | + | ret 0; |
|
| 19 | + | } |
lib/std/lang/lower/tests/match.nested.pattern.rad
added
+18 -0
| 1 | + | /// Nested pattern matching in case arms. |
|
| 2 | + | record Inner { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | union Outer { |
|
| 8 | + | A { inner: Inner, z: i32 }, |
|
| 9 | + | B, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Destructure a nested record inside a union variant pattern. |
|
| 13 | + | fn nested(o: Outer) -> i32 { |
|
| 14 | + | match o { |
|
| 15 | + | case Outer::A { inner: Inner { x, y }, z } => return x + y + z, |
|
| 16 | + | case Outer::B => return 0, |
|
| 17 | + | } |
|
| 18 | + | } |
lib/std/lang/lower/tests/match.nested.pattern.ril
added
+21 -0
| 1 | + | fn w32 $nested(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 16 4; |
|
| 4 | + | blit %1 %0 16; |
|
| 5 | + | jmp @arm1; |
|
| 6 | + | @arm1 |
|
| 7 | + | load w8 %2 %1 0; |
|
| 8 | + | br.eq w8 %2 0 @case2 @arm3; |
|
| 9 | + | @case2 |
|
| 10 | + | add w64 %3 %1 4; |
|
| 11 | + | sload w32 %4 %3 0; |
|
| 12 | + | sload w32 %5 %3 4; |
|
| 13 | + | sload w32 %6 %3 8; |
|
| 14 | + | add w32 %7 %4 %5; |
|
| 15 | + | add w32 %8 %7 %6; |
|
| 16 | + | ret %8; |
|
| 17 | + | @arm3 |
|
| 18 | + | jmp @case4; |
|
| 19 | + | @case4 |
|
| 20 | + | ret 0; |
|
| 21 | + | } |
lib/std/lang/lower/tests/match.nested.record.rad
added
+18 -0
| 1 | + | /// Nested record destructuring inside a union variant. |
|
| 2 | + | record Point { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | union Shape { |
|
| 8 | + | Circle { center: Point, radius: i32 }, |
|
| 9 | + | None, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Destructure nested record fields in both arms. |
|
| 13 | + | fn getX(s: Shape) -> i32 { |
|
| 14 | + | match s { |
|
| 15 | + | case Shape::Circle { center: Point { x, .. }, .. } => return x, |
|
| 16 | + | case Shape::None => return 0, |
|
| 17 | + | } |
|
| 18 | + | } |
lib/std/lang/lower/tests/match.nested.record.ril
added
+17 -0
| 1 | + | fn w32 $getX(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 16 4; |
|
| 4 | + | blit %1 %0 16; |
|
| 5 | + | jmp @arm1; |
|
| 6 | + | @arm1 |
|
| 7 | + | load w8 %2 %1 0; |
|
| 8 | + | br.eq w8 %2 0 @case2 @arm3; |
|
| 9 | + | @case2 |
|
| 10 | + | add w64 %3 %1 4; |
|
| 11 | + | sload w32 %4 %3 0; |
|
| 12 | + | ret %4; |
|
| 13 | + | @arm3 |
|
| 14 | + | jmp @case4; |
|
| 15 | + | @case4 |
|
| 16 | + | ret 0; |
|
| 17 | + | } |
lib/std/lang/lower/tests/match.nested.union.rad
added
+16 -0
| 1 | + | /// Nested union variant pattern inside a record field. |
|
| 2 | + | union Dir { N, S } |
|
| 3 | + | ||
| 4 | + | union Cmd { |
|
| 5 | + | Move { dir: Dir, speed: i32 }, |
|
| 6 | + | Stop, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | /// Match nested union variants inside record fields. |
|
| 10 | + | fn dispatch(c: Cmd) -> i32 { |
|
| 11 | + | match c { |
|
| 12 | + | case Cmd::Move { dir: Dir::N, speed } => return speed, |
|
| 13 | + | case Cmd::Move { dir: Dir::S, speed } => return 0 - speed, |
|
| 14 | + | else => return 0, |
|
| 15 | + | } |
|
| 16 | + | } |
lib/std/lang/lower/tests/match.nested.union.ril
added
+35 -0
| 1 | + | fn w32 $dispatch(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 12 4; |
|
| 4 | + | blit %1 %0 12; |
|
| 5 | + | jmp @arm1; |
|
| 6 | + | @arm1 |
|
| 7 | + | load w8 %2 %1 0; |
|
| 8 | + | br.eq w8 %2 0 @case2 @arm3; |
|
| 9 | + | @case2 |
|
| 10 | + | add w64 %3 %1 4; |
|
| 11 | + | load w8 %4 %3 0; |
|
| 12 | + | br.eq w8 %4 0 @nest4 @arm3; |
|
| 13 | + | @arm3 |
|
| 14 | + | load w8 %6 %1 0; |
|
| 15 | + | br.eq w8 %6 0 @case6 @arm7; |
|
| 16 | + | @nest4 |
|
| 17 | + | sload w32 %5 %3 4; |
|
| 18 | + | jmp @case5; |
|
| 19 | + | @case5 |
|
| 20 | + | ret %5; |
|
| 21 | + | @case6 |
|
| 22 | + | add w64 %7 %1 4; |
|
| 23 | + | load w8 %8 %7 0; |
|
| 24 | + | br.eq w8 %8 1 @nest8 @arm7; |
|
| 25 | + | @arm7 |
|
| 26 | + | jmp @else10; |
|
| 27 | + | @nest8 |
|
| 28 | + | sload w32 %9 %7 4; |
|
| 29 | + | jmp @case9; |
|
| 30 | + | @case9 |
|
| 31 | + | sub w32 %10 0 %9; |
|
| 32 | + | ret %10; |
|
| 33 | + | @else10 |
|
| 34 | + | ret 0; |
|
| 35 | + | } |
lib/std/lang/resolver.rad
+152 -13
| 382 | 382 | Char(u8), |
|
| 383 | 383 | String(*[u8]), |
|
| 384 | 384 | Int(ConstInt), |
|
| 385 | 385 | } |
|
| 386 | 386 | ||
| 387 | + | /// Check whether a constant value is a scalar that can be used in a |
|
| 388 | + | /// switch instruction (Bool, Char, or Int but not String). |
|
| 389 | + | pub fn isScalarConst(cv: ?ConstValue) -> bool { |
|
| 390 | + | if let c = cv { |
|
| 391 | + | match c { |
|
| 392 | + | case ConstValue::Bool(_), ConstValue::Char(_), ConstValue::Int(_) => return true, |
|
| 393 | + | else => return false, |
|
| 394 | + | } |
|
| 395 | + | } |
|
| 396 | + | return false; |
|
| 397 | + | } |
|
| 398 | + | ||
| 387 | 399 | /// Integer range metadata for primitive integer types. |
|
| 388 | 400 | union IntegerRange { |
|
| 389 | 401 | Signed { |
|
| 390 | 402 | bits: u8, |
|
| 391 | 403 | min: i64, |
| 3893 | 3905 | } else => {} |
|
| 3894 | 3906 | } |
|
| 3895 | 3907 | } |
|
| 3896 | 3908 | case Type::Array(arrayInfo) => { |
|
| 3897 | 3909 | if let case ast::NodeValue::ArrayLit(items) = pattern.value { |
|
| 3910 | + | if items.len as u32 != arrayInfo.length { |
|
| 3911 | + | throw emitError(self, pattern, ErrorKind::RecordFieldCountMismatch( |
|
| 3912 | + | CountMismatch { expected: arrayInfo.length, actual: items.len as u32 } |
|
| 3913 | + | )); |
|
| 3914 | + | } |
|
| 3898 | 3915 | let elemTy = *arrayInfo.item; |
|
| 3899 | 3916 | for item in items { |
|
| 3900 | 3917 | try resolveCasePattern(self, item, elemTy, IdentMode::Bind, matchBy); |
|
| 3901 | 3918 | } |
|
| 3919 | + | setNodeType(self, pattern, scrutineeTy); |
|
| 3902 | 3920 | return; |
|
| 3903 | 3921 | } |
|
| 3904 | 3922 | } else => {} |
|
| 3905 | 3923 | } |
|
| 3906 | 3924 | // Handle non-binding patterns (literals, placeholders) and bindings. |
| 4017 | 4035 | try visitOptional(self, forStmt.elseBranch, Type::Void); |
|
| 4018 | 4036 | ||
| 4019 | 4037 | return setNodeType(self, node, Type::Void); |
|
| 4020 | 4038 | } |
|
| 4021 | 4039 | ||
| 4022 | - | /// Check whether any pattern in a case prong is a wildcard `_`. |
|
| 4040 | + | /// Get the node within a pattern that carries the `UnionVariant` extra. |
|
| 4041 | + | /// For `ScopeAccess` it is the pattern itself, for `RecordLit` it is the |
|
| 4042 | + | /// type name, and for `Call` it is the callee. |
|
| 4043 | + | pub fn patternVariantKeyNode(pattern: *ast::Node) -> ?*ast::Node { |
|
| 4044 | + | match pattern.value { |
|
| 4045 | + | case ast::NodeValue::ScopeAccess(_) => return pattern, |
|
| 4046 | + | case ast::NodeValue::RecordLit(lit) => return lit.typeName, |
|
| 4047 | + | case ast::NodeValue::Call(call) => return call.callee, |
|
| 4048 | + | else => return nil, |
|
| 4049 | + | } |
|
| 4050 | + | } |
|
| 4051 | + | ||
| 4052 | + | /// Get the i-th sub-pattern element from a compound pattern. |
|
| 4053 | + | /// For `RecordLit` this is the i-th field's value; for `Call` it is the |
|
| 4054 | + | /// i-th argument. |
|
| 4055 | + | fn patternSubElement(pattern: *ast::Node, idx: u32) -> ?*ast::Node { |
|
| 4056 | + | match pattern.value { |
|
| 4057 | + | case ast::NodeValue::RecordLit(lit) => { |
|
| 4058 | + | if idx < lit.fields.len as u32 { |
|
| 4059 | + | if let case ast::NodeValue::RecordLitField(field) = lit.fields[idx].value { |
|
| 4060 | + | return field.value; |
|
| 4061 | + | } |
|
| 4062 | + | } |
|
| 4063 | + | } |
|
| 4064 | + | case ast::NodeValue::Call(call) => { |
|
| 4065 | + | if idx < call.args.len as u32 { |
|
| 4066 | + | return call.args[idx]; |
|
| 4067 | + | } |
|
| 4068 | + | } |
|
| 4069 | + | else => {} |
|
| 4070 | + | } |
|
| 4071 | + | return nil; |
|
| 4072 | + | } |
|
| 4073 | + | ||
| 4074 | + | /// Get the number of sub-pattern elements in a compound pattern. |
|
| 4075 | + | fn patternSubCount(pattern: *ast::Node) -> u32 { |
|
| 4076 | + | match pattern.value { |
|
| 4077 | + | case ast::NodeValue::RecordLit(lit) => return lit.fields.len as u32, |
|
| 4078 | + | case ast::NodeValue::Call(call) => return call.args.len as u32, |
|
| 4079 | + | else => return 0, |
|
| 4080 | + | } |
|
| 4081 | + | } |
|
| 4082 | + | ||
| 4083 | + | /// Check whether a pattern contains nested sub-patterns that further |
|
| 4084 | + | /// refine the match beyond the outer variant (e.g. nested union variant |
|
| 4085 | + | /// tests or literal comparisons). Used to allow the same outer variant |
|
| 4086 | + | /// to appear in multiple match arms. |
|
| 4087 | + | fn hasNestedRefiningPattern(self: *Resolver, pattern: *ast::Node) -> bool { |
|
| 4088 | + | for i in 0..patternSubCount(pattern) { |
|
| 4089 | + | if let sub = patternSubElement(pattern, i) { |
|
| 4090 | + | if isRefiningPattern(self, sub) { |
|
| 4091 | + | return true; |
|
| 4092 | + | } |
|
| 4093 | + | } |
|
| 4094 | + | } |
|
| 4095 | + | return false; |
|
| 4096 | + | } |
|
| 4097 | + | ||
| 4098 | + | /// Check whether a single pattern node is a refining pattern that tests |
|
| 4099 | + | /// a value rather than just binding it. Union variants, literals, and |
|
| 4100 | + | /// scope accesses are refining; identifiers, placeholders, and plain |
|
| 4101 | + | /// record destructurings are not. |
|
| 4102 | + | fn isRefiningPattern(self: *Resolver, pattern: *ast::Node) -> bool { |
|
| 4103 | + | match pattern.value { |
|
| 4104 | + | case ast::NodeValue::Ident(_), ast::NodeValue::Placeholder => return false, |
|
| 4105 | + | case ast::NodeValue::RecordLit(_), ast::NodeValue::Call(_) => { |
|
| 4106 | + | if let keyNode = patternVariantKeyNode(pattern) { |
|
| 4107 | + | if let case NodeExtra::UnionVariant { .. } = self.nodeData.entries[keyNode.id].extra { |
|
| 4108 | + | return true; |
|
| 4109 | + | } |
|
| 4110 | + | } |
|
| 4111 | + | // Plain record destructuring / non-variant call is not directly |
|
| 4112 | + | // refining; recurse to check sub-patterns. |
|
| 4113 | + | return hasNestedRefiningPattern(self, pattern); |
|
| 4114 | + | } |
|
| 4115 | + | case ast::NodeValue::ArrayLit(items) => { |
|
| 4116 | + | for item in items { |
|
| 4117 | + | if isRefiningPattern(self, item) { |
|
| 4118 | + | return true; |
|
| 4119 | + | } |
|
| 4120 | + | } |
|
| 4121 | + | return false; |
|
| 4122 | + | } |
|
| 4123 | + | case ast::NodeValue::ScopeAccess(_) => return true, |
|
| 4124 | + | else => return true, |
|
| 4125 | + | } |
|
| 4126 | + | } |
|
| 4127 | + | ||
| 4128 | + | /// Check whether any pattern in a case prong matches unconditionally. |
|
| 4129 | + | /// A plain `_` or an all-binding array pattern (e.g. `[x, y]`) qualifies. |
|
| 4130 | + | /// Note: top-level identifiers in `case` are comparisons, not bindings, |
|
| 4131 | + | /// so they do not count as wildcards. |
|
| 4023 | 4132 | fn hasWildcardPattern(patterns: *mut [*ast::Node]) -> bool { |
|
| 4024 | 4133 | for pattern in patterns { |
|
| 4025 | - | if let case ast::NodeValue::Placeholder = pattern.value { |
|
| 4026 | - | return true; |
|
| 4134 | + | match pattern.value { |
|
| 4135 | + | case ast::NodeValue::Placeholder => return true, |
|
| 4136 | + | case ast::NodeValue::ArrayLit(items) => { |
|
| 4137 | + | if isIrrefutableArrayPattern(items) { |
|
| 4138 | + | return true; |
|
| 4139 | + | } |
|
| 4140 | + | } |
|
| 4141 | + | else => {} |
|
| 4027 | 4142 | } |
|
| 4028 | 4143 | } |
|
| 4029 | 4144 | return false; |
|
| 4030 | 4145 | } |
|
| 4031 | 4146 | ||
| 4147 | + | /// Check whether all elements of an array pattern are irrefutable |
|
| 4148 | + | /// (identifiers, placeholders, or nested irrefutable arrays). |
|
| 4149 | + | /// Inside array patterns, identifiers are bindings, not comparisons. |
|
| 4150 | + | fn isIrrefutableArrayPattern(items: *mut [*ast::Node]) -> bool { |
|
| 4151 | + | for item in items { |
|
| 4152 | + | match item.value { |
|
| 4153 | + | case ast::NodeValue::Ident(_), ast::NodeValue::Placeholder => {} |
|
| 4154 | + | case ast::NodeValue::ArrayLit(inner) => { |
|
| 4155 | + | if not isIrrefutableArrayPattern(inner) { |
|
| 4156 | + | return false; |
|
| 4157 | + | } |
|
| 4158 | + | } |
|
| 4159 | + | else => return false, |
|
| 4160 | + | } |
|
| 4161 | + | } |
|
| 4162 | + | return true; |
|
| 4163 | + | } |
|
| 4164 | + | ||
| 4032 | 4165 | /// Analyze a match prong, checking for duplicate catch-alls. Returns the |
|
| 4033 | 4166 | /// unified match type. |
|
| 4034 | 4167 | fn resolveMatchProng( |
|
| 4035 | 4168 | self: *mut Resolver, |
|
| 4036 | 4169 | prongNode: *ast::Node, |
| 4187 | 4320 | else panic "resolveMatchUnion: expected match prong"; |
|
| 4188 | 4321 | ||
| 4189 | 4322 | matchType = try resolveMatchProng(self, prongNode, prong, subjectTy, &mut state, matchType, matchBy); |
|
| 4190 | 4323 | ||
| 4191 | 4324 | // Record covered variants. Guarded prongs don't count as covering. |
|
| 4325 | + | // Patterns with nested refining sub-patterns (e.g. matching different |
|
| 4326 | + | // inner union variants) don't count as duplicates or as fully covering. |
|
| 4192 | 4327 | if prong.guard == nil { |
|
| 4193 | 4328 | if let case ast::ProngArm::Case(patterns) = prong.arm { |
|
| 4194 | 4329 | for pattern in patterns { |
|
| 4195 | 4330 | if let case NodeExtra::UnionVariant { ordinal: ix, .. } = self.nodeData.entries[pattern.id].extra { |
|
| 4196 | - | if covered[ix] { |
|
| 4197 | - | throw emitError(self, pattern, ErrorKind::DuplicateMatchPattern); |
|
| 4331 | + | if not hasNestedRefiningPattern(self, pattern) { |
|
| 4332 | + | if covered[ix] { |
|
| 4333 | + | throw emitError(self, pattern, ErrorKind::DuplicateMatchPattern); |
|
| 4334 | + | } |
|
| 4335 | + | covered[ix] = true; |
|
| 4336 | + | coveredCount += 1; |
|
| 4198 | 4337 | } |
|
| 4199 | - | covered[ix] = true; |
|
| 4200 | - | coveredCount += 1; |
|
| 4201 | 4338 | } |
|
| 4202 | 4339 | } |
|
| 4203 | 4340 | } |
|
| 4204 | 4341 | } |
|
| 4205 | 4342 | } |
| 4250 | 4387 | } else { |
|
| 4251 | 4388 | hasFalse = true; |
|
| 4252 | 4389 | } |
|
| 4253 | 4390 | } |
|
| 4254 | 4391 | } |
|
| 4255 | - | // Constant case values are required for the match to be |
|
| 4256 | - | // considered static. |
|
| 4257 | - | if constValueEntry(self, p) == nil { |
|
| 4258 | - | state.isConst = false; |
|
| 4259 | - | } else { |
|
| 4392 | + | // Scalar constant patterns allow the match to be lowered |
|
| 4393 | + | // to a switch instruction. |
|
| 4394 | + | if isScalarConst(constValueEntry(self, p)) { |
|
| 4260 | 4395 | hasConstCase = true; |
|
| 4396 | + | } else { |
|
| 4397 | + | state.isConst = false; |
|
| 4261 | 4398 | } |
|
| 4262 | 4399 | } |
|
| 4263 | 4400 | } |
|
| 4264 | 4401 | } |
|
| 4265 | 4402 |
| 4421 | 4558 | } |
|
| 4422 | 4559 | case ast::NodeValue::Ident(_) => { |
|
| 4423 | 4560 | try bindValueIdent(self, binding, binding, bindTy, false, 0, 0); |
|
| 4424 | 4561 | } |
|
| 4425 | 4562 | else => { |
|
| 4426 | - | throw emitError(self, binding, ErrorKind::ExpectedIdentifier); |
|
| 4563 | + | // Nested pattern: recursively resolve (record destructuring, |
|
| 4564 | + | // union variant, scope access, call, literals, etc). |
|
| 4565 | + | try resolveCasePattern(self, binding, ty, IdentMode::Bind, matchBy); |
|
| 4427 | 4566 | } |
|
| 4428 | 4567 | } |
|
| 4429 | 4568 | } |
|
| 4430 | 4569 | ||
| 4431 | 4570 | /// Bind record pattern fields to variables in the current scope. |
lib/std/lang/resolver/tests.rad
+2 -2
| 4004 | 4004 | ||
| 4005 | 4005 | /// Test array pattern matching with element bindings. |
|
| 4006 | 4006 | /// Pattern syntax: `[x, y]` matches an array and binds elements. |
|
| 4007 | 4007 | @test fn testResolveMatchArrayPattern() throws (testing::TestError) { |
|
| 4008 | 4008 | let mut a = testResolver(); |
|
| 4009 | - | let program = "fn f(arr: [i32; 2]) -> i32 { match arr { case [x, y] => return x + y, else => return 0 } }"; |
|
| 4009 | + | let program = "fn f(arr: [i32; 2]) -> i32 { match arr { case [x, y] => return x + y } }"; |
|
| 4010 | 4010 | let result = try resolveProgramStr(&mut a, program); |
|
| 4011 | 4011 | try expectNoErrors(&result); |
|
| 4012 | 4012 | } |
|
| 4013 | 4013 | ||
| 4014 | 4014 | /// Test array pattern with placeholder elements. |
|
| 4015 | 4015 | /// Pattern syntax: `[_, y]` ignores first element. |
|
| 4016 | 4016 | @test fn testResolveMatchArrayPatternPlaceholder() throws (testing::TestError) { |
|
| 4017 | 4017 | let mut a = testResolver(); |
|
| 4018 | - | let program = "fn f(arr: [i32; 2]) -> i32 { match arr { case [_, y] => return y, else => return 0 } }"; |
|
| 4018 | + | let program = "fn f(arr: [i32; 2]) -> i32 { match arr { case [_, y] => return y } }"; |
|
| 4019 | 4019 | let result = try resolveProgramStr(&mut a, program); |
|
| 4020 | 4020 | try expectNoErrors(&result); |
|
| 4021 | 4021 | } |
|
| 4022 | 4022 | ||
| 4023 | 4023 | /// Test identifier pattern that binds the whole value. |