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