aver/codegen/rust/emit_ctx.rs
1/// Rust-specific emission context for type and borrow policy.
2///
3/// Clone/move decisions now come from `last_use` annotations on `Expr::Resolved`
4/// nodes (set by `ir::last_use`), NOT from name-based liveness sets. EmitCtx
5/// provides only Rust-specific policy: Copy types, borrow semantics, Rc wrapping.
6use crate::ast::Expr;
7use crate::types::Type;
8use std::collections::{HashMap, HashSet};
9
10/// Emission context carrying Rust-specific type/borrow policy.
11#[derive(Clone)]
12pub struct EmitCtx {
13 /// Local variable types (from fn params) for copy-type elision.
14 pub local_types: HashMap<String, Type>,
15 /// Parameters passed as `Rc<T>` (self-TCO) or `&T` (mutual-TCO pass-through).
16 pub rc_wrapped: HashSet<String>,
17 /// Parameters emitted as `&T` borrows (borrow-by-default for non-Copy, non-Str params).
18 pub borrowed_params: HashSet<String>,
19}
20
21impl EmitCtx {
22 /// Empty context — conservative (clones everything non-Copy).
23 pub fn empty() -> Self {
24 EmitCtx {
25 local_types: HashMap::new(),
26 rc_wrapped: HashSet::new(),
27 borrowed_params: HashSet::new(),
28 }
29 }
30
31 /// Build context for a function with known parameter types.
32 /// Automatically computes `borrowed_params` from param types.
33 pub fn for_fn(param_types: HashMap<String, Type>) -> Self {
34 let borrowed_params = param_types
35 .iter()
36 .filter(|(_, ty)| should_borrow_param(ty))
37 .map(|(name, _)| name.clone())
38 .collect();
39 EmitCtx {
40 local_types: param_types,
41 rc_wrapped: HashSet::new(),
42 borrowed_params,
43 }
44 }
45
46 /// Build context for a function WITHOUT borrow-by-default (e.g. TCO, memo).
47 pub fn for_fn_no_borrow(param_types: HashMap<String, Type>) -> Self {
48 EmitCtx {
49 local_types: param_types,
50 rc_wrapped: HashSet::new(),
51 borrowed_params: HashSet::new(),
52 }
53 }
54
55 /// Is this variable a Copy type in Rust (i64, f64, bool, ())?
56 pub fn is_copy(&self, name: &str) -> bool {
57 self.local_types.get(name).is_some_and(is_copy_type)
58 }
59
60 /// Is this variable a pass-through parameter (Rc<T> in self-TCO, &T in mutual-TCO)?
61 pub fn is_rc_wrapped(&self, name: &str) -> bool {
62 self.rc_wrapped.contains(name)
63 }
64
65 /// Is this variable a borrowed parameter (`&T` from borrow-by-default)?
66 pub fn is_borrowed_param(&self, name: &str) -> bool {
67 self.borrowed_params.contains(name)
68 }
69
70 /// Create a context with specified Rc-wrapped parameters (TCO pass-through).
71 pub fn with_rc_wrapped(&self, rc: HashSet<String>) -> Self {
72 EmitCtx {
73 local_types: self.local_types.clone(),
74 rc_wrapped: rc,
75 borrowed_params: self.borrowed_params.clone(),
76 }
77 }
78}
79
80// ── Expression-level move/clone decisions ───────────────────────────────
81
82/// Can this expression be moved (not cloned)?
83/// Checks `last_use` on Resolved nodes; Ident without local_types entry
84/// is treated as a global (always moveable).
85pub fn expr_can_move(expr: &Expr) -> bool {
86 match expr {
87 Expr::Resolved { last_use, .. } => last_use.0,
88 Expr::Ident(_) => true, // globals/namespaces never need clone
89 _ => false,
90 }
91}
92
93/// Should `.clone()` be skipped for this expression?
94/// True for: Copy types, last-use locals, globals/namespaces.
95/// False for: rc_wrapped, borrowed_params (need special clone paths).
96///
97/// For `Expr::Ident`: in Rust codegen, Ident is used for both globals AND locals
98/// (resolver doesn't run). Check ectx to distinguish:
99/// - If name is in local_types → it's a local/param, apply rc_wrapped/borrowed checks
100/// - If name is NOT in local_types → it's a global/namespace, skip clone
101pub fn expr_skip_clone(expr: &Expr, ectx: &EmitCtx) -> bool {
102 match expr {
103 Expr::Resolved { name, last_use, .. } => {
104 if ectx.rc_wrapped.contains(name.as_str()) {
105 return false;
106 }
107 if ectx.borrowed_params.contains(name.as_str()) {
108 return false;
109 }
110 last_use.0 || ectx.is_copy(name)
111 }
112 Expr::Ident(name) => {
113 // If not a known local, treat as global/namespace — skip clone
114 if !ectx.local_types.contains_key(name.as_str()) {
115 return true;
116 }
117 // Known local: check special categories
118 if ectx.rc_wrapped.contains(name.as_str()) {
119 return false;
120 }
121 if ectx.borrowed_params.contains(name.as_str()) {
122 return false;
123 }
124 // Without last_use info, only skip for Copy types
125 ectx.is_copy(name)
126 }
127 _ => false,
128 }
129}
130
131// ── Rust-specific policy ──────────────────────────────────────────────
132
133/// Is a Type Copy in Rust? (Int, Float, Bool, Unit)
134pub fn is_copy_type(ty: &Type) -> bool {
135 matches!(ty, Type::Int | Type::Float | Type::Bool | Type::Unit)
136}
137
138/// Should this param be borrowed (`&T`) instead of owned?
139pub fn should_borrow_param(ty: &Type) -> bool {
140 matches!(
141 ty,
142 Type::Map(_, _)
143 | Type::List(_)
144 | Type::Vector(_)
145 | Type::Result(_, _)
146 | Type::Option(_)
147 | Type::Tuple(_)
148 | Type::Named(_)
149 )
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_is_copy_type() {
158 assert!(is_copy_type(&Type::Int));
159 assert!(is_copy_type(&Type::Float));
160 assert!(is_copy_type(&Type::Bool));
161 assert!(is_copy_type(&Type::Unit));
162 assert!(!is_copy_type(&Type::Str));
163 assert!(!is_copy_type(&Type::List(Box::new(Type::Int))));
164 assert!(!is_copy_type(&Type::Named("Foo".to_string())));
165 }
166}