Skip to main content

i_slint_compiler/
namedreference.rs

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/*!
5This module contains the [`NamedReference`] and its helper
6*/
7
8use smol_str::SmolStr;
9use std::cell::RefCell;
10use std::collections::HashMap;
11use std::hash::Hash;
12use std::rc::{Rc, Weak};
13
14use crate::langtype::{ElementType, Type};
15use crate::object_tree::{Element, ElementRc, PropertyAnalysis, PropertyVisibility};
16
17/// Reference to a property or callback of a given name within an element.
18#[derive(Clone)]
19pub struct NamedReference(Rc<NamedReferenceInner>);
20
21pub fn pretty_print_element_ref(
22    f: &mut dyn std::fmt::Write,
23    element: &Weak<RefCell<Element>>,
24) -> std::fmt::Result {
25    match element.upgrade() {
26        Some(e) => match e.try_borrow() {
27            Ok(el) => write!(f, "{}", el.id),
28            Err(_) => write!(f, "<borrowed>"),
29        },
30        None => write!(f, "<null>"),
31    }
32}
33
34impl std::fmt::Debug for NamedReference {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        pretty_print_element_ref(f, &self.0.element)?;
37        write!(f, ".{}", self.0.name)
38    }
39}
40
41impl NamedReference {
42    pub fn new(element: &ElementRc, name: SmolStr) -> Self {
43        Self(NamedReferenceInner::from_name(element, name))
44    }
45    pub(crate) fn snapshot(&self, snapshotter: &mut crate::typeloader::Snapshotter) -> Self {
46        NamedReference(Rc::new(self.0.snapshot(snapshotter)))
47    }
48    pub fn name(&self) -> &SmolStr {
49        &self.0.name
50    }
51    #[track_caller]
52    pub fn element(&self) -> ElementRc {
53        self.0
54            .element
55            .upgrade()
56            .unwrap_or_else(|| panic!("{}: NamedReference to a dead element", self.0.name))
57    }
58    pub fn ty(&self) -> Type {
59        self.element().borrow().lookup_property(self.name()).property_type
60    }
61
62    /// return true if the property has a constant value for the lifetime of the program
63    pub fn is_constant(&self) -> bool {
64        self.is_constant_impl(true)
65    }
66
67    /// return true if we know that this property is changed by other means than its own binding
68    pub fn is_externally_modified(&self) -> bool {
69        !self.is_constant_impl(false)
70    }
71
72    /// return true if the property has a constant value for the lifetime of the program
73    fn is_constant_impl(&self, mut check_binding: bool) -> bool {
74        let mut elem = self.element();
75        let e = elem.borrow();
76        if let Some(decl) = e.property_declarations.get(self.name())
77            && decl.expose_in_public_api
78            && decl.visibility != PropertyVisibility::Input
79        {
80            // could be set by the public API
81            return false;
82        }
83        if e.property_analysis.borrow().get(self.name()).is_some_and(|a| a.is_set_externally) {
84            return false;
85        }
86        drop(e);
87
88        loop {
89            let e = elem.borrow();
90            if e.property_analysis.borrow().get(self.name()).is_some_and(|a| a.is_set) {
91                // if the property is set somewhere, it is not constant
92                return false;
93            }
94
95            if let Some(b) = e.bindings.get(self.name()) {
96                if check_binding && !b.borrow().analysis.as_ref().is_some_and(|a| a.is_const) {
97                    return false;
98                }
99                if !b.borrow().two_way_bindings.iter().all(|n| n.property.is_constant()) {
100                    return false;
101                }
102                check_binding = false;
103            }
104            if let Some(decl) = e.property_declarations.get(self.name()) {
105                if let Some(alias) = &decl.is_alias {
106                    return alias.is_constant();
107                }
108                return true;
109            }
110            match &e.base_type {
111                ElementType::Component(c) => {
112                    let next = c.root_element.clone();
113                    drop(e);
114                    elem = next;
115                    continue;
116                }
117                ElementType::Builtin(b) => {
118                    return b.properties.get(self.name()).is_none_or(|pi| !pi.is_native_output());
119                }
120                ElementType::Native(n) => {
121                    return n.properties.get(self.name()).is_none_or(|pi| !pi.is_native_output());
122                }
123                crate::langtype::ElementType::Error
124                | crate::langtype::ElementType::Global
125                | crate::langtype::ElementType::Interface => {
126                    return true;
127                }
128            }
129        }
130    }
131
132    /// Mark that this property is set  somewhere in the code
133    pub fn mark_as_set(&self) {
134        let element = self.element();
135        element
136            .borrow()
137            .property_analysis
138            .borrow_mut()
139            .entry(self.name().clone())
140            .or_default()
141            .is_set = true;
142        mark_property_set_derived_in_base(element, self.name())
143    }
144}
145
146impl Eq for NamedReference {}
147
148impl PartialEq for NamedReference {
149    fn eq(&self, other: &Self) -> bool {
150        Rc::ptr_eq(&self.0, &other.0)
151    }
152}
153
154impl Hash for NamedReference {
155    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
156        Rc::as_ptr(&self.0).hash(state);
157    }
158}
159
160struct NamedReferenceInner {
161    /// The element.
162    element: Weak<RefCell<Element>>,
163    /// The property name
164    name: SmolStr,
165}
166
167impl NamedReferenceInner {
168    fn check_invariant(&self) {
169        debug_assert!(std::ptr::eq(
170            self as *const Self,
171            Rc::as_ptr(
172                &self.element.upgrade().unwrap().borrow().named_references.0.borrow()[&self.name]
173            )
174        ))
175    }
176
177    pub fn from_name(element: &ElementRc, name: SmolStr) -> Rc<Self> {
178        let elem = element.borrow();
179        let mut named_references = elem.named_references.0.borrow_mut();
180        let result = if let Some(r) = named_references.get(&name) {
181            r.clone()
182        } else {
183            let r = Rc::new(Self { element: Rc::downgrade(element), name });
184            named_references.insert(r.name.clone(), r.clone());
185            r
186        };
187        drop(named_references);
188        result.check_invariant();
189        result
190    }
191
192    pub(crate) fn snapshot(&self, snapshotter: &mut crate::typeloader::Snapshotter) -> Self {
193        let element = if let Some(el) = self.element.upgrade() {
194            Rc::downgrade(&snapshotter.use_element(&el))
195        } else {
196            std::rc::Weak::default()
197        };
198
199        Self { element, name: self.name.clone() }
200    }
201}
202
203/// Must be put inside the Element and owns all the NamedReferenceInner
204#[derive(Default)]
205pub struct NamedReferenceContainer(RefCell<HashMap<SmolStr, Rc<NamedReferenceInner>>>);
206
207impl NamedReferenceContainer {
208    /// Returns true if there is at least one NamedReference pointing to the property `name` in this element.
209    pub fn is_referenced(&self, name: &str) -> bool {
210        if let Some(nri) = self.0.borrow().get(name) {
211            // one reference for the hashmap itself
212            Rc::strong_count(nri) > 1
213        } else {
214            false
215        }
216    }
217
218    pub(crate) fn snapshot(
219        &self,
220        snapshotter: &mut crate::typeloader::Snapshotter,
221    ) -> NamedReferenceContainer {
222        let inner = self
223            .0
224            .borrow()
225            .iter()
226            .map(|(k, v)| (k.clone(), Rc::new(v.snapshot(snapshotter))))
227            .collect();
228        NamedReferenceContainer(RefCell::new(inner))
229    }
230}
231
232/// Mark that a given property is `is_set_externally` in all bases
233pub(crate) fn mark_property_set_derived_in_base(mut element: ElementRc, name: &str) {
234    loop {
235        let next = if let ElementType::Component(c) = &element.borrow().base_type {
236            if element.borrow().property_declarations.contains_key(name) {
237                return;
238            };
239            match c.root_element.borrow().property_analysis.borrow_mut().entry(name.into()) {
240                std::collections::hash_map::Entry::Occupied(e) if e.get().is_set_externally => {
241                    return;
242                }
243                std::collections::hash_map::Entry::Occupied(mut e) => {
244                    e.get_mut().is_set_externally = true;
245                }
246                std::collections::hash_map::Entry::Vacant(e) => {
247                    e.insert(PropertyAnalysis { is_set_externally: true, ..Default::default() });
248                }
249            }
250            c.root_element.clone()
251        } else {
252            return;
253        };
254        element = next;
255    }
256}
257
258/// Mark that a given property is `is_read_externally` in all bases
259pub(crate) fn mark_property_read_derived_in_base(mut element: ElementRc, name: &str) {
260    loop {
261        let next = if let ElementType::Component(c) = &element.borrow().base_type {
262            if element.borrow().property_declarations.contains_key(name) {
263                return;
264            };
265            match c.root_element.borrow().property_analysis.borrow_mut().entry(name.into()) {
266                std::collections::hash_map::Entry::Occupied(e) if e.get().is_read_externally => {
267                    return;
268                }
269                std::collections::hash_map::Entry::Occupied(mut e) => {
270                    e.get_mut().is_read_externally = true;
271                }
272                std::collections::hash_map::Entry::Vacant(e) => {
273                    e.insert(PropertyAnalysis { is_read_externally: true, ..Default::default() });
274                }
275            }
276            c.root_element.clone()
277        } else {
278            return;
279        };
280        element = next;
281    }
282}