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
4use crate::Cli;
5use by_address::ByAddress;
6use 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};
14use std::{
15 cell::RefCell,
16 collections::{HashMap, HashSet},
17 io::Write,
18 rc::Rc,
19};
20
21#[derive(Clone, Default)]
22pub 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
39pub(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`
109fn 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
202fn 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
256pub(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
276pub(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
315fn 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
327pub(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