Compiler: make calling functions work

This commit is contained in:
Olivier Goffart 2022-12-01 12:17:46 +01:00 committed by Olivier Goffart
parent 851a910e41
commit 4672e54f5e
17 changed files with 500 additions and 40 deletions

View file

@ -1472,6 +1472,10 @@ fn generate_sub_component(
expr_str
}));
target_struct
.members
.extend(generate_functions(&component.functions, &ctx).map(|x| (Access::Public, x)));
target_struct.members.push((
field_access,
Declaration::Function(Function {
@ -1806,11 +1810,42 @@ fn generate_global(file: &mut File, global: &llr::GlobalComponent, root: &llr::P
let declarations = generate_public_api_for_properties(&global.public_properties, &ctx);
global_struct.members.extend(declarations.into_iter().map(|decl| (Access::Public, decl)));
global_struct
.members
.extend(generate_functions(&global.functions, &ctx).map(|x| (Access::Public, x)));
file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
file.declarations.push(Declaration::Struct(global_struct));
}
fn generate_functions<'a>(
functions: &'a [llr::Function],
ctx: &'a EvaluationContext,
) -> impl Iterator<Item = Declaration> + 'a {
functions.iter().map(|f| {
let mut ctx2 = ctx.clone();
ctx2.argument_types = &f.args;
let body = vec![
"[[maybe_unused]] auto self = this;".into(),
format!("return {};", compile_expression_wrap_return(&f.code, &ctx2)),
];
Declaration::Function(Function {
name: ident(&format!("fn_{}", f.name)),
signature: format!(
"({}) const -> {}",
f.args
.iter()
.enumerate()
.map(|(i, ty)| format!("{} arg_{}", ty.cpp_type().unwrap(), i))
.join(", "),
f.ret_ty.cpp_type().unwrap()
),
statements: Some(body),
..Default::default()
})
})
}
fn generate_public_api_for_properties(
public_properties: &llr::PublicProperties,
ctx: &EvaluationContext,
@ -1912,6 +1947,12 @@ fn access_window_field(ctx: &EvaluationContext) -> String {
/// let access = access_member(...);
/// format!("{}.get()", access)
/// ```
/// or for a function
/// ```ignore
/// let access = access_member(...);
/// format!("{access}(...)")
/// ```
fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) -> String {
fn in_native_item(
ctx: &EvaluationContext,
@ -1949,6 +1990,18 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
unreachable!()
}
}
llr::PropertyReference::Function { sub_component_path, function_index } => {
if let Some(sub_component) = ctx.current_sub_component {
let (compo_path, sub_component) =
follow_sub_component_path(sub_component, sub_component_path);
let name = ident(&sub_component.functions[*function_index].name);
format!("self->{compo_path}fn_{name}")
} else if let Some(current_global) = ctx.current_global {
format!("this->fn_{}", ident(&current_global.functions[*function_index].name))
} else {
unreachable!()
}
}
llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => {
in_native_item(ctx, sub_component_path, *item_index, prop_name, "self")
}
@ -1968,12 +2021,21 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
let property_name = ident(&sub_component.properties[*property_index].name);
format!("{}->{}{}", path, compo_path, property_name)
}
llr::PropertyReference::Function { sub_component_path, function_index } => {
let sub_component = ctx.current_sub_component.unwrap();
let (compo_path, sub_component) =
follow_sub_component_path(sub_component, sub_component_path);
let name = ident(&sub_component.functions[*function_index].name);
format!("{path}->{compo_path}fn_{name}")
}
llr::PropertyReference::InNativeItem {
sub_component_path,
item_index,
prop_name,
} => in_native_item(ctx, sub_component_path, *item_index, prop_name, &path),
llr::PropertyReference::InParent { .. } | llr::PropertyReference::Global { .. } => {
llr::PropertyReference::InParent { .. }
| llr::PropertyReference::Global { .. }
| llr::PropertyReference::GlobalFunction { .. } => {
unreachable!()
}
}
@ -1987,6 +2049,14 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
);
format!("{}->{}->{}", root_access, global_id, property_name)
}
llr::PropertyReference::GlobalFunction { global_index, function_index } => {
let root_access = &ctx.generator_state;
let global = &ctx.public_component.globals[*global_index];
let global_id = format!("global_{}", ident(&global.name));
let name =
ident(&ctx.public_component.globals[*global_index].functions[*function_index].name);
format!("{root_access}->{global_id}->fn_{name}")
}
}
}
@ -2042,6 +2112,12 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
format!("{}.call({})", f, a.join(","))
}
Expression::FunctionCall{ function, arguments } => {
let f = access_member(function, ctx);
let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
format!("{}({})", f, a.join(","))
}
Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => {
let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
format!("slint::private_api::{}({})", ident(function), a.join(","))

View file

@ -639,6 +639,8 @@ fn generate_sub_component(
}
}
let declared_functions = generate_functions(&component.functions, &ctx);
let mut init = vec![];
let mut item_names = vec![];
let mut item_types = vec![];
@ -1017,12 +1019,41 @@ fn generate_sub_component(
_ => Default::default(),
}
}
#(#declared_functions)*
}
#(#extra_components)*
)
}
fn generate_functions(functions: &[llr::Function], ctx: &EvaluationContext) -> Vec<TokenStream> {
functions
.iter()
.map(|f| {
let mut ctx2 = ctx.clone();
ctx2.argument_types = &f.args;
let tokens_for_expression = compile_expression(&f.code, &ctx2);
let as_ = if f.ret_ty == Type::Void { quote!(;) } else { quote!(as _) };
let fn_id = ident(&format!("fn_{}", f.name));
let args_ty =
f.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
let return_type = rust_primitive_type(&f.ret_ty).unwrap();
let args_name =
(0..f.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
quote! {
#[allow(dead_code, unused)]
pub fn #fn_id(self: ::core::pin::Pin<&Self>, #(#args_name : #args_ty,)*) -> #return_type {
let _self = self;
let args = (#(#args_name,)*);
(#tokens_for_expression) #as_
}
}
})
.collect()
}
fn generate_global(global: &llr::GlobalComponent, root: &llr::PublicComponent) -> TokenStream {
let mut declared_property_vars = vec![];
let mut declared_property_types = vec![];
@ -1062,6 +1093,8 @@ fn generate_global(global: &llr::GlobalComponent, root: &llr::PublicComponent) -
quote!(_self.root.get().unwrap().upgrade().unwrap()),
);
let declared_functions = generate_functions(&global.functions, &ctx);
for (property_index, expression) in global.init_values.iter().enumerate() {
if global.properties[property_index].use_count.get() == 0 {
continue;
@ -1135,6 +1168,8 @@ fn generate_global(global: &llr::GlobalComponent, root: &llr::PublicComponent) -
let _self = self_rc.as_ref();
#(#init)*
}
#(#declared_functions)*
}
#public_interface
@ -1520,6 +1555,14 @@ fn property_set_value_tokens(
/// let access = access_member(...)
/// quote!(#access.get())
/// ```
///
/// Or for functions:
///
/// ```ignore
/// let access = access_member(...)
/// quote!(#access(arg1, arg2))
/// ```
fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) -> TokenStream {
fn in_native_item(
ctx: &EvaluationContext,
@ -1545,7 +1588,6 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
quote!((#compo_path #item_field #flick + #item_ty::FIELD_OFFSETS.#property_name).apply_pin(#path))
}
}
match reference {
llr::PropertyReference::Local { sub_component_path, property_index } => {
if let Some(sub_component) = ctx.current_sub_component {
@ -1589,7 +1631,23 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
item_index,
prop_name,
} => in_native_item(ctx, sub_component_path, *item_index, prop_name, path),
llr::PropertyReference::InParent { .. } | llr::PropertyReference::Global { .. } => {
llr::PropertyReference::Function { sub_component_path, function_index } => {
let mut sub_component = ctx.current_sub_component.unwrap();
let mut compo_path = path.clone();
for i in sub_component_path {
let component_id = inner_component_id(sub_component);
let sub_component_name = ident(&sub_component.sub_components[*i].name);
compo_path = quote!( #component_id::FIELD_OFFSETS.#sub_component_name.apply_pin(#compo_path));
sub_component = &sub_component.sub_components[*i].ty;
}
let fn_id =
ident(&format!("fn_{}", sub_component.functions[*function_index].name));
quote!(#compo_path.#fn_id)
}
llr::PropertyReference::InParent { .. }
| llr::PropertyReference::Global { .. }
| llr::PropertyReference::GlobalFunction { .. } => {
unreachable!()
}
}
@ -1604,6 +1662,35 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
);
quote!(#global_name::FIELD_OFFSETS.#property_name.apply_pin(#root_access.globals.#global_id.as_ref()))
}
llr::PropertyReference::Function { sub_component_path, function_index } => {
if let Some(mut sub_component) = ctx.current_sub_component {
let mut compo_path = quote!(_self);
for i in sub_component_path {
let component_id = inner_component_id(sub_component);
let sub_component_name = ident(&sub_component.sub_components[*i].name);
compo_path = quote!( #component_id::FIELD_OFFSETS.#sub_component_name.apply_pin(#compo_path));
sub_component = &sub_component.sub_components[*i].ty;
}
let fn_id = ident(&format!("fn_{}", sub_component.functions[*function_index].name));
quote!(#compo_path.#fn_id)
} else if let Some(current_global) = ctx.current_global {
let fn_id =
ident(&format!("fn_{}", current_global.functions[*function_index].name));
quote!(_self.#fn_id)
} else {
unreachable!()
}
}
llr::PropertyReference::GlobalFunction { global_index, function_index } => {
let root_access = &ctx.generator_state;
let global = &ctx.public_component.globals[*global_index];
let global_id = format_ident!("global_{}", ident(&global.name));
let fn_id = ident(&format!(
"fn_{}",
ctx.public_component.globals[*global_index].functions[*function_index].name
));
quote!(#root_access.globals.#global_id.as_ref().#fn_id)
}
}
}
@ -1761,6 +1848,12 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
let a = arguments.iter().map(|a| compile_expression(a, ctx));
quote! { #f.call(&(#(#a as _,)*).into())}
}
Expression::FunctionCall { function, arguments } => {
let a = arguments.iter().map(|a| compile_expression(a, ctx));
let access_function = access_member(function, ctx);
quote! { #access_function( #(#a as _),*) }
}
Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => {
let f = ident(function);
let a = arguments.iter().map(|a| {

View file

@ -71,6 +71,10 @@ pub enum Expression {
callback: PropertyReference,
arguments: Vec<Expression>,
},
FunctionCall {
function: PropertyReference,
arguments: Vec<Expression>,
},
/// A BuiltinFunctionCall, but the function is not yet in the `BuiltinFunction` enum
/// TODO: merge in BuiltinFunctionCall
@ -261,6 +265,7 @@ impl Expression {
Type::Invalid
}
}
Self::FunctionCall { function, .. } => ctx.property_ty(function).clone(),
Self::ExtraBuiltinFunctionCall { return_ty, .. } => return_ty.clone(),
Self::PropertyAssignment { .. } => Type::Void,
Self::ModelDataAssignment { .. } => Type::Void,
@ -308,10 +313,9 @@ macro_rules! visit_impl {
}
Expression::Cast { from, .. } => $visitor(from),
Expression::CodeBlock(b) => b.$iter().for_each($visitor),
Expression::BuiltinFunctionCall { arguments, .. } => {
arguments.$iter().for_each($visitor)
}
Expression::CallBackCall { arguments, .. } => arguments.$iter().for_each($visitor),
Expression::BuiltinFunctionCall { arguments, .. }
| Expression::CallBackCall { arguments, .. }
| Expression::FunctionCall { arguments, .. } => arguments.$iter().for_each($visitor),
Expression::ExtraBuiltinFunctionCall { arguments, .. } => {
arguments.$iter().for_each($visitor)
}
@ -352,7 +356,11 @@ macro_rules! visit_impl {
}
}
Expression::EnumerationValue(_) => {}
Expression::ReturnStatement(_) => {}
Expression::ReturnStatement(r) => {
if let Some(r) = r {
$visitor(r);
}
}
Expression::LayoutCacheAccess { repeater_index, .. } => {
if let Some(repeater_index) = repeater_index {
$visitor(repeater_index);
@ -389,6 +397,9 @@ impl Expression {
}
pub trait TypeResolutionContext {
/// The type of the property.
///
/// For reference to function, this is the return type
fn property_ty(&self, _: &PropertyReference) -> &Type;
// The type of the specified argument when evaluating a callback
fn arg_type(&self, _index: usize) -> &Type {
@ -500,6 +511,21 @@ impl<'a, T> TypeResolutionContext for EvaluationContext<'a, T> {
PropertyReference::Global { global_index, property_index } => {
&self.public_component.globals[*global_index].properties[*property_index].ty
}
PropertyReference::Function { sub_component_path, function_index } => {
if let Some(mut sub_component) = self.current_sub_component {
for i in sub_component_path {
sub_component = &sub_component.sub_components[*i].ty;
}
&sub_component.functions[*function_index].ret_ty
} else if let Some(current_global) = self.current_global {
&current_global.functions[*function_index].ret_ty
} else {
unreachable!()
}
}
PropertyReference::GlobalFunction { global_index, function_index } => {
&self.public_component.globals[*global_index].functions[*function_index].ret_ty
}
}
}

View file

@ -55,6 +55,7 @@ pub struct BindingExpression {
pub struct GlobalComponent {
pub name: String,
pub properties: Vec<Property>,
pub functions: Vec<Function>,
/// One entry per property
pub init_values: Vec<Option<BindingExpression>>,
pub const_properties: Vec<bool>,
@ -82,6 +83,11 @@ pub enum PropertyReference {
InParent { level: NonZeroUsize, parent_reference: Box<PropertyReference> },
/// The property within a GlobalComponent
Global { global_index: usize, property_index: usize },
/// A function in a sub component.
Function { sub_component_path: Vec<usize>, function_index: usize },
/// A function in a global.
GlobalFunction { global_index: usize, function_index: usize },
}
#[derive(Debug, Default)]
@ -93,6 +99,14 @@ pub struct Property {
pub use_count: Cell<usize>,
}
#[derive(Debug)]
pub struct Function {
pub name: String,
pub ret_ty: Type,
pub args: Vec<Type>,
pub code: Expression,
}
#[derive(Debug, Clone)]
/// The property references might be either in the parent context, or in the
/// repeated's component context
@ -206,6 +220,7 @@ impl TreeNode {
pub struct SubComponent {
pub name: String,
pub properties: Vec<Property>,
pub functions: Vec<Function>,
pub items: Vec<Item>,
pub repeated: Vec<RepeatedElement>,
pub popup_windows: Vec<ItemTree>,

View file

@ -123,7 +123,17 @@ pub fn lower_expression(
}
tree_Expression::CallbackReference(nr) => {
let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
llr_Expression::CallBackCall { callback: ctx.map_property_reference(nr), arguments }
if matches!(nr.ty(), Type::Function { .. }) {
llr_Expression::FunctionCall {
function: ctx.map_property_reference(nr),
arguments,
}
} else {
llr_Expression::CallBackCall {
callback: ctx.map_property_reference(nr),
arguments,
}
}
}
_ => panic!("not calling a function"),
},

View file

@ -144,11 +144,12 @@ fn property_reference_within_sub_component(
) -> PropertyReference {
match &mut prop_ref {
PropertyReference::Local { sub_component_path, .. }
| PropertyReference::InNativeItem { sub_component_path, .. } => {
| PropertyReference::InNativeItem { sub_component_path, .. }
| PropertyReference::Function { sub_component_path, .. } => {
sub_component_path.insert(0, sub_component);
}
PropertyReference::InParent { .. } => panic!("the sub-component had no parents"),
PropertyReference::Global { .. } => (),
PropertyReference::Global { .. } | PropertyReference::GlobalFunction { .. } => (),
}
prop_ref
}
@ -179,6 +180,7 @@ fn lower_sub_component(
let mut sub_component = SubComponent {
name: component_id(component),
properties: Default::default(),
functions: Default::default(),
items: Default::default(),
repeated: Default::default(),
popup_windows: Default::default(),
@ -225,6 +227,21 @@ fn lower_sub_component(
if x.is_alias.is_some() {
continue;
}
if let Type::Function { return_type, args } = &x.property_type {
let function_index = sub_component.functions.len();
mapping.property_mapping.insert(
NamedReference::new(element, p),
PropertyReference::Function { sub_component_path: vec![], function_index },
);
sub_component.functions.push(Function {
name: p.into(),
ret_ty: (**return_type).clone(),
args: args.clone(),
// will be replaced later
code: super::Expression::CodeBlock(vec![]),
});
continue;
}
let property_index = sub_component.properties.len();
mapping.property_mapping.insert(
NamedReference::new(element, p),
@ -292,7 +309,20 @@ fn lower_sub_component(
});
let ctx = ExpressionContext { mapping: &mapping, state, parent: parent_context, component };
crate::generator::handle_property_bindings_init(component, |e, p, binding| {
let prop = ctx.map_property_reference(&NamedReference::new(e, p));
let nr = NamedReference::new(e, p);
let prop = ctx.map_property_reference(&nr);
if let Type::Function { .. } = nr.ty() {
if let PropertyReference::Function { sub_component_path, function_index } = prop {
assert!(sub_component_path.is_empty());
sub_component.functions[function_index].code =
super::lower_expression::lower_expression(&binding.expression, &ctx);
} else {
unreachable!()
}
return;
}
for tw in &binding.two_way_bindings {
sub_component.two_way_bindings.push((prop.clone(), ctx.map_property_reference(tw)))
}
@ -507,10 +537,32 @@ fn lower_global(
let mut properties = vec![];
let mut const_properties = vec![];
let mut prop_analysis = vec![];
let mut functions = vec![];
for (p, x) in &global.root_element.borrow().property_declarations {
let property_index = properties.len();
let nr = NamedReference::new(&global.root_element, p);
if let Type::Function { return_type, args } = &x.property_type {
let function_index = functions.len();
mapping.property_mapping.insert(
nr.clone(),
PropertyReference::Function { sub_component_path: vec![], function_index },
);
state.global_properties.insert(
nr.clone(),
PropertyReference::GlobalFunction { global_index, function_index },
);
functions.push(Function {
name: p.into(),
ret_ty: (**return_type).clone(),
args: args.clone(),
// will be replaced later
code: super::Expression::CodeBlock(vec![]),
});
continue;
}
mapping.property_mapping.insert(
nr.clone(),
PropertyReference::Local { sub_component_path: vec![], property_index },
@ -548,16 +600,21 @@ fn lower_global(
assert!(binding.borrow().two_way_bindings.is_empty());
assert!(binding.borrow().animation.is_none());
let expression =
super::lower_expression::lower_expression(&binding.borrow().expression, &ctx).into();
super::lower_expression::lower_expression(&binding.borrow().expression, &ctx);
let nr = NamedReference::new(&global.root_element, prop);
let property_index = match mapping.property_mapping[&nr] {
PropertyReference::Local { property_index, .. } => property_index,
PropertyReference::Function { ref sub_component_path, function_index } => {
assert!(sub_component_path.is_empty());
functions[function_index].code = expression;
continue;
}
_ => unreachable!(),
};
let is_constant = binding.borrow().analysis.as_ref().map_or(false, |a| a.is_const);
init_values[property_index] = Some(BindingExpression {
expression,
expression: expression.into(),
animation: None,
is_constant,
is_state_info: false,
@ -594,6 +651,7 @@ fn lower_global(
GlobalComponent {
name: global.root_element.borrow().id.clone(),
properties,
functions,
init_values,
const_properties,
public_properties,

View file

@ -89,7 +89,20 @@ pub fn count_property_use(root: &PublicComponent) {
visit_property(a, ctx);
visit_property(b, ctx);
}
})
// 8.functions (TODO: only visit used function)
for f in &sc.functions {
visit_expression(&f.code, &ctx);
}
});
// TODO: only visit used function
for g in root.globals.iter() {
let ctx = EvaluationContext::new_global(root, g, ());
for f in &g.functions {
f.code.visit_recursive(&mut |e| visit_expression(e, &ctx));
}
}
}
fn visit_property(pr: &PropertyReference, ctx: &EvaluationContext) {

View file

@ -35,6 +35,7 @@ fn expression_cost(exp: &Expression, ctx: &EvaluationContext) -> isize {
Expression::CodeBlock(_) => 0,
Expression::BuiltinFunctionCall { function, .. } => builtin_function_cost(*function),
Expression::CallBackCall { callback, .. } => callback_cost(callback, ctx),
Expression::FunctionCall { function, .. } => callback_cost(function, ctx),
Expression::ExtraBuiltinFunctionCall { .. } => return isize::MAX,
Expression::PropertyAssignment { .. } => return isize::MAX,
Expression::ModelDataAssignment { .. } => return isize::MAX,
@ -247,6 +248,9 @@ pub(crate) fn property_binding_and_analysis<'a>(
}
ret
}
PropertyReference::Function { .. } | PropertyReference::GlobalFunction { .. } => {
unreachable!()
}
}
}
@ -276,17 +280,23 @@ impl ContextMap {
match self {
ContextMap::Identity => p.clone(),
ContextMap::InSubElement { path, parent } => {
let map_sub_path = |sub_component_path: &[usize]| -> Vec<usize> {
path.iter().chain(sub_component_path.iter()).copied().collect()
};
let p2 = match p {
PropertyReference::Local { sub_component_path, property_index } => {
PropertyReference::Local {
sub_component_path: path
.iter()
.chain(sub_component_path.iter())
.copied()
.collect(),
sub_component_path: map_sub_path(sub_component_path),
property_index: *property_index,
}
}
PropertyReference::Function { sub_component_path, function_index } => {
PropertyReference::Function {
sub_component_path: map_sub_path(sub_component_path),
function_index: *function_index,
}
}
PropertyReference::InNativeItem {
sub_component_path,
item_index,
@ -294,11 +304,7 @@ impl ContextMap {
} => PropertyReference::InNativeItem {
item_index: *item_index,
prop_name: prop_name.clone(),
sub_component_path: path
.iter()
.chain(sub_component_path.iter())
.copied()
.collect(),
sub_component_path: map_sub_path(sub_component_path),
},
PropertyReference::InParent { level, parent_reference } => {
return PropertyReference::InParent {
@ -306,7 +312,9 @@ impl ContextMap {
parent_reference: parent_reference.clone(),
}
}
PropertyReference::Global { .. } => return p.clone(),
PropertyReference::Global { .. } | PropertyReference::GlobalFunction { .. } => {
return p.clone()
}
};
if let Some(level) = NonZeroUsize::new(*parent) {
PropertyReference::InParent { level, parent_reference: p2.into() }

View file

@ -122,6 +122,22 @@ impl<T> Display for DisplayPropertyRef<'_, T> {
let g = &ctx.public_component.globals[*global_index];
write!(f, "{}.{}", g.name, g.properties[*property_index].name)
}
PropertyReference::Function { sub_component_path, function_index } => {
if let Some(g) = ctx.current_global {
write!(f, "{}.{}", g.name, g.functions[*function_index].name)
} else {
let mut sc = ctx.current_sub_component.unwrap();
for i in sub_component_path {
write!(f, "{}.", sc.sub_components[*i].name)?;
sc = &sc.sub_components[*i].ty;
}
write!(f, "{}", sc.functions[*function_index].name)
}
}
PropertyReference::GlobalFunction { global_index, function_index } => {
let g = &ctx.public_component.globals[*global_index];
write!(f, "{}.{}", g.name, g.functions[*function_index].name)
}
}
}
}
@ -158,6 +174,14 @@ impl<'a, T> Display for DisplayExpression<'a, T> {
arguments.iter().map(e).join(", ")
)
}
Expression::FunctionCall { function, arguments } => {
write!(
f,
"{}({})",
DisplayPropertyRef(function, ctx),
arguments.iter().map(e).join(", ")
)
}
Expression::ExtraBuiltinFunctionCall { function, arguments, .. } => {
write!(f, "{}({})", function, arguments.iter().map(e).join(", "))
}

View file

@ -61,10 +61,12 @@ impl<'a> LookupCtx<'a> {
}
pub fn return_type(&self) -> &Type {
if let Type::Callback { return_type, .. } = &self.property_type {
return_type.as_ref().map_or(&Type::Void, |b| &(**b))
} else {
&self.property_type
match &self.property_type {
Type::Callback { return_type, .. } => {
return_type.as_ref().map_or(&Type::Void, |b| &(**b))
}
Type::Function { return_type, .. } => &return_type,
_ => &self.property_type,
}
}
@ -419,7 +421,7 @@ impl LookupObject for ElementRc {
}
fn expression_from_reference(n: NamedReference, ty: &Type) -> Expression {
if matches!(ty, Type::Callback { .. }) {
if matches!(ty, Type::Callback { .. } | Type::Function { .. }) {
Expression::CallbackReference(n)
} else {
Expression::PropertyReference(n)

View file

@ -978,7 +978,7 @@ impl Element {
PropertyDeclaration {
property_type: Type::Function { return_type, args },
node: Some(func.into()),
visibility: PropertyVisibility::InOut,
visibility: PropertyVisibility::Private,
..Default::default()
},
);

View file

@ -49,6 +49,7 @@ fn resolve_expression(
//FIXME: proper callback support (node is a codeblock)
Expression::from_callback_connection(node.clone().into(), &mut lookup_ctx)
}
SyntaxKind::Function => Expression::from_function(node.clone().into(), &mut lookup_ctx),
SyntaxKind::Expression => {
//FIXME again: this happen for non-binding expression (i.e: model)
Expression::from_expression_node(node.clone().into(), &mut lookup_ctx)
@ -212,6 +213,18 @@ impl Expression {
)
}
fn from_function(node: syntax_nodes::Function, ctx: &mut LookupCtx) -> Expression {
ctx.arguments = node
.ArgumentDeclaration()
.map(|x| identifier_text(&x.DeclaredIdentifier()).unwrap_or_default())
.collect();
Self::from_codeblock_node(node.CodeBlock(), ctx).maybe_convert_to(
ctx.return_type().clone(),
&node,
ctx.diag,
)
}
fn from_expression_node(node: syntax_nodes::Expression, ctx: &mut LookupCtx) -> Self {
node.Expression()
.map(|n| Self::from_expression_node(n, ctx))
@ -557,7 +570,11 @@ impl Expression {
expression: r @ Expression::CallbackReference(..), ..
} => {
if let Some(x) = it.next() {
ctx.diag.push_error("Cannot access fields of callback".into(), &x)
if matches!(r.ty(), Type::Function { .. }) {
ctx.diag.push_error("Cannot access fields of a function".into(), &x)
} else {
ctx.diag.push_error("Cannot access fields of callback".into(), &x)
}
}
r
}
@ -1044,11 +1061,19 @@ fn continue_lookup_within_element(
}
Expression::CallbackReference(NamedReference::new(elem, &lookup_result.resolved_name))
} else if matches!(lookup_result.property_type, Type::Function { .. }) {
if let Some(x) = it.next() {
ctx.diag.push_error("Cannot access fields of a function".into(), &x)
}
let member = elem.borrow().base_type.lookup_member_function(&lookup_result.resolved_name);
Expression::MemberFunction {
base: Box::new(Expression::ElementReference(Rc::downgrade(elem))),
base_node: Some(NodeOrToken::Node(node.into())),
member: Box::new(member),
if !matches!(member, Expression::Invalid) {
// builtin member function
Expression::MemberFunction {
base: Box::new(Expression::ElementReference(Rc::downgrade(elem))),
base_node: Some(NodeOrToken::Node(node.into())),
member: Box::new(member),
}
} else {
Expression::CallbackReference(NamedReference::new(elem, &lookup_result.resolved_name))
}
} else {
let mut err = |extra: &str| {

View file

@ -0,0 +1,26 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
Xxx := Rectangle {
function foo(a: int) -> string { return a; }
function bar() {
foo(45, 45);
// ^error{The callback or function expects 1 arguments, but 2 are provided}
foo.hello(45);
// ^error{Cannot access fields of a function}
root.foo();
// ^error{The callback or function expects 1 arguments, but 0 are provided}
root.foo.hello(45);
// ^error{Cannot access fields of a function}
}
}

View file

@ -1012,6 +1012,7 @@ pub(crate) fn generate_component<'id>(
i_slint_common::for_each_enums!(match_enum_type)
}
Type::LayoutCache => property_info::<SharedVector<f32>>(),
Type::Function { .. } => continue,
_ => panic!("bad type {:?}", &decl.property_type),
};
custom_properties.insert(
@ -1286,7 +1287,9 @@ pub fn instantiate(
let is_const = binding.analysis.as_ref().map_or(false, |a| a.is_const);
let property_type = elem.lookup_property(prop_name).property_type;
if let Type::Callback { .. } = property_type {
if let Type::Function { .. } = property_type {
// function don't need initialization
} else if let Type::Callback { .. } = property_type {
let expr = binding.expression.clone();
let component_type = component_type.clone();
if let Some(callback_offset) =

View file

@ -213,10 +213,23 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
Expression::FunctionCall { function, arguments, source_location: _ } => match &**function {
Expression::CallbackReference(nr) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args).unwrap()
if matches!(nr.ty(), Type::Function { .. }) {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(&nr.element(), local_context.component_instance, guard) {
ComponentInstance::InstanceRef(c) => {
let mut ctx = EvalLocalContext::from_function_arguments(c, args);
eval_expression(&nr.element().borrow().bindings.get(nr.name()).unwrap().borrow().expression, &mut ctx)
}
ComponentInstance::GlobalComponent(g) => {
g.as_ref().eval_function(nr.name(), args).unwrap()
},
}
} else {
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args).unwrap()
}
}
Expression::BuiltinFunctionReference(f, _) => call_builtin_function(*f, arguments, local_context),
_ => panic!("call of something not a callback"),
_ => panic!("call of something not a callback: {function:?}"),
}
Expression::SelfAssignment { lhs, rhs, op } => {
let rhs = eval_expression(&**rhs, local_context);

View file

@ -91,6 +91,8 @@ pub trait GlobalComponent {
fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()>;
fn get_property_ptr(self: Pin<&Self>, prop_name: &str) -> *const ();
fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec<Value>) -> Result<Value, ()>;
}
/// Instantiate the global singleton and store it in `globals`
@ -184,6 +186,27 @@ impl GlobalComponent for GlobalComponentInstance {
let comp = self.0.unerase(guard);
comp.description().set_callback_handler(comp.borrow(), callback_name, handler)
}
fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec<Value>) -> Result<Value, ()> {
generativity::make_guard!(guard);
let comp = self.0.unerase(guard);
let mut ctx =
crate::eval::EvalLocalContext::from_function_arguments(comp.borrow_instance(), args);
let result = crate::eval::eval_expression(
&comp
.description()
.original
.root_element
.borrow()
.bindings
.get(fn_name)
.ok_or(())?
.borrow()
.expression,
&mut ctx,
);
Ok(result)
}
}
impl<T: rtti::BuiltinItem + 'static> GlobalComponent for T {
@ -224,6 +247,10 @@ impl<T: rtti::BuiltinItem + 'static> GlobalComponent for T {
let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1;
cb.set_handler(self, handler)
}
fn eval_function(self: Pin<&Self>, _fn_name: &str, _args: Vec<Value>) -> Result<Value, ()> {
Err(())
}
}
pub(crate) fn generate(component: &Rc<Component>) -> CompiledGlobal {

View file

@ -0,0 +1,41 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
global G := {
property <string> hello: "hello";
function meh(w: string) -> string {
return hello + " " + w;
}
}
TestCase := Rectangle {
property <int> c: 100000;
function the_function(a: int, b: int) -> int { a + b + c }
if true : Rectangle {
background: the_function(1, 2) > 3 ? blue:red;
}
property <bool> test: the_function(4500, 12) == 104512 && G.meh("world") == "hello world";
}
/*
```rust
let instance = TestCase::new();
assert!(instance.get_test());
```
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
assert(instance.get_test());
```
```js
var instance = new slint.TestCase();
assert(instance.test);
```
*/