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