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