i_slint_compiler/passes/
collect_globals.rs1#![allow(clippy::mutable_key_type)] use by_address::ByAddress;
9use smol_str::format_smolstr;
10
11use crate::diagnostics::BuildDiagnostics;
12use crate::expression_tree::NamedReference;
13use crate::object_tree::*;
14use std::collections::HashSet;
15use std::rc::Rc;
16
17pub fn collect_globals(doc: &Document, _diag: &mut BuildDiagnostics) {
19 doc.used_types.borrow_mut().globals.clear();
20 let mut set = HashSet::new();
21 let mut sorted_globals = Vec::new();
22 for (_, ty) in &*doc.exports {
23 if let Some(c) = ty.as_ref().left()
24 && c.is_global()
25 && set.insert(ByAddress(c.clone()))
26 {
27 collect_in_component(c, &mut set, &mut sorted_globals);
28 sorted_globals.push(c.clone());
29 }
30 }
31 doc.visit_all_used_components(|component| {
32 collect_in_component(component, &mut set, &mut sorted_globals)
33 });
34
35 doc.used_types.borrow_mut().globals = sorted_globals;
36}
37
38pub fn mark_library_globals(doc: &Document) {
43 let mut used_types = doc.used_types.borrow_mut();
44 used_types.globals.clone().iter().for_each(|component| {
45 if let Some(library_info) = doc.library_exports.get(component.id.as_str()) {
46 component.from_library.set(true);
47 let root = component.root_element.borrow();
48 let mut analysis = root.property_analysis.borrow_mut();
49 for name in root.property_declarations.keys() {
50 let entry = analysis.entry(name.clone()).or_default();
51 entry.is_set_externally = true;
52 entry.is_read_externally = true;
53 }
54 used_types.library_types_imports.push((component.id.clone(), library_info.clone()));
55 used_types
56 .library_types_imports
57 .push((format_smolstr!("Inner{}", component.id.clone()), library_info.clone()));
58 }
59 });
60}
61
62fn collect_in_component(
63 component: &Rc<Component>,
64 global_set: &mut HashSet<ByAddress<Rc<Component>>>,
65 sorted_globals: &mut Vec<Rc<Component>>,
66) {
67 let mut maybe_collect_global = |nr: &mut NamedReference| {
68 let element = nr.element();
69 let global_component = element.borrow().enclosing_component.upgrade().unwrap();
70 if global_component.is_global() && global_set.insert(ByAddress(global_component.clone())) {
71 collect_in_component(&global_component, global_set, sorted_globals);
72 sorted_globals.push(global_component);
73 }
74 };
75 visit_all_named_references(component, &mut maybe_collect_global);
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
89 fn mark_library_globals_marks_properties_as_externally_used() {
90 let mut compiler_config =
91 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
92 compiler_config.style = Some("fluent".into());
93 let mut diag = crate::diagnostics::BuildDiagnostics::default();
94 let doc_node = crate::parser::parse(
95 r#"
96export global LibGlobal {
97 in-out property <int> value: 0;
98 in property <int> count: 5;
99 out property <int> ready: 9;
100}
101export component App {
102 out property <string> text-out: "\{LibGlobal.value} \{LibGlobal.count} \{LibGlobal.ready}";
103}
104"#
105 .into(),
106 Some(std::path::Path::new("test.slint")),
107 &mut diag,
108 );
109 let (mut doc, diag, _) =
110 spin_on::spin_on(crate::compile_syntax_node(doc_node, diag, compiler_config));
111 assert!(!diag.has_errors(), "compile error: {:?}", diag.to_string_vec());
112
113 let global = doc
114 .used_types
115 .borrow()
116 .globals
117 .iter()
118 .find(|g| g.id == "LibGlobal")
119 .expect("LibGlobal not found")
120 .clone();
121
122 doc.library_exports.insert(
126 "LibGlobal".to_string(),
127 crate::typeloader::LibraryInfo {
128 name: "Lib".into(),
129 package: "lib".into(),
130 module: None,
131 exports: Vec::new(),
132 },
133 );
134 for (_, d) in global.root_element.borrow_mut().property_declarations.iter_mut() {
135 d.expose_in_public_api = false;
136 }
137 global.root_element.borrow().property_analysis.borrow_mut().clear();
138
139 mark_library_globals(&doc);
140
141 let root = global.root_element.borrow();
142 let analysis = root.property_analysis.borrow();
143 for prop in ["value", "count", "ready"] {
146 let a = analysis
147 .get(prop)
148 .unwrap_or_else(|| panic!("{prop}: no analysis entry for library global property"));
149 assert!(
150 a.is_set_externally,
151 "{prop}: every property on a library global must be marked is_set_externally \
152 — the library or its host code may write it at runtime regardless of visibility"
153 );
154 assert!(
155 a.is_read_externally,
156 "{prop}: every property on a library global must be marked is_read_externally \
157 — the library's bindings or host code may read it"
158 );
159 }
160 drop(analysis);
161 drop(root);
162
163 for prop in ["value", "count", "ready"] {
164 assert!(
165 !NamedReference::new(&global.root_element, prop.into()).is_constant(),
166 "{prop} on a library global must never be constant"
167 );
168 }
169 }
170}