Skip to content

Commit 750c343

Browse files
olwangclaude
andcommitted
rsscript: allow inline manage of a fresh/owned rvalue
Relax the RS0307 (INVALID_MANAGE_OPERAND) operand check so `manage` accepts a freshly-produced, owned rvalue (a struct constructor or a fresh-returning call) inline, in addition to the existing `manage <local>` form. A fresh shell value is created on the spot and is not an alias of any existing managed/borrowed/local binding, so promoting it into the managed runtime is sound. Every other rvalue (idents, field/index projections, an existing managed handle, etc.) may alias live state and is still rejected with RS0307. The lowerer (manage_at) and reg-VM (RegInstr::Manage) already evaluate the operand as a generic expression, so no backend changes were needed; VM and compiled backends produce identical output for the new form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3774163 commit 750c343

4 files changed

Lines changed: 112 additions & 12 deletions

File tree

crates/rsscript/src/checks/body/effects.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@ pub(super) fn check_manage_operand_is_local(
88
state: &BodyState,
99
) {
1010
let Some(name) = hir_ident_name(value) else {
11+
// A freshly-produced, owned rvalue (a struct constructor or a
12+
// `fresh`-returning call) is sound to `manage` inline: the value has
13+
// just been created here and is not an alias of any existing managed,
14+
// borrowed, or local binding. This is the same freshness model that
15+
// lets a `fresh` value materialize directly. Every other rvalue
16+
// (idents, field/index projections, an existing `manage`, etc.) may
17+
// alias live state and is still rejected below.
18+
if expr_is_fresh_shell(value) {
19+
return;
20+
}
1121
invalid_manage_operand_diagnostic(
1222
analyzer,
13-
"`manage` can only move a named local binding.",
23+
"`manage` can only move a named local binding or a freshly produced value.",
1424
span.clone(),
1525
);
1626
return;

crates/rsscript/tests/checker_frontend/effects.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,82 @@ fn bad(buffers: read List<Buffer>) -> Unit {
210210
assert_eq!(read_view_errors, 3, "{diagnostics:?}");
211211
}
212212

213+
#[test]
214+
fn checker_accepts_inline_manage_of_fresh_rvalue() {
215+
// A struct constructor and a `fresh`-returning call are freshly produced,
216+
// owned rvalues: inline `manage` of them is sound and must be accepted.
217+
let source = r#"
218+
features: local
219+
220+
struct Frame {
221+
pixels: Int
222+
}
223+
224+
fn make_frame() -> fresh Frame
225+
226+
fn ok() -> Unit {
227+
let shared = manage Frame(pixels: 0)
228+
let from_call = manage make_frame()
229+
return Unit
230+
}
231+
"#;
232+
let diagnostics = analyze_source("inline-manage-fresh.rss", source);
233+
assert!(
234+
diagnostics
235+
.iter()
236+
.all(|diagnostic| diagnostic.code != "RS0307"),
237+
"inline manage of a fresh rvalue must not be rejected: {diagnostics:?}"
238+
);
239+
}
240+
241+
#[test]
242+
fn checker_rejects_inline_manage_of_unsound_rvalue() {
243+
// Non-fresh rvalues may alias live state and must still be rejected with
244+
// RS0307: a class constructor (managed identity, not fresh) and a plain
245+
// (non-fresh) function call result.
246+
let class_diags = analyze_source(
247+
"inline-manage-class.rss",
248+
r#"
249+
features: local
250+
251+
class Session {
252+
id: Int
253+
}
254+
255+
fn bad_class() -> Unit {
256+
let s = manage Session(id: 1)
257+
return Unit
258+
}
259+
"#,
260+
);
261+
assert!(
262+
class_diags.iter().any(|d| d.code == "RS0307"),
263+
"inline manage of a class constructor must be rejected: {class_diags:?}"
264+
);
265+
266+
let plain_diags = analyze_source(
267+
"inline-manage-plain.rss",
268+
r#"
269+
features: local
270+
271+
struct Frame {
272+
pixels: Int
273+
}
274+
275+
fn plain_frame() -> Frame
276+
277+
fn bad_plain_call() -> Unit {
278+
let v = manage plain_frame()
279+
return Unit
280+
}
281+
"#,
282+
);
283+
assert!(
284+
plain_diags.iter().any(|d| d.code == "RS0307"),
285+
"inline manage of a non-fresh call result must be rejected: {plain_diags:?}"
286+
);
287+
}
288+
213289
#[test]
214290
fn checker_accepts_closure_parameter_without_treating_closure_as_data_effect_param() {
215291
let source = r#"

crates/rsscript/tests/fixtures/fail/manage-constructor-expression.rss

Lines changed: 0 additions & 11 deletions
This file was deleted.

crates/rsscript/tests/vm_eval_parity/misc.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,31 @@ fn main() -> Unit {
9292
common::assert_vm_eval_matches_backend("parity-struct.rss", "rsscript_parity_struct", source);
9393
}
9494

95+
#[test]
96+
fn parity_inline_manage_of_fresh_rvalue() {
97+
// Inline `manage` of a freshly produced struct constructor (no named local
98+
// binding). The VM and compiled backend must agree on the managed value's
99+
// observable field.
100+
let source = r#"
101+
features: local
102+
103+
struct Tally {
104+
value: Int
105+
}
106+
107+
fn main() -> Unit {
108+
let shared = manage Tally(value: 7)
109+
Log.write(message: read String.from_int(value: shared.value))
110+
return Unit
111+
}
112+
"#;
113+
common::assert_vm_eval_matches_backend(
114+
"parity-inline-manage.rss",
115+
"rsscript_parity_inline_manage",
116+
source,
117+
);
118+
}
119+
95120
#[test]
96121
fn parity_option_and_result_match() {
97122
let source = r#"

0 commit comments

Comments
 (0)