use indexmap::IndexSet as HashSet;
use syn::{
spanned::Spanned, AngleBracketedGenericArguments, GenericArgument, PatType, PathArguments,
PathSegment, ReturnType, Signature, Type, TypePath, TypeReference,
};
use crate::{
conversion::convert_error::{ConvertErrorFromRust, LocatedConvertErrorFromRust},
types::QualifiedName,
};
pub(super) fn assemble_extern_fun_deps(
sig: &Signature,
file: &str,
) -> Result<Vec<QualifiedName>, LocatedConvertErrorFromRust> {
let mut deps = HashSet::new();
if let ReturnType::Type(_, ty) = &sig.output {
add_type_to_deps(ty, &mut deps, file)?;
}
for input in &sig.inputs {
match input {
syn::FnArg::Receiver(_) => {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::ExternRustFunRequiresFullyQualifiedReceiver,
&input.span(),
file,
))
}
syn::FnArg::Typed(PatType { ty, .. }) => add_type_to_deps(ty, &mut deps, file)?,
}
}
Ok(deps.into_iter().collect())
}
fn add_type_to_deps(
ty: &Type,
deps: &mut HashSet<QualifiedName>,
file: &str,
) -> Result<(), LocatedConvertErrorFromRust> {
match ty {
Type::Reference(TypeReference {
mutability: Some(_),
..
}) => {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::PinnedReferencesRequiredForExternFun,
&ty.span(),
file,
))
}
Type::Reference(TypeReference { elem, .. }) => match &**elem {
Type::Path(tp) => add_path_to_deps(tp, deps, file)?,
_ => {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&ty.span(),
file,
))
}
},
Type::Path(tp) => {
if tp.path.segments.len() != 1 {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::NamespacesNotSupportedForExternFun,
&tp.span(),
file,
));
}
if let Some(PathSegment {
ident,
arguments:
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
}) = tp.path.segments.last()
{
if ident == "Pin" {
if args.len() != 1 {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&tp.span(),
file,
));
}
if let Some(GenericArgument::Type(Type::Reference(TypeReference {
mutability: Some(_),
elem,
..
}))) = args.first()
{
if let Type::Path(tp) = &**elem {
add_path_to_deps(tp, deps, file)?
} else {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&elem.span(),
file,
));
}
} else {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&ty.span(),
file,
));
}
} else if ident == "Box" || ident == "Vec" {
if args.len() != 1 {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&tp.span(),
file,
));
}
if let Some(GenericArgument::Type(Type::Path(tp))) = args.first() {
add_path_to_deps(tp, deps, file)?
} else {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&ty.span(),
file,
));
}
} else {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&ident.span(),
file,
));
}
} else {
add_path_to_deps(tp, deps, file)?
}
}
_ => {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&ty.span(),
file,
))
}
};
Ok(())
}
fn add_path_to_deps(
type_path: &TypePath,
deps: &mut HashSet<QualifiedName>,
file: &str,
) -> Result<(), LocatedConvertErrorFromRust> {
if let Some(PathSegment {
arguments: PathArguments::AngleBracketed(..) | PathArguments::Parenthesized(..),
..
}) = type_path.path.segments.last()
{
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::UnsupportedTypeForExternFun,
&type_path.span(),
file,
));
}
let qn = QualifiedName::from_type_path(type_path);
if !qn.get_namespace().is_empty() {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::NamespacesNotSupportedForExternFun,
&type_path.span(),
file,
));
}
if qn.get_final_item() == "Self" {
return Err(LocatedConvertErrorFromRust::new(
ConvertErrorFromRust::ExplicitSelf,
&type_path.span(),
file,
));
}
deps.insert(qn);
Ok(())
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
use super::*;
fn run_test_expect_ok(sig: Signature, expected_deps: &[&str]) {
let expected_as_set: HashSet<QualifiedName> = expected_deps
.iter()
.cloned()
.map(QualifiedName::new_from_cpp_name)
.collect();
let result = assemble_extern_fun_deps(&sig, "").unwrap();
let actual_as_set: HashSet<QualifiedName> = result.into_iter().collect();
assert_eq!(expected_as_set, actual_as_set);
}
fn run_test_expect_fail(sig: Signature) {
assert!(assemble_extern_fun_deps(&sig, "").is_err())
}
#[test]
fn test_assemble_extern_fun_deps() {
run_test_expect_fail(parse_quote! { fn function(self: A::B)});
run_test_expect_fail(parse_quote! { fn function(self: Self)});
run_test_expect_fail(parse_quote! { fn function(self: Self)});
run_test_expect_fail(parse_quote! { fn function(self)});
run_test_expect_fail(parse_quote! { fn function(&self)});
run_test_expect_fail(parse_quote! { fn function(&mut self)});
run_test_expect_fail(parse_quote! { fn function(self: Pin<&mut Self>)});
run_test_expect_fail(parse_quote! { fn function(self: Pin<&mut A::B>)});
run_test_expect_fail(parse_quote! { fn function(a: Pin<A>)});
run_test_expect_fail(parse_quote! { fn function(a: Pin<A::B>)});
run_test_expect_fail(parse_quote! { fn function(a: A::B)});
run_test_expect_fail(parse_quote! { fn function(a: &mut A)});
run_test_expect_fail(parse_quote! { fn function() -> A::B});
run_test_expect_fail(parse_quote! { fn function() -> &A::B});
run_test_expect_fail(parse_quote! { fn function(a: ())});
run_test_expect_fail(parse_quote! { fn function(a: &[A])});
run_test_expect_fail(parse_quote! { fn function(a: Bob<A>)});
run_test_expect_fail(parse_quote! { fn function(a: Box<A, B>)});
run_test_expect_fail(parse_quote! { fn function(a: a::Pin<&mut A>)});
run_test_expect_fail(parse_quote! { fn function(a: Pin<&A>)});
run_test_expect_ok(parse_quote! { fn function(a: A, b: B)}, &["A", "B"]);
run_test_expect_ok(parse_quote! { fn function(a: Box<A>)}, &["A"]);
run_test_expect_ok(parse_quote! { fn function(a: Vec<A>)}, &["A"]);
run_test_expect_ok(parse_quote! { fn function(a: &A)}, &["A"]);
run_test_expect_ok(parse_quote! { fn function(a: Pin<&mut A>)}, &["A"]);
run_test_expect_ok(
parse_quote! { fn function(a: A, b: B) -> Box<C>},
&["A", "B", "C"],
);
}
}