mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 10:50:00 +00:00
Add support for protected functions
Protected function can only be called from the direct base Issue #3636
This commit is contained in:
parent
351ae4b93c
commit
8f001ac490
11 changed files with 142 additions and 17 deletions
|
@ -20,6 +20,7 @@ All notable changes to this project are documented in this file.
|
|||
- Added `spacing-horizontal` and `spacing-vertical` in `GridLayout`
|
||||
- Fixed conversion in array of array of structs (#3574)
|
||||
- Added `scroll-event` callback to `TouchArea`
|
||||
- Added support for `protected` functions.
|
||||
|
||||
### Rust API
|
||||
|
||||
|
|
|
@ -1,15 +1,40 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
# Functions
|
||||
|
||||
Declare helper functions with the `function` keyword.
|
||||
Functions are private by default, but can be made public with the `public` annotation.
|
||||
Functions in Slint are declared using the `function` keyword.
|
||||
|
||||
Each function can have parameters which are declared within parentheses, following the format `name : type`.
|
||||
These parameters can be referenced by their names within the function body.
|
||||
|
||||
Functions can also return a value. The return type is specified after `->` in the function signature.
|
||||
The `return` keyword is used within the function body to return an expression of the declared type.
|
||||
If a function does not explicitly return a value, the value of the last statement is returned by default.
|
||||
|
||||
Functions can be annotated with the `pure` keyword.
|
||||
This indicates that the function does not cause any side effects.
|
||||
More details can be found in the [Purity](../concepts/purity.md) chapter.
|
||||
|
||||
By default, functions are private and cannot be accessed from external components.
|
||||
However, their accessibility can be modified using the `public` or `protected` keywords.
|
||||
|
||||
- A function annotated with `public` can be accessed by any component.
|
||||
- A function annotated with `protected` can only be accessed by components that directly inherit from it.
|
||||
|
||||
## Example
|
||||
|
||||
```slint,no-preview
|
||||
export component Example {
|
||||
in property <int> min;
|
||||
in property <int> max;
|
||||
public function inbound(x: int) -> int {
|
||||
in-out property <int> min;
|
||||
in-out property <int> max;
|
||||
protected function set-bounds(min: int, max: int) {
|
||||
root.min = min;
|
||||
root.max = max
|
||||
}
|
||||
public pure function inbound(x: int) -> int {
|
||||
return Math.min(root.max, Math.max(root.min, x));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `set-bounds` is a protected function that updates the `min` and `max` properties of the root component.
|
||||
The `inbound` function is a public, pure function that takes an integer `x` and returns the value constrained within the `min` and `max` bounds.
|
||||
|
|
|
@ -413,6 +413,7 @@ impl ElementType {
|
|||
property_visibility: PropertyVisibility::Private,
|
||||
declared_pure: None,
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
}
|
||||
} else {
|
||||
crate::typeregister::reserved_property(name)
|
||||
|
@ -424,6 +425,7 @@ impl ElementType {
|
|||
property_visibility: p.property_visibility,
|
||||
declared_pure: None,
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -441,6 +443,7 @@ impl ElementType {
|
|||
property_visibility: PropertyVisibility::InOut,
|
||||
declared_pure: None,
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
}
|
||||
}
|
||||
_ => PropertyLookupResult {
|
||||
|
@ -449,6 +452,7 @@ impl ElementType {
|
|||
property_visibility: PropertyVisibility::Private,
|
||||
declared_pure: None,
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -705,6 +709,8 @@ pub struct PropertyLookupResult<'a> {
|
|||
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,
|
||||
/// True if the property in the direct base of the component (for visibility purposes)
|
||||
pub is_in_direct_base: bool,
|
||||
}
|
||||
|
||||
impl<'a> PropertyLookupResult<'a> {
|
||||
|
|
|
@ -435,6 +435,7 @@ pub enum PropertyVisibility {
|
|||
InOut,
|
||||
/// For functions, not properties
|
||||
Public,
|
||||
Protected,
|
||||
}
|
||||
|
||||
impl Display for PropertyVisibility {
|
||||
|
@ -445,6 +446,7 @@ impl Display for PropertyVisibility {
|
|||
PropertyVisibility::Output => f.write_str("output"),
|
||||
PropertyVisibility::InOut => f.write_str("input output"),
|
||||
PropertyVisibility::Public => f.write_str("public"),
|
||||
PropertyVisibility::Protected => f.write_str("protected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1134,9 +1136,15 @@ impl Element {
|
|||
match token.as_token().unwrap().text() {
|
||||
"pure" => pure = Some(true),
|
||||
"public" => {
|
||||
debug_assert_eq!(visibility, PropertyVisibility::Private);
|
||||
visibility = PropertyVisibility::Public;
|
||||
pure = pure.or(Some(false));
|
||||
}
|
||||
"protected" => {
|
||||
debug_assert_eq!(visibility, PropertyVisibility::Private);
|
||||
visibility = PropertyVisibility::Protected;
|
||||
pure = pure.or(Some(false));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -1482,6 +1490,7 @@ impl Element {
|
|||
self.property_declarations.get(name).map_or_else(
|
||||
|| {
|
||||
let mut r = self.base_type.lookup_property(name);
|
||||
r.is_in_direct_base = r.is_local_to_component;
|
||||
r.is_local_to_component = false;
|
||||
r
|
||||
},
|
||||
|
@ -1491,6 +1500,7 @@ impl Element {
|
|||
property_visibility: p.visibility,
|
||||
declared_pure: p.pure,
|
||||
is_local_to_component: true,
|
||||
is_in_direct_base: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ pub fn parse_element(p: &mut impl Parser) -> bool {
|
|||
/// animate * { }
|
||||
/// @children
|
||||
/// double_binding <=> element.property;
|
||||
/// public pure function foo() {}
|
||||
/// ```
|
||||
pub fn parse_element_content(p: &mut impl Parser) {
|
||||
let mut had_parse_error = false;
|
||||
|
@ -63,9 +64,9 @@ pub fn parse_element_content(p: &mut impl Parser) {
|
|||
}
|
||||
SyntaxKind::Identifier
|
||||
if p.peek().as_str() == "function"
|
||||
|| (matches!(p.peek().as_str(), "public" | "pure")
|
||||
|| (matches!(p.peek().as_str(), "public" | "pure" | "protected")
|
||||
&& p.nth(1).as_str() == "function")
|
||||
|| (matches!(p.nth(1).as_str(), "public" | "pure")
|
||||
|| (matches!(p.nth(1).as_str(), "public" | "pure" | "protected")
|
||||
&& p.nth(2).as_str() == "function") =>
|
||||
{
|
||||
parse_function(&mut *p);
|
||||
|
@ -575,17 +576,18 @@ fn parse_transition_inner(p: &mut impl Parser) -> bool {
|
|||
/// function bar(xx : int) { yy = xx; }
|
||||
/// function bar(xx : int,) -> int { return 42; }
|
||||
/// public function aa(x: int, b: {a: int}, c: int) {}
|
||||
/// protected pure function fff() {}
|
||||
/// ```
|
||||
fn parse_function(p: &mut impl Parser) {
|
||||
let mut p = p.start_node(SyntaxKind::Function);
|
||||
if p.peek().as_str() == "public" {
|
||||
if matches!(p.peek().as_str(), "public" | "protected") {
|
||||
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" {
|
||||
if matches!(p.peek().as_str(), "public" | "protected") {
|
||||
p.consume()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1333,11 +1333,21 @@ fn continue_lookup_within_element(
|
|||
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
|
||||
if lookup_result.property_visibility == PropertyVisibility::Private && !local_to_component {
|
||||
let message = format!("The function '{}' is private. Annotate it with 'public' to make it accessible from other components", second.text());
|
||||
if !lookup_result.is_local_to_component {
|
||||
ctx.diag.push_error(message, &second);
|
||||
} else {
|
||||
ctx.diag.push_warning(message+". Note: this used to be allowed in previous version, but this should be considered an error", &second);
|
||||
}
|
||||
} else if lookup_result.property_visibility == PropertyVisibility::Protected
|
||||
&& !local_to_component
|
||||
&& !(lookup_result.is_in_direct_base
|
||||
&& ctx.component_scope.first().map_or(false, |x| Rc::ptr_eq(x, elem)))
|
||||
{
|
||||
ctx.diag.push_error(format!("The function '{}' is private. Annotate it with 'public' to make it accessible from other components", second.text()), &second);
|
||||
} else if let Some(x) = it.next() {
|
||||
ctx.diag.push_error(format!("The function '{}' is protected", second.text()), &second);
|
||||
}
|
||||
if let Some(x) = it.next() {
|
||||
ctx.diag.push_error("Cannot access fields of a function".into(), &x)
|
||||
}
|
||||
if let Some(f) =
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
|
||||
component Err {
|
||||
public public function double1() {}
|
||||
// ^error{Unexpected identifier}
|
||||
protected public function double2() {}
|
||||
// ^error{Unexpected identifier}
|
||||
public protected function double3() {}
|
||||
// ^error{Unexpected identifier}
|
||||
protected protected function double4() {}
|
||||
// ^error{Unexpected identifier}
|
||||
pure pure function double5() {}
|
||||
// ^error{Unexpected identifier}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
|
||||
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
global Glob {
|
||||
function g1() {}
|
||||
protected function g2() {}
|
||||
public function g3() {}
|
||||
|
||||
function c() {
|
||||
g1();g2();g3();
|
||||
}
|
||||
}
|
||||
|
||||
Comp := Rectangle {
|
||||
function f1() {}
|
||||
public function f2() {}
|
||||
protected function f3() {}
|
||||
|
||||
function c() {
|
||||
f1();f2();f3();
|
||||
}
|
||||
}
|
||||
|
||||
export Xxx := Rectangle {
|
||||
|
@ -31,8 +42,47 @@ export Xxx := Rectangle {
|
|||
comp.f1();
|
||||
// ^error{The function 'f1' is private. Annotate it with 'public' to make it accessible from other components}
|
||||
comp.f2();
|
||||
|
||||
comp.f3();
|
||||
// ^error{The function 'f3' is protected}
|
||||
}
|
||||
|
||||
callback xx <=> foo;
|
||||
// ^error{Binding to callback 'xx' must bind to another callback}
|
||||
}
|
||||
|
||||
|
||||
export component DerComp inherits Comp {
|
||||
public function f4() {
|
||||
root.f1();
|
||||
// ^error{The function 'f1' is private. Annotate it with 'public' to make it accessible from other components}
|
||||
root.f2();
|
||||
root.f3();
|
||||
|
||||
self.f1();
|
||||
// ^error{The function 'f1' is private. Annotate it with 'public' to make it accessible from other components}
|
||||
self.f2();
|
||||
self.f3();
|
||||
}
|
||||
}
|
||||
|
||||
export component DerDerComp inherits DerComp {
|
||||
public function f5() {
|
||||
root.f1();
|
||||
// ^error{The function 'f1' is private. Annotate it with 'public' to make it accessible from other components}
|
||||
root.f2();
|
||||
root.f3();
|
||||
// ^error{The function 'f3' is protected}
|
||||
self.f1();
|
||||
// ^error{The function 'f1' is private. Annotate it with 'public' to make it accessible from other components}
|
||||
self.f2();
|
||||
self.f3();
|
||||
// ^error{The function 'f3' is protected}
|
||||
|
||||
Glob.g1();
|
||||
// ^warning{The function 'g1' is private. Annotate it with 'public' to make it accessible from other components. Note: this used to be allowed in previous version, but this should be considered an error}
|
||||
Glob.g2();
|
||||
// ^error{The function 'g2' is protected}
|
||||
Glob.g3();
|
||||
}
|
||||
}
|
|
@ -36,6 +36,8 @@ export component Foo {
|
|||
c2();
|
||||
}
|
||||
|
||||
protected function f6() {}
|
||||
|
||||
c1 => { f2() }
|
||||
c2 => {
|
||||
c1();
|
||||
|
@ -54,6 +56,8 @@ export component Foo {
|
|||
// ^error{Call of impure function}
|
||||
fs.focus();
|
||||
// ^error{Call of impure function}
|
||||
f6();
|
||||
// ^error{Call of impure function 'f6'}
|
||||
42
|
||||
};
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
|
|||
property_type: t,
|
||||
resolved_name: name.into(),
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
property_visibility: crate::object_tree::PropertyVisibility::InOut,
|
||||
declared_pure: None,
|
||||
};
|
||||
|
@ -158,6 +159,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
|
|||
property_type: Type::LogicalLength,
|
||||
resolved_name: format!("{}-{}", pre, suf).into(),
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
property_visibility: crate::object_tree::PropertyVisibility::InOut,
|
||||
declared_pure: None,
|
||||
};
|
||||
|
@ -170,6 +172,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
|
|||
resolved_name: name.into(),
|
||||
property_type: Type::Invalid,
|
||||
is_local_to_component: false,
|
||||
is_in_direct_base: false,
|
||||
property_visibility: crate::object_tree::PropertyVisibility::Private,
|
||||
declared_pure: None,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue