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