initial untested implementation

This commit is contained in:
Jacob Lifshay 2019-11-24 06:00:21 -08:00
parent 42ee180116
commit 3c1a975ec0
6 changed files with 172 additions and 17 deletions

View File

@ -47,10 +47,10 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
pub fn parse(
pub fn parse<'b>(
name: &'a syn::Ident,
sig: &'a syn::Signature,
meth_attrs: &'a mut Vec<syn::Attribute>,
meth_attrs: &'b mut Vec<syn::Attribute>,
) -> syn::Result<FnSpec<'a>> {
let (mut fn_type, fn_attrs) = parse_attributes(meth_attrs)?;

View File

@ -133,7 +133,7 @@ fn function_wrapper_ident(name: &Ident) -> Ident {
/// Generates python wrapper over a function that allows adding it to a python module as a python
/// function
pub fn add_fn_to_module(
func: &syn::ItemFn,
func: &mut syn::ItemFn,
python_name: &Ident,
pyfn_attrs: Vec<pyfunction::Argument>,
) -> TokenStream {
@ -157,7 +157,14 @@ pub fn add_fn_to_module(
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let wrapper = function_c_wrapper(&func.sig.ident, &spec);
let doc = utils::get_doc(&func.attrs, true);
let text_signature = match utils::parse_text_signature_attrs(&mut func.attrs, python_name) {
Ok(text_signature) => text_signature,
Err(err) => return err.to_compile_error(),
};
let doc = match utils::get_doc(&func.attrs, text_signature, true) {
Ok(doc) => doc,
Err(err) => return err.to_compile_error(),
};
let tokens = quote! {
fn #function_wrapper_ident(py: pyo3::Python) -> pyo3::PyObject {

View File

@ -157,7 +157,11 @@ impl PyClassArgs {
}
pub fn build_py_class(class: &mut syn::ItemStruct, attr: &PyClassArgs) -> syn::Result<TokenStream> {
let doc = utils::get_doc(&class.attrs, true);
let text_signature = utils::parse_text_signature_attrs(
&mut class.attrs,
&get_class_python_name(&class.ident, attr),
)?;
let doc = utils::get_doc(&class.attrs, text_signature, true)?;
let mut descriptors = Vec::new();
check_generics(class)?;
@ -245,16 +249,20 @@ fn impl_inventory(cls: &syn::Ident) -> TokenStream {
}
}
fn get_class_python_name(cls: &syn::Ident, attr: &PyClassArgs) -> TokenStream {
match &attr.name {
Some(name) => quote! { #name },
None => quote! { #cls },
}
}
fn impl_class(
cls: &syn::Ident,
attr: &PyClassArgs,
doc: syn::Lit,
descriptors: Vec<(syn::Field, Vec<FnType>)>,
) -> TokenStream {
let cls_name = match &attr.name {
Some(name) => quote! { #name }.to_string(),
None => cls.to_string(),
};
let cls_name = get_class_python_name(cls, attr);
let extra = {
if let Some(freelist) = &attr.freelist {

View File

@ -12,8 +12,38 @@ pub fn gen_py_method(
) -> syn::Result<TokenStream> {
check_generic(name, sig)?;
let doc = utils::get_doc(&meth_attrs, true);
let spec = FnSpec::parse(name, sig, meth_attrs)?;
let spec = FnSpec::parse(name, sig, &mut *meth_attrs)?;
let text_signature = match &spec.tp {
FnType::Fn | FnType::PySelf(_) | FnType::FnClass | FnType::FnStatic => {
utils::parse_text_signature_attrs(&mut *meth_attrs, name)?
}
FnType::FnNew => utils::parse_text_signature_attrs(
&mut *meth_attrs,
&syn::Ident::new("__new__", name.span()),
)?,
FnType::FnCall => utils::parse_text_signature_attrs(
&mut *meth_attrs,
&syn::Ident::new("__call__", name.span()),
)?,
FnType::Getter(get_set_name) | FnType::Setter(get_set_name) => {
// try to parse anyway to give better error messages
let get_set_name = match get_set_name {
None => name.clone(),
Some(get_set_name) => syn::Ident::new(get_set_name, name.span()),
};
if let Some(type_signature) =
utils::parse_text_signature_attrs(&mut *meth_attrs, &get_set_name)?
{
return Err(syn::Error::new_spanned(
type_signature,
"type_signature not allowed on getters/setters",
));
}
None
}
};
let doc = utils::get_doc(&meth_attrs, text_signature, true)?;
Ok(match spec.tp {
FnType::Fn => impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)),

View File

@ -2,6 +2,7 @@
use proc_macro2::Span;
use proc_macro2::TokenStream;
use std::fmt::Display;
pub fn print_err(msg: String, t: TokenStream) {
println!("Error: {} in '{}'", msg, t.to_string());
@ -20,9 +21,108 @@ pub fn if_type_is_python(ty: &syn::Type) -> bool {
}
}
pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
attr.path.is_ident("text_signature")
}
fn parse_text_signature_attr<T: Display + quote::ToTokens + ?Sized>(
attr: &syn::Attribute,
python_name: &T,
text_signature: &mut Option<syn::LitStr>,
) -> syn::Result<Option<()>> {
if !is_text_signature_attr(attr) {
return Ok(None);
}
if text_signature.is_some() {
return Err(syn::Error::new_spanned(
attr,
"text_signature attribute already specified previously",
));
}
let value: String;
match attr.parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit),
..
}) => {
value = lit.value();
*text_signature = Some(lit);
}
meta => {
return Err(syn::Error::new_spanned(
meta,
"text_signature must be of the form #[text_signature = \"\"]",
));
}
};
let python_name_str = python_name.to_string();
let python_name_str = python_name_str
.rsplit('.')
.next()
.map(str::trim)
.filter(|v| !v.is_empty())
.ok_or_else(|| {
syn::Error::new_spanned(
&python_name,
format!("failed to parse python name: {}", python_name),
)
})?;
if !value.starts_with(&python_name_str) || !value[python_name_str.len()..].starts_with('(') {
return Err(syn::Error::new_spanned(
text_signature,
format!("text_signature must start with \"{}(\"", python_name_str),
));
}
if !value.ends_with(')') {
return Err(syn::Error::new_spanned(
text_signature,
"text_signature must end with \")\"",
));
}
Ok(Some(()))
}
pub fn parse_text_signature_attrs<T: Display + quote::ToTokens + ?Sized>(
attrs: &mut Vec<syn::Attribute>,
python_name: &T,
) -> syn::Result<Option<syn::LitStr>> {
let mut parse_error: Option<syn::Error> = None;
let mut text_signature = None;
attrs.retain(|attr| {
match parse_text_signature_attr(attr, python_name, &mut text_signature) {
Ok(None) => return true,
Ok(Some(_)) => {}
Err(err) => {
if let Some(parse_error) = &mut parse_error {
parse_error.combine(err);
} else {
parse_error = Some(err);
}
}
}
false
});
if let Some(parse_error) = parse_error {
return Err(parse_error);
}
Ok(text_signature)
}
// FIXME(althonos): not sure the docstring formatting is on par here.
pub fn get_doc(attrs: &[syn::Attribute], null_terminated: bool) -> syn::Lit {
pub fn get_doc(
attrs: &[syn::Attribute],
text_signature: Option<syn::LitStr>,
null_terminated: bool,
) -> syn::Result<syn::Lit> {
let mut doc = Vec::new();
let mut needs_terminating_newline = false;
if let Some(text_signature) = text_signature {
doc.push(text_signature.value());
doc.push("--".to_string());
doc.push(String::new());
needs_terminating_newline = true;
}
// TODO(althonos): set span on produced doc str literal
// let mut span = None;
@ -38,17 +138,22 @@ pub fn get_doc(attrs: &[syn::Attribute], null_terminated: bool) -> syn::Lit {
} else {
d
});
needs_terminating_newline = false;
} else {
panic!("Invalid doc comment");
return Err(syn::Error::new_spanned(metanv, "Invalid doc comment"));
}
}
}
}
if needs_terminating_newline {
doc.push(String::new());
}
let mut docstr = doc.join("\n");
if null_terminated {
docstr.push('\0');
}
syn::Lit::Str(syn::LitStr::new(&docstr, Span::call_site()))
Ok(syn::Lit::Str(syn::LitStr::new(&docstr, Span::call_site())))
}

View File

@ -27,7 +27,12 @@ pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
process_functions_in_module(&mut ast);
let expanded = py_init(&ast.sig.ident, &modname, get_doc(&ast.attrs, false));
let doc = match get_doc(&ast.attrs, None, false) {
Ok(doc) => doc,
Err(err) => return err.to_compile_error().into(),
};
let expanded = py_init(&ast.sig.ident, &modname, doc);
quote!(
#ast
@ -75,11 +80,11 @@ pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as syn::ItemFn);
let mut ast = parse_macro_input!(input as syn::ItemFn);
let args = parse_macro_input!(attr as PyFunctionAttr);
let python_name = syn::Ident::new(&ast.sig.ident.unraw().to_string(), Span::call_site());
let expanded = add_fn_to_module(&ast, &python_name, args.arguments);
let expanded = add_fn_to_module(&mut ast, &python_name, args.arguments);
quote!(
#ast