Skip to content

Commit 9c96f33

Browse files
authored
Merge pull request #759 from wado-lang/claude/implement-variadic-trait-bound-XUAom
Implement tuple Eq/NotEq expansion for variadic trait bounds
2 parents fe15e41 + 155ea1e commit 9c96f33

File tree

4 files changed

+1059
-2
lines changed

4 files changed

+1059
-2
lines changed

docs/wep-2026-03-14-variadic-type-parameters.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,9 @@ where T: Reflect<Fields = [..F]>
410410
- [ ] `Reflect` trait: synthesize per-struct impl in the lowering pass
411411
- [ ] `where` clause pack binding: parse `T: Trait<Assoc = [..F]>` and extract `F`
412412
- [ ] Error messages: show call site, element index, and body location
413-
- [ ] Standard library: add variadic impls for `Eq`, `Default`, `Clone`
413+
- [x] Tuple `Eq`: monomorphizer expands `==`/`!=` on concrete tuples to element-wise
414+
comparisons; enables `<..T: Eq>` trait bounds on variadic functions
415+
- [ ] Standard library: add variadic impls for `Default`, `Clone`
414416
- [ ] Remove compiler-magic struct `Inspect`; replace with the `Reflect`-based impl
415417

416418
---

wado-compiler/src/monomorphize/func_inst.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3367,6 +3367,21 @@ fn try_lower_comparison(
33673367
.collect();
33683368
(name.clone(), args, Some(module_source.clone()))
33693369
}
3370+
ResolvedType::Tuple(elems) => {
3371+
// Tuple comparison: expand to element-wise comparisons inline.
3372+
// [i32, String] == [i32, String] → a.0 == b.0 && a.1 == b.1
3373+
let elems = elems.clone();
3374+
return lower_tuple_comparison(
3375+
trait_method_locations,
3376+
current_module_source,
3377+
span,
3378+
op,
3379+
left,
3380+
right,
3381+
&elems,
3382+
type_table,
3383+
);
3384+
}
33703385
_ => return None,
33713386
};
33723387

@@ -3482,6 +3497,113 @@ fn try_lower_comparison(
34823497
None
34833498
}
34843499

3500+
/// Expand a tuple comparison to element-wise comparisons.
3501+
///
3502+
/// For `Eq`/`NotEq`: `a == b` → `a.0 == b.0 && a.1 == b.1 && ...`
3503+
/// For `Ord` comparisons: `a < b` → `Tuple^Ord::cmp` (delegates to element-wise cmp)
3504+
///
3505+
/// Each element comparison is recursively lowered: struct elements become
3506+
/// `Eq::eq` method calls, primitives remain as binary ops.
3507+
fn lower_tuple_comparison(
3508+
trait_method_locations: &IndexMap<String, ModuleSource>,
3509+
current_module_source: &ModuleSource,
3510+
span: crate::token::Span,
3511+
op: TirBinaryOp,
3512+
left: &TirExpr,
3513+
right: &TirExpr,
3514+
elems: &[TypeId],
3515+
type_table: &mut TypeTable,
3516+
) -> Option<TirExprKind> {
3517+
if !matches!(op, TirBinaryOp::Eq | TirBinaryOp::NotEq) {
3518+
// Ord comparisons on tuples are not yet supported
3519+
return None;
3520+
}
3521+
3522+
if elems.is_empty() {
3523+
// Empty tuple: always equal
3524+
let result = TirExprKind::BoolLiteral(true);
3525+
if op == TirBinaryOp::NotEq {
3526+
return Some(TirExprKind::Unary {
3527+
op: TirUnaryOp::Not,
3528+
expr: Box::new(TirExpr::new(result, TypeTable::BOOL, span)),
3529+
});
3530+
}
3531+
return Some(result);
3532+
}
3533+
3534+
// Build element-wise equality: a.0 == b.0 && a.1 == b.1 && ...
3535+
let mut combined: Option<TirExpr> = None;
3536+
3537+
for (i, &elem_type) in elems.iter().enumerate() {
3538+
let left_field = TirExpr::new(
3539+
TirExprKind::FieldAccess {
3540+
expr: Box::new(left.clone()),
3541+
field_index: i as u32,
3542+
field_name: i.to_string(),
3543+
},
3544+
elem_type,
3545+
span,
3546+
);
3547+
let right_field = TirExpr::new(
3548+
TirExprKind::FieldAccess {
3549+
expr: Box::new(right.clone()),
3550+
field_index: i as u32,
3551+
field_name: i.to_string(),
3552+
},
3553+
elem_type,
3554+
span,
3555+
);
3556+
3557+
// Try to lower this element comparison (e.g., struct → MethodCall).
3558+
// Use an empty trait_method_locations — the element's own try_lower_comparison
3559+
// will handle struct/variant/generic types. For primitives, it returns None
3560+
// and we use a direct Binary op.
3561+
let elem_eq = if let Some(lowered) = try_lower_comparison(
3562+
trait_method_locations,
3563+
current_module_source,
3564+
span,
3565+
TirBinaryOp::Eq,
3566+
&left_field,
3567+
&right_field,
3568+
type_table,
3569+
) {
3570+
TirExpr::new(lowered, TypeTable::BOOL, span)
3571+
} else {
3572+
TirExpr::new(
3573+
TirExprKind::Binary {
3574+
op: TirBinaryOp::Eq,
3575+
left: Box::new(left_field),
3576+
right: Box::new(right_field),
3577+
},
3578+
TypeTable::BOOL,
3579+
span,
3580+
)
3581+
};
3582+
3583+
combined = Some(match combined {
3584+
None => elem_eq,
3585+
Some(prev) => TirExpr::new(
3586+
TirExprKind::Binary {
3587+
op: TirBinaryOp::And,
3588+
left: Box::new(prev),
3589+
right: Box::new(elem_eq),
3590+
},
3591+
TypeTable::BOOL,
3592+
span,
3593+
),
3594+
});
3595+
}
3596+
3597+
let result = combined.unwrap();
3598+
if op == TirBinaryOp::NotEq {
3599+
return Some(TirExprKind::Unary {
3600+
op: TirUnaryOp::Not,
3601+
expr: Box::new(result),
3602+
});
3603+
}
3604+
Some(result.kind)
3605+
}
3606+
34853607
/// Convert a trait method name to a TIR binary operator, if applicable.
34863608
/// Used when a type-param-receiver method call is monomorphized to a primitive type.
34873609
fn trait_method_to_binary_op(trait_name: Option<&str>, method_name: &str) -> Option<TirBinaryOp> {

0 commit comments

Comments
 (0)