mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
Compiler: make calling functions work
This commit is contained in:
parent
851a910e41
commit
4672e54f5e
17 changed files with 500 additions and 40 deletions
|
@ -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(¤t_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(","))
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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 {
|
||||
¤t_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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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"),
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -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(", "))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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) =
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
41
tests/cases/callbacks/functions.slint
Normal file
41
tests/cases/callbacks/functions.slint
Normal 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);
|
||||
```
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue