Skip to content

Commit 5dd4063

Browse files
committed
feat(rss): payload-less sum value params + == on read-enum params
GAP1: a payload-less (fieldless) sum is Copy-like, so a by-value param fn f(o: Op) no longer requires read/mut/take (analyzer is_payloadless_sum_type exemption), and f(o: A) works. GAP2: o == A on a read-bound enum param lowered to &Op == Op (E0277); now derefs one side when a read enum-param ref is compared to a sum value (mirrors field-access enum compare). Removes two recurring workarounds in the tinygrad port. Full cargo test -p rsscript green (25 targets, 0 failed). Subagent-produced, applied + reverified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7358f50 commit 5dd4063

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

crates/rsscript/src/analyzer.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2320,6 +2320,23 @@ impl Analyzer<'_> {
23202320
}
23212321
}
23222322

2323+
/// A payload-less sum type (every variant carries no fields) behaves like a
2324+
/// plain C-style enum: it is cheap to copy and may be passed by value without
2325+
/// an explicit data effect, mirroring how primitive Copy types are treated.
2326+
fn is_payloadless_sum_type(&self, ty: &TypeRef) -> bool {
2327+
if !ty.args.is_empty() || ty.is_noescape || ty.is_owned || ty.is_fresh {
2328+
return false;
2329+
}
2330+
let root = type_root_name(&ty.name);
2331+
if self.hir.type_kind(root) != Some(HirTypeKind::Sum) {
2332+
return false;
2333+
}
2334+
match self.sum_variants_for_type(root) {
2335+
Some(variants) => variants.iter().all(|(_, fields)| fields.is_empty()),
2336+
None => false,
2337+
}
2338+
}
2339+
23232340
fn sum_variants_for_type(&self, root: &str) -> Option<Vec<(String, Vec<FieldInfo>)>> {
23242341
for item in &self.syntax_program.items {
23252342
if let Item::SumType(sum) = item
@@ -2491,6 +2508,7 @@ impl Analyzer<'_> {
24912508
&& !type_ref_has_surface_reference(&param.ty, self.tokens)
24922509
&& !type_ref_contains_name(&param.ty, "Fd")
24932510
&& !is_copy_type(&param.ty)
2511+
&& !self.is_payloadless_sum_type(&param.ty)
24942512
{
24952513
self.diagnostics.push(
24962514
Diagnostic::error(

crates/rsscript/src/rust_lower/lowerer.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,28 @@ impl<'a> RustLowerer<'a> {
22352235
self.lower_string_comparison_operand(right)
22362236
);
22372237
}
2238+
if matches!(op, "==" | "!=") {
2239+
// A `read`-bound enum parameter lowers to `&Op`; comparing it
2240+
// against a sum *value* (e.g. a bare variant) needs a deref so
2241+
// both sides are `Op`. Mirrors how field-access enum comparison
2242+
// already lowers to a value on both sides.
2243+
let left_ref = self.is_enum_read_ref_operand(left);
2244+
let right_ref = self.is_enum_read_ref_operand(right);
2245+
if left_ref && !right_ref && self.is_enum_value_operand(right) {
2246+
return format!(
2247+
"*{} {op} {}",
2248+
self.lower_binary_operand(left, binary_op, false),
2249+
self.lower_binary_operand(right, binary_op, true)
2250+
);
2251+
}
2252+
if right_ref && !left_ref && self.is_enum_value_operand(left) {
2253+
return format!(
2254+
"{} {op} *{}",
2255+
self.lower_binary_operand(left, binary_op, false),
2256+
self.lower_binary_operand(right, binary_op, true)
2257+
);
2258+
}
2259+
}
22382260
format!(
22392261
"{} {op} {}",
22402262
self.lower_binary_operand(left, binary_op, false),
@@ -3899,6 +3921,38 @@ impl<'a> RustLowerer<'a> {
38993921
.is_some_and(|ty| self.is_class_type(&ty))
39003922
}
39013923

3924+
/// True when this operand is a `read`-bound parameter whose type is a
3925+
/// user-defined sum type. Such a parameter lowers to `&Op`, so comparing it
3926+
/// against a sum *value* (e.g. a bare variant literal) needs a deref.
3927+
fn is_enum_read_ref_operand(&self, expr: &Expr) -> bool {
3928+
let Expr::Ident(name, _) = expr else {
3929+
return false;
3930+
};
3931+
if self.param_effects.get(name) != Some(&DataEffect::Read) {
3932+
return false;
3933+
}
3934+
let Some(ty) = self.value_types.get(name) else {
3935+
return false;
3936+
};
3937+
ty.args.is_empty() && self.is_sum_type_name(&ty.name)
3938+
}
3939+
3940+
/// True when this operand is a sum *value* that lowers by value (not behind a
3941+
/// reference) — most commonly a bare variant literal such as `A`.
3942+
fn is_enum_value_operand(&self, expr: &Expr) -> bool {
3943+
match expr {
3944+
Expr::Ident(name, _) => self.find_sum_type_for_variant(name).is_some(),
3945+
_ => false,
3946+
}
3947+
}
3948+
3949+
fn is_sum_type_name(&self, name: &str) -> bool {
3950+
self.program
3951+
.items
3952+
.iter()
3953+
.any(|item| matches!(item, Item::SumType(sum) if sum.name == name))
3954+
}
3955+
39023956
fn is_string_comparison_operand(&self, expr: &Expr) -> bool {
39033957
matches!(expr, Expr::String(_, _) | Expr::MultilineString(_, _))
39043958
|| self

0 commit comments

Comments
 (0)