1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
3 | |
4 | use crate::Cli; |
5 | use by_address::ByAddress; |
6 | use i_slint_compiler::{ |
7 | expression_tree::{Callable, Expression}, |
8 | langtype::Type, |
9 | lookup::{LookupCtx, LookupObject, LookupResult, LookupResultCallable}, |
10 | namedreference::NamedReference, |
11 | object_tree::ElementRc, |
12 | parser::{SyntaxKind, SyntaxNode}, |
13 | }; |
14 | use smol_str::{format_smolstr, SmolStr}; |
15 | use std::{ |
16 | cell::RefCell, |
17 | collections::{HashMap, HashSet}, |
18 | io::Write, |
19 | rc::Rc, |
20 | }; |
21 | |
22 | #[derive (Clone, Default)] |
23 | pub struct LookupChangeState { |
24 | /// the lookup change pass will map that property reference to another |
25 | property_mappings: HashMap<NamedReference, String>, |
26 | |
27 | /// What needs to be added before the closing brace of the component |
28 | extra_component_stuff: Rc<RefCell<Vec<u8>>>, |
29 | |
30 | /// Replace `self.` with that id |
31 | replace_self: Option<SmolStr>, |
32 | |
33 | /// Elements that should get an id |
34 | elements_id: HashMap<ByAddress<ElementRc>, SmolStr>, |
35 | |
36 | /// the full lookup scope |
37 | pub scope: Vec<ElementRc>, |
38 | } |
39 | |
40 | pub(crate) fn fold_node( |
41 | node: &SyntaxNode, |
42 | file: &mut impl Write, |
43 | state: &mut crate::State, |
44 | args: &Cli, |
45 | ) -> std::io::Result<bool> { |
46 | let kind = node.kind(); |
47 | if kind == SyntaxKind::QualifiedName |
48 | && node.parent().is_some_and(|n| n.kind() == SyntaxKind::Expression) |
49 | { |
50 | return fully_qualify_property_access(node, file, state); |
51 | } else if kind == SyntaxKind::Element |
52 | && node.parent().is_some_and(|n| n.kind() == SyntaxKind::Component) |
53 | { |
54 | return move_properties_to_root(node, state, file, args); |
55 | } else if kind == SyntaxKind::Element { |
56 | if let Some(new_id) = state |
57 | .current_elem |
58 | .as_ref() |
59 | .and_then(|e| state.lookup_change.elements_id.get(&ByAddress(e.clone()))) |
60 | { |
61 | write!(file, " {new_id} := " )?; |
62 | } |
63 | } else if matches!( |
64 | kind, |
65 | SyntaxKind::Binding | SyntaxKind::TwoWayBinding | SyntaxKind::CallbackConnection |
66 | ) && !state.lookup_change.property_mappings.is_empty() |
67 | && node.parent().is_some_and(|n| n.kind() == SyntaxKind::Element) |
68 | { |
69 | if let Some(el) = &state.current_elem { |
70 | let prop_name = i_slint_compiler::parser::normalize_identifier( |
71 | node.child_token(SyntaxKind::Identifier).unwrap().text(), |
72 | ); |
73 | let nr = NamedReference::new(el, prop_name); |
74 | if let Some(new_name) = state.lookup_change.property_mappings.get(&nr).cloned() { |
75 | state.lookup_change.replace_self = Some( |
76 | state |
77 | .lookup_change |
78 | .elements_id |
79 | .get(&ByAddress(el.clone())) |
80 | .map_or_else(|| el.borrow().id.clone(), |s| s.clone()), |
81 | ); |
82 | for n in node.children_with_tokens() { |
83 | let extra = state.lookup_change.extra_component_stuff.clone(); |
84 | if n.kind() == SyntaxKind::Identifier { |
85 | write!(&mut *extra.borrow_mut(), " {new_name}" )?; |
86 | } else { |
87 | crate::visit_node_or_token(n, &mut *extra.borrow_mut(), state, args)?; |
88 | } |
89 | } |
90 | state.lookup_change.replace_self = None; |
91 | return Ok(true); |
92 | } |
93 | } |
94 | } else if matches!(kind, SyntaxKind::PropertyDeclaration | SyntaxKind::CallbackDeclaration) { |
95 | if let Some(el) = &state.current_elem { |
96 | let prop_name = i_slint_compiler::parser::normalize_identifier( |
97 | &node.child_node(SyntaxKind::DeclaredIdentifier).unwrap().text().to_string(), |
98 | ); |
99 | let nr = NamedReference::new(el, prop_name); |
100 | if state.lookup_change.property_mappings.contains_key(&nr) { |
101 | return Ok(true); |
102 | } |
103 | } |
104 | } |
105 | Ok(false) |
106 | } |
107 | |
108 | /// Make sure that a qualified name is fully qualified with `self.`. |
109 | /// Also rename the property in `state.lookup_change.property_mappings` |
110 | fn fully_qualify_property_access( |
111 | node: &SyntaxNode, |
112 | file: &mut impl Write, |
113 | state: &mut crate::State, |
114 | ) -> std::io::Result<bool> { |
115 | let mut it = node |
116 | .children_with_tokens() |
117 | .filter(|n| n.kind() == SyntaxKind::Identifier) |
118 | .filter_map(|n| n.into_token()); |
119 | let first = match it.next() { |
120 | Some(first) => first, |
121 | None => return Ok(false), |
122 | }; |
123 | let first_str = i_slint_compiler::parser::normalize_identifier(first.text()); |
124 | with_lookup_ctx(state, |ctx| -> std::io::Result<bool> { |
125 | ctx.current_token = Some(first.clone().into()); |
126 | let global_lookup = i_slint_compiler::lookup::global_lookup(); |
127 | match global_lookup.lookup(ctx, &first_str) { |
128 | Some( |
129 | LookupResult::Expression { expression: Expression::PropertyReference(nr), .. } |
130 | | LookupResult::Callable(LookupResultCallable::Callable( |
131 | Callable::Callback(nr) | Callable::Function(nr), |
132 | )), |
133 | ) => { |
134 | if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) { |
135 | write!(file, "root. {new_name} " )?; |
136 | Ok(true) |
137 | } else { |
138 | let element = nr.element(); |
139 | if state |
140 | .current_component |
141 | .as_ref() |
142 | .is_some_and(|c| Rc::ptr_eq(&element, &c.root_element)) |
143 | { |
144 | write!(file, "root." )?; |
145 | } else if state |
146 | .lookup_change |
147 | .scope |
148 | .last() |
149 | .is_some_and(|e| Rc::ptr_eq(&element, e)) |
150 | { |
151 | if let Some(replace_self) = &state.lookup_change.replace_self { |
152 | write!(file, " {replace_self}." )?; |
153 | } else { |
154 | write!(file, "self." )?; |
155 | } |
156 | } |
157 | Ok(false) |
158 | } |
159 | } |
160 | Some(LookupResult::Expression { |
161 | expression: Expression::ElementReference(el), .. |
162 | }) => { |
163 | let second = match it.next() { |
164 | Some(second) => second, |
165 | None => return Ok(false), |
166 | }; |
167 | let prop_name = i_slint_compiler::parser::normalize_identifier(second.text()); |
168 | let nr = NamedReference::new(&el.upgrade().unwrap(), prop_name); |
169 | if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) { |
170 | write!(file, "root. {new_name} " )?; |
171 | Ok(true) |
172 | } else if let Some(replace_self) = &state.lookup_change.replace_self { |
173 | if first_str == "self" || first_str == "parent" { |
174 | if first_str == "self" { |
175 | write!(file, " {replace_self}. {second} " )?; |
176 | } else { |
177 | let replace_parent = state |
178 | .lookup_change |
179 | .elements_id |
180 | .get(&ByAddress(nr.element())) |
181 | .map_or_else(|| nr.element().borrow().id.clone(), |s| s.clone()); |
182 | write!(file, " {replace_parent}. {second} " )?; |
183 | } |
184 | for t in it { |
185 | write!(file, ". {} " , t.text())?; |
186 | } |
187 | Ok(true) |
188 | } else { |
189 | Ok(false) |
190 | } |
191 | } else { |
192 | Ok(false) |
193 | } |
194 | } |
195 | _ => Ok(false), |
196 | } |
197 | }) |
198 | .unwrap_or(Ok(false)) |
199 | } |
200 | |
201 | /// Move the properties from state.lookup_change.property_mappings to the root |
202 | fn move_properties_to_root( |
203 | node: &SyntaxNode, |
204 | state: &mut crate::State, |
205 | file: &mut impl Write, |
206 | args: &Cli, |
207 | ) -> std::io::Result<bool> { |
208 | if state.lookup_change.property_mappings.is_empty() { |
209 | return Ok(false); |
210 | } |
211 | let mut seen_brace = false; |
212 | for c in node.children_with_tokens() { |
213 | let k = c.kind(); |
214 | if k == SyntaxKind::LBrace { |
215 | seen_brace = true; |
216 | } else if seen_brace && k != SyntaxKind::Whitespace { |
217 | let property_mappings = state.lookup_change.property_mappings.clone(); |
218 | for (nr, prop) in property_mappings.iter() { |
219 | let decl = |
220 | nr.element().borrow().property_declarations.get(nr.name()).unwrap().clone(); |
221 | let n2: SyntaxNode = decl.node.clone().unwrap(); |
222 | let old_current_element = |
223 | std::mem::replace(&mut state.current_elem, Some(nr.element())); |
224 | state.lookup_change.replace_self = Some( |
225 | state |
226 | .lookup_change |
227 | .elements_id |
228 | .get(&ByAddress(nr.element())) |
229 | .map_or_else(|| nr.element().borrow().id.clone(), |s| s.clone()), |
230 | ); |
231 | for c2 in n2.children_with_tokens() { |
232 | if c2.kind() == SyntaxKind::DeclaredIdentifier { |
233 | write!(file, " {prop} " )?; |
234 | } else { |
235 | crate::visit_node_or_token(c2, file, state, args)?; |
236 | } |
237 | } |
238 | state.lookup_change.replace_self = None; |
239 | write!(file, " \n " )?; |
240 | state.current_elem = old_current_element; |
241 | } |
242 | seen_brace = false; |
243 | } |
244 | |
245 | if k == SyntaxKind::RBrace { |
246 | file.write_all(&*std::mem::take( |
247 | &mut *state.lookup_change.extra_component_stuff.borrow_mut(), |
248 | ))?; |
249 | } |
250 | crate::visit_node_or_token(c, file, state, args)?; |
251 | } |
252 | |
253 | Ok(true) |
254 | } |
255 | |
256 | pub(crate) fn with_lookup_ctx<R>( |
257 | state: &crate::State, |
258 | f: impl FnOnce(&mut LookupCtx) -> R, |
259 | ) -> Option<R> { |
260 | let mut build_diagnostics = Default::default(); |
261 | let tr: &TypeRegister = &state.current_doc.as_ref()?.local_registry; |
262 | let mut lookup_context: LookupCtx<'_> = LookupCtx::empty_context(type_register:tr, &mut build_diagnostics); |
263 | |
264 | let ty: Type = state |
265 | .current_elem |
266 | .as_ref() |
267 | .zip(state.property_name.as_ref()) |
268 | .map_or(default:Type::Invalid, |(e: &Rc>, n: &SmolStr)| e.borrow().lookup_property(name:n).property_type); |
269 | |
270 | lookup_context.property_name = state.property_name.as_ref().map(SmolStr::as_str); |
271 | lookup_context.property_type = ty; |
272 | lookup_context.component_scope = &state.lookup_change.scope; |
273 | Some(f(&mut lookup_context)) |
274 | } |
275 | |
276 | pub(crate) fn collect_movable_properties(state: &mut crate::State) { |
277 | pub fn collect_movable_properties_recursive(vec: &mut Vec<NamedReference>, elem: &ElementRc) { |
278 | for c in &elem.borrow().children { |
279 | if c.borrow().repeated.is_some() { |
280 | continue; |
281 | } |
282 | vec.extend( |
283 | c.borrow() |
284 | .property_declarations |
285 | .iter() |
286 | .map(|(name, _)| NamedReference::new(c, name.clone())), |
287 | ); |
288 | collect_movable_properties_recursive(vec, c); |
289 | } |
290 | } |
291 | if let Some(c) = &state.current_component { |
292 | let mut props_to_move = Vec::new(); |
293 | collect_movable_properties_recursive(&mut props_to_move, &c.root_element); |
294 | let mut seen = HashSet::new(); |
295 | state.lookup_change.property_mappings = props_to_move |
296 | .into_iter() |
297 | .map(|nr| { |
298 | let element = nr.element(); |
299 | ensure_element_has_id(&element, &mut state.lookup_change.elements_id); |
300 | if let Some(parent) = i_slint_compiler::object_tree::find_parent_element(&element) { |
301 | ensure_element_has_id(&parent, &mut state.lookup_change.elements_id); |
302 | } |
303 | let mut name = format!("_ {}_ {}" , element.borrow().id, nr.name()); |
304 | while !seen.insert(name.clone()) |
305 | || c.root_element.borrow().lookup_property(&name).is_valid() |
306 | { |
307 | name = format!("_ {name}" ); |
308 | } |
309 | (nr, name) |
310 | }) |
311 | .collect() |
312 | } |
313 | } |
314 | |
315 | fn ensure_element_has_id( |
316 | element: &ElementRc, |
317 | elements_id: &mut HashMap<ByAddress<ElementRc>, SmolStr>, |
318 | ) { |
319 | static ID_GEN: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); |
320 | if element.borrow().id.is_empty() { |
321 | elements_id.entry(ByAddress(element.clone())).or_insert_with(|| { |
322 | format_smolstr!("_ {}" , ID_GEN.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) |
323 | }); |
324 | } |
325 | } |
326 | |
327 | pub(crate) fn enter_element(state: &mut crate::State) { |
328 | if state.lookup_change.scope.last().is_some_and(|e: &Rc>| e.borrow().base_type.to_string() == "Path" ) |
329 | { |
330 | // Path's sub-elements have strange lookup rules: They are considering self as the Path |
331 | state.lookup_change.replace_self = Some("parent" .into()); |
332 | } else { |
333 | state.lookup_change.scope.extend(iter:state.current_elem.iter().cloned()) |
334 | } |
335 | } |
336 | |