Skip to main content

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