initial untested implementation
This commit is contained in:
parent
42ee180116
commit
3c1a975ec0
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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())))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue