switch text_signature to arguments only & add tests

This commit is contained in:
Jacob Lifshay 2019-11-29 12:19:56 -08:00
parent 2915f50fc4
commit af8c0d2531
3 changed files with 244 additions and 38 deletions

View File

@ -18,14 +18,32 @@ pub fn gen_py_method(
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::FnNew => {
// try to parse anyway to give better error messages
if let Some(type_signature) = utils::parse_text_signature_attrs(
&mut *meth_attrs,
&syn::Ident::new("__new__", name.span()),
)? {
return Err(syn::Error::new_spanned(
type_signature,
"type_signature not allowed on __new__, put it on the struct definition instead",
));
}
None
}
FnType::FnCall => {
// try to parse anyway to give better error messages
if let Some(type_signature) = utils::parse_text_signature_attrs(
&mut *meth_attrs,
&syn::Ident::new("__call__", name.span()),
)? {
return Err(syn::Error::new_spanned(
type_signature,
"type_signature not allowed on __call__, put it on the struct definition instead",
));
}
None
}
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 {

View File

@ -28,33 +28,17 @@ pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
fn parse_text_signature_attr<T: Display + quote::ToTokens + ?Sized>(
attr: &syn::Attribute,
python_name: &T,
text_signature: &mut Option<syn::LitStr>,
text_signature_line: &mut Option<syn::LitStr>,
) -> syn::Result<Option<()>> {
if !is_text_signature_attr(attr) {
return Ok(None);
}
if text_signature.is_some() {
if text_signature_line.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('.')
@ -67,18 +51,36 @@ fn parse_text_signature_attr<T: Display + quote::ToTokens + ?Sized>(
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 \")\"",
));
}
match attr.parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit),
..
}) => {
let value = lit.value();
if !value.starts_with('(') {
return Err(syn::Error::new_spanned(
lit,
"text_signature must start with \"(\"",
));
}
if !value.ends_with(')') {
return Err(syn::Error::new_spanned(
lit,
"text_signature must end with \")\"",
));
}
*text_signature_line = Some(syn::LitStr::new(
&(python_name_str.to_owned() + &value),
lit.span(),
));
}
meta => {
return Err(syn::Error::new_spanned(
meta,
"text_signature must be of the form #[text_signature = \"\"]",
));
}
};
Ok(Some(()))
}

View File

@ -0,0 +1,186 @@
use pyo3::prelude::*;
use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule};
mod common;
#[test]
fn class_without_docs_or_signature() {
#[pyclass]
struct MyClass {}
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ is None");
py_assert!(py, typeobj, "typeobj.__text_signature__ is None");
}
#[test]
fn class_with_docs() {
/// docs line1
#[pyclass]
/// docs line2
struct MyClass {}
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ == 'docs line1\\ndocs line2'");
py_assert!(py, typeobj, "typeobj.__text_signature__ is None");
}
#[test]
fn class_with_docs_and_signature() {
/// docs line1
#[pyclass]
/// docs line2
#[text_signature = "(a, b=None, *, c=42)"]
/// docs line3
struct MyClass {}
#[pymethods]
impl MyClass {
#[new]
#[args(a, b = "None", "*", c = 42)]
fn __new__(obj: &PyRawObject, a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
obj.init(Self {});
}
}
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<MyClass>();
py_assert!(
py,
typeobj,
"typeobj.__doc__ == 'docs line1\\ndocs line2\\ndocs line3'"
);
py_assert!(
py,
typeobj,
"typeobj.__text_signature__ == '(a, b=None, *, c=42)'"
);
}
#[test]
fn class_with_signature() {
#[pyclass]
#[text_signature = "(a, b=None, *, c=42)"]
struct MyClass {}
#[pymethods]
impl MyClass {
#[new]
#[args(a, b = "None", "*", c = 42)]
fn __new__(obj: &PyRawObject, a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
obj.init(Self {});
}
}
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ is None");
py_assert!(
py,
typeobj,
"typeobj.__text_signature__ == '(a, b=None, *, c=42)'"
);
}
#[test]
fn test_function() {
#[pyfunction(a, b = "None", "*", c = 42)]
#[text_signature = "(a, b=None, *, c=42)"]
fn my_function(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(my_function)(py);
py_assert!(py, f, "f.__text_signature__ == '(a, b=None, *, c=42)'");
}
#[test]
fn test_pyfn() {
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m, "my_function", a, b = "None", "*", c = 42)]
#[text_signature = "(a, b=None, *, c=42)"]
fn my_function(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
Ok(())
}
let gil = Python::acquire_gil();
let py = gil.python();
let m = wrap_pymodule!(my_module)(py);
py_assert!(
py,
m,
"m.my_function.__text_signature__ == '(a, b=None, *, c=42)'"
);
}
#[test]
fn test_methods() {
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[text_signature = "($self, a)"]
fn method(&self, a: i32) {
let _ = a;
}
#[text_signature = "($self, b)"]
fn pyself_method(_this: PyRef<Self>, b: i32) {
let _ = b;
}
#[classmethod]
#[text_signature = "($cls, c)"]
fn class_method(_cls: &PyType, c: i32) {
let _ = c;
}
#[staticmethod]
#[text_signature = "(d)"]
fn static_method(d: i32) {
let _ = d;
}
}
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<MyClass>();
py_assert!(
py,
typeobj,
"typeobj.method.__text_signature__ == '($self, a)'"
);
py_assert!(
py,
typeobj,
"typeobj.pyself_method.__text_signature__ == '($self, b)'"
);
py_assert!(
py,
typeobj,
"typeobj.class_method.__text_signature__ == '($cls, c)'"
);
py_assert!(
py,
typeobj,
"typeobj.static_method.__text_signature__ == '(d)'"
);
}