Skip to content

Commit

Permalink
Allow lifetime parameters in cxx_qt::Constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonMatthesKDAB authored and ahayzen-kdab committed Jul 28, 2023
1 parent 7c3c97f commit 7b7cc19
Show file tree
Hide file tree
Showing 10 changed files with 517 additions and 81 deletions.
3 changes: 3 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ mod tests {
base_arguments: vec![],
new_arguments: vec![],
initialize_arguments: vec![],
lifetime: None,
// dummy impl
imp: parse_quote! { impl X {} },
}
}
Expand Down Expand Up @@ -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()],
Expand Down
207 changes: 165 additions & 42 deletions crates/cxx-qt-gen/src/generator/rust/constructor.rs

Large diffs are not rendered by default.

56 changes: 45 additions & 11 deletions crates/cxx-qt-gen/src/parser/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -33,6 +33,9 @@ pub struct Constructor {
/// The `initialize` function is run after the QObject is created.
pub initialize_arguments: Vec<Type>,

// The lifetime argument of the impl block.
pub lifetime: Option<Lifetime>,

/// The original impl that this constructor was parse from.
pub imp: ItemImpl,
}
Expand Down Expand Up @@ -120,18 +123,30 @@ impl Constructor {
}
}

pub fn parse(imp: ItemImpl) -> Result<Self> {
if let Some(unsafety) = imp.unsafety {
pub fn parse_impl_generics(generics: &Generics) -> Result<Option<Lifetime>> {
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<Self> {
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.",
));
}

Expand All @@ -142,6 +157,8 @@ impl Constructor {
));
}

let lifetime = Self::parse_impl_generics(&imp.generics)?;

let (_, trait_path, _) = &imp
.trait_
.as_ref()
Expand All @@ -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,
})
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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]
Expand All @@ -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 }));
}
}
152 changes: 152 additions & 0 deletions crates/cxx-qt-gen/src/syntax/lifetimes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Leon Matthes <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use syn::{Error, GenericArgument, Lifetime, PathArguments, PathSegment, Result, Type};

fn err_unsupported_type<T: quote::ToTokens>(ty: &T) -> Error {
Error::new_spanned(ty, "Type not supported by CXX-Qt!")
}

fn from_generic_argument(argument: &GenericArgument) -> Result<Vec<Lifetime>> {
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<Vec<Lifetime>> {
match segment.arguments {
PathArguments::None => Ok(vec![]),
PathArguments::AngleBracketed(ref angles) => Ok(angles
.args
.iter()
.map(from_generic_argument)
.collect::<Result<Vec<Vec<Lifetime>>>>()?
.into_iter()
.flatten()
.collect()),
PathArguments::Parenthesized(ref parens) => Ok(parens
.inputs
.iter()
.map(from_type)
.collect::<Result<Vec<Vec<Lifetime>>>>()?
.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<Vec<Lifetime>> {
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::<Result<Vec<Vec<Lifetime>>>>()?
.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::<Result<Vec<Vec<Lifetime>>>>()?
.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<A> };
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 };
}
}
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
pub mod attribute;
pub mod expr;
pub mod foreignmod;
pub mod lifetimes;
pub mod path;
mod qtfile;
mod qtitem;
Expand Down
8 changes: 5 additions & 3 deletions crates/cxx-qt-gen/test_inputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
}
21 changes: 20 additions & 1 deletion crates/cxx-qt-gen/test_outputs/invokables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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<MyObject>>(
this))
{
::cxx_qt::my_object::cxx_qt_my_object::initialize1(
*this, ::std::move(args.initialize));
}

} // namespace cxx_qt::my_object
5 changes: 4 additions & 1 deletion crates/cxx-qt-gen/test_outputs/invokables.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<MyObjectRust> m_rustObj;
Expand Down
Loading

0 comments on commit 7b7cc19

Please sign in to comment.