1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | use crate::Cli; |
5 | use by_address::ByAddress; |
6 | use i_slint_compiler::{ |
7 | expression_tree::Expression, |
8 | langtype::Type, |
9 | lookup::{LookupCtx, LookupObject, LookupResult}, |
10 | namedreference::NamedReference, |
11 | object_tree::ElementRc, |
12 | parser::{SyntaxKind, SyntaxNode}, |
13 | }; |
14 | use std::{ |
15 | cell::RefCell, |
16 | collections::{HashMap, HashSet}, |
17 | io::Write, |
18 | rc::Rc, |
19 | }; |
20 | |
21 | #[derive (Clone, Default)] |
22 | pub struct LookupChangeState { |
23 | /// the lookup change pass will map that property reference to another |
24 | property_mappings: HashMap<NamedReference, String>, |
25 | |
26 | /// What needs to be added before the closing brace of the component |
27 | extra_component_stuff: Rc<RefCell<Vec<u8>>>, |
28 | |
29 | /// Replace `self.` with that id |
30 | replace_self: Option<String>, |
31 | |
32 | /// Elements that should get an id |
33 | elements_id: HashMap<ByAddress<ElementRc>, String>, |
34 | |
35 | /// the full lookup scope |
36 | pub scope: Vec<ElementRc>, |
37 | } |
38 | |
39 | pub(crate) fn fold_node( |
40 | node: &SyntaxNode, |
41 | file: &mut impl Write, |
42 | state: &mut crate::State, |
43 | args: &Cli, |
44 | ) -> std::io::Result<bool> { |
45 | let kind = node.kind(); |
46 | if kind == SyntaxKind::QualifiedName |
47 | && node.parent().map_or(false, |n| n.kind() == SyntaxKind::Expression) |
48 | { |
49 | return fully_qualify_property_access(node, file, state); |
50 | } else if kind == SyntaxKind::Element |
51 | && node.parent().map_or(false, |n| n.kind() == SyntaxKind::Component) |
52 | { |
53 | return move_properties_to_root(node, state, file, args); |
54 | } else if kind == SyntaxKind::Element { |
55 | if let Some(new_id) = state |
56 | .current_elem |
57 | .as_ref() |
58 | .and_then(|e| state.lookup_change.elements_id.get(&ByAddress(e.clone()))) |
59 | { |
60 | write!(file, " {new_id} := " )?; |
61 | } |
62 | } else if matches!( |
63 | kind, |
64 | SyntaxKind::Binding | SyntaxKind::TwoWayBinding | SyntaxKind::CallbackConnection |
65 | ) && !state.lookup_change.property_mappings.is_empty() |
66 | && node.parent().map_or(false, |n| n.kind() == SyntaxKind::Element) |
67 | { |
68 | if let Some(el) = &state.current_elem { |
69 | let prop_name = i_slint_compiler::parser::normalize_identifier( |
70 | node.child_token(SyntaxKind::Identifier).unwrap().text(), |
71 | ); |
72 | let nr = NamedReference::new(el, &prop_name); |
73 | if let Some(new_name) = state.lookup_change.property_mappings.get(&nr).cloned() { |
74 | state.lookup_change.replace_self = Some( |
75 | state |
76 | .lookup_change |
77 | .elements_id |
78 | .get(&ByAddress(el.clone())) |
79 | .map_or_else(|| el.borrow().id.clone(), |s| s.clone()), |
80 | ); |
81 | for n in node.children_with_tokens() { |
82 | let extra = state.lookup_change.extra_component_stuff.clone(); |
83 | if n.kind() == SyntaxKind::Identifier { |
84 | write!(&mut *extra.borrow_mut(), " {new_name}" )?; |
85 | } else { |
86 | crate::visit_node_or_token(n, &mut *extra.borrow_mut(), state, args)?; |
87 | } |
88 | } |
89 | state.lookup_change.replace_self = None; |
90 | return Ok(true); |
91 | } |
92 | } |
93 | } else if matches!(kind, SyntaxKind::PropertyDeclaration | SyntaxKind::CallbackDeclaration) { |
94 | if let Some(el) = &state.current_elem { |
95 | let prop_name = i_slint_compiler::parser::normalize_identifier( |
96 | &node.child_node(SyntaxKind::DeclaredIdentifier).unwrap().text().to_string(), |
97 | ); |
98 | let nr = NamedReference::new(el, &prop_name); |
99 | if state.lookup_change.property_mappings.contains_key(&nr) { |
100 | return Ok(true); |
101 | } |
102 | } |
103 | } |
104 | Ok(false) |
105 | } |
106 | |
107 | /// Make sure that a qualified name is fully qualified with `self.`. |
108 | /// Also rename the property in `state.lookup_change.property_mappings` |
109 | fn fully_qualify_property_access( |
110 | node: &SyntaxNode, |
111 | file: &mut impl Write, |
112 | state: &mut crate::State, |
113 | ) -> std::io::Result<bool> { |
114 | let mut it = node |
115 | .children_with_tokens() |
116 | .filter(|n| n.kind() == SyntaxKind::Identifier) |
117 | .filter_map(|n| n.into_token()); |
118 | let first = match it.next() { |
119 | Some(first) => first, |
120 | None => return Ok(false), |
121 | }; |
122 | let first_str = i_slint_compiler::parser::normalize_identifier(first.text()); |
123 | with_lookup_ctx(state, |ctx| -> std::io::Result<bool> { |
124 | ctx.current_token = Some(first.clone().into()); |
125 | let global_lookup = i_slint_compiler::lookup::global_lookup(); |
126 | match global_lookup.lookup(ctx, &first_str) { |
127 | Some(LookupResult::Expression { |
128 | expression: |
129 | Expression::PropertyReference(nr) |
130 | | Expression::CallbackReference(nr, _) |
131 | | Expression::FunctionReference(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 | .map_or(false, |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 | .map_or(false, |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: BuildDiagnostics = 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: &String)| e.borrow().lookup_property(&n).property_type); |
269 | |
270 | lookup_context.property_name = state.property_name.as_ref().map(String::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)), |
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>, String>, |
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!("_ {}" , 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 |
329 | .lookup_change |
330 | .scope |
331 | .last() |
332 | .map_or(default:false, |e: &Rc>| e.borrow().base_type.to_string() == "Path" ) |
333 | { |
334 | // Path's sub-elements have strange lookup rules: They are considering self as the Path |
335 | state.lookup_change.replace_self = Some("parent" .into()); |
336 | } else { |
337 | state.lookup_change.scope.extend(iter:state.current_elem.iter().cloned()) |
338 | } |
339 | } |
340 | |