Skip to content

Commit f5fab92

Browse files
committed
fix(rss): let-mut clone of read-param init + List.get type inference on struct-field lists
GAP1 (lowerer.rs): let mut s = <read-param> lowered s to &T, so reassigning an owned T was ill-typed (E0308). Now, tightly gated (let mut + initializer is exactly a read-param ident that lowers to plain &T, non-Copy, non-retained-Managed), the init lowers to .clone() so the local owns T. Narrow: no snapshot churn (checker_lowering 212 unchanged). GAP2 (hir.rs): infer_arg_expr_type returned None for Expr::Field, so List.get(list: read s.xs) kept return type T (RS0210 at ==/arith); now delegates field exprs to infer_hir_expr_type so List<Int> element type flows through. Full cargo test green (0 failed). Subagent-produced, applied + reverified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5dd4063 commit f5fab92

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

crates/rsscript/src/hir.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,8 +2700,8 @@ fn infer_arg_expr_type(
27002700
Expr::ObjectLiteral { .. } | Expr::MapLiteral { .. } | Expr::ArrayLiteral { .. } => {
27012701
infer_hir_expr_type(hir, expr, value_types)
27022702
}
2703-
Expr::Field { .. }
2704-
| Expr::Index { .. }
2703+
Expr::Field { .. } => infer_hir_expr_type(hir, expr, value_types),
2704+
Expr::Index { .. }
27052705
| Expr::Binary { .. }
27062706
| Expr::Number(_, _)
27072707
| Expr::String(_, _)

crates/rsscript/src/rust_lower/lowerer.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,19 @@ impl<'a> RustLowerer<'a> {
15981598
} else {
15991599
self.lower_expr(value)
16001600
};
1601+
// `let mut x = <read-param>` of a managed (non-Copy) type: the
1602+
// read-PARAM lowers to `&T`, so binding it directly makes `x: &T`.
1603+
// A later `x = <owned T>` reassignment is then ill-typed (E0308).
1604+
// Clone the borrow into an owned `T` so the mutable local owns its
1605+
// value. Gated tightly: only `let mut`, only when the initializer
1606+
// is exactly a `read`-param ident that lowers to `&T`.
1607+
let lowered = if !mutable.is_empty()
1608+
&& self.let_init_is_clonable_read_param_ref(value)
1609+
{
1610+
format!("{lowered}.clone()")
1611+
} else {
1612+
lowered
1613+
};
16011614
let inferred_ty = self.infer_expr_type(value);
16021615
let annotation = stmt
16031616
.type_annotation
@@ -3893,6 +3906,40 @@ impl<'a> RustLowerer<'a> {
38933906
self.function_param_effects.get(&key)?.get(arg_index + 1)?.1
38943907
}
38953908

3909+
/// True when `expr` is exactly a `read`-effect parameter ident whose type
3910+
/// lowers to a plain `&T` borrow (managed / non-Copy) — i.e. binding it to a
3911+
/// `let mut` local would yield `&T` and break a later owned reassignment.
3912+
/// Excludes Copy scalars (passed by value), retained `Managed<T>` params
3913+
/// (which lower to `&Managed<T>`, not a plain `&T`), and `read`-view binds
3914+
/// (already cloned at use sites).
3915+
fn let_init_is_clonable_read_param_ref(&self, expr: &Expr) -> bool {
3916+
let Expr::Ident(name, _) = expr else {
3917+
return false;
3918+
};
3919+
if self.param_effects.get(name) != Some(&DataEffect::Read) {
3920+
return false;
3921+
}
3922+
if self.read_view_bindings.contains(name) {
3923+
return false;
3924+
}
3925+
let Some(ty) = self.value_types.get(name) else {
3926+
return false;
3927+
};
3928+
// Copy scalars are passed `read` by value, so the local already owns a `T`.
3929+
if is_copy_type_ref(ty) || Self::read_effect_lowers_by_value(ty) {
3930+
return false;
3931+
}
3932+
// Retained non-class params lower to `&Managed<T>`, not a plain `&T`;
3933+
// cloning that would clone the `Managed` handle, not produce an owned `T`.
3934+
if self.current_retained_params.contains(name)
3935+
&& !self.is_class_type(ty)
3936+
&& self.type_kinds.contains_key(&ty.name)
3937+
{
3938+
return false;
3939+
}
3940+
true
3941+
}
3942+
38963943
fn expr_lowers_to_managed_handle(&self, expr: &Expr) -> bool {
38973944
match expr {
38983945
Expr::Ident(name, _) => self.managed_bindings.contains(name),

0 commit comments

Comments
 (0)