use std::collections::HashMap;
use super::analysis::ReturnOwnershipMode;
use super::types::*;
pub fn infer_return_ownership_mode(
mir: &MirFunction,
callee_modes: &HashMap<String, ReturnOwnershipMode>,
) -> ReturnOwnershipMode {
let return_slot = SlotId(0);
let reachable = reachable_blocks(mir);
let mut combined: Option<ReturnOwnershipMode> = None;
for block in mir.iter_blocks() {
if !reachable.contains(&block.id) {
continue;
}
for stmt in &block.statements {
let StatementKind::Assign(Place::Local(dest), rvalue) = &stmt.kind else {
continue;
};
if *dest != return_slot {
continue;
}
let mode = classify_rvalue(rvalue, mir, callee_modes);
combined = Some(match combined {
None => mode,
Some(existing) => existing.meet(mode),
});
}
}
combined.unwrap_or(ReturnOwnershipMode::Unknown)
}
fn reachable_blocks(mir: &MirFunction) -> std::collections::HashSet<BasicBlockId> {
use std::collections::HashSet;
use std::collections::VecDeque;
let mut visited: HashSet<BasicBlockId> = HashSet::new();
let mut queue: VecDeque<BasicBlockId> = VecDeque::new();
if !mir.blocks.is_empty() {
queue.push_back(mir.entry_block());
}
while let Some(id) = queue.pop_front() {
if !visited.insert(id) {
continue;
}
let block = mir.block(id);
match &block.terminator.kind {
TerminatorKind::Goto(target) => queue.push_back(*target),
TerminatorKind::SwitchBool {
true_bb, false_bb, ..
} => {
queue.push_back(*true_bb);
queue.push_back(*false_bb);
}
TerminatorKind::Call { next, .. } => queue.push_back(*next),
TerminatorKind::Return | TerminatorKind::Unreachable => {}
}
}
visited
}
fn classify_rvalue(
rvalue: &Rvalue,
mir: &MirFunction,
callee_modes: &HashMap<String, ReturnOwnershipMode>,
) -> ReturnOwnershipMode {
match rvalue {
Rvalue::Aggregate(_) => ReturnOwnershipMode::NewlyOwned,
Rvalue::Clone(_) => ReturnOwnershipMode::NewlyOwned,
Rvalue::Borrow(kind, place) => classify_borrow_rvalue(*kind, place, mir),
Rvalue::Use(operand) => classify_operand(operand, mir, callee_modes),
Rvalue::BinaryOp(_, _, _) | Rvalue::UnaryOp(_, _) => ReturnOwnershipMode::NewlyOwned,
Rvalue::EnumTest { .. }
| Rvalue::EnumPayload { .. }
| Rvalue::TypePatternTest { .. }
| Rvalue::EnumDiscriminantTest { .. } => ReturnOwnershipMode::NewlyOwned,
}
}
fn classify_borrow_rvalue(
_kind: BorrowKind,
place: &Place,
mir: &MirFunction,
) -> ReturnOwnershipMode {
let root = place.root_local();
if let Some(param_idx) = mir.param_slots.iter().position(|s| *s == root) {
ReturnOwnershipMode::BorrowedFromParam(param_idx)
} else {
ReturnOwnershipMode::Unknown
}
}
fn classify_operand(
operand: &Operand,
mir: &MirFunction,
callee_modes: &HashMap<String, ReturnOwnershipMode>,
) -> ReturnOwnershipMode {
match operand {
Operand::Constant(c) => classify_constant(c),
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
classify_place(place, mir, callee_modes)
}
}
}
fn classify_constant(c: &MirConstant) -> ReturnOwnershipMode {
match c {
MirConstant::Str(_)
| MirConstant::StringId(_)
| MirConstant::Function(_)
| MirConstant::Method(_) => ReturnOwnershipMode::Static,
MirConstant::Int(_)
| MirConstant::Bool(_)
| MirConstant::Float(_)
| MirConstant::Char(_)
| MirConstant::None => ReturnOwnershipMode::NewlyOwned,
MirConstant::Decimal(_) => ReturnOwnershipMode::NewlyOwned,
MirConstant::ClosurePlaceholder => ReturnOwnershipMode::Unknown,
}
}
fn classify_place(
place: &Place,
mir: &MirFunction,
callee_modes: &HashMap<String, ReturnOwnershipMode>,
) -> ReturnOwnershipMode {
let root = place.root_local();
if let Some(param_idx) = mir.param_slots.iter().position(|s| *s == root) {
return ReturnOwnershipMode::BorrowedFromParam(param_idx);
}
let mut visited = std::collections::HashSet::new();
trace_local_defining_mode(root, mir, callee_modes, &mut visited)
}
fn trace_local_defining_mode(
slot: SlotId,
mir: &MirFunction,
callee_modes: &HashMap<String, ReturnOwnershipMode>,
visited: &mut std::collections::HashSet<SlotId>,
) -> ReturnOwnershipMode {
if !visited.insert(slot) {
return ReturnOwnershipMode::Unknown;
}
let mut combined: Option<ReturnOwnershipMode> = None;
for block in mir.iter_blocks() {
for stmt in &block.statements {
if let StatementKind::Assign(Place::Local(dest), rvalue) = &stmt.kind {
if *dest != slot {
continue;
}
let mode = classify_defining_rvalue(rvalue, mir, callee_modes, visited);
combined = Some(match combined {
None => mode,
Some(existing) => existing.meet(mode),
});
}
}
if let TerminatorKind::Call {
destination,
func,
..
} = &block.terminator.kind
{
if destination.root_local() == slot {
let mode = match func {
Operand::Constant(MirConstant::Function(name)) => callee_modes
.get(name)
.copied()
.unwrap_or(ReturnOwnershipMode::Unknown),
_ => ReturnOwnershipMode::Unknown,
};
combined = Some(match combined {
None => mode,
Some(existing) => existing.meet(mode),
});
}
}
}
combined.unwrap_or(ReturnOwnershipMode::Unknown)
}
fn classify_defining_rvalue(
rvalue: &Rvalue,
mir: &MirFunction,
callee_modes: &HashMap<String, ReturnOwnershipMode>,
visited: &mut std::collections::HashSet<SlotId>,
) -> ReturnOwnershipMode {
match rvalue {
Rvalue::Aggregate(_) | Rvalue::Clone(_) => ReturnOwnershipMode::NewlyOwned,
Rvalue::BinaryOp(_, _, _) | Rvalue::UnaryOp(_, _) => ReturnOwnershipMode::NewlyOwned,
Rvalue::EnumTest { .. }
| Rvalue::EnumPayload { .. }
| Rvalue::TypePatternTest { .. }
| Rvalue::EnumDiscriminantTest { .. } => ReturnOwnershipMode::NewlyOwned,
Rvalue::Borrow(kind, p) => classify_borrow_rvalue(*kind, p, mir),
Rvalue::Use(op) => match op {
Operand::Constant(c) => classify_constant(c),
Operand::Copy(p) | Operand::Move(p) | Operand::MoveExplicit(p) => {
let root = p.root_local();
if let Some(idx) = mir.param_slots.iter().position(|s| *s == root) {
ReturnOwnershipMode::BorrowedFromParam(idx)
} else {
trace_local_defining_mode(root, mir, callee_modes, visited)
}
}
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use shape_ast::ast::Span;
use std::collections::HashMap as StdHashMap;
fn dummy_span() -> Span {
Span::DUMMY
}
fn empty_mir(name: &str) -> MirFunction {
MirFunction {
name: name.to_string(),
blocks: Vec::new(),
num_locals: 1,
param_slots: Vec::new(),
param_reference_kinds: Vec::new(),
local_types: vec![LocalTypeInfo::Unknown],
span: dummy_span(),
field_name_table: StdHashMap::new(),
local_struct_type_names: StdHashMap::new(),
local_typed_array_element_types: StdHashMap::new(),
local_declared_scalar_types: StdHashMap::new(),
}
}
fn push_stmt(block: &mut BasicBlock, kind: StatementKind, point: u32) {
block.statements.push(MirStatement {
kind,
span: dummy_span(),
point: Point(point),
});
}
fn return_terminator() -> Terminator {
Terminator {
kind: TerminatorKind::Return,
span: dummy_span(),
}
}
#[test]
fn test_aggregate_return_is_newly_owned() {
let mut mir = empty_mir("make");
mir.num_locals = 2;
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(1)),
Operand::Constant(MirConstant::Int(2)),
Operand::Constant(MirConstant::Int(3)),
]),
),
0,
);
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
1,
);
mir.blocks.push(bb0);
let callee_modes = HashMap::new();
assert_eq!(
infer_return_ownership_mode(&mir, &callee_modes),
ReturnOwnershipMode::NewlyOwned
);
}
#[test]
fn test_param_copy_return_is_borrowed_from_param() {
let mut mir = empty_mir("first");
mir.num_locals = 2;
mir.param_slots = vec![SlotId(1)];
mir.param_reference_kinds = vec![None];
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
0,
);
mir.blocks.push(bb0);
let callee_modes = HashMap::new();
assert_eq!(
infer_return_ownership_mode(&mir, &callee_modes),
ReturnOwnershipMode::BorrowedFromParam(0)
);
}
#[test]
fn test_constant_return_is_newly_owned() {
let mut mir = empty_mir("answer");
mir.local_types = vec![LocalTypeInfo::Copy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::NewlyOwned
);
}
#[test]
fn test_binary_op_return_is_newly_owned() {
let mut mir = empty_mir("add");
mir.num_locals = 3;
mir.param_slots = vec![SlotId(1), SlotId(2)];
mir.param_reference_kinds = vec![None, None];
mir.local_types = vec![LocalTypeInfo::Copy, LocalTypeInfo::Copy, LocalTypeInfo::Copy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(1))),
Operand::Copy(Place::Local(SlotId(2))),
),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::NewlyOwned
);
}
#[test]
fn test_two_branches_same_newly_owned_meets_newly_owned() {
let mut mir = empty_mir("make");
mir.num_locals = 4;
mir.param_slots = vec![SlotId(3)];
mir.param_reference_kinds = vec![None];
mir.local_types = vec![
LocalTypeInfo::NonCopy,
LocalTypeInfo::NonCopy,
LocalTypeInfo::NonCopy,
LocalTypeInfo::Copy,
];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(SlotId(3))),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![Operand::Constant(MirConstant::Int(1))]),
),
0,
);
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
1,
);
let mut bb2 = BasicBlock {
id: BasicBlockId(2),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Aggregate(vec![Operand::Constant(MirConstant::Int(2))]),
),
2,
);
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(2)))),
),
3,
);
mir.blocks = vec![bb0, bb1, bb2];
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::NewlyOwned
);
}
#[test]
fn test_route_between_params_meets_to_unknown() {
let mut mir = empty_mir("route");
mir.num_locals = 3;
mir.param_slots = vec![SlotId(0) , SlotId(1), SlotId(2)];
mir.param_slots = vec![SlotId(1), SlotId(2)];
mir.param_reference_kinds = vec![None, None];
mir.local_types = vec![
LocalTypeInfo::NonCopy,
LocalTypeInfo::NonCopy,
LocalTypeInfo::NonCopy,
];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::SwitchBool {
operand: Operand::Constant(MirConstant::Bool(true)),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
0,
);
let mut bb2 = BasicBlock {
id: BasicBlockId(2),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
),
1,
);
mir.blocks = vec![bb0, bb1, bb2];
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Unknown
);
}
#[test]
fn test_call_return_uses_callee_mode() {
let mut mir = empty_mir("wrap");
mir.num_locals = 2;
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function("make".into())),
args: Vec::new(),
destination: Place::Local(SlotId(1)),
next: BasicBlockId(1),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
0,
);
mir.blocks = vec![bb0, bb1];
let mut callee_modes = HashMap::new();
callee_modes.insert("make".to_string(), ReturnOwnershipMode::NewlyOwned);
assert_eq!(
infer_return_ownership_mode(&mir, &callee_modes),
ReturnOwnershipMode::NewlyOwned
);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Unknown
);
}
#[test]
fn test_no_return_is_unknown() {
let mut mir = empty_mir("diverge");
mir.blocks.push(BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::Unreachable,
span: dummy_span(),
},
});
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Unknown
);
}
#[test]
fn test_borrow_from_param_is_borrowed_from_param() {
let mut mir = empty_mir("first");
mir.num_locals = 2;
mir.param_slots = vec![SlotId(1)];
mir.param_reference_kinds = vec![Some(BorrowKind::Shared)];
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(1))),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::BorrowedFromParam(0)
);
}
#[test]
fn test_meet_identical_modes() {
assert_eq!(
ReturnOwnershipMode::NewlyOwned.meet(ReturnOwnershipMode::NewlyOwned),
ReturnOwnershipMode::NewlyOwned
);
assert_eq!(
ReturnOwnershipMode::BorrowedFromParam(1).meet(ReturnOwnershipMode::BorrowedFromParam(1)),
ReturnOwnershipMode::BorrowedFromParam(1)
);
}
#[test]
fn test_meet_mismatch_collapses_to_unknown() {
assert_eq!(
ReturnOwnershipMode::NewlyOwned.meet(ReturnOwnershipMode::BorrowedFromParam(0)),
ReturnOwnershipMode::Unknown
);
assert_eq!(
ReturnOwnershipMode::BorrowedFromParam(0).meet(ReturnOwnershipMode::BorrowedFromParam(1)),
ReturnOwnershipMode::Unknown
);
}
#[test]
fn test_default_is_unknown() {
assert_eq!(ReturnOwnershipMode::default(), ReturnOwnershipMode::Unknown);
}
#[test]
fn test_string_literal_return_is_static() {
let mut mir = empty_mir("static_string");
mir.local_types = vec![LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Str("hello".into()))),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Static
);
}
#[test]
fn test_interned_string_id_return_is_static() {
let mut mir = empty_mir("interned");
mir.local_types = vec![LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::StringId(7))),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Static
);
}
#[test]
fn test_function_ref_return_is_static() {
let mut mir = empty_mir("returns_fn");
mir.local_types = vec![LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Function("foo".into()))),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Static
);
}
#[test]
fn test_static_string_via_temp_is_static() {
let mut mir = empty_mir("static_via_temp");
mir.num_locals = 2;
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Str("greet".into()))),
),
0,
);
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
1,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Static
);
}
#[test]
fn test_call_returning_shared_propagates_shared() {
let mut mir = empty_mir("wrap_arc");
mir.num_locals = 2;
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function("arc_new".into())),
args: Vec::new(),
destination: Place::Local(SlotId(1)),
next: BasicBlockId(1),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
0,
);
mir.blocks = vec![bb0, bb1];
let mut callee_modes = HashMap::new();
callee_modes.insert("arc_new".to_string(), ReturnOwnershipMode::Shared);
assert_eq!(
infer_return_ownership_mode(&mir, &callee_modes),
ReturnOwnershipMode::Shared
);
}
#[test]
fn test_call_returning_static_propagates_static() {
let mut mir = empty_mir("wrap_singleton");
mir.num_locals = 2;
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function("get_singleton".into())),
args: Vec::new(),
destination: Place::Local(SlotId(1)),
next: BasicBlockId(1),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
0,
);
mir.blocks = vec![bb0, bb1];
let mut callee_modes = HashMap::new();
callee_modes.insert(
"get_singleton".to_string(),
ReturnOwnershipMode::Static,
);
assert_eq!(
infer_return_ownership_mode(&mir, &callee_modes),
ReturnOwnershipMode::Static
);
}
#[test]
fn test_arc_param_passthrough_stays_borrowed_not_shared() {
let mut mir = empty_mir("passthrough_arc");
mir.num_locals = 2;
mir.param_slots = vec![SlotId(1)];
mir.param_reference_kinds = vec![None];
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy];
let mut bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb0,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
0,
);
mir.blocks.push(bb0);
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::BorrowedFromParam(0)
);
}
#[test]
fn test_static_and_newly_owned_branches_meet_to_unknown() {
let mut mir = empty_mir("mixed");
mir.num_locals = 3;
mir.param_slots = vec![SlotId(2)];
mir.param_reference_kinds = vec![None];
mir.local_types = vec![
LocalTypeInfo::NonCopy,
LocalTypeInfo::NonCopy,
LocalTypeInfo::Copy,
];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(SlotId(2))),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Str("hi".into()))),
),
0,
);
let mut bb2 = BasicBlock {
id: BasicBlockId(2),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![Operand::Constant(MirConstant::Int(1))]),
),
1,
);
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Move(Place::Local(SlotId(1)))),
),
2,
);
mir.blocks = vec![bb0, bb1, bb2];
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Unknown
);
}
#[test]
fn test_two_static_branches_meet_to_static() {
let mut mir = empty_mir("pick_word");
mir.num_locals = 2;
mir.param_slots = vec![SlotId(1)];
mir.param_reference_kinds = vec![None];
mir.local_types = vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(SlotId(1))),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Str("yes".into()))),
),
0,
);
let mut bb2 = BasicBlock {
id: BasicBlockId(2),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Str("no".into()))),
),
1,
);
mir.blocks = vec![bb0, bb1, bb2];
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::Static
);
}
#[test]
fn test_meet_shared_and_static_collapses_to_unknown() {
assert_eq!(
ReturnOwnershipMode::Shared.meet(ReturnOwnershipMode::Shared),
ReturnOwnershipMode::Shared
);
assert_eq!(
ReturnOwnershipMode::Static.meet(ReturnOwnershipMode::Static),
ReturnOwnershipMode::Static
);
assert_eq!(
ReturnOwnershipMode::Shared.meet(ReturnOwnershipMode::Static),
ReturnOwnershipMode::Unknown
);
assert_eq!(
ReturnOwnershipMode::Static.meet(ReturnOwnershipMode::NewlyOwned),
ReturnOwnershipMode::Unknown
);
assert_eq!(
ReturnOwnershipMode::Shared.meet(ReturnOwnershipMode::NewlyOwned),
ReturnOwnershipMode::Unknown
);
}
#[test]
fn test_int_conditional_return_stays_newly_owned() {
let mut mir = empty_mir("return_unknown");
mir.num_locals = 2;
mir.param_slots = vec![SlotId(1)];
mir.param_reference_kinds = vec![None];
mir.local_types = vec![LocalTypeInfo::Copy, LocalTypeInfo::Copy];
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: Vec::new(),
terminator: Terminator {
kind: TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(SlotId(1))),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
},
span: dummy_span(),
},
};
let mut bb1 = BasicBlock {
id: BasicBlockId(1),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb1,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
);
let mut bb2 = BasicBlock {
id: BasicBlockId(2),
statements: Vec::new(),
terminator: return_terminator(),
};
push_stmt(
&mut bb2,
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
1,
);
mir.blocks = vec![bb0, bb1, bb2];
assert_eq!(
infer_return_ownership_mode(&mir, &HashMap::new()),
ReturnOwnershipMode::NewlyOwned
);
}
fn infer_from_source(
code: &str,
) -> std::collections::HashMap<String, ReturnOwnershipMode> {
use shape_ast::ast::Item;
let program = shape_ast::parser::parse_program(code).expect("parse failed");
let mut modes = std::collections::HashMap::new();
for item in &program.items {
if let Item::Function(def, _) = item {
let lowering = crate::mir::lowering::lower_function_detailed(
&def.name,
&def.params,
&def.body,
def.name_span,
);
let mode = infer_return_ownership_mode(&lowering.mir, &modes);
modes.insert(def.name.clone(), mode);
}
}
modes
}
fn mode_of(
modes: &std::collections::HashMap<String, ReturnOwnershipMode>,
name: &str,
) -> ReturnOwnershipMode {
modes.get(name).copied().unwrap_or(ReturnOwnershipMode::Unknown)
}
#[test]
fn test_source_array_literal_return_is_newly_owned() {
let modes = infer_from_source("fn make() -> Array<int> { [1, 2, 3] }");
assert_eq!(mode_of(&modes, "make"), ReturnOwnershipMode::NewlyOwned);
}
#[test]
fn test_source_int_constant_return_is_newly_owned() {
let modes = infer_from_source("fn answer() -> int { 42 }");
assert_eq!(mode_of(&modes, "answer"), ReturnOwnershipMode::NewlyOwned);
}
#[test]
fn test_source_arith_return_is_newly_owned() {
let modes = infer_from_source("fn add(a: int, b: int) -> int { a + b }");
assert_eq!(mode_of(&modes, "add"), ReturnOwnershipMode::NewlyOwned);
}
#[test]
fn test_source_passthrough_is_borrowed_from_param() {
let modes = infer_from_source("fn pass(x: Array<int>) -> Array<int> { x }");
assert_eq!(
mode_of(&modes, "pass"),
ReturnOwnershipMode::BorrowedFromParam(0)
);
}
#[test]
fn test_source_if_both_branches_allocate_is_newly_owned() {
let modes = infer_from_source(
"fn choose(cond: bool) -> Array<int> { if cond { [1] } else { [2] } }",
);
assert_eq!(mode_of(&modes, "choose"), ReturnOwnershipMode::NewlyOwned);
}
#[test]
fn test_source_route_between_params_meets_to_unknown() {
let modes = infer_from_source(
"fn route(cond: bool, a: Array<int>, b: Array<int>) -> Array<int> { if cond { a } else { b } }",
);
assert_eq!(mode_of(&modes, "route"), ReturnOwnershipMode::Unknown);
}
#[test]
fn test_source_call_through_inherits_callee_mode() {
let modes = infer_from_source(
r#"
fn make() -> Array<int> { [1, 2, 3] }
fn wrap() -> Array<int> { make() }
"#,
);
assert_eq!(mode_of(&modes, "make"), ReturnOwnershipMode::NewlyOwned);
assert_eq!(mode_of(&modes, "wrap"), ReturnOwnershipMode::NewlyOwned);
}
#[test]
fn test_source_three_stage_pipeline_propagates() {
let modes = infer_from_source(
r#"
fn a() -> Array<int> { [1, 2, 3] }
fn b() -> Array<int> { a() }
fn c() -> Array<int> { b() }
"#,
);
assert_eq!(mode_of(&modes, "a"), ReturnOwnershipMode::NewlyOwned);
assert_eq!(mode_of(&modes, "b"), ReturnOwnershipMode::NewlyOwned);
assert_eq!(mode_of(&modes, "c"), ReturnOwnershipMode::NewlyOwned);
}
#[test]
fn test_source_call_without_callee_info_is_unknown() {
let modes = infer_from_source("fn wrap() -> Array<int> { external() }");
assert_eq!(mode_of(&modes, "wrap"), ReturnOwnershipMode::Unknown);
}
#[test]
fn test_source_string_literal_return_is_static() {
let modes = infer_from_source(r#"fn greet() -> string { "hello" }"#);
assert_eq!(mode_of(&modes, "greet"), ReturnOwnershipMode::Static);
}
#[test]
fn test_source_object_literal_return_is_newly_owned() {
let modes = infer_from_source(
r#"
type Pair { a: int, b: int }
fn make_pair() -> Pair { Pair { a: 1, b: 2 } }
"#,
);
assert_eq!(
mode_of(&modes, "make_pair"),
ReturnOwnershipMode::NewlyOwned
);
}
}