mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-17 18:57:10 +00:00
WIP: pure qualifier for callback and functions
This commit is contained in:
parent
8a09043e63
commit
1cbd61145e
34 changed files with 366 additions and 93 deletions
|
|
@ -940,7 +940,7 @@ It is possible to re-expose a callback or properties from a global using the two
|
||||||
```slint,no-preview
|
```slint,no-preview
|
||||||
global Logic := {
|
global Logic := {
|
||||||
property <int> the-value;
|
property <int> the-value;
|
||||||
callback magic-operation(int) -> int;
|
pure callback magic-operation(int) -> int;
|
||||||
}
|
}
|
||||||
|
|
||||||
SomeComponent := Text {
|
SomeComponent := Text {
|
||||||
|
|
@ -952,7 +952,7 @@ export MainWindow := Window {
|
||||||
// re-expose the global properties such that the native code
|
// re-expose the global properties such that the native code
|
||||||
// can access or modify them
|
// can access or modify them
|
||||||
property the-value <=> Logic.the-value;
|
property the-value <=> Logic.the-value;
|
||||||
callback magic-operation <=> Logic.magic-operation;
|
pure callback magic-operation <=> Logic.magic-operation;
|
||||||
|
|
||||||
SomeComponent {}
|
SomeComponent {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ connected to the native code.
|
||||||
import { HorizontalBox, VerticalBox, LineEdit } from "std-widgets.slint";
|
import { HorizontalBox, VerticalBox, LineEdit } from "std-widgets.slint";
|
||||||
|
|
||||||
export global Logic := {
|
export global Logic := {
|
||||||
callback to-upper-case(string) -> string;
|
pure callback to-upper-case(string) -> string;
|
||||||
// You can collect other global properties here
|
// You can collect other global properties here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,7 +486,7 @@ import { HorizontalBox, Button } from "std-widgets.slint";
|
||||||
|
|
||||||
export global Tr := {
|
export global Tr := {
|
||||||
// Do the translation of the first argument, with an array of string as supstitution
|
// 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.
|
// A default implementation that returns the original string for preview purposes.
|
||||||
gettext(text, _) => { return text; }
|
gettext(text, _) => { return text; }
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import { LineEdit, Button, ComboBox, VerticalBox } from "std-widgets.slint";
|
||||||
|
|
||||||
Booker := Window {
|
Booker := Window {
|
||||||
// returns true if the string parameter is a valid date
|
// returns true if the string parameter is a valid date
|
||||||
callback validate-date(string) -> bool;
|
pure callback validate-date(string) -> bool;
|
||||||
validate-date(_) => { true }
|
validate-date(_) => { true }
|
||||||
// returns true if the first date is before the second date and they are both valid
|
// 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 }
|
compare-date(a, b) => { a <= b }
|
||||||
property <bool> message-visible;
|
property <bool> message-visible;
|
||||||
VerticalBox {
|
VerticalBox {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ slint::slint! {
|
||||||
|
|
||||||
property original-image <=> original.source;
|
property original-image <=> original.source;
|
||||||
property filters <=> filter-combo.model;
|
property filters <=> filter-combo.model;
|
||||||
callback filter-image(int) -> image;
|
pure callback filter-image(int) -> image;
|
||||||
|
|
||||||
HorizontalBox {
|
HorizontalBox {
|
||||||
VerticalBox {
|
VerticalBox {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export MainWindow := Window {
|
||||||
preferred-width: 800px;
|
preferred-width: 800px;
|
||||||
preferred-height: 600px;
|
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> pitch: 0.15;
|
||||||
property <float> yaw: 0.5;
|
property <float> yaw: 0.5;
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,8 @@ impl BuiltinFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// It is pure if the return value only depends on its argument and has no side effect
|
/// It is const if the return value only depends on its argument and has no side effect
|
||||||
fn is_pure(&self) -> bool {
|
fn is_const(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
BuiltinFunction::GetWindowScaleFactor => false,
|
BuiltinFunction::GetWindowScaleFactor => false,
|
||||||
BuiltinFunction::GetWindowDefaultFontSize => false,
|
BuiltinFunction::GetWindowDefaultFontSize => false,
|
||||||
|
|
@ -215,6 +215,43 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::RegisterBitmapFont => false,
|
| 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)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
|
@ -344,13 +381,13 @@ pub enum Expression {
|
||||||
/// Reference to the callback `<name>` in the `<element>`
|
/// 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
|
/// 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
|
/// Reference to the property
|
||||||
PropertyReference(NamedReference),
|
PropertyReference(NamedReference),
|
||||||
|
|
||||||
/// Reference to a function
|
/// Reference to a function
|
||||||
FunctionReference(NamedReference),
|
FunctionReference(NamedReference, Option<NodeOrToken>),
|
||||||
|
|
||||||
/// Reference to a function built into the run-time, implemented natively
|
/// Reference to a function built into the run-time, implemented natively
|
||||||
BuiltinFunctionReference(BuiltinFunction, Option<SourceLocation>),
|
BuiltinFunctionReference(BuiltinFunction, Option<SourceLocation>),
|
||||||
|
|
@ -436,12 +473,13 @@ pub enum Expression {
|
||||||
source_location: Option<SourceLocation>,
|
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 {
|
SelfAssignment {
|
||||||
lhs: Box<Expression>,
|
lhs: Box<Expression>,
|
||||||
rhs: Box<Expression>,
|
rhs: Box<Expression>,
|
||||||
/// '+', '-', '/', '*', or '='
|
/// '+', '-', '/', '*', or '='
|
||||||
op: char,
|
op: char,
|
||||||
|
node: Option<NodeOrToken>,
|
||||||
},
|
},
|
||||||
|
|
||||||
BinaryExpression {
|
BinaryExpression {
|
||||||
|
|
@ -518,8 +556,8 @@ impl Expression {
|
||||||
Expression::StringLiteral(_) => Type::String,
|
Expression::StringLiteral(_) => Type::String,
|
||||||
Expression::NumberLiteral(_, unit) => unit.ty(),
|
Expression::NumberLiteral(_, unit) => unit.ty(),
|
||||||
Expression::BoolLiteral(_) => Type::Bool,
|
Expression::BoolLiteral(_) => Type::Bool,
|
||||||
Expression::CallbackReference(nr) => nr.ty(),
|
Expression::CallbackReference(nr, _) => nr.ty(),
|
||||||
Expression::FunctionReference(nr) => nr.ty(),
|
Expression::FunctionReference(nr, _) => nr.ty(),
|
||||||
Expression::PropertyReference(nr) => nr.ty(),
|
Expression::PropertyReference(nr) => nr.ty(),
|
||||||
Expression::BuiltinFunctionReference(funcref, _) => funcref.ty(),
|
Expression::BuiltinFunctionReference(funcref, _) => funcref.ty(),
|
||||||
Expression::MemberFunction { member, .. } => member.ty(),
|
Expression::MemberFunction { member, .. } => member.ty(),
|
||||||
|
|
@ -865,9 +903,9 @@ impl Expression {
|
||||||
Expression::NumberLiteral(_, _) => true,
|
Expression::NumberLiteral(_, _) => true,
|
||||||
Expression::BoolLiteral(_) => true,
|
Expression::BoolLiteral(_) => true,
|
||||||
Expression::CallbackReference { .. } => false,
|
Expression::CallbackReference { .. } => false,
|
||||||
Expression::FunctionReference(nr) => nr.is_constant(),
|
Expression::FunctionReference(nr, _) => nr.is_constant(),
|
||||||
Expression::PropertyReference(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::MemberFunction { .. } => false,
|
||||||
Expression::ElementReference(_) => false,
|
Expression::ElementReference(_) => false,
|
||||||
Expression::RepeaterIndexReference { .. } => 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::StringLiteral(s) => write!(f, "{:?}", s),
|
||||||
Expression::NumberLiteral(vl, unit) => write!(f, "{}{}", vl, unit),
|
Expression::NumberLiteral(vl, unit) => write!(f, "{}{}", vl, unit),
|
||||||
Expression::BoolLiteral(b) => write!(f, "{:?}", b),
|
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::PropertyReference(a) => write!(f, "{:?}", a),
|
||||||
Expression::FunctionReference(a) => write!(f, "{:?}", a),
|
Expression::FunctionReference(a, _) => write!(f, "{:?}", a),
|
||||||
Expression::BuiltinFunctionReference(a, _) => write!(f, "{:?}", a),
|
Expression::BuiltinFunctionReference(a, _) => write!(f, "{:?}", a),
|
||||||
Expression::MemberFunction { base, base_node: _, member } => {
|
Expression::MemberFunction { base, base_node: _, member } => {
|
||||||
pretty_print(f, base)?;
|
pretty_print(f, base)?;
|
||||||
|
|
@ -1425,7 +1463,7 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std
|
||||||
}
|
}
|
||||||
write!(f, ")")
|
write!(f, ")")
|
||||||
}
|
}
|
||||||
Expression::SelfAssignment { lhs, rhs, op } => {
|
Expression::SelfAssignment { lhs, rhs, op, .. } => {
|
||||||
pretty_print(f, lhs)?;
|
pretty_print(f, lhs)?;
|
||||||
write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
|
write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
|
||||||
pretty_print(f, rhs)
|
pretty_print(f, rhs)
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@ impl ElementType {
|
||||||
resolved_name,
|
resolved_name,
|
||||||
property_type: Type::Invalid,
|
property_type: Type::Invalid,
|
||||||
property_visibility: PropertyVisibility::Private,
|
property_visibility: PropertyVisibility::Private,
|
||||||
|
declared_pure: None,
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -413,6 +414,7 @@ impl ElementType {
|
||||||
resolved_name,
|
resolved_name,
|
||||||
property_type: p.ty.clone(),
|
property_type: p.ty.clone(),
|
||||||
property_visibility: p.property_visibility,
|
property_visibility: p.property_visibility,
|
||||||
|
declared_pure: None,
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -429,6 +431,7 @@ impl ElementType {
|
||||||
resolved_name,
|
resolved_name,
|
||||||
property_type,
|
property_type,
|
||||||
property_visibility: PropertyVisibility::InOut,
|
property_visibility: PropertyVisibility::InOut,
|
||||||
|
declared_pure: None,
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,6 +439,7 @@ impl ElementType {
|
||||||
resolved_name: Cow::Borrowed(name),
|
resolved_name: Cow::Borrowed(name),
|
||||||
property_type: Type::Invalid,
|
property_type: Type::Invalid,
|
||||||
property_visibility: PropertyVisibility::Private,
|
property_visibility: PropertyVisibility::Private,
|
||||||
|
declared_pure: None,
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -690,6 +694,7 @@ pub struct PropertyLookupResult<'a> {
|
||||||
pub resolved_name: std::borrow::Cow<'a, str>,
|
pub resolved_name: std::borrow::Cow<'a, str>,
|
||||||
pub property_type: Type,
|
pub property_type: Type,
|
||||||
pub property_visibility: PropertyVisibility,
|
pub property_visibility: PropertyVisibility,
|
||||||
|
pub declared_pure: Option<bool>,
|
||||||
/// True if the property is part of the the current component (for visibility purposes)
|
/// True if the property is part of the the current component (for visibility purposes)
|
||||||
pub is_local_to_component: bool,
|
pub is_local_to_component: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ pub fn lower_expression(
|
||||||
llr_Expression::NumberLiteral(unit.normalize(*n))
|
llr_Expression::NumberLiteral(unit.normalize(*n))
|
||||||
}
|
}
|
||||||
tree_Expression::BoolLiteral(b) => llr_Expression::BoolLiteral(*b),
|
tree_Expression::BoolLiteral(b) => llr_Expression::BoolLiteral(*b),
|
||||||
tree_Expression::CallbackReference(nr)
|
tree_Expression::CallbackReference(nr, _)
|
||||||
| tree_Expression::PropertyReference(nr)
|
| tree_Expression::PropertyReference(nr)
|
||||||
| tree_Expression::FunctionReference(nr) => {
|
| tree_Expression::FunctionReference(nr, _) => {
|
||||||
llr_Expression::PropertyReference(ctx.map_property_reference(nr))
|
llr_Expression::PropertyReference(ctx.map_property_reference(nr))
|
||||||
}
|
}
|
||||||
tree_Expression::BuiltinFunctionReference(_, _) => panic!(),
|
tree_Expression::BuiltinFunctionReference(_, _) => panic!(),
|
||||||
|
|
@ -120,17 +120,19 @@ pub fn lower_expression(
|
||||||
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
|
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
|
||||||
llr_Expression::BuiltinFunctionCall { function: *f, arguments }
|
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::<_>();
|
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
|
||||||
llr_Expression::CallBackCall { callback: ctx.map_property_reference(nr), arguments }
|
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::<_>();
|
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
|
||||||
llr_Expression::FunctionCall { function: ctx.map_property_reference(nr), arguments }
|
llr_Expression::FunctionCall { function: ctx.map_property_reference(nr), arguments }
|
||||||
}
|
}
|
||||||
_ => panic!("not calling a function"),
|
_ => 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 {
|
tree_Expression::BinaryExpression { lhs, rhs, op } => llr_Expression::BinaryExpression {
|
||||||
lhs: Box::new(lower_expression(lhs, ctx)),
|
lhs: Box::new(lower_expression(lhs, ctx)),
|
||||||
rhs: Box::new(lower_expression(rhs, ctx)),
|
rhs: Box::new(lower_expression(rhs, ctx)),
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,7 @@ impl LookupObject for InScopeLookup {
|
||||||
let e = expression_from_reference(
|
let e = expression_from_reference(
|
||||||
NamedReference::new(elem, name),
|
NamedReference::new(elem, name),
|
||||||
&prop.property_type,
|
&prop.property_type,
|
||||||
|
&ctx.current_token,
|
||||||
);
|
);
|
||||||
if let Some(r) = f.borrow_mut()(name, e.into()) {
|
if let Some(r) = f.borrow_mut()(name, e.into()) {
|
||||||
return Some(r);
|
return Some(r);
|
||||||
|
|
@ -364,8 +365,12 @@ impl LookupObject for InScopeLookup {
|
||||||
|elem| elem.lookup(ctx, name),
|
|elem| elem.lookup(ctx, name),
|
||||||
|elem| {
|
|elem| {
|
||||||
elem.borrow().property_declarations.get(name).map(|prop| {
|
elem.borrow().property_declarations.get(name).map(|prop| {
|
||||||
expression_from_reference(NamedReference::new(elem, name), &prop.property_type)
|
expression_from_reference(
|
||||||
.into()
|
NamedReference::new(elem, name),
|
||||||
|
&prop.property_type,
|
||||||
|
&ctx.current_token,
|
||||||
|
)
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -375,24 +380,33 @@ impl LookupObject for InScopeLookup {
|
||||||
impl LookupObject for ElementRc {
|
impl LookupObject for ElementRc {
|
||||||
fn for_each_entry<R>(
|
fn for_each_entry<R>(
|
||||||
&self,
|
&self,
|
||||||
_ctx: &LookupCtx,
|
ctx: &LookupCtx,
|
||||||
f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
|
f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
|
||||||
) -> Option<R> {
|
) -> Option<R> {
|
||||||
for (name, prop) in &self.borrow().property_declarations {
|
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()) {
|
if let Some(r) = f(name, e.into()) {
|
||||||
return Some(r);
|
return Some(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let list = self.borrow().base_type.property_list();
|
let list = self.borrow().base_type.property_list();
|
||||||
for (name, ty) in 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()) {
|
if let Some(r) = f(&name, e.into()) {
|
||||||
return Some(r);
|
return Some(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (name, ty) in crate::typeregister::reserved_properties() {
|
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()) {
|
if let Some(r) = f(name, e.into()) {
|
||||||
return Some(r);
|
return Some(r);
|
||||||
}
|
}
|
||||||
|
|
@ -400,7 +414,7 @@ impl LookupObject for ElementRc {
|
||||||
None
|
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);
|
let lookup_result = self.borrow().lookup_property(name);
|
||||||
if lookup_result.property_type != Type::Invalid
|
if lookup_result.property_type != Type::Invalid
|
||||||
&& (lookup_result.is_local_to_component
|
&& (lookup_result.is_local_to_component
|
||||||
|
|
@ -410,6 +424,7 @@ impl LookupObject for ElementRc {
|
||||||
expression: expression_from_reference(
|
expression: expression_from_reference(
|
||||||
NamedReference::new(self, &lookup_result.resolved_name),
|
NamedReference::new(self, &lookup_result.resolved_name),
|
||||||
&lookup_result.property_type,
|
&lookup_result.property_type,
|
||||||
|
&ctx.current_token,
|
||||||
),
|
),
|
||||||
deprecated: (lookup_result.resolved_name != name)
|
deprecated: (lookup_result.resolved_name != name)
|
||||||
.then(|| lookup_result.resolved_name.to_string()),
|
.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 {
|
match ty {
|
||||||
Type::Callback { .. } => Expression::CallbackReference(n),
|
Type::Callback { .. } => Expression::CallbackReference(n, node.clone()),
|
||||||
Type::Function { .. } => Expression::FunctionReference(n),
|
Type::Function { .. } => Expression::FunctionReference(n, node.clone()),
|
||||||
_ => Expression::PropertyReference(n),
|
_ => Expression::PropertyReference(n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
/// Public API property exposed as an alias: it shouldn't be generated but instead forward to the alias.
|
||||||
pub is_alias: Option<NamedReference>,
|
pub is_alias: Option<NamedReference>,
|
||||||
pub visibility: PropertyVisibility,
|
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 {
|
impl PropertyDeclaration {
|
||||||
|
|
@ -869,6 +871,10 @@ impl Element {
|
||||||
let name =
|
let name =
|
||||||
unwrap_or_continue!(parser::identifier_text(&sig_decl.DeclaredIdentifier()); diag);
|
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() {
|
if let Some(csn) = sig_decl.TwoWayBinding() {
|
||||||
r.bindings
|
r.bindings
|
||||||
.insert(name.clone(), BindingExpression::new_uncompiled(csn.into()).into());
|
.insert(name.clone(), BindingExpression::new_uncompiled(csn.into()).into());
|
||||||
|
|
@ -878,6 +884,7 @@ impl Element {
|
||||||
property_type: Type::InferredCallback,
|
property_type: Type::InferredCallback,
|
||||||
node: Some(sig_decl.into()),
|
node: Some(sig_decl.into()),
|
||||||
visibility: PropertyVisibility::InOut,
|
visibility: PropertyVisibility::InOut,
|
||||||
|
pure,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -924,6 +931,7 @@ impl Element {
|
||||||
property_type: Type::Callback { return_type, args },
|
property_type: Type::Callback { return_type, args },
|
||||||
node: Some(sig_decl.into()),
|
node: Some(sig_decl.into()),
|
||||||
visibility: PropertyVisibility::InOut,
|
visibility: PropertyVisibility::InOut,
|
||||||
|
pure,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -979,18 +987,29 @@ impl Element {
|
||||||
assert!(diag.has_error());
|
assert!(diag.has_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
let public =
|
let mut visibility = PropertyVisibility::Private;
|
||||||
func.child_token(SyntaxKind::Identifier).map_or(false, |t| t.text() == "public");
|
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(
|
r.property_declarations.insert(
|
||||||
name,
|
name,
|
||||||
PropertyDeclaration {
|
PropertyDeclaration {
|
||||||
property_type: Type::Function { return_type, args },
|
property_type: Type::Function { return_type, args },
|
||||||
node: Some(func.into()),
|
node: Some(func.into()),
|
||||||
visibility: if public {
|
visibility,
|
||||||
PropertyVisibility::Public
|
pure,
|
||||||
} else {
|
|
||||||
PropertyVisibility::Private
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1334,6 +1353,7 @@ impl Element {
|
||||||
resolved_name: name.into(),
|
resolved_name: name.into(),
|
||||||
property_type: p.property_type.clone(),
|
property_type: p.property_type.clone(),
|
||||||
property_visibility: p.visibility,
|
property_visibility: p.visibility,
|
||||||
|
declared_pure: p.pure,
|
||||||
is_local_to_component: true,
|
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));
|
expr.visit_mut(|sub| visit_named_references_in_expression(sub, vis));
|
||||||
match expr {
|
match expr {
|
||||||
Expression::PropertyReference(r)
|
Expression::PropertyReference(r)
|
||||||
| Expression::CallbackReference(r)
|
| Expression::CallbackReference(r, _)
|
||||||
| Expression::FunctionReference(r) => vis(r),
|
| Expression::FunctionReference(r, _) => vis(r),
|
||||||
Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
|
Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
|
||||||
Expression::SolveLayout(l, _) => l.visit_named_references(vis),
|
Expression::SolveLayout(l, _) => l.visit_named_references(vis),
|
||||||
Expression::ComputeLayoutInfo(l, _) => l.visit_named_references(vis),
|
Expression::ComputeLayoutInfo(l, _) => l.visit_named_references(vis),
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,18 @@ pub fn parse_element_content(p: &mut impl Parser) {
|
||||||
SyntaxKind::Identifier if p.peek().as_str() == "for" => {
|
SyntaxKind::Identifier if p.peek().as_str() == "for" => {
|
||||||
parse_repeated_element(&mut *p);
|
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);
|
parse_callback_declaration(&mut *p);
|
||||||
}
|
}
|
||||||
SyntaxKind::Identifier if p.peek().as_str() == "function" => {
|
|
||||||
parse_function(&mut *p);
|
|
||||||
}
|
|
||||||
SyntaxKind::Identifier
|
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);
|
parse_function(&mut *p);
|
||||||
}
|
}
|
||||||
|
|
@ -288,7 +292,7 @@ fn parse_two_way_binding(p: &mut impl Parser) {
|
||||||
/// callback foobar;
|
/// callback foobar;
|
||||||
/// callback my_callback();
|
/// callback my_callback();
|
||||||
/// callback foo(int, string);
|
/// 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 end_coma(a, b, c,);
|
||||||
/// callback with_return(a, b) -> int;
|
/// callback with_return(a, b) -> int;
|
||||||
/// callback with_return2({a: string}) -> { a: string };
|
/// 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
|
/// Must consume at least one token
|
||||||
fn parse_callback_declaration(p: &mut impl Parser) {
|
fn parse_callback_declaration(p: &mut impl Parser) {
|
||||||
debug_assert_eq!(p.peek().as_str(), "callback");
|
|
||||||
let mut p = p.start_node(SyntaxKind::CallbackDeclaration);
|
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"
|
p.expect(SyntaxKind::Identifier); // "callback"
|
||||||
{
|
{
|
||||||
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
|
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) {
|
fn parse_function(p: &mut impl Parser) {
|
||||||
let mut p = p.start_node(SyntaxKind::Function);
|
let mut p = p.start_node(SyntaxKind::Function);
|
||||||
if p.peek().as_str() == "public" {
|
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");
|
debug_assert_eq!(p.peek().as_str(), "function");
|
||||||
p.expect(SyntaxKind::Identifier); // "function"
|
p.expect(SyntaxKind::Identifier); // "function"
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ mod lower_tabwidget;
|
||||||
mod materialize_fake_properties;
|
mod materialize_fake_properties;
|
||||||
mod move_declarations;
|
mod move_declarations;
|
||||||
mod optimize_useless_rectangles;
|
mod optimize_useless_rectangles;
|
||||||
|
mod purity_check;
|
||||||
mod remove_aliases;
|
mod remove_aliases;
|
||||||
mod remove_unused_properties;
|
mod remove_unused_properties;
|
||||||
mod repeater_component;
|
mod repeater_component;
|
||||||
|
|
@ -74,11 +75,7 @@ pub async fn run_passes(
|
||||||
|
|
||||||
let global_type_registry = type_loader.global_type_registry.clone();
|
let global_type_registry = type_loader.global_type_registry.clone();
|
||||||
let root_component = &doc.root_component;
|
let root_component = &doc.root_component;
|
||||||
infer_aliases_types::resolve_aliases(doc, diag);
|
run_import_passes(doc, type_loader, 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);
|
|
||||||
check_public_api::check_public_api(doc, diag);
|
check_public_api::check_public_api(doc, diag);
|
||||||
|
|
||||||
collect_subcomponents::collect_subcomponents(root_component);
|
collect_subcomponents::collect_subcomponents(root_component);
|
||||||
|
|
@ -268,6 +265,7 @@ pub fn run_import_passes(
|
||||||
infer_aliases_types::resolve_aliases(doc, diag);
|
infer_aliases_types::resolve_aliases(doc, diag);
|
||||||
resolving::resolve_expressions(doc, type_loader, diag);
|
resolving::resolve_expressions(doc, type_loader, diag);
|
||||||
check_expressions::check_expressions(doc, diag);
|
check_expressions::check_expressions(doc, diag);
|
||||||
|
purity_check::purity_check(doc, diag);
|
||||||
check_rotation::check_rotation(doc, diag);
|
check_rotation::check_rotation(doc, diag);
|
||||||
unique_id::check_unique_id(doc, diag);
|
unique_id::check_unique_id(doc, diag);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -335,8 +335,8 @@ fn recurse_expression(expr: &Expression, vis: &mut impl FnMut(&PropertyPath)) {
|
||||||
expr.visit(|sub| recurse_expression(sub, vis));
|
expr.visit(|sub| recurse_expression(sub, vis));
|
||||||
match expr {
|
match expr {
|
||||||
Expression::PropertyReference(r)
|
Expression::PropertyReference(r)
|
||||||
| Expression::CallbackReference(r)
|
| Expression::CallbackReference(r, _)
|
||||||
| Expression::FunctionReference(r) => vis(&r.clone().into()),
|
| Expression::FunctionReference(r, _) => vis(&r.clone().into()),
|
||||||
Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
|
Expression::LayoutCacheAccess { layout_cache_prop, .. } => {
|
||||||
vis(&layout_cache_prop.clone().into())
|
vis(&layout_cache_prop.clone().into())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -497,6 +497,7 @@ fn lower_dialog_layout(
|
||||||
"clicked",
|
"clicked",
|
||||||
)),
|
)),
|
||||||
visibility: PropertyVisibility::InOut,
|
visibility: PropertyVisibility::InOut,
|
||||||
|
pure: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,9 +215,9 @@ fn expression_for_property(element: &ElementRc, name: &str) -> ExpressionForProp
|
||||||
// Check that the expresison is valid in the new scope
|
// Check that the expresison is valid in the new scope
|
||||||
let mut has_invalid = false;
|
let mut has_invalid = false;
|
||||||
e.expression.visit_recursive(&mut |ex| match ex {
|
e.expression.visit_recursive(&mut |ex| match ex {
|
||||||
Expression::CallbackReference(nr)
|
Expression::CallbackReference(nr, _)
|
||||||
| Expression::PropertyReference(nr)
|
| Expression::PropertyReference(nr)
|
||||||
| Expression::FunctionReference(nr) => {
|
| Expression::FunctionReference(nr, _) => {
|
||||||
let e = nr.element();
|
let e = nr.element();
|
||||||
if !Rc::ptr_eq(&e, &element)
|
if !Rc::ptr_eq(&e, &element)
|
||||||
&& Weak::ptr_eq(
|
&& Weak::ptr_eq(
|
||||||
|
|
|
||||||
94
internal/compiler/passes/purity_check.rs
Normal file
94
internal/compiler/passes/purity_check.rs
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -746,6 +746,7 @@ impl Expression {
|
||||||
lhs: Box::new(lhs),
|
lhs: Box::new(lhs),
|
||||||
rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
|
rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
|
||||||
op,
|
op,
|
||||||
|
node: Some(NodeOrToken::Node(node.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1063,7 +1064,10 @@ fn continue_lookup_within_element(
|
||||||
if let Some(x) = it.next() {
|
if let Some(x) = it.next() {
|
||||||
ctx.diag.push_error("Cannot access fields of callback".into(), &x)
|
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 { .. }) {
|
} else if matches!(lookup_result.property_type, Type::Function { .. }) {
|
||||||
if !lookup_result.is_local_to_component
|
if !lookup_result.is_local_to_component
|
||||||
&& lookup_result.property_visibility == PropertyVisibility::Private
|
&& lookup_result.property_visibility == PropertyVisibility::Private
|
||||||
|
|
@ -1083,7 +1087,10 @@ fn continue_lookup_within_element(
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Expression::FunctionReference(NamedReference::new(elem, &lookup_result.resolved_name))
|
Expression::FunctionReference(
|
||||||
|
NamedReference::new(elem, &lookup_result.resolved_name),
|
||||||
|
Some(NodeOrToken::Token(second)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut err = |extra: &str| {
|
let mut err = |extra: &str| {
|
||||||
|
|
@ -1295,7 +1302,7 @@ pub fn resolve_two_way_binding(
|
||||||
}
|
}
|
||||||
Some(n)
|
Some(n)
|
||||||
}
|
}
|
||||||
Expression::CallbackReference(n) => {
|
Expression::CallbackReference(n, _) => {
|
||||||
if ctx.property_type != Type::InferredCallback && ty != ctx.property_type {
|
if ctx.property_type != Type::InferredCallback && ty != ctx.property_type {
|
||||||
ctx.diag.push_error("Cannot bind to a callback".into(), &node);
|
ctx.diag.push_error("Cannot bind to a callback".into(), &node);
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Compo1 := Rectangle {
|
||||||
|
|
||||||
property <int> a : aa();
|
property <int> a : aa();
|
||||||
// ^error{The binding for the property 'a' is part of a binding loop}
|
// ^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 {
|
function factorial(n: int) -> int {
|
||||||
// ^error{The binding for the property 'factorial' is part of a binding loop}
|
// ^error{The binding for the property 'factorial' is part of a binding loop}
|
||||||
|
|
@ -14,7 +14,7 @@ Compo1 := Rectangle {
|
||||||
|
|
||||||
|
|
||||||
property <int> b;
|
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}
|
// ^error{The binding for the property 'bb' is part of a binding loop}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,5 @@ X := Rectangle {
|
||||||
x: edit.focus;
|
x: edit.focus;
|
||||||
// ^error{Cannot convert function\(element ref\) -> void to length}
|
// ^error{Cannot convert function\(element ref\) -> void to length}
|
||||||
// ^^error{'edit.focus' must be called. Did you forgot the '\(\)'\?}
|
// ^^error{'edit.focus' must be called. Did you forgot the '\(\)'\?}
|
||||||
|
// ^^^error{Cannot call impure function}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -141,6 +141,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
|
||||||
resolved_name: name.into(),
|
resolved_name: name.into(),
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
property_visibility: crate::object_tree::PropertyVisibility::InOut,
|
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(),
|
resolved_name: format!("{}-{}", pre, suf).into(),
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
property_visibility: crate::object_tree::PropertyVisibility::InOut,
|
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,
|
property_type: Type::Invalid,
|
||||||
is_local_to_component: false,
|
is_local_to_component: false,
|
||||||
property_visibility: crate::object_tree::PropertyVisibility::Private,
|
property_visibility: crate::object_tree::PropertyVisibility::Private,
|
||||||
|
declared_pure: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
Expression::FunctionCall { function, arguments, source_location: _ } => match &**function {
|
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<_>>();
|
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
|
||||||
generativity::make_guard!(guard);
|
generativity::make_guard!(guard);
|
||||||
match enclosing_component_instance_for_element(&nr.element(), local_context.component_instance, 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<_>>();
|
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()
|
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args).unwrap()
|
||||||
}
|
}
|
||||||
Expression::BuiltinFunctionReference(f, _) => call_builtin_function(*f, arguments, local_context),
|
Expression::BuiltinFunctionReference(f, _) => call_builtin_function(*f, arguments, local_context),
|
||||||
_ => panic!("call of something not a callback: {function:?}"),
|
_ => 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);
|
let rhs = eval_expression(&**rhs, local_context);
|
||||||
eval_assignment(lhs, *op, rhs, local_context);
|
eval_assignment(lhs, *op, rhs, local_context);
|
||||||
Value::Void
|
Value::Void
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ pub(crate) fn add_highlight_items(doc: &Document) {
|
||||||
expose_in_public_api: false,
|
expose_in_public_api: false,
|
||||||
is_alias: None,
|
is_alias: None,
|
||||||
visibility: PropertyVisibility::Input,
|
visibility: PropertyVisibility::Input,
|
||||||
|
pure: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
doc.root_component.root_element.borrow_mut().property_analysis.borrow_mut().insert(
|
doc.root_component.root_element.borrow_mut().property_analysis.borrow_mut().insert(
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
Foo := Rectangle {
|
Foo := Rectangle {
|
||||||
callback hallo_alias <=> xxx.hallo;
|
pure callback hallo_alias <=> xxx.hallo;
|
||||||
callback clicked <=> are.clicked;
|
callback clicked <=> are.clicked;
|
||||||
xxx := Rectangle {
|
xxx := Rectangle {
|
||||||
callback hallo(int) -> int;
|
pure callback hallo(int) -> int;
|
||||||
hallo(a) => { return a + 88; }
|
hallo(a) => { return a + 88; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,8 +14,8 @@ Foo := Rectangle {
|
||||||
|
|
||||||
TestCase := Rectangle {
|
TestCase := Rectangle {
|
||||||
|
|
||||||
callback foo1_alias <=> foo1.hallo_alias;
|
pure callback foo1_alias <=> foo1.hallo_alias;
|
||||||
callback foo2_alias <=> foo2.hallo_alias;
|
pure callback foo2_alias <=> foo2.hallo_alias;
|
||||||
|
|
||||||
callback foo1_clicked <=> foo1.clicked;
|
callback foo1_clicked <=> foo1.clicked;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ global G := {
|
||||||
}
|
}
|
||||||
|
|
||||||
SubCompo := Rectangle {
|
SubCompo := Rectangle {
|
||||||
public function hello() -> color { red }
|
public pure function hello() -> color { red }
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCase := Rectangle {
|
TestCase := Rectangle {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
TestCase := Rectangle {
|
TestCase := Rectangle {
|
||||||
callback test_func(int) -> int;
|
pure callback test_func(int) -> int;
|
||||||
callback test_func2(string, int) -> string;
|
pure callback test_func2(string, int) -> string;
|
||||||
test_func2(str, val) => { str + "=" + (val + some_value) }
|
test_func2(str, val) => { str + "=" + (val + some_value) }
|
||||||
|
|
||||||
property <int> test_prop: 4 + test_func(2);
|
property <int> test_prop: 4 + test_func(2);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ struct MyStruct := { x:int, y: int, }
|
||||||
global InternalGlobal := {
|
global InternalGlobal := {
|
||||||
property <int> hello: 42;
|
property <int> hello: 42;
|
||||||
property <MyStruct> my_struct;
|
property <MyStruct> my_struct;
|
||||||
callback sum(int, int)->int;
|
pure callback sum(int, int)->int;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { InternalGlobal as PublicGlobal }
|
export { InternalGlobal as PublicGlobal }
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
global Glo := {
|
global Glo := {
|
||||||
property <int> hello: 42;
|
property <int> hello: 42;
|
||||||
callback sum(int, int) -> int;
|
pure callback sum(int, int) -> int;
|
||||||
callback mul(int, int) -> int;
|
pure callback mul(int, int) -> int;
|
||||||
callback calculate_profit() -> int;
|
pure callback calculate_profit() -> int;
|
||||||
calculate_profit() => { return 1000; }
|
calculate_profit() => { return 1000; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,8 +16,8 @@ ExtraComp := Rectangle {
|
||||||
|
|
||||||
|
|
||||||
TestCase := Window {
|
TestCase := Window {
|
||||||
callback sum <=> Glo.sum;
|
pure callback sum <=> Glo.sum;
|
||||||
callback mul <=> Glo.mul;
|
pure callback mul <=> Glo.mul;
|
||||||
|
|
||||||
x := ExtraComp {}
|
x := ExtraComp {}
|
||||||
property<int> five: x.five;
|
property<int> five: x.five;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
global G {
|
global G {
|
||||||
callback is-center(length, length, length) -> bool;
|
public pure function is-center(pos: length, size: length, parent: length) -> bool {
|
||||||
is-center(pos, size, parent) => {
|
|
||||||
return abs(pos / 1px - (parent - size) / 2px) < 0.001;
|
return abs(pos / 1px - (parent - size) / 2px) < 0.001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ TestCase := Rectangle {
|
||||||
foo.b += 8 + obj_conversion2.b;
|
foo.b += 8 + obj_conversion2.b;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback return_object() -> { aa: { bb: int } };
|
function return_object() -> { aa: { bb: int } }
|
||||||
return_object => { return { aa: { bb: { cc: 42 }.cc } }; }
|
{ return { aa: { bb: { cc: 42 }.cc } }; }
|
||||||
property <bool> test: return_object().aa.bb == 42 && obj_binop_merge;
|
property <bool> test: return_object().aa.bb == 42 && obj_binop_merge;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ TestCase := Rectangle {
|
||||||
cb1(foo) => { return { b: foo.a.member+1 }; }
|
cb1(foo) => { return { b: foo.a.member+1 }; }
|
||||||
|
|
||||||
property<Foo2> xx;
|
property<Foo2> xx;
|
||||||
callback cb2(Foo2, Foo2)->Foo2;
|
pure callback cb2(Foo2, Foo2)->Foo2;
|
||||||
property<Foo2> xx2: cb2(xx, xx);
|
property<Foo2> xx2: cb2(xx, xx);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
let mut c = CompletionItem::new_simple(str.to_string(), expression.ty().to_string());
|
||||||
c.kind = match expression {
|
c.kind = match expression {
|
||||||
Expression::BoolLiteral(_) => Some(CompletionItemKind::CONSTANT),
|
Expression::BoolLiteral(_) => Some(CompletionItemKind::CONSTANT),
|
||||||
Expression::CallbackReference(_) => Some(CompletionItemKind::METHOD),
|
Expression::CallbackReference(..) => Some(CompletionItemKind::METHOD),
|
||||||
Expression::FunctionReference(_) => Some(CompletionItemKind::FUNCTION),
|
Expression::FunctionReference(..) => Some(CompletionItemKind::FUNCTION),
|
||||||
Expression::PropertyReference(_) => Some(CompletionItemKind::PROPERTY),
|
Expression::PropertyReference(_) => Some(CompletionItemKind::PROPERTY),
|
||||||
Expression::BuiltinFunctionReference(..) => Some(CompletionItemKind::FUNCTION),
|
Expression::BuiltinFunctionReference(..) => Some(CompletionItemKind::FUNCTION),
|
||||||
Expression::BuiltinMacroReference(..) => Some(CompletionItemKind::FUNCTION),
|
Expression::BuiltinMacroReference(..) => Some(CompletionItemKind::FUNCTION),
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,9 @@ pub fn goto_definition(
|
||||||
} => e.upgrade()?.borrow().node.clone()?.into(),
|
} => e.upgrade()?.borrow().node.clone()?.into(),
|
||||||
LookupResult::Expression {
|
LookupResult::Expression {
|
||||||
expression:
|
expression:
|
||||||
Expression::CallbackReference(nr)
|
Expression::CallbackReference(nr, _)
|
||||||
| Expression::PropertyReference(nr)
|
| Expression::PropertyReference(nr)
|
||||||
| Expression::FunctionReference(nr),
|
| Expression::FunctionReference(nr, _),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let mut el = nr.element();
|
let mut el = nr.element();
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,8 @@ fn fully_qualify_property_access(
|
||||||
Some(LookupResult::Expression {
|
Some(LookupResult::Expression {
|
||||||
expression:
|
expression:
|
||||||
Expression::PropertyReference(nr)
|
Expression::PropertyReference(nr)
|
||||||
| Expression::CallbackReference(nr)
|
| Expression::CallbackReference(nr, _)
|
||||||
| Expression::FunctionReference(nr),
|
| Expression::FunctionReference(nr, _),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) {
|
if let Some(new_name) = state.lookup_change.property_mappings.get(&nr) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue