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