Skip to main content

bock_interp/
pattern.rs

1//! Pure pattern matching: test a [`Value`] against an AST [`Pattern`], extract bindings.
2//!
3//! This module provides [`match_pattern`], a pure function that structurally
4//! matches a runtime value against a pattern and returns extracted bindings on
5//! success. Guards are **not** evaluated here — that is handled by the
6//! match-expression evaluator which calls this function first.
7
8use bock_ast::{Literal, Pattern, RecordPatternField};
9
10use crate::value::{BockString, OrdF64, Value};
11
12/// A single extracted binding: variable name → matched value.
13pub type Binding = (String, Value);
14
15/// Attempt to match `value` against `pattern`.
16///
17/// Returns extracted bindings on success, `None` on failure.
18/// Does **not** evaluate guards — guard evaluation requires the expression
19/// evaluator and is handled by the match-expression evaluator in `interp.rs`.
20#[must_use]
21pub fn match_pattern(value: &Value, pattern: &Pattern) -> Option<Vec<Binding>> {
22    match pattern {
23        Pattern::Wildcard { .. } | Pattern::Rest { .. } => Some(vec![]),
24
25        Pattern::Bind { name, .. } => Some(vec![(name.name.clone(), value.clone())]),
26
27        Pattern::MutBind { name, .. } => Some(vec![(name.name.clone(), value.clone())]),
28
29        Pattern::Literal { lit, .. } => {
30            if literal_matches(lit, value) {
31                Some(vec![])
32            } else {
33                None
34            }
35        }
36
37        Pattern::Constructor { path, fields, .. } => {
38            let variant_name = path.segments.last().map(|s| s.name.as_str()).unwrap_or("");
39            match_constructor(variant_name, fields, value)
40        }
41
42        Pattern::Record {
43            path, fields, rest, ..
44        } => match_record(path, fields, *rest, value),
45
46        Pattern::Tuple { elems, .. } => match_tuple(elems, value),
47
48        Pattern::List { elems, rest, .. } => match_list(elems, rest.as_deref(), value),
49
50        Pattern::Or { alternatives, .. } => {
51            for alt in alternatives {
52                if let Some(bindings) = match_pattern(value, alt) {
53                    return Some(bindings);
54                }
55            }
56            None
57        }
58
59        Pattern::Range {
60            lo, hi, inclusive, ..
61        } => match_range(lo, hi, *inclusive, value),
62    }
63}
64
65// ─── Helpers ──────────────────────────────────────────────────────────────────
66
67/// Check whether a literal pattern matches a value.
68fn literal_matches(lit: &Literal, value: &Value) -> bool {
69    match lit {
70        Literal::Int(s) => {
71            let (numeric, _) = bock_ast::strip_type_suffix(s);
72            let clean = numeric.replace('_', "");
73            let parsed = if clean.starts_with("0x") || clean.starts_with("0X") {
74                i64::from_str_radix(&clean[2..], 16)
75            } else if clean.starts_with("0o") || clean.starts_with("0O") {
76                i64::from_str_radix(&clean[2..], 8)
77            } else if clean.starts_with("0b") || clean.starts_with("0B") {
78                i64::from_str_radix(&clean[2..], 2)
79            } else {
80                clean.parse::<i64>()
81            };
82            matches!(parsed, Ok(n) if *value == Value::Int(n))
83        }
84        Literal::Float(s) => {
85            let (numeric, _) = bock_ast::strip_type_suffix(s);
86            let parsed = numeric.replace('_', "").parse::<f64>();
87            matches!(parsed, Ok(f) if *value == Value::Float(OrdF64(f)))
88        }
89        Literal::Bool(b) => *value == Value::Bool(*b),
90        Literal::Char(s) => *value == Value::Char(s.chars().next().unwrap_or('\0')),
91        Literal::String(s) => *value == Value::String(BockString::new(s.clone())),
92        Literal::Unit => *value == Value::Void,
93    }
94}
95
96/// Match a constructor pattern (Some, None, Ok, Err, or user-defined enum variants).
97fn match_constructor(
98    variant_name: &str,
99    fields: &[Pattern],
100    value: &Value,
101) -> Option<Vec<Binding>> {
102    match (variant_name, value) {
103        ("Some", Value::Optional(Some(inner))) => {
104            if fields.len() == 1 {
105                match_pattern(inner, &fields[0])
106            } else {
107                None
108            }
109        }
110        ("None", Value::Optional(None)) => {
111            if fields.is_empty() {
112                Some(vec![])
113            } else {
114                None
115            }
116        }
117        ("Ok", Value::Result(Ok(inner))) => {
118            if fields.is_empty() {
119                Some(vec![])
120            } else if fields.len() == 1 {
121                match_pattern(inner, &fields[0])
122            } else {
123                None
124            }
125        }
126        ("Err", Value::Result(Err(inner))) => {
127            if fields.is_empty() {
128                Some(vec![])
129            } else if fields.len() == 1 {
130                match_pattern(inner, &fields[0])
131            } else {
132                None
133            }
134        }
135        (name, Value::Enum(ev)) if ev.variant == name => match (&ev.payload, fields.len()) {
136            (None, 0) => Some(vec![]),
137            (Some(inner), 1) => match_pattern(inner, &fields[0]),
138            _ => None,
139        },
140        _ => None,
141    }
142}
143
144/// Match a record pattern against a record value.
145fn match_record(
146    path: &bock_ast::TypePath,
147    fields: &[RecordPatternField],
148    rest: bool,
149    value: &Value,
150) -> Option<Vec<Binding>> {
151    let rv = match value {
152        Value::Record(rv) => rv,
153        _ => return None,
154    };
155
156    let type_name = path.segments.last().map(|s| s.name.as_str()).unwrap_or("");
157    if rv.type_name != type_name {
158        return None;
159    }
160    // Without `..`, all fields must be covered.
161    if !rest && fields.len() != rv.fields.len() {
162        return None;
163    }
164
165    let mut bindings = Vec::new();
166    for field in fields {
167        let field_val = rv.fields.get(&field.name.name)?;
168        if let Some(pat) = &field.pattern {
169            bindings.extend(match_pattern(field_val, pat)?);
170        } else {
171            // Shorthand: `{ name }` ≡ `{ name: name }`
172            bindings.push((field.name.name.clone(), field_val.clone()));
173        }
174    }
175    Some(bindings)
176}
177
178/// Match a tuple pattern against a tuple value.
179fn match_tuple(elems: &[Pattern], value: &Value) -> Option<Vec<Binding>> {
180    let vals = match value {
181        Value::Tuple(vals) => vals,
182        _ => return None,
183    };
184    if elems.len() != vals.len() {
185        return None;
186    }
187    let mut bindings = Vec::new();
188    for (pat, val) in elems.iter().zip(vals.iter()) {
189        bindings.extend(match_pattern(val, pat)?);
190    }
191    Some(bindings)
192}
193
194/// Match a list pattern against a list value.
195fn match_list(elems: &[Pattern], rest: Option<&Pattern>, value: &Value) -> Option<Vec<Binding>> {
196    let vals = match value {
197        Value::List(vals) => vals,
198        _ => return None,
199    };
200    if elems.len() > vals.len() {
201        return None;
202    }
203    if rest.is_none() && elems.len() != vals.len() {
204        return None;
205    }
206    let mut bindings = Vec::new();
207    for (pat, val) in elems.iter().zip(vals.iter()) {
208        bindings.extend(match_pattern(val, pat)?);
209    }
210    if let Some(rest_pat) = rest {
211        let rest_val = Value::List(vals[elems.len()..].to_vec());
212        bindings.extend(match_pattern(&rest_val, rest_pat)?);
213    }
214    Some(bindings)
215}
216
217/// Match a range pattern: both endpoints must be literal patterns.
218fn match_range(lo: &Pattern, hi: &Pattern, inclusive: bool, value: &Value) -> Option<Vec<Binding>> {
219    let lo_val = pattern_to_value(lo)?;
220    let hi_val = pattern_to_value(hi)?;
221    let in_range = if inclusive {
222        *value >= lo_val && *value <= hi_val
223    } else {
224        *value >= lo_val && *value < hi_val
225    };
226    if in_range {
227        Some(vec![])
228    } else {
229        None
230    }
231}
232
233/// Extract a comparable value from a literal pattern (used for range endpoints).
234fn pattern_to_value(pattern: &Pattern) -> Option<Value> {
235    match pattern {
236        Pattern::Literal { lit, .. } => literal_to_value(lit),
237        _ => None,
238    }
239}
240
241/// Convert a literal to a runtime value.
242fn literal_to_value(lit: &Literal) -> Option<Value> {
243    match lit {
244        Literal::Int(s) => {
245            let (numeric, _) = bock_ast::strip_type_suffix(s);
246            let clean = numeric.replace('_', "");
247            let n = if clean.starts_with("0x") || clean.starts_with("0X") {
248                i64::from_str_radix(&clean[2..], 16)
249            } else if clean.starts_with("0o") || clean.starts_with("0O") {
250                i64::from_str_radix(&clean[2..], 8)
251            } else if clean.starts_with("0b") || clean.starts_with("0B") {
252                i64::from_str_radix(&clean[2..], 2)
253            } else {
254                clean.parse::<i64>()
255            };
256            n.ok().map(Value::Int)
257        }
258        Literal::Float(s) => {
259            let (numeric, _) = bock_ast::strip_type_suffix(s);
260            numeric
261                .replace('_', "")
262                .parse::<f64>()
263                .ok()
264                .map(|f| Value::Float(OrdF64(f)))
265        }
266        Literal::Bool(b) => Some(Value::Bool(*b)),
267        Literal::Char(s) => Some(Value::Char(s.chars().next().unwrap_or('\0'))),
268        Literal::String(s) => Some(Value::String(BockString::new(s.clone()))),
269        Literal::Unit => Some(Value::Void),
270    }
271}
272
273// ─── Tests ────────────────────────────────────────────────────────────────────
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use crate::value::{EnumValue, RecordValue};
279    use bock_air::NodeIdGen;
280    use bock_ast::{Ident, TypePath};
281    use bock_errors::Span;
282    use std::collections::BTreeMap;
283
284    fn span() -> Span {
285        Span::dummy()
286    }
287
288    fn ident(name: &str) -> Ident {
289        Ident {
290            name: name.to_string(),
291            span: span(),
292        }
293    }
294
295    fn gen() -> NodeIdGen {
296        NodeIdGen::new()
297    }
298
299    fn type_path(name: &str) -> TypePath {
300        TypePath {
301            segments: vec![ident(name)],
302            span: span(),
303        }
304    }
305
306    // ── Wildcard ─────────────────────────────────────────────────────────────
307
308    #[test]
309    fn wildcard_matches_anything() {
310        let g = gen();
311        let pat = Pattern::Wildcard {
312            id: g.next(),
313            span: span(),
314        };
315        assert_eq!(match_pattern(&Value::Int(42), &pat), Some(vec![]));
316        assert_eq!(match_pattern(&Value::Bool(true), &pat), Some(vec![]));
317        assert_eq!(match_pattern(&Value::Void, &pat), Some(vec![]));
318    }
319
320    // ── Bind ─────────────────────────────────────────────────────────────────
321
322    #[test]
323    fn bind_captures_value() {
324        let g = gen();
325        let pat = Pattern::Bind {
326            id: g.next(),
327            span: span(),
328            name: ident("x"),
329        };
330        let result = match_pattern(&Value::Int(99), &pat);
331        assert_eq!(result, Some(vec![("x".into(), Value::Int(99))]));
332    }
333
334    #[test]
335    fn mut_bind_captures_value() {
336        let g = gen();
337        let pat = Pattern::MutBind {
338            id: g.next(),
339            span: span(),
340            name: ident("y"),
341        };
342        let result = match_pattern(&Value::Bool(true), &pat);
343        assert_eq!(result, Some(vec![("y".into(), Value::Bool(true))]));
344    }
345
346    // ── Literal ──────────────────────────────────────────────────────────────
347
348    #[test]
349    fn literal_int_match() {
350        let g = gen();
351        let pat = Pattern::Literal {
352            id: g.next(),
353            span: span(),
354            lit: Literal::Int("42".to_string()),
355        };
356        assert_eq!(match_pattern(&Value::Int(42), &pat), Some(vec![]));
357        assert_eq!(match_pattern(&Value::Int(99), &pat), None);
358    }
359
360    #[test]
361    fn literal_bool_match() {
362        let g = gen();
363        let pat = Pattern::Literal {
364            id: g.next(),
365            span: span(),
366            lit: Literal::Bool(true),
367        };
368        assert_eq!(match_pattern(&Value::Bool(true), &pat), Some(vec![]));
369        assert_eq!(match_pattern(&Value::Bool(false), &pat), None);
370    }
371
372    #[test]
373    fn literal_string_match() {
374        let g = gen();
375        let pat = Pattern::Literal {
376            id: g.next(),
377            span: span(),
378            lit: Literal::String("hello".to_string()),
379        };
380        assert_eq!(
381            match_pattern(&Value::String(BockString::new("hello")), &pat),
382            Some(vec![])
383        );
384        assert_eq!(
385            match_pattern(&Value::String(BockString::new("world")), &pat),
386            None
387        );
388    }
389
390    #[test]
391    fn literal_float_match() {
392        let g = gen();
393        let pat = Pattern::Literal {
394            id: g.next(),
395            span: span(),
396            lit: Literal::Float("3.14".to_string()),
397        };
398        assert_eq!(
399            match_pattern(&Value::Float(OrdF64(3.14)), &pat),
400            Some(vec![])
401        );
402    }
403
404    #[test]
405    fn literal_unit_match() {
406        let g = gen();
407        let pat = Pattern::Literal {
408            id: g.next(),
409            span: span(),
410            lit: Literal::Unit,
411        };
412        assert_eq!(match_pattern(&Value::Void, &pat), Some(vec![]));
413    }
414
415    // ── Constructor: Some / None / Ok / Err ──────────────────────────────────
416
417    #[test]
418    fn constructor_some_match() {
419        let g = gen();
420        let inner_pat = Pattern::Bind {
421            id: g.next(),
422            span: span(),
423            name: ident("x"),
424        };
425        let pat = Pattern::Constructor {
426            id: g.next(),
427            span: span(),
428            path: type_path("Some"),
429            fields: vec![inner_pat],
430        };
431        let value = Value::Optional(Some(Box::new(Value::Int(5))));
432        let result = match_pattern(&value, &pat);
433        assert_eq!(result, Some(vec![("x".into(), Value::Int(5))]));
434    }
435
436    #[test]
437    fn constructor_none_match() {
438        let g = gen();
439        let pat = Pattern::Constructor {
440            id: g.next(),
441            span: span(),
442            path: type_path("None"),
443            fields: vec![],
444        };
445        assert_eq!(match_pattern(&Value::Optional(None), &pat), Some(vec![]));
446        // Some doesn't match None pattern
447        let some = Value::Optional(Some(Box::new(Value::Int(1))));
448        assert_eq!(match_pattern(&some, &pat), None);
449    }
450
451    #[test]
452    fn constructor_ok_match() {
453        let g = gen();
454        let inner = Pattern::Bind {
455            id: g.next(),
456            span: span(),
457            name: ident("v"),
458        };
459        let pat = Pattern::Constructor {
460            id: g.next(),
461            span: span(),
462            path: type_path("Ok"),
463            fields: vec![inner],
464        };
465        let value = Value::Result(Ok(Box::new(Value::Int(42))));
466        assert_eq!(
467            match_pattern(&value, &pat),
468            Some(vec![("v".into(), Value::Int(42))])
469        );
470    }
471
472    #[test]
473    fn constructor_err_match() {
474        let g = gen();
475        let inner = Pattern::Bind {
476            id: g.next(),
477            span: span(),
478            name: ident("e"),
479        };
480        let pat = Pattern::Constructor {
481            id: g.next(),
482            span: span(),
483            path: type_path("Err"),
484            fields: vec![inner],
485        };
486        let value = Value::Result(Err(Box::new(Value::String(BockString::new("fail")))));
487        assert_eq!(
488            match_pattern(&value, &pat),
489            Some(vec![("e".into(), Value::String(BockString::new("fail")))])
490        );
491    }
492
493    #[test]
494    fn constructor_enum_match() {
495        let g = gen();
496        let inner = Pattern::Bind {
497            id: g.next(),
498            span: span(),
499            name: ident("r"),
500        };
501        let pat = Pattern::Constructor {
502            id: g.next(),
503            span: span(),
504            path: type_path("Circle"),
505            fields: vec![inner],
506        };
507        let value = Value::Enum(EnumValue {
508            type_name: "Shape".into(),
509            variant: "Circle".into(),
510            payload: Some(Box::new(Value::Float(OrdF64(1.5)))),
511        });
512        assert_eq!(
513            match_pattern(&value, &pat),
514            Some(vec![("r".into(), Value::Float(OrdF64(1.5)))])
515        );
516    }
517
518    #[test]
519    fn constructor_enum_no_payload() {
520        let g = gen();
521        let pat = Pattern::Constructor {
522            id: g.next(),
523            span: span(),
524            path: type_path("Red"),
525            fields: vec![],
526        };
527        let value = Value::Enum(EnumValue {
528            type_name: "Color".into(),
529            variant: "Red".into(),
530            payload: None,
531        });
532        assert_eq!(match_pattern(&value, &pat), Some(vec![]));
533    }
534
535    #[test]
536    fn constructor_enum_wrong_variant() {
537        let g = gen();
538        let pat = Pattern::Constructor {
539            id: g.next(),
540            span: span(),
541            path: type_path("Red"),
542            fields: vec![],
543        };
544        let value = Value::Enum(EnumValue {
545            type_name: "Color".into(),
546            variant: "Blue".into(),
547            payload: None,
548        });
549        assert_eq!(match_pattern(&value, &pat), None);
550    }
551
552    // ── Record ───────────────────────────────────────────────────────────────
553
554    #[test]
555    fn record_match_shorthand() {
556        let g = gen();
557        let pat = Pattern::Record {
558            id: g.next(),
559            span: span(),
560            path: type_path("Point"),
561            fields: vec![
562                RecordPatternField {
563                    span: span(),
564                    name: ident("x"),
565                    pattern: None,
566                },
567                RecordPatternField {
568                    span: span(),
569                    name: ident("y"),
570                    pattern: None,
571                },
572            ],
573            rest: false,
574        };
575        let mut fields = BTreeMap::new();
576        fields.insert("x".to_string(), Value::Int(1));
577        fields.insert("y".to_string(), Value::Int(2));
578        let value = Value::Record(RecordValue {
579            type_name: "Point".into(),
580            fields,
581        });
582        let result = match_pattern(&value, &pat).unwrap();
583        assert!(result.contains(&("x".into(), Value::Int(1))));
584        assert!(result.contains(&("y".into(), Value::Int(2))));
585    }
586
587    #[test]
588    fn record_match_with_sub_pattern() {
589        let g = gen();
590        let pat = Pattern::Record {
591            id: g.next(),
592            span: span(),
593            path: type_path("User"),
594            fields: vec![RecordPatternField {
595                span: span(),
596                name: ident("name"),
597                pattern: Some(Pattern::Bind {
598                    id: g.next(),
599                    span: span(),
600                    name: ident("n"),
601                }),
602            }],
603            rest: true,
604        };
605        let mut fields = BTreeMap::new();
606        fields.insert("name".to_string(), Value::String(BockString::new("alice")));
607        fields.insert("age".to_string(), Value::Int(30));
608        let value = Value::Record(RecordValue {
609            type_name: "User".into(),
610            fields,
611        });
612        let result = match_pattern(&value, &pat);
613        assert_eq!(
614            result,
615            Some(vec![("n".into(), Value::String(BockString::new("alice")))])
616        );
617    }
618
619    #[test]
620    fn record_wrong_type_name() {
621        let g = gen();
622        let pat = Pattern::Record {
623            id: g.next(),
624            span: span(),
625            path: type_path("Point"),
626            fields: vec![],
627            rest: true,
628        };
629        let value = Value::Record(RecordValue {
630            type_name: "Rect".into(),
631            fields: BTreeMap::new(),
632        });
633        assert_eq!(match_pattern(&value, &pat), None);
634    }
635
636    #[test]
637    fn record_rest_ignores_extra_fields() {
638        let g = gen();
639        let pat = Pattern::Record {
640            id: g.next(),
641            span: span(),
642            path: type_path("Point"),
643            fields: vec![RecordPatternField {
644                span: span(),
645                name: ident("x"),
646                pattern: None,
647            }],
648            rest: true,
649        };
650        let mut fields = BTreeMap::new();
651        fields.insert("x".to_string(), Value::Int(1));
652        fields.insert("y".to_string(), Value::Int(2));
653        fields.insert("z".to_string(), Value::Int(3));
654        let value = Value::Record(RecordValue {
655            type_name: "Point".into(),
656            fields,
657        });
658        let result = match_pattern(&value, &pat);
659        assert_eq!(result, Some(vec![("x".into(), Value::Int(1))]));
660    }
661
662    #[test]
663    fn record_no_rest_field_count_mismatch() {
664        let g = gen();
665        let pat = Pattern::Record {
666            id: g.next(),
667            span: span(),
668            path: type_path("Point"),
669            fields: vec![RecordPatternField {
670                span: span(),
671                name: ident("x"),
672                pattern: None,
673            }],
674            rest: false,
675        };
676        let mut fields = BTreeMap::new();
677        fields.insert("x".to_string(), Value::Int(1));
678        fields.insert("y".to_string(), Value::Int(2));
679        let value = Value::Record(RecordValue {
680            type_name: "Point".into(),
681            fields,
682        });
683        assert_eq!(match_pattern(&value, &pat), None);
684    }
685
686    // ── Tuple ────────────────────────────────────────────────────────────────
687
688    #[test]
689    fn tuple_match() {
690        let g = gen();
691        let pat = Pattern::Tuple {
692            id: g.next(),
693            span: span(),
694            elems: vec![
695                Pattern::Bind {
696                    id: g.next(),
697                    span: span(),
698                    name: ident("a"),
699                },
700                Pattern::Bind {
701                    id: g.next(),
702                    span: span(),
703                    name: ident("b"),
704                },
705            ],
706        };
707        let value = Value::Tuple(vec![Value::Int(1), Value::Int(2)]);
708        let result = match_pattern(&value, &pat);
709        assert_eq!(
710            result,
711            Some(vec![
712                ("a".into(), Value::Int(1)),
713                ("b".into(), Value::Int(2))
714            ])
715        );
716    }
717
718    #[test]
719    fn tuple_length_mismatch() {
720        let g = gen();
721        let pat = Pattern::Tuple {
722            id: g.next(),
723            span: span(),
724            elems: vec![Pattern::Bind {
725                id: g.next(),
726                span: span(),
727                name: ident("a"),
728            }],
729        };
730        let value = Value::Tuple(vec![Value::Int(1), Value::Int(2)]);
731        assert_eq!(match_pattern(&value, &pat), None);
732    }
733
734    // ── List ─────────────────────────────────────────────────────────────────
735
736    #[test]
737    fn list_exact_match() {
738        let g = gen();
739        let pat = Pattern::List {
740            id: g.next(),
741            span: span(),
742            elems: vec![
743                Pattern::Bind {
744                    id: g.next(),
745                    span: span(),
746                    name: ident("a"),
747                },
748                Pattern::Bind {
749                    id: g.next(),
750                    span: span(),
751                    name: ident("b"),
752                },
753            ],
754            rest: None,
755        };
756        let value = Value::List(vec![Value::Int(10), Value::Int(20)]);
757        let result = match_pattern(&value, &pat);
758        assert_eq!(
759            result,
760            Some(vec![
761                ("a".into(), Value::Int(10)),
762                ("b".into(), Value::Int(20))
763            ])
764        );
765    }
766
767    #[test]
768    fn list_with_rest_bind() {
769        let g = gen();
770        let pat = Pattern::List {
771            id: g.next(),
772            span: span(),
773            elems: vec![Pattern::Bind {
774                id: g.next(),
775                span: span(),
776                name: ident("head"),
777            }],
778            rest: Some(Box::new(Pattern::Bind {
779                id: g.next(),
780                span: span(),
781                name: ident("tail"),
782            })),
783        };
784        let value = Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
785        let result = match_pattern(&value, &pat);
786        assert_eq!(
787            result,
788            Some(vec![
789                ("head".into(), Value::Int(1)),
790                (
791                    "tail".into(),
792                    Value::List(vec![Value::Int(2), Value::Int(3)])
793                ),
794            ])
795        );
796    }
797
798    #[test]
799    fn list_with_rest_wildcard() {
800        let g = gen();
801        let pat = Pattern::List {
802            id: g.next(),
803            span: span(),
804            elems: vec![Pattern::Bind {
805                id: g.next(),
806                span: span(),
807                name: ident("first"),
808            }],
809            rest: Some(Box::new(Pattern::Rest {
810                id: g.next(),
811                span: span(),
812            })),
813        };
814        let value = Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
815        let result = match_pattern(&value, &pat);
816        assert_eq!(result, Some(vec![("first".into(), Value::Int(1))]));
817    }
818
819    #[test]
820    fn list_too_short() {
821        let g = gen();
822        let pat = Pattern::List {
823            id: g.next(),
824            span: span(),
825            elems: vec![
826                Pattern::Bind {
827                    id: g.next(),
828                    span: span(),
829                    name: ident("a"),
830                },
831                Pattern::Bind {
832                    id: g.next(),
833                    span: span(),
834                    name: ident("b"),
835                },
836            ],
837            rest: None,
838        };
839        let value = Value::List(vec![Value::Int(1)]);
840        assert_eq!(match_pattern(&value, &pat), None);
841    }
842
843    #[test]
844    fn list_no_rest_length_mismatch() {
845        let g = gen();
846        let pat = Pattern::List {
847            id: g.next(),
848            span: span(),
849            elems: vec![Pattern::Bind {
850                id: g.next(),
851                span: span(),
852                name: ident("a"),
853            }],
854            rest: None,
855        };
856        let value = Value::List(vec![Value::Int(1), Value::Int(2)]);
857        assert_eq!(match_pattern(&value, &pat), None);
858    }
859
860    // ── Or-pattern ───────────────────────────────────────────────────────────
861
862    #[test]
863    fn or_pattern_first_alternative() {
864        let g = gen();
865        let pat = Pattern::Or {
866            id: g.next(),
867            span: span(),
868            alternatives: vec![
869                Pattern::Literal {
870                    id: g.next(),
871                    span: span(),
872                    lit: Literal::Int("1".into()),
873                },
874                Pattern::Literal {
875                    id: g.next(),
876                    span: span(),
877                    lit: Literal::Int("2".into()),
878                },
879            ],
880        };
881        assert_eq!(match_pattern(&Value::Int(1), &pat), Some(vec![]));
882    }
883
884    #[test]
885    fn or_pattern_second_alternative() {
886        let g = gen();
887        let pat = Pattern::Or {
888            id: g.next(),
889            span: span(),
890            alternatives: vec![
891                Pattern::Literal {
892                    id: g.next(),
893                    span: span(),
894                    lit: Literal::Int("1".into()),
895                },
896                Pattern::Literal {
897                    id: g.next(),
898                    span: span(),
899                    lit: Literal::Int("2".into()),
900                },
901            ],
902        };
903        assert_eq!(match_pattern(&Value::Int(2), &pat), Some(vec![]));
904    }
905
906    #[test]
907    fn or_pattern_no_match() {
908        let g = gen();
909        let pat = Pattern::Or {
910            id: g.next(),
911            span: span(),
912            alternatives: vec![
913                Pattern::Literal {
914                    id: g.next(),
915                    span: span(),
916                    lit: Literal::Int("1".into()),
917                },
918                Pattern::Literal {
919                    id: g.next(),
920                    span: span(),
921                    lit: Literal::Int("2".into()),
922                },
923            ],
924        };
925        assert_eq!(match_pattern(&Value::Int(3), &pat), None);
926    }
927
928    // ── Range ────────────────────────────────────────────────────────────────
929
930    #[test]
931    fn range_exclusive() {
932        let g = gen();
933        let pat = Pattern::Range {
934            id: g.next(),
935            span: span(),
936            lo: Box::new(Pattern::Literal {
937                id: g.next(),
938                span: span(),
939                lit: Literal::Int("1".into()),
940            }),
941            hi: Box::new(Pattern::Literal {
942                id: g.next(),
943                span: span(),
944                lit: Literal::Int("10".into()),
945            }),
946            inclusive: false,
947        };
948        assert_eq!(match_pattern(&Value::Int(1), &pat), Some(vec![]));
949        assert_eq!(match_pattern(&Value::Int(5), &pat), Some(vec![]));
950        assert_eq!(match_pattern(&Value::Int(10), &pat), None); // exclusive
951        assert_eq!(match_pattern(&Value::Int(0), &pat), None);
952    }
953
954    #[test]
955    fn range_inclusive() {
956        let g = gen();
957        let pat = Pattern::Range {
958            id: g.next(),
959            span: span(),
960            lo: Box::new(Pattern::Literal {
961                id: g.next(),
962                span: span(),
963                lit: Literal::Int("1".into()),
964            }),
965            hi: Box::new(Pattern::Literal {
966                id: g.next(),
967                span: span(),
968                lit: Literal::Int("10".into()),
969            }),
970            inclusive: true,
971        };
972        assert_eq!(match_pattern(&Value::Int(10), &pat), Some(vec![]));
973        assert_eq!(match_pattern(&Value::Int(11), &pat), None);
974    }
975
976    // ── Rest pattern ─────────────────────────────────────────────────────────
977
978    #[test]
979    fn rest_pattern_matches_anything() {
980        let g = gen();
981        let pat = Pattern::Rest {
982            id: g.next(),
983            span: span(),
984        };
985        assert_eq!(match_pattern(&Value::Int(42), &pat), Some(vec![]));
986    }
987
988    // ── Nested patterns ──────────────────────────────────────────────────────
989
990    #[test]
991    fn nested_some_ok_tuple() {
992        // Some(Ok((a, b)))
993        let g = gen();
994        let tuple_pat = Pattern::Tuple {
995            id: g.next(),
996            span: span(),
997            elems: vec![
998                Pattern::Bind {
999                    id: g.next(),
1000                    span: span(),
1001                    name: ident("a"),
1002                },
1003                Pattern::Bind {
1004                    id: g.next(),
1005                    span: span(),
1006                    name: ident("b"),
1007                },
1008            ],
1009        };
1010        let ok_pat = Pattern::Constructor {
1011            id: g.next(),
1012            span: span(),
1013            path: type_path("Ok"),
1014            fields: vec![tuple_pat],
1015        };
1016        let some_pat = Pattern::Constructor {
1017            id: g.next(),
1018            span: span(),
1019            path: type_path("Some"),
1020            fields: vec![ok_pat],
1021        };
1022        let value = Value::Optional(Some(Box::new(Value::Result(Ok(Box::new(Value::Tuple(
1023            vec![Value::Int(1), Value::Int(2)],
1024        )))))));
1025        let result = match_pattern(&value, &some_pat);
1026        assert_eq!(
1027            result,
1028            Some(vec![
1029                ("a".into(), Value::Int(1)),
1030                ("b".into(), Value::Int(2))
1031            ])
1032        );
1033    }
1034
1035    #[test]
1036    fn nested_some_ok_mismatch() {
1037        // Some(Ok((a, b))) against Some(Err(...))
1038        let g = gen();
1039        let tuple_pat = Pattern::Tuple {
1040            id: g.next(),
1041            span: span(),
1042            elems: vec![
1043                Pattern::Bind {
1044                    id: g.next(),
1045                    span: span(),
1046                    name: ident("a"),
1047                },
1048                Pattern::Bind {
1049                    id: g.next(),
1050                    span: span(),
1051                    name: ident("b"),
1052                },
1053            ],
1054        };
1055        let ok_pat = Pattern::Constructor {
1056            id: g.next(),
1057            span: span(),
1058            path: type_path("Ok"),
1059            fields: vec![tuple_pat],
1060        };
1061        let some_pat = Pattern::Constructor {
1062            id: g.next(),
1063            span: span(),
1064            path: type_path("Some"),
1065            fields: vec![ok_pat],
1066        };
1067        let value = Value::Optional(Some(Box::new(Value::Result(Err(Box::new(Value::Int(99)))))));
1068        assert_eq!(match_pattern(&value, &some_pat), None);
1069    }
1070
1071    #[test]
1072    fn nested_list_with_constructor() {
1073        // [Some(x), ..]
1074        let g = gen();
1075        let some_pat = Pattern::Constructor {
1076            id: g.next(),
1077            span: span(),
1078            path: type_path("Some"),
1079            fields: vec![Pattern::Bind {
1080                id: g.next(),
1081                span: span(),
1082                name: ident("x"),
1083            }],
1084        };
1085        let pat = Pattern::List {
1086            id: g.next(),
1087            span: span(),
1088            elems: vec![some_pat],
1089            rest: Some(Box::new(Pattern::Rest {
1090                id: g.next(),
1091                span: span(),
1092            })),
1093        };
1094        let value = Value::List(vec![
1095            Value::Optional(Some(Box::new(Value::Int(5)))),
1096            Value::Optional(None),
1097        ]);
1098        let result = match_pattern(&value, &pat);
1099        assert_eq!(result, Some(vec![("x".into(), Value::Int(5))]));
1100    }
1101
1102    #[test]
1103    fn record_with_nested_constructor() {
1104        // Point { x: 0, y } matches record where x is literal 0
1105        let g = gen();
1106        let pat = Pattern::Record {
1107            id: g.next(),
1108            span: span(),
1109            path: type_path("Point"),
1110            fields: vec![
1111                RecordPatternField {
1112                    span: span(),
1113                    name: ident("x"),
1114                    pattern: Some(Pattern::Literal {
1115                        id: g.next(),
1116                        span: span(),
1117                        lit: Literal::Int("0".to_string()),
1118                    }),
1119                },
1120                RecordPatternField {
1121                    span: span(),
1122                    name: ident("y"),
1123                    pattern: None,
1124                },
1125            ],
1126            rest: false,
1127        };
1128        let mut fields = BTreeMap::new();
1129        fields.insert("x".to_string(), Value::Int(0));
1130        fields.insert("y".to_string(), Value::Int(5));
1131        let value = Value::Record(RecordValue {
1132            type_name: "Point".into(),
1133            fields,
1134        });
1135        let result = match_pattern(&value, &pat);
1136        assert_eq!(result, Some(vec![("y".into(), Value::Int(5))]));
1137    }
1138
1139    #[test]
1140    fn record_with_nested_constructor_mismatch() {
1141        let g = gen();
1142        let pat = Pattern::Record {
1143            id: g.next(),
1144            span: span(),
1145            path: type_path("Point"),
1146            fields: vec![
1147                RecordPatternField {
1148                    span: span(),
1149                    name: ident("x"),
1150                    pattern: Some(Pattern::Literal {
1151                        id: g.next(),
1152                        span: span(),
1153                        lit: Literal::Int("0".to_string()),
1154                    }),
1155                },
1156                RecordPatternField {
1157                    span: span(),
1158                    name: ident("y"),
1159                    pattern: None,
1160                },
1161            ],
1162            rest: false,
1163        };
1164        let mut fields = BTreeMap::new();
1165        fields.insert("x".to_string(), Value::Int(1)); // not 0
1166        fields.insert("y".to_string(), Value::Int(5));
1167        let value = Value::Record(RecordValue {
1168            type_name: "Point".into(),
1169            fields,
1170        });
1171        assert_eq!(match_pattern(&value, &pat), None);
1172    }
1173
1174    // ── Non-matching value types ─────────────────────────────────────────────
1175
1176    #[test]
1177    fn tuple_pattern_against_non_tuple() {
1178        let g = gen();
1179        let pat = Pattern::Tuple {
1180            id: g.next(),
1181            span: span(),
1182            elems: vec![Pattern::Wildcard {
1183                id: g.next(),
1184                span: span(),
1185            }],
1186        };
1187        assert_eq!(match_pattern(&Value::Int(1), &pat), None);
1188    }
1189
1190    #[test]
1191    fn list_pattern_against_non_list() {
1192        let g = gen();
1193        let pat = Pattern::List {
1194            id: g.next(),
1195            span: span(),
1196            elems: vec![],
1197            rest: None,
1198        };
1199        assert_eq!(match_pattern(&Value::Int(1), &pat), None);
1200    }
1201
1202    #[test]
1203    fn record_pattern_against_non_record() {
1204        let g = gen();
1205        let pat = Pattern::Record {
1206            id: g.next(),
1207            span: span(),
1208            path: type_path("Foo"),
1209            fields: vec![],
1210            rest: true,
1211        };
1212        assert_eq!(match_pattern(&Value::Int(1), &pat), None);
1213    }
1214}