Add support for protected functions

Protected function can only be called from the direct base

Issue #3636
This commit is contained in:
Olivier Goffart 2023-10-10 14:45:32 +02:00 committed by Olivier Goffart
parent 351ae4b93c
commit 8f001ac490
11 changed files with 142 additions and 17 deletions

View file

@ -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

View file

@ -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.

View file

@ -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> {

View file

@ -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,
},
)
}

View file

@ -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()
}
}

View file

@ -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) =

View file

@ -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}
}

View file

@ -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

View file

@ -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();
}
}

View file

@ -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
};

View file

@ -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,
}