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