switch text_signature to arguments only & add tests
This commit is contained in:
parent
2915f50fc4
commit
af8c0d2531
|
@ -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 {
|
||||
|
|
|
@ -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(()))
|
||||
}
|
||||
|
||||
|
|
|
@ -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)'"
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue