Skip to content

Commit

Permalink
cxx-qt-gen: parse types in the CXX bridge to generate qualified mappings
Browse files Browse the repository at this point in the history
Related to KDAB#404
  • Loading branch information
ahayzen-kdab committed Jul 24, 2023
1 parent 38bf0de commit 7429dc2
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 20 deletions.
176 changes: 158 additions & 18 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ use crate::{
parser::{inherit::ParsedInheritedMethod, qobject::ParsedQObject, signals::ParsedSignal},
syntax::expr::expr_to_string,
};
use quote::format_ident;
use std::collections::BTreeMap;
use syn::ForeignItem;
use syn::{Attribute, Error, Ident, Item, ItemForeignMod, ItemImpl, Result, Type, TypePath};
use syn::{
parse_quote, spanned::Spanned, Attribute, Error, ForeignItem, Ident, Item, ItemForeignMod,
ItemImpl, Path, Result, Type, TypePath,
};

use super::invokable::ParsedQInvokable;

Expand Down Expand Up @@ -48,8 +51,10 @@ impl ParsedCxxMappings {

#[derive(Default)]
pub struct ParsedCxxQtData {
/// Mappings for
/// Mappings for CXX types when used in C++
pub cxx_mappings: ParsedCxxMappings,
/// Mappings for CXX types when used outside the bridge
pub qualified_mappings: BTreeMap<Ident, Path>,
/// Map of the QObjects defined in the module that will be used for code generation
//
// We have to use a BTreeMap here, instead of a HashMap, to keep the order of QObjects stable.
Expand Down Expand Up @@ -100,21 +105,23 @@ impl ParsedCxxQtData {
Ok(())
}

/// Search through Item's and look for a cxx_name or namespace attribute on a type
/// Search through Item's and look for a cxx_name, rust_name, or namespace attribute on a type
///
/// We need to know this as it affects the type name used in the C++ generation
pub fn populate_cxx_mappings_from_item(
/// And it is used to create the qualified Rust name
pub fn populate_mappings_from_item(
&mut self,
item: &Item,
bridge_namespace: &str,
module_ident: &Ident,
) -> Result<()> {
// Consider if shared types have mappings
match item {
Item::Enum(item) => {
self.populate_cxx_mappings(&item.ident, &item.attrs, bridge_namespace)?;
self.populate_mappings(&item.ident, &item.attrs, bridge_namespace, module_ident)?;
}
Item::Struct(item) => {
self.populate_cxx_mappings(&item.ident, &item.attrs, bridge_namespace)?;
self.populate_mappings(&item.ident, &item.attrs, bridge_namespace, module_ident)?;
}
_others => {}
}
Expand All @@ -131,23 +138,25 @@ impl ParsedCxxQtData {

// Read each of the types in the mod (type A;)
for foreign_type in foreign_mod_to_foreign_item_types(foreign_mod)? {
self.populate_cxx_mappings(
self.populate_mappings(
&foreign_type.ident,
&foreign_type.attrs,
&block_namespace,
module_ident,
)?;
}
}

Ok(())
}

/// Helper which adds cxx_name and namespace mappings from the ident, attrs, and parent namespace
fn populate_cxx_mappings(
/// Helper which adds cxx_name, rust_name, and namespace mappings from the ident, attrs, parent namespace, and module ident
fn populate_mappings(
&mut self,
ident: &Ident,
attrs: &[Attribute],
parent_namespace: &str,
module_ident: &Ident,
) -> Result<()> {
// Retrieve the namespace for the type itself if there is one
let namespace = if let Some(index) = attribute_find_path(attrs, &["namespace"]) {
Expand All @@ -171,6 +180,19 @@ impl ParsedCxxQtData {
.insert(ident.to_string(), namespace);
}

// Add type to qualified mappings
let rust_ident = if let Some(index) = attribute_find_path(attrs, &["rust_name"]) {
format_ident!(
"{}",
expr_to_string(&attrs[index].meta.require_name_value()?.value)?,
span = attrs[index].span()
)
} else {
ident.clone()
};
self.qualified_mappings
.insert(ident.clone(), parse_quote! { #module_ident::#rust_ident });

Ok(())
}

Expand Down Expand Up @@ -549,9 +571,18 @@ mod tests {
}
};
assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "")
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert!(cxx_qt_data.cxx_mappings.cxx_names.is_empty());

assert_eq!(cxx_qt_data.qualified_mappings.len(), 1);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::A }
);
}

#[test]
Expand All @@ -565,10 +596,19 @@ mod tests {
}
};
assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "")
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 1);
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.get("A").unwrap(), "B");

assert_eq!(cxx_qt_data.qualified_mappings.len(), 1);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::A }
);
}

#[test]
Expand All @@ -582,10 +622,19 @@ mod tests {
}
};
assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "")
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 1);
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.get("A").unwrap(), "B");

assert_eq!(cxx_qt_data.qualified_mappings.len(), 1);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::A }
);
}

#[test]
Expand All @@ -601,7 +650,7 @@ mod tests {
}
};
assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "bridge_namespace")
.populate_mappings_from_item(&item, "bridge_namespace", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 1);
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.get("B").unwrap(), "C");
Expand All @@ -615,6 +664,22 @@ mod tests {
cxx_qt_data.cxx_mappings.namespaces.get("B").unwrap(),
"bridge_namespace"
);

assert_eq!(cxx_qt_data.qualified_mappings.len(), 2);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::A }
);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("B"))
.unwrap(),
&parse_quote! { ffi::B }
);
}

#[test]
Expand All @@ -632,7 +697,7 @@ mod tests {
};
// Also ensure item namespace is chosen instead of bridge namespace
assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "namespace")
.populate_mappings_from_item(&item, "namespace", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 0);

Expand All @@ -645,6 +710,22 @@ mod tests {
cxx_qt_data.cxx_mappings.namespaces.get("B").unwrap(),
"type_namespace"
);

assert_eq!(cxx_qt_data.qualified_mappings.len(), 2);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::A }
);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("B"))
.unwrap(),
&parse_quote! { ffi::B }
);
}

#[test]
Expand All @@ -663,7 +744,7 @@ mod tests {
}
};
assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "")
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 2);
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.get("A").unwrap(), "B");
Expand All @@ -678,6 +759,22 @@ mod tests {
cxx_qt_data.cxx_mappings.namespaces.get("C").unwrap(),
"extern_namespace"
);

assert_eq!(cxx_qt_data.qualified_mappings.len(), 2);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::A }
);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("C"))
.unwrap(),
&parse_quote! { ffi::C }
);
}

#[test]
Expand All @@ -693,7 +790,7 @@ mod tests {
};

assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "")
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 1);
assert_eq!(
Expand All @@ -706,6 +803,15 @@ mod tests {
cxx_qt_data.cxx_mappings.namespaces.get("EnumA").unwrap(),
"enum_namespace"
);

assert_eq!(cxx_qt_data.qualified_mappings.len(), 1);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("EnumA"))
.unwrap(),
&parse_quote! { ffi::EnumA }
);
}

#[test]
Expand All @@ -721,7 +827,7 @@ mod tests {
};

assert!(cxx_qt_data
.populate_cxx_mappings_from_item(&item, "")
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert_eq!(cxx_qt_data.cxx_mappings.cxx_names.len(), 1);
assert_eq!(
Expand All @@ -734,6 +840,40 @@ mod tests {
cxx_qt_data.cxx_mappings.namespaces.get("StructA").unwrap(),
"struct_namespace"
);

assert_eq!(cxx_qt_data.qualified_mappings.len(), 1);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("StructA"))
.unwrap(),
&parse_quote! { ffi::StructA }
);
}

#[test]
fn test_cxx_mappings_rust_name_normal() {
let mut cxx_qt_data = create_parsed_cxx_qt_data();

let item: Item = parse_quote! {
extern "C++" {
#[rust_name = "B"]
type A;
}
};
assert!(cxx_qt_data
.populate_mappings_from_item(&item, "", &format_ident!("ffi"))
.is_ok());
assert!(cxx_qt_data.cxx_mappings.cxx_names.is_empty());

assert_eq!(cxx_qt_data.qualified_mappings.len(), 1);
assert_eq!(
cxx_qt_data
.qualified_mappings
.get(&format_ident!("A"))
.unwrap(),
&parse_quote! { ffi::B }
);
}

#[test]
Expand Down
12 changes: 10 additions & 2 deletions crates/cxx-qt-gen/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ impl Parser {
// qobject. Otherwise return them to be added to other
if let Some(other) = cxx_qt_data.parse_cxx_qt_item(item)? {
// Load any CXX name mappings
cxx_qt_data.populate_cxx_mappings_from_item(&other, &bridge_namespace)?;
cxx_qt_data.populate_mappings_from_item(
&other,
&bridge_namespace,
&module.ident,
)?;

// Unknown item so add to the other list
others.push(other);
Expand All @@ -86,7 +90,11 @@ impl Parser {
} else {
// Load any CXX name mappings
for item in &items.1 {
cxx_qt_data.populate_cxx_mappings_from_item(item, &bridge_namespace)?;
cxx_qt_data.populate_mappings_from_item(
item,
&bridge_namespace,
&module.ident,
)?;
}

// No qobjects found so pass everything through
Expand Down

0 comments on commit 7429dc2

Please sign in to comment.