1use 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
22pub(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()) .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 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 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
272fn 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
292fn 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
312fn 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
336fn 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;