i_slint_compiler/passes/
purity_check.rs1use std::collections::HashSet;
5
6use crate::diagnostics::BuildDiagnostics;
7use crate::expression_tree::{Callable, Expression, NamedReference};
8
9pub fn purity_check(doc: &crate::object_tree::Document, diag: &mut BuildDiagnostics) {
11 for component in &doc.inner_components {
12 crate::object_tree::recurse_elem_including_sub_components_no_borrow(
13 component,
14 &(),
15 &mut |elem, &()| {
16 let level = match elem.borrow().is_legacy_syntax {
17 true => crate::diagnostics::DiagnosticLevel::Warning,
18 false => crate::diagnostics::DiagnosticLevel::Error,
19 };
20 crate::object_tree::visit_element_expressions(elem, |expr, name, _| {
21 if let Some(name) = name {
22 let lookup = elem.borrow().lookup_property(name);
23 if lookup.declared_pure.unwrap_or(false)
24 || lookup.property_type.is_property_type()
25 {
26 ensure_pure(expr, Some(diag), level, &mut Default::default());
27 }
28 } else {
29 ensure_pure(expr, Some(diag), level, &mut Default::default());
31 };
32 })
33 },
34 )
35 }
36}
37
38fn ensure_pure(
39 expr: &Expression,
40 mut diag: Option<&mut BuildDiagnostics>,
41 level: crate::diagnostics::DiagnosticLevel,
42 recursion_test: &mut HashSet<NamedReference>,
43) -> bool {
44 let mut r = true;
45 expr.visit_recursive(&mut |e| match e {
46 Expression::FunctionCall { function: Callable::Callback(nr), source_location, .. } => {
47 if !nr.element().borrow().lookup_property(nr.name()).declared_pure.unwrap_or(false) {
48 if let Some(diag) = diag.as_deref_mut() {
49 diag.push_diagnostic(
50 format!("Call of impure callback '{}'", nr.name()),
51 source_location,
52 level,
53 );
54 }
55 r = false;
56 }
57 }
58 Expression::FunctionCall { function: Callable::Function(nr), source_location, .. } => {
59 match nr.element().borrow().lookup_property(nr.name()).declared_pure {
60 Some(true) => (),
61 Some(false) => {
62 if let Some(diag) = diag.as_deref_mut() {
63 diag.push_diagnostic(
64 format!("Call of impure function '{}'", nr.name(),),
65 source_location,
66 level,
67 );
68 }
69 r = false;
70 }
71 None => {
72 if recursion_test.insert(nr.clone()) {
73 match nr.element().borrow().bindings.get(nr.name()) {
74 None => {
75 debug_assert!(
76 diag.as_ref().map_or(true, |d| d.has_errors()),
77 "private functions must be local and defined"
78 );
79 }
80 Some(binding) => {
81 if !ensure_pure(
82 &binding.borrow().expression,
83 None,
84 level,
85 recursion_test,
86 ) {
87 if let Some(diag) = diag.as_deref_mut() {
88 diag.push_diagnostic(
89 format!("Call of impure function '{}'", nr.name()),
90 source_location,
91 level,
92 );
93 }
94 r = false;
95 }
96 }
97 }
98 }
99 }
100 }
101 }
102 Expression::FunctionCall { function: Callable::Builtin(func), source_location, .. } => {
103 if !func.is_pure() {
104 if let Some(diag) = diag.as_deref_mut() {
105 diag.push_diagnostic("Call of impure function".into(), source_location, level);
106 }
107 r = false;
108 }
109 }
110 Expression::SelfAssignment { node, .. } => {
111 if let Some(diag) = diag.as_deref_mut() {
112 diag.push_diagnostic("Assignment in a pure context".into(), node, level);
113 }
114 r = false;
115 }
116 _ => (),
117 });
118 r
119}