WIP: pure qualifier for callback and functions

This commit is contained in:
Olivier Goffart 2022-12-14 19:23:00 +01:00 committed by Olivier Goffart
parent 8a09043e63
commit 1cbd61145e
34 changed files with 366 additions and 93 deletions

View file

@ -335,8 +335,8 @@ fn recurse_expression(expr: &Expression, vis: &mut impl FnMut(&PropertyPath)) {
expr.visit(|sub| recurse_expression(sub, vis));
match expr {
Expression::PropertyReference(r)
| Expression::CallbackReference(r)
| Expression::FunctionReference(r) => vis(&r.clone().into()),
| Expression::CallbackReference(r, _)
| Expression::FunctionReference(r, _) => vis(&r.clone().into()),
Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
vis(&layout_cache_prop.clone().into())
}

View file

@ -497,6 +497,7 @@ fn lower_dialog_layout(
"clicked",
)),
visibility: PropertyVisibility::InOut,
pure: None,
});
}
}

View file

@ -215,9 +215,9 @@ fn expression_for_property(element: &ElementRc, name: &str) -> ExpressionForProp
// Check that the expresison is valid in the new scope
let mut has_invalid = false;
e.expression.visit_recursive(&mut |ex| match ex {
Expression::CallbackReference(nr)
Expression::CallbackReference(nr, _)
| Expression::PropertyReference(nr)
| Expression::FunctionReference(nr) => {
| Expression::FunctionReference(nr, _) => {
let e = nr.element();
if !Rc::ptr_eq(&e, &element)
&& Weak::ptr_eq(

View file

@ -0,0 +1,94 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::Expression;
/// Check that pure expression only call pure functions
pub fn purity_check(doc: &crate::object_tree::Document, diag: &mut BuildDiagnostics) {
for component in &doc.inner_components {
crate::object_tree::recurse_elem_including_sub_components_no_borrow(
component,
&(),
&mut |elem, &()| {
crate::object_tree::visit_element_expressions(elem, |expr, name, _| {
if let Some(name) = name {
let lookup = elem.borrow().lookup_property(name);
if lookup.declared_pure.unwrap_or(false)
|| lookup.property_type.is_property_type()
{
ensure_pure(expr, Some(diag));
}
} else {
// model expression must be pure
ensure_pure(expr, Some(diag));
};
})
},
)
}
}
fn ensure_pure(expr: &Expression, mut diag: Option<&mut BuildDiagnostics>) -> bool {
let mut r = true;
expr.visit_recursive(&mut |e| match e {
Expression::CallbackReference(nr, node) => {
if !nr.element().borrow().lookup_property(nr.name()).declared_pure.unwrap_or(false) {
if let Some(diag) = diag.as_deref_mut() {
diag.push_error(format!("Cannot call impure callback '{}'", nr.name()), node);
}
r = false;
}
}
Expression::FunctionReference(nr, node) => {
match nr.element().borrow().lookup_property(nr.name()).declared_pure {
Some(true) => return,
Some(false) => {
if let Some(diag) = diag.as_deref_mut() {
diag.push_error(
format!("Cannot call impure function '{}'", nr.name(),),
node,
);
}
r = false;
}
None => {
if !ensure_pure(
&nr.element()
.borrow()
.bindings
.get(nr.name())
.expect("private function must be local and defined")
.borrow()
.expression,
None,
) {
if let Some(diag) = diag.as_deref_mut() {
diag.push_error(
format!("Cannot call impure function '{}'", nr.name(),),
node,
);
}
r = false;
}
}
}
}
Expression::BuiltinFunctionReference(func, node) => {
if !func.is_pure() {
if let Some(diag) = diag.as_deref_mut() {
diag.push_error("Cannot call impure function".into(), node);
}
r = false;
}
}
Expression::SelfAssignment { node, .. } => {
if let Some(diag) = diag.as_deref_mut() {
diag.push_error("Cannot assign in a pure context".into(), node);
}
r = false;
}
_ => (),
});
r
}

View file

@ -746,6 +746,7 @@ impl Expression {
lhs: Box::new(lhs),
rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
op,
node: Some(NodeOrToken::Node(node.into())),
}
}
@ -1063,7 +1064,10 @@ fn continue_lookup_within_element(
if let Some(x) = it.next() {
ctx.diag.push_error("Cannot access fields of callback".into(), &x)
}
Expression::CallbackReference(NamedReference::new(elem, &lookup_result.resolved_name))
Expression::CallbackReference(
NamedReference::new(elem, &lookup_result.resolved_name),
Some(NodeOrToken::Token(second)),
)
} else if matches!(lookup_result.property_type, Type::Function { .. }) {
if !lookup_result.is_local_to_component
&& lookup_result.property_visibility == PropertyVisibility::Private
@ -1083,7 +1087,10 @@ fn continue_lookup_within_element(
.into(),
}
} else {
Expression::FunctionReference(NamedReference::new(elem, &lookup_result.resolved_name))
Expression::FunctionReference(
NamedReference::new(elem, &lookup_result.resolved_name),
Some(NodeOrToken::Token(second)),
)
}
} else {
let mut err = |extra: &str| {
@ -1295,7 +1302,7 @@ pub fn resolve_two_way_binding(
}
Some(n)
}
Expression::CallbackReference(n) => {
Expression::CallbackReference(n, _) => {
if ctx.property_type != Type::InferredCallback && ty != ctx.property_type {
ctx.diag.push_error("Cannot bind to a callback".into(), &node);
None