Skip to content

Commit 765ea5a

Browse files
authored
Merge pull request #707 from wado-lang/claude/add-pipe-pattern-matching-WcOXD
Add support for or-patterns in match expressions
2 parents a74bb9e + 3059bc3 commit 765ea5a

30 files changed

+4572
-27
lines changed

docs/cheatsheet.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,21 @@ let result = match opt {
682682
None => 0,
683683
};
684684
685+
// Or patterns
686+
match color {
687+
Red | Blue => "cool",
688+
Green => "warm",
689+
}
690+
691+
// Or patterns with bindings (all alternatives must bind the same names)
692+
match expr {
693+
Num(n) | Neg(n) => use(n),
694+
Zero => 0,
695+
}
696+
697+
// Or patterns in matches operator
698+
if shape matches { Circle(_) | Square(_) } { ... }
699+
685700
// Match with guard
686701
let label = match value {
687702
Some(x) && x > 100 => "large",

docs/spec.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,33 @@ match customer {
652652
}
653653
```
654654

655+
**Or Patterns:**
656+
657+
Or patterns match if any alternative matches. All alternatives must bind the same names with the same types:
658+
659+
```wado
660+
// Enum or-patterns
661+
match color {
662+
Red | Blue => "cool",
663+
Green => "warm",
664+
}
665+
666+
// Variant or-patterns with bindings
667+
match expr {
668+
Num(n) | Neg(n) => use(n),
669+
Zero => 0,
670+
}
671+
672+
// Literal or-patterns
673+
match n {
674+
1 | 2 | 3 => "low",
675+
_ => "high",
676+
}
677+
678+
// Or patterns in matches operator
679+
if shape matches { Circle(_) | Square(_) } { ... }
680+
```
681+
655682
**Nested Sub-Patterns in Tuple/Struct Destructuring:**
656683

657684
Tuple and struct patterns support literal, variant, and enum sub-patterns. These are lowered into guard conditions with appropriate checks:

wado-compiler/src/ast.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,8 @@ pub enum Pattern {
10451045
has_rest: bool,
10461046
span: Span,
10471047
},
1048+
/// Or pattern: `Red | Blue` or `Some(x) | Other(x)`
1049+
Or(Vec<Pattern>),
10481050
}
10491051

10501052
#[derive(Debug, Clone)]

wado-compiler/src/bind.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,12 @@ impl<'a, H: CompilerHost> Binder<'a, H> {
548548
}
549549
}
550550
crate::ast::Pattern::Literal(_) | crate::ast::Pattern::Variant { .. } => {}
551+
crate::ast::Pattern::Or(alternatives) => {
552+
// Bind variables from the first alternative (all alternatives must bind the same names)
553+
if let Some(first) = alternatives.first() {
554+
self.bind_let_pattern_uninit(first, is_mut, is_reactive, span)?;
555+
}
556+
}
551557
}
552558
Ok(())
553559
}
@@ -577,6 +583,11 @@ impl<'a, H: CompilerHost> Binder<'a, H> {
577583
self.bind_let_pattern(&field.pattern, is_mut, is_reactive, span)?;
578584
}
579585
}
586+
crate::ast::Pattern::Or(alternatives) => {
587+
if let Some(first) = alternatives.first() {
588+
self.bind_let_pattern(first, is_mut, is_reactive, span)?;
589+
}
590+
}
580591
crate::ast::Pattern::Literal(_) | crate::ast::Pattern::Variant { .. } => {
581592
// Literal and variant patterns are not valid in let statements
582593
// This would be caught by the type checker
@@ -1051,6 +1062,12 @@ impl<'a, H: CompilerHost> Binder<'a, H> {
10511062
crate::ast::Pattern::Literal(_) | crate::ast::Pattern::Wildcard => {
10521063
// No variables introduced
10531064
}
1065+
crate::ast::Pattern::Or(alternatives) => {
1066+
// Bind variables from the first alternative (all alternatives must bind the same names)
1067+
if let Some(first) = alternatives.first() {
1068+
self.bind_pattern(first, span)?;
1069+
}
1070+
}
10541071
}
10551072
Ok(())
10561073
}

wado-compiler/src/desugar.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ fn desugar_pattern(p: &Pattern) -> Pattern {
285285
has_rest: *has_rest,
286286
span: *span,
287287
},
288+
Pattern::Or(alternatives) => {
289+
Pattern::Or(alternatives.iter().map(desugar_pattern).collect())
290+
}
288291
}
289292
}
290293

wado-compiler/src/lower/closure.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,12 @@ impl ClosureLowerer {
16191619
.collect(),
16201620
has_rest: *has_rest,
16211621
},
1622+
TirPattern::Or(alternatives) => TirPattern::Or(
1623+
alternatives
1624+
.iter()
1625+
.map(|p| self.transform_closure_body_pattern(p))
1626+
.collect(),
1627+
),
16221628
}
16231629
}
16241630

wado-compiler/src/lower/globals.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ fn renumber_locals_in_pattern(pattern: &mut TirPattern, offset: u32) {
585585
}
586586
}
587587
TirPattern::Wildcard | TirPattern::Literal(_) | TirPattern::Enum { .. } => {}
588+
TirPattern::Or(alternatives) => {
589+
for p in alternatives {
590+
renumber_locals_in_pattern(p, offset);
591+
}
592+
}
588593
}
589594
}
590595

wado-compiler/src/lower/pattern.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,12 @@ impl<'a> PatternLowerer<'a> {
12701270
// Literal/Enum patterns don't bind anything, just evaluate for side effects
12711271
out.push(TirStmt::new(TirStmtKind::Expr(value), span));
12721272
}
1273+
TirPattern::Or(alternatives) => {
1274+
// Or patterns in let-destructure: use first alternative's bindings
1275+
if let Some(first) = alternatives.first() {
1276+
self.lower_pattern_to_lets(first, is_mut, value, span, out, type_table);
1277+
}
1278+
}
12731279
}
12741280
}
12751281

@@ -1522,6 +1528,12 @@ impl<'a> PatternLowerer<'a> {
15221528
// Just evaluate for side effects (no bindings)
15231529
out.push(TirStmt::new(TirStmtKind::Expr(value), span));
15241530
}
1531+
TirPattern::Or(alternatives) => {
1532+
// Or patterns in lets: use first alternative's bindings
1533+
if let Some(first) = alternatives.first() {
1534+
self.lower_pattern_to_lets(first, is_mut, value, span, out, type_table);
1535+
}
1536+
}
15251537
}
15261538
}
15271539

@@ -2058,6 +2070,42 @@ impl<'a> PatternLowerer<'a> {
20582070
binding_stmts,
20592071
)
20602072
}
2073+
TirPattern::Or(alternatives) => {
2074+
// Or pattern: combine conditions with logical OR, use first alternative's bindings
2075+
let mut or_conditions: Vec<TirExpr> = Vec::new();
2076+
for alt in alternatives {
2077+
let (cond, _) = self.pattern_to_condition_and_bindings(
2078+
alt,
2079+
scrutinee.clone(),
2080+
span,
2081+
type_table,
2082+
);
2083+
or_conditions.push(cond);
2084+
}
2085+
// Emit bindings from the first alternative (all alternatives bind the same names)
2086+
if let Some(first) = alternatives.first() {
2087+
let (_, first_bindings) =
2088+
self.pattern_to_condition_and_bindings(first, scrutinee, span, type_table);
2089+
binding_stmts.extend(first_bindings);
2090+
}
2091+
let combined = or_conditions
2092+
.into_iter()
2093+
.reduce(|acc, cond| {
2094+
TirExpr::new(
2095+
TirExprKind::Binary {
2096+
op: TirBinaryOp::Or,
2097+
left: Box::new(acc),
2098+
right: Box::new(cond),
2099+
},
2100+
TypeTable::BOOL,
2101+
span,
2102+
)
2103+
})
2104+
.unwrap_or_else(|| {
2105+
TirExpr::new(TirExprKind::BoolLiteral(false), TypeTable::BOOL, span)
2106+
});
2107+
(combined, binding_stmts)
2108+
}
20612109
}
20622110
}
20632111

@@ -2091,6 +2139,24 @@ impl<'a> PatternLowerer<'a> {
20912139
self.lower_expr(&mut arm.body, type_table);
20922140
}
20932141

2142+
// Expand or-patterns: `A | B => body` becomes `A => body, B => body`
2143+
let mut expanded_arms = Vec::new();
2144+
for arm in arms.drain(..) {
2145+
if let TirPattern::Or(alternatives) = arm.pattern {
2146+
for alt in alternatives {
2147+
expanded_arms.push(TirMatchArm {
2148+
pattern: alt,
2149+
guard: arm.guard.clone(),
2150+
body: arm.body.clone(),
2151+
span: arm.span,
2152+
});
2153+
}
2154+
} else {
2155+
expanded_arms.push(arm);
2156+
}
2157+
}
2158+
*arms = expanded_arms;
2159+
20942160
// Match ergonomics: insert deref if scrutinee is Ref/MutRef
20952161
while let ResolvedType::Ref(inner) | ResolvedType::MutRef(inner) =
20962162
type_table.get(scrutinee.type_id)

wado-compiler/src/monomorphize/substitute.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,11 @@ impl Monomorphizer {
388388
self.substitute_types_in_pattern(&mut field.pattern, substitution, type_table);
389389
}
390390
}
391+
TirPattern::Or(alternatives) => {
392+
for p in alternatives {
393+
self.substitute_types_in_pattern(p, substitution, type_table);
394+
}
395+
}
391396
}
392397
}
393398
}

wado-compiler/src/optimize/dce.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,11 @@ fn collect_types_from_pattern(
16531653
collect_types_from_pattern(&field.pattern, type_table, reachable);
16541654
}
16551655
}
1656+
TirPattern::Or(alternatives) => {
1657+
for p in alternatives {
1658+
collect_types_from_pattern(p, type_table, reachable);
1659+
}
1660+
}
16561661
}
16571662
}
16581663

0 commit comments

Comments
 (0)