Skip to main content

i_slint_compiler/
load_builtins.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/*!
5    Parse the contents of builtins.slint and fill the builtin type registry
6*/
7
8use smol_str::{SmolStr, ToSmolStr};
9use std::cell::RefCell;
10use std::collections::HashMap;
11use std::rc::Rc;
12
13use crate::expression_tree::Expression;
14use crate::langtype::{
15    BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, BuiltinStruct, DefaultSizeBinding,
16    ElementType, Function, NativeClass, Type,
17};
18use crate::object_tree::{self, *};
19use crate::parser::{SyntaxKind, SyntaxNode, identifier_text, syntax_nodes};
20use crate::typeregister::TypeRegister;
21
22/// Parse the contents of builtins.slint and fill the builtin type registry
23/// `register` is the register to fill with the builtin types.
24/// At this point, it really should already contain the basic Types (string, int, ...)
25pub(crate) fn load_builtins(register: &mut TypeRegister) {
26    let mut diag = crate::diagnostics::BuildDiagnostics::default();
27    let node = crate::parser::parse(include_str!("builtins.slint").into(), None, &mut diag);
28    if !diag.is_empty() {
29        let vec = diag.to_string_vec();
30        #[cfg(feature = "display-diagnostics")]
31        diag.print();
32        panic!("Error parsing the builtin elements: {vec:?}");
33    }
34
35    assert_eq!(node.kind(), crate::parser::SyntaxKind::Document);
36    let doc: syntax_nodes::Document = node.into();
37
38    let mut natives = HashMap::<SmolStr, Rc<BuiltinElement>>::new();
39
40    let exports = doc
41        .ExportsList()
42        .flat_map(|e| {
43            e.Component()
44                .map(|x| {
45                    let x = identifier_text(&x.DeclaredIdentifier()).unwrap();
46                    (x.clone(), x)
47                })
48                .into_iter()
49                .chain(e.ExportSpecifier().map(|e| {
50                    (
51                        identifier_text(&e.ExportIdentifier()).unwrap(),
52                        identifier_text(&e.ExportName().unwrap()).unwrap(),
53                    )
54                }))
55        })
56        .collect::<HashMap<_, _>>();
57
58    for c in doc.Component().chain(doc.ExportsList().filter_map(|e| e.Component())) {
59        let id = identifier_text(&c.DeclaredIdentifier()).unwrap();
60        let e = c.Element();
61        let diag = RefCell::new(&mut diag);
62        let mut n = NativeClass::new_with_properties(
63            &id,
64            e.PropertyDeclaration()
65                .filter(|p| p.TwoWayBinding().is_none()) // aliases are handled further down
66                .map(|p| {
67                    let prop_name = identifier_text(&p.DeclaredIdentifier()).unwrap();
68
69                    let mut info = BuiltinPropertyInfo::new(object_tree::type_from_node(
70                        p.Type().unwrap(),
71                        *diag.borrow_mut(),
72                        register,
73                    ));
74
75                    info.property_visibility = PropertyVisibility::Private;
76
77                    for token in p.children_with_tokens() {
78                        if token.kind() != SyntaxKind::Identifier {
79                            continue;
80                        }
81                        match (token.as_token().unwrap().text(), info.property_visibility) {
82                            ("in", PropertyVisibility::Private) => {
83                                info.property_visibility = PropertyVisibility::Input
84                            }
85                            ("out", PropertyVisibility::Private) => {
86                                info.property_visibility = PropertyVisibility::Output
87                            }
88                            ("in-out", PropertyVisibility::Private) => {
89                                info.property_visibility = PropertyVisibility::InOut
90                            }
91                            ("property", _) => (),
92                            _ => unreachable!("invalid property keyword when parsing builtin file for property {id}::{prop_name}"),
93                        }
94                    }
95
96                    info.docs = docs::doc_comment(&p);
97                    info.shadowable = has_shadowable_annotation(&p);
98
99                    if let Some(e) = p.BindingExpression() {
100                        assert!(!info.shadowable, "shadowable property {id}::{prop_name} can't have a default value as it would end up on the shadowing declaration");
101                        let ty = info.ty.clone();
102                        info.default_value = BuiltinPropertyDefault::Expr(compiled(e, register, ty));
103                    }
104
105                    (prop_name, info)
106                })
107                .chain(e.CallbackDeclaration().map(|s| {
108                    let mut info = BuiltinPropertyInfo::new(Type::Callback(Rc::new(Function{
109                        args: s
110                            .CallbackDeclarationParameter()
111                            .map(|a| {
112                                object_tree::type_from_node(a.Type(), *diag.borrow_mut(), register)
113                            })
114                            .collect(),
115                        return_type: s.ReturnType().map(|a| {
116                            object_tree::type_from_node(
117                                a.Type(),
118                                *diag.borrow_mut(),
119                                register,
120                            )
121                        }).unwrap_or(Type::Void),
122                        arg_names: s
123                            .CallbackDeclarationParameter()
124                            .map(|a| a.DeclaredIdentifier().and_then(|x| identifier_text(&x)).unwrap_or_default())
125                            .collect()
126                    })));
127                    info.docs = docs::doc_comment(&s);
128                    info.shadowable = has_shadowable_annotation(&s);
129                    (identifier_text(&s.DeclaredIdentifier()).unwrap(), info)
130                }))
131        );
132        n.deprecated_aliases = e
133            .PropertyDeclaration()
134            .flat_map(|p| {
135                if let Some(twb) = p.TwoWayBinding() {
136                    let alias_name = identifier_text(&p.DeclaredIdentifier()).unwrap();
137                    let alias_target = identifier_text(&twb.Expression().QualifiedName().expect(
138                        "internal error: built-in aliases can only be declared within the type",
139                    ))
140                    .unwrap();
141                    Some((alias_name, alias_target))
142                } else {
143                    None
144                }
145            })
146            .collect();
147        n.builtin_struct = parse_annotation("builtin_struct", &e)
148            .map(|x| x.unwrap().parse::<BuiltinStruct>().unwrap());
149        enum Base {
150            None,
151            Global,
152            NativeParent(Rc<BuiltinElement>),
153        }
154        let base = if c.child_text(SyntaxKind::Identifier).is_some_and(|t| t == "global") {
155            Base::Global
156        } else if let Some(base) = e.QualifiedName() {
157            let base = QualifiedTypeName::from_node(base).to_smolstr();
158            let base = natives.get(&base).unwrap().clone();
159            // because they are not taken from if we inherit from it
160            assert!(
161                base.additional_accepted_child_types.is_empty() && !base.additional_accept_self
162            );
163            n.parent = Some(base.native_class.clone());
164            Base::NativeParent(base)
165        } else {
166            Base::None
167        };
168
169        n.properties.extend(e.Function().map(|f| {
170            let name = identifier_text(&f.DeclaredIdentifier()).unwrap();
171            let return_type = f.ReturnType().map_or(Type::Void, |p| {
172                object_tree::type_from_node(p.Type(), *diag.borrow_mut(), register)
173            });
174            let mut args = Vec::new();
175            let mut arg_names = Vec::new();
176            for a in f.ArgumentDeclaration() {
177                args.push(object_tree::type_from_node(a.Type(), *diag.borrow_mut(), register));
178                arg_names.push(identifier_text(&a.DeclaredIdentifier()).unwrap_or_default());
179            }
180            let mut info = BuiltinPropertyInfo::new(Type::Function(
181                Function { return_type, args, arg_names }.into(),
182            ));
183            info.docs = docs::doc_comment(&f);
184            info.shadowable = has_shadowable_annotation(&f);
185            (name, info)
186        }));
187
188        let mut builtin = BuiltinElement::new(Rc::new(n));
189        builtin.is_global = matches!(base, Base::Global);
190        let properties = &mut builtin.properties;
191        if let Base::NativeParent(parent) = &base {
192            properties.extend(parent.properties.iter().map(|(k, v)| (k.clone(), v.clone())));
193        }
194        properties
195            .extend(builtin.native_class.properties.iter().map(|(k, v)| (k.clone(), v.clone())));
196        let entries = docs::element_doc_entries(&c, &e, &mut diag.borrow_mut());
197        let parent_builtin = match &base {
198            Base::NativeParent(p) => Some(p.as_ref()),
199            _ => None,
200        };
201        // Assemble docs as [description, inherited parent body, own body].
202        // docs[0] is always the description so children can skip it
203        // with `parent.docs[1..]`.
204        builtin.docs = docs::assemble(entries, parent_builtin);
205
206        builtin.slint_sc = matches!(
207            builtin.docs.first(),
208            Some(crate::doc_comments::ElementDocEntry::Text(desc)) if has_sc_marker(desc)
209        ) || matches!(&base, Base::NativeParent(p) if p.slint_sc);
210
211        builtin.disallow_global_types_as_child_elements =
212            parse_annotation("disallow_global_types_as_child_elements", &e).is_some();
213        builtin.is_non_item_type = parse_annotation("is_non_item_type", &e).is_some();
214        builtin.is_internal = parse_annotation("is_internal", &e).is_some();
215        builtin.can_be_declared_without_children_slot =
216            parse_annotation("can_be_declared_without_children_slot", &e).is_some();
217        builtin.accepts_focus = parse_annotation("accepts_focus", &e).is_some();
218        builtin.default_size_binding = parse_annotation("default_size_binding", &e)
219            .map(|size_type| match size_type.as_deref() {
220                Some("expands_to_parent_geometry") => DefaultSizeBinding::ExpandsToParentGeometry,
221                Some("implicit_size") => DefaultSizeBinding::ImplicitSize,
222                other => panic!("invalid default size binding {other:?}"),
223            })
224            .unwrap_or(DefaultSizeBinding::None);
225        builtin.additional_accepted_child_types = e
226            .SubElement()
227            .filter_map(|s| {
228                let a = identifier_text(&s.Element().QualifiedName().unwrap()).unwrap();
229                if a == builtin.native_class.class_name {
230                    builtin.additional_accept_self = true;
231                    None
232                } else {
233                    let t = natives[&a].clone();
234                    Some((a, t))
235                }
236            })
237            .collect();
238        if let Some(builtin_name) = exports.get(&id) {
239            if !matches!(&base, Base::Global) {
240                builtin.name.clone_from(builtin_name);
241                register.add_builtin(Rc::new(builtin));
242            } else {
243                let glob = Rc::new(Component {
244                    id: builtin_name.clone(),
245                    root_element: Rc::new(RefCell::new(Element {
246                        base_type: ElementType::Builtin(Rc::new(builtin)),
247                        ..Default::default()
248                    })),
249                    ..Default::default()
250                });
251                glob.root_element.borrow_mut().enclosing_component = Rc::downgrade(&glob);
252                register.add(glob);
253            }
254        } else {
255            natives.insert(id, Rc::new(builtin));
256        }
257    }
258
259    register.property_animation_type =
260        ElementType::Builtin(natives.remove("PropertyAnimation").unwrap());
261
262    register.empty_type = ElementType::Builtin(natives.remove("Empty").unwrap());
263
264    if !diag.is_empty() {
265        let vec = diag.to_string_vec();
266        #[cfg(feature = "display-diagnostics")]
267        diag.print();
268        panic!("Error loading the builtin elements: {vec:?}");
269    }
270}
271
272/// Compile an expression, knowing that the expression is basic (does not have lookup to other things)
273fn compiled(
274    node: syntax_nodes::BindingExpression,
275    type_register: &TypeRegister,
276    ty: Type,
277) -> Expression {
278    let mut diag = crate::diagnostics::BuildDiagnostics::default();
279    let mut ctx = crate::lookup::LookupCtx::empty_context(type_register, &mut diag);
280    ctx.property_type = ty.clone();
281    let e = Expression::from_binding_expression_node(node.clone().into(), &mut ctx)
282        .maybe_convert_to(ty, &node, &mut diag);
283    if diag.has_errors() {
284        let vec = diag.to_string_vec();
285        #[cfg(feature = "display-diagnostics")]
286        diag.print();
287        panic!("Error parsing the builtin elements: {vec:?}");
288    }
289    e
290}
291
292/// Check for a `//-shadowable` annotation in the comments immediately before a
293/// member declaration. Marks members that a component may shadow with a local
294/// declaration of the same name.
295fn has_shadowable_annotation(node: &SyntaxNode) -> bool {
296    let mut cursor = node.node.prev_sibling_or_token();
297    while let Some(cur) = cursor {
298        match cur.kind() {
299            SyntaxKind::Whitespace => {}
300            SyntaxKind::Comment => {
301                if cur.as_token().unwrap().text().trim_end() == "//-shadowable" {
302                    return true;
303                }
304            }
305            _ => return false,
306        }
307        cursor = cur.prev_sibling_or_token();
308    }
309    false
310}
311
312/// Find out if there are comments that starts with `//-key` and returns `None`
313/// if no annotation with this key is found, or `Some(None)` if it is found without a value
314/// or `Some(Some(value))` if there is a `//-key:value`  match
315fn parse_annotation(key: &str, node: &SyntaxNode) -> Option<Option<SmolStr>> {
316    for x in node.children_with_tokens() {
317        if x.kind() == SyntaxKind::Comment
318            && let Some(comment) = x
319                .as_token()
320                .unwrap()
321                .text()
322                .strip_prefix("//-")
323                .and_then(|x| x.trim_end().strip_prefix(key))
324        {
325            if comment.is_empty() {
326                return Some(None);
327            }
328            if let Some(comment) = comment.strip_prefix(':') {
329                return Some(Some(comment.into()));
330            }
331        }
332    }
333    None
334}
335
336/// Check for standalone `\sc` marker in a doc string, ensuring it is not
337/// followed by an alphanumeric or underscore character (avoids matching
338/// `\score`, `\scale`, etc.).
339fn has_sc_marker(doc: &str) -> bool {
340    doc.match_indices("\\sc").any(|(start, _)| {
341        let end = start + 3;
342        match doc.as_bytes().get(end).copied() {
343            None => true,
344            Some(b) => !b.is_ascii_alphanumeric() && b != b'_',
345        }
346    })
347}
348
349use crate::doc_comments as docs;