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