Skip to content

Commit

Permalink
Merge pull request #20 from snipsco/fix/macro-paths
Browse files Browse the repository at this point in the history
Fix : Fully qualified paths for field types in struct definitions
  • Loading branch information
anthonyray authored Mar 23, 2020
2 parents 26785ee + e7275c6 commit bb15c70
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 25 deletions.
7 changes: 5 additions & 2 deletions ffi-convert-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ffi-convert-derive"
version = "0.1.1"
version = "0.1.2"
authors = ["Sonos"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand All @@ -13,6 +13,9 @@ keywords = ["ffi"]
proc-macro = true

[dependencies]
syn = "1.0.5"
quote = "1.0.2"
proc-macro2 = "1.0.6"

[dependencies.syn]
version = "1.0.16"
features = ["extra-traits"]
174 changes: 154 additions & 20 deletions ffi-convert-derive/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use quote::quote;

pub fn parse_target_type(attrs: &Vec<syn::Attribute>) -> syn::Path {
let target_type_attribute = attrs
.iter()
Expand Down Expand Up @@ -33,7 +31,8 @@ pub fn parse_struct_fields(data: &syn::Data) -> Vec<Field> {

pub struct Field<'a> {
pub name: &'a syn::Ident,
pub field_type: proc_macro2::TokenStream,
pub field_type: syn::TypePath,
pub type_params: Option<syn::AngleBracketedGenericArguments>,
pub is_nullable: bool,
pub is_string: bool,
pub is_pointer: bool,
Expand All @@ -51,8 +50,8 @@ pub fn parse_field(field: &syn::Field) -> Field {
levels_of_indirection += 1;
}

let field_type = match inner_field_type {
syn::Type::Path(path_t) => generic_path_to_concrete_type_path(&path_t.path),
let (field_type, extracted_type_params) = match inner_field_type {
syn::Type::Path(type_path) => generic_path_to_concrete_type_path(type_path),
_ => panic!("Field type used in this struct is not supported by the proc macro"),
};

Expand Down Expand Up @@ -91,24 +90,159 @@ pub fn parse_field(field: &syn::Field) -> Field {
is_string: is_string_field,
is_pointer: is_ptr_field,
levels_of_indirection,
type_params: extracted_type_params,
}
}

pub fn generic_path_to_concrete_type_path(path: &syn::Path) -> proc_macro2::TokenStream {
let mut path = path.clone();
let last_segment = path.segments.pop().unwrap();
let segments = &path.segments;
let ident = &last_segment.value().ident;
let turbofished_type = if let syn::PathArguments::AngleBracketed(bracketed_args) =
&last_segment.value().arguments
{
quote!(#ident::#bracketed_args)
} else {
quote!(#ident)
};
if segments.is_empty() {
turbofished_type
/// A helper function that extracts type parameters from type definitions of fields.
///
/// Some procedural macros need to extract type parameters from the definitions of a struct's fields.
/// For instance, if a struct has a field, with the following type :
/// `std::module1::module2::Vec<Hello>`, the goal of this function is to transform this in :
/// `(std::module1::module2::Vec`, `Hello`)`
///
pub fn generic_path_to_concrete_type_path(
path: syn::TypePath,
) -> (syn::TypePath, Option<syn::AngleBracketedGenericArguments>) {
let mut path_mut = path.clone();
let last_seg: Option<&mut syn::PathSegment> = path_mut.path.segments.last_mut();

if let Some(last_segment) = last_seg {
if let syn::PathArguments::AngleBracketed(ref bracketed_type_params) =
last_segment.arguments
{
let extracted_type_params = (*bracketed_type_params).clone();
last_segment.arguments = syn::PathArguments::None;
(path_mut, Some(extracted_type_params))
} else {
(path_mut, None)
}
} else {
quote!(#segments::#turbofished_type)
panic!("The field with type : {:?} is invalid. No segments on the TypePath")
}
}

#[cfg(test)]
mod tests {
use super::*;
use syn::TypePath;

#[test]
fn test_type_parameter_extraction() {
let type_path = syn::parse_str::<TypePath>("std::mod1::mod2::Foo<Bar>").unwrap();

let (transformed_type_path, extracted_type_param) =
generic_path_to_concrete_type_path(type_path);

assert_eq!(extracted_type_param.unwrap().args.len(), 1);
assert_eq!(
transformed_type_path,
syn::parse_str::<TypePath>("std::mod1::mod2::Foo").unwrap()
);
}

#[test]
fn test_type_parameters_extraction() {
let type_path = syn::parse_str::<TypePath>("std::mod1::mod2::Foo<Bar, Baz>").unwrap();

let (transformed_type_path, extracted_type_param) =
generic_path_to_concrete_type_path(type_path);

assert_eq!(
transformed_type_path,
syn::parse_str::<TypePath>("std::mod1::mod2::Foo").unwrap()
);
assert_eq!(extracted_type_param.unwrap().args.len(), 2)
}

#[test]
fn test_type_parameter_extraction_works_without_params() {
let original_path = syn::parse_str::<TypePath>("std::module1::module2::Hello")
.expect("Could not parse str into syn::Path");
let (transformed_path, extracted_type_params) =
generic_path_to_concrete_type_path(original_path);

assert!(extracted_type_params.is_none());
assert_eq!(
transformed_path,
syn::parse_str::<TypePath>("std::module1::module2::Hello").unwrap()
)
}

#[test]
fn test_field_parsing_1() {
let fields = syn::parse_str::<syn::FieldsNamed>("{ field : *const mod1::CDummy }").unwrap();

let parsed_fields = fields.named.iter().map(parse_field).collect::<Vec<Field>>();

assert_eq!(parsed_fields[0].is_string, false);
assert_eq!(parsed_fields[0].is_pointer, true);
assert_eq!(parsed_fields[0].is_nullable, false);

assert_eq!(parsed_fields[0].field_type.path.segments.len(), 2);
}

#[test]
fn test_field_parsing_2() {
let fields = syn::parse_str::<syn::FieldsNamed>(
"{\
field1: *const mod1::CDummy, \
field2: *const CDummy\
}",
)
.unwrap();

let parsed_fields = fields
.named
.iter()
.map(|f| {
println!("f : {:?}", f);
f
})
.map(parse_field)
.collect::<Vec<Field>>();

assert_eq!(parsed_fields[0].is_pointer, true);
assert_eq!(parsed_fields[1].is_pointer, true);
assert_eq!(parsed_fields[0].is_string, false);
assert_eq!(parsed_fields[1].is_string, false);

let parsed_path_0 = parsed_fields[0].field_type.path.clone();
let parsed_path_1 = parsed_fields[1].field_type.path.clone();

assert_eq!(parsed_path_0.segments.len(), 2);
assert_eq!(parsed_path_1.segments.len(), 1);
}

#[test]
fn test_field_parsing_3() {
let fields = syn::parse_str::<syn::FieldsNamed>(
"{\
field1: *const mod1::CFoo<CBar>, \
field2: *const CFoo<CBar>\
}",
)
.unwrap();

let parsed_fields = fields
.named
.iter()
.map(|f| {
println!("f : {:?}", f);
f
})
.map(parse_field)
.collect::<Vec<Field>>();

assert_eq!(parsed_fields[0].is_pointer, true);
assert_eq!(parsed_fields[1].is_pointer, true);
assert_eq!(parsed_fields[0].is_string, false);
assert_eq!(parsed_fields[1].is_string, false);

let parsed_path_0 = parsed_fields[0].field_type.path.clone();
let parsed_path_1 = parsed_fields[1].field_type.path.clone();

assert_eq!(parsed_path_0.segments.len(), 2);
assert_eq!(parsed_path_1.segments.len(), 1);
}
}
2 changes: 1 addition & 1 deletion ffi-convert-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ edition = "2018"

[dependencies]
failure = "0.1"
ffi-convert = "0.1.1"
ffi-convert = "0.1.2"
libc = "0.2.66"
4 changes: 2 additions & 2 deletions ffi-convert/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ffi-convert"
version = "0.1.1"
version = "0.1.2"
authors = ["Sonos"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand All @@ -10,6 +10,6 @@ readme = "../README.md"
keywords = ["ffi"]

[dependencies]
ffi-convert-derive = "0.1.1"
ffi-convert-derive = "0.1.2"
failure = "0.1"
libc = "0.2"

0 comments on commit bb15c70

Please sign in to comment.