1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! Method call receiver codegen (object expr + recv fixes).
use crate::parser::Expression;
use crate::codegen::rust::CodeGenerator;
impl<'ast> CodeGenerator<'ast> {
#[allow(clippy::too_many_lines)]
pub(in crate::codegen::rust) fn mc_build_method_receiver_string(
&mut self,
object: &Expression<'ast>,
method: &str,
) -> String {
// METHOD CALL CONTEXT: Suppress Vec index auto-clone when generating the
// object of a method call. Methods take &self or &mut self, so Rust allows
// calling methods on &T returned by Vec indexing without cloning.
// e.g., self.lights[i].is_enabled() → no need to clone the whole Light2D
let prev_field_access = self.in_field_access_object;
self.in_field_access_object = true;
// DOUBLE-CLONE FIX: When the source has explicit .clone(), suppress auto-clone
// on the object to prevent .clone().clone(). The explicit clone IS the clone.
let prev_explicit_clone = self.in_explicit_clone_call;
if method == "clone" {
self.in_explicit_clone_call = true;
}
// Suppress .into() coercion on receiver of .to_string() / .to_owned() / .clone()
// since those methods already produce an owned value.
let prev_coerce = self.coerce_string_literals_to_owned;
if matches!(method, "clone" | "to_owned" | "to_vec" | "into_iter") || method == "to_string"
{
self.coerce_string_literals_to_owned = false;
}
let mut obj_str = self.generate_expression_with_precedence(object);
self.coerce_string_literals_to_owned = prev_coerce;
self.in_field_access_object = prev_field_access;
self.in_explicit_clone_call = prev_explicit_clone;
// E0507: `collection[i].method(args)` on non-Copy elements must clone before the call.
// Rust cannot move out of a Vec index. Even when the registry says `&self`, library
// multipass metadata can disagree with the emitted receiver (`self` vs `&self`), so
// always clone indexed non-Copy receivers (extra clone on `&self` methods is correct).
if matches!(object, Expression::Index { .. }) && !obj_str.ends_with(".clone()") {
let is_copy = self
.infer_expression_type(object)
.as_ref()
.is_some_and(|t| self.is_type_copy(t));
if !is_copy {
obj_str = format!("{}.clone()", obj_str);
}
}
// E0507: `borrowed_var.method(args)` when the method consumes `self` (owned receiver)
// and the variable is a borrowed iterator variable (from `for x in &collection`).
// Must clone: `condition.clone().evaluate(state)` instead of `condition.evaluate(state)`.
if let Expression::Identifier { name, .. } = object {
let is_type_preserving =
matches!(method, "clone" | "to_owned" | "to_vec" | "into_iter");
let is_borrowed_iter =
self.borrowed_iterator_vars.contains(name) && !is_type_preserving;
let is_mut_borrowed_param =
self.inferred_mut_borrowed_params.contains(name) && !is_type_preserving;
if is_borrowed_iter || is_mut_borrowed_param {
if let Some(recv_ty) = self.infer_expression_type(object) {
if !self.is_type_copy(&recv_ty) {
if let Some(tn) = Self::type_to_name(&recv_ty) {
let qualified = format!("{}::{}", tn, method);
let sig_opt = self
.signature_registry
.get_signature(&qualified)
.or_else(|| {
let base = tn.split('<').next().unwrap_or(&tn);
if base != tn {
let base_q = format!("{base}::{method}");
self.signature_registry.get_signature(&base_q)
} else {
None
}
})
.or_else(|| {
// For `Box<dyn Trait>`, extract the trait name from
// the Parameterized type and look up `Trait::method`.
Self::extract_dyn_trait_name(&recv_ty).and_then(|trait_name| {
let trait_q = format!("{trait_name}::{method}");
self.signature_registry.get_signature(&trait_q)
})
})
.or_else(|| {
// Suffix match: find any `::method` in the registry.
// Conservative for ownership check (any mutating method
// prevents spurious clone).
self.signature_registry
.find_signature_ending_with(&format!("::{method}"))
});
if let Some(sig) = sig_opt {
if sig.has_self_receiver
&& sig.param_ownership.first()
== Some(&crate::analyzer::OwnershipMode::Owned)
&& !obj_str.ends_with(".clone()")
{
obj_str = format!("{}.clone()", obj_str);
}
} else if is_borrowed_iter
&& !is_mut_borrowed_param
&& !obj_str.ends_with(".clone()")
{
// Unknown signature on borrowed iterator var — clone conservatively (E0507).
obj_str = format!("{}.clone()", obj_str);
}
// For &mut params with unknown signatures, do NOT clone.
// &mut refs can call &self and &mut self methods without cloning.
}
}
}
}
}
// DOUBLE-CLONE SAFETY NET: If the object was auto-cloned by the FieldAccess
// handler and this IS a .clone() call, strip the redundant auto-clone.
// e.g., "stack.item.clone()" from auto-clone + ".clone()" from source
// → should be "stack.item.clone()", not "stack.item.clone().clone()"
if method == "clone" && obj_str.ends_with(".clone()") {
obj_str = obj_str[..obj_str.len() - 8].to_string();
}
// TDD FIX: Option::unwrap() move error prevention
// TDD FIX: AUTO-CLONE Option::unwrap() on borrowed fields
// When calling .unwrap() on a borrowed Option field, we must clone before unwrap:
// node.children.unwrap() where node is &Node → ERROR: cannot move from &Option
// node.children.clone().unwrap() → ✅ OK
// THE WINDJAMMER WAY: Users write .unwrap() naturally, compiler handles ownership
if matches!(method, "unwrap" | "first" | "last") {
// Check if object is a field access (node.children) that needs clone
let needs_clone = if let Expression::FieldAccess {
object: field_obj, ..
} = object
{
// Is this accessing a field on a borrowed parameter?
if let Expression::Identifier { ref name, .. } = **field_obj {
// Check if the identifier is an inferred borrowed parameter
self.inferred_borrowed_params.contains(name)
} else {
false
}
} else {
false
};
if needs_clone && !obj_str.contains(".clone()") {
obj_str = format!("{}.clone()", obj_str);
}
}
// E0507 fix: Option::map on self.field with &self must use .as_ref().map(...)
// self.children.map(|c| ...) with &self → self.children.as_ref().map(|c| ...)
if method == "map"
&& self.inferred_borrowed_params.contains("self")
&& self.codegen_expression_traces_to_self(object)
&& !obj_str.contains(".as_ref()")
{
obj_str = format!("{}.as_ref()", obj_str);
}
obj_str
}
/// Extract the trait name from `Box<dyn Trait>`, `dyn Trait`, or `TraitObject("Trait")`.
fn extract_dyn_trait_name(ty: &crate::parser::Type) -> Option<&str> {
use crate::parser::Type;
match ty {
Type::Reference(inner) | Type::MutableReference(inner) => {
Self::extract_dyn_trait_name(inner)
}
Type::Parameterized(name, params) if name == "Box" => params
.first()
.and_then(|inner| Self::extract_dyn_trait_name(inner)),
Type::TraitObject(name) => Some(name.as_str()),
Type::Custom(name) => name.strip_prefix("dyn "),
_ => None,
}
}
}