i_slint_compiler/
typeloader.rs

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