mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Finish return statement handling
This commit is contained in:
parent
b22bbb1c0f
commit
c2dc0cef2c
8 changed files with 158 additions and 50 deletions
|
@ -17,6 +17,8 @@ All notable changes to this project will be documented in this file.
|
||||||
- color property to the Window element
|
- color property to the Window element
|
||||||
- maximum/minimum properties to the SpinBox
|
- maximum/minimum properties to the SpinBox
|
||||||
- strings can contain escape code
|
- strings can contain escape code
|
||||||
|
- FocusScope to handle key events
|
||||||
|
- `return` statements
|
||||||
|
|
||||||
|
|
||||||
## [0.0.4] - 2020-12-04
|
## [0.0.4] - 2020-12-04
|
||||||
|
|
|
@ -209,6 +209,12 @@ vtable::Layout drop_in_place(ComponentRef component)
|
||||||
reinterpret_cast<T *>(component.instance)->~T();
|
reinterpret_cast<T *>(component.instance)->~T();
|
||||||
return vtable::Layout { sizeof(T), alignof(T) };
|
return vtable::Layout { sizeof(T), alignof(T) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T> struct ReturnWrapper {
|
||||||
|
ReturnWrapper(T val) : value(std::move(val)) {}
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
template<> struct ReturnWrapper<void> {};
|
||||||
} // namespace private_api
|
} // namespace private_api
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
|
@ -471,7 +471,8 @@ impl Expression {
|
||||||
Expression::ReadLocalVariable { ty, .. } => ty.clone(),
|
Expression::ReadLocalVariable { ty, .. } => ty.clone(),
|
||||||
Expression::EasingCurve(_) => Type::Easing,
|
Expression::EasingCurve(_) => Type::Easing,
|
||||||
Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
|
Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
|
||||||
Expression::ReturnStatement(expr) => expr.as_ref().map_or(Type::Void, |expr| expr.ty()),
|
// invalid because the expression is unreachable
|
||||||
|
Expression::ReturnStatement(_) => Type::Invalid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -688,15 +689,12 @@ impl Expression {
|
||||||
node: &impl SpannedWithSourceFile,
|
node: &impl SpannedWithSourceFile,
|
||||||
diag: &mut BuildDiagnostics,
|
diag: &mut BuildDiagnostics,
|
||||||
) -> Expression {
|
) -> Expression {
|
||||||
if let Expression::ReturnStatement(expr) = self {
|
|
||||||
return Expression::ReturnStatement(expr.map(|mut expr| {
|
|
||||||
let expr = std::mem::replace(&mut *expr, Expression::Invalid);
|
|
||||||
Box::new(expr.maybe_convert_to(target_type, node, diag))
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ty = self.ty();
|
let ty = self.ty();
|
||||||
if ty == target_type || target_type == Type::Void || target_type == Type::Invalid {
|
if ty == target_type
|
||||||
|
|| target_type == Type::Void
|
||||||
|
|| target_type == Type::Invalid
|
||||||
|
|| ty == Type::Invalid
|
||||||
|
{
|
||||||
self
|
self
|
||||||
} else if ty.can_convert(&target_type) {
|
} else if ty.can_convert(&target_type) {
|
||||||
let from = match (ty, &target_type) {
|
let from = match (ty, &target_type) {
|
||||||
|
@ -784,8 +782,6 @@ impl Expression {
|
||||||
_ => self,
|
_ => self,
|
||||||
};
|
};
|
||||||
Expression::Cast { from: Box::new(from), to: target_type }
|
Expression::Cast { from: Box::new(from), to: target_type }
|
||||||
} else if ty == Type::Invalid || target_type == Type::Invalid {
|
|
||||||
self
|
|
||||||
} else if matches!((&ty, &target_type, &self), (Type::Array(a), Type::Array(b), Expression::Array{..})
|
} else if matches!((&ty, &target_type, &self), (Type::Array(a), Type::Array(b), Expression::Array{..})
|
||||||
if a.can_convert(b) || **a == Type::Invalid)
|
if a.can_convert(b) || **a == Type::Invalid)
|
||||||
{
|
{
|
||||||
|
|
|
@ -356,7 +356,7 @@ fn handle_property_binding(
|
||||||
callback_accessor_prefix = callback_accessor_prefix,
|
callback_accessor_prefix = callback_accessor_prefix,
|
||||||
prop = prop_name,
|
prop = prop_name,
|
||||||
params = params.join(", "),
|
params = params.join(", "),
|
||||||
code = compile_expression(binding_expression, &component)
|
code = compile_expression_wrap_return(binding_expression, &component)
|
||||||
));
|
));
|
||||||
} else if let Expression::TwoWayBinding(nr, next) = &binding_expression {
|
} else if let Expression::TwoWayBinding(nr, next) = &binding_expression {
|
||||||
init.push(format!(
|
init.push(format!(
|
||||||
|
@ -377,7 +377,7 @@ fn handle_property_binding(
|
||||||
|
|
||||||
let component = &item.enclosing_component.upgrade().unwrap();
|
let component = &item.enclosing_component.upgrade().unwrap();
|
||||||
|
|
||||||
let init_expr = compile_expression(binding_expression, component);
|
let init_expr = compile_expression_wrap_return(binding_expression, component);
|
||||||
let cpp_prop = if let Some(vp) = super::as_flickable_viewport_property(elem, prop_name) {
|
let cpp_prop = if let Some(vp) = super::as_flickable_viewport_property(elem, prop_name) {
|
||||||
format!("{}viewport.{}", accessor_prefix, vp,)
|
format!("{}viewport.{}", accessor_prefix, vp,)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1454,15 +1454,14 @@ fn compile_expression(
|
||||||
Expression::CodeBlock(sub) => {
|
Expression::CodeBlock(sub) => {
|
||||||
let len = sub.len();
|
let len = sub.len();
|
||||||
let mut x = sub.iter().enumerate().map(|(i, e)| {
|
let mut x = sub.iter().enumerate().map(|(i, e)| {
|
||||||
match e {
|
if i == len - 1 {
|
||||||
Expression::ReturnStatement(return_expr) if i == len - 1 => {
|
return_compile_expression(e, component, None) + ";"
|
||||||
return_expr.as_ref().map_or_else(String::new, |return_expr| compile_expression(return_expr, component))
|
}
|
||||||
},
|
else {
|
||||||
e => compile_expression(e, component)
|
compile_expression(e, component)
|
||||||
}
|
}
|
||||||
|
|
||||||
}).collect::<Vec<_>>();
|
});
|
||||||
if let Some(s) = x.last_mut() { *s = format!("return {};", s) };
|
|
||||||
|
|
||||||
format!("[&]{{ {} }}()", x.join(";"))
|
format!("[&]{{ {} }}()", x.join(";"))
|
||||||
}
|
}
|
||||||
|
@ -1552,27 +1551,19 @@ fn compile_expression(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Condition { condition, true_expr, false_expr } => {
|
Expression::Condition { condition, true_expr, false_expr } => {
|
||||||
|
let ty = expr.ty();
|
||||||
let cond_code = compile_expression(condition, component);
|
let cond_code = compile_expression(condition, component);
|
||||||
let cond_code = remove_parentheses(&cond_code);
|
let cond_code = remove_parentheses(&cond_code);
|
||||||
let true_code = compile_expression(true_expr, component);
|
let true_code = return_compile_expression(true_expr, component, Some(&ty));
|
||||||
let false_code = compile_expression(false_expr, component);
|
let false_code = return_compile_expression(false_expr, component, Some(&ty));
|
||||||
let ty = expr.ty();
|
|
||||||
if ty == Type::Invalid || ty == Type::Void {
|
|
||||||
format!(
|
format!(
|
||||||
r#"[&]() {{ if ({}) {{ {}; }} else {{ {}; }}}}()"#,
|
r#"[&]() -> {} {{ if ({}) {{ {}; }} else {{ {}; }}}}()"#,
|
||||||
|
ty.cpp_type().unwrap_or("void".into()),
|
||||||
cond_code,
|
cond_code,
|
||||||
true_code,
|
true_code,
|
||||||
false_code
|
false_code
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
r#"[&]() -> {} {{ if ({}) {{ return {}; }} else {{ return {}; }}}}()"#,
|
|
||||||
ty.cpp_type().unwrap(),
|
|
||||||
cond_code,
|
|
||||||
true_code,
|
|
||||||
false_code
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Expression::Array { element_ty, values } => {
|
Expression::Array { element_ty, values } => {
|
||||||
let ty = element_ty.cpp_type().unwrap_or_else(|| "FIXME: report error".to_owned());
|
let ty = element_ty.cpp_type().unwrap_or_else(|| "FIXME: report error".to_owned());
|
||||||
|
@ -1616,9 +1607,13 @@ fn compile_expression(
|
||||||
Expression::EnumerationValue(value) => {
|
Expression::EnumerationValue(value) => {
|
||||||
format!("sixtyfps::{}::{}", value.enumeration.name, value.to_string())
|
format!("sixtyfps::{}::{}", value.enumeration.name, value.to_string())
|
||||||
}
|
}
|
||||||
|
Expression::ReturnStatement(Some(expr)) => format!(
|
||||||
|
"throw sixtyfps::private_api::ReturnWrapper({})",
|
||||||
|
compile_expression(expr, component)
|
||||||
|
),
|
||||||
|
Expression::ReturnStatement(None) => "throw sixtyfps::private_api::ReturnWrapper<void>()".to_owned(),
|
||||||
Expression::Uncompiled(_) | Expression::TwoWayBinding(..) => panic!(),
|
Expression::Uncompiled(_) | Expression::TwoWayBinding(..) => panic!(),
|
||||||
Expression::Invalid => "\n#error invalid expression\n".to_string(),
|
Expression::Invalid => "\n#error invalid expression\n".to_string(),
|
||||||
Expression::ReturnStatement(expr) => format!("return {};", expr.as_ref().map_or_else(String::new, |expr| compile_expression(expr, component))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2290,3 +2285,62 @@ fn compile_path_events(events: &crate::expression_tree::PathEvents) -> (Vec<Stri
|
||||||
|
|
||||||
(events, coordinates)
|
(events, coordinates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like compile_expression, but wrap inside a try{}catch{} block to intercept the return
|
||||||
|
fn compile_expression_wrap_return(expr: &Expression, component: &Rc<Component>) -> String {
|
||||||
|
/// Return a type if there is any `return` in sub expressions
|
||||||
|
fn return_type(expr: &Expression) -> Option<Type> {
|
||||||
|
if let Expression::ReturnStatement(val) = expr {
|
||||||
|
return Some(val.as_ref().map_or(Type::Void, |v| v.ty()));
|
||||||
|
}
|
||||||
|
let mut ret = None;
|
||||||
|
expr.visit(|e| {
|
||||||
|
if ret.is_none() {
|
||||||
|
ret = return_type(e)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ty) = return_type(expr) {
|
||||||
|
if ty == Type::Void || ty == Type::Invalid {
|
||||||
|
format!(
|
||||||
|
"[&]{{ try {{ {}; }} catch(const sixtyfps::private_api::ReturnWrapper<void> &w) {{ }} }}()",
|
||||||
|
compile_expression(expr, component)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let cpp_ty = ty.cpp_type().unwrap_or_default();
|
||||||
|
format!(
|
||||||
|
"[&]() -> {} {{ try {{ {}; }} catch(const sixtyfps::private_api::ReturnWrapper<{}> &w) {{ return w.value; }} }}()",
|
||||||
|
cpp_ty,
|
||||||
|
return_compile_expression(expr, component, Some(&ty)),
|
||||||
|
cpp_ty
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
compile_expression(expr, component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like compile expression, but prepended with `return` if not void.
|
||||||
|
/// ret_type is the expecting type that should be returned with that return statement
|
||||||
|
fn return_compile_expression(
|
||||||
|
expr: &Expression,
|
||||||
|
component: &Rc<Component>,
|
||||||
|
ret_type: Option<&Type>,
|
||||||
|
) -> String {
|
||||||
|
let e = compile_expression(expr, component);
|
||||||
|
if ret_type == Some(&Type::Void) || ret_type == Some(&Type::Invalid) {
|
||||||
|
e
|
||||||
|
} else {
|
||||||
|
let ty = expr.ty();
|
||||||
|
if ty == Type::Invalid && ret_type.is_some() {
|
||||||
|
// e is unreachable so it probably throws. But we still need to return something to avoid a warning
|
||||||
|
format!("{}; return {{}}", e)
|
||||||
|
} else if ty == Type::Invalid || ty == Type::Void {
|
||||||
|
e
|
||||||
|
} else {
|
||||||
|
format!("return {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -175,7 +175,8 @@ fn handle_property_binding(
|
||||||
} else {
|
} else {
|
||||||
let tokens_for_expression = compile_expression(binding_expression, &component);
|
let tokens_for_expression = compile_expression(binding_expression, &component);
|
||||||
init.push(if binding_expression.is_constant() {
|
init.push(if binding_expression.is_constant() {
|
||||||
quote! { #rust_property.set((#tokens_for_expression) as _); }
|
let t = rust_type(&prop_type, &Default::default()).unwrap_or(quote!(_));
|
||||||
|
quote! { #rust_property.set((||-> #t { (#tokens_for_expression) as #t })()); }
|
||||||
} else {
|
} else {
|
||||||
let binding_tokens = quote!({
|
let binding_tokens = quote!({
|
||||||
let self_weak = sixtyfps::re_exports::VRc::downgrade(&self_pinned);
|
let self_weak = sixtyfps::re_exports::VRc::downgrade(&self_pinned);
|
||||||
|
|
|
@ -275,6 +275,8 @@ pub struct EvalLocalContext<'a, 'id> {
|
||||||
local_variables: HashMap<String, Value>,
|
local_variables: HashMap<String, Value>,
|
||||||
function_arguments: Vec<Value>,
|
function_arguments: Vec<Value>,
|
||||||
component_instance: ComponentInstance<'a, 'id>,
|
component_instance: ComponentInstance<'a, 'id>,
|
||||||
|
/// When Some, a return statement was executed and one must stop evaluating
|
||||||
|
return_value: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'id> EvalLocalContext<'a, 'id> {
|
impl<'a, 'id> EvalLocalContext<'a, 'id> {
|
||||||
|
@ -283,6 +285,7 @@ impl<'a, 'id> EvalLocalContext<'a, 'id> {
|
||||||
local_variables: Default::default(),
|
local_variables: Default::default(),
|
||||||
function_arguments: Default::default(),
|
function_arguments: Default::default(),
|
||||||
component_instance: ComponentInstance::InstanceRef(component),
|
component_instance: ComponentInstance::InstanceRef(component),
|
||||||
|
return_value: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,6 +298,7 @@ impl<'a, 'id> EvalLocalContext<'a, 'id> {
|
||||||
component_instance: ComponentInstance::InstanceRef(component),
|
component_instance: ComponentInstance::InstanceRef(component),
|
||||||
function_arguments,
|
function_arguments,
|
||||||
local_variables: Default::default(),
|
local_variables: Default::default(),
|
||||||
|
return_value: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,12 +307,16 @@ impl<'a, 'id> EvalLocalContext<'a, 'id> {
|
||||||
local_variables: Default::default(),
|
local_variables: Default::default(),
|
||||||
function_arguments: Default::default(),
|
function_arguments: Default::default(),
|
||||||
component_instance: ComponentInstance::GlobalComponent(&global),
|
component_instance: ComponentInstance::GlobalComponent(&global),
|
||||||
|
return_value: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an expression and return a Value as the result of this expression
|
/// Evaluate an expression and return a Value as the result of this expression
|
||||||
pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) -> Value {
|
pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) -> Value {
|
||||||
|
if let Some(r) = &local_context.return_value {
|
||||||
|
return r.clone();
|
||||||
|
}
|
||||||
match e {
|
match e {
|
||||||
Expression::Invalid => panic!("invalid expression while evaluating"),
|
Expression::Invalid => panic!("invalid expression while evaluating"),
|
||||||
Expression::Uncompiled(_) => panic!("uncompiled expression while evaluating"),
|
Expression::Uncompiled(_) => panic!("uncompiled expression while evaluating"),
|
||||||
|
@ -360,10 +368,10 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
|
||||||
Expression::CodeBlock(sub) => {
|
Expression::CodeBlock(sub) => {
|
||||||
let mut v = Value::Void;
|
let mut v = Value::Void;
|
||||||
for e in sub {
|
for e in sub {
|
||||||
if let Expression::ReturnStatement(expr) = e {
|
|
||||||
return expr.as_ref().map_or(Value::Void, |expr| eval_expression(expr, local_context))
|
|
||||||
}
|
|
||||||
v = eval_expression(e, local_context);
|
v = eval_expression(e, local_context);
|
||||||
|
if let Some(r) = &local_context.return_value {
|
||||||
|
return r.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
@ -577,7 +585,7 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
|
||||||
{
|
{
|
||||||
Ok(true) => eval_expression(&**true_expr, local_context),
|
Ok(true) => eval_expression(&**true_expr, local_context),
|
||||||
Ok(false) => eval_expression(&**false_expr, local_context),
|
Ok(false) => eval_expression(&**false_expr, local_context),
|
||||||
_ => panic!("conditional expression did not evaluate to boolean"),
|
_ => local_context.return_value.clone().expect("conditional expression did not evaluate to boolean"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Array { values, .. } => Value::Array(
|
Expression::Array { values, .. } => Value::Array(
|
||||||
|
@ -609,7 +617,13 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
|
||||||
Expression::EnumerationValue(value) => {
|
Expression::EnumerationValue(value) => {
|
||||||
Value::EnumerationValue(value.enumeration.name.clone(), value.to_string())
|
Value::EnumerationValue(value.enumeration.name.clone(), value.to_string())
|
||||||
}
|
}
|
||||||
Expression::ReturnStatement(_) => panic!("internal error: return statement must only appear inside code block and handled there")
|
Expression::ReturnStatement(x) => {
|
||||||
|
let val = x.as_ref().map_or(Value::Void, |x| eval_expression(&x, local_context));
|
||||||
|
if local_context.return_value.is_none() {
|
||||||
|
local_context.return_value = Some(val);
|
||||||
|
}
|
||||||
|
local_context.return_value.clone().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,20 @@
|
||||||
This file is also available under commercial licensing terms.
|
This file is also available under commercial licensing terms.
|
||||||
Please contact info@sixtyfps.io for more information.
|
Please contact info@sixtyfps.io for more information.
|
||||||
LICENSE END */
|
LICENSE END */
|
||||||
TestCase := Rectangle {
|
TestCase := Rectangle {
|
||||||
property <bool> toggle;
|
property <bool> toggle: { return false; }
|
||||||
property <int> value: {
|
property <int> value: {
|
||||||
|
debug("XXX \{ toggle ? "true" : "false" }");
|
||||||
if (toggle) {
|
if (toggle) {
|
||||||
|
debug("HI");
|
||||||
return 42;
|
return 42;
|
||||||
|
debug("HO");
|
||||||
}
|
}
|
||||||
|
debug("NOP");
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
signal test_signal;
|
callback test_signal;
|
||||||
property <bool> block_signal;
|
property <bool> block_signal;
|
||||||
property <bool> signal_handled;
|
property <bool> signal_handled;
|
||||||
test_signal => {
|
test_signal => {
|
||||||
|
@ -43,4 +47,35 @@ instance.emit_test_signal();
|
||||||
assert(!instance.get_signal_handled());
|
assert(!instance.get_signal_handled());
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new();
|
||||||
|
assert_eq!(instance.get_value(), 100);
|
||||||
|
instance.set_toggle(true);
|
||||||
|
assert_eq!(instance.get_value(), 42);
|
||||||
|
|
||||||
|
instance.emit_test_signal();
|
||||||
|
assert!(instance.get_signal_handled());
|
||||||
|
|
||||||
|
instance.set_signal_handled(false);
|
||||||
|
instance.set_block_signal(true);
|
||||||
|
instance.emit_test_signal();
|
||||||
|
assert!(!instance.get_signal_handled());
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new sixtyfps.TestCase({});
|
||||||
|
assert.equal(instance.value, 100);
|
||||||
|
instance.toggle = (true);
|
||||||
|
assert.equal(instance.value, 42);
|
||||||
|
|
||||||
|
instance.test_signal();
|
||||||
|
assert(instance.signal_handled);
|
||||||
|
|
||||||
|
instance.signal_handled = (false);
|
||||||
|
instance.block_signal = (true);
|
||||||
|
instance.test_signal();
|
||||||
|
assert(!instance._signal_handled);
|
||||||
|
```
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -129,7 +129,7 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
||||||
if let Some(exit_code) = output.status.code() {
|
if let Some(exit_code) = output.status.code() {
|
||||||
return Err(format!("Test case exited with non-zero code: {}", exit_code).into());
|
return Err(format!("Test case exited with non-zero code: {}", exit_code).into());
|
||||||
} else {
|
} else {
|
||||||
return Err("Test case exited by callback".into());
|
return Err("Test case exited by signal".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue