Also wrap langtype::Type::Struct in an Rc

This makes copying such types much cheaper and will allow us to
intern common struct types in the future too. This further
drops the sample cost for langtype.rs from ~6.6% down to 4.0%.

We are now also able to share/intern common struct types.

Before:
```
  Time (mean ± σ):      1.073 s ±  0.021 s    [User: 0.759 s, System: 0.215 s]
  Range (min … max):    1.034 s …  1.105 s    10 runs

        allocations:            3074261
```

After:
```
  Time (mean ± σ):      1.034 s ±  0.026 s    [User: 0.733 s, System: 0.201 s]
  Range (min … max):    1.000 s …  1.078 s    10 runs

        allocations:            2917476
```
This commit is contained in:
Milian Wolff 2024-10-23 21:39:36 +02:00 committed by Olivier Goffart
parent efdecf0a13
commit 69c68b22b2
29 changed files with 302 additions and 259 deletions

View file

@ -114,10 +114,11 @@ impl JsComponentCompiler {
pub fn structs(&self, env: Env) -> HashMap<String, JsUnknown> { pub fn structs(&self, env: Env) -> HashMap<String, JsUnknown> {
fn convert_type(env: &Env, ty: &Type) -> Option<(String, JsUnknown)> { fn convert_type(env: &Env, ty: &Type) -> Option<(String, JsUnknown)> {
match ty { match ty {
Type::Struct { fields, name: Some(name), node: Some(_), .. } => { Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
let name = s.name.as_ref().unwrap();
let struct_instance = to_js_unknown( let struct_instance = to_js_unknown(
env, env,
&Value::Struct(slint_interpreter::Struct::from_iter(fields.iter().map( &Value::Struct(slint_interpreter::Struct::from_iter(s.fields.iter().map(
|(name, field_type)| { |(name, field_type)| {
( (
name.to_string(), name.to_string(),

View file

@ -216,11 +216,11 @@ pub fn to_value(env: &Env, unknown: JsUnknown, typ: &Type) -> Result<Value> {
Ok(Value::Image(Image::from_rgba8(pixel_buffer))) Ok(Value::Image(Image::from_rgba8(pixel_buffer)))
} }
} }
Type::Struct { fields, name: _, node: _, rust_attributes: _ } => { Type::Struct(s) => {
let js_object = unknown.coerce_to_object()?; let js_object = unknown.coerce_to_object()?;
Ok(Value::Struct( Ok(Value::Struct(
fields s.fields
.iter() .iter()
.map(|(pro_name, pro_ty)| { .map(|(pro_name, pro_ty)| {
let prop: JsUnknown = js_object let prop: JsUnknown = js_object

View file

@ -153,9 +153,9 @@ impl CompilationResult {
fn convert_type(py: Python<'_>, ty: &Type) -> Option<(String, PyObject)> { fn convert_type(py: Python<'_>, ty: &Type) -> Option<(String, PyObject)> {
match ty { match ty {
Type::Struct { fields, name: Some(name), node: Some(_), .. } => { Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
let struct_instance = PyStruct::from(slint_interpreter::Struct::from_iter( let struct_instance = PyStruct::from(slint_interpreter::Struct::from_iter(
fields.iter().map(|(name, field_type)| { s.fields.iter().map(|(name, field_type)| {
( (
name.to_string(), name.to_string(),
slint_interpreter::default_value_for_type(field_type), slint_interpreter::default_value_for_type(field_type),
@ -163,7 +163,10 @@ impl CompilationResult {
}), }),
)); ));
return Some((name.to_string(), struct_instance.into_py(py))); return Some((
s.name.as_ref().unwrap().to_string(),
struct_instance.into_py(py),
));
} }
Type::Enumeration(_en) => { Type::Enumeration(_en) => {
// TODO // TODO

View file

@ -380,13 +380,13 @@ fn to_debug_string(
true_expr: Box::new(Expression::StringLiteral("true".into())), true_expr: Box::new(Expression::StringLiteral("true".into())),
false_expr: Box::new(Expression::StringLiteral("false".into())), false_expr: Box::new(Expression::StringLiteral("false".into())),
}, },
Type::Struct { fields, .. } => { Type::Struct(s) => {
let local_object = format_smolstr!( let local_object = format_smolstr!(
"debug_struct{}", "debug_struct{}",
COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
); );
let mut string = None; let mut string = None;
for k in fields.keys() { for k in s.fields.keys() {
let field_name = if string.is_some() { let field_name = if string.is_some() {
format_smolstr!(", {}: ", k) format_smolstr!(", {}: ", k)
} else { } else {

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned}; use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::langtype::{BuiltinElement, EnumerationValue, Function, Type}; use crate::langtype::{BuiltinElement, EnumerationValue, Function, Struct, Type};
use crate::layout::Orientation; use crate::layout::Orientation;
use crate::lookup::LookupCtx; use crate::lookup::LookupCtx;
use crate::object_tree::*; use crate::object_tree::*;
@ -175,7 +175,7 @@ impl BuiltinFunction {
args: vec![Type::ElementReference], args: vec![Type::ElementReference],
}, },
BuiltinFunction::ColorRgbaStruct => Function { BuiltinFunction::ColorRgbaStruct => Function {
return_type: Type::Struct { return_type: Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("red"), Type::Int32), (SmolStr::new_static("red"), Type::Int32),
(SmolStr::new_static("green"), Type::Int32), (SmolStr::new_static("green"), Type::Int32),
@ -186,11 +186,11 @@ impl BuiltinFunction {
name: Some("Color".into()), name: Some("Color".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
args: vec![Type::Color], args: vec![Type::Color],
}, },
BuiltinFunction::ColorHsvaStruct => Function { BuiltinFunction::ColorHsvaStruct => Function {
return_type: Type::Struct { return_type: Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("hue"), Type::Float32), (SmolStr::new_static("hue"), Type::Float32),
(SmolStr::new_static("saturation"), Type::Float32), (SmolStr::new_static("saturation"), Type::Float32),
@ -201,7 +201,7 @@ impl BuiltinFunction {
name: Some("Color".into()), name: Some("Color".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
args: vec![Type::Color], args: vec![Type::Color],
}, },
BuiltinFunction::ColorBrighter => { BuiltinFunction::ColorBrighter => {
@ -221,7 +221,7 @@ impl BuiltinFunction {
Function { return_type: Type::Brush, args: vec![Type::Brush, Type::Float32] } Function { return_type: Type::Brush, args: vec![Type::Brush, Type::Float32] }
} }
BuiltinFunction::ImageSize => Function { BuiltinFunction::ImageSize => Function {
return_type: Type::Struct { return_type: Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("width"), Type::Int32), (SmolStr::new_static("width"), Type::Int32),
(SmolStr::new_static("height"), Type::Int32), (SmolStr::new_static("height"), Type::Int32),
@ -230,7 +230,7 @@ impl BuiltinFunction {
name: Some("Size".into()), name: Some("Size".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
args: vec![Type::Image], args: vec![Type::Image],
}, },
BuiltinFunction::ArrayLength => { BuiltinFunction::ArrayLength => {
@ -767,9 +767,7 @@ impl Expression {
.map_or(Type::Invalid, |e| model_inner_type(&e.model)), .map_or(Type::Invalid, |e| model_inner_type(&e.model)),
Expression::FunctionParameterReference { ty, .. } => ty.clone(), Expression::FunctionParameterReference { ty, .. } => ty.clone(),
Expression::StructFieldAccess { base, name } => match base.ty() { Expression::StructFieldAccess { base, name } => match base.ty() {
Type::Struct { fields, .. } => { Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(),
fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone()
}
_ => Type::Invalid, _ => Type::Invalid,
}, },
Expression::ArrayIndex { array, .. } => match array.ty() { Expression::ArrayIndex { array, .. } => match array.ty() {
@ -1181,13 +1179,12 @@ impl Expression {
rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)), rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
op: '*', op: '*',
}, },
( (ref from_ty @ Type::Struct(ref left), Type::Struct(ref right))
ref from_ty @ Type::Struct { fields: ref left, .. }, if left.fields != right.fields =>
Type::Struct { fields: right, .. }, {
) if left != right => {
if let Expression::Struct { mut values, .. } = self { if let Expression::Struct { mut values, .. } = self {
let mut new_values = HashMap::new(); let mut new_values = HashMap::new();
for (key, ty) in right { for (key, ty) in &right.fields {
let (key, expression) = values.remove_entry(key).map_or_else( let (key, expression) = values.remove_entry(key).map_or_else(
|| (key.clone(), Expression::default_value_for_type(ty)), || (key.clone(), Expression::default_value_for_type(ty)),
|(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)), |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)),
@ -1198,8 +1195,8 @@ impl Expression {
} }
let var_name = "tmpobj"; let var_name = "tmpobj";
let mut new_values = HashMap::new(); let mut new_values = HashMap::new();
for (key, ty) in right { for (key, ty) in &right.fields {
let expression = if left.contains_key(key) { let expression = if left.fields.contains_key(key) {
Expression::StructFieldAccess { Expression::StructFieldAccess {
base: Box::new(Expression::ReadLocalVariable { base: Box::new(Expression::ReadLocalVariable {
name: var_name.into(), name: var_name.into(),
@ -1290,11 +1287,11 @@ impl Expression {
}, },
_ => unreachable!(), _ => unreachable!(),
} }
} else if let (Type::Struct { fields, .. }, Expression::Struct { values, .. }) = } else if let (Type::Struct(struct_type), Expression::Struct { values, .. }) =
(&target_type, &self) (&target_type, &self)
{ {
// Also special case struct literal in case they contain array literal // Also special case struct literal in case they contain array literal
let mut fields = fields.clone(); let mut fields = struct_type.fields.clone();
let mut new_values = HashMap::new(); let mut new_values = HashMap::new();
for (f, v) in values { for (f, v) in values {
if let Some(t) = fields.remove(f) { if let Some(t) = fields.remove(f) {
@ -1371,9 +1368,10 @@ impl Expression {
Type::Array(element_ty) => { Type::Array(element_ty) => {
Expression::Array { element_ty: (**element_ty).clone(), values: vec![] } Expression::Array { element_ty: (**element_ty).clone(), values: vec![] }
} }
Type::Struct { fields, .. } => Expression::Struct { Type::Struct(s) => Expression::Struct {
ty: ty.clone(), ty: ty.clone(),
values: fields values: s
.fields
.iter() .iter()
.map(|(k, v)| (k.clone(), Expression::default_value_for_type(v))) .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v)))
.collect(), .collect(),

View file

@ -464,7 +464,7 @@ pub fn for_each_const_properties(component: &Rc<Component>, mut f: impl FnMut(&E
.iter() .iter()
.filter(|(_, x)| { .filter(|(_, x)| {
x.property_type.is_property_type() && x.property_type.is_property_type() &&
!matches!( &x.property_type, crate::langtype::Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo")) !matches!( &x.property_type, crate::langtype::Type::Struct(s) if s.name.as_ref().map_or(false, |name| name.ends_with("::StateInfo")))
}) })
.map(|(k, _)| k.clone()), .map(|(k, _)| k.clone()),
); );

View file

@ -511,20 +511,20 @@ impl CppType for Type {
Type::Rem => Some("float".into()), Type::Rem => Some("float".into()),
Type::Percent => Some("float".into()), Type::Percent => Some("float".into()),
Type::Bool => Some("bool".into()), Type::Bool => Some("bool".into()),
Type::Struct { name: Some(name), node: Some(_), .. } => Some(ident(name)), Type::Struct(s) => match (&s.name, &s.node) {
Type::Struct { name: Some(name), node: None, .. } => { (Some(name), Some(_)) => Some(ident(name)),
Some(if name.starts_with("slint::") { (Some(name), None) => Some(if name.starts_with("slint::") {
name.clone() name.clone()
} else { } else {
format_smolstr!("slint::cbindgen_private::{}", ident(name)) format_smolstr!("slint::cbindgen_private::{}", ident(name))
}) }),
} _ => {
Type::Struct { fields, .. } => { let elem =
let elem = fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?; s.fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;
Some(format_smolstr!("std::tuple<{}>", elem.join(", "))) Some(format_smolstr!("std::tuple<{}>", elem.join(", ")))
} }
},
Type::Array(i) => { Type::Array(i) => {
Some(format_smolstr!("std::shared_ptr<slint::Model<{}>>", i.cpp_type()?)) Some(format_smolstr!("std::shared_ptr<slint::Model<{}>>", i.cpp_type()?))
} }
@ -693,8 +693,13 @@ pub fn generate(
for ty in doc.used_types.borrow().structs_and_enums.iter() { for ty in doc.used_types.borrow().structs_and_enums.iter() {
match ty { match ty {
Type::Struct { fields, name: Some(name), node: Some(node), .. } => { Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
generate_struct(&mut file, name, fields, node); generate_struct(
&mut file,
s.name.as_ref().unwrap(),
&s.fields,
s.node.as_ref().unwrap(),
);
} }
Type::Enumeration(en) => { Type::Enumeration(en) => {
generate_enum(&mut file, en); generate_enum(&mut file, en);
@ -2997,16 +3002,17 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
} }
Expression::ReadLocalVariable { name, .. } => ident(name).to_string(), Expression::ReadLocalVariable { name, .. } => ident(name).to_string(),
Expression::StructFieldAccess { base, name } => match base.ty(ctx) { Expression::StructFieldAccess { base, name } => match base.ty(ctx) {
Type::Struct { fields, name : None, .. } => { Type::Struct(s)=> {
let index = fields if s.name.is_none() {
let index = s.fields
.keys() .keys()
.position(|k| k == name) .position(|k| k == name)
.expect("Expression::ObjectAccess: Cannot find a key in an object"); .expect("Expression::ObjectAccess: Cannot find a key in an object");
format!("std::get<{}>({})", index, compile_expression(base, ctx)) format!("std::get<{}>({})", index, compile_expression(base, ctx))
} } else {
Type::Struct{..} => {
format!("{}.{}", compile_expression(base, ctx), ident(name)) format!("{}.{}", compile_expression(base, ctx), ident(name))
} }
}
_ => panic!("Expression::ObjectAccess's base expression is not an Object type"), _ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
}, },
Expression::ArrayIndex { array, index } => { Expression::ArrayIndex { array, index } => {
@ -3037,11 +3043,11 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
(Type::Brush, Type::Color) => { (Type::Brush, Type::Color) => {
format!("{}.color()", f) format!("{}.color()", f)
} }
(Type::Struct { .. }, Type::Struct{ fields, name: Some(_), ..}) => { (Type::Struct (_), Type::Struct(s)) if s.name.is_some() => {
format!( format!(
"[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})", "[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})",
struct_name = to.cpp_type().unwrap(), struct_name = to.cpp_type().unwrap(),
fields = fields.keys().enumerate().map(|(i, n)| format!("s.{} = std::get<{}>(o); ", ident(n), i)).join(""), fields = s.fields.keys().enumerate().map(|(i, n)| format!("s.{} = std::get<{}>(o); ", ident(n), i)).join(""),
obj = f, obj = f,
) )
} }
@ -3056,7 +3062,7 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
.iter() .iter()
.map(|path_elem_expr| { .map(|path_elem_expr| {
let (field_count, qualified_elem_type_name) = match path_elem_expr.ty(ctx) { let (field_count, qualified_elem_type_name) = match path_elem_expr.ty(ctx) {
Type::Struct{ fields, name: Some(name), .. } => (fields.len(), name), Type::Struct(s) if s.name.is_some() => (s.fields.len(), s.name.as_ref().unwrap().clone()),
_ => unreachable!() _ => unreachable!()
}; };
// Turn slint::private_api::PathLineTo into `LineTo` // Turn slint::private_api::PathLineTo into `LineTo`
@ -3247,8 +3253,9 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
} }
} }
Expression::Struct { ty, values } => { Expression::Struct { ty, values } => {
if let Type::Struct{fields, name: None, ..} = ty { match ty {
let mut elem = fields.iter().map(|(k, t)| { Type::Struct(s) if s.name.is_none() => {
let mut elem = s.fields.iter().map(|(k, t)| {
values values
.get(k) .get(k)
.map(|e| compile_expression(e, ctx)) .map(|e| compile_expression(e, ctx))
@ -3259,7 +3266,8 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
.unwrap_or_else(|| "(Error: missing member in object)".to_owned()) .unwrap_or_else(|| "(Error: missing member in object)".to_owned())
}); });
format!("std::make_tuple({})", elem.join(", ")) format!("std::make_tuple({})", elem.join(", "))
} else if let Type::Struct{ name: Some(_), .. } = ty { },
Type::Struct(_) => {
format!( format!(
"[&]({args}){{ {ty} o{{}}; {fields}return o; }}({vals})", "[&]({args}){{ {ty} o{{}}; {fields}return o; }}({vals})",
args = (0..values.len()).map(|i| format!("const auto &a_{}", i)).join(", "), args = (0..values.len()).map(|i| format!("const auto &a_{}", i)).join(", "),
@ -3267,10 +3275,12 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
fields = values.keys().enumerate().map(|(i, f)| format!("o.{} = a_{}; ", ident(f), i)).join(""), fields = values.keys().enumerate().map(|(i, f)| format!("o.{} = a_{}; ", ident(f), i)).join(""),
vals = values.values().map(|e| compile_expression(e, ctx)).join(", "), vals = values.values().map(|e| compile_expression(e, ctx)).join(", "),
) )
} else { },
_ => {
panic!("Expression::Object is not a Type::Object") panic!("Expression::Object is not a Type::Object")
} }
} }
}
Expression::EasingCurve(EasingCurve::Linear) => "slint::cbindgen_private::EasingCurve()".into(), Expression::EasingCurve(EasingCurve::Linear) => "slint::cbindgen_private::EasingCurve()".into(),
Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!( Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!(
"slint::cbindgen_private::EasingCurve(slint::cbindgen_private::EasingCurve::Tag::CubicBezier, {}, {}, {}, {})", "slint::cbindgen_private::EasingCurve(slint::cbindgen_private::EasingCurve::Tag::CubicBezier, {}, {}, {}, {})",
@ -3809,8 +3819,8 @@ fn generate_type_aliases(file: &mut File, doc: &Document) {
Some((&export.0.name, &component.id)) Some((&export.0.name, &component.id))
} }
Either::Right(ty) => match &ty { Either::Right(ty) => match &ty {
Type::Struct { name: Some(name), node: Some(_), .. } => { Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
Some((&export.0.name, name)) Some((&export.0.name, s.name.as_ref().unwrap()))
} }
Type::Enumeration(en) => Some((&export.0.name, &en.name)), Type::Enumeration(en) => Some((&export.0.name, &en.name)),
_ => None, _ => None,

View file

@ -13,7 +13,7 @@ Some convention used in the generated code:
*/ */
use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, OperatorClass}; use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, OperatorClass};
use crate::langtype::{Enumeration, EnumerationValue, Type}; use crate::langtype::{Enumeration, EnumerationValue, Struct, Type};
use crate::layout::Orientation; use crate::layout::Orientation;
use crate::llr::{ use crate::llr::{
self, EvaluationContext as llr_EvaluationContext, Expression, ParentCtx as llr_ParentCtx, self, EvaluationContext as llr_EvaluationContext, Expression, ParentCtx as llr_ParentCtx,
@ -92,12 +92,16 @@ fn rust_primitive_type(ty: &Type) -> Option<proc_macro2::TokenStream> {
Type::Percent => Some(quote!(f32)), Type::Percent => Some(quote!(f32)),
Type::Bool => Some(quote!(bool)), Type::Bool => Some(quote!(bool)),
Type::Image => Some(quote!(sp::Image)), Type::Image => Some(quote!(sp::Image)),
Type::Struct { fields, name: None, .. } => { Type::Struct(s) => {
let elem = fields.values().map(rust_primitive_type).collect::<Option<Vec<_>>>()?; if let Some(name) = &s.name {
Some(struct_name_to_tokens(name))
} else {
let elem =
s.fields.values().map(rust_primitive_type).collect::<Option<Vec<_>>>()?;
// This will produce a tuple // This will produce a tuple
Some(quote!((#(#elem,)*))) Some(quote!((#(#elem,)*)))
} }
Type::Struct { name: Some(name), .. } => Some(struct_name_to_tokens(name)), }
Type::Array(o) => { Type::Array(o) => {
let inner = rust_primitive_type(o)?; let inner = rust_primitive_type(o)?;
Some(quote!(sp::ModelRc<#inner>)) Some(quote!(sp::ModelRc<#inner>))
@ -154,9 +158,12 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
.structs_and_enums .structs_and_enums
.iter() .iter()
.filter_map(|ty| match ty { .filter_map(|ty| match ty {
Type::Struct { fields, name: Some(name), node: Some(_), rust_attributes } => { Type::Struct(s) => match s.as_ref() {
Struct { fields, name: Some(name), node: Some(_), rust_attributes } => {
Some((ident(name), generate_struct(name, fields, rust_attributes))) Some((ident(name), generate_struct(name, fields, rust_attributes)))
} }
_ => None,
},
Type::Enumeration(en) => Some((ident(&en.name), generate_enum(en))), Type::Enumeration(en) => Some((ident(&en.name), generate_enum(en))),
_ => None, _ => None,
}) })
@ -2146,13 +2153,13 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
(Type::Brush, Type::Color) => { (Type::Brush, Type::Color) => {
quote!(#f.color()) quote!(#f.color())
} }
(Type::Struct { ref fields, .. }, Type::Struct { name: Some(n), .. }) => { (Type::Struct (lhs), Type::Struct (rhs)) if rhs.name.is_some() => {
let fields = fields.iter().enumerate().map(|(index, (name, _))| { let fields = lhs.fields.iter().enumerate().map(|(index, (name, _))| {
let index = proc_macro2::Literal::usize_unsuffixed(index); let index = proc_macro2::Literal::usize_unsuffixed(index);
let name = ident(name); let name = ident(name);
quote!(the_struct.#name = obj.#index as _;) quote!(the_struct.#name = obj.#index as _;)
}); });
let id = struct_name_to_tokens(n); let id = struct_name_to_tokens(rhs.name.as_ref().unwrap());
quote!({ let obj = #f; let mut the_struct = #id::default(); #(#fields)* the_struct }) quote!({ let obj = #f; let mut the_struct = #id::default(); #(#fields)* the_struct })
} }
(Type::Array(..), Type::PathData) (Type::Array(..), Type::PathData)
@ -2166,7 +2173,7 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
.iter() .iter()
.map(|path_elem_expr| .map(|path_elem_expr|
// Close{} is a struct with no fields in markup, and PathElement::Close has no fields // Close{} is a struct with no fields in markup, and PathElement::Close has no fields
if matches!(path_elem_expr, Expression::Struct { ty: Type::Struct { fields, .. }, .. } if fields.is_empty()) { if matches!(path_elem_expr, Expression::Struct { ty: Type::Struct (s), .. } if s.fields.is_empty()) {
quote!(sp::PathElement::Close) quote!(sp::PathElement::Close)
} else { } else {
compile_expression(path_elem_expr, ctx) compile_expression(path_elem_expr, ctx)
@ -2245,8 +2252,8 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
quote! {args.#i.clone()} quote! {args.#i.clone()}
} }
Expression::StructFieldAccess { base, name } => match base.ty(ctx) { Expression::StructFieldAccess { base, name } => match base.ty(ctx) {
Type::Struct { fields, name: None, .. } => { Type::Struct (s) if s.name.is_none() => {
let index = fields let index = s.fields
.keys() .keys()
.position(|k| k == name) .position(|k| k == name)
.expect("Expression::StructFieldAccess: Cannot find a key in an object"); .expect("Expression::StructFieldAccess: Cannot find a key in an object");
@ -2428,18 +2435,18 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
} }
} }
Expression::Struct { ty, values } => { Expression::Struct { ty, values } => {
if let Type::Struct { fields, name, .. } = ty { if let Type::Struct (s) = ty {
let elem = fields.keys().map(|k| values.get(k).map(|e| compile_expression(e, ctx))); let elem = s.fields.keys().map(|k| values.get(k).map(|e| compile_expression(e, ctx)));
if let Some(name) = name { if let Some(name) = &s.name {
let name_tokens: TokenStream = struct_name_to_tokens(name.as_str()); let name_tokens: TokenStream = struct_name_to_tokens(name.as_str());
let keys = fields.keys().map(|k| ident(k)); let keys = s.fields.keys().map(|k| ident(k));
if name.starts_with("slint::private_api::") && name.ends_with("LayoutData") { if name.starts_with("slint::private_api::") && name.ends_with("LayoutData") {
quote!(#name_tokens{#(#keys: #elem as _,)*}) quote!(#name_tokens{#(#keys: #elem as _,)*})
} else { } else {
quote!({ let mut the_struct = #name_tokens::default(); #(the_struct.#keys = #elem as _;)* the_struct}) quote!({ let mut the_struct = #name_tokens::default(); #(the_struct.#keys = #elem as _;)* the_struct})
} }
} else { } else {
let as_ = fields.values().map(|t| { let as_ = s.fields.values().map(|t| {
if t.as_unit_product().is_some() { if t.as_unit_product().is_some() {
// number needs to be converted to the right things because intermediate // number needs to be converted to the right things because intermediate
// result might be f64 and that's usually not what the type of the tuple is in the end // result might be f64 and that's usually not what the type of the tuple is in the end
@ -3128,8 +3135,8 @@ fn generate_named_exports(doc: &Document) -> Vec<TokenStream> {
Some((&export.0.name, &component.id)) Some((&export.0.name, &component.id))
} }
Either::Right(ty) => match &ty { Either::Right(ty) => match &ty {
Type::Struct { name: Some(name), node: Some(_), .. } => { Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
Some((&export.0.name, name)) Some((&export.0.name, s.name.as_ref().unwrap()))
} }
Type::Enumeration(en) => Some((&export.0.name, &en.name)), Type::Enumeration(en) => Some((&export.0.name, &en.name)),
_ => None, _ => None,

View file

@ -52,16 +52,7 @@ pub enum Type {
Brush, Brush,
/// This is usually a model /// This is usually a model
Array(Box<Type>), Array(Box<Type>),
Struct { Struct(Rc<Struct>),
fields: BTreeMap<SmolStr, Type>,
/// When declared in .slint as `struct Foo := { }`, then the name is "Foo"
/// When there is no node, but there is a name, then it is a builtin type
name: Option<SmolStr>,
/// When declared in .slint, this is the node of the declaration.
node: Option<syntax_nodes::ObjectType>,
/// derived
rust_attributes: Option<Vec<SmolStr>>,
},
Enumeration(Rc<Enumeration>), Enumeration(Rc<Enumeration>),
/// A type made up of the product of several "unit" types. /// A type made up of the product of several "unit" types.
@ -106,8 +97,8 @@ impl core::cmp::PartialEq for Type {
Type::Easing => matches!(other, Type::Easing), Type::Easing => matches!(other, Type::Easing),
Type::Brush => matches!(other, Type::Brush), Type::Brush => matches!(other, Type::Brush),
Type::Array(a) => matches!(other, Type::Array(b) if a == b), Type::Array(a) => matches!(other, Type::Array(b) if a == b),
Type::Struct { fields, name, node: _, rust_attributes: _ } => { Type::Struct(lhs) => {
matches!(other, Type::Struct{fields:f,name:n,node:_, rust_attributes: _ } if fields == f && name == n) matches!(other, Type::Struct(rhs) if lhs.fields == rhs.fields && lhs.name == rhs.name)
} }
Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs), Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b), Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
@ -166,15 +157,17 @@ impl Display for Type {
Type::Bool => write!(f, "bool"), Type::Bool => write!(f, "bool"),
Type::Model => write!(f, "model"), Type::Model => write!(f, "model"),
Type::Array(t) => write!(f, "[{}]", t), Type::Array(t) => write!(f, "[{}]", t),
Type::Struct { name: Some(name), .. } => write!(f, "{}", name), Type::Struct(t) => {
Type::Struct { fields, name: None, .. } => { if let Some(name) = &t.name {
write!(f, "{}", name)
} else {
write!(f, "{{ ")?; write!(f, "{{ ")?;
for (k, v) in fields { for (k, v) in &t.fields {
write!(f, "{}: {},", k, v)?; write!(f, "{}: {},", k, v)?;
} }
write!(f, "}}") write!(f, "}}")
} }
}
Type::PathData => write!(f, "pathdata"), Type::PathData => write!(f, "pathdata"),
Type::Easing => write!(f, "easing"), Type::Easing => write!(f, "easing"),
Type::Brush => write!(f, "brush"), Type::Brush => write!(f, "brush"),
@ -281,9 +274,7 @@ impl Type {
| (Type::Percent, Type::Float32) | (Type::Percent, Type::Float32)
| (Type::Brush, Type::Color) | (Type::Brush, Type::Color)
| (Type::Color, Type::Brush) => true, | (Type::Color, Type::Brush) => true,
(Type::Struct { fields: a, .. }, Type::Struct { fields: b, .. }) => { (Type::Struct(a), Type::Struct(b)) => can_convert_struct(&a.fields, &b.fields),
can_convert_struct(a, b)
}
(Type::UnitProduct(u), o) => match o.as_unit_product() { (Type::UnitProduct(u), o) => match o.as_unit_product() {
Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(), Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
None => false, None => false,
@ -803,6 +794,18 @@ pub struct Function {
pub args: Vec<Type>, pub args: Vec<Type>,
} }
#[derive(Debug, Clone)]
pub struct Struct {
pub fields: BTreeMap<SmolStr, Type>,
/// When declared in .slint as `struct Foo := { }`, then the name is "Foo"
/// When there is no node, but there is a name, then it is a builtin type
pub name: Option<SmolStr>,
/// When declared in .slint, this is the node of the declaration.
pub node: Option<syntax_nodes::ObjectType>,
/// derived
pub rust_attributes: Option<Vec<SmolStr>>,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Enumeration { pub struct Enumeration {
pub name: SmolStr, pub name: SmolStr,

View file

@ -5,7 +5,7 @@
use crate::diagnostics::BuildDiagnostics; use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::*; use crate::expression_tree::*;
use crate::langtype::{ElementType, PropertyLookupResult, Type}; use crate::langtype::{ElementType, PropertyLookupResult, Struct, Type};
use crate::object_tree::{Component, ElementRc}; use crate::object_tree::{Component, ElementRc};
use smol_str::{format_smolstr, SmolStr}; use smol_str::{format_smolstr, SmolStr};
@ -487,7 +487,7 @@ impl BoxLayout {
/// The [`Type`] for a runtime LayoutInfo structure /// The [`Type`] for a runtime LayoutInfo structure
pub fn layout_info_type() -> Type { pub fn layout_info_type() -> Type {
Type::Struct { Type::Struct(Rc::new(Struct {
fields: ["min", "max", "preferred"] fields: ["min", "max", "preferred"]
.iter() .iter()
.map(|s| (SmolStr::new_static(s), Type::LogicalLength)) .map(|s| (SmolStr::new_static(s), Type::LogicalLength))
@ -500,7 +500,7 @@ pub fn layout_info_type() -> Type {
name: Some("slint::private_api::LayoutInfo".into()), name: Some("slint::private_api::LayoutInfo".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
} }))
} }
/// Get the implicit layout info of a particular element /// Get the implicit layout info of a particular element

View file

@ -228,9 +228,10 @@ impl Expression {
values: vec![], values: vec![],
as_model: true, as_model: true,
}, },
Type::Struct { fields, .. } => Expression::Struct { Type::Struct(s) => Expression::Struct {
ty: ty.clone(), ty: ty.clone(),
values: fields values: s
.fields
.iter() .iter()
.map(|(k, v)| Some((k.clone(), Expression::default_value_for_type(v)?))) .map(|(k, v)| Some((k.clone(), Expression::default_value_for_type(v)?)))
.collect::<Option<_>>()?, .collect::<Option<_>>()?,
@ -257,7 +258,7 @@ impl Expression {
Self::StoreLocalVariable { .. } => Type::Void, Self::StoreLocalVariable { .. } => Type::Void,
Self::ReadLocalVariable { ty, .. } => ty.clone(), Self::ReadLocalVariable { ty, .. } => ty.clone(),
Self::StructFieldAccess { base, name } => match base.ty(ctx) { Self::StructFieldAccess { base, name } => match base.ty(ctx) {
Type::Struct { fields, .. } => fields[name].clone(), Type::Struct(s) => s.fields[name].clone(),
_ => unreachable!(), _ => unreachable!(),
}, },
Self::ArrayIndex { array, .. } => match array.ty(ctx) { Self::ArrayIndex { array, .. } => match array.ty(ctx) {

View file

@ -12,7 +12,7 @@ use smol_str::{format_smolstr, SmolStr};
use super::lower_to_item_tree::{LoweredElement, LoweredSubComponentMapping, LoweringState}; use super::lower_to_item_tree::{LoweredElement, LoweredSubComponentMapping, LoweringState};
use super::{Animation, PropertyReference}; use super::{Animation, PropertyReference};
use crate::langtype::{EnumerationValue, Type}; use crate::langtype::{EnumerationValue, Struct, Type};
use crate::layout::Orientation; use crate::layout::Orientation;
use crate::llr::Expression as llr_Expression; use crate::llr::Expression as llr_Expression;
use crate::namedreference::NamedReference; use crate::namedreference::NamedReference;
@ -260,8 +260,8 @@ fn lower_assignment(
tree_Expression::ReadLocalVariable { name: unique_name, ty: ty.clone() }; tree_Expression::ReadLocalVariable { name: unique_name, ty: ty.clone() };
let mut values = HashMap::new(); let mut values = HashMap::new();
match &ty { match &ty {
Type::Struct { fields, .. } => { Type::Struct(s) => {
for field in fields.keys() { for field in s.fields.keys() {
let e = if field != name { let e = if field != name {
tree_Expression::StructFieldAccess { tree_Expression::StructFieldAccess {
base: lower_base.clone().into(), base: lower_base.clone().into(),
@ -423,12 +423,12 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
} }
fn animation_ty() -> Type { fn animation_ty() -> Type {
Type::Struct { Type::Struct(Rc::new(Struct {
fields: animation_fields().collect(), fields: animation_fields().collect(),
name: Some("slint::private_api::PropertyAnimation".into()), name: Some("slint::private_api::PropertyAnimation".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
} }))
} }
match a { match a {
@ -456,7 +456,7 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
} }
let result = llr_Expression::Struct { let result = llr_Expression::Struct {
// This is going to be a tuple // This is going to be a tuple
ty: Type::Struct { ty: Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("0"), animation_ty), (SmolStr::new_static("0"), animation_ty),
// The type is an instant, which does not exist in our type system // The type is an instant, which does not exist in our type system
@ -466,7 +466,7 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
name: None, name: None,
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
values: IntoIterator::into_iter([ values: IntoIterator::into_iter([
(SmolStr::new_static("0"), get_anim), (SmolStr::new_static("0"), get_anim),
( (
@ -684,13 +684,13 @@ fn box_layout_data(
let repeater_count = let repeater_count =
layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count(); layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
let element_ty = Type::Struct { let element_ty = Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([("constraint".into(), crate::layout::layout_info_type())]) fields: IntoIterator::into_iter([("constraint".into(), crate::layout::layout_info_type())])
.collect(), .collect(),
name: Some("BoxLayoutCellData".into()), name: Some("BoxLayoutCellData".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}; }));
if repeater_count == 0 { if repeater_count == 0 {
let cells = llr_Expression::Array { let cells = llr_Expression::Array {
@ -767,7 +767,7 @@ fn grid_layout_cell_data(
} }
pub(super) fn grid_layout_cell_data_ty() -> Type { pub(super) fn grid_layout_cell_data_ty() -> Type {
Type::Struct { Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("col_or_row"), Type::Int32), (SmolStr::new_static("col_or_row"), Type::Int32),
(SmolStr::new_static("span"), Type::Int32), (SmolStr::new_static("span"), Type::Int32),
@ -777,7 +777,7 @@ pub(super) fn grid_layout_cell_data_ty() -> Type {
name: Some("GridLayoutCellData".into()), name: Some("GridLayoutCellData".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
} }))
} }
fn generate_layout_padding_and_spacing( fn generate_layout_padding_and_spacing(
@ -833,7 +833,7 @@ pub fn get_layout_info(
}; };
let ty = crate::layout::layout_info_type(); let ty = crate::layout::layout_info_type();
let fields = match &ty { let fields = match &ty {
Type::Struct { fields, .. } => fields, Type::Struct(s) => &s.fields,
_ => panic!(), _ => panic!(),
}; };
let mut values = fields let mut values = fields
@ -869,12 +869,12 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
fn llr_path_elements(elements: Vec<llr_Expression>) -> llr_Expression { fn llr_path_elements(elements: Vec<llr_Expression>) -> llr_Expression {
llr_Expression::Cast { llr_Expression::Cast {
from: llr_Expression::Array { from: llr_Expression::Array {
element_ty: Type::Struct { element_ty: Type::Struct(Rc::new(Struct {
fields: Default::default(), fields: Default::default(),
name: Some("PathElement".into()), name: Some("PathElement".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
values: elements, values: elements,
as_model: false, as_model: false,
} }
@ -888,7 +888,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
let converted_elements = elements let converted_elements = elements
.iter() .iter()
.map(|element| { .map(|element| {
let element_type = Type::Struct { let element_type = Type::Struct(Rc::new(Struct {
fields: element fields: element
.element_type .element_type
.properties .properties
@ -898,7 +898,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
name: element.element_type.native_class.cpp_type.clone(), name: element.element_type.native_class.cpp_type.clone(),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}; }));
llr_Expression::Struct { llr_Expression::Struct {
ty: element_type, ty: element_type,
@ -941,7 +941,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
llr_Expression::Cast { llr_Expression::Cast {
from: llr_Expression::Struct { from: llr_Expression::Struct {
ty: Type::Struct { ty: Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("events"), Type::Array(event_type.clone().into())), (SmolStr::new_static("events"), Type::Array(event_type.clone().into())),
(SmolStr::new_static("points"), Type::Array(point_type.clone().into())), (SmolStr::new_static("points"), Type::Array(point_type.clone().into())),
@ -950,7 +950,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
name: None, name: None,
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
values: IntoIterator::into_iter([ values: IntoIterator::into_iter([
( (
SmolStr::new_static("events"), SmolStr::new_static("events"),
@ -994,12 +994,12 @@ pub fn make_struct(
} }
llr_Expression::Struct { llr_Expression::Struct {
ty: Type::Struct { ty: Type::Struct(Rc::new(Struct {
fields, fields,
name: Some(format_smolstr!("slint::private_api::{name}")), name: Some(format_smolstr!("slint::private_api::{name}")),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}, })),
values, values,
} }
} }

View file

@ -5,7 +5,7 @@ use by_address::ByAddress;
use super::lower_expression::ExpressionContext; use super::lower_expression::ExpressionContext;
use crate::expression_tree::Expression as tree_Expression; use crate::expression_tree::Expression as tree_Expression;
use crate::langtype::{ElementType, Type}; use crate::langtype::{ElementType, Struct, Type};
use crate::llr::item_tree::*; use crate::llr::item_tree::*;
use crate::namedreference::NamedReference; use crate::namedreference::NamedReference;
use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis, PropertyVisibility}; use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis, PropertyVisibility};
@ -395,7 +395,7 @@ fn lower_sub_component(
let is_state_info = matches!( let is_state_info = matches!(
e.borrow().lookup_property(p).property_type, e.borrow().lookup_property(p).property_type,
Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo") Type::Struct(s) if s.name.as_ref().map_or(false, |name| name.ends_with("::StateInfo"))
); );
sub_component.property_init.push(( sub_component.property_init.push((
@ -554,7 +554,7 @@ fn lower_geometry(
.insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v))); .insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v)));
} }
super::Expression::Struct { super::Expression::Struct {
ty: Type::Struct { fields, name: None, node: None, rust_attributes: None }, ty: Type::Struct(Rc::new(Struct { fields, name: None, node: None, rust_attributes: None })),
values, values,
} }
} }

View file

@ -954,8 +954,8 @@ impl LookupObject for Expression {
match self { match self {
Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f), Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
_ => match self.ty() { _ => match self.ty() {
Type::Struct { fields, .. } => { Type::Struct(s) => {
for name in fields.keys() { for name in s.fields.keys() {
if let Some(r) = f( if let Some(r) = f(
name, name,
Expression::StructFieldAccess { Expression::StructFieldAccess {
@ -988,7 +988,7 @@ impl LookupObject for Expression {
match self { match self {
Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name), Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
_ => match self.ty() { _ => match self.ty() {
Type::Struct { fields, .. } => fields.contains_key(name).then(|| { Type::Struct(s) => s.fields.contains_key(name).then(|| {
LookupResult::from(Expression::StructFieldAccess { LookupResult::from(Expression::StructFieldAccess {
base: Box::new(self.clone()), base: Box::new(self.clone()),
name: name.into(), name: name.into(),

View file

@ -11,7 +11,8 @@ use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::expression_tree::{self, BindingExpression, Expression, Unit}; use crate::expression_tree::{self, BindingExpression, Expression, Unit};
use crate::langtype::EnumerationValue; use crate::langtype::EnumerationValue;
use crate::langtype::{ use crate::langtype::{
BuiltinElement, BuiltinPropertyDefault, Callback, Enumeration, Function, NativeClass, Type, BuiltinElement, BuiltinPropertyDefault, Callback, Enumeration, Function, NativeClass, Struct,
Type,
}; };
use crate::langtype::{ElementType, PropertyLookupResult}; use crate::langtype::{ElementType, PropertyLookupResult};
use crate::layout::{LayoutConstraints, Orientation}; use crate::layout::{LayoutConstraints, Orientation};
@ -90,14 +91,14 @@ impl Document {
local_registry: &mut TypeRegister, local_registry: &mut TypeRegister,
inner_types: &mut Vec<Type>| { inner_types: &mut Vec<Type>| {
let rust_attributes = n.AtRustAttr().map(|child| vec![child.text().to_smolstr()]); let rust_attributes = n.AtRustAttr().map(|child| vec![child.text().to_smolstr()]);
let mut ty = let ty = type_struct_from_node(
type_struct_from_node(n.ObjectType(), diag, local_registry, rust_attributes); n.ObjectType(),
if let Type::Struct { name, .. } = &mut ty { diag,
*name = parser::identifier_text(&n.DeclaredIdentifier()); local_registry,
} else { rust_attributes,
assert!(diag.has_errors()); parser::identifier_text(&n.DeclaredIdentifier()),
return; );
} assert!(matches!(ty, Type::Struct(_)));
local_registry.insert_type(ty.clone()); local_registry.insert_type(ty.clone());
inner_types.push(ty); inner_types.push(ty);
}; };
@ -1882,7 +1883,7 @@ pub fn type_from_node(
} }
prop_type prop_type
} else if let Some(object_node) = node.ObjectType() { } else if let Some(object_node) = node.ObjectType() {
type_struct_from_node(object_node, diag, tr, None) type_struct_from_node(object_node, diag, tr, None, None)
} else if let Some(array_node) = node.ArrayType() { } else if let Some(array_node) = node.ArrayType() {
Type::Array(Box::new(type_from_node(array_node.Type(), diag, tr))) Type::Array(Box::new(type_from_node(array_node.Type(), diag, tr)))
} else { } else {
@ -1897,6 +1898,7 @@ pub fn type_struct_from_node(
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
tr: &TypeRegister, tr: &TypeRegister,
rust_attributes: Option<Vec<SmolStr>>, rust_attributes: Option<Vec<SmolStr>>,
name: Option<SmolStr>,
) -> Type { ) -> Type {
let fields = object_node let fields = object_node
.ObjectTypeMember() .ObjectTypeMember()
@ -1907,7 +1909,7 @@ pub fn type_struct_from_node(
) )
}) })
.collect(); .collect();
Type::Struct { fields, name: None, node: Some(object_node), rust_attributes } Type::Struct(Rc::new(Struct { fields, name, node: Some(object_node), rust_attributes }))
} }
fn animation_element_from_node( fn animation_element_from_node(

View file

@ -61,30 +61,32 @@ fn collect_types_in_component(root_component: &Rc<Component>, hash: &mut BTreeMa
/// it are placed before in the vector /// it are placed before in the vector
fn sort_types(hash: &mut BTreeMap<SmolStr, Type>, vec: &mut Vec<Type>, key: &str) { fn sort_types(hash: &mut BTreeMap<SmolStr, Type>, vec: &mut Vec<Type>, key: &str) {
let ty = if let Some(ty) = hash.remove(key) { ty } else { return }; let ty = if let Some(ty) = hash.remove(key) { ty } else { return };
if let Type::Struct { fields, name: Some(name), .. } = &ty { if let Type::Struct(s) = &ty {
if let Some(name) = &s.name {
if name.contains("::") { if name.contains("::") {
// This is a builtin type. // This is a builtin type.
// FIXME! there should be a better way to handle builtin struct // FIXME! there should be a better way to handle builtin struct
return; return;
} }
for sub_ty in fields.values() { for sub_ty in s.fields.values() {
visit_declared_type(sub_ty, &mut |name, _| sort_types(hash, vec, name)); visit_declared_type(sub_ty, &mut |name, _| sort_types(hash, vec, name));
} }
} }
}
vec.push(ty) vec.push(ty)
} }
/// Will call the `visitor` for every named struct or enum that is not builtin /// Will call the `visitor` for every named struct or enum that is not builtin
fn visit_declared_type(ty: &Type, visitor: &mut impl FnMut(&SmolStr, &Type)) { fn visit_declared_type(ty: &Type, visitor: &mut impl FnMut(&SmolStr, &Type)) {
match ty { match ty {
Type::Struct { fields, name, node, .. } => { Type::Struct(s) => {
if node.is_some() { if s.node.is_some() {
if let Some(struct_name) = name.as_ref() { if let Some(struct_name) = s.name.as_ref() {
visitor(struct_name, ty); visitor(struct_name, ty);
} }
} }
for sub_ty in fields.values() { for sub_ty in s.fields.values() {
visit_declared_type(sub_ty, visitor); visit_declared_type(sub_ty, visitor);
} }
} }

View file

@ -12,7 +12,7 @@
use crate::diagnostics::BuildDiagnostics; use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::*; use crate::expression_tree::*;
use crate::langtype::ElementType; use crate::langtype::ElementType;
use crate::langtype::Type; use crate::langtype::{Struct, Type};
use crate::object_tree::*; use crate::object_tree::*;
use crate::EmbedResourcesKind; use crate::EmbedResourcesKind;
use smol_str::SmolStr; use smol_str::SmolStr;
@ -152,7 +152,7 @@ fn compile_path_from_string_literal(
let path = builder.build(); let path = builder.build();
let event_enum = crate::typeregister::BUILTIN_ENUMS.with(|e| e.PathEvent.clone()); let event_enum = crate::typeregister::BUILTIN_ENUMS.with(|e| e.PathEvent.clone());
let point_type = Type::Struct { let point_type = Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("x"), Type::Float32), (SmolStr::new_static("x"), Type::Float32),
(SmolStr::new_static("y"), Type::Float32), (SmolStr::new_static("y"), Type::Float32),
@ -161,7 +161,7 @@ fn compile_path_from_string_literal(
name: Some("slint::private_api::Point".into()), name: Some("slint::private_api::Point".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}; }));
let mut points = Vec::new(); let mut points = Vec::new();
let events = path let events = path

View file

@ -23,7 +23,12 @@ fn simplify_expression(expr: &mut Expression) -> bool {
match expr { match expr {
Expression::PropertyReference(nr) => { Expression::PropertyReference(nr) => {
if nr.is_constant() if nr.is_constant()
&& !matches!(nr.ty(), Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo")) && !match nr.ty() {
Type::Struct(s) => {
s.name.as_ref().map_or(false, |name| name.ends_with("::StateInfo"))
}
_ => false,
}
{ {
// Inline the constant value // Inline the constant value
if let Some(result) = extract_constant_property_reference(nr) { if let Some(result) = extract_constant_property_reference(nr) {

View file

@ -264,8 +264,9 @@ fn merge_explicit_constraints(
name: unique_name.clone(), name: unique_name.clone(),
value: Box::new(std::mem::take(expr)), value: Box::new(std::mem::take(expr)),
}; };
let Type::Struct { fields, .. } = &ty else { unreachable!() }; let Type::Struct(s) = &ty else { unreachable!() };
let mut values = fields let mut values = s
.fields
.keys() .keys()
.map(|p| { .map(|p| {
( (

View file

@ -21,7 +21,7 @@ pub fn lower_states(
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
) { ) {
let state_info_type = tr.lookup("StateInfo"); let state_info_type = tr.lookup("StateInfo");
assert!(matches!(state_info_type, Type::Struct { name: Some(_), .. })); assert!(matches!(state_info_type, Type::Struct(ref s) if s.name.is_some()));
recurse_elem(&component.root_element, &(), &mut |elem, _| { recurse_elem(&component.root_element, &(), &mut |elem, _| {
lower_state_in_element(elem, &state_info_type, diag) lower_state_in_element(elem, &state_info_type, diag)
}); });

View file

@ -3,9 +3,10 @@
use smol_str::{format_smolstr, SmolStr}; use smol_str::{format_smolstr, SmolStr};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
use crate::expression_tree::Expression; use crate::expression_tree::Expression;
use crate::langtype::Type; use crate::langtype::{Struct, Type};
pub fn remove_return(doc: &crate::object_tree::Document) { pub fn remove_return(doc: &crate::object_tree::Document) {
doc.visit_all_used_components(|component| { doc.visit_all_used_components(|component| {
@ -431,7 +432,12 @@ fn make_struct(it: impl Iterator<Item = (&'static str, Type, Expression)>) -> Ex
codeblock_with_expr( codeblock_with_expr(
voids, voids,
Expression::Struct { Expression::Struct {
ty: Type::Struct { fields, name: None, node: None, rust_attributes: None }, ty: Type::Struct(Rc::new(Struct {
fields,
name: None,
node: None,
rust_attributes: None,
})),
values, values,
}, },
) )
@ -440,13 +446,13 @@ fn make_struct(it: impl Iterator<Item = (&'static str, Type, Expression)>) -> Ex
/// Given an expression `from` of type Struct, convert to another type struct with more fields /// Given an expression `from` of type Struct, convert to another type struct with more fields
/// Add missing members in `from` /// Add missing members in `from`
fn convert_struct(from: Expression, to: Type) -> Expression { fn convert_struct(from: Expression, to: Type) -> Expression {
let Type::Struct { fields, .. } = &to else { let Type::Struct(s) = &to else {
assert_eq!(to, Type::Invalid); assert_eq!(to, Type::Invalid);
return Expression::Invalid; return Expression::Invalid;
}; };
if let Expression::Struct { mut values, .. } = from { if let Expression::Struct { mut values, .. } = from {
let mut new_values = HashMap::new(); let mut new_values = HashMap::new();
for (key, ty) in fields { for (key, ty) in &s.fields {
let (key, expression) = values let (key, expression) = values
.remove_entry(key) .remove_entry(key)
.unwrap_or_else(|| (key.clone(), Expression::default_value_for_type(ty))); .unwrap_or_else(|| (key.clone(), Expression::default_value_for_type(ty)));
@ -457,12 +463,12 @@ fn convert_struct(from: Expression, to: Type) -> Expression {
let var_name = "tmpobj"; let var_name = "tmpobj";
let from_ty = from.ty(); let from_ty = from.ty();
let mut new_values = HashMap::new(); let mut new_values = HashMap::new();
let Type::Struct { fields: form_fields, .. } = &from_ty else { let Type::Struct(from_s) = &from_ty else {
assert_eq!(from_ty, Type::Invalid); assert_eq!(from_ty, Type::Invalid);
return Expression::Invalid; return Expression::Invalid;
}; };
for (key, ty) in fields { for (key, ty) in &s.fields {
let expression = if form_fields.contains_key(key) { let expression = if from_s.fields.contains_key(key) {
Expression::StructFieldAccess { Expression::StructFieldAccess {
base: Box::new(Expression::ReadLocalVariable { base: Box::new(Expression::ReadLocalVariable {
name: var_name.into(), name: var_name.into(),

View file

@ -10,7 +10,7 @@
use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::diagnostics::{BuildDiagnostics, Spanned};
use crate::expression_tree::*; use crate::expression_tree::*;
use crate::langtype::{ElementType, Type}; use crate::langtype::{ElementType, Struct, Type};
use crate::lookup::{LookupCtx, LookupObject, LookupResult}; use crate::lookup::{LookupCtx, LookupObject, LookupResult};
use crate::object_tree::*; use crate::object_tree::*;
use crate::parser::{identifier_text, syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode}; use crate::parser::{identifier_text, syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode};
@ -1205,12 +1205,12 @@ impl Expression {
) )
}) })
.collect(); .collect();
let ty = Type::Struct { let ty = Type::Struct(Rc::new(Struct {
fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(), fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
name: None, name: None,
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}; }));
Expression::Struct { ty, values } Expression::Struct { ty, values }
} }
@ -1268,41 +1268,34 @@ impl Expression {
expr_ty expr_ty
} else { } else {
match (target_type, expr_ty) { match (target_type, expr_ty) {
( (Type::Struct(ref result), Type::Struct(ref elem)) => {
Type::Struct { let mut fields = result.fields.clone();
fields: mut result_fields, for (elem_name, elem_ty) in elem.fields.iter() {
name: result_name, match fields.entry(elem_name.clone()) {
node: result_node,
rust_attributes,
},
Type::Struct {
fields: elem_fields,
name: elem_name,
node: elem_node,
rust_attributes: derived,
},
) => {
for (elem_name, elem_ty) in elem_fields.into_iter() {
match result_fields.entry(elem_name) {
std::collections::btree_map::Entry::Vacant(free_entry) => { std::collections::btree_map::Entry::Vacant(free_entry) => {
free_entry.insert(elem_ty); free_entry.insert(elem_ty.clone());
} }
std::collections::btree_map::Entry::Occupied( std::collections::btree_map::Entry::Occupied(
mut existing_field, mut existing_field,
) => { ) => {
*existing_field.get_mut() = *existing_field.get_mut() =
Self::common_target_type_for_type_list( Self::common_target_type_for_type_list(
[existing_field.get().clone(), elem_ty].into_iter(), [existing_field.get().clone(), elem_ty.clone()]
.into_iter(),
); );
} }
} }
} }
Type::Struct { Type::Struct(Rc::new(Struct {
name: result_name.or(elem_name), name: result.name.as_ref().or(elem.name.as_ref()).cloned(),
fields: result_fields, fields,
node: result_node.or(elem_node), node: result.node.as_ref().or(elem.node.as_ref()).cloned(),
rust_attributes: rust_attributes.or(derived), rust_attributes: result
} .rust_attributes
.as_ref()
.or(elem.rust_attributes.as_ref())
.cloned(),
}))
} }
(Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void { (Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void {
rhs rhs

View file

@ -11,7 +11,7 @@ use std::rc::Rc;
use crate::expression_tree::BuiltinFunction; use crate::expression_tree::BuiltinFunction;
use crate::langtype::{ use crate::langtype::{
BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, Callback, ElementType, BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, Callback, ElementType,
Enumeration, PropertyLookupResult, Type, Enumeration, PropertyLookupResult, Struct, Type,
}; };
use crate::object_tree::{Component, PropertyVisibility}; use crate::object_tree::{Component, PropertyVisibility};
use crate::typeloader; use crate::typeloader;
@ -340,14 +340,14 @@ impl TypeRegister {
} }
} }
)*) => { $( )*) => { $(
let $Name = Type::Struct { let $Name = Type::Struct(Rc::new(Struct{
fields: BTreeMap::from([ fields: BTreeMap::from([
$((stringify!($pub_field).replace_smolstr("_", "-"), map_type!($pub_type, $pub_type))),* $((stringify!($pub_field).replace_smolstr("_", "-"), map_type!($pub_type, $pub_type))),*
]), ]),
name: Some(format_smolstr!("{}", $inner_name)), name: Some(format_smolstr!("{}", $inner_name)),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}; }));
register.insert_type_with_name(maybe_clone!($Name, $Name), SmolStr::new(stringify!($Name))); register.insert_type_with_name(maybe_clone!($Name, $Name), SmolStr::new(stringify!($Name)));
)* }; )* };
} }
@ -585,7 +585,8 @@ impl TypeRegister {
} }
pub fn logical_point_type() -> Type { pub fn logical_point_type() -> Type {
Type::Struct { thread_local! {
static TYPE: Type = Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("x"), Type::LogicalLength), (SmolStr::new_static("x"), Type::LogicalLength),
(SmolStr::new_static("y"), Type::LogicalLength), (SmolStr::new_static("y"), Type::LogicalLength),
@ -594,11 +595,14 @@ pub fn logical_point_type() -> Type {
name: Some("slint::LogicalPosition".into()), name: Some("slint::LogicalPosition".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}));
} }
TYPE.with(|t| t.clone())
} }
pub fn font_metrics_type() -> Type { pub fn font_metrics_type() -> Type {
Type::Struct { thread_local! {
static TYPE: Type = Type::Struct(Rc::new(Struct {
fields: IntoIterator::into_iter([ fields: IntoIterator::into_iter([
(SmolStr::new_static("ascent"), Type::LogicalLength), (SmolStr::new_static("ascent"), Type::LogicalLength),
(SmolStr::new_static("descent"), Type::LogicalLength), (SmolStr::new_static("descent"), Type::LogicalLength),
@ -609,5 +613,7 @@ pub fn font_metrics_type() -> Type {
name: Some("slint::private_api::FontMetrics".into()), name: Some("slint::private_api::FontMetrics".into()),
node: None, node: None,
rust_attributes: None, rust_attributes: None,
}));
} }
TYPE.with(|t| t.clone())
} }

View file

@ -2007,6 +2007,7 @@ fn component_definition_model_properties() {
#[test] #[test]
fn lang_type_to_value_type() { fn lang_type_to_value_type() {
use i_slint_compiler::langtype::Struct as LangStruct;
use std::collections::BTreeMap; use std::collections::BTreeMap;
assert_eq!(ValueType::from(LangType::Void), ValueType::Void); assert_eq!(ValueType::from(LangType::Void), ValueType::Void);
@ -2024,12 +2025,12 @@ fn lang_type_to_value_type() {
assert_eq!(ValueType::from(LangType::Array(Box::new(LangType::Void))), ValueType::Model); assert_eq!(ValueType::from(LangType::Array(Box::new(LangType::Void))), ValueType::Model);
assert_eq!(ValueType::from(LangType::Bool), ValueType::Bool); assert_eq!(ValueType::from(LangType::Bool), ValueType::Bool);
assert_eq!( assert_eq!(
ValueType::from(LangType::Struct { ValueType::from(LangType::Struct(Rc::new(LangStruct {
fields: BTreeMap::default(), fields: BTreeMap::default(),
name: None, name: None,
node: None, node: None,
rust_attributes: None rust_attributes: None
}), }))),
ValueType::Struct ValueType::Struct
); );
assert_eq!(ValueType::from(LangType::Image), ValueType::Image); assert_eq!(ValueType::from(LangType::Image), ValueType::Image);

View file

@ -915,8 +915,8 @@ pub async fn load(
Some((&export.0.name, &component.id)) Some((&export.0.name, &component.id))
} }
Either::Right(ty) => match &ty { Either::Right(ty) => match &ty {
Type::Struct { name: Some(name), node: Some(_), .. } => { Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
Some((&export.0.name, name)) Some((&export.0.name, s.name.as_ref().unwrap()))
} }
Type::Enumeration(en) => Some((&export.0.name, &en.name)), Type::Enumeration(en) => Some((&export.0.name, &en.name)),
_ => None, _ => None,
@ -1185,10 +1185,12 @@ pub(crate) fn generate_item_tree<'id>(
Type::Image => property_info::<i_slint_core::graphics::Image>(), Type::Image => property_info::<i_slint_core::graphics::Image>(),
Type::Bool => property_info::<bool>(), Type::Bool => property_info::<bool>(),
Type::ComponentFactory => property_info::<ComponentFactory>(), Type::ComponentFactory => property_info::<ComponentFactory>(),
Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo") => { Type::Struct(s)
if s.name.as_ref().map_or(false, |name| name.ends_with("::StateInfo")) =>
{
property_info::<i_slint_core::properties::StateInfo>() property_info::<i_slint_core::properties::StateInfo>()
} }
Type::Struct { .. } => property_info::<Value>(), Type::Struct(_) => property_info::<Value>(),
Type::Array(_) => property_info::<Value>(), Type::Array(_) => property_info::<Value>(),
Type::Easing => property_info::<i_slint_core::animations::EasingCurve>(), Type::Easing => property_info::<i_slint_core::animations::EasingCurve>(),
Type::Percent => property_info::<f32>(), Type::Percent => property_info::<f32>(),
@ -1577,7 +1579,7 @@ pub fn instantiate(
} else if let Some(PropertiesWithinComponent { offset, prop: prop_info, .. }) = } else if let Some(PropertiesWithinComponent { offset, prop: prop_info, .. }) =
description.custom_properties.get(prop_name).filter(|_| is_root) description.custom_properties.get(prop_name).filter(|_| is_root)
{ {
let is_state_info = matches!(property_type, Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo")); let is_state_info = matches!(property_type, Type::Struct (s) if s.name.as_ref().map_or(false, |name| name.ends_with("::StateInfo")));
if is_state_info { if is_state_info {
let prop = Pin::new_unchecked( let prop = Pin::new_unchecked(
&*(instance_ref.as_ptr().add(*offset) &*(instance_ref.as_ptr().add(*offset)

View file

@ -1445,8 +1445,8 @@ fn check_value_type(value: &Value, ty: &Type) -> bool {
Type::Array(inner) => { Type::Array(inner) => {
matches!(value, Value::Model(m) if m.iter().all(|v| check_value_type(&v, inner))) matches!(value, Value::Model(m) if m.iter().all(|v| check_value_type(&v, inner)))
} }
Type::Struct { fields, .. } => { Type::Struct(s) => {
matches!(value, Value::Struct(str) if str.iter().all(|(k, v)| fields.get(k).map_or(false, |ty| check_value_type(v, ty)))) matches!(value, Value::Struct(str) if str.iter().all(|(k, v)| s.fields.get(k).map_or(false, |ty| check_value_type(v, ty))))
} }
Type::Enumeration(en) => { Type::Enumeration(en) => {
matches!(value, Value::EnumerationValue(name, _) if name == en.name.as_str()) matches!(value, Value::EnumerationValue(name, _) if name == en.name.as_str())
@ -1688,8 +1688,8 @@ pub fn default_value_for_type(ty: &Type) -> Value {
Type::Image => Value::Image(Default::default()), Type::Image => Value::Image(Default::default()),
Type::Bool => Value::Bool(false), Type::Bool => Value::Bool(false),
Type::Callback { .. } => Value::Void, Type::Callback { .. } => Value::Void,
Type::Struct { fields, .. } => Value::Struct( Type::Struct(s) => Value::Struct(
fields s.fields
.iter() .iter()
.map(|(n, t)| (n.to_string(), default_value_for_type(t))) .map(|(n, t)| (n.to_string(), default_value_for_type(t)))
.collect::<Struct>(), .collect::<Struct>(),

View file

@ -1015,12 +1015,12 @@ fn get_document_symbols(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
r.extend(inner_types.iter().filter_map(|c| match c { r.extend(inner_types.iter().filter_map(|c| match c {
Type::Struct { name: Some(name), node: Some(node), .. } => Some(DocumentSymbol { Type::Struct(s) if s.name.is_some() && s.node.is_some() => Some(DocumentSymbol {
range: util::node_to_lsp_range(node.parent().as_ref()?), range: util::node_to_lsp_range(s.node.as_ref().unwrap().parent().as_ref()?),
selection_range: util::node_to_lsp_range( selection_range: util::node_to_lsp_range(
&node.parent()?.child_node(SyntaxKind::DeclaredIdentifier)?, &s.node.as_ref().unwrap().parent()?.child_node(SyntaxKind::DeclaredIdentifier)?,
), ),
name: name.to_string(), name: s.name.as_ref().unwrap().to_string(),
kind: lsp_types::SymbolKind::STRUCT, kind: lsp_types::SymbolKind::STRUCT,
..ds.clone() ..ds.clone()
}), }),

View file

@ -61,7 +61,9 @@ pub fn goto_definition(
fn goto_type(ty: &Type) -> Option<GotoDefinitionResponse> { fn goto_type(ty: &Type) -> Option<GotoDefinitionResponse> {
match ty { match ty {
Type::Struct { node: Some(node), .. } => goto_node(node.parent().as_ref()?), Type::Struct(s) if s.node.is_some() => {
goto_node(s.node.as_ref().unwrap().parent().as_ref()?)
}
Type::Enumeration(e) => goto_node(e.node.as_ref()?), Type::Enumeration(e) => goto_node(e.node.as_ref()?),
_ => None, _ => None,
} }

View file

@ -405,11 +405,11 @@ fn load_data(
_ => slint_interpreter::Value::Void, _ => slint_interpreter::Value::Void,
}, },
serde_json::Value::Object(obj) => match t { serde_json::Value::Object(obj) => match t {
i_slint_compiler::langtype::Type::Struct { fields, .. } => obj i_slint_compiler::langtype::Type::Struct(s) => obj
.iter() .iter()
.filter_map(|(k, v)| { .filter_map(|(k, v)| {
let k = k.to_smolstr(); let k = k.to_smolstr();
match fields.get(&k) { match s.fields.get(&k) {
Some(t) => Some((k.to_string(), from_json(t, v))), Some(t) => Some((k.to_string(), from_json(t, v))),
None => { None => {
eprintln!("Warning: ignoring unknown property: {}", k); eprintln!("Warning: ignoring unknown property: {}", k);