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

@ -940,7 +940,7 @@ It is possible to re-expose a callback or properties from a global using the two
```slint,no-preview
global Logic := {
property <int> the-value;
callback magic-operation(int) -> int;
pure callback magic-operation(int) -> int;
}
SomeComponent := Text {
@ -952,7 +952,7 @@ export MainWindow := Window {
// re-expose the global properties such that the native code
// can access or modify them
property the-value <=> Logic.the-value;
callback magic-operation <=> Logic.magic-operation;
pure callback magic-operation <=> Logic.magic-operation;
SomeComponent {}
}

View file

@ -393,7 +393,7 @@ connected to the native code.
import { HorizontalBox, VerticalBox, LineEdit } from "std-widgets.slint";
export global Logic := {
callback to-upper-case(string) -> string;
pure callback to-upper-case(string) -> string;
// You can collect other global properties here
}
@ -486,7 +486,7 @@ import { HorizontalBox, Button } from "std-widgets.slint";
export global Tr := {
// Do the translation of the first argument, with an array of string as supstitution
callback gettext(string, [string]) -> string;
pure callback gettext(string, [string]) -> string;
// A default implementation that returns the original string for preview purposes.
gettext(text, _) => { return text; }

View file

@ -5,10 +5,10 @@ import { LineEdit, Button, ComboBox, VerticalBox } from "std-widgets.slint";
Booker := Window {
// returns true if the string parameter is a valid date
callback validate-date(string) -> bool;
pure callback validate-date(string) -> bool;
validate-date(_) => { true }
// returns true if the first date is before the second date and they are both valid
callback compare-date(string, string) -> bool;
pure callback compare-date(string, string) -> bool;
compare-date(a, b) => { a <= b }
property <bool> message-visible;
VerticalBox {

View file

@ -17,7 +17,7 @@ slint::slint! {
property original-image <=> original.source;
property filters <=> filter-combo.model;
callback filter-image(int) -> image;
pure callback filter-image(int) -> image;
HorizontalBox {
VerticalBox {

View file

@ -8,7 +8,7 @@ export MainWindow := Window {
preferred-width: 800px;
preferred-height: 600px;
callback render_plot(float /* pitch */, float /* yaw */, float /* amplitude */) -> image;
pure callback render_plot(float /* pitch */, float /* yaw */, float /* amplitude */) -> image;
property <float> pitch: 0.15;
property <float> yaw: 0.5;

View file

@ -172,8 +172,8 @@ impl BuiltinFunction {
}
}
/// It is pure if the return value only depends on its argument and has no side effect
fn is_pure(&self) -> bool {
/// It is const if the return value only depends on its argument and has no side effect
fn is_const(&self) -> bool {
match self {
BuiltinFunction::GetWindowScaleFactor => false,
BuiltinFunction::GetWindowDefaultFontSize => false,
@ -215,6 +215,43 @@ impl BuiltinFunction {
| BuiltinFunction::RegisterBitmapFont => false,
}
}
// It is pure if it has no side effect
pub fn is_pure(&self) -> bool {
match self {
BuiltinFunction::GetWindowScaleFactor => true,
BuiltinFunction::GetWindowDefaultFontSize => true,
BuiltinFunction::AnimationTick => true,
BuiltinFunction::DarkColorScheme => true,
// Even if it has technically side effect, we still consider it as pure for our purpose
BuiltinFunction::Debug => true,
BuiltinFunction::Mod
| BuiltinFunction::Round
| BuiltinFunction::Ceil
| BuiltinFunction::Floor
| BuiltinFunction::Abs
| BuiltinFunction::Sqrt
| BuiltinFunction::Cos
| BuiltinFunction::Sin
| BuiltinFunction::Tan
| BuiltinFunction::ACos
| BuiltinFunction::ASin
| BuiltinFunction::Log
| BuiltinFunction::Pow
| BuiltinFunction::ATan => true,
BuiltinFunction::SetFocusItem => false,
BuiltinFunction::ShowPopupWindow => false,
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
BuiltinFunction::ColorBrighter | BuiltinFunction::ColorDarker => true,
BuiltinFunction::ImageSize => true,
BuiltinFunction::ArrayLength => true,
BuiltinFunction::Rgb => true,
BuiltinFunction::ImplicitLayoutInfo(_) => true,
BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory
| BuiltinFunction::RegisterBitmapFont => false,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -344,13 +381,13 @@ pub enum Expression {
/// Reference to the callback `<name>` in the `<element>`
///
/// Note: if we are to separate expression and statement, we probably do not need to have callback reference within expressions
CallbackReference(NamedReference),
CallbackReference(NamedReference, Option<NodeOrToken>),
/// Reference to the property
PropertyReference(NamedReference),
/// Reference to a function
FunctionReference(NamedReference),
FunctionReference(NamedReference, Option<NodeOrToken>),
/// Reference to a function built into the run-time, implemented natively
BuiltinFunctionReference(BuiltinFunction, Option<SourceLocation>),
@ -436,12 +473,13 @@ pub enum Expression {
source_location: Option<SourceLocation>,
},
/// A SelfAssignment or an Assignment. When op is '=' this is a signal assignment.
/// A SelfAssignment or an Assignment. When op is '=' this is a simple assignment.
SelfAssignment {
lhs: Box<Expression>,
rhs: Box<Expression>,
/// '+', '-', '/', '*', or '='
op: char,
node: Option<NodeOrToken>,
},
BinaryExpression {
@ -518,8 +556,8 @@ impl Expression {
Expression::StringLiteral(_) => Type::String,
Expression::NumberLiteral(_, unit) => unit.ty(),
Expression::BoolLiteral(_) => Type::Bool,
Expression::CallbackReference(nr) => nr.ty(),
Expression::FunctionReference(nr) => nr.ty(),
Expression::CallbackReference(nr, _) => nr.ty(),
Expression::FunctionReference(nr, _) => nr.ty(),
Expression::PropertyReference(nr) => nr.ty(),
Expression::BuiltinFunctionReference(funcref, _) => funcref.ty(),
Expression::MemberFunction { member, .. } => member.ty(),
@ -865,9 +903,9 @@ impl Expression {
Expression::NumberLiteral(_, _) => true,
Expression::BoolLiteral(_) => true,
Expression::CallbackReference { .. } => false,
Expression::FunctionReference(nr) => nr.is_constant(),
Expression::FunctionReference(nr, _) => nr.is_constant(),
Expression::PropertyReference(nr) => nr.is_constant(),
Expression::BuiltinFunctionReference(func, _) => func.is_pure(),
Expression::BuiltinFunctionReference(func, _) => func.is_const(),
Expression::MemberFunction { .. } => false,
Expression::ElementReference(_) => false,
Expression::RepeaterIndexReference { .. } => false,
@ -1369,9 +1407,9 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std
Expression::StringLiteral(s) => write!(f, "{:?}", s),
Expression::NumberLiteral(vl, unit) => write!(f, "{}{}", vl, unit),
Expression::BoolLiteral(b) => write!(f, "{:?}", b),
Expression::CallbackReference(a) => write!(f, "{:?}", a),
Expression::CallbackReference(a, _) => write!(f, "{:?}", a),
Expression::PropertyReference(a) => write!(f, "{:?}", a),
Expression::FunctionReference(a) => write!(f, "{:?}", a),
Expression::FunctionReference(a, _) => write!(f, "{:?}", a),
Expression::BuiltinFunctionReference(a, _) => write!(f, "{:?}", a),
Expression::MemberFunction { base, base_node: _, member } => {
pretty_print(f, base)?;
@ -1425,7 +1463,7 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std
}
write!(f, ")")
}
Expression::SelfAssignment { lhs, rhs, op } => {
Expression::SelfAssignment { lhs, rhs, op, .. } => {
pretty_print(f, lhs)?;
write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
pretty_print(f, rhs)

View file

@ -403,6 +403,7 @@ impl ElementType {
resolved_name,
property_type: Type::Invalid,
property_visibility: PropertyVisibility::Private,
declared_pure: None,
is_local_to_component: false,
}
} else {
@ -413,6 +414,7 @@ impl ElementType {
resolved_name,
property_type: p.ty.clone(),
property_visibility: p.property_visibility,
declared_pure: None,
is_local_to_component: false,
},
}
@ -429,6 +431,7 @@ impl ElementType {
resolved_name,
property_type,
property_visibility: PropertyVisibility::InOut,
declared_pure: None,
is_local_to_component: false,
}
}
@ -436,6 +439,7 @@ impl ElementType {
resolved_name: Cow::Borrowed(name),
property_type: Type::Invalid,
property_visibility: PropertyVisibility::Private,
declared_pure: None,
is_local_to_component: false,
},
}
@ -690,6 +694,7 @@ pub struct PropertyLookupResult<'a> {
pub resolved_name: std::borrow::Cow<'a, str>,
pub property_type: Type,
pub property_visibility: PropertyVisibility,
pub declared_pure: Option<bool>,
/// True if the property is part of the the current component (for visibility purposes)
pub is_local_to_component: bool,
}

View file

@ -68,9 +68,9 @@ pub fn lower_expression(
llr_Expression::NumberLiteral(unit.normalize(*n))
}
tree_Expression::BoolLiteral(b) => llr_Expression::BoolLiteral(*b),
tree_Expression::CallbackReference(nr)
tree_Expression::CallbackReference(nr, _)
| tree_Expression::PropertyReference(nr)
| tree_Expression::FunctionReference(nr) => {
| tree_Expression::FunctionReference(nr, _) => {
llr_Expression::PropertyReference(ctx.map_property_reference(nr))
}
tree_Expression::BuiltinFunctionReference(_, _) => panic!(),
@ -120,17 +120,19 @@ pub fn lower_expression(
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
llr_Expression::BuiltinFunctionCall { function: *f, arguments }
}
tree_Expression::CallbackReference(nr) => {
tree_Expression::CallbackReference(nr, _) => {
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
llr_Expression::CallBackCall { callback: ctx.map_property_reference(nr), arguments }
}
tree_Expression::FunctionReference(nr) => {
tree_Expression::FunctionReference(nr, _) => {
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
llr_Expression::FunctionCall { function: ctx.map_property_reference(nr), arguments }
}
_ => panic!("not calling a function"),
},
tree_Expression::SelfAssignment { lhs, rhs, op } => lower_assignment(lhs, rhs, *op, ctx),
tree_Expression::SelfAssignment { lhs, rhs, op, .. } => {
lower_assignment(lhs, rhs, *op, ctx)
}
tree_Expression::BinaryExpression { lhs, rhs, op } => llr_Expression::BinaryExpression {
lhs: Box::new(lower_expression(lhs, ctx)),
rhs: Box::new(lower_expression(rhs, ctx)),

View file

@ -344,6 +344,7 @@ impl LookupObject for InScopeLookup {
let e = expression_from_reference(
NamedReference::new(elem, name),
&prop.property_type,
&ctx.current_token,
);
if let Some(r) = f.borrow_mut()(name, e.into()) {
return Some(r);
@ -364,8 +365,12 @@ impl LookupObject for InScopeLookup {
|elem| elem.lookup(ctx, name),
|elem| {
elem.borrow().property_declarations.get(name).map(|prop| {
expression_from_reference(NamedReference::new(elem, name), &prop.property_type)
.into()
expression_from_reference(
NamedReference::new(elem, name),
&prop.property_type,
&ctx.current_token,
)
.into()
})
},
)
@ -375,24 +380,33 @@ impl LookupObject for InScopeLookup {
impl LookupObject for ElementRc {
fn for_each_entry<R>(
&self,
_ctx: &LookupCtx,
ctx: &LookupCtx,
f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
) -> Option<R> {
for (name, prop) in &self.borrow().property_declarations {
let e = expression_from_reference(NamedReference::new(self, name), &prop.property_type);
let e = expression_from_reference(
NamedReference::new(self, name),
&prop.property_type,
&ctx.current_token,
);
if let Some(r) = f(name, e.into()) {
return Some(r);
}
}
let list = self.borrow().base_type.property_list();
for (name, ty) in list {
let e = expression_from_reference(NamedReference::new(self, &name), &ty);
let e = expression_from_reference(
NamedReference::new(self, &name),
&ty,
&ctx.current_token,
);
if let Some(r) = f(&name, e.into()) {
return Some(r);
}
}
for (name, ty) in crate::typeregister::reserved_properties() {
let e = expression_from_reference(NamedReference::new(self, name), &ty);
let e =
expression_from_reference(NamedReference::new(self, name), &ty, &ctx.current_token);
if let Some(r) = f(name, e.into()) {
return Some(r);
}
@ -400,7 +414,7 @@ impl LookupObject for ElementRc {
None
}
fn lookup(&self, _ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
let lookup_result = self.borrow().lookup_property(name);
if lookup_result.property_type != Type::Invalid
&& (lookup_result.is_local_to_component
@ -410,6 +424,7 @@ impl LookupObject for ElementRc {
expression: expression_from_reference(
NamedReference::new(self, &lookup_result.resolved_name),
&lookup_result.property_type,
&ctx.current_token,
),
deprecated: (lookup_result.resolved_name != name)
.then(|| lookup_result.resolved_name.to_string()),
@ -420,10 +435,14 @@ impl LookupObject for ElementRc {
}
}
fn expression_from_reference(n: NamedReference, ty: &Type) -> Expression {
fn expression_from_reference(
n: NamedReference,
ty: &Type,
node: &Option<NodeOrToken>,
) -> Expression {
match ty {
Type::Callback { .. } => Expression::CallbackReference(n),
Type::Function { .. } => Expression::FunctionReference(n),
Type::Callback { .. } => Expression::CallbackReference(n, node.clone()),
Type::Function { .. } => Expression::FunctionReference(n, node.clone()),
_ => Expression::PropertyReference(n),
}
}

View file

@ -360,6 +360,8 @@ pub struct PropertyDeclaration {
/// Public API property exposed as an alias: it shouldn't be generated but instead forward to the alias.
pub is_alias: Option<NamedReference>,
pub visibility: PropertyVisibility,
/// For function or callback: whether it is declared as `pure` (None for private function for which this has to be deduced)
pub pure: Option<bool>,
}
impl PropertyDeclaration {
@ -869,6 +871,10 @@ impl Element {
let name =
unwrap_or_continue!(parser::identifier_text(&sig_decl.DeclaredIdentifier()); diag);
let pure = Some(
sig_decl.child_token(SyntaxKind::Identifier).map_or(false, |t| t.text() == "pure"),
);
if let Some(csn) = sig_decl.TwoWayBinding() {
r.bindings
.insert(name.clone(), BindingExpression::new_uncompiled(csn.into()).into());
@ -878,6 +884,7 @@ impl Element {
property_type: Type::InferredCallback,
node: Some(sig_decl.into()),
visibility: PropertyVisibility::InOut,
pure,
..Default::default()
},
);
@ -924,6 +931,7 @@ impl Element {
property_type: Type::Callback { return_type, args },
node: Some(sig_decl.into()),
visibility: PropertyVisibility::InOut,
pure,
..Default::default()
},
);
@ -979,18 +987,29 @@ impl Element {
assert!(diag.has_error());
}
let public =
func.child_token(SyntaxKind::Identifier).map_or(false, |t| t.text() == "public");
let mut visibility = PropertyVisibility::Private;
let mut pure = None;
for token in func.children_with_tokens() {
if token.kind() != SyntaxKind::Identifier {
continue;
}
match token.as_token().unwrap().text() {
"pure" => pure = Some(true),
"public" => {
visibility = PropertyVisibility::Public;
pure = pure.or_else(|| Some(false));
}
_ => (),
}
}
r.property_declarations.insert(
name,
PropertyDeclaration {
property_type: Type::Function { return_type, args },
node: Some(func.into()),
visibility: if public {
PropertyVisibility::Public
} else {
PropertyVisibility::Private
},
visibility,
pure,
..Default::default()
},
);
@ -1334,6 +1353,7 @@ impl Element {
resolved_name: name.into(),
property_type: p.property_type.clone(),
property_visibility: p.visibility,
declared_pure: p.pure,
is_local_to_component: true,
},
)
@ -1891,8 +1911,8 @@ pub fn visit_named_references_in_expression(
expr.visit_mut(|sub| visit_named_references_in_expression(sub, vis));
match expr {
Expression::PropertyReference(r)
| Expression::CallbackReference(r)
| Expression::FunctionReference(r) => vis(r),
| Expression::CallbackReference(r, _)
| Expression::FunctionReference(r, _) => vis(r),
Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
Expression::SolveLayout(l, _) => l.visit_named_references(vis),
Expression::ComputeLayoutInfo(l, _) => l.visit_named_references(vis),

View file

@ -55,14 +55,18 @@ pub fn parse_element_content(p: &mut impl Parser) {
SyntaxKind::Identifier if p.peek().as_str() == "for" => {
parse_repeated_element(&mut *p);
}
SyntaxKind::Identifier if p.peek().as_str() == "callback" => {
SyntaxKind::Identifier
if p.peek().as_str() == "callback"
|| (p.peek().as_str() == "pure" && p.nth(1).as_str() == "callback") =>
{
parse_callback_declaration(&mut *p);
}
SyntaxKind::Identifier if p.peek().as_str() == "function" => {
parse_function(&mut *p);
}
SyntaxKind::Identifier
if p.peek().as_str() == "public" && p.nth(1).as_str() == "function" =>
if p.peek().as_str() == "function"
|| (matches!(p.peek().as_str(), "public" | "pure")
&& p.nth(1).as_str() == "function")
|| (matches!(p.nth(1).as_str(), "public" | "pure")
&& p.nth(2).as_str() == "function") =>
{
parse_function(&mut *p);
}
@ -288,7 +292,7 @@ fn parse_two_way_binding(p: &mut impl Parser) {
/// callback foobar;
/// callback my_callback();
/// callback foo(int, string);
/// callback one_arg({ a: string, b: string});
/// pure callback one_arg({ a: string, b: string});
/// callback end_coma(a, b, c,);
/// callback with_return(a, b) -> int;
/// callback with_return2({a: string}) -> { a: string };
@ -296,8 +300,11 @@ fn parse_two_way_binding(p: &mut impl Parser) {
/// ```
/// Must consume at least one token
fn parse_callback_declaration(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "callback");
let mut p = p.start_node(SyntaxKind::CallbackDeclaration);
if p.peek().as_str() == "pure" {
p.consume();
}
debug_assert_eq!(p.peek().as_str(), "callback");
p.expect(SyntaxKind::Identifier); // "callback"
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
@ -572,7 +579,22 @@ fn parse_transition_inner(p: &mut impl Parser) -> bool {
fn parse_function(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::Function);
if p.peek().as_str() == "public" {
p.consume()
p.consume();
if p.peek().as_str() == "pure" {
p.consume()
}
} else if p.peek().as_str() == "pure" {
p.consume();
if p.peek().as_str() == "public" {
p.consume()
}
}
if p.peek().as_str() != "function" {
p.error("Unexpected identifier");
p.consume();
while p.peek().kind == SyntaxKind::Identifier && p.peek().as_str() != "function" {
p.consume();
}
}
debug_assert_eq!(p.peek().as_str(), "function");
p.expect(SyntaxKind::Identifier); // "function"

View file

@ -35,6 +35,7 @@ mod lower_tabwidget;
mod materialize_fake_properties;
mod move_declarations;
mod optimize_useless_rectangles;
mod purity_check;
mod remove_aliases;
mod remove_unused_properties;
mod repeater_component;
@ -74,11 +75,7 @@ pub async fn run_passes(
let global_type_registry = type_loader.global_type_registry.clone();
let root_component = &doc.root_component;
infer_aliases_types::resolve_aliases(doc, diag);
resolving::resolve_expressions(doc, type_loader, diag);
check_expressions::check_expressions(doc, diag);
check_rotation::check_rotation(doc, diag);
unique_id::check_unique_id(doc, diag);
run_import_passes(doc, type_loader, diag);
check_public_api::check_public_api(doc, diag);
collect_subcomponents::collect_subcomponents(root_component);
@ -268,6 +265,7 @@ pub fn run_import_passes(
infer_aliases_types::resolve_aliases(doc, diag);
resolving::resolve_expressions(doc, type_loader, diag);
check_expressions::check_expressions(doc, diag);
purity_check::purity_check(doc, diag);
check_rotation::check_rotation(doc, diag);
unique_id::check_unique_id(doc, diag);
}

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

View file

@ -5,7 +5,7 @@ Compo1 := Rectangle {
property <int> a : aa();
// ^error{The binding for the property 'a' is part of a binding loop}
callback aa() -> int;
pure callback aa() -> int;
function factorial(n: int) -> int {
// ^error{The binding for the property 'factorial' is part of a binding loop}
@ -14,7 +14,7 @@ Compo1 := Rectangle {
property <int> b;
public function bb() -> int { return b; }
public pure function bb() -> int { return b; }
// ^error{The binding for the property 'bb' is part of a binding loop}
}

View file

@ -15,4 +15,5 @@ X := Rectangle {
x: edit.focus;
// ^error{Cannot convert function\(element ref\) -> void to length}
// ^^error{'edit.focus' must be called. Did you forgot the '\(\)'\?}
// ^^^error{Cannot call impure function}
}

View file

@ -0,0 +1,63 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
component Foo {
property <int> prop;
callback c1;
pure callback c2;
function f1() {
prop = 1;
}
function f2() -> int { 42 }
pure function f3() {
f2();
f1();
// ^error{Cannot call impure function 'f1'}
prop /= 5;
// ^error{Cannot assign in a pure context}
}
public function f4() {}
pure function f5() {
f1();
// ^error{Cannot call impure function 'f1'}
f2(); // ok, private function auto-detected as pure
f3();
f4();
// ^error{Cannot call impure function 'f4'}
c1();
// ^error{Cannot call impure callback 'c1'}
c2();
}
c1 => { f2() }
c2 => {
c1();
// ^error{Cannot call impure callback 'c1'}
}
property <int> p1: f2();
property <int> p2: {
p1 = 42;
// ^error{Cannot assign in a pure context}
42
};
property <int> p3: {
pw.show();
// ^error{Cannot call impure function}
fs.focus();
// ^error{Cannot call impure function}
42
};
pw := PopupWindow {}
fs := FocusScope {}
}

View file

@ -141,6 +141,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
resolved_name: name.into(),
is_local_to_component: false,
property_visibility: crate::object_tree::PropertyVisibility::InOut,
declared_pure: None,
};
}
}
@ -156,6 +157,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
resolved_name: format!("{}-{}", pre, suf).into(),
is_local_to_component: false,
property_visibility: crate::object_tree::PropertyVisibility::InOut,
declared_pure: None,
};
}
}
@ -167,6 +169,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
property_type: Type::Invalid,
is_local_to_component: false,
property_visibility: crate::object_tree::PropertyVisibility::Private,
declared_pure: None,
}
}

View file

@ -212,7 +212,7 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
v
}
Expression::FunctionCall { function, arguments, source_location: _ } => match &**function {
Expression::FunctionReference(nr) => {
Expression::FunctionReference(nr, _) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(&nr.element(), local_context.component_instance, guard) {
@ -225,14 +225,14 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
},
}
}
Expression::CallbackReference(nr) => {
Expression::CallbackReference(nr, _) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args).unwrap()
}
Expression::BuiltinFunctionReference(f, _) => call_builtin_function(*f, arguments, local_context),
_ => panic!("call of something not a callback: {function:?}"),
}
Expression::SelfAssignment { lhs, rhs, op } => {
Expression::SelfAssignment { lhs, rhs, op, .. } => {
let rhs = eval_expression(&**rhs, local_context);
eval_assignment(lhs, *op, rhs, local_context);
Value::Void

View file

@ -148,6 +148,7 @@ pub(crate) fn add_highlight_items(doc: &Document) {
expose_in_public_api: false,
is_alias: None,
visibility: PropertyVisibility::Input,
pure: None,
},
);
doc.root_component.root_element.borrow_mut().property_analysis.borrow_mut().insert(

View file

@ -2,10 +2,10 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
Foo := Rectangle {
callback hallo_alias <=> xxx.hallo;
pure callback hallo_alias <=> xxx.hallo;
callback clicked <=> are.clicked;
xxx := Rectangle {
callback hallo(int) -> int;
pure callback hallo(int) -> int;
hallo(a) => { return a + 88; }
}
@ -14,8 +14,8 @@ Foo := Rectangle {
TestCase := Rectangle {
callback foo1_alias <=> foo1.hallo_alias;
callback foo2_alias <=> foo2.hallo_alias;
pure callback foo1_alias <=> foo1.hallo_alias;
pure callback foo2_alias <=> foo2.hallo_alias;
callback foo1_clicked <=> foo1.clicked;

View file

@ -9,7 +9,7 @@ global G := {
}
SubCompo := Rectangle {
public function hello() -> color { red }
public pure function hello() -> color { red }
}
TestCase := Rectangle {

View file

@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
TestCase := Rectangle {
callback test_func(int) -> int;
callback test_func2(string, int) -> string;
pure callback test_func(int) -> int;
pure callback test_func2(string, int) -> string;
test_func2(str, val) => { str + "=" + (val + some_value) }
property <int> test_prop: 4 + test_func(2);

View file

@ -11,7 +11,7 @@ struct MyStruct := { x:int, y: int, }
global InternalGlobal := {
property <int> hello: 42;
property <MyStruct> my_struct;
callback sum(int, int)->int;
pure callback sum(int, int)->int;
}
export { InternalGlobal as PublicGlobal }

View file

@ -3,9 +3,9 @@
global Glo := {
property <int> hello: 42;
callback sum(int, int) -> int;
callback mul(int, int) -> int;
callback calculate_profit() -> int;
pure callback sum(int, int) -> int;
pure callback mul(int, int) -> int;
pure callback calculate_profit() -> int;
calculate_profit() => { return 1000; }
}
@ -16,8 +16,8 @@ ExtraComp := Rectangle {
TestCase := Window {
callback sum <=> Glo.sum;
callback mul <=> Glo.mul;
pure callback sum <=> Glo.sum;
pure callback mul <=> Glo.mul;
x := ExtraComp {}
property<int> five: x.five;

View file

@ -2,8 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
global G {
callback is-center(length, length, length) -> bool;
is-center(pos, size, parent) => {
public pure function is-center(pos: length, size: length, parent: length) -> bool {
return abs(pos / 1px - (parent - size) / 2px) < 0.001;
}
}

View file

@ -18,8 +18,8 @@ TestCase := Rectangle {
foo.b += 8 + obj_conversion2.b;
}
callback return_object() -> { aa: { bb: int } };
return_object => { return { aa: { bb: { cc: 42 }.cc } }; }
function return_object() -> { aa: { bb: int } }
{ return { aa: { bb: { cc: 42 }.cc } }; }
property <bool> test: return_object().aa.bb == 42 && obj_binop_merge;
}

View file

@ -15,7 +15,7 @@ TestCase := Rectangle {
cb1(foo) => { return { b: foo.a.member+1 }; }
property<Foo2> xx;
callback cb2(Foo2, Foo2)->Foo2;
pure callback cb2(Foo2, Foo2)->Foo2;
property<Foo2> xx2: cb2(xx, xx);

View file

@ -453,8 +453,8 @@ fn completion_item_from_expression(str: &str, lookup_result: LookupResult) -> Co
let mut c = CompletionItem::new_simple(str.to_string(), expression.ty().to_string());
c.kind = match expression {
Expression::BoolLiteral(_) => Some(CompletionItemKind::CONSTANT),
Expression::CallbackReference(_) => Some(CompletionItemKind::METHOD),
Expression::FunctionReference(_) => Some(CompletionItemKind::FUNCTION),
Expression::CallbackReference(..) => Some(CompletionItemKind::METHOD),
Expression::FunctionReference(..) => Some(CompletionItemKind::FUNCTION),
Expression::PropertyReference(_) => Some(CompletionItemKind::PROPERTY),
Expression::BuiltinFunctionReference(..) => Some(CompletionItemKind::FUNCTION),
Expression::BuiltinMacroReference(..) => Some(CompletionItemKind::FUNCTION),

View file

@ -71,9 +71,9 @@ pub fn goto_definition(
} => e.upgrade()?.borrow().node.clone()?.into(),
LookupResult::Expression {
expression:
Expression::CallbackReference(nr)
Expression::CallbackReference(nr, _)
| Expression::PropertyReference(nr)
| Expression::FunctionReference(nr),
| Expression::FunctionReference(nr, _),
..
} => {
let mut el = nr.element();

View file

@ -127,8 +127,8 @@ fn fully_qualify_property_access(
Some(LookupResult::Expression {
expression:
Expression::PropertyReference(nr)
| Expression::CallbackReference(nr)
| Expression::FunctionReference(nr),
| Expression::CallbackReference(nr, _)
| Expression::FunctionReference(nr, _),
..
}) => {
if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) {