Skip to main content

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}