Skip to content

Commit

Permalink
Minimum implementation for propagating macro errors (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
yutannihilation authored Oct 16, 2023
1 parent 479fdd0 commit fee0431
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 83 deletions.
1 change: 1 addition & 0 deletions savvy-bindgen/src/gen/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ impl SavvyFn {
}
),
};

out
}

Expand Down
2 changes: 1 addition & 1 deletion savvy-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ pub use savvy_impl::SavvyImpl;

pub use utils::extract_docs;

pub use parse_file::{parse_file, parse_savvy_fn, parse_savvy_impl, read_file};
pub use parse_file::{parse_file, read_file};
48 changes: 6 additions & 42 deletions savvy-bindgen/src/parse_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,6 @@ use syn::parse_quote;

use crate::{ParsedResult, SavvyFn, SavvyImpl};

pub fn parse_savvy_fn(item: &syn::Item) -> Option<SavvyFn> {
let func = match item {
syn::Item::Fn(func) => func,
_ => {
return None;
}
};

// Generate bindings only when the function is marked by #[savvy]
if func
.attrs
.iter()
.any(|attr| attr == &parse_quote!(#[savvy]))
{
Some(SavvyFn::from_fn(func))
} else {
None
}
}

pub fn parse_savvy_impl(item: &syn::Item) -> Vec<SavvyFn> {
let item_impl = match item {
syn::Item::Impl(item_impl) => item_impl,
_ => {
return Vec::new();
}
};

// Generate bindings only when the function is marked by #[savvy]
if item_impl
.attrs
.iter()
.any(|attr| attr == &parse_quote!(#[savvy]))
{
SavvyImpl::new(item_impl).fns
} else {
Vec::new()
}
}

fn is_marked(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| attr == &parse_quote!(#[savvy]))
}
Expand Down Expand Up @@ -89,13 +49,17 @@ pub fn parse_file(path: &Path) -> ParsedResult {
match item {
syn::Item::Fn(item_fn) => {
if is_marked(item_fn.attrs.as_slice()) {
result.bare_fns.push(SavvyFn::from_fn(&item_fn))
result
.bare_fns
.push(SavvyFn::from_fn(&item_fn).expect("Failed to parse function"))
}
}

syn::Item::Impl(item_impl) => {
if is_marked(item_impl.attrs.as_slice()) {
result.impls.push(SavvyImpl::new(&item_impl))
result
.impls
.push(SavvyImpl::new(&item_impl).expect("Failed to parse impl"))
}
}
_ => continue,
Expand Down
38 changes: 24 additions & 14 deletions savvy-bindgen/src/savvy_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl SavvyFn {
}
}

pub fn from_fn(orig: &syn::ItemFn) -> Self {
pub fn from_fn(orig: &syn::ItemFn) -> syn::Result<Self> {
Self::new(
&orig.attrs,
&orig.sig,
Expand All @@ -196,11 +196,16 @@ impl SavvyFn {
)
}

pub fn from_impl_fn(orig: &syn::ImplItemFn, fn_type: SavvyFnType) -> Self {
pub fn from_impl_fn(orig: &syn::ImplItemFn, fn_type: SavvyFnType) -> syn::Result<Self> {
Self::new(&orig.attrs, &orig.sig, &orig.block, fn_type)
}

pub fn new(attrs: &[Attribute], sig: &Signature, block: &Block, fn_type: SavvyFnType) -> Self {
pub fn new(
attrs: &[Attribute],
sig: &Signature,
block: &Block,
fn_type: SavvyFnType,
) -> syn::Result<Self> {
// TODO: check function signature and abort if any of it is unexpected one.

let mut attrs = attrs.to_vec();
Expand All @@ -215,26 +220,32 @@ impl SavvyFn {
let stmts_orig = block.stmts.clone();
let mut stmts_additional: Vec<Stmt> = Vec::new();

let args_new: Vec<SavvyFnArg> = sig
let args_new = sig
.inputs
.iter()
.filter_map(|arg| match arg {
Typed(PatType { pat, ty, .. }) => {
let pat = match pat.as_ref() {
Ident(arg) => arg.ident.clone(),
_ => panic!("non-ident is not supported"),
_ => {
return Some(Err(syn::Error::new_spanned(
pat,
"non-ident is not supported",
)));
}
};

let ty = SavvySupportedTypes::from_type(ty.as_ref())
.expect("the type is not supported");

let ty = match SavvySupportedTypes::from_type(ty.as_ref()) {
Some(ty) => ty,
None => return Some(Err(syn::Error::new_spanned(ty, "Unsupported type"))),
};
let ty_ident = ty.to_rust_type_outer();

stmts_additional.push(parse_quote! {
let #pat = <#ty_ident>::try_from(savvy::Sxp(#pat))?;
});

Some(SavvyFnArg { pat, ty })
Some(Ok(SavvyFnArg { pat, ty }))
}
// Skip `self`
syn::FnArg::Receiver(syn::Receiver { reference, .. }) => {
Expand All @@ -248,19 +259,18 @@ impl SavvyFn {
None
}
})
.collect();
.collect::<syn::Result<Vec<SavvyFnArg>>>()?;

Self {
Ok(Self {
docs,
attrs,
fn_name,
fn_type,
args: args_new,
// TODO: propagate error
return_type: get_savvy_return_type(&sig.output).unwrap(),
return_type: get_savvy_return_type(&sig.output)?,
stmts_orig,
stmts_additional,
}
})
}
}

Expand Down
29 changes: 16 additions & 13 deletions savvy-bindgen/src/savvy_impl.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use quote::format_ident;
use syn::parse_quote;

use crate::savvy_fn::{SavvyFn, SavvyFnType};
Expand All @@ -18,50 +17,54 @@ pub struct SavvyImpl {
}

impl SavvyImpl {
pub fn new(orig: &syn::ItemImpl) -> Self {
pub fn new(orig: &syn::ItemImpl) -> syn::Result<Self> {
let mut attrs = orig.attrs.clone();
// Remove #[savvy]
attrs.retain(|attr| attr != &parse_quote!(#[savvy]));
// Extract doc comments
let docs = extract_docs(attrs.as_slice());
let self_ty = orig.self_ty.as_ref();

let ty = match orig.self_ty.as_ref() {
let ty = match self_ty {
syn::Type::Path(type_path) => type_path.path.segments.last().unwrap().ident.clone(),
_ => {
// TODO: propagate syn::Error
// panic!("should not happen");
format_ident!("UNEXPETED")
return Err(syn::Error::new_spanned(self_ty, "Unexpected type"));
}
};

let fns: Vec<SavvyFn> = orig
let fns = orig
.items
.clone()
.iter()
.filter_map(|f| match f {
syn::ImplItem::Fn(impl_item_fn) => {
let ty = orig.self_ty.as_ref().clone();
let ty = self_ty.clone();

let fn_type = match (is_method(impl_item_fn), is_ctor(impl_item_fn)) {
(true, false) => SavvyFnType::Method(ty),
(false, true) => SavvyFnType::Constructor(ty),
(false, false) => SavvyFnType::AssociatedFunction(ty),
(true, true) => panic!("`fn foo(self, ...) -> Self` is not allowed"),
(true, true) => {
return Some(Err(syn::Error::new_spanned(
f,
"For safety, a function that takes `self` and returns `Self` is not allowed",
)));
}
};

Some(SavvyFn::from_impl_fn(impl_item_fn, fn_type))
}
_ => None,
})
.collect();
.collect::<syn::Result<Vec<SavvyFn>>>()?;

Self {
Ok(Self {
docs,
attrs,
ty,
fns,
orig: orig.clone(),
}
})
}

#[allow(dead_code)]
Expand Down Expand Up @@ -136,7 +139,7 @@ mod tests {
}
);

let parsed = SavvyImpl::new(&item_impl);
let parsed = SavvyImpl::new(&item_impl).expect("Failed to parse");
assert_eq!(parsed.ty.to_string().as_str(), "Person");

assert_eq!(parsed.fns.len(), 4);
Expand Down
3 changes: 3 additions & 0 deletions savvy-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ syn = { version = "2", features = ["full", "extra-traits"] }

savvy-bindgen = { version = "0", path = "../savvy-bindgen" }

[dev-dependencies]
trybuild = "1"

[package.metadata.dist]
dist = false
37 changes: 24 additions & 13 deletions savvy-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ use savvy_bindgen::{SavvyFn, SavvyImpl};
#[proc_macro_attribute]
pub fn savvy(_args: TokenStream, input: TokenStream) -> TokenStream {
if let Ok(item_fn) = syn::parse::<syn::ItemFn>(input.clone()) {
return savvy_fn(&item_fn);
match savvy_fn(&item_fn) {
Ok(result) => return result,
Err(e) => return e.into_compile_error().into(),
}
}

let parse_result = syn::parse::<syn::ItemImpl>(input.clone());
if let Ok(item_impl) = parse_result {
return savvy_impl(&item_impl);
match savvy_impl(&item_impl) {
Ok(result) => return result,
Err(e) => return e.into_compile_error().into(),
}
}

proc_macro::TokenStream::from(
Expand All @@ -23,36 +29,36 @@ pub fn savvy(_args: TokenStream, input: TokenStream) -> TokenStream {
)
}

fn savvy_fn(item_fn: &syn::ItemFn) -> TokenStream {
let savvy_fn = SavvyFn::from_fn(item_fn);
fn savvy_fn(item_fn: &syn::ItemFn) -> syn::Result<TokenStream> {
let savvy_fn = SavvyFn::from_fn(item_fn)?;

let item_fn_inner = savvy_fn.generate_inner_fn();
let item_fn_outer = savvy_fn.generate_outer_fn();

quote! {
Ok(quote! {
#item_fn_inner
#item_fn_outer
}
.into()
.into())
}

fn savvy_impl(item_impl: &syn::ItemImpl) -> TokenStream {
let savvy_impl = SavvyImpl::new(item_impl);
fn savvy_impl(item_impl: &syn::ItemImpl) -> syn::Result<TokenStream> {
let savvy_impl = SavvyImpl::new(item_impl)?;
let orig = savvy_impl.orig.clone();
let ty = savvy_impl.ty.clone();

let list_fn_inner = savvy_impl.generate_inner_fns();
let list_fn_outer = savvy_impl.generate_outer_fns();

quote! {
Ok(quote! {
#orig

impl savvy::IntoExtPtrSxp for #ty {}

#(#list_fn_inner)*
#(#list_fn_outer)*
}
.into()
.into())
}

#[cfg(test)]
Expand All @@ -61,7 +67,9 @@ mod tests {
use syn::parse_quote;

fn assert_eq_inner(orig: syn::ItemFn, expected: syn::ItemFn) {
let result = SavvyFn::from_fn(&orig).generate_inner_fn();
let result = SavvyFn::from_fn(&orig)
.expect("Failed to parse a function")
.generate_inner_fn();
assert_eq!(result, expected);
}

Expand Down Expand Up @@ -134,7 +142,9 @@ mod tests {
}

fn assert_eq_outer(orig: syn::ItemFn, expected: syn::ItemFn) {
let result = SavvyFn::from_fn(&orig).generate_outer_fn();
let result = SavvyFn::from_fn(&orig)
.expect("Failed to parse an impl")
.generate_outer_fn();
assert_eq!(result, expected);
}

Expand Down Expand Up @@ -207,7 +217,8 @@ mod tests {
}

fn assert_eq_outer_impl(orig: &syn::ItemImpl, expected: syn::ItemFn, i: usize) {
let result = SavvyImpl::new(orig).fns[i].generate_outer_fn();
let result =
SavvyImpl::new(orig).expect("Failed to parse an impl").fns[i].generate_outer_fn();
assert_eq!(result, expected);
}

Expand Down
25 changes: 25 additions & 0 deletions savvy-macro/tests/cases/simple_cases.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use savvy_macro::savvy;

#[savvy]
fn foo_ref(&x: i32) -> savvy::Result<()> {}

#[savvy]
fn foo_mut_ref(&mut x: i32) -> savvy::Result<()> {}

#[savvy]
fn foo_unsupported1(x: usize) -> savvy::Result<()> {}

#[savvy]
fn foo_no_return_type(x: i32) {}

#[savvy]
fn foo_wrong_return_type(x: i32) -> i32 {}

struct Foo;

#[savvy]
impl Foo {
fn foo_self(&self) -> Self {}
}

fn main() {}
Loading

0 comments on commit fee0431

Please sign in to comment.