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