1use smol_str::{SmolStr, ToSmolStr};
5use std::cell::RefCell;
6use std::collections::{HashMap, HashSet};
7use std::path::{Path, PathBuf};
8use std::rc::{Rc, Weak};
9
10use crate::diagnostics::{BuildDiagnostics, Spanned};
11use crate::expression_tree::Callable;
12use crate::object_tree::{self, Document, ExportedName, Exports};
13use crate::parser::{syntax_nodes, NodeOrToken, SyntaxKind, SyntaxToken};
14use crate::typeregister::TypeRegister;
15use crate::{expression_tree, CompilerConfiguration};
16use crate::{fileaccess, langtype, layout, parser};
17use core::future::Future;
18use itertools::Itertools;
19
20enum LoadedDocument {
21 Document(Document),
22 Invalidated(syntax_nodes::Document),
23}
24
25#[derive(Default)]
27struct LoadedDocuments {
28 docs: HashMap<PathBuf, (LoadedDocument, Vec<crate::diagnostics::Diagnostic>)>,
31 currently_loading: HashMap<PathBuf, Vec<std::task::Waker>>,
36
37 dependencies: HashMap<PathBuf, HashSet<PathBuf>>,
40}
41
42#[derive(Debug, Clone)]
43pub enum ImportKind {
44 ImportList(syntax_nodes::ImportIdentifierList),
46 FileImport,
48 ModuleReexport(syntax_nodes::ExportsList),
50}
51
52#[derive(Debug, Clone)]
53pub struct ImportedTypes {
54 pub import_uri_token: SyntaxToken,
55 pub import_kind: ImportKind,
56 pub file: String,
57}
58
59#[derive(Debug)]
60pub struct ImportedName {
61 pub external_name: SmolStr,
63 pub internal_name: SmolStr,
65}
66
67impl ImportedName {
68 pub fn extract_imported_names(
69 import_identifiers: &syntax_nodes::ImportIdentifierList,
70 ) -> impl Iterator<Item = ImportedName> + '_ {
71 import_identifiers.ImportIdentifier().map(Self::from_node)
72 }
73
74 pub fn from_node(importident: syntax_nodes::ImportIdentifier) -> Self {
75 let external_name =
76 parser::normalize_identifier(importident.ExternalName().text().to_smolstr().trim());
77
78 let internal_name = match importident.InternalName() {
79 Some(name_ident) => parser::normalize_identifier(name_ident.text().to_smolstr().trim()),
80 None => external_name.clone(),
81 };
82
83 ImportedName { internal_name, external_name }
84 }
85}
86
87pub fn snapshot(type_loader: &TypeLoader) -> Option<TypeLoader> {
98 let mut snapshotter = Snapshotter {
99 component_map: HashMap::new(),
100 element_map: HashMap::new(),
101 type_register_map: HashMap::new(),
102 keep_alive: Vec::new(),
103 keep_alive_elements: Vec::new(),
104 };
105 snapshotter.snapshot_type_loader(type_loader)
106}
107
108pub(crate) fn snapshot_with_extra_doc(
121 type_loader: &TypeLoader,
122 doc: &object_tree::Document,
123) -> Option<TypeLoader> {
124 let mut snapshotter = Snapshotter {
125 component_map: HashMap::new(),
126 element_map: HashMap::new(),
127 type_register_map: HashMap::new(),
128 keep_alive: Vec::new(),
129 keep_alive_elements: Vec::new(),
130 };
131 let mut result = snapshotter.snapshot_type_loader(type_loader);
132
133 snapshotter.create_document(doc);
134 let new_doc = snapshotter.snapshot_document(doc);
135
136 snapshotter.finalize();
137
138 if let Some(doc_node) = &new_doc.node {
139 let path = doc_node.source_file.path().to_path_buf();
140 if let Some(r) = &mut result {
141 r.all_documents.docs.insert(path, (LoadedDocument::Document(new_doc), vec![]));
142 }
143 }
144
145 result
146}
147
148pub(crate) struct Snapshotter {
149 component_map:
150 HashMap<by_address::ByAddress<Rc<object_tree::Component>>, Weak<object_tree::Component>>,
151 element_map:
152 HashMap<by_address::ByAddress<object_tree::ElementRc>, Weak<RefCell<object_tree::Element>>>,
153 type_register_map:
154 HashMap<by_address::ByAddress<Rc<RefCell<TypeRegister>>>, Rc<RefCell<TypeRegister>>>,
155
156 keep_alive: Vec<(Rc<object_tree::Component>, Rc<object_tree::Component>)>,
157 keep_alive_elements: Vec<(object_tree::ElementRc, object_tree::ElementRc)>,
158}
159
160impl Snapshotter {
161 fn snapshot_globals(&mut self, type_loader: &TypeLoader) {
162 let registry = type_loader.global_type_registry.clone();
163 registry
164 .borrow()
165 .all_elements()
166 .iter()
167 .filter_map(|(_, ty)| match ty {
168 langtype::ElementType::Component(c) if c.is_global() => Some(c),
169 _ => None,
170 })
171 .for_each(|c| {
172 self.create_component(c);
173 });
174 }
175
176 fn finalize(&mut self) {
177 let mut elements = std::mem::take(&mut self.keep_alive_elements);
178
179 while !elements.is_empty() {
180 for (s, t) in elements.iter_mut() {
181 self.snapshot_element(s, &mut t.borrow_mut());
182 }
183 elements = std::mem::take(&mut self.keep_alive_elements);
184 }
185 }
186
187 fn snapshot_type_loader(&mut self, type_loader: &TypeLoader) -> Option<TypeLoader> {
188 self.snapshot_globals(type_loader);
189
190 let all_documents = self.snapshot_loaded_documents(&type_loader.all_documents)?;
191
192 self.finalize();
193
194 Some(TypeLoader {
195 all_documents,
196 global_type_registry: self.snapshot_type_register(&type_loader.global_type_registry),
197 compiler_config: type_loader.compiler_config.clone(),
198 resolved_style: type_loader.resolved_style.clone(),
199 })
200 }
201
202 pub(crate) fn snapshot_type_register(
203 &mut self,
204 type_register: &Rc<RefCell<TypeRegister>>,
205 ) -> Rc<RefCell<TypeRegister>> {
206 if let Some(r) = self.type_register_map.get(&by_address::ByAddress(type_register.clone())) {
207 return r.clone();
208 }
209
210 let tr = Rc::new(RefCell::new(TypeRegister::default()));
211 self.type_register_map.insert(by_address::ByAddress(type_register.clone()), tr.clone());
212
213 *tr.borrow_mut() = self.snapshot_type_register_impl(type_register);
214
215 tr
216 }
217
218 fn snapshot_type_register_impl(
219 &mut self,
220 type_register: &Rc<RefCell<TypeRegister>>,
221 ) -> TypeRegister {
222 type_register.borrow().snapshot(self)
223 }
224
225 fn snapshot_loaded_documents(
226 &mut self,
227 loaded_documents: &LoadedDocuments,
228 ) -> Option<LoadedDocuments> {
229 if !loaded_documents.currently_loading.is_empty() {
230 return None;
231 }
232
233 loaded_documents.docs.values().for_each(|(d, _)| {
234 if let LoadedDocument::Document(d) = d {
235 self.create_document(d)
236 }
237 });
238
239 Some(LoadedDocuments {
240 docs: loaded_documents
241 .docs
242 .iter()
243 .map(|(p, (d, err))| {
244 (
245 p.clone(),
246 (
247 match d {
248 LoadedDocument::Document(d) => {
249 LoadedDocument::Document(self.snapshot_document(d))
250 }
251 LoadedDocument::Invalidated(d) => {
252 LoadedDocument::Invalidated(d.clone())
253 }
254 },
255 err.clone(),
256 ),
257 )
258 })
259 .collect(),
260 currently_loading: Default::default(),
261 dependencies: Default::default(),
262 })
263 }
264
265 fn create_document(&mut self, document: &object_tree::Document) {
266 document.inner_components.iter().for_each(|ic| {
267 let _ = self.create_component(ic);
268 });
269 if let Some(popup_menu_impl) = &document.popup_menu_impl {
270 let _ = self.create_component(popup_menu_impl);
271 }
272 }
273
274 fn snapshot_document(&mut self, document: &object_tree::Document) -> object_tree::Document {
275 let inner_components = document
276 .inner_components
277 .iter()
278 .map(|ic| {
279 Weak::upgrade(&self.use_component(ic))
280 .expect("Components can get upgraded at this point")
281 })
282 .collect();
283 let exports = document.exports.snapshot(self);
284
285 object_tree::Document {
286 node: document.node.clone(),
287 inner_components,
288 inner_types: document.inner_types.clone(),
289 local_registry: document.local_registry.snapshot(self),
290 custom_fonts: document.custom_fonts.clone(),
291 imports: document.imports.clone(),
292 exports,
293 embedded_file_resources: document.embedded_file_resources.clone(),
294 #[cfg(feature = "bundle-translations")]
295 translation_builder: document.translation_builder.clone(),
296 used_types: RefCell::new(self.snapshot_used_sub_types(&document.used_types.borrow())),
297 popup_menu_impl: document.popup_menu_impl.as_ref().map(|p| {
298 Weak::upgrade(&self.use_component(p))
299 .expect("Components can get upgraded at this point")
300 }),
301 }
302 }
303
304 pub(crate) fn create_component(
305 &mut self,
306 component: &Rc<object_tree::Component>,
307 ) -> Rc<object_tree::Component> {
308 let input_address = by_address::ByAddress(component.clone());
309
310 let parent_element = if let Some(pe) = component.parent_element.upgrade() {
311 Rc::downgrade(&self.use_element(&pe))
312 } else {
313 Weak::default()
314 };
315
316 let result = Rc::new_cyclic(|weak| {
317 self.component_map.insert(input_address, weak.clone());
318
319 let root_element = self.create_element(&component.root_element);
320
321 let optimized_elements = RefCell::new(
322 component
323 .optimized_elements
324 .borrow()
325 .iter()
326 .map(|e| self.create_element(e))
327 .collect(),
328 );
329
330 let child_insertion_point =
331 RefCell::new(component.child_insertion_point.borrow().clone());
332
333 let popup_windows = RefCell::new(
334 component
335 .popup_windows
336 .borrow()
337 .iter()
338 .map(|p| self.snapshot_popup_window(p))
339 .collect(),
340 );
341 let timers = RefCell::new(
342 component.timers.borrow().iter().map(|p| self.snapshot_timer(p)).collect(),
343 );
344 let root_constraints = RefCell::new(
345 self.snapshot_layout_constraints(&component.root_constraints.borrow()),
346 );
347 let menu_item_tree = component
348 .menu_item_tree
349 .borrow()
350 .iter()
351 .map(|it| self.create_component(it))
352 .collect::<Vec<_>>()
353 .into();
354 object_tree::Component {
355 node: component.node.clone(),
356 id: component.id.clone(),
357 child_insertion_point,
358 exported_global_names: RefCell::new(
359 component.exported_global_names.borrow().clone(),
360 ),
361 used: component.used.clone(),
362 init_code: RefCell::new(component.init_code.borrow().clone()),
363 inherits_popup_window: std::cell::Cell::new(component.inherits_popup_window.get()),
364 optimized_elements,
365 parent_element,
366 popup_windows,
367 timers,
368 menu_item_tree,
369 private_properties: RefCell::new(component.private_properties.borrow().clone()),
370 root_constraints,
371 root_element,
372 }
373 });
374 self.keep_alive.push((component.clone(), result.clone()));
375 result
376 }
377
378 pub(crate) fn use_component(
379 &self,
380 component: &Rc<object_tree::Component>,
381 ) -> Weak<object_tree::Component> {
382 self.component_map
383 .get(&by_address::ByAddress(component.clone()))
384 .expect("Component (Weak!) must exist at this point.")
385 .clone()
386 }
387
388 pub(crate) fn create_element(
389 &mut self,
390 element: &object_tree::ElementRc,
391 ) -> object_tree::ElementRc {
392 let enclosing_component = if let Some(ec) = element.borrow().enclosing_component.upgrade() {
393 self.use_component(&ec)
394 } else {
395 Weak::default()
396 };
397
398 let elem = element.borrow();
399
400 let r = Rc::new_cyclic(|weak| {
401 self.element_map.insert(by_address::ByAddress(element.clone()), weak.clone());
402
403 let children = elem.children.iter().map(|c| self.create_element(c)).collect();
404
405 RefCell::new(object_tree::Element {
406 id: elem.id.clone(),
407 enclosing_component,
408 children,
409 debug: elem.debug.clone(),
410 ..Default::default()
411 })
412 });
413
414 self.keep_alive_elements.push((element.clone(), r.clone()));
415 r
416 }
417
418 fn create_and_snapshot_element(
419 &mut self,
420 element: &object_tree::ElementRc,
421 ) -> object_tree::ElementRc {
422 let target = self.create_element(element);
423 self.snapshot_element(element, &mut target.borrow_mut());
424 target
425 }
426
427 pub(crate) fn use_element(&self, element: &object_tree::ElementRc) -> object_tree::ElementRc {
428 Weak::upgrade(
429 &self
430 .element_map
431 .get(&by_address::ByAddress(element.clone()))
432 .expect("Elements should have been known at this point")
433 .clone(),
434 )
435 .expect("Must be able to upgrade here")
436 }
437
438 fn snapshot_element(
439 &mut self,
440 element: &object_tree::ElementRc,
441 target_element: &mut object_tree::Element,
442 ) {
443 let elem = element.borrow();
444
445 target_element.base_type = self.snapshot_element_type(&elem.base_type);
446
447 target_element.transitions = elem
448 .transitions
449 .iter()
450 .map(|t| object_tree::Transition {
451 direction: t.direction,
452 state_id: t.state_id.clone(),
453 property_animations: t
454 .property_animations
455 .iter()
456 .map(|(nr, sl, el)| {
457 (nr.snapshot(self), sl.clone(), self.create_and_snapshot_element(el))
458 })
459 .collect(),
460 node: t.node.clone(),
461 })
462 .collect();
463
464 target_element.bindings = elem
465 .bindings
466 .iter()
467 .map(|(k, v)| {
468 let bm = v.borrow();
469 let binding = self.snapshot_binding_expression(&bm);
470 (k.clone(), RefCell::new(binding))
471 })
472 .collect();
473 target_element.states = elem
474 .states
475 .iter()
476 .map(|s| object_tree::State {
477 id: s.id.clone(),
478 condition: s.condition.clone(),
479 property_changes: s
480 .property_changes
481 .iter()
482 .map(|(nr, expr, spc)| {
483 let nr = nr.snapshot(self);
484 let expr = self.snapshot_expression(expr);
485 (nr, expr, spc.clone())
486 })
487 .collect(),
488 })
489 .collect();
490 target_element.repeated =
491 elem.repeated.as_ref().map(|r| object_tree::RepeatedElementInfo {
492 model: self.snapshot_expression(&r.model),
493 model_data_id: r.model_data_id.clone(),
494 index_id: r.index_id.clone(),
495 is_conditional_element: r.is_conditional_element,
496 is_listview: r.is_listview.as_ref().map(|lv| object_tree::ListViewInfo {
497 viewport_y: lv.viewport_y.snapshot(self),
498 viewport_height: lv.viewport_height.snapshot(self),
499 viewport_width: lv.viewport_width.snapshot(self),
500 listview_height: lv.listview_height.snapshot(self),
501 listview_width: lv.listview_width.snapshot(self),
502 }),
503 });
504
505 target_element.accessibility_props = object_tree::AccessibilityProps(
506 elem.accessibility_props.0.iter().map(|(k, v)| (k.clone(), v.snapshot(self))).collect(),
507 );
508 target_element.geometry_props =
509 elem.geometry_props.as_ref().map(|gp| object_tree::GeometryProps {
510 x: gp.x.snapshot(self),
511 y: gp.y.snapshot(self),
512 width: gp.width.snapshot(self),
513 height: gp.height.snapshot(self),
514 });
515 target_element.property_declarations = elem
516 .property_declarations
517 .iter()
518 .map(|(k, v)| {
519 let decl = object_tree::PropertyDeclaration {
520 property_type: v.property_type.clone(),
521 node: v.node.clone(),
522 expose_in_public_api: v.expose_in_public_api,
523 is_alias: v.is_alias.as_ref().map(|a| a.snapshot(self)),
524 visibility: v.visibility,
525 pure: v.pure,
526 };
527 (k.clone(), decl)
528 })
529 .collect();
530 target_element.layout_info_prop =
531 elem.layout_info_prop.as_ref().map(|(n1, n2)| (n1.snapshot(self), n2.snapshot(self)));
532 target_element.property_analysis = RefCell::new(elem.property_analysis.borrow().clone());
533
534 target_element.change_callbacks = elem.change_callbacks.clone();
535 target_element.child_of_layout = elem.child_of_layout;
536 target_element.default_fill_parent = elem.default_fill_parent;
537 target_element.has_popup_child = elem.has_popup_child;
538 target_element.inline_depth = elem.inline_depth;
539 target_element.is_component_placeholder = elem.is_component_placeholder;
540 target_element.is_flickable_viewport = elem.is_flickable_viewport;
541 target_element.is_legacy_syntax = elem.is_legacy_syntax;
542 target_element.item_index = elem.item_index.clone();
543 target_element.item_index_of_first_children = elem.item_index_of_first_children.clone();
544 target_element.named_references = elem.named_references.snapshot(self);
545 }
546
547 fn snapshot_binding_expression(
548 &mut self,
549 binding_expression: &expression_tree::BindingExpression,
550 ) -> expression_tree::BindingExpression {
551 expression_tree::BindingExpression {
552 expression: self.snapshot_expression(&binding_expression.expression),
553 span: binding_expression.span.clone(),
554 priority: binding_expression.priority,
555 animation: binding_expression.animation.as_ref().map(|pa| match pa {
556 object_tree::PropertyAnimation::Static(element) => {
557 object_tree::PropertyAnimation::Static(
558 self.create_and_snapshot_element(element),
559 )
560 }
561 object_tree::PropertyAnimation::Transition { state_ref, animations } => {
562 object_tree::PropertyAnimation::Transition {
563 state_ref: self.snapshot_expression(state_ref),
564 animations: animations
565 .iter()
566 .map(|tpa| object_tree::TransitionPropertyAnimation {
567 state_id: tpa.state_id,
568 direction: tpa.direction,
569 animation: self.create_and_snapshot_element(&tpa.animation),
570 })
571 .collect(),
572 }
573 }
574 }),
575 analysis: binding_expression.analysis.as_ref().map(|a| {
576 expression_tree::BindingAnalysis {
577 is_in_binding_loop: a.is_in_binding_loop.clone(),
578 is_const: a.is_const,
579 no_external_dependencies: a.no_external_dependencies,
580 }
581 }),
582 two_way_bindings: binding_expression
583 .two_way_bindings
584 .iter()
585 .map(|twb| twb.snapshot(self))
586 .collect(),
587 }
588 }
589
590 pub(crate) fn snapshot_element_type(
591 &mut self,
592 element_type: &langtype::ElementType,
593 ) -> langtype::ElementType {
594 match element_type {
596 langtype::ElementType::Component(component) => {
597 langtype::ElementType::Component(
599 Weak::upgrade(&self.use_component(component))
600 .expect("I can unwrap at this point"),
601 )
602 }
603 _ => element_type.clone(),
604 }
605 }
606
607 fn snapshot_used_sub_types(
608 &mut self,
609 used_types: &object_tree::UsedSubTypes,
610 ) -> object_tree::UsedSubTypes {
611 let globals = used_types
612 .globals
613 .iter()
614 .map(|component| {
615 Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
616 })
617 .collect();
618 let structs_and_enums = used_types.structs_and_enums.clone();
619 let sub_components = used_types
620 .sub_components
621 .iter()
622 .map(|component| {
623 Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
624 })
625 .collect();
626 object_tree::UsedSubTypes { globals, structs_and_enums, sub_components }
627 }
628
629 fn snapshot_popup_window(
630 &mut self,
631 popup_window: &object_tree::PopupWindow,
632 ) -> object_tree::PopupWindow {
633 object_tree::PopupWindow {
634 component: Weak::upgrade(&self.use_component(&popup_window.component))
635 .expect("Looking at a known component"),
636 x: popup_window.x.snapshot(self),
637 y: popup_window.y.snapshot(self),
638 close_policy: popup_window.close_policy.clone(),
639 parent_element: self.use_element(&popup_window.parent_element),
640 }
641 }
642
643 fn snapshot_timer(&mut self, popup_window: &object_tree::Timer) -> object_tree::Timer {
644 object_tree::Timer {
645 interval: popup_window.interval.snapshot(self),
646 running: popup_window.running.snapshot(self),
647 triggered: popup_window.triggered.snapshot(self),
648 }
649 }
650
651 fn snapshot_layout_constraints(
652 &mut self,
653 layout_constraints: &layout::LayoutConstraints,
654 ) -> layout::LayoutConstraints {
655 layout::LayoutConstraints {
656 min_width: layout_constraints.min_width.as_ref().map(|lc| lc.snapshot(self)),
657 max_width: layout_constraints.max_width.as_ref().map(|lc| lc.snapshot(self)),
658 min_height: layout_constraints.min_height.as_ref().map(|lc| lc.snapshot(self)),
659 max_height: layout_constraints.max_height.as_ref().map(|lc| lc.snapshot(self)),
660 preferred_width: layout_constraints
661 .preferred_width
662 .as_ref()
663 .map(|lc| lc.snapshot(self)),
664 preferred_height: layout_constraints
665 .preferred_height
666 .as_ref()
667 .map(|lc| lc.snapshot(self)),
668 horizontal_stretch: layout_constraints
669 .horizontal_stretch
670 .as_ref()
671 .map(|lc| lc.snapshot(self)),
672 vertical_stretch: layout_constraints
673 .vertical_stretch
674 .as_ref()
675 .map(|lc| lc.snapshot(self)),
676 fixed_width: layout_constraints.fixed_width,
677 fixed_height: layout_constraints.fixed_height,
678 }
679 }
680
681 fn snapshot_expression(
682 &mut self,
683 expr: &expression_tree::Expression,
684 ) -> expression_tree::Expression {
685 use expression_tree::Expression;
686 match expr {
687 Expression::PropertyReference(nr) => Expression::PropertyReference(nr.snapshot(self)),
688 Expression::ElementReference(el) => {
689 Expression::ElementReference(if let Some(el) = el.upgrade() {
690 Rc::downgrade(&el)
691 } else {
692 Weak::default()
693 })
694 }
695 Expression::RepeaterIndexReference { element } => Expression::RepeaterIndexReference {
696 element: if let Some(el) = element.upgrade() {
697 Rc::downgrade(&el)
698 } else {
699 Weak::default()
700 },
701 },
702 Expression::RepeaterModelReference { element } => Expression::RepeaterModelReference {
703 element: if let Some(el) = element.upgrade() {
704 Rc::downgrade(&el)
705 } else {
706 Weak::default()
707 },
708 },
709 Expression::StoreLocalVariable { name, value } => Expression::StoreLocalVariable {
710 name: name.clone(),
711 value: Box::new(self.snapshot_expression(value)),
712 },
713 Expression::StructFieldAccess { base, name } => Expression::StructFieldAccess {
714 base: Box::new(self.snapshot_expression(base)),
715 name: name.clone(),
716 },
717 Expression::ArrayIndex { array, index } => Expression::ArrayIndex {
718 array: Box::new(self.snapshot_expression(array)),
719 index: Box::new(self.snapshot_expression(index)),
720 },
721 Expression::Cast { from, to } => {
722 Expression::Cast { from: Box::new(self.snapshot_expression(from)), to: to.clone() }
723 }
724 Expression::CodeBlock(exprs) => {
725 Expression::CodeBlock(exprs.iter().map(|e| self.snapshot_expression(e)).collect())
726 }
727 Expression::FunctionCall { function, arguments, source_location } => {
728 Expression::FunctionCall {
729 function: match function {
730 Callable::Callback(nr) => Callable::Callback(nr.snapshot(self)),
731 Callable::Function(nr) => Callable::Function(nr.snapshot(self)),
732 Callable::Builtin(b) => Callable::Builtin(b.clone()),
733 },
734 arguments: arguments.iter().map(|e| self.snapshot_expression(e)).collect(),
735 source_location: source_location.clone(),
736 }
737 }
738 Expression::SelfAssignment { lhs, rhs, op, node } => Expression::SelfAssignment {
739 lhs: Box::new(self.snapshot_expression(lhs)),
740 rhs: Box::new(self.snapshot_expression(rhs)),
741 op: *op,
742 node: node.clone(),
743 },
744 Expression::BinaryExpression { lhs, rhs, op } => Expression::BinaryExpression {
745 lhs: Box::new(self.snapshot_expression(lhs)),
746 rhs: Box::new(self.snapshot_expression(rhs)),
747 op: *op,
748 },
749 Expression::UnaryOp { sub, op } => {
750 Expression::UnaryOp { sub: Box::new(self.snapshot_expression(sub)), op: *op }
751 }
752 Expression::Condition { condition, true_expr, false_expr } => Expression::Condition {
753 condition: Box::new(self.snapshot_expression(condition)),
754 true_expr: Box::new(self.snapshot_expression(true_expr)),
755 false_expr: Box::new(self.snapshot_expression(false_expr)),
756 },
757 Expression::Array { element_ty, values } => Expression::Array {
758 element_ty: element_ty.clone(),
759 values: values.iter().map(|e| self.snapshot_expression(e)).collect(),
760 },
761 Expression::Struct { ty, values } => Expression::Struct {
762 ty: ty.clone(),
763 values: values
764 .iter()
765 .map(|(k, v)| (k.clone(), self.snapshot_expression(v)))
766 .collect(),
767 },
768 Expression::PathData(path) => Expression::PathData(match path {
769 expression_tree::Path::Elements(path_elements) => expression_tree::Path::Elements(
770 path_elements
771 .iter()
772 .map(|p| {
773 expression_tree::PathElement {
774 element_type: p.element_type.clone(), bindings: p
776 .bindings
777 .iter()
778 .map(|(k, v)| {
779 (
780 k.clone(),
781 RefCell::new(
782 self.snapshot_binding_expression(&v.borrow()),
783 ),
784 )
785 })
786 .collect(),
787 }
788 })
789 .collect(),
790 ),
791 expression_tree::Path::Events(ex1, ex2) => expression_tree::Path::Events(
792 ex1.iter().map(|e| self.snapshot_expression(e)).collect(),
793 ex2.iter().map(|e| self.snapshot_expression(e)).collect(),
794 ),
795 expression_tree::Path::Commands(ex) => {
796 expression_tree::Path::Commands(Box::new(self.snapshot_expression(ex)))
797 }
798 }),
799 Expression::LinearGradient { angle, stops } => Expression::LinearGradient {
800 angle: Box::new(self.snapshot_expression(angle)),
801 stops: stops
802 .iter()
803 .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
804 .collect(),
805 },
806 Expression::RadialGradient { stops } => Expression::RadialGradient {
807 stops: stops
808 .iter()
809 .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
810 .collect(),
811 },
812 Expression::ReturnStatement(expr) => Expression::ReturnStatement(
813 expr.as_ref().map(|e| Box::new(self.snapshot_expression(e))),
814 ),
815 Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
816 Expression::LayoutCacheAccess {
817 layout_cache_prop: layout_cache_prop.snapshot(self),
818 index: *index,
819 repeater_index: repeater_index
820 .as_ref()
821 .map(|e| Box::new(self.snapshot_expression(e))),
822 }
823 }
824 Expression::MinMax { ty, op, lhs, rhs } => Expression::MinMax {
825 ty: ty.clone(),
826 lhs: Box::new(self.snapshot_expression(lhs)),
827 rhs: Box::new(self.snapshot_expression(rhs)),
828 op: *op,
829 },
830 _ => expr.clone(),
831 }
832 }
833}
834
835pub struct TypeLoader {
836 pub global_type_registry: Rc<RefCell<TypeRegister>>,
837 pub compiler_config: CompilerConfiguration,
838 pub resolved_style: String,
841 all_documents: LoadedDocuments,
842}
843
844struct BorrowedTypeLoader<'a> {
845 tl: &'a mut TypeLoader,
846 diag: &'a mut BuildDiagnostics,
847}
848
849impl TypeLoader {
850 pub fn new(
851 global_type_registry: Rc<RefCell<TypeRegister>>,
852 compiler_config: CompilerConfiguration,
853 diag: &mut BuildDiagnostics,
854 ) -> Self {
855 let mut style = compiler_config
856 .style
857 .clone()
858 .or_else(|| std::env::var("SLINT_STYLE").ok())
859 .unwrap_or_else(|| "native".into());
860
861 if style == "native" {
862 style = get_native_style(&mut diag.all_loaded_files);
863 }
864
865 let myself = Self {
866 global_type_registry,
867 compiler_config,
868 resolved_style: style.clone(),
869 all_documents: Default::default(),
870 };
871
872 let mut known_styles = fileaccess::styles();
873 known_styles.push("native");
874 if !known_styles.contains(&style.as_ref())
875 && myself
876 .find_file_in_include_path(None, &format!("{style}/std-widgets.slint"))
877 .is_none()
878 {
879 diag.push_diagnostic_with_span(
880 format!(
881 "Style {} is not known. Use one of the builtin styles [{}] or make sure your custom style is found in the include directories",
882 &style,
883 known_styles.join(", ")
884 ),
885 Default::default(),
886 crate::diagnostics::DiagnosticLevel::Error,
887 );
888 }
889
890 myself
891 }
892
893 pub fn drop_document(&mut self, path: &Path) -> Result<(), std::io::Error> {
894 if let Some((LoadedDocument::Document(doc), _)) = self.all_documents.docs.remove(path) {
895 for dep in &doc.imports {
896 self.all_documents
897 .dependencies
898 .entry(Path::new(&dep.file).into())
899 .or_default()
900 .remove(path);
901 }
902 }
903 self.all_documents.dependencies.remove(path);
904 if self.all_documents.currently_loading.contains_key(path) {
905 Err(std::io::Error::new(
906 std::io::ErrorKind::InvalidInput,
907 format!("{path:?} is still loading"),
908 ))
909 } else {
910 Ok(())
911 }
912 }
913
914 pub fn invalidate_document(&mut self, path: &Path) -> HashSet<PathBuf> {
916 if let Some((d, _)) = self.all_documents.docs.get_mut(path) {
917 if let LoadedDocument::Document(doc) = d {
918 for dep in &doc.imports {
919 self.all_documents
920 .dependencies
921 .entry(Path::new(&dep.file).into())
922 .or_default()
923 .remove(path);
924 }
925 match doc.node.take() {
926 None => {
927 self.all_documents.docs.remove(path);
928 }
929 Some(n) => {
930 *d = LoadedDocument::Invalidated(n);
931 }
932 };
933 } else {
934 return HashSet::new();
935 }
936 } else {
937 return HashSet::new();
938 }
939 let deps = self.all_documents.dependencies.remove(path).unwrap_or_default();
940 let mut extra_deps = HashSet::new();
941 for dep in &deps {
942 extra_deps.extend(self.invalidate_document(dep));
943 }
944 extra_deps.extend(deps);
945 extra_deps
946 }
947
948 pub async fn load_dependencies_recursively<'a>(
950 &'a mut self,
951 doc: &'a syntax_nodes::Document,
952 diag: &'a mut BuildDiagnostics,
953 registry_to_populate: &'a Rc<RefCell<TypeRegister>>,
954 ) -> (Vec<ImportedTypes>, Exports) {
955 let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
956 Self::load_dependencies_recursively_impl(
957 &state,
958 doc,
959 registry_to_populate,
960 &Default::default(),
961 )
962 .await
963 }
964
965 async fn load_dependencies_recursively_impl<'a: 'b, 'b>(
966 state: &'a RefCell<BorrowedTypeLoader<'a>>,
967 doc: &'b syntax_nodes::Document,
968 registry_to_populate: &'b Rc<RefCell<TypeRegister>>,
969 import_stack: &'b HashSet<PathBuf>,
970 ) -> (Vec<ImportedTypes>, Exports) {
971 let mut imports = vec![];
972 let mut dependencies_futures = vec![];
973 for mut import in Self::collect_dependencies(state, doc) {
974 if matches!(import.import_kind, ImportKind::FileImport) {
975 if let Some((path, _)) = state.borrow().tl.resolve_import_path(
976 Some(&import.import_uri_token.clone().into()),
977 &import.file,
978 ) {
979 import.file = path.to_string_lossy().into_owned();
980 };
981 imports.push(import);
982 continue;
983 }
984 dependencies_futures.push(Box::pin(async move {
985 let file = import.file.as_str();
986 let doc_path = Self::ensure_document_loaded(
987 state,
988 file,
989 Some(import.import_uri_token.clone().into()),
990 import_stack.clone(),
991 )
992 .await;
993 (import, doc_path)
994 }));
995 }
996
997 let mut reexports = None;
998 let mut has_star_reexport = false;
999 std::future::poll_fn(|cx| {
1000 dependencies_futures.retain_mut(|fut| {
1001 let core::task::Poll::Ready((mut import, doc_path)) = fut.as_mut().poll(cx) else { return true; };
1002 let Some(doc_path) = doc_path else { return false };
1003 let mut state = state.borrow_mut();
1004 let state = &mut *state;
1005 let Some(doc) = state.tl.get_document(&doc_path) else {
1006 panic!("Just loaded document not available")
1007 };
1008 match &import.import_kind {
1009 ImportKind::ImportList(imported_types) => {
1010 let mut imported_types = ImportedName::extract_imported_names(imported_types).peekable();
1011 if imported_types.peek().is_some() {
1012 Self::register_imported_types(doc, &import, imported_types, registry_to_populate, state.diag);
1013 } else {
1014 state.diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
1015 }
1016 }
1017 ImportKind::ModuleReexport(export_module_syntax_node) => {
1018 let exports = reexports.get_or_insert_with(Exports::default);
1019 if let Some(star_reexport) = export_module_syntax_node.ExportModule().and_then(|x| x.child_token(SyntaxKind::Star))
1020 {
1021 if has_star_reexport {
1022 state.diag.push_error("re-exporting modules is only allowed once per file".into(), &star_reexport);
1023 return false;
1024 }
1025 has_star_reexport = true;
1026 exports.add_reexports(
1027 doc.exports.iter().map(|(exported_name, compo_or_type)| {
1028 let exported_name = ExportedName {
1029 name: exported_name.name.clone(),
1030 name_ident: (**export_module_syntax_node).clone(),
1031 };
1032 (exported_name, compo_or_type.clone())
1033 }),
1034 state.diag,
1035 );
1036 } else if export_module_syntax_node.ExportSpecifier().next().is_none() {
1037 state.diag.push_error("Import names are missing. Please specify which types you would like to re-export".into(), export_module_syntax_node);
1038 } else {
1039 let e = export_module_syntax_node
1040 .ExportSpecifier()
1041 .filter_map(|e| {
1042 let (imported_name, exported_name) = ExportedName::from_export_specifier(&e);
1043 let Some(r) = doc.exports.find(&imported_name) else {
1044 state.diag.push_error(format!("No exported type called '{imported_name}' found in \"{}\"", doc_path.display()), &e);
1045 return None;
1046 };
1047 Some((exported_name, r))
1048 })
1049 .collect::<Vec<_>>();
1050 exports.add_reexports(e, state.diag);
1051 }
1052 }
1053 ImportKind::FileImport => {
1054 unreachable!("FileImport should have been handled above")
1055 }
1056 }
1057 import.file = doc_path.to_string_lossy().into_owned();
1058 imports.push(import);
1059 false
1060 });
1061 if dependencies_futures.is_empty() {
1062 core::task::Poll::Ready(())
1063 } else {
1064 core::task::Poll::Pending
1065 }
1066 }).await;
1067 (imports, reexports.unwrap_or_default())
1068 }
1069
1070 pub async fn import_component(
1071 &mut self,
1072 file_to_import: &str,
1073 type_name: &str,
1074 diag: &mut BuildDiagnostics,
1075 ) -> Option<Rc<object_tree::Component>> {
1076 let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1077 let doc_path =
1078 match Self::ensure_document_loaded(&state, file_to_import, None, Default::default())
1079 .await
1080 {
1081 Some(doc_path) => doc_path,
1082 None => return None,
1083 };
1084
1085 let Some(doc) = self.get_document(&doc_path) else {
1086 panic!("Just loaded document not available")
1087 };
1088
1089 doc.exports.find(type_name).and_then(|compo_or_type| compo_or_type.left())
1090 }
1091
1092 pub fn resolve_import_path(
1095 &self,
1096 import_token: Option<&NodeOrToken>,
1097 maybe_relative_path_or_url: &str,
1098 ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1099 if let Some(maybe_library_import) = maybe_relative_path_or_url.strip_prefix('@') {
1100 self.find_file_in_library_path(maybe_library_import)
1101 } else {
1102 let referencing_file_or_url =
1103 import_token.and_then(|tok| tok.source_file().map(|s| s.path()));
1104 self.find_file_in_include_path(referencing_file_or_url, maybe_relative_path_or_url)
1105 .or_else(|| {
1106 referencing_file_or_url
1107 .and_then(|base_path_or_url| {
1108 crate::pathutils::join(
1109 &crate::pathutils::dirname(base_path_or_url),
1110 &PathBuf::from(maybe_relative_path_or_url),
1111 )
1112 })
1113 .filter(|p| p.exists())
1114 .map(|p| (p, None))
1115 })
1116 }
1117 }
1118
1119 async fn ensure_document_loaded<'a: 'b, 'b>(
1120 state: &'a RefCell<BorrowedTypeLoader<'a>>,
1121 file_to_import: &'b str,
1122 import_token: Option<NodeOrToken>,
1123 mut import_stack: HashSet<PathBuf>,
1124 ) -> Option<PathBuf> {
1125 let mut borrowed_state = state.borrow_mut();
1126
1127 let (path_canon, builtin) = match borrowed_state
1128 .tl
1129 .resolve_import_path(import_token.as_ref(), file_to_import)
1130 {
1131 Some(x) => {
1132 if let Some(file_name) = x.0.file_name().and_then(|f| f.to_str()) {
1133 let len = file_to_import.len();
1134 if !file_to_import.ends_with(file_name)
1135 && len >= file_name.len()
1136 && file_name.eq_ignore_ascii_case(
1137 file_to_import.get(len - file_name.len()..).unwrap_or(""),
1138 )
1139 {
1140 if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1141 borrowed_state.diag.push_warning(
1142 format!("Loading \"{file_to_import}\" resolved to a file named \"{file_name}\" with different casing. This behavior is not cross platform. Rename the file, or edit the import to use the same casing"),
1143 &import_token,
1144 );
1145 }
1146 }
1147 }
1148 x
1149 }
1150 None => {
1151 let import_path = crate::pathutils::clean_path(Path::new(file_to_import));
1152 if import_path.exists() {
1153 if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1154 borrowed_state.diag.push_warning(
1155 format!(
1156 "Loading \"{file_to_import}\" relative to the work directory is deprecated. Files should be imported relative to their import location",
1157 ),
1158 &import_token,
1159 );
1160 }
1161 (import_path, None)
1162 } else {
1163 let base_path = import_token
1166 .as_ref()
1167 .and_then(|tok| tok.source_file().map(|s| s.path()))
1168 .map_or(PathBuf::new(), |p| p.into());
1169 let path = crate::pathutils::join(
1170 &crate::pathutils::dirname(&base_path),
1171 Path::new(file_to_import),
1172 )?;
1173 (path, None)
1174 }
1175 }
1176 };
1177
1178 if !import_stack.insert(path_canon.clone()) {
1179 borrowed_state.diag.push_error(
1180 format!("Recursive import of \"{}\"", path_canon.display()),
1181 &import_token,
1182 );
1183 return None;
1184 }
1185
1186 drop(borrowed_state);
1187
1188 let (is_loaded, doc_node) = core::future::poll_fn(|cx| {
1189 let mut state = state.borrow_mut();
1190 let all_documents = &mut state.tl.all_documents;
1191 match all_documents.currently_loading.entry(path_canon.clone()) {
1192 std::collections::hash_map::Entry::Occupied(mut e) => {
1193 let waker = cx.waker();
1194 if !e.get().iter().any(|w| w.will_wake(waker)) {
1195 e.get_mut().push(cx.waker().clone());
1196 }
1197 core::task::Poll::Pending
1198 }
1199 std::collections::hash_map::Entry::Vacant(v) => {
1200 match all_documents.docs.get(path_canon.as_path()) {
1201 Some((LoadedDocument::Document(_), _)) => {
1202 core::task::Poll::Ready((true, None))
1203 }
1204 Some((LoadedDocument::Invalidated(doc), errors)) => {
1205 v.insert(Default::default());
1206 core::task::Poll::Ready((false, Some((doc.clone(), errors.clone()))))
1207 }
1208 None => {
1209 v.insert(Default::default());
1210 core::task::Poll::Ready((false, None))
1211 }
1212 }
1213 }
1214 }
1215 })
1216 .await;
1217 if is_loaded {
1218 return Some(path_canon);
1219 }
1220
1221 let doc_node = if let Some((doc_node, errors)) = doc_node {
1222 for e in errors {
1223 state.borrow_mut().diag.push_internal_error(e);
1224 }
1225 Some(doc_node)
1226 } else {
1227 let source_code_result = if let Some(builtin) = builtin {
1228 Ok(String::from(
1229 core::str::from_utf8(builtin)
1230 .expect("internal error: embedded file is not UTF-8 source code"),
1231 ))
1232 } else {
1233 let fallback = state.borrow().tl.compiler_config.open_import_fallback.clone();
1234 if let Some(fallback) = fallback {
1235 let result = fallback(path_canon.to_string_lossy().into()).await;
1236 result.unwrap_or_else(|| std::fs::read_to_string(&path_canon))
1237 } else {
1238 std::fs::read_to_string(&path_canon)
1239 }
1240 };
1241 match source_code_result {
1242 Ok(source) => syntax_nodes::Document::new(crate::parser::parse(
1243 source,
1244 Some(&path_canon),
1245 state.borrow_mut().diag,
1246 )),
1247 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
1248 state.borrow_mut().diag.push_error(
1249 if file_to_import.starts_with('@') {
1250 format!(
1251 "Cannot find requested import \"{file_to_import}\" in the library search path",
1252 )
1253 } else {
1254 format!(
1255 "Cannot find requested import \"{file_to_import}\" in the include search path",
1256 )
1257 },
1258 &import_token,
1259 );
1260 None
1261 }
1262 Err(err) => {
1263 state.borrow_mut().diag.push_error(
1264 format!(
1265 "Error reading requested import \"{}\": {}",
1266 path_canon.display(),
1267 err
1268 ),
1269 &import_token,
1270 );
1271 None
1272 }
1273 }
1274 };
1275
1276 let ok = if let Some(doc_node) = doc_node {
1277 Self::load_file_impl(state, &path_canon, doc_node, builtin.is_some(), &import_stack)
1278 .await;
1279 state.borrow_mut().diag.all_loaded_files.insert(path_canon.clone());
1280 true
1281 } else {
1282 false
1283 };
1284
1285 let wakers = state
1286 .borrow_mut()
1287 .tl
1288 .all_documents
1289 .currently_loading
1290 .remove(path_canon.as_path())
1291 .unwrap();
1292 for x in wakers {
1293 x.wake();
1294 }
1295
1296 ok.then_some(path_canon)
1297 }
1298
1299 pub async fn load_file(
1303 &mut self,
1304 path: &Path,
1305 source_path: &Path,
1306 source_code: String,
1307 is_builtin: bool,
1308 diag: &mut BuildDiagnostics,
1309 ) {
1310 let doc_node: syntax_nodes::Document =
1311 crate::parser::parse(source_code, Some(source_path), diag).into();
1312 let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1313 Self::load_file_impl(&state, path, doc_node, is_builtin, &Default::default()).await;
1314 }
1315
1316 pub async fn reload_cached_file(&mut self, path: &Path, diag: &mut BuildDiagnostics) {
1320 let Some((LoadedDocument::Invalidated(doc_node), errors)) =
1321 self.all_documents.docs.get(path)
1322 else {
1323 return;
1324 };
1325 let doc_node = doc_node.clone();
1326 for e in errors {
1327 diag.push_internal_error(e.clone());
1328 }
1329 let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1330 Self::load_file_impl(&state, path, doc_node, false, &Default::default()).await;
1331 }
1332
1333 pub async fn load_root_file(
1337 &mut self,
1338 path: &Path,
1339 source_path: &Path,
1340 source_code: String,
1341 keep_raw: bool,
1342 diag: &mut BuildDiagnostics,
1343 ) -> (PathBuf, Option<TypeLoader>) {
1344 let path = crate::pathutils::clean_path(path);
1345 let doc_node: syntax_nodes::Document =
1346 crate::parser::parse(source_code, Some(source_path), diag).into();
1347 let parse_errors = diag.iter().cloned().collect();
1348 let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1349 let (path, mut doc) =
1350 Self::load_doc_no_pass(&state, &path, doc_node, false, &Default::default()).await;
1351
1352 let mut state = state.borrow_mut();
1353 let state = &mut *state;
1354 let raw_type_loader = if !state.diag.has_errors() {
1355 crate::passes::run_passes(&mut doc, state.tl, keep_raw, state.diag).await
1356 } else {
1357 None
1358 };
1359 state
1360 .tl
1361 .all_documents
1362 .docs
1363 .insert(path.clone(), (LoadedDocument::Document(doc), parse_errors));
1364 (path, raw_type_loader)
1365 }
1366
1367 async fn load_file_impl<'a>(
1368 state: &'a RefCell<BorrowedTypeLoader<'a>>,
1369 path: &Path,
1370 doc_node: syntax_nodes::Document,
1371 is_builtin: bool,
1372 import_stack: &HashSet<PathBuf>,
1373 ) {
1374 let parse_errors = state
1375 .borrow()
1376 .diag
1377 .iter()
1378 .filter(|e| e.source_file().is_some_and(|f| f == path))
1379 .cloned()
1380 .collect();
1381 let (path, doc) =
1382 Self::load_doc_no_pass(state, path, doc_node, is_builtin, import_stack).await;
1383
1384 let mut state = state.borrow_mut();
1385 let state = &mut *state;
1386 if !state.diag.has_errors() {
1387 crate::passes::run_import_passes(&doc, state.tl, state.diag);
1388 }
1389 for dep in &doc.imports {
1390 state
1391 .tl
1392 .all_documents
1393 .dependencies
1394 .entry(Path::new(&dep.file).into())
1395 .or_default()
1396 .insert(path.clone());
1397 }
1398 state.tl.all_documents.docs.insert(path, (LoadedDocument::Document(doc), parse_errors));
1399 }
1400
1401 async fn load_doc_no_pass<'a>(
1402 state: &'a RefCell<BorrowedTypeLoader<'a>>,
1403 path: &Path,
1404 dependency_doc: syntax_nodes::Document,
1405 is_builtin: bool,
1406 import_stack: &HashSet<PathBuf>,
1407 ) -> (PathBuf, Document) {
1408 let dependency_registry =
1409 Rc::new(RefCell::new(TypeRegister::new(&state.borrow().tl.global_type_registry)));
1410 dependency_registry.borrow_mut().expose_internal_types =
1411 is_builtin || state.borrow().tl.compiler_config.enable_experimental;
1412 let (imports, reexports) = Self::load_dependencies_recursively_impl(
1413 state,
1414 &dependency_doc,
1415 &dependency_registry,
1416 import_stack,
1417 )
1418 .await;
1419
1420 if state.borrow().diag.has_errors() {
1421 let mut ignore_diag = BuildDiagnostics::default();
1425 ignore_diag.push_error_with_span(
1426 "Dummy error because some of the code asserts there was an error".into(),
1427 Default::default(),
1428 );
1429 let doc = crate::object_tree::Document::from_node(
1430 dependency_doc,
1431 imports,
1432 reexports,
1433 &mut ignore_diag,
1434 &dependency_registry,
1435 );
1436 return (path.to_owned(), doc);
1437 }
1438 let mut state = state.borrow_mut();
1439 let state = &mut *state;
1440 let doc = crate::object_tree::Document::from_node(
1441 dependency_doc,
1442 imports,
1443 reexports,
1444 state.diag,
1445 &dependency_registry,
1446 );
1447 (path.to_owned(), doc)
1448 }
1449
1450 fn register_imported_types(
1451 doc: &Document,
1452 import: &ImportedTypes,
1453 imported_types: impl Iterator<Item = ImportedName>,
1454 registry_to_populate: &Rc<RefCell<TypeRegister>>,
1455 build_diagnostics: &mut BuildDiagnostics,
1456 ) {
1457 for import_name in imported_types {
1458 let imported_type = doc.exports.find(&import_name.external_name);
1459
1460 let imported_type = match imported_type {
1461 Some(ty) => ty,
1462 None => {
1463 build_diagnostics.push_error(
1464 format!(
1465 "No exported type called '{}' found in \"{}\"",
1466 import_name.external_name, import.file
1467 ),
1468 &import.import_uri_token,
1469 );
1470 continue;
1471 }
1472 };
1473
1474 match imported_type {
1475 itertools::Either::Left(c) => {
1476 registry_to_populate.borrow_mut().add_with_name(import_name.internal_name, c)
1477 }
1478 itertools::Either::Right(ty) => registry_to_populate
1479 .borrow_mut()
1480 .insert_type_with_name(ty, import_name.internal_name),
1481 };
1482 }
1483 }
1484
1485 fn find_file_in_library_path(
1487 &self,
1488 maybe_library_import: &str,
1489 ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1490 let (library, file) = maybe_library_import
1491 .splitn(2, '/')
1492 .collect_tuple()
1493 .map(|(library, path)| (library, Some(path)))
1494 .unwrap_or((maybe_library_import, None));
1495 self.compiler_config.library_paths.get(library).and_then(|library_path| {
1496 let path = match file {
1497 Some(file) => library_path.join(file),
1499 None => library_path.clone(),
1501 };
1502 crate::fileaccess::load_file(path.as_path())
1503 .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1504 })
1505 }
1506
1507 pub fn find_file_in_include_path(
1510 &self,
1511 referencing_file: Option<&Path>,
1512 file_to_import: &str,
1513 ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1514 referencing_file
1516 .map(base_directory)
1517 .into_iter()
1518 .chain(self.compiler_config.include_paths.iter().map(PathBuf::as_path).map(
1519 |include_path| {
1520 let base = referencing_file.map(Path::to_path_buf).unwrap_or_default();
1521 crate::pathutils::join(&crate::pathutils::dirname(&base), include_path)
1522 .unwrap_or_else(|| include_path.to_path_buf())
1523 },
1524 ))
1525 .chain(
1526 (file_to_import == "std-widgets.slint"
1527 || referencing_file.is_some_and(|x| x.starts_with("builtin:/")))
1528 .then(|| format!("builtin:/{}", self.resolved_style).into()),
1529 )
1530 .find_map(|include_dir| {
1531 let candidate = crate::pathutils::join(&include_dir, Path::new(file_to_import))?;
1532 crate::fileaccess::load_file(&candidate)
1533 .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1534 })
1535 }
1536
1537 fn collect_dependencies<'a: 'b, 'b>(
1538 state: &'a RefCell<BorrowedTypeLoader<'a>>,
1539 doc: &'b syntax_nodes::Document,
1540 ) -> impl Iterator<Item = ImportedTypes> + 'a {
1541 doc.ImportSpecifier()
1542 .map(|import| {
1543 let maybe_import_uri = import.child_token(SyntaxKind::StringLiteral);
1544 let kind = import
1545 .ImportIdentifierList()
1546 .map(ImportKind::ImportList)
1547 .unwrap_or(ImportKind::FileImport);
1548 (maybe_import_uri, kind)
1549 })
1550 .chain(
1551 doc.ExportsList().filter_map(|exports| {
1553 exports.ExportModule().map(|reexport| {
1554 let maybe_import_uri = reexport.child_token(SyntaxKind::StringLiteral);
1555 (maybe_import_uri, ImportKind::ModuleReexport(exports))
1556 })
1557 }),
1558 )
1559 .filter_map(|(maybe_import_uri, type_specifier)| {
1560 let import_uri = match maybe_import_uri {
1561 Some(import_uri) => import_uri,
1562 None => {
1563 debug_assert!(state.borrow().diag.has_errors());
1564 return None;
1565 }
1566 };
1567 let path_to_import = import_uri.text().to_string();
1568 let path_to_import = path_to_import.trim_matches('\"').to_string();
1569
1570 if path_to_import.is_empty() {
1571 state
1572 .borrow_mut()
1573 .diag
1574 .push_error("Unexpected empty import url".to_owned(), &import_uri);
1575 return None;
1576 }
1577
1578 Some(ImportedTypes {
1579 import_uri_token: import_uri,
1580 import_kind: type_specifier,
1581 file: path_to_import,
1582 })
1583 })
1584 }
1585
1586 pub fn get_document<'b>(&'b self, path: &Path) -> Option<&'b object_tree::Document> {
1588 let path = crate::pathutils::clean_path(path);
1589 if let Some((LoadedDocument::Document(d), _)) = self.all_documents.docs.get(&path) {
1590 Some(d)
1591 } else {
1592 None
1593 }
1594 }
1595
1596 pub fn all_files(&self) -> impl Iterator<Item = &PathBuf> {
1598 self.all_documents.docs.keys()
1599 }
1600
1601 pub fn all_documents(&self) -> impl Iterator<Item = &object_tree::Document> + '_ {
1603 self.all_documents.docs.values().filter_map(|(d, _)| match d {
1604 LoadedDocument::Document(d) => Some(d),
1605 LoadedDocument::Invalidated(_) => None,
1606 })
1607 }
1608
1609 pub fn all_file_documents(
1611 &self,
1612 ) -> impl Iterator<Item = (&PathBuf, &syntax_nodes::Document)> + '_ {
1613 self.all_documents.docs.iter().filter_map(|(p, (d, _))| {
1614 Some((
1615 p,
1616 match d {
1617 LoadedDocument::Document(d) => d.node.as_ref()?,
1618 LoadedDocument::Invalidated(d) => d,
1619 },
1620 ))
1621 })
1622 }
1623}
1624
1625fn get_native_style(all_loaded_files: &mut std::collections::BTreeSet<PathBuf>) -> String {
1626 let target_path = std::env::var_os("OUT_DIR")
1630 .and_then(|path| {
1631 crate::pathutils::join(Path::new(&path), Path::new("../../SLINT_DEFAULT_STYLE.txt"))
1633 })
1634 .or_else(|| {
1635 let mut args = std::env::args();
1639 let mut out_dir = None;
1640 while let Some(arg) = args.next() {
1641 if arg == "--out-dir" {
1642 out_dir = args.next();
1643 break;
1644 }
1645 }
1646 out_dir.and_then(|od| {
1647 crate::pathutils::join(
1648 Path::new(&od),
1649 Path::new("../build/SLINT_DEFAULT_STYLE.txt"),
1650 )
1651 })
1652 });
1653
1654 if let Some(style) = target_path.and_then(|target_path| {
1655 std::fs::read_to_string(&target_path)
1656 .map(|style| {
1657 all_loaded_files.insert(target_path);
1658 style.trim().into()
1659 })
1660 .ok()
1661 }) {
1662 return style;
1663 }
1664 i_slint_common::get_native_style(false, &std::env::var("TARGET").unwrap_or_default()).into()
1665}
1666
1667pub fn base_directory(referencing_file: &Path) -> PathBuf {
1676 if referencing_file.extension().is_some_and(|e| e == "rs") {
1677 let mut candidate = referencing_file;
1679 loop {
1680 candidate =
1681 if let Some(c) = candidate.parent() { c } else { break referencing_file.parent() };
1682
1683 if candidate.join("Cargo.toml").exists() {
1684 break Some(candidate);
1685 }
1686 }
1687 } else {
1688 referencing_file.parent()
1689 }
1690 .map_or_else(Default::default, |p| p.to_path_buf())
1691}
1692
1693#[test]
1694fn test_dependency_loading() {
1695 let test_source_path: PathBuf =
1696 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1697
1698 let mut incdir = test_source_path.clone();
1699 incdir.push("incpath");
1700
1701 let mut compiler_config =
1702 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1703 compiler_config.include_paths = vec![incdir];
1704 compiler_config.library_paths =
1705 HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
1706 compiler_config.style = Some("fluent".into());
1707
1708 let mut main_test_path = test_source_path;
1709 main_test_path.push("dependency_test_main.slint");
1710
1711 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1712 let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
1713
1714 let doc_node: syntax_nodes::Document = doc_node.into();
1715
1716 let global_registry = TypeRegister::builtin();
1717
1718 let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1719
1720 let mut build_diagnostics = BuildDiagnostics::default();
1721
1722 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1723
1724 let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
1725 &doc_node,
1726 &mut build_diagnostics,
1727 ®istry,
1728 ));
1729
1730 assert!(!test_diags.has_errors());
1731 assert!(!build_diagnostics.has_errors());
1732 assert_eq!(foreign_imports.len(), 3);
1733 assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
1734}
1735
1736#[test]
1737fn test_dependency_loading_from_rust() {
1738 let test_source_path: PathBuf =
1739 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1740
1741 let mut incdir = test_source_path.clone();
1742 incdir.push("incpath");
1743
1744 let mut compiler_config =
1745 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1746 compiler_config.include_paths = vec![incdir];
1747 compiler_config.library_paths =
1748 HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
1749 compiler_config.style = Some("fluent".into());
1750
1751 let mut main_test_path = test_source_path;
1752 main_test_path.push("some_rust_file.rs");
1753
1754 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1755 let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
1756
1757 let doc_node: syntax_nodes::Document = doc_node.into();
1758
1759 let global_registry = TypeRegister::builtin();
1760
1761 let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1762
1763 let mut build_diagnostics = BuildDiagnostics::default();
1764
1765 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1766
1767 let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
1768 &doc_node,
1769 &mut build_diagnostics,
1770 ®istry,
1771 ));
1772
1773 assert!(!test_diags.has_errors());
1774 assert!(test_diags.is_empty()); assert!(!build_diagnostics.has_errors());
1776 assert!(build_diagnostics.is_empty()); assert_eq!(foreign_imports.len(), 3);
1778 assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
1779}
1780
1781#[test]
1782fn test_load_from_callback_ok() {
1783 let ok = Rc::new(core::cell::Cell::new(false));
1784 let ok_ = ok.clone();
1785
1786 let mut compiler_config =
1787 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1788 compiler_config.style = Some("fluent".into());
1789 compiler_config.open_import_fallback = Some(Rc::new(move |path| {
1790 let ok_ = ok_.clone();
1791 Box::pin(async move {
1792 assert_eq!(path.replace('\\', "/"), "../FooBar.slint");
1793 assert!(!ok_.get());
1794 ok_.set(true);
1795 Some(Ok("export XX := Rectangle {} ".to_owned()))
1796 })
1797 }));
1798
1799 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1800 let doc_node = crate::parser::parse(
1801 r#"
1802/* ... */
1803import { XX } from "../Ab/.././FooBar.slint";
1804X := XX {}
1805"#
1806 .into(),
1807 Some(std::path::Path::new("HELLO")),
1808 &mut test_diags,
1809 );
1810
1811 let doc_node: syntax_nodes::Document = doc_node.into();
1812 let global_registry = TypeRegister::builtin();
1813 let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1814 let mut build_diagnostics = BuildDiagnostics::default();
1815 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1816 spin_on::spin_on(loader.load_dependencies_recursively(
1817 &doc_node,
1818 &mut build_diagnostics,
1819 ®istry,
1820 ));
1821 assert!(ok.get());
1822 assert!(!test_diags.has_errors());
1823 assert!(!build_diagnostics.has_errors());
1824}
1825
1826#[test]
1827fn test_load_error_twice() {
1828 let mut compiler_config =
1829 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1830 compiler_config.style = Some("fluent".into());
1831 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1832
1833 let doc_node = crate::parser::parse(
1834 r#"
1835/* ... */
1836import { XX } from "error.slint";
1837component Foo { XX {} }
1838"#
1839 .into(),
1840 Some(std::path::Path::new("HELLO")),
1841 &mut test_diags,
1842 );
1843
1844 let doc_node: syntax_nodes::Document = doc_node.into();
1845 let global_registry = TypeRegister::builtin();
1846 let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1847 let mut build_diagnostics = BuildDiagnostics::default();
1848 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1849 spin_on::spin_on(loader.load_dependencies_recursively(
1850 &doc_node,
1851 &mut build_diagnostics,
1852 ®istry,
1853 ));
1854 assert!(!test_diags.has_errors());
1855 assert!(build_diagnostics.has_errors());
1856 let diags = build_diagnostics.to_string_vec();
1857 assert_eq!(
1858 diags,
1859 &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
1860 );
1861 let mut build_diagnostics = BuildDiagnostics::default();
1863 spin_on::spin_on(loader.load_dependencies_recursively(
1864 &doc_node,
1865 &mut build_diagnostics,
1866 ®istry,
1867 ));
1868 assert!(build_diagnostics.has_errors());
1869 let diags = build_diagnostics.to_string_vec();
1870 assert_eq!(
1871 diags,
1872 &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
1873 );
1874}
1875
1876#[test]
1877fn test_manual_import() {
1878 let mut compiler_config =
1879 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1880 compiler_config.style = Some("fluent".into());
1881 let global_registry = TypeRegister::builtin();
1882 let mut build_diagnostics = BuildDiagnostics::default();
1883 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1884
1885 let maybe_button_type = spin_on::spin_on(loader.import_component(
1886 "std-widgets.slint",
1887 "Button",
1888 &mut build_diagnostics,
1889 ));
1890
1891 assert!(!build_diagnostics.has_errors());
1892 assert!(maybe_button_type.is_some());
1893}
1894
1895#[test]
1896fn test_builtin_style() {
1897 let test_source_path: PathBuf =
1898 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1899
1900 let incdir = test_source_path.join("custom_style");
1901
1902 let mut compiler_config =
1903 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1904 compiler_config.include_paths = vec![incdir];
1905 compiler_config.style = Some("fluent".into());
1906
1907 let global_registry = TypeRegister::builtin();
1908 let mut build_diagnostics = BuildDiagnostics::default();
1909 let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1910
1911 assert!(!build_diagnostics.has_errors());
1912}
1913
1914#[test]
1915fn test_user_style() {
1916 let test_source_path: PathBuf =
1917 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1918
1919 let incdir = test_source_path.join("custom_style");
1920
1921 let mut compiler_config =
1922 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1923 compiler_config.include_paths = vec![incdir];
1924 compiler_config.style = Some("TestStyle".into());
1925
1926 let global_registry = TypeRegister::builtin();
1927 let mut build_diagnostics = BuildDiagnostics::default();
1928 let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1929
1930 assert!(!build_diagnostics.has_errors());
1931}
1932
1933#[test]
1934fn test_unknown_style() {
1935 let test_source_path: PathBuf =
1936 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1937
1938 let incdir = test_source_path.join("custom_style");
1939
1940 let mut compiler_config =
1941 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1942 compiler_config.include_paths = vec![incdir];
1943 compiler_config.style = Some("FooBar".into());
1944
1945 let global_registry = TypeRegister::builtin();
1946 let mut build_diagnostics = BuildDiagnostics::default();
1947 let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1948
1949 assert!(build_diagnostics.has_errors());
1950 let diags = build_diagnostics.to_string_vec();
1951 assert_eq!(diags.len(), 1);
1952 assert!(diags[0].starts_with("Style FooBar is not known. Use one of the builtin styles ["));
1953}
1954
1955#[test]
1956fn test_library_import() {
1957 let test_source_path: PathBuf =
1958 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
1959
1960 let library_paths = HashMap::from([
1961 ("libdir".into(), test_source_path.clone()),
1962 ("libfile.slint".into(), test_source_path.join("lib.slint")),
1963 ]);
1964
1965 let mut compiler_config =
1966 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1967 compiler_config.library_paths = library_paths;
1968 compiler_config.style = Some("fluent".into());
1969 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1970
1971 let doc_node = crate::parser::parse(
1972 r#"
1973/* ... */
1974import { LibraryType } from "@libfile.slint";
1975import { LibraryHelperType } from "@libdir/library_helper_type.slint";
1976"#
1977 .into(),
1978 Some(std::path::Path::new("HELLO")),
1979 &mut test_diags,
1980 );
1981
1982 let doc_node: syntax_nodes::Document = doc_node.into();
1983 let global_registry = TypeRegister::builtin();
1984 let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1985 let mut build_diagnostics = BuildDiagnostics::default();
1986 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1987 spin_on::spin_on(loader.load_dependencies_recursively(
1988 &doc_node,
1989 &mut build_diagnostics,
1990 ®istry,
1991 ));
1992 assert!(!test_diags.has_errors());
1993 assert!(!build_diagnostics.has_errors());
1994}
1995
1996#[test]
1997fn test_library_import_errors() {
1998 let test_source_path: PathBuf =
1999 [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
2000
2001 let library_paths = HashMap::from([
2002 ("libdir".into(), test_source_path.clone()),
2003 ("libfile.slint".into(), test_source_path.join("lib.slint")),
2004 ]);
2005
2006 let mut compiler_config =
2007 CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2008 compiler_config.library_paths = library_paths;
2009 compiler_config.style = Some("fluent".into());
2010 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2011
2012 let doc_node = crate::parser::parse(
2013 r#"
2014/* ... */
2015import { A } from "@libdir";
2016import { B } from "@libdir/unknown.slint";
2017import { C } from "@libfile.slint/unknown.slint";
2018import { D } from "@unknown";
2019import { E } from "@unknown/lib.slint";
2020"#
2021 .into(),
2022 Some(std::path::Path::new("HELLO")),
2023 &mut test_diags,
2024 );
2025
2026 let doc_node: syntax_nodes::Document = doc_node.into();
2027 let global_registry = TypeRegister::builtin();
2028 let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
2029 let mut build_diagnostics = BuildDiagnostics::default();
2030 let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
2031 spin_on::spin_on(loader.load_dependencies_recursively(
2032 &doc_node,
2033 &mut build_diagnostics,
2034 ®istry,
2035 ));
2036 assert!(!test_diags.has_errors());
2037 assert!(build_diagnostics.has_errors());
2038 let diags = build_diagnostics.to_string_vec();
2039 assert_eq!(diags.len(), 5);
2040 assert!(diags[0].starts_with(&format!(
2041 "HELLO:3: Error reading requested import \"{}\": ",
2042 test_source_path.to_string_lossy()
2043 )));
2044 assert_eq!(&diags[1], "HELLO:4: Cannot find requested import \"@libdir/unknown.slint\" in the library search path");
2045 assert_eq!(&diags[2], "HELLO:5: Cannot find requested import \"@libfile.slint/unknown.slint\" in the library search path");
2046 assert_eq!(
2047 &diags[3],
2048 "HELLO:6: Cannot find requested import \"@unknown\" in the library search path"
2049 );
2050 assert_eq!(
2051 &diags[4],
2052 "HELLO:7: Cannot find requested import \"@unknown/lib.slint\" in the library search path"
2053 );
2054}
2055
2056#[test]
2057fn test_snapshotting() {
2058 let mut type_loader = TypeLoader::new(
2059 crate::typeregister::TypeRegister::builtin(),
2060 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter),
2061 &mut BuildDiagnostics::default(),
2062 );
2063
2064 let path = PathBuf::from("/tmp/test.slint");
2065 let mut diag = BuildDiagnostics::default();
2066 spin_on::spin_on(type_loader.load_file(
2067 &path,
2068 &path,
2069 "export component Foobar inherits Rectangle { }".to_string(),
2070 false,
2071 &mut diag,
2072 ));
2073
2074 assert!(!diag.has_errors());
2075
2076 let doc = type_loader.get_document(&path).unwrap();
2077 let c = doc.inner_components.first().unwrap();
2078 assert_eq!(c.id, "Foobar");
2079 let root_element = c.root_element.clone();
2080 assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2081
2082 let copy = snapshot(&type_loader).unwrap();
2083
2084 let doc = copy.get_document(&path).unwrap();
2085 let c = doc.inner_components.first().unwrap();
2086 assert_eq!(c.id, "Foobar");
2087 let root_element = c.root_element.clone();
2088 assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2089}