Add @rust-attr on scturct (#2785)

Fixes: #2660
This commit is contained in:
Amirhossein Akhlaghpour 2023-06-05 17:59:55 +03:30 committed by GitHub
parent bef2e3617d
commit 3a4f3c61d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 196 additions and 16 deletions

View file

@ -138,6 +138,8 @@ log = { version = "0.4.17", optional = true }
[dev-dependencies]
slint-build = { path = "../build" }
i-slint-backend-testing = { version = "=1.0.3", path = "../../../internal/backends/testing" }
serde_json = "1.0.96"
serde = { version = "1.0.163", features = ["derive"] }
[package.metadata.docs.rs]
rustdoc-args = [

View file

@ -208,6 +208,26 @@ struct MyStruct {
}
```
The `.slint` file allows you to utilize Rust attributes and features for defining structures using the `@rust-attr()` directive.
This enables you to customize the generated code by applying additional traits, derivations, or annotations.
Consider the following structure defined in the `.slint` file with Rust attributes:
```slint,ignore
@rust-attr(derive(serde::Serialize, serde::Deserialize))
struct MyStruct {
foo : i32,
}
```
Based on this structure, the following Rust code would be generated:
```rust
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone, Debug, PartialEq)]
struct MyStruct {
foo : i32,
}
```
## Exported Global singletons
*/

View file

@ -15,3 +15,19 @@ fn empty_stuff() {
slint!(export struct Hei { abcd: bool });
slint!(export global G { });
}
#[test]
fn test_serialize_deserialize_struct() {
i_slint_backend_testing::init();
slint! {
@rust-attr(derive(serde::Serialize, serde::Deserialize))
export struct TestStruct {
foo: int,
}
export component Test { }
}
let data = TestStruct { foo: 1 };
let serialized = serde_json::to_string(&data).unwrap();
let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
assert_eq!(data, deserialized);
}

View file

@ -173,6 +173,7 @@ impl BuiltinFunction {
.collect(),
name: Some("Size".to_string()),
node: None,
rust_attributes: None,
}),
args: vec![Type::Image],
},
@ -1027,7 +1028,7 @@ impl Expression {
},
(
Type::Struct { fields: ref left, .. },
Type::Struct { fields: right, name, node: n },
Type::Struct { fields: right, name, node: n, rust_attributes },
) if left != right => {
if let Expression::Struct { mut values, .. } = self {
let mut new_values = HashMap::new();
@ -1051,6 +1052,7 @@ impl Expression {
fields: left.clone(),
name: name.clone(),
node: n.clone(),
rust_attributes: rust_attributes.clone(),
},
}),
name: key.clone(),

View file

@ -579,7 +579,7 @@ pub fn generate(doc: &Document) -> impl std::fmt::Display {
}
for ty in doc.root_component.used_types.borrow().structs.iter() {
if let Type::Struct { fields, name: Some(name), node: Some(node) } = ty {
if let Type::Struct { fields, name: Some(name), node: Some(node), .. } = ty {
generate_struct(&mut file, name, fields, node);
}
}

View file

@ -25,6 +25,7 @@ use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::str::FromStr;
type EvaluationContext<'a> = llr_EvaluationContext<'a, TokenStream>;
type ParentCtx<'a> = llr_ParentCtx<'a, TokenStream>;
@ -139,8 +140,8 @@ pub fn generate(doc: &Document) -> TokenStream {
.structs
.iter()
.filter_map(|ty| {
if let Type::Struct { fields, name: Some(name), node: Some(_) } = ty {
Some((ident(name), generate_struct(name, fields)))
if let Type::Struct { fields, name: Some(name), node: Some(_), rust_attributes } = ty {
Some((ident(name), generate_struct(name, fields, rust_attributes)))
} else {
None
}
@ -422,12 +423,28 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
)
}
fn generate_struct(name: &str, fields: &BTreeMap<String, Type>) -> TokenStream {
fn generate_struct(
name: &str,
fields: &BTreeMap<String, Type>,
rust_attributes: &Option<Vec<String>>,
) -> TokenStream {
let component_id = struct_name_to_tokens(name);
let (declared_property_vars, declared_property_types): (Vec<_>, Vec<_>) =
fields.iter().map(|(name, ty)| (ident(name), rust_primitive_type(ty).unwrap())).unzip();
let attributes = if let Some(feature) = rust_attributes {
let attr =
feature.iter().map(|f| match TokenStream::from_str(format!(r#"#[{}]"#, f).as_str()) {
Ok(eval) => eval,
Err(_) => quote! {},
});
quote! { #(#attr)* }
} else {
quote! {}
};
quote! {
#attributes
#[derive(Default, PartialEq, Debug, Clone)]
pub struct #component_id {
#(pub #declared_property_vars : #declared_property_types),*

View file

@ -60,6 +60,8 @@ pub enum Type {
name: Option<String>,
/// When declared in .slint, this is the node of the declaration.
node: Option<syntax_nodes::ObjectType>,
/// deriven
rust_attributes: Option<Vec<String>>,
},
Enumeration(Rc<Enumeration>),
@ -104,8 +106,8 @@ impl core::cmp::PartialEq for Type {
Type::Easing => matches!(other, Type::Easing),
Type::Brush => matches!(other, Type::Brush),
Type::Array(a) => matches!(other, Type::Array(b) if a == b),
Type::Struct { fields, name, node: _ } => {
matches!(other, Type::Struct{fields: f, name: n, node: _} if fields == f && name == n)
Type::Struct { fields, name, node: _, rust_attributes: _ } => {
matches!(other, Type::Struct{fields:f,name:n,node:_, rust_attributes: _ } if fields == f && name == n)
}
Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),

View file

@ -460,6 +460,7 @@ pub fn layout_info_type() -> Type {
.collect(),
name: Some("LayoutInfo".into()),
node: None,
rust_attributes: None,
}
}

View file

@ -398,6 +398,7 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
fields: animation_fields().collect(),
name: Some("PropertyAnimation".into()),
node: None,
rust_attributes: None,
}
}
@ -435,6 +436,7 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
.collect(),
name: None,
node: None,
rust_attributes: None,
},
values: IntoIterator::into_iter([
("0".to_string(), get_anim),
@ -661,6 +663,7 @@ fn box_layout_data(
.collect(),
name: Some("BoxLayoutCellData".into()),
node: None,
rust_attributes: None,
};
if repeater_count == 0 {
@ -747,6 +750,7 @@ pub(super) fn grid_layout_cell_data_ty() -> Type {
.collect(),
name: Some("GridLayoutCellData".into()),
node: None,
rust_attributes: None,
}
}
@ -843,6 +847,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
fields: Default::default(),
name: Some("PathElement".to_owned()),
node: None,
rust_attributes: None,
},
values: elements,
as_model: false,
@ -866,6 +871,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
.collect(),
name: element.element_type.native_class.cpp_type.clone(),
node: None,
rust_attributes: None,
};
llr_Expression::Struct {
@ -917,6 +923,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
.collect(),
name: None,
node: None,
rust_attributes: None,
},
values: IntoIterator::into_iter([
(
@ -960,5 +967,8 @@ fn make_struct(
values.insert(name.to_string(), expr);
}
llr_Expression::Struct { ty: Type::Struct { fields, name: Some(name), node: None }, values }
llr_Expression::Struct {
ty: Type::Struct { fields, name: Some(name), node: None, rust_attributes: None },
values,
}
}

View file

@ -36,7 +36,7 @@ pub fn load_builtins(register: &mut TypeRegister) {
// parse structs
for s in doc.StructDeclaration().chain(doc.ExportsList().flat_map(|e| e.StructDeclaration())) {
let external_name = identifier_text(&s.DeclaredIdentifier()).unwrap();
let mut ty = object_tree::type_struct_from_node(s.ObjectType(), &mut diag, register);
let mut ty = object_tree::type_struct_from_node(s.ObjectType(), &mut diag, register, None);
if let Type::Struct { name, .. } = &mut ty {
*name = Some(
parse_annotation("name", &s.ObjectType())

View file

@ -7,7 +7,7 @@
// cSpell: ignore qualname
use itertools::Either;
use itertools::{Either, Itertools};
use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::expression_tree::{self, BindingExpression, Expression, Unit};
@ -77,7 +77,21 @@ impl Document {
|n: syntax_nodes::StructDeclaration,
diag: &mut BuildDiagnostics,
local_registry: &mut TypeRegister| {
let mut ty = type_struct_from_node(n.ObjectType(), diag, local_registry);
let rust_attributes: Vec<String> = n
.children()
.filter(|child| child.kind() == SyntaxKind::AtRustAttr)
.map(|child| {
let mut text = child.text().to_string();
text.pop();
text
})
.collect_vec();
let mut ty = type_struct_from_node(
n.ObjectType(),
diag,
local_registry,
Some(rust_attributes),
);
if let Type::Struct { name, .. } = &mut ty {
*name = parser::identifier_text(&n.DeclaredIdentifier());
} else {
@ -1630,7 +1644,7 @@ pub fn type_from_node(
}
prop_type
} else if let Some(object_node) = node.ObjectType() {
type_struct_from_node(object_node, diag, tr)
type_struct_from_node(object_node, diag, tr, None)
} else if let Some(array_node) = node.ArrayType() {
Type::Array(Box::new(type_from_node(array_node.Type(), diag, tr)))
} else {
@ -1644,6 +1658,7 @@ pub fn type_struct_from_node(
object_node: syntax_nodes::ObjectType,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
rust_attributes: Option<Vec<String>>,
) -> Type {
let fields = object_node
.ObjectTypeMember()
@ -1654,7 +1669,7 @@ pub fn type_struct_from_node(
)
})
.collect();
Type::Struct { fields, name: None, node: Some(object_node) }
Type::Struct { fields, name: None, node: Some(object_node), rust_attributes }
}
fn animation_element_from_node(

View file

@ -422,8 +422,9 @@ declare_syntax! {
/// `[ type ]`
ArrayType -> [ Type ],
/// `struct Foo := { ... }
StructDeclaration -> [DeclaredIdentifier, ObjectType],
StructDeclaration -> [DeclaredIdentifier, ObjectType, ?AtRustAttr],
/// `@rust-attr(...)`
AtRustAttr -> [],
}
}

View file

@ -4,6 +4,7 @@
use super::element::{parse_element, parse_element_content};
use super::prelude::*;
use super::r#type::parse_struct_declaration;
use crate::parser::r#type::parse_rustattr;
#[cfg_attr(test, parser_test)]
/// ```test,Document
@ -45,6 +46,35 @@ pub fn parse_document(p: &mut impl Parser) -> bool {
break;
}
}
"@" if p.nth(1).as_str() == "rust-attr" => {
let mut is_export = false;
let mut i = 0;
loop {
let value = p.nth(i);
if value.as_str() == ")" && p.nth(i + 1).as_str() == "export" {
is_export = true;
break;
} else if (value.as_str() == ")"
&& p.nth(i + 1).as_str() != "struct"
&& p.nth(i + 1).as_str() != "export"
&& p.nth(i + 1).as_str() != ")")
|| (value.as_str() == ")" && p.nth(i + 1).as_str() == "struct")
{
break;
}
i += 1;
}
if is_export {
let mut p = p.start_node(SyntaxKind::ExportsList);
if !parse_rustattr(&mut *p) {
break;
}
} else {
if !parse_rustattr(&mut *p) {
break;
}
}
}
_ => {
if !parse_component(&mut *p) {
break;

View file

@ -85,3 +85,57 @@ pub fn parse_struct_declaration(p: &mut impl Parser) -> bool {
parse_type_object(&mut *p);
true
}
pub fn parse_rustattr(p: &mut impl Parser) -> bool {
let checkpoint = p.checkpoint();
debug_assert_eq!(p.peek().as_str(), "@");
p.consume(); // "@"
if p.peek().as_str() != "rust-attr" {
p.expect(SyntaxKind::AtRustAttr);
}
p.consume(); // "rust-attr"
p.expect(SyntaxKind::LParent);
parse_parentheses(&mut *p);
if p.peek().as_str() == "export" {
p.consume();
}
let mut p = p.start_node_at(checkpoint, SyntaxKind::StructDeclaration);
p.consume(); // "struct"
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
if p.peek().kind() == SyntaxKind::ColonEqual {
p.warning("':=' to declare a struct is deprecated. Remove the ':='");
p.consume();
}
parse_type_object(&mut *p);
true
}
fn parse_parentheses(p: &mut impl Parser) -> bool {
let mut p = p.start_node(SyntaxKind::AtRustAttr);
let mut opened = 0;
let mut closed = 0;
while closed <= opened {
if p.peek().kind() == SyntaxKind::LParent {
opened += 1;
}
if p.peek().kind() == SyntaxKind::RParent {
closed += 1;
}
if closed == opened && opened != 0 && closed != 0 && p.peek().kind() != SyntaxKind::RParent
{
p.error("Parse error: `)` or `,`");
return false;
}
p.consume();
}
if p.peek().as_str() != "struct" && p.peek().as_str() != "export" {
p.error("Parse error: expected `struct` or `export`");
return false;
}
true
}

View file

@ -139,6 +139,7 @@ fn compile_path_from_string_literal(
.collect(),
name: Some("Point".into()),
node: None,
rust_attributes: None,
};
let mut points = Vec::new();

View file

@ -969,6 +969,7 @@ impl Expression {
fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
name: None,
node: None,
rust_attributes: None,
};
Expression::Struct { ty, values }
}
@ -1030,8 +1031,14 @@ impl Expression {
fields: mut result_fields,
name: result_name,
node: result_node,
rust_attributes,
},
Type::Struct {
fields: elem_fields,
name: elem_name,
node: elem_node,
rust_attributes: deriven,
},
Type::Struct { fields: elem_fields, name: elem_name, node: elem_node },
) => {
for (elem_name, elem_ty) in elem_fields.into_iter() {
match result_fields.entry(elem_name) {
@ -1052,6 +1059,7 @@ impl Expression {
name: result_name.or(elem_name),
fields: result_fields,
node: result_node.or(elem_node),
rust_attributes: rust_attributes.or(deriven),
}
}
(Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,

View file

@ -355,6 +355,7 @@ fn add_highlight_items(doc: &Document) {
.collect(),
name: None,
node: None,
rust_attributes: None,
}
.into(),
),