From 7b7cc19f9f89d5f638444073e40d61ee203098ee Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 21 Jul 2023 11:53:26 +0200 Subject: [PATCH] Allow lifetime parameters in cxx_qt::Constructor --- .../src/generator/cpp/constructor.rs | 3 + .../src/generator/rust/constructor.rs | 207 ++++++++++++++---- crates/cxx-qt-gen/src/parser/constructor.rs | 56 ++++- crates/cxx-qt-gen/src/syntax/lifetimes.rs | 152 +++++++++++++ crates/cxx-qt-gen/src/syntax/mod.rs | 1 + crates/cxx-qt-gen/test_inputs/invokables.rs | 8 +- crates/cxx-qt-gen/test_outputs/invokables.cpp | 21 +- crates/cxx-qt-gen/test_outputs/invokables.h | 5 +- crates/cxx-qt-gen/test_outputs/invokables.rs | 117 ++++++++-- examples/qml_features/rust/src/signals.rs | 28 +++ 10 files changed, 517 insertions(+), 81 deletions(-) create mode 100644 crates/cxx-qt-gen/src/syntax/lifetimes.rs diff --git a/crates/cxx-qt-gen/src/generator/cpp/constructor.rs b/crates/cxx-qt-gen/src/generator/cpp/constructor.rs index 4006b0d77..715827819 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/constructor.rs @@ -153,6 +153,8 @@ mod tests { base_arguments: vec![], new_arguments: vec![], initialize_arguments: vec![], + lifetime: None, + // dummy impl imp: parse_quote! { impl X {} }, } } @@ -275,6 +277,7 @@ mod tests { new_arguments: vec![parse_quote! { i16}, parse_quote! { i32 }], initialize_arguments: vec![parse_quote! { i32 }, parse_quote! { i64 }], base_arguments: vec![parse_quote! { i64 }, parse_quote! { *mut QObject }], + lifetime: Some(parse_quote! { 'a_lifetime }), ..mock_constructor() }], &["initializer".to_string()], diff --git a/crates/cxx-qt-gen/src/generator/rust/constructor.rs b/crates/cxx-qt-gen/src/generator/rust/constructor.rs index c6e153b0a..06158f7b3 100644 --- a/crates/cxx-qt-gen/src/generator/rust/constructor.rs +++ b/crates/cxx-qt-gen/src/generator/rust/constructor.rs @@ -15,14 +15,15 @@ use crate::{ }, }, parser::constructor::Constructor, + syntax::lifetimes, }; use convert_case::{Case, Casing}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ - parse_quote, parse_quote_spanned, spanned::Spanned, Expr, FnArg, Ident, Item, Path, Result, - Type, + parse_quote, parse_quote_spanned, spanned::Spanned, Error, Expr, FnArg, Ident, Item, Lifetime, + Path, Result, Type, }; const CONSTRUCTOR_ARGUMENTS: &str = "CxxQtConstructorArguments"; @@ -97,6 +98,7 @@ fn generate_default_constructor( fn generate_arguments_struct( namespace_internals: &str, struct_name: &CombinedIdent, + lifetime: &Option, argument_list: &[Type], ) -> Item { let argument_members = argument_members(argument_list); @@ -112,7 +114,7 @@ fn generate_arguments_struct( #[namespace = #namespace_internals] #[cxx_name = #cxx_name] #[doc(hidden)] - struct #rust_name { + struct #rust_name #lifetime { #(#argument_members,)* #not_empty // Make sure there's always at least one struct member, as CXX // doesn't support empty shared structs. @@ -140,6 +142,43 @@ fn generate_arguments_initialization( } } +fn lifetime_of_arguments( + lifetime: &Option, + argument_list: &[Type], +) -> Result> { + if let Some(lifetime) = lifetime.as_ref() { + let argument_lifetimes: Vec = argument_list + .iter() + .map(lifetimes::from_type) + .collect::>>>()? + .into_iter() + .flatten() + .collect(); + + if argument_lifetimes.is_empty() { + Ok(None) + } else { + // Any of the argument lifetimes may be different from the lifetime passed to the constructor. + // However, then the compiler will just generate an appropriate error, as the lifetime has the wrong name. + Ok(Some(quote! { + < #lifetime > + })) + } + } else { + // If there's a lifetime in the arguments, we can just let the compiler error on us. + // The error message will be reasonably nice. + Ok(None) + } +} + +fn unsafe_if(condition: bool) -> Option { + if condition { + Some(quote! { unsafe }) + } else { + None + } +} + pub fn generate( constructors: &[Constructor], qobject_idents: &QObjectName, @@ -163,13 +202,50 @@ pub fn generate( let rust_struct_name_rust = &qobject_idents.rust_struct.rust; for (index, constructor) in constructors.iter().enumerate() { - let arguments_rust = format_ident!("{CONSTRUCTOR_ARGUMENTS}{qobject_name}{index}"); + let lifetime = constructor.lifetime.as_ref().map(|lifetime| { + quote! { + < #lifetime > + } + }); + let arguments_lifetime = + lifetime_of_arguments(&constructor.lifetime, &constructor.arguments)?; + let base_lifetime = + lifetime_of_arguments(&constructor.lifetime, &constructor.base_arguments)?; + let new_lifetime = + lifetime_of_arguments(&constructor.lifetime, &constructor.new_arguments)?; + let initialize_lifetime = + lifetime_of_arguments(&constructor.lifetime, &constructor.initialize_arguments)?; + let args_tuple_lifetime = base_lifetime + .clone() + .or(new_lifetime.clone()) + .or(initialize_lifetime.clone()); + + // If there is a lifetime declared, but it's not used, The compiler will catch this, + // but the error message is so obscure, that we should catch it here with a better error + // message. + if lifetime.is_some() + && [ + &arguments_lifetime, + &base_lifetime, + &new_lifetime, + &initialize_lifetime, + ] + .into_iter() + .all(Option::is_none) + { + return Err(Error::new_spanned( + &constructor.lifetime, + "this lifetime isn't used in the Constructor declaration!", + )); + } + + let args_tuple_rust = format_ident!("{CONSTRUCTOR_ARGUMENTS}{qobject_name}{index}"); let base_arguments_rust = format_ident!("{BASE_ARGUMENTS}{qobject_name}{index}"); let new_arguments_rust = format_ident!("{NEW_ARGUMENTS}{qobject_name}{index}"); let initialize_arguments_rust = format_ident!("{INITIALIZE_ARGUMENTS}{qobject_name}{index}"); - let arguments_cxx = format!("{CONSTRUCTOR_ARGUMENTS}{index}"); + let args_tuple_cxx = format!("{CONSTRUCTOR_ARGUMENTS}{index}"); let base_arguments_cxx = format_ident!("{BASE_ARGUMENTS}{index}"); let new_arguments_cxx = format_ident!("{NEW_ARGUMENTS}{index}"); let initialize_arguments_cxx = format_ident!("{INITIALIZE_ARGUMENTS}{index}"); @@ -181,7 +257,7 @@ pub fn generate( let initialize_cxx = format!("initialize{index}"); let route_arguments_rust = format_ident!("route_arguments_{qobject_name_snake}_{index}"); - let route_arguemnts_cxx = format!("routeArguments{index}"); + let route_arguments_cxx = format!("routeArguments{index}"); let argument_types_qualified: Vec = constructor .arguments @@ -209,15 +285,24 @@ pub fn generate( parameter }) .collect(); - let route_arguments_safety = if constructor - .arguments - .iter() - .any(syn_type_is_cxx_bridge_unsafe) - { - quote! { unsafe } - } else { - quote! {} - }; + + // As the function implementations cast to `Constructor<(Args)>`, these `Args` may + // include the lifetime. + let new_function_lifetime = new_lifetime.clone().or(arguments_lifetime.clone()); + let initialize_function_lifetime = + initialize_lifetime.clone().or(arguments_lifetime.clone()); + let route_function_decl_lifetime = + arguments_lifetime.clone().or(args_tuple_lifetime.clone()); + + let route_arguments_safety = unsafe_if( + lifetime.is_some() + || constructor + .arguments + .iter() + .any(syn_type_is_cxx_bridge_unsafe), + ); + let new_safety = unsafe_if(new_function_lifetime.is_some()); + let initialize_safety = unsafe_if(initialize_function_lifetime.is_some()); let assign_arguments = constructor .arguments @@ -258,49 +343,52 @@ pub fn generate( result.cxx_mod_contents.append(&mut vec![ parse_quote! { #[namespace = #namespace_internals] - #[cxx_name = #arguments_cxx] + #[cxx_name = #args_tuple_cxx] #[doc(hidden)] - struct #arguments_rust { - base: #base_arguments_rust, + struct #args_tuple_rust #args_tuple_lifetime { + base: #base_arguments_rust #base_lifetime, // new a keyword in C++, so we use `new_` here #[cxx_name = "new_"] - new: #new_arguments_rust, - initialize: #initialize_arguments_rust, + new: #new_arguments_rust #new_lifetime, + initialize: #initialize_arguments_rust #initialize_lifetime, } }, generate_arguments_struct(&namespace.internal, &CombinedIdent { cpp: base_arguments_cxx.clone(), rust: base_arguments_rust.clone(), - }, &constructor.base_arguments), + }, &base_lifetime, &constructor.base_arguments), generate_arguments_struct(&namespace.internal, &CombinedIdent { cpp: new_arguments_cxx.clone(), rust: new_arguments_rust.clone(), - }, &constructor.new_arguments), + }, &new_lifetime, &constructor.new_arguments), generate_arguments_struct(&namespace.internal, &CombinedIdent { cpp: initialize_arguments_cxx.clone(), rust: initialize_arguments_rust.clone(), - }, &constructor.initialize_arguments), + }, &initialize_lifetime, &constructor.initialize_arguments), parse_quote! { extern "Rust" { #[namespace = #namespace_internals] - #[cxx_name = #route_arguemnts_cxx] + #[cxx_name = #route_arguments_cxx] // This function may need to be marked unsafe, as some arguments may be pointers. - #route_arguments_safety fn #route_arguments_rust(#(#route_arguments_parameters),*) -> #arguments_rust; + #route_arguments_safety fn #route_arguments_rust #route_function_decl_lifetime (#(#route_arguments_parameters),*) -> #args_tuple_rust #args_tuple_lifetime; #[namespace = #namespace_internals] #[cxx_name = #new_cxx] - fn #new_rust(args: #new_arguments_rust) -> Box<#rust_struct_name_rust>; + #new_safety fn #new_rust #new_lifetime (args: #new_arguments_rust #new_lifetime) -> Box<#rust_struct_name_rust>; #[namespace = #namespace_internals] #[cxx_name = #initialize_cxx] - fn #initialize_rust(qobject: Pin<&mut #qobject_name_rust>, args: #initialize_arguments_rust); + #initialize_safety fn #initialize_rust #initialize_lifetime (qobject: Pin<&mut #qobject_name_rust>, args: #initialize_arguments_rust #initialize_lifetime); } }, ]); result.cxx_qt_mod_contents.append(&mut vec![parse_quote_spanned! { constructor.imp.span() => #[doc(hidden)] - pub fn #route_arguments_rust(#(#route_arguments_parameter_qualified),*) -> #module_ident::#arguments_rust { + // Use the catch-all lifetime here, as if a lifetime argument is specified, it should + // be used in either the argument list itself, or the returned, routed arguments. + // So it must be used by this function somewhere. + pub fn #route_arguments_rust #lifetime(#(#route_arguments_parameter_qualified),*) -> #module_ident::#args_tuple_rust #args_tuple_lifetime { // These variables won't be used if the corresponding argument list is empty. #[allow(unused_variables)] #[allow(clippy::let_unit_value)] @@ -310,7 +398,7 @@ pub fn generate( initialize_arguments ) = <#qobject_name_rust_qualified as cxx_qt::Constructor<(#(#argument_types_qualified,)*)>> ::route_arguments((#(#assign_arguments,)*)); - #module_ident::#arguments_rust { + #module_ident::#args_tuple_rust { base: #module_ident::#init_base_arguments, initialize: #module_ident::#init_initalize_arguments, new: #module_ident::#init_new_arguments, @@ -321,7 +409,12 @@ pub fn generate( constructor.imp.span() => #[doc(hidden)] #[allow(unused_variables)] - pub fn #new_rust(new_arguments: #module_ident::#new_arguments_rust) -> std::boxed::Box<#rust_struct_name_rust> { + #[allow(clippy::extra_unused_lifetimes)] + // If we use the lifetime here for casting to the specific Constructor type, then + // clippy for some reason thinks that the lifetime is unused even though it is used + // by the `as` expression. + // So let's just allow unused extra lifetimes for this function. + pub fn #new_rust #new_function_lifetime(new_arguments: #module_ident::#new_arguments_rust #new_lifetime) -> std::boxed::Box<#rust_struct_name_rust> { std::boxed::Box::new(<#qobject_name_rust_qualified as cxx_qt::Constructor<(#(#argument_types_qualified,)*)>>::new((#(#extract_new_arguments,)*))) } }, @@ -329,9 +422,14 @@ pub fn generate( constructor.imp.span() => #[doc(hidden)] #[allow(unused_variables)] - pub fn #initialize_rust( + #[allow(clippy::extra_unused_lifetimes)] + // If we use the lifetime here for casting to the specific Constructor type, then + // clippy for some reason thinks that the lifetime is unused even though it is used + // by the `as` expression. + // So let's just allow unused extra lifetimes for this function. + pub fn #initialize_rust #initialize_function_lifetime( qobject: core::pin::Pin<&mut #qobject_name_rust_qualified>, - initialize_arguments: #module_ident::#initialize_arguments_rust + initialize_arguments: #module_ident::#initialize_arguments_rust #initialize_lifetime ) { <#qobject_name_rust_qualified as cxx_qt::Constructor<(#(#argument_types_qualified,)*)>>::initialize( qobject, @@ -355,6 +453,7 @@ mod tests { base_arguments: vec![], initialize_arguments: vec![], arguments: vec![], + lifetime: None, // dummy impl for testing imp: parse_quote! {impl X {}}, } @@ -506,6 +605,7 @@ mod tests { quote! { #[doc(hidden)] #[allow(unused_variables)] + #[allow(clippy::extra_unused_lifetimes)] pub fn new_rs_my_object_0(new_arguments: ffi::CxxQtConstructorNewArgumentsMyObject0) -> std::boxed::Box { std::boxed::Box::new( >::new(()) @@ -518,6 +618,7 @@ mod tests { quote! { #[doc(hidden)] #[allow(unused_variables)] + #[allow(clippy::extra_unused_lifetimes)] pub fn initialize_my_object_0( qobject: core::pin::Pin<&mut MyObject>, initialize_arguments: ffi::CxxQtConstructorInitializeArgumentsMyObject0) @@ -536,11 +637,11 @@ mod tests { #namespace_attr #[cxx_name = "CxxQtConstructorArguments1"] #[doc(hidden)] - struct CxxQtConstructorArgumentsMyObject1 { + struct CxxQtConstructorArgumentsMyObject1<'lifetime> { base: CxxQtConstructorBaseArgumentsMyObject1, #[cxx_name="new_"] new: CxxQtConstructorNewArgumentsMyObject1, - initialize : CxxQtConstructorInitializeArgumentsMyObject1, + initialize : CxxQtConstructorInitializeArgumentsMyObject1<'lifetime>, } }, ); @@ -575,9 +676,9 @@ mod tests { #namespace_attr #[cxx_name="CxxQtConstructorInitializeArguments1"] #[doc(hidden)] - struct CxxQtConstructorInitializeArgumentsMyObject1 { + struct CxxQtConstructorInitializeArgumentsMyObject1<'lifetime> { arg0: i32, - arg1: i64, + arg1: &'lifetime QString, } }, ); @@ -587,7 +688,7 @@ mod tests { extern "Rust" { #namespace_attr #[cxx_name = "routeArguments1"] - unsafe fn route_arguments_my_object_1(arg0: *const QObject) -> CxxQtConstructorArgumentsMyObject1; + unsafe fn route_arguments_my_object_1<'lifetime>(arg0: *const QObject) -> CxxQtConstructorArgumentsMyObject1<'lifetime>; #namespace_attr #[cxx_name = "newRs1"] @@ -595,7 +696,7 @@ mod tests { #namespace_attr #[cxx_name = "initialize1"] - fn initialize_my_object_1(qobject: Pin<&mut MyObject>, args: CxxQtConstructorInitializeArgumentsMyObject1); + unsafe fn initialize_my_object_1<'lifetime>(qobject: Pin<&mut MyObject>, args: CxxQtConstructorInitializeArgumentsMyObject1<'lifetime>); } }, ); @@ -604,7 +705,7 @@ mod tests { &blocks.cxx_qt_mod_contents[3], quote! { #[doc(hidden)] - pub fn route_arguments_my_object_1(arg0: *const QObject) -> ffi::CxxQtConstructorArgumentsMyObject1 + pub fn route_arguments_my_object_1<'lifetime>(arg0: *const QObject) -> ffi::CxxQtConstructorArgumentsMyObject1<'lifetime> { #[allow(unused_variables)] #[allow(clippy::let_unit_value)] @@ -633,6 +734,7 @@ mod tests { quote! { #[doc(hidden)] #[allow(unused_variables)] + #[allow(clippy::extra_unused_lifetimes)] pub fn new_rs_my_object_1(new_arguments: ffi::CxxQtConstructorNewArgumentsMyObject1) -> std::boxed::Box { std::boxed::Box::new( >::new( @@ -645,9 +747,10 @@ mod tests { quote! { #[doc(hidden)] #[allow(unused_variables)] - pub fn initialize_my_object_1( + #[allow(clippy::extra_unused_lifetimes)] + pub fn initialize_my_object_1<'lifetime>( qobject: core::pin::Pin<&mut MyObject>, - initialize_arguments: ffi::CxxQtConstructorInitializeArgumentsMyObject1) + initialize_arguments: ffi::CxxQtConstructorInitializeArgumentsMyObject1<'lifetime>) { >::initialize( qobject, @@ -664,12 +767,16 @@ mod tests { Constructor { arguments: vec![parse_quote! { *const QObject }], new_arguments: vec![parse_quote! { i16 }], - initialize_arguments: vec![parse_quote! { i32 }, parse_quote! { i64 }], + initialize_arguments: vec![ + parse_quote! { i32 }, + parse_quote! { &'lifetime QString }, + ], base_arguments: vec![ parse_quote! { i64 }, parse_quote! { *mut QObject }, parse_quote! { f32 }, ], + lifetime: Some(parse_quote! { 'lifetime }), ..mock_constructor() }, ]); @@ -685,4 +792,20 @@ mod tests { assert_full_constructor_blocks(&blocks, &namespace_attr); } + + #[test] + fn constructor_impl_with_unused_lifetime() { + let result = super::generate( + &[Constructor { + lifetime: Some(parse_quote! { 'a }), + ..mock_constructor() + }], + &mock_name(), + &mock_namespace(), + &BTreeMap::::default(), + &format_ident!("ffi"), + ); + + assert!(result.is_err()); + } } diff --git a/crates/cxx-qt-gen/src/parser/constructor.rs b/crates/cxx-qt-gen/src/parser/constructor.rs index a7f22f559..dbf651359 100644 --- a/crates/cxx-qt-gen/src/parser/constructor.rs +++ b/crates/cxx-qt-gen/src/parser/constructor.rs @@ -3,8 +3,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 use syn::{ - spanned::Spanned, AngleBracketedGenericArguments, Error, GenericArgument, ItemImpl, Path, - PathArguments, PathSegment, Result, Type, + spanned::Spanned, AngleBracketedGenericArguments, Error, GenericArgument, GenericParam, + Generics, ItemImpl, Lifetime, Path, PathArguments, PathSegment, Result, Type, }; #[derive(Default)] @@ -33,6 +33,9 @@ pub struct Constructor { /// The `initialize` function is run after the QObject is created. pub initialize_arguments: Vec, + // The lifetime argument of the impl block. + pub lifetime: Option, + /// The original impl that this constructor was parse from. pub imp: ItemImpl, } @@ -120,18 +123,30 @@ impl Constructor { } } - pub fn parse(imp: ItemImpl) -> Result { - if let Some(unsafety) = imp.unsafety { + pub fn parse_impl_generics(generics: &Generics) -> Result> { + if generics.where_clause.is_some() { return Err(Error::new_spanned( - unsafety, - "Unnecessary unsafe around constructor impl.", + &generics.where_clause, + "Where clauses are not allowed on cxx_qt::Constructor impls!", )); } - if !imp.generics.params.is_empty() { + let parameters: Vec<_> = generics.params.iter().collect(); + match *parameters { + [] => Ok(None), + [GenericParam::Lifetime(lifetime)] => Ok(Some(lifetime.lifetime.clone())), + _ => Err(Error::new_spanned( + generics, + "Only a single lifetime parameter is allowed on cxx_qt::Constructor impls!", + )), + } + } + + pub fn parse(imp: ItemImpl) -> Result { + if let Some(unsafety) = imp.unsafety { return Err(Error::new_spanned( - imp.generics.params, - "Generics are not allowed on cxx_qt::Constructor impls!", + unsafety, + "Unnecessary unsafe around constructor impl.", )); } @@ -142,6 +157,8 @@ impl Constructor { )); } + let lifetime = Self::parse_impl_generics(&imp.generics)?; + let (_, trait_path, _) = &imp .trait_ .as_ref() @@ -153,6 +170,7 @@ impl Constructor { new_arguments: arguments.new.unwrap_or_default(), base_arguments: arguments.base.unwrap_or_default(), initialize_arguments: arguments.initialize.unwrap_or_default(), + lifetime, imp, }) } @@ -204,9 +222,9 @@ mod tests { // TODO This should be allowed at some point if the lifetime is actually used. assert_parse_error( parse_quote! { - impl<'a> cxx_qt::Constructor<()> for T {} + impl<'a, 'b> cxx_qt::Constructor<()> for T {} }, - "lifetime on impl block", + "multiple lifetimes on impl block", ); assert_parse_error( @@ -245,6 +263,7 @@ mod tests { assert!(constructor.new_arguments.is_empty()); assert!(constructor.base_arguments.is_empty()); assert!(constructor.initialize_arguments.is_empty()); + assert!(constructor.lifetime.is_none()); } #[test] @@ -266,5 +285,20 @@ mod tests { ); assert!(constructor.initialize_arguments.is_empty()); assert_eq!(constructor.base_arguments, vec![parse_quote!(i64)]); + assert!(constructor.lifetime.is_none()); + } + + #[test] + fn parse_generic_lifetime() { + let constructor = Constructor::parse(parse_quote! { + impl<'my_lifetime> cxx_qt::Constructor<()> for X {} + }) + .unwrap(); + + assert!(constructor.arguments.is_empty()); + assert!(constructor.base_arguments.is_empty()); + assert!(constructor.initialize_arguments.is_empty()); + assert!(constructor.new_arguments.is_empty()); + assert_eq!(constructor.lifetime, Some(parse_quote! { 'my_lifetime })); } } diff --git a/crates/cxx-qt-gen/src/syntax/lifetimes.rs b/crates/cxx-qt-gen/src/syntax/lifetimes.rs new file mode 100644 index 000000000..bd70bdde5 --- /dev/null +++ b/crates/cxx-qt-gen/src/syntax/lifetimes.rs @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Leon Matthes +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use syn::{Error, GenericArgument, Lifetime, PathArguments, PathSegment, Result, Type}; + +fn err_unsupported_type(ty: &T) -> Error { + Error::new_spanned(ty, "Type not supported by CXX-Qt!") +} + +fn from_generic_argument(argument: &GenericArgument) -> Result> { + match argument { + GenericArgument::Lifetime(lifetime) => Ok(vec![lifetime.clone()]), + GenericArgument::Type(ty) => from_type(ty), + _ => Err(err_unsupported_type(argument)), + } +} + +fn from_pathsegment(segment: &PathSegment) -> Result> { + match segment.arguments { + PathArguments::None => Ok(vec![]), + PathArguments::AngleBracketed(ref angles) => Ok(angles + .args + .iter() + .map(from_generic_argument) + .collect::>>>()? + .into_iter() + .flatten() + .collect()), + PathArguments::Parenthesized(ref parens) => Ok(parens + .inputs + .iter() + .map(from_type) + .collect::>>>()? + .into_iter() + .flatten() + .chain( + if let syn::ReturnType::Type(_arrow, ref return_ty) = parens.output { + from_type(return_ty)? + } else { + vec![] + }, + ) + .collect()), + } +} + +pub fn from_type(ty: &Type) -> Result> { + match ty { + Type::Array(array) => from_type(&array.elem), + Type::Group(group) => from_type(&group.elem), + Type::Paren(paren) => from_type(&paren.elem), + Type::Ptr(pointer) => from_type(&pointer.elem), + Type::Slice(slice) => from_type(&slice.elem), + Type::Path(path) => { + if path.qself.is_some() { + Err(err_unsupported_type(ty)) + } else { + Ok(path + .path + .segments + .iter() + .map(from_pathsegment) + .collect::>>>()? + .into_iter() + .flatten() + .collect()) + } + } + Type::Reference(reference) => Ok(from_type(&reference.elem)? + .into_iter() + .chain(reference.lifetime.clone()) + .collect()), + Type::Tuple(tuple) => Ok(tuple + .elems + .iter() + .map(from_type) + .collect::>>>()? + .into_iter() + .flatten() + .collect()), + _ => Err(err_unsupported_type(ty)), + } +} + +#[cfg(test)] +mod tests { + use syn::parse_quote; + + macro_rules! assert_no_lifetimes { + ($($tt:tt)*) => { + assert!(super::from_type(&parse_quote! { $($tt)* }) + .unwrap() + .is_empty()); + }; + } + + #[test] + fn extract_no_lifetimes() { + assert_no_lifetimes! { () }; + assert_no_lifetimes! { T }; + assert_no_lifetimes! { T }; + assert_no_lifetimes! { *mut X }; + assert_no_lifetimes! { *const X }; + assert_no_lifetimes! { Pin<*mut T> }; + assert_no_lifetimes! { &T }; + assert_no_lifetimes! { &mut T }; + assert_no_lifetimes! { [T] }; + assert_no_lifetimes! { [T;4] }; + assert_no_lifetimes! { (X, Y) }; + } + + macro_rules! assert_lifetimes { + ([$($lifetime:lifetime),*] $($tt:tt)*) => { + assert_eq!( + super::from_type(&parse_quote! { $($tt)* }).unwrap(), + vec![$(parse_quote! { $lifetime }),*] + ); + } + } + + #[test] + fn assert_lifetimes() { + assert_lifetimes! { ['a] &'a T }; + assert_lifetimes! { ['a] [&'a T] }; + assert_lifetimes! { ['a] [&'a T;5] }; + + assert_lifetimes! { ['a, 'a] (&'a A, &'a mut B) }; + assert_lifetimes! { ['a, 'a] &'a A<'a> }; + + assert_lifetimes! { ['a, 'b] &'b &'a mut T }; + assert_lifetimes! { ['a, 'b] Pin<&'a X, &'b mut Y> }; + assert_lifetimes! { ['a, 'b] (&'a A, &'b mut B) }; + + assert_lifetimes! { ['lifetime] A<'lifetime> }; + } + + macro_rules! assert_unsupported_type { + ($( $tt:tt )*) => { + assert!(super::from_type(&parse_quote! { $($tt)* }).is_err()); + }; + } + + #[test] + fn extract_lifetimes_unsupported_types() { + assert_unsupported_type! { dyn Foo }; + assert_unsupported_type! { &dyn Foo }; + assert_unsupported_type! { fn(A) }; + assert_unsupported_type! { fn(i64) -> i32 }; + } +} diff --git a/crates/cxx-qt-gen/src/syntax/mod.rs b/crates/cxx-qt-gen/src/syntax/mod.rs index d90bb538e..9677ae0ca 100644 --- a/crates/cxx-qt-gen/src/syntax/mod.rs +++ b/crates/cxx-qt-gen/src/syntax/mod.rs @@ -6,6 +6,7 @@ pub mod attribute; pub mod expr; pub mod foreignmod; +pub mod lifetimes; pub mod path; mod qtfile; mod qtitem; diff --git a/crates/cxx-qt-gen/test_inputs/invokables.rs b/crates/cxx-qt-gen/test_inputs/invokables.rs index de6aee48a..bbbe69475 100644 --- a/crates/cxx-qt-gen/test_inputs/invokables.rs +++ b/crates/cxx-qt-gen/test_inputs/invokables.rs @@ -52,12 +52,14 @@ mod ffi { impl cxx_qt::Threading for MyObject {} - impl + impl<'a> cxx_qt::Constructor< - (i32, *mut QObject), + (i32, &'a QString), BaseArguments = (*mut QObject,), - NewArguments = (i32,), + NewArguments = (&'a QString,), > for MyObject { } + + impl cxx_qt::Constructor<()> for MyObject {} } diff --git a/crates/cxx-qt-gen/test_outputs/invokables.cpp b/crates/cxx-qt-gen/test_outputs/invokables.cpp index 4c8147116..f8b8ae80f 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.cpp +++ b/crates/cxx-qt-gen/test_outputs/invokables.cpp @@ -110,13 +110,18 @@ MyObject::qtThread() const return MyObjectCxxQtThread(m_cxxQtThreadObj, m_rustObjMutex); } -MyObject::MyObject(::std::int32_t arg0, QObject* arg1) +MyObject::MyObject(::std::int32_t arg0, QString const& arg1) : MyObject( ::cxx_qt::my_object::cxx_qt_my_object::routeArguments0(::std::move(arg0), ::std::move(arg1))) { } +MyObject::MyObject() + : MyObject(::cxx_qt::my_object::cxx_qt_my_object::routeArguments1()) +{ +} + MyObject::MyObject( ::cxx_qt::my_object::cxx_qt_my_object::CxxQtConstructorArguments0&& args) : QObject(::std::move(args.base.arg0)) @@ -131,4 +136,18 @@ MyObject::MyObject( *this, ::std::move(args.initialize)); } +MyObject::MyObject( + ::cxx_qt::my_object::cxx_qt_my_object::CxxQtConstructorArguments1&& args) + : QObject() + , m_rustObj( + ::cxx_qt::my_object::cxx_qt_my_object::newRs1(::std::move(args.new_))) + , m_rustObjMutex(::std::make_shared<::std::recursive_mutex>()) + , m_cxxQtThreadObj( + ::std::make_shared<::rust::cxxqtlib1::CxxQtGuardedPointer>( + this)) +{ + ::cxx_qt::my_object::cxx_qt_my_object::initialize1( + *this, ::std::move(args.initialize)); +} + } // namespace cxx_qt::my_object diff --git a/crates/cxx-qt-gen/test_outputs/invokables.h b/crates/cxx-qt-gen/test_outputs/invokables.h index f42910e82..5d7fdf46b 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.h +++ b/crates/cxx-qt-gen/test_outputs/invokables.h @@ -40,7 +40,8 @@ class MyObject : public QObject Q_INVOKABLE void invokableResultTuple() const; Q_INVOKABLE ::rust::String invokableResultType() const; MyObjectCxxQtThread qtThread() const; - explicit MyObject(::std::int32_t arg0, QObject* arg1); + explicit MyObject(::std::int32_t arg0, QString const& arg1); + explicit MyObject(); private: void cppMethodWrapper() const noexcept; @@ -58,6 +59,8 @@ class MyObject : public QObject ::rust::String invokableResultTypeWrapper() const; explicit MyObject( ::cxx_qt::my_object::cxx_qt_my_object::CxxQtConstructorArguments0&& args); + explicit MyObject( + ::cxx_qt::my_object::cxx_qt_my_object::CxxQtConstructorArguments1&& args); private: ::rust::Box m_rustObj; diff --git a/crates/cxx-qt-gen/test_outputs/invokables.rs b/crates/cxx-qt-gen/test_outputs/invokables.rs index 3410b285f..9c41fc94f 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.rs +++ b/crates/cxx-qt-gen/test_outputs/invokables.rs @@ -125,10 +125,10 @@ mod ffi { #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] #[cxx_name = "CxxQtConstructorArguments0"] #[doc(hidden)] - struct CxxQtConstructorArgumentsMyObject0 { + struct CxxQtConstructorArgumentsMyObject0<'a> { base: CxxQtConstructorBaseArgumentsMyObject0, #[cxx_name = "new_"] - new: CxxQtConstructorNewArgumentsMyObject0, + new: CxxQtConstructorNewArgumentsMyObject0<'a>, initialize: CxxQtConstructorInitializeArgumentsMyObject0, } #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] @@ -140,8 +140,8 @@ mod ffi { #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] #[cxx_name = "CxxQtConstructorNewArguments0"] #[doc(hidden)] - struct CxxQtConstructorNewArgumentsMyObject0 { - arg0: i32, + struct CxxQtConstructorNewArgumentsMyObject0<'a> { + arg0: &'a QString, } #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] #[cxx_name = "CxxQtConstructorInitializeArguments0"] @@ -152,20 +152,63 @@ mod ffi { extern "Rust" { #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] #[cxx_name = "routeArguments0"] - unsafe fn route_arguments_my_object_0( + unsafe fn route_arguments_my_object_0<'a>( arg0: i32, - arg1: *mut QObject, - ) -> CxxQtConstructorArgumentsMyObject0; + arg1: &'a QString, + ) -> CxxQtConstructorArgumentsMyObject0<'a>; #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] #[cxx_name = "newRs0"] - fn new_rs_my_object_0(args: CxxQtConstructorNewArgumentsMyObject0) -> Box; + unsafe fn new_rs_my_object_0<'a>( + args: CxxQtConstructorNewArgumentsMyObject0<'a>, + ) -> Box; #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] #[cxx_name = "initialize0"] - fn initialize_my_object_0( + unsafe fn initialize_my_object_0( qobject: Pin<&mut MyObject>, args: CxxQtConstructorInitializeArgumentsMyObject0, ); } + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "CxxQtConstructorArguments1"] + #[doc(hidden)] + struct CxxQtConstructorArgumentsMyObject1 { + base: CxxQtConstructorBaseArgumentsMyObject1, + #[cxx_name = "new_"] + new: CxxQtConstructorNewArgumentsMyObject1, + initialize: CxxQtConstructorInitializeArgumentsMyObject1, + } + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "CxxQtConstructorBaseArguments1"] + #[doc(hidden)] + struct CxxQtConstructorBaseArgumentsMyObject1 { + not_empty: i8, + } + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "CxxQtConstructorNewArguments1"] + #[doc(hidden)] + struct CxxQtConstructorNewArgumentsMyObject1 { + not_empty: i8, + } + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "CxxQtConstructorInitializeArguments1"] + #[doc(hidden)] + struct CxxQtConstructorInitializeArgumentsMyObject1 { + not_empty: i8, + } + extern "Rust" { + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "routeArguments1"] + fn route_arguments_my_object_1() -> CxxQtConstructorArgumentsMyObject1; + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "newRs1"] + fn new_rs_my_object_1(args: CxxQtConstructorNewArgumentsMyObject1) -> Box; + #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] + #[cxx_name = "initialize1"] + fn initialize_my_object_1( + qobject: Pin<&mut MyObject>, + args: CxxQtConstructorInitializeArgumentsMyObject1, + ); + } unsafe extern "C++" { #[cxx_name = "unsafeRust"] #[doc(hidden)] @@ -220,16 +263,14 @@ pub struct MyObjectCxxQtThreadQueuedFn { } impl cxx_qt::Locking for ffi::MyObject {} #[doc(hidden)] -pub fn route_arguments_my_object_0( +pub fn route_arguments_my_object_0<'a>( arg0: i32, - arg1: *mut ffi::QObject, -) -> ffi::CxxQtConstructorArgumentsMyObject0 { + arg1: &'a QString, +) -> ffi::CxxQtConstructorArgumentsMyObject0<'a> { #[allow(unused_variables)] #[allow(clippy::let_unit_value)] let (new_arguments, base_arguments, initialize_arguments) = - >::route_arguments(( - arg0, arg1, - )); + >::route_arguments((arg0, arg1)); ffi::CxxQtConstructorArgumentsMyObject0 { base: ffi::CxxQtConstructorBaseArgumentsMyObject0 { arg0: base_arguments.0, @@ -242,21 +283,51 @@ pub fn route_arguments_my_object_0( } #[doc(hidden)] #[allow(unused_variables)] -pub fn new_rs_my_object_0( - new_arguments: ffi::CxxQtConstructorNewArgumentsMyObject0, +#[allow(clippy::extra_unused_lifetimes)] +pub fn new_rs_my_object_0<'a>( + new_arguments: ffi::CxxQtConstructorNewArgumentsMyObject0<'a>, ) -> std::boxed::Box { - std::boxed::Box::new(>::new((new_arguments.arg0,))) + std::boxed::Box::new( + >::new((new_arguments.arg0,)), + ) } #[doc(hidden)] #[allow(unused_variables)] -pub fn initialize_my_object_0( +#[allow(clippy::extra_unused_lifetimes)] +pub fn initialize_my_object_0<'a>( qobject: core::pin::Pin<&mut ffi::MyObject>, initialize_arguments: ffi::CxxQtConstructorInitializeArgumentsMyObject0, ) { - >::initialize(qobject, ()); + >::initialize(qobject, ()); +} +#[doc(hidden)] +pub fn route_arguments_my_object_1() -> ffi::CxxQtConstructorArgumentsMyObject1 { + #[allow(unused_variables)] + #[allow(clippy::let_unit_value)] + let (new_arguments, base_arguments, initialize_arguments) = + >::route_arguments(()); + ffi::CxxQtConstructorArgumentsMyObject1 { + base: ffi::CxxQtConstructorBaseArgumentsMyObject1 { not_empty: 0 }, + initialize: ffi::CxxQtConstructorInitializeArgumentsMyObject1 { not_empty: 0 }, + new: ffi::CxxQtConstructorNewArgumentsMyObject1 { not_empty: 0 }, + } +} +#[doc(hidden)] +#[allow(unused_variables)] +#[allow(clippy::extra_unused_lifetimes)] +pub fn new_rs_my_object_1( + new_arguments: ffi::CxxQtConstructorNewArgumentsMyObject1, +) -> std::boxed::Box { + std::boxed::Box::new(>::new(())) +} +#[doc(hidden)] +#[allow(unused_variables)] +#[allow(clippy::extra_unused_lifetimes)] +pub fn initialize_my_object_1( + qobject: core::pin::Pin<&mut ffi::MyObject>, + initialize_arguments: ffi::CxxQtConstructorInitializeArgumentsMyObject1, +) { + >::initialize(qobject, ()); } impl core::ops::Deref for ffi::MyObject { type Target = MyObjectRust; diff --git a/examples/qml_features/rust/src/signals.rs b/examples/qml_features/rust/src/signals.rs index 7a9a989e5..9290188a0 100644 --- a/examples/qml_features/rust/src/signals.rs +++ b/examples/qml_features/rust/src/signals.rs @@ -55,6 +55,8 @@ pub mod qobject { // ANCHOR_END: book_rust_obj_impl impl cxx_qt::Constructor<()> for RustSignals {} + + impl<'a> cxx_qt::Constructor<(&'a QUrl,), InitializeArguments = (&'a QUrl,)> for RustSignals {} } use core::pin::Pin; @@ -146,4 +148,30 @@ impl cxx_qt::Constructor<()> for qobject::RustSignals { .release(); } } + +impl<'a> cxx_qt::Constructor<(&'a QUrl,)> for qobject::RustSignals { + type NewArguments = (); + type BaseArguments = (); + type InitializeArguments = (&'a QUrl,); + + fn route_arguments( + (url,): (&'a QUrl,), + ) -> ( + Self::NewArguments, + Self::BaseArguments, + Self::InitializeArguments, + ) { + ((), (), (url,)) + } + + fn new(_arguments: Self::NewArguments) -> ::Rust { + Default::default() + } + + fn initialize(mut self: core::pin::Pin<&mut Self>, (url,): Self::InitializeArguments) { + >::initialize(self.as_mut(), ()); + + self.connect(url); + } +} // ANCHOR_END: book_macro_code