Add support for nested pattern matching

d9a090bf7eeaf7e20bd9c0bf70745fda4fd97634b9e3cd33ae4c79a0b5a19cea
Eg. `case A { b: B { c } }`
Alexis Sellier committed ago 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 +200 -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 need structural comparison rather than
2233 +
                // 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 +
2239 +
                try emitBr(self, eqReg, matchBlock, fallthrough);
2240 +
            } else {
2241 +
                try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough);
2242 +
            }
2227 2243
        }
2228 2244
    }
2229 2245
}
2230 2246
2231 2247
/// Emit branches for multiple patterns. The first pattern that matches
3042 3058
    // Declare the variable in the current block's scope.
3043 3059
    return newVar(self, name, ilType(self.low, subject.bindType), mutable, subject.val);
3044 3060
}
3045 3061
3046 3062
/// Bind variables from inside case patterns (union variants, records, slices).
3047 -
fn bindPatternVariables(self: *mut FnLowerer, subject: *MatchSubject, patterns: *mut [*ast::Node]) throws (LowerError) {
3063 +
/// `failBlock` is passed when nested patterns may require additional tests
3064 +
/// that branch on mismatch (e.g. nested union variant tests).
3065 +
fn bindPatternVariables(self: *mut FnLowerer, subject: *MatchSubject, patterns: *mut [*ast::Node], failBlock: BlockId) throws (LowerError) {
3048 3066
    for pattern in patterns {
3049 3067
3050 3068
        // Handle simple variant patterns like `Variant(x)`.
3051 -
        // TODO: Why is this handled differently from the cases below?
3052 3069
        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);
3070 +
            if let bindType = resolver::typeFor(self.low.resolver, arg) {
3071 +
                let case MatchSubjectKind::Union(unionInfo) = subject.kind
3072 +
                    else panic "bindPatternVariables: expected union subject";
3073 +
                let valOffset = unionInfo.valOffset as i32;
3074 +
3075 +
                match arg.value {
3076 +
                    case ast::NodeValue::Ident(name) => {
3077 +
                        try bindPayloadVariable(self, name, subject.val, bindType, subject.by, valOffset, false);
3078 +
                    }
3079 +
                    case ast::NodeValue::Placeholder => {}
3080 +
                    else => {
3081 +
                        // Nested pattern inside a variant call, e.g. `Variant(Inner { x, y })`.
3082 +
                        let base = emitValToReg(self, subject.val);
3083 +
                        let payloadBase = emitPtrOffset(self, base, valOffset);
3084 +
                        let fieldInfo = resolver::RecordField {
3085 +
                            name: nil,
3086 +
                            fieldType: bindType,
3087 +
                            offset: 0,
3088 +
                        };
3089 +
                        try bindFieldVariable(self, arg, payloadBase, fieldInfo, subject.by, failBlock);
3090 +
                    }
3060 3091
                }
3061 3092
            }
3062 3093
        }
3063 3094
        match pattern.value {
3064 3095
            // Compound variant patterns like `Variant { a, b }`.
3065 3096
            case ast::NodeValue::RecordLit(lit) =>
3066 -
                try bindRecordPatternFields(self, subject, pattern, lit),
3097 +
                try bindRecordPatternFields(self, subject, pattern, lit, failBlock),
3067 3098
            // Array patterns like `[a, b, c]`.
3068 -
            case ast::NodeValue::ArrayLit(_) =>
3069 -
                panic "bindPatternVariables: array patterns not yet implemented",
3099 +
            case ast::NodeValue::ArrayLit(items) =>
3100 +
                try bindArrayPatternElements(self, subject, items, failBlock),
3070 3101
            // Literals, wildcards, identifiers: no bindings needed.
3071 3102
            else => {},
3072 3103
        }
3073 3104
    }
3074 3105
}
3075 3106
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) {
3107 +
/// Bind variables from an array literal pattern (e.g., `[a, 1, c]`).
3108 +
/// Each element is either bound as a variable, skipped (placeholder), or
3109 +
/// tested against the subject element, branching to `failBlock` on mismatch.
3110 +
fn bindArrayPatternElements(
3111 +
    self: *mut FnLowerer,
3112 +
    subject: *MatchSubject,
3113 +
    items: *mut [*ast::Node],
3114 +
    failBlock: BlockId
3115 +
) throws (LowerError) {
3116 +
    let case resolver::Type::Array(arrInfo) = subject.type
3117 +
        else throw LowerError::ExpectedSliceOrArray;
3118 +
3119 +
    let elemTy = *arrInfo.item;
3120 +
    let elemLayout = resolver::getTypeLayout(elemTy);
3121 +
    let stride = elemLayout.size as i32;
3122 +
    let base = emitValToReg(self, subject.val);
3123 +
3124 +
    for elem, i in items {
3125 +
        let fieldInfo = resolver::RecordField {
3126 +
            name: nil,
3127 +
            fieldType: elemTy,
3128 +
            offset: (i as i32) * stride,
3129 +
        };
3130 +
        try bindFieldVariable(self, elem, base, fieldInfo, subject.by, failBlock);
3131 +
    }
3132 +
}
3133 +
3134 +
fn bindRecordPatternFields(self: *mut FnLowerer, subject: *MatchSubject, pattern: *ast::Node, lit: ast::RecordLit, failBlock: BlockId) throws (LowerError) {
3080 3135
    // No fields to bind (e.g., `{ .. }`).
3081 3136
    if lit.fields.len == 0 {
3082 3137
        return;
3083 3138
    }
3084 3139
    // Get the union type info from the subject.
3098 3153
    // Get the payload base pointer which points to the record within the tagged union.
3099 3154
    let base = emitValToReg(self, subject.val);
3100 3155
    let valOffset = unionInfo.valOffset as i32;
3101 3156
    let payloadBase = emitPtrOffset(self, base, valOffset);
3102 3157
3103 -
    // Bind each field.
3158 +
    try bindNestedRecordFields(self, payloadBase, lit, recInfo, subject.by, failBlock);
3159 +
}
3160 +
3161 +
/// Emit a field read from a base pointer.
3162 +
fn emitFieldRead(self: *mut FnLowerer, base: il::Reg, fieldInfo: resolver::RecordField, matchBy: resolver::MatchBy) -> il::Val {
3163 +
    match matchBy {
3164 +
        case resolver::MatchBy::Value => {
3165 +
            return emitRead(self, base, fieldInfo.offset, fieldInfo.fieldType);
3166 +
        }
3167 +
        case resolver::MatchBy::Ref, resolver::MatchBy::MutRef => {
3168 +
            return il::Val::Reg(emitPtrOffset(self, base, fieldInfo.offset));
3169 +
        }
3170 +
    }
3171 +
}
3172 +
3173 +
/// Bind a single record field to a pattern variable, with support for nested
3174 +
/// pattern tests that branch to `failBlock` on mismatch.
3175 +
fn bindFieldVariable(
3176 +
    self: *mut FnLowerer,
3177 +
    binding: *ast::Node,
3178 +
    base: il::Reg,
3179 +
    fieldInfo: resolver::RecordField,
3180 +
    matchBy: resolver::MatchBy,
3181 +
    failBlock: BlockId
3182 +
) throws (LowerError) {
3183 +
    match binding.value {
3184 +
        case ast::NodeValue::Ident(name) => {
3185 +
            let val = emitFieldRead(self, base, fieldInfo, matchBy);
3186 +
            newVar(self, name, ilType(self.low, fieldInfo.fieldType), false, val);
3187 +
        }
3188 +
        case ast::NodeValue::Placeholder => {}
3189 +
        case ast::NodeValue::RecordLit(lit) => {
3190 +
            // Check if this record literal is a union variant pattern.
3191 +
            if let keyNode = resolver::patternVariantKeyNode(binding) {
3192 +
                if let case resolver::NodeExtra::UnionVariant { .. } = resolver::nodeData(self.low.resolver, keyNode).extra {
3193 +
                    try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock);
3194 +
                    return;
3195 +
                }
3196 +
            }
3197 +
            // Plain nested record destructuring pattern.
3198 +
            let recInfo = resolver::getRecord(fieldInfo.fieldType)
3199 +
                else throw LowerError::ExpectedRecord;
3200 +
            let nestedBase = emitPtrOffset(self, base, fieldInfo.offset);
3201 +
3202 +
            try bindNestedRecordFields(self, nestedBase, lit, recInfo, matchBy, failBlock);
3203 +
        }
3204 +
        else => {
3205 +
            // Nested pattern requiring a test (union variant scope access, literal, etc).
3206 +
            try emitNestedFieldTest(self, binding, base, fieldInfo, matchBy, failBlock);
3207 +
        }
3208 +
    }
3209 +
}
3210 +
3211 +
/// Emit a nested pattern test for a record field value, branching to
3212 +
/// `failBlock` if the pattern does not match. On success, continues in
3213 +
/// a fresh block and binds any nested variables.
3214 +
fn emitNestedFieldTest(
3215 +
    self: *mut FnLowerer,
3216 +
    pattern: *ast::Node,
3217 +
    base: il::Reg,
3218 +
    fieldInfo: resolver::RecordField,
3219 +
    matchBy: resolver::MatchBy,
3220 +
    failBlock: BlockId
3221 +
) throws (LowerError) {
3222 +
    let fieldType = fieldInfo.fieldType;
3223 +
    let fieldPtr = emitPtrOffset(self, base, fieldInfo.offset);
3224 +
3225 +
    // Build a MatchSubject for the nested field.
3226 +
    let ilTy = ilType(self.low, fieldType);
3227 +
    let kind = matchSubjectKind(fieldType);
3228 +
3229 +
    // For aggregate subjects, the value must be a pointer.
3230 +
    let mut val = il::Val::Reg(fieldPtr);
3231 +
    if not isAggregateType(fieldType) {
3232 +
        val = emitRead(self, base, fieldInfo.offset, fieldType);
3233 +
    }
3234 +
    let nestedSubject = MatchSubject {
3235 +
        val,
3236 +
        type: fieldType,
3237 +
        ilType: ilTy,
3238 +
        bindType: fieldType,
3239 +
        kind,
3240 +
        by: matchBy,
3241 +
    };
3242 +
3243 +
    // Emit the pattern test: on success jump to `continueBlock`, on fail to `failBlock`.
3244 +
    let continueBlock = try createBlock(self, "nest");
3245 +
    try emitPatternMatch(self, &nestedSubject, pattern, continueBlock, failBlock);
3246 +
    try switchToAndSeal(self, continueBlock);
3247 +
3248 +
    // After the test succeeds, bind any nested variables.
3249 +
    let patterns: *mut [*ast::Node] = &mut [pattern];
3250 +
    try bindPatternVariables(self, &nestedSubject, patterns, failBlock);
3251 +
}
3252 +
3253 +
/// Bind variables from a nested record literal pattern.
3254 +
fn bindNestedRecordFields(
3255 +
    self: *mut FnLowerer,
3256 +
    base: il::Reg,
3257 +
    lit: ast::RecordLit,
3258 +
    recInfo: resolver::RecordType,
3259 +
    matchBy: resolver::MatchBy,
3260 +
    failBlock: BlockId
3261 +
) throws (LowerError) {
3104 3262
    for fieldNode in lit.fields {
3105 3263
        let case ast::NodeValue::RecordLitField(field) = fieldNode.value else {
3106 3264
            throw LowerError::UnexpectedNodeValue(fieldNode);
3107 3265
        };
3108 3266
        let fieldIdx = resolver::recordFieldIndexFor(self.low.resolver, fieldNode)
3110 3268
        if fieldIdx >= recInfo.fields.len {
3111 3269
            throw LowerError::MissingMetadata;
3112 3270
        }
3113 3271
        let fieldInfo = recInfo.fields[fieldIdx];
3114 3272
3115 -
        bindFieldVariable(self, field.value, payloadBase, fieldInfo, subject.by);
3273 +
        try bindFieldVariable(self, field.value, base, fieldInfo, matchBy, failBlock);
3116 3274
    }
3117 3275
}
3118 3276
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 3277
/// Lower function body to a list of basic blocks.
3143 3278
fn lowerFnBody(self: *mut FnLowerer, body: *ast::Node) -> *[il::Block] throws (LowerError) {
3144 3279
    // Create and switch to entry block.
3145 3280
    let entry = try createBlock(self, "entry");
3146 3281
    self.entryBlock = entry;
3315 3450
        // Body block: where the case body lives.
3316 3451
        let mut bodyLabel = "case";
3317 3452
        if prong.arm == ast::ProngArm::Else {
3318 3453
            bodyLabel = "else";
3319 3454
        }
3320 -
        let bodyBlock = try createBlock(self, bodyLabel);
3455 +
        let mut bodyBlock = try createBlock(self, bodyLabel);
3321 3456
        if not hasGuard {
3322 3457
            entryBlock = bodyBlock;
3323 3458
        }
3324 3459
        // Fallthrough block: jumped to when pattern or guard fails.
3325 3460
        let nextArm = try createBlock(self, "arm");
3335 3470
        }
3336 3471
        // Switch to entry block, where any variable bindings need to be created.
3337 3472
        try switchToAndSeal(self, entryBlock);
3338 3473
3339 3474
        // Bind pattern variables after successful match. Note that the guard
3340 -
        // has not been evaluated yet.
3475 +
        // has not been evaluated yet. Nested patterns may emit additional
3476 +
        // tests that branch to `nextArm` on failure, switching the current
3477 +
        // block.
3341 3478
        match prong.arm {
3342 3479
            case ast::ProngArm::Binding(pat) =>
3343 3480
                try bindMatchVariable(self, &subject, pat, false),
3344 3481
            case ast::ProngArm::Case(patterns) =>
3345 -
                try bindPatternVariables(self, &subject, patterns),
3482 +
                try bindPatternVariables(self, &subject, patterns, nextArm),
3346 3483
            else => {},
3347 3484
        }
3348 3485
3349 3486
        // Evaluate guard if present; can still fail to next arm.
3350 3487
        if let g = prong.guard {
3351 3488
            try emitCondBranch(self, g, bodyBlock, nextArm);
3489 +
        } else if currentBlock(self).n != bodyBlock.n {
3490 +
            // Nested tests changed the current block. Create a new body block
3491 +
            // after the nest blocks to maintain RPO ordering, and jump to it.
3492 +
            bodyBlock = try createBlock(self, bodyLabel);
3493 +
            try emitJmp(self, bodyBlock);
3352 3494
        }
3353 3495
        // Lower prong body and jump to merge if unterminated.
3354 3496
        try switchToAndSeal(self, bodyBlock);
3355 3497
        try lowerNode(self, prong.body);
3356 3498
        try emitMergeIfUnterminated(self, &mut mergeBlock);
3430 3572
        case ast::PatternKind::Case => {
3431 3573
            let patterns: *mut [*ast::Node] = &mut [pat.pattern];
3432 3574
            // Jump to `targetBlock` if the pattern matches, `failBlock` otherwise.
3433 3575
            try emitPatternMatches(self, subject, patterns, targetBlock, failBlock);
3434 3576
            try switchToAndSeal(self, targetBlock);
3435 -
            // Bind any variables inside the pattern.
3436 -
            try bindPatternVariables(self, subject, patterns);
3577 +
            // Bind any variables inside the pattern. Nested patterns may
3578 +
            // emit additional tests that branch to `failBlock`, switching
3579 +
            // the current block.
3580 +
            try bindPatternVariables(self, subject, patterns, failBlock);
3437 3581
        }
3438 3582
        case ast::PatternKind::Binding => {
3439 3583
            // Jump to `targetBlock` if there is a value present, `failBlock` otherwise.
3440 3584
            try emitBindingTest(self, subject, targetBlock, failBlock);
3441 3585
            try switchToAndSeal(self, targetBlock);
3445 3589
    }
3446 3590
    // Handle guard: on success jump to `successBlock`, on failure jump to `failBlock`.
3447 3591
    if let g = pat.guard {
3448 3592
        try emitCondBranch(self, g, *successBlock, failBlock);
3449 3593
        try switchToAndSeal(self, *successBlock);
3594 +
    } else if currentBlock(self).n != targetBlock.n {
3595 +
        // Nested tests changed the current block. Create a new success block
3596 +
        // after the nest blocks to maintain RPO ordering, and jump to it.
3597 +
        *successBlock = try createBlock(self, successLabel);
3598 +
3599 +
        try emitJmp(self, *successBlock);
3600 +
        try switchToAndSeal(self, *successBlock);
3450 3601
    }
3451 3602
}
3452 3603
3453 3604
/// Lower a `let-else` statement.
3454 3605
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 +147 -15
3893 3893
                } else => {}
3894 3894
            }
3895 3895
        }
3896 3896
        case Type::Array(arrayInfo) => {
3897 3897
            if let case ast::NodeValue::ArrayLit(items) = pattern.value {
3898 +
                if items.len as u32 != arrayInfo.length {
3899 +
                    throw emitError(self, pattern, ErrorKind::RecordFieldCountMismatch(
3900 +
                        CountMismatch { expected: arrayInfo.length, actual: items.len as u32 }
3901 +
                    ));
3902 +
                }
3898 3903
                let elemTy = *arrayInfo.item;
3899 3904
                for item in items {
3900 3905
                    try resolveCasePattern(self, item, elemTy, IdentMode::Bind, matchBy);
3901 3906
                }
3907 +
                setNodeType(self, pattern, scrutineeTy);
3902 3908
                return;
3903 3909
            }
3904 3910
        } else => {}
3905 3911
    }
3906 3912
    // Handle non-binding patterns (literals, placeholders) and bindings.
4017 4023
    try visitOptional(self, forStmt.elseBranch, Type::Void);
4018 4024
4019 4025
    return setNodeType(self, node, Type::Void);
4020 4026
}
4021 4027
4022 -
/// Check whether any pattern in a case prong is a wildcard `_`.
4028 +
/// Get the node within a pattern that carries the `UnionVariant` extra.
4029 +
/// For `ScopeAccess` it is the pattern itself, for `RecordLit` it is the
4030 +
/// type name, and for `Call` it is the callee.
4031 +
pub fn patternVariantKeyNode(pattern: *ast::Node) -> ?*ast::Node {
4032 +
    match pattern.value {
4033 +
        case ast::NodeValue::ScopeAccess(_) => return pattern,
4034 +
        case ast::NodeValue::RecordLit(lit) => return lit.typeName,
4035 +
        case ast::NodeValue::Call(call) => return call.callee,
4036 +
        else => return nil,
4037 +
    }
4038 +
}
4039 +
4040 +
/// Get the i-th sub-pattern element from a compound pattern.
4041 +
/// For `RecordLit` this is the i-th field's value; for `Call` it is the
4042 +
/// i-th argument.
4043 +
fn patternSubElement(pattern: *ast::Node, idx: u32) -> ?*ast::Node {
4044 +
    match pattern.value {
4045 +
        case ast::NodeValue::RecordLit(lit) => {
4046 +
            if idx < lit.fields.len as u32 {
4047 +
                if let case ast::NodeValue::RecordLitField(field) = lit.fields[idx].value {
4048 +
                    return field.value;
4049 +
                }
4050 +
            }
4051 +
        }
4052 +
        case ast::NodeValue::Call(call) => {
4053 +
            if idx < call.args.len as u32 {
4054 +
                return call.args[idx];
4055 +
            }
4056 +
        }
4057 +
        else => {}
4058 +
    }
4059 +
    return nil;
4060 +
}
4061 +
4062 +
/// Get the number of sub-pattern elements in a compound pattern.
4063 +
fn patternSubCount(pattern: *ast::Node) -> u32 {
4064 +
    match pattern.value {
4065 +
        case ast::NodeValue::RecordLit(lit) => return lit.fields.len as u32,
4066 +
        case ast::NodeValue::Call(call) => return call.args.len as u32,
4067 +
        else => return 0,
4068 +
    }
4069 +
}
4070 +
4071 +
/// Check whether a pattern contains nested sub-patterns that further
4072 +
/// refine the match beyond the outer variant (e.g. nested union variant
4073 +
/// tests or literal comparisons). Used to allow the same outer variant
4074 +
/// to appear in multiple match arms.
4075 +
fn hasNestedRefiningPattern(self: *Resolver, pattern: *ast::Node) -> bool {
4076 +
    for i in 0..patternSubCount(pattern) {
4077 +
        if let sub = patternSubElement(pattern, i) {
4078 +
            if isRefiningPattern(self, sub) {
4079 +
                return true;
4080 +
            }
4081 +
        }
4082 +
    }
4083 +
    return false;
4084 +
}
4085 +
4086 +
/// Check whether a single pattern node is a refining pattern that tests
4087 +
/// a value rather than just binding it. Union variants, literals, and
4088 +
/// scope accesses are refining; identifiers, placeholders, and plain
4089 +
/// record destructurings are not.
4090 +
fn isRefiningPattern(self: *Resolver, pattern: *ast::Node) -> bool {
4091 +
    match pattern.value {
4092 +
        case ast::NodeValue::Ident(_), ast::NodeValue::Placeholder =>
4093 +
            return false,
4094 +
        case ast::NodeValue::RecordLit(_), ast::NodeValue::Call(_) => {
4095 +
            if let keyNode = patternVariantKeyNode(pattern) {
4096 +
                if let case NodeExtra::UnionVariant { .. } = self.nodeData.entries[keyNode.id].extra {
4097 +
                    return true;
4098 +
                }
4099 +
            }
4100 +
            // Plain record destructuring / non-variant call is not directly
4101 +
            // refining; recurse to check sub-patterns.
4102 +
            return hasNestedRefiningPattern(self, pattern);
4103 +
        }
4104 +
        case ast::NodeValue::ArrayLit(items) => {
4105 +
            for item in items {
4106 +
                if isRefiningPattern(self, item) {
4107 +
                    return true;
4108 +
                }
4109 +
            }
4110 +
            return false;
4111 +
        }
4112 +
        case ast::NodeValue::ScopeAccess(_) =>
4113 +
            return true,
4114 +
        else =>
4115 +
            return true,
4116 +
    }
4117 +
}
4118 +
4119 +
/// Check whether any pattern in a case prong matches unconditionally.
4120 +
/// A plain `_` or an all-binding array pattern (e.g. `[x, y]`) qualifies.
4121 +
/// Note: top-level identifiers in `case` are comparisons, not bindings,
4122 +
/// so they do not count as wildcards.
4023 4123
fn hasWildcardPattern(patterns: *mut [*ast::Node]) -> bool {
4024 4124
    for pattern in patterns {
4025 -
        if let case ast::NodeValue::Placeholder = pattern.value {
4026 -
            return true;
4125 +
        match pattern.value {
4126 +
            case ast::NodeValue::Placeholder => return true,
4127 +
            case ast::NodeValue::ArrayLit(items) => {
4128 +
                if isIrrefutableArrayPattern(items) {
4129 +
                    return true;
4130 +
                }
4131 +
            }
4132 +
            else => {}
4027 4133
        }
4028 4134
    }
4029 4135
    return false;
4030 4136
}
4031 4137
4138 +
/// Check whether all elements of an array pattern are irrefutable.
4139 +
/// Inside array patterns, identifiers are bindings, not comparisons.
4140 +
fn isIrrefutableArrayPattern(items: *mut [*ast::Node]) -> bool {
4141 +
    for item in items {
4142 +
        match item.value {
4143 +
            case ast::NodeValue::Ident(_), ast::NodeValue::Placeholder => {}
4144 +
            case ast::NodeValue::ArrayLit(inner) => {
4145 +
                if not isIrrefutableArrayPattern(inner) {
4146 +
                    return false;
4147 +
                }
4148 +
            }
4149 +
            else => return false,
4150 +
        }
4151 +
    }
4152 +
    return true;
4153 +
}
4154 +
4032 4155
/// Analyze a match prong, checking for duplicate catch-alls. Returns the
4033 4156
/// unified match type.
4034 4157
fn resolveMatchProng(
4035 4158
    self: *mut Resolver,
4036 4159
    prongNode: *ast::Node,
4186 4309
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
4187 4310
            else panic "resolveMatchUnion: expected match prong";
4188 4311
4189 4312
        matchType = try resolveMatchProng(self, prongNode, prong, subjectTy, &mut state, matchType, matchBy);
4190 4313
4191 -
        // Record covered variants. Guarded prongs don't count as covering.
4314 +
        // Guarded prongs don't count as covering. Patterns with nested
4315 +
        // refining sub-patterns (e.g. matching different inner union variants)
4316 +
        // don't count as duplicates or as fully covering.
4192 4317
        if prong.guard == nil {
4193 4318
            if let case ast::ProngArm::Case(patterns) = prong.arm {
4194 4319
                for pattern in patterns {
4195 4320
                    if let case NodeExtra::UnionVariant { ordinal: ix, .. } = self.nodeData.entries[pattern.id].extra {
4196 -
                        if covered[ix] {
4197 -
                            throw emitError(self, pattern, ErrorKind::DuplicateMatchPattern);
4321 +
                        if not hasNestedRefiningPattern(self, pattern) {
4322 +
                            if covered[ix] {
4323 +
                                throw emitError(self, pattern, ErrorKind::DuplicateMatchPattern);
4324 +
                            }
4325 +
                            covered[ix] = true;
4326 +
                            coveredCount += 1;
4198 4327
                        }
4199 -
                        covered[ix] = true;
4200 -
                        coveredCount += 1;
4201 4328
                    }
4202 4329
                }
4203 4330
            }
4204 4331
        }
4205 4332
    }
4250 4377
                        } else {
4251 4378
                            hasFalse = true;
4252 4379
                        }
4253 4380
                    }
4254 4381
                }
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 {
4260 -
                    hasConstCase = true;
4382 +
                // Scalar constant patterns allow the match to be lowered
4383 +
                // to a switch instruction.
4384 +
                if let c = constValueEntry(self, p) {
4385 +
                    match c {
4386 +
                        case ConstValue::Bool(_), ConstValue::Char(_), ConstValue::Int(_) =>
4387 +
                            hasConstCase = true,
4388 +
                        else =>
4389 +
                            state.isConst = false,
4390 +
                    }
4261 4391
                }
4262 4392
            }
4263 4393
        }
4264 4394
    }
4265 4395
4421 4551
        }
4422 4552
        case ast::NodeValue::Ident(_) => {
4423 4553
            try bindValueIdent(self, binding, binding, bindTy, false, 0, 0);
4424 4554
        }
4425 4555
        else => {
4426 -
            throw emitError(self, binding, ErrorKind::ExpectedIdentifier);
4556 +
            // Nested pattern: recursively resolve (record destructuring,
4557 +
            // union variant, scope access, call, literals, etc).
4558 +
            try resolveCasePattern(self, binding, ty, IdentMode::Bind, matchBy);
4427 4559
        }
4428 4560
    }
4429 4561
}
4430 4562
4431 4563
/// 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.