Skip to content

Commit

Permalink
WIP: cxx-qt-gen: use extern "RustQt" block for inherit and signals
Browse files Browse the repository at this point in the history
Closes KDAB#557
  • Loading branch information
ahayzen-kdab committed Jun 7, 2023
1 parent 433a932 commit 86eb41e
Show file tree
Hide file tree
Showing 16 changed files with 128 additions and 361 deletions.
148 changes: 71 additions & 77 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::syntax::foreignmod::foreign_mod_to_foreign_item_types;
use crate::syntax::safety::Safety;
use crate::syntax::{attribute::attribute_find_path, path::path_to_single_ident};
use crate::{
parser::{
inherit::{InheritMethods, ParsedInheritedMethod},
qobject::ParsedQObject,
signals::{ParsedSignal, SignalMethods},
},
parser::{inherit::ParsedInheritedMethod, qobject::ParsedQObject, signals::ParsedSignal},
syntax::expr::expr_to_string,
};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::collections::BTreeMap;
use syn::ForeignItem;
use syn::{
spanned::Spanned, Attribute, Error, Ident, Item, ItemForeignMod, ItemImpl, Result, Type,
TypePath,
Expand Down Expand Up @@ -184,75 +180,60 @@ impl ParsedCxxQtData {
}
}

fn parse_inherit_mod(&mut self, tokens: TokenStream) -> Result<()> {
let inherited: InheritMethods = syn::parse2(tokens)?;

self.add_inherited_methods(inherited)
}

fn add_inherited_methods(&mut self, inherited: InheritMethods) -> Result<()> {
for method in inherited.base_functions.into_iter() {
let parsed_inherited_method = ParsedInheritedMethod::parse(method, inherited.safety)?;

if let Some(ref mut qobject) = self
.qobjects
.get_mut(&parsed_inherited_method.qobject_ident)
{
qobject.inherited_methods.push(parsed_inherited_method);
} else {
return Err(Error::new_spanned(
parsed_inherited_method.qobject_ident,
"No QObject with this name found.",
));
fn parse_foreign_mod(&mut self, foreign_mod: ItemForeignMod) -> Result<Option<Item>> {
if let Some(lit_str) = &foreign_mod.abi.name {
match lit_str.value().as_str() {
"RustQt" => {
self.parse_foreign_mod_rust_qt(foreign_mod)?;
return Ok(None);
}
// TODO: look for "C++Qt" later
_others => {}
}
}
Ok(())
}

fn parse_signals_mod(&mut self, tokens: TokenStream) -> Result<()> {
let signals: SignalMethods = syn::parse2(tokens)?;

self.add_signal_methods(signals)
}

fn add_signal_methods(&mut self, signals: SignalMethods) -> Result<()> {
for method in signals.base_functions.into_iter() {
let parsed_signal_method = ParsedSignal::parse(method, signals.safety)?;

if let Some(ref mut qobject) =
self.qobjects.get_mut(&parsed_signal_method.qobject_ident)
{
qobject.signals.push(parsed_signal_method);
} else {
return Err(Error::new_spanned(
parsed_signal_method.qobject_ident,
"No QObject with this name found.",
));
}
}
Ok(())
Ok(Some(Item::ForeignMod(foreign_mod)))
}

fn parse_foreign_mod(&mut self, mut foreign_mod: ItemForeignMod) -> Result<Option<Item>> {
// Check if the foreign mod has cxx_qt::inherit on it
if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "inherit"]) {
// Remove the inherit attribute
foreign_mod.attrs.remove(index);

self.parse_inherit_mod(foreign_mod.into_token_stream())?;
return Ok(None);
}
fn parse_foreign_mod_rust_qt(&mut self, mut foreign_mod: ItemForeignMod) -> Result<()> {
let safe_call = if foreign_mod.unsafety.is_some() {
Safety::Safe
} else {
Safety::Unsafe
};

// Check if the foreign mod has cxx_qt::qsignals on it
if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "qsignals"]) {
// Remove the signals attribute
foreign_mod.attrs.remove(index);
for item in foreign_mod.items.drain(..) {
if let ForeignItem::Fn(mut foreign_fn) = item {
// Test if the function is a signal
if let Some(index) = attribute_find_path(&foreign_fn.attrs, &["qsignal"]) {
// Remove the signals attribute
foreign_fn.attrs.remove(index);

let parsed_signal_method = ParsedSignal::parse(foreign_fn, safe_call)?;

self.with_qobject(&parsed_signal_method.qobject_ident)?
.signals
.push(parsed_signal_method);
// Test if the function is an inheritance method
//
// Note that we need to test for qsignal first as qsignals have their own inherit meaning
} else if let Some(index) = attribute_find_path(&foreign_fn.attrs, &["inherit"]) {
// Remove the inherit attribute
foreign_fn.attrs.remove(index);

let parsed_inherited_method =
ParsedInheritedMethod::parse(foreign_fn, safe_call)?;

self.with_qobject(&parsed_inherited_method.qobject_ident)?
.inherited_methods
.push(parsed_inherited_method);
}

self.parse_signals_mod(foreign_mod.into_token_stream())?;
return Ok(None);
// TODO: test for qinvokable later
}
}

Ok(Some(Item::ForeignMod(foreign_mod)))
Ok(())
}

/// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation
Expand Down Expand Up @@ -287,6 +268,17 @@ impl ParsedCxxQtData {

Ok(Some(Item::Impl(imp)))
}

fn with_qobject(&mut self, qobject_ident: &Ident) -> Result<&mut ParsedQObject> {
if let Some(qobject) = self.qobjects.get_mut(qobject_ident) {
Ok(qobject)
} else {
Err(Error::new_spanned(
qobject_ident,
"No QObject with this name found.",
))
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -732,17 +724,18 @@ mod tests {
let mut cxxqtdata = create_parsed_cxx_qt_data();

let unsafe_block: Item = parse_quote! {
#[cxx_qt::inherit]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[inherit]
fn test(self: &qobject::MyObject);

#[inherit]
fn with_args(self: &qobject::MyObject, arg: i32);
}
};
let safe_block: Item = parse_quote! {
#[cxx_qt::inherit]
extern "C++" {
extern "RustQt" {
#[cxx_name="withRename"]
#[inherit]
unsafe fn with_rename(self: Pin<&mut qobject::MyObject>, arg: i32);
}
};
Expand Down Expand Up @@ -771,12 +764,13 @@ mod tests {
fn test_parse_qsignals_safe() {
let mut cxxqtdata = create_parsed_cxx_qt_data();
let block: Item = parse_quote! {
#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::MyObject>);

#[cxx_name="cppDataChanged"]
#[inherit]
#[qsignal]
fn data_changed(self: Pin<&mut qobject::MyObject>, data: i32);
}
};
Expand Down Expand Up @@ -815,8 +809,8 @@ mod tests {
fn test_parse_qsignals_unknown_obj() {
let mut cxxqtdata = create_parsed_cxx_qt_data();
let block: Item = parse_quote! {
#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::UnknownObj>);
}
};
Expand All @@ -827,8 +821,8 @@ mod tests {
fn test_parse_qsignals_unsafe() {
let mut cxxqtdata = create_parsed_cxx_qt_data();
let block: Item = parse_quote! {
#[cxx_qt::qsignals]
extern "C++" {
extern "RustQt" {
#[qsignal]
unsafe fn unsafe_signal(self: Pin<&mut qobject::MyObject>, arg: *mut T);
}
};
Expand Down
109 changes: 3 additions & 106 deletions crates/cxx-qt-gen/src/parser/inherit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,74 +11,9 @@ use crate::{
},
};
use quote::format_ident;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Attribute, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, LitStr, Result, Token,
};

/// This type is used when parsing the `#[cxx_qt::inherit]` macro contents into raw ForeignItemFn items
pub struct InheritMethods {
pub safety: Safety,
pub base_functions: Vec<ForeignItemFn>,
}

impl Parse for InheritMethods {
fn parse(input: ParseStream) -> Result<Self> {
let mut base_functions = Vec::new();

// Ensure that any attributes on the block have been removed
//
// Otherwise parsing of unsafe can fail due to #[doc]
let attrs = input.call(Attribute::parse_outer)?;
if !attrs.is_empty() {
return Err(Error::new(
attrs.first().span(),
"Unexpected attribute on #[cxx_qt::qsignals] block.",
));
}

// This looks somewhat counter-intuitive, but if we add `unsafe`
// to the `extern "C++"` block, the contained functions will be safe to call.
let safety = if input.peek(Token![unsafe]) {
Safety::Safe
} else {
Safety::Unsafe
};
if safety == Safety::Safe {
input.parse::<Token![unsafe]>()?;
}

let extern_block = input.parse::<ItemForeignMod>()?;
if extern_block.abi.name != Some(LitStr::new("C++", extern_block.abi.span())) {
return Err(Error::new(
extern_block.abi.span(),
"Inherit blocks must be marked with `extern \"C++\"`",
));
}

for item in extern_block.items {
match item {
ForeignItem::Fn(function) => {
base_functions.push(function);
}
_ => {
return Err(Error::new(
item.span(),
"Only functions are allowed in #[cxx_qt::inherit] blocks",
))
}
}
}
use syn::{spanned::Spanned, Error, ForeignItemFn, Ident, Result};

Ok(InheritMethods {
safety,
base_functions,
})
}
}

/// Describes a method found in #[cxx_qt::inherit]
/// Describes a method found in an extern "RustQt" with #[inherit]
pub struct ParsedInheritedMethod {
/// The original [syn::ForeignItemFn] of the inherited method declaration
pub method: ForeignItemFn,
Expand Down Expand Up @@ -141,46 +76,8 @@ impl ParsedInheritedMethod {
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
use syn::parse_quote;

#[test]
fn test_parse_unsafe_mod() {
let module = quote! {
extern "C++" {
unsafe fn test(self: &qobject::T);
}
};
let parsed: InheritMethods = syn::parse2(module).unwrap();
assert_eq!(parsed.base_functions.len(), 1);
assert_eq!(parsed.safety, Safety::Unsafe);
}

#[test]
fn test_parse_safe_mod() {
let module = quote! {
unsafe extern "C++" {
fn test(self: &qobject::T);
unsafe fn test2(self: &qobject::T);
}
};
let parsed: InheritMethods = syn::parse2(module).unwrap();
assert_eq!(parsed.base_functions.len(), 2);
assert_eq!(parsed.safety, Safety::Safe);
}

#[test]
fn test_parse_attributes() {
let module = quote! {
unsafe extern "C++" {
#[attribute]
fn test(self: &qobject::T);
}
};
let parsed: InheritMethods = syn::parse2(module).unwrap();
assert_eq!(parsed.base_functions.len(), 1);
assert_eq!(parsed.base_functions[0].attrs.len(), 1);
}
use syn::parse_quote;

fn assert_parse_error(function: ForeignItemFn) {
let result = ParsedInheritedMethod::parse(function, Safety::Safe);
Expand Down
12 changes: 6 additions & 6 deletions crates/cxx-qt-gen/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ mod tests {
#[cxx_qt::qobject]
pub struct MyObject;

#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::MyObject>);
}
}
Expand All @@ -187,8 +187,8 @@ mod tests {
#[cxx_qt::qobject]
pub struct MyObject;

#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::MyObject>);
}

Expand Down Expand Up @@ -216,8 +216,8 @@ mod tests {
#[cxx_qt::qobject]
pub struct MyObject;

#[cxx_qt::qsignals]
unsafe extern "C++" {
unsafe extern "RustQt" {
#[qsignal]
fn ready(self: Pin<&mut qobject::UnknownObject>);
}
}
Expand Down
Loading

0 comments on commit 86eb41e

Please sign in to comment.