support `text_signature` on `#[new]`
This commit is contained in:
parent
157e0b4ad6
commit
8bd17f02c7
|
@ -722,22 +722,20 @@ py_args=(), py_kwargs=None, name=World, num=-1, num_before=44
|
|||
|
||||
## Making class method signatures available to Python
|
||||
|
||||
The [`text_signature = "..."`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
|
||||
The [`text_signature = "..."`](./function.md#text_signature) option for `#[pyfunction]` also works for `#[pymethods]`:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyType;
|
||||
|
||||
// it works even if the item is not documented:
|
||||
#[pyclass(text_signature = "(c, d, /)")]
|
||||
#[pyclass]
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
// the signature for the constructor is attached
|
||||
// to the struct definition instead.
|
||||
#[new]
|
||||
#[pyo3(text_signature = "(c, d)")]
|
||||
fn new(c: i32, d: &str) -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
@ -746,8 +744,9 @@ impl MyClass {
|
|||
fn my_method(&self, e: i32, f: i32) -> i32 {
|
||||
e + f
|
||||
}
|
||||
// similarly for classmethod arguments, use $cls
|
||||
#[classmethod]
|
||||
#[pyo3(text_signature = "(cls, e, f)")]
|
||||
#[pyo3(text_signature = "($cls, e, f)")]
|
||||
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
|
||||
e + f
|
||||
}
|
||||
|
@ -773,7 +772,7 @@ impl MyClass {
|
|||
# .call1((class,))?
|
||||
# .call_method0("__str__")?
|
||||
# .extract()?;
|
||||
# assert_eq!(sig, "(c, d, /)");
|
||||
# assert_eq!(sig, "(c, d)");
|
||||
# } else {
|
||||
# let doc: String = class.getattr("__doc__")?.extract()?;
|
||||
# assert_eq!(doc, "");
|
||||
|
@ -802,7 +801,7 @@ impl MyClass {
|
|||
# .call1((method,))?
|
||||
# .call_method0("__str__")?
|
||||
# .extract()?;
|
||||
# assert_eq!(sig, "(cls, e, f)");
|
||||
# assert_eq!(sig, "(e, f)"); // inspect.signature skips the $cls arg
|
||||
# }
|
||||
#
|
||||
# {
|
||||
|
@ -822,7 +821,7 @@ impl MyClass {
|
|||
# }
|
||||
```
|
||||
|
||||
Note that `text_signature` on classes is not compatible with compilation in
|
||||
Note that `text_signature` on `#[new]` is not compatible with compilation in
|
||||
`abi3` mode until Python 3.10 or greater.
|
||||
|
||||
## #[pyclass] enums
|
||||
|
@ -1018,7 +1017,6 @@ impl pyo3::IntoPy<PyObject> for MyClass {
|
|||
}
|
||||
|
||||
impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
||||
const DOC: &'static str = "Class for demonstration\u{0}";
|
||||
const IS_BASETYPE: bool = false;
|
||||
const IS_SUBCLASS: bool = false;
|
||||
type Layout = PyCell<MyClass>;
|
||||
|
@ -1041,6 +1039,15 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
|||
static TYPE_OBJECT: LazyTypeObject<MyClass> = LazyTypeObject::new();
|
||||
&TYPE_OBJECT
|
||||
}
|
||||
|
||||
fn doc(py: Python<'_>) -> pyo3::PyResult<&'static ::std::ffi::CStr> {
|
||||
use pyo3::impl_::pyclass::*;
|
||||
static DOC: pyo3::once_cell::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::once_cell::GILOnceCell::new();
|
||||
DOC.get_or_try_init(py, || {
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "", None.or_else(|| collector.new_text_signature()))
|
||||
}).map(::std::ops::Deref::deref)
|
||||
}
|
||||
}
|
||||
|
||||
# Python::with_gil(|py| {
|
||||
|
|
|
@ -42,7 +42,7 @@ fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
|||
Ok(val as i32)
|
||||
}
|
||||
```
|
||||
We also add documentation, via `///` comments and the `#[pyo3(text_signature = "...")]` attribute, both of which are visible to Python users.
|
||||
We also add documentation, via `///` comments, which are visible to Python users.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
|
@ -57,7 +57,6 @@ fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
|||
/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
|
||||
/// It's not a story C would tell you. It's a Rust legend.
|
||||
#[pyclass(module = "my_module")]
|
||||
#[pyo3(text_signature = "(int)")]
|
||||
struct Number(i32);
|
||||
|
||||
#[pymethods]
|
||||
|
@ -223,7 +222,6 @@ fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
|
|||
/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
|
||||
/// It's not a story C would tell you. It's a Rust legend.
|
||||
#[pyclass(module = "my_module")]
|
||||
#[pyo3(text_signature = "(int)")]
|
||||
struct Number(i32);
|
||||
|
||||
#[pymethods]
|
||||
|
@ -377,7 +375,7 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
|||
# assert Number(12345234523452) == Number(1498514748)
|
||||
# try:
|
||||
# import inspect
|
||||
# assert inspect.signature(Number).__str__() == '(int)'
|
||||
# assert inspect.signature(Number).__str__() == '(value)'
|
||||
# except ValueError:
|
||||
# # Not supported with `abi3` before Python 3.10
|
||||
# pass
|
||||
|
|
|
@ -272,9 +272,7 @@ impl MyClass {
|
|||
|
||||
The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]` directly from the Rust function, taking into account any override done with the `#[pyo3(signature = (...))]` option.
|
||||
|
||||
This automatic generation has some limitations, which may be improved in the future:
|
||||
- It will not include the value of default arguments, replacing them all with `...`. (`.pyi` type stub files commonly also use `...` for all default arguments in the same way.)
|
||||
- Nothing is generated for the `#[new]` method of a `#[pyclass]`.
|
||||
This automatic generation can only display the value of default arguments for strings, integers, boolean types, and `None`. Any other default arguments will be displayed as `...`. (`.pyi` type stub files commonly also use `...` for default arguments in the same way.)
|
||||
|
||||
In cases where the automatically-generated signature needs adjusting, it can [be overridden](#overriding-the-generated-signature) using the `#[pyo3(text_signature)]` option.)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Support `text_signature` option (and automatically generate signature) for `#[new]` in `#[pymethods]`.
|
|
@ -0,0 +1 @@
|
|||
Deprecate `text_signature` option on `#[pyclass]`.
|
|
@ -2,6 +2,7 @@ use proc_macro2::{Span, TokenStream};
|
|||
use quote::{quote_spanned, ToTokens};
|
||||
|
||||
pub enum Deprecation {
|
||||
PyClassTextSignature,
|
||||
PyFunctionArguments,
|
||||
PyMethodArgsAttribute,
|
||||
RequiredArgumentAfterOption,
|
||||
|
@ -10,6 +11,7 @@ pub enum Deprecation {
|
|||
impl Deprecation {
|
||||
fn ident(&self, span: Span) -> syn::Ident {
|
||||
let string = match self {
|
||||
Deprecation::PyClassTextSignature => "PYCLASS_TEXT_SIGNATURE",
|
||||
Deprecation::PyFunctionArguments => "PYFUNCTION_ARGUMENTS",
|
||||
Deprecation::PyMethodArgsAttribute => "PYMETHODS_ARGS_ATTRIBUTE",
|
||||
Deprecation::RequiredArgumentAfterOption => "REQUIRED_ARGUMENT_AFTER_OPTION",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::attributes::TextSignatureAttribute;
|
||||
use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue};
|
||||
use crate::deprecations::{Deprecation, Deprecations};
|
||||
use crate::params::impl_arg_params;
|
||||
use crate::pyfunction::{DeprecatedArgs, FunctionSignature, PyFunctionArgPyO3Attributes};
|
||||
|
@ -593,6 +593,33 @@ impl<'a> FnSpec<'a> {
|
|||
CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards to [utils::get_doc] with the text signature of this spec.
|
||||
pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc {
|
||||
let text_signature = self
|
||||
.text_signature_call_signature()
|
||||
.map(|sig| format!("{}{}", self.python_name, sig));
|
||||
utils::get_doc(attrs, text_signature)
|
||||
}
|
||||
|
||||
/// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature
|
||||
/// and/or attributes. Prepend the callable name to make a complete `__text_signature__`.
|
||||
pub fn text_signature_call_signature(&self) -> Option<String> {
|
||||
let self_argument = match &self.tp {
|
||||
// Getters / Setters / ClassAttribute are not callables on the Python side
|
||||
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None,
|
||||
FnType::Fn(_) => Some("self"),
|
||||
FnType::FnModule => Some("module"),
|
||||
FnType::FnClass => Some("cls"),
|
||||
FnType::FnStatic | FnType::FnNew => None,
|
||||
};
|
||||
|
||||
match self.text_signature.as_ref().map(|attr| &attr.value) {
|
||||
Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()),
|
||||
None => Some(self.signature.text_signature(self_argument)),
|
||||
Some(TextSignatureAttributeValue::Disabled(_)) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -755,11 +782,6 @@ fn ensure_signatures_on_valid_method(
|
|||
}
|
||||
if let Some(text_signature) = text_signature {
|
||||
match fn_type {
|
||||
FnType::FnNew => bail_spanned!(
|
||||
text_signature.kw.span() =>
|
||||
"`text_signature` not allowed on `__new__`; if you want to add a signature on \
|
||||
`__new__`, put it on the struct definition instead"
|
||||
),
|
||||
FnType::Getter(_) => {
|
||||
bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `getter`")
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ use std::borrow::Cow;
|
|||
use crate::attributes::{
|
||||
self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
|
||||
ModuleAttribute, NameAttribute, NameLitStr, TextSignatureAttribute,
|
||||
TextSignatureAttributeValue,
|
||||
};
|
||||
use crate::deprecations::Deprecations;
|
||||
use crate::deprecations::{Deprecation, Deprecations};
|
||||
use crate::konst::{ConstAttributes, ConstSpec};
|
||||
use crate::method::FnSpec;
|
||||
use crate::pyfunction::text_signature_or_none;
|
||||
use crate::pyimpl::{gen_py_const, PyClassMethodsType};
|
||||
use crate::pymethod::{
|
||||
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
|
||||
|
@ -177,7 +177,11 @@ impl PyClassPyO3Options {
|
|||
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
||||
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
||||
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
|
||||
PyClassPyO3Option::TextSignature(text_signature) => set_option!(text_signature),
|
||||
PyClassPyO3Option::TextSignature(text_signature) => {
|
||||
self.deprecations
|
||||
.push(Deprecation::PyClassTextSignature, text_signature.span());
|
||||
set_option!(text_signature)
|
||||
}
|
||||
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
|
||||
PyClassPyO3Option::Weakref(weakref) => set_option!(weakref),
|
||||
}
|
||||
|
@ -191,11 +195,7 @@ pub fn build_py_class(
|
|||
methods_type: PyClassMethodsType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
args.options.take_pyo3_options(&mut class.attrs)?;
|
||||
let text_signature_string = text_signature_or_none(args.options.text_signature.as_ref());
|
||||
let doc = utils::get_doc(
|
||||
&class.attrs,
|
||||
text_signature_string.map(|s| (get_class_python_name(&class.ident, &args), s)),
|
||||
);
|
||||
let doc = utils::get_doc(&class.attrs, None);
|
||||
let krate = get_pyo3_crate(&args.options.krate);
|
||||
|
||||
if let Some(lt) = class.generics.lifetimes().next() {
|
||||
|
@ -452,12 +452,7 @@ pub fn build_py_enum(
|
|||
bail_spanned!(enum_.brace_token.span => "#[pyclass] can't be used on enums without any variants");
|
||||
}
|
||||
|
||||
let text_signature_string = text_signature_or_none(args.options.text_signature.as_ref());
|
||||
|
||||
let doc = utils::get_doc(
|
||||
&enum_.attrs,
|
||||
text_signature_string.map(|s| (get_class_python_name(&enum_.ident, &args), s)),
|
||||
);
|
||||
let doc = utils::get_doc(&enum_.attrs, None);
|
||||
let enum_ = PyClassEnum::new(enum_)?;
|
||||
impl_enum(enum_, &args, doc, method_type)
|
||||
}
|
||||
|
@ -509,16 +504,6 @@ fn impl_enum(
|
|||
methods_type: PyClassMethodsType,
|
||||
) -> Result<TokenStream> {
|
||||
let krate = get_pyo3_crate(&args.options.krate);
|
||||
impl_enum_class(enum_, args, doc, methods_type, krate)
|
||||
}
|
||||
|
||||
fn impl_enum_class(
|
||||
enum_: PyClassEnum<'_>,
|
||||
args: &PyClassArgs,
|
||||
doc: PythonDoc,
|
||||
methods_type: PyClassMethodsType,
|
||||
krate: syn::Path,
|
||||
) -> Result<TokenStream> {
|
||||
let cls = enum_.ident;
|
||||
let ty: syn::Type = syn::parse_quote!(#cls);
|
||||
let variants = enum_.variants;
|
||||
|
@ -889,6 +874,18 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
fn impl_pyclassimpl(&self) -> Result<TokenStream> {
|
||||
let cls = self.cls;
|
||||
let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc});
|
||||
let deprecated_text_signature = match self
|
||||
.attr
|
||||
.options
|
||||
.text_signature
|
||||
.as_ref()
|
||||
.map(|attr| &attr.value)
|
||||
{
|
||||
Some(TextSignatureAttributeValue::Str(s)) => quote!(::std::option::Option::Some(#s)),
|
||||
Some(TextSignatureAttributeValue::Disabled(_)) | None => {
|
||||
quote!(::std::option::Option::None)
|
||||
}
|
||||
};
|
||||
let is_basetype = self.attr.options.subclass.is_some();
|
||||
let base = self
|
||||
.attr
|
||||
|
@ -1009,7 +1006,6 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
|
||||
Ok(quote! {
|
||||
impl _pyo3::impl_::pyclass::PyClassImpl for #cls {
|
||||
const DOC: &'static str = #doc;
|
||||
const IS_BASETYPE: bool = #is_basetype;
|
||||
const IS_SUBCLASS: bool = #is_subclass;
|
||||
const IS_MAPPING: bool = #is_mapping;
|
||||
|
@ -1035,6 +1031,15 @@ impl<'a> PyClassImplsBuilder<'a> {
|
|||
PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items)
|
||||
}
|
||||
|
||||
fn doc(py: _pyo3::Python<'_>) -> _pyo3::PyResult<&'static ::std::ffi::CStr> {
|
||||
use _pyo3::impl_::pyclass::*;
|
||||
static DOC: _pyo3::once_cell::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = _pyo3::once_cell::GILOnceCell::new();
|
||||
DOC.get_or_try_init(py, || {
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
build_pyclass_doc(<#cls as _pyo3::PyTypeInfo>::NAME, #doc, #deprecated_text_signature.or_else(|| collector.new_text_signature()))
|
||||
}).map(::std::ops::Deref::deref)
|
||||
}
|
||||
|
||||
#dict_offset
|
||||
|
||||
#weaklist_offset
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
attributes::{
|
||||
self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute,
|
||||
FromPyWithAttribute, NameAttribute, TextSignatureAttribute, TextSignatureAttributeValue,
|
||||
FromPyWithAttribute, NameAttribute, TextSignatureAttribute,
|
||||
},
|
||||
deprecations::{Deprecation, Deprecations},
|
||||
method::{self, CallingConvention, FnArg, FnType},
|
||||
method::{self, CallingConvention, FnArg},
|
||||
pymethod::check_generic,
|
||||
utils::{self, ensure_not_async_fn, get_pyo3_crate},
|
||||
utils::{ensure_not_async_fn, get_pyo3_crate},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
@ -409,15 +407,6 @@ pub fn impl_wrap_pyfunction(
|
|||
|
||||
let ty = method::get_return_info(&func.sig.output);
|
||||
|
||||
let text_signature_string = text_signature_or_auto(text_signature.as_ref(), &signature, &tp);
|
||||
|
||||
let doc = utils::get_doc(
|
||||
&func.attrs,
|
||||
text_signature_string.map(|s| (Cow::Borrowed(&python_name), s)),
|
||||
);
|
||||
|
||||
let krate = get_pyo3_crate(&krate);
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp,
|
||||
name: &func.sig.ident,
|
||||
|
@ -430,12 +419,14 @@ pub fn impl_wrap_pyfunction(
|
|||
unsafety: func.sig.unsafety,
|
||||
};
|
||||
|
||||
let krate = get_pyo3_crate(&krate);
|
||||
|
||||
let vis = &func.vis;
|
||||
let name = &func.sig.ident;
|
||||
|
||||
let wrapper_ident = format_ident!("__pyfunction_{}", spec.name);
|
||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, None)?;
|
||||
let methoddef = spec.get_methoddef(wrapper_ident, &doc);
|
||||
let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs));
|
||||
|
||||
let wrapped_pyfunction = quote! {
|
||||
|
||||
|
@ -480,26 +471,3 @@ fn type_is_pymodule(ty: &syn::Type) -> bool {
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Helper to get a text signature string, or None if unset or disabled
|
||||
pub(crate) fn text_signature_or_none(
|
||||
text_signature: Option<&TextSignatureAttribute>,
|
||||
) -> Option<String> {
|
||||
match text_signature.map(|attr| &attr.value) {
|
||||
Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()),
|
||||
Some(TextSignatureAttributeValue::Disabled(_)) | None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to get a text signature string, using automatic generation if unset, or None if disabled
|
||||
pub(crate) fn text_signature_or_auto(
|
||||
text_signature: Option<&TextSignatureAttribute>,
|
||||
signature: &FunctionSignature<'_>,
|
||||
fn_type: &FnType,
|
||||
) -> Option<String> {
|
||||
match text_signature.map(|attr| &attr.value) {
|
||||
Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()),
|
||||
None => Some(signature.text_signature(fn_type)),
|
||||
Some(TextSignatureAttributeValue::Disabled(_)) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use syn::{
|
|||
use crate::{
|
||||
attributes::{kw, KeywordAttribute},
|
||||
deprecations::{Deprecation, Deprecations},
|
||||
method::{FnArg, FnType},
|
||||
method::FnArg,
|
||||
pyfunction::Argument,
|
||||
};
|
||||
|
||||
|
@ -623,18 +623,7 @@ impl<'a> FunctionSignature<'a> {
|
|||
default
|
||||
}
|
||||
|
||||
pub fn text_signature(&self, fn_type: &FnType) -> String {
|
||||
// automatic text signature generation
|
||||
let self_argument = match fn_type {
|
||||
FnType::FnNew | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => {
|
||||
unreachable!()
|
||||
}
|
||||
FnType::Fn(_) => Some("self"),
|
||||
FnType::FnModule => Some("module"),
|
||||
FnType::FnClass => Some("cls"),
|
||||
FnType::FnStatic => None,
|
||||
};
|
||||
|
||||
pub fn text_signature(&self, self_argument: Option<&str>) -> String {
|
||||
let mut output = String::new();
|
||||
output.push('(');
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::borrow::Cow;
|
|||
|
||||
use crate::attributes::NameAttribute;
|
||||
use crate::method::{CallingConvention, ExtractErrorMode};
|
||||
use crate::pyfunction::text_signature_or_auto;
|
||||
use crate::utils::{ensure_not_async_fn, PythonDoc};
|
||||
use crate::{deprecations::Deprecations, utils};
|
||||
use crate::{
|
||||
|
@ -219,19 +218,19 @@ pub fn gen_py_method(
|
|||
(_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&create_doc(meth_attrs, spec),
|
||||
&spec.get_doc(meth_attrs),
|
||||
None,
|
||||
)?),
|
||||
(_, FnType::FnClass) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&create_doc(meth_attrs, spec),
|
||||
&spec.get_doc(meth_attrs),
|
||||
Some(quote!(_pyo3::ffi::METH_CLASS)),
|
||||
)?),
|
||||
(_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
|
||||
cls,
|
||||
spec,
|
||||
&create_doc(meth_attrs, spec),
|
||||
&spec.get_doc(meth_attrs),
|
||||
Some(quote!(_pyo3::ffi::METH_STATIC)),
|
||||
)?),
|
||||
// special prototypes
|
||||
|
@ -242,7 +241,7 @@ pub fn gen_py_method(
|
|||
PropertyType::Function {
|
||||
self_type,
|
||||
spec,
|
||||
doc: create_doc(meth_attrs, spec),
|
||||
doc: spec.get_doc(meth_attrs),
|
||||
},
|
||||
)?),
|
||||
(_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||
|
@ -250,7 +249,7 @@ pub fn gen_py_method(
|
|||
PropertyType::Function {
|
||||
self_type,
|
||||
spec,
|
||||
doc: create_doc(meth_attrs, spec),
|
||||
doc: spec.get_doc(meth_attrs),
|
||||
},
|
||||
)?),
|
||||
(_, FnType::FnModule) => {
|
||||
|
@ -259,18 +258,6 @@ pub fn gen_py_method(
|
|||
})
|
||||
}
|
||||
|
||||
fn create_doc(meth_attrs: &[syn::Attribute], spec: &FnSpec<'_>) -> PythonDoc {
|
||||
let text_signature_string = match &spec.tp {
|
||||
FnType::FnNew | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => None,
|
||||
_ => text_signature_or_auto(spec.text_signature.as_ref(), &spec.signature, &spec.tp),
|
||||
};
|
||||
|
||||
utils::get_doc(
|
||||
meth_attrs,
|
||||
text_signature_string.map(|sig| (Cow::Borrowed(&spec.python_name), sig)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
||||
let err_msg = |typ| format!("Python functions cannot have generic {} parameters", typ);
|
||||
for param in &sig.generics.params {
|
||||
|
@ -335,6 +322,13 @@ pub fn impl_py_method_def(
|
|||
fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<MethodAndSlotDef> {
|
||||
let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site());
|
||||
let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
||||
// Use just the text_signature_call_signature() because the class' Python name
|
||||
// isn't known to `#[pymethods]` - that has to be attached at runtime from the PyClassImpl
|
||||
// trait implementation created by `#[pyclass]`.
|
||||
let text_signature_body = spec.text_signature_call_signature().map_or_else(
|
||||
|| quote!(::std::option::Option::None),
|
||||
|text_signature| quote!(::std::option::Option::Some(#text_signature)),
|
||||
);
|
||||
let slot_def = quote! {
|
||||
_pyo3::ffi::PyType_Slot {
|
||||
slot: _pyo3::ffi::Py_tp_new,
|
||||
|
@ -345,6 +339,14 @@ fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<MethodAn
|
|||
kwargs: *mut _pyo3::ffi::PyObject,
|
||||
) -> *mut _pyo3::ffi::PyObject
|
||||
{
|
||||
use _pyo3::impl_::pyclass::*;
|
||||
impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> {
|
||||
#[inline]
|
||||
fn new_text_signature(self) -> ::std::option::Option<&'static str> {
|
||||
#text_signature_body
|
||||
}
|
||||
}
|
||||
|
||||
_pyo3::impl_::trampoline::newfunc(
|
||||
subtype,
|
||||
args,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use std::{borrow::Cow, fmt::Write};
|
||||
|
||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Ident, Token};
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
|
||||
|
||||
use crate::attributes::CrateAttribute;
|
||||
|
||||
|
@ -67,24 +65,20 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
|
|||
pub struct PythonDoc(TokenStream);
|
||||
|
||||
/// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a null-terminated string.
|
||||
pub fn get_doc(
|
||||
attrs: &[syn::Attribute],
|
||||
text_signature: Option<(Cow<'_, Ident>, String)>,
|
||||
) -> PythonDoc {
|
||||
let mut parts = Punctuated::<TokenStream, Token![,]>::new();
|
||||
let mut current_part = String::new();
|
||||
|
||||
if let Some((python_name, text_signature)) = text_signature {
|
||||
// create special doc string lines to set `__text_signature__`
|
||||
write!(
|
||||
&mut current_part,
|
||||
"{}{}\n--\n\n",
|
||||
python_name, text_signature
|
||||
)
|
||||
.expect("error occurred while trying to format text_signature to string")
|
||||
///
|
||||
/// If this doc is for a callable, the provided `text_signature` can be passed to prepend
|
||||
/// this to the documentation suitable for Python to extract this into the `__text_signature__`
|
||||
/// attribute.
|
||||
pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option<String>) -> PythonDoc {
|
||||
// insert special divider between `__text_signature__` and doc
|
||||
// (assume text_signature is itself well-formed)
|
||||
if let Some(text_signature) = &mut text_signature {
|
||||
text_signature.push_str("\n--\n\n");
|
||||
}
|
||||
|
||||
let mut parts = Punctuated::<TokenStream, Token![,]>::new();
|
||||
let mut first = true;
|
||||
let mut current_part = text_signature.unwrap_or_default();
|
||||
|
||||
for attr in attrs.iter() {
|
||||
if attr.path.is_ident("doc") {
|
||||
|
|
|
@ -17,3 +17,9 @@ pub const PYMETHODS_ARGS_ATTRIBUTE: () = ();
|
|||
note = "required arguments after an `Option<_>` argument are ambiguous and being phased out\n= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters"
|
||||
)]
|
||||
pub const REQUIRED_ARGUMENT_AFTER_OPTION: () = ();
|
||||
|
||||
#[deprecated(
|
||||
since = "0.19.0",
|
||||
note = "put `text_signature` on `#[new]` instead of `#[pyclass]`"
|
||||
)]
|
||||
pub const PYCLASS_TEXT_SIGNATURE: () = ();
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use crate::{
|
||||
exceptions::{PyAttributeError, PyNotImplementedError},
|
||||
exceptions::{PyAttributeError, PyNotImplementedError, PyValueError},
|
||||
ffi,
|
||||
impl_::freelist::FreeList,
|
||||
impl_::pycell::{GetBorrowChecker, PyClassMutability},
|
||||
internal_tricks::extract_c_string,
|
||||
pycell::PyCellLayout,
|
||||
pyclass_init::PyObjectInit,
|
||||
type_object::PyLayout,
|
||||
Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::{CStr, CString},
|
||||
marker::PhantomData,
|
||||
os::raw::{c_int, c_void},
|
||||
ptr::NonNull,
|
||||
|
@ -138,9 +141,6 @@ unsafe impl Sync for PyClassItems {}
|
|||
/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
|
||||
/// and may be changed at any time.
|
||||
pub trait PyClassImpl: Sized + 'static {
|
||||
/// Class doc string
|
||||
const DOC: &'static str = "\0";
|
||||
|
||||
/// #[pyclass(subclass)]
|
||||
const IS_BASETYPE: bool = false;
|
||||
|
||||
|
@ -184,12 +184,16 @@ pub trait PyClassImpl: Sized + 'static {
|
|||
#[cfg(feature = "multiple-pymethods")]
|
||||
type Inventory: PyClassInventory;
|
||||
|
||||
/// Rendered class doc
|
||||
fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
|
||||
|
||||
fn items_iter() -> PyClassItemsIter;
|
||||
|
||||
#[inline]
|
||||
fn dict_offset() -> Option<ffi::Py_ssize_t> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
|
||||
None
|
||||
|
@ -198,6 +202,29 @@ pub trait PyClassImpl: Sized + 'static {
|
|||
fn lazy_type_object() -> &'static LazyTypeObject<Self>;
|
||||
}
|
||||
|
||||
/// Runtime helper to build a class docstring from the `doc` and `text_signature`.
|
||||
///
|
||||
/// This is done at runtime because the class text signature is collected via dtolnay
|
||||
/// specialization in to the `#[pyclass]` macro from the `#[pymethods]` macro.
|
||||
pub fn build_pyclass_doc(
|
||||
class_name: &'static str,
|
||||
doc: &'static str,
|
||||
text_signature: Option<&'static str>,
|
||||
) -> PyResult<Cow<'static, CStr>> {
|
||||
if let Some(text_signature) = text_signature {
|
||||
let doc = CString::new(format!(
|
||||
"{}{}\n--\n\n{}",
|
||||
class_name,
|
||||
text_signature,
|
||||
doc.trim_end_matches('\0')
|
||||
))
|
||||
.map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
|
||||
Ok(Cow::Owned(doc))
|
||||
} else {
|
||||
extract_c_string(doc, "class doc cannot contain nul bytes")
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator used to process all class items during type instantiation.
|
||||
pub struct PyClassItemsIter {
|
||||
/// Iteration state
|
||||
|
@ -840,6 +867,18 @@ impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// Text signature for __new__
|
||||
pub trait PyClassNewTextSignature<T> {
|
||||
fn new_text_signature(self) -> Option<&'static str>;
|
||||
}
|
||||
|
||||
impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
|
||||
#[inline]
|
||||
fn new_text_signature(self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Thread checkers
|
||||
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::exceptions::PyValueError;
|
||||
use crate::internal_tricks::extract_c_string;
|
||||
use crate::{ffi, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, PyTraverseError, Python};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
|
@ -319,25 +319,3 @@ where
|
|||
self.map(|o| o.into_py(py))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_c_string(src: &'static str, err_msg: &'static str) -> PyResult<Cow<'static, CStr>> {
|
||||
let bytes = src.as_bytes();
|
||||
let cow = match bytes {
|
||||
[] => {
|
||||
// Empty string, we can trivially refer to a static "\0" string
|
||||
Cow::Borrowed(unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") })
|
||||
}
|
||||
[.., 0] => {
|
||||
// Last byte is a nul; try to create as a CStr
|
||||
let c_str =
|
||||
CStr::from_bytes_with_nul(bytes).map_err(|_| PyValueError::new_err(err_msg))?;
|
||||
Cow::Borrowed(c_str)
|
||||
}
|
||||
_ => {
|
||||
// Allocate a new CString for this
|
||||
let c_string = CString::new(bytes).map_err(|_| PyValueError::new_err(err_msg))?;
|
||||
Cow::Owned(c_string)
|
||||
}
|
||||
};
|
||||
Ok(cow)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
use crate::ffi::{Py_ssize_t, PY_SSIZE_T_MAX};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::{CStr, CString},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
exceptions::PyValueError,
|
||||
ffi::{Py_ssize_t, PY_SSIZE_T_MAX},
|
||||
PyResult,
|
||||
};
|
||||
pub struct PrivateMarker;
|
||||
|
||||
macro_rules! private_decl {
|
||||
|
@ -178,3 +187,28 @@ pub(crate) fn slice_end_index_len_fail(index: usize, ty_name: &str, len: usize)
|
|||
pub(crate) fn slice_index_order_fail(index: usize, end: usize) -> ! {
|
||||
panic!("slice index starts at {} but ends at {}", index, end);
|
||||
}
|
||||
|
||||
pub(crate) fn extract_c_string(
|
||||
src: &'static str,
|
||||
err_msg: &'static str,
|
||||
) -> PyResult<Cow<'static, CStr>> {
|
||||
let bytes = src.as_bytes();
|
||||
let cow = match bytes {
|
||||
[] => {
|
||||
// Empty string, we can trivially refer to a static "\0" string
|
||||
Cow::Borrowed(unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") })
|
||||
}
|
||||
[.., 0] => {
|
||||
// Last byte is a nul; try to create as a CStr
|
||||
let c_str =
|
||||
CStr::from_bytes_with_nul(bytes).map_err(|_| PyValueError::new_err(err_msg))?;
|
||||
Cow::Borrowed(c_str)
|
||||
}
|
||||
_ => {
|
||||
// Allocate a new CString for this
|
||||
let c_string = CString::new(bytes).map_err(|_| PyValueError::new_err(err_msg))?;
|
||||
Cow::Owned(c_string)
|
||||
}
|
||||
};
|
||||
Ok(cow)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ where
|
|||
{
|
||||
unsafe {
|
||||
PyTypeBuilder::default()
|
||||
.type_doc(T::DOC)
|
||||
.type_doc(T::doc(py)?)
|
||||
.offsets(T::dict_offset(), T::weaklist_offset())
|
||||
.slot(ffi::Py_tp_base, T::BaseType::type_object_raw(py))
|
||||
.slot(ffi::Py_tp_dealloc, tp_dealloc::<T> as *mut c_void)
|
||||
|
@ -233,25 +233,26 @@ impl PyTypeBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
fn type_doc(mut self, type_doc: &'static str) -> Self {
|
||||
if let Some(doc) = py_class_doc(type_doc) {
|
||||
unsafe { self.push_slot(ffi::Py_tp_doc, doc) }
|
||||
}
|
||||
fn type_doc(mut self, type_doc: &'static CStr) -> Self {
|
||||
let slice = type_doc.to_bytes();
|
||||
if !slice.is_empty() {
|
||||
unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
|
||||
|
||||
// Running this causes PyPy to segfault.
|
||||
#[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
|
||||
if type_doc != "\0" {
|
||||
// Until CPython 3.10, tp_doc was treated specially for
|
||||
// heap-types, and it removed the text_signature value from it.
|
||||
// We go in after the fact and replace tp_doc with something
|
||||
// that _does_ include the text_signature value!
|
||||
self.cleanup
|
||||
.push(Box::new(move |_self, type_object| unsafe {
|
||||
ffi::PyObject_Free((*type_object).tp_doc as _);
|
||||
let data = ffi::PyObject_Malloc(type_doc.len());
|
||||
data.copy_from(type_doc.as_ptr() as _, type_doc.len());
|
||||
(*type_object).tp_doc = data as _;
|
||||
}))
|
||||
// Running this causes PyPy to segfault.
|
||||
#[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
|
||||
{
|
||||
// Until CPython 3.10, tp_doc was treated specially for
|
||||
// heap-types, and it removed the text_signature value from it.
|
||||
// We go in after the fact and replace tp_doc with something
|
||||
// that _does_ include the text_signature value!
|
||||
self.cleanup
|
||||
.push(Box::new(move |_self, type_object| unsafe {
|
||||
ffi::PyObject_Free((*type_object).tp_doc as _);
|
||||
let data = ffi::PyMem_Malloc(slice.len());
|
||||
data.copy_from(slice.as_ptr() as _, slice.len());
|
||||
(*type_object).tp_doc = data as _;
|
||||
}))
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -383,24 +384,6 @@ impl PyTypeBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn py_class_doc(class_doc: &str) -> Option<*mut c_char> {
|
||||
match class_doc {
|
||||
"\0" => None,
|
||||
s => {
|
||||
// To pass *mut pointer to python safely, leak a CString in whichever case
|
||||
let cstring = if s.as_bytes().last() == Some(&0) {
|
||||
CStr::from_bytes_with_nul(s.as_bytes())
|
||||
.unwrap_or_else(|e| panic!("doc contains interior nul byte: {:?} in {}", e, s))
|
||||
.to_owned()
|
||||
} else {
|
||||
CString::new(s)
|
||||
.unwrap_or_else(|e| panic!("doc contains interior nul byte: {:?} in {}", e, s))
|
||||
};
|
||||
Some(cstring.into_raw())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<*mut c_char> {
|
||||
Ok(CString::new(format!(
|
||||
"{}.{}",
|
||||
|
|
|
@ -36,18 +36,14 @@ fn class_with_docs() {
|
|||
|
||||
#[test]
|
||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
||||
fn class_with_docs_and_signature() {
|
||||
/// docs line1
|
||||
fn class_with_signature_no_doc() {
|
||||
#[pyclass]
|
||||
/// docs line2
|
||||
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
|
||||
/// docs line3
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(signature = (a, b=None, *, c=42))]
|
||||
#[pyo3(signature = (a, b=None, *, c=42), text_signature = "(a, b=None, *, c=42)")]
|
||||
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
|
@ -56,12 +52,7 @@ fn class_with_docs_and_signature() {
|
|||
|
||||
Python::with_gil(|py| {
|
||||
let typeobj = py.get_type::<MyClass>();
|
||||
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
"typeobj.__doc__ == 'docs line1\\ndocs line2\\ndocs line3'"
|
||||
);
|
||||
py_assert!(py, typeobj, "typeobj.__doc__ == ''");
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
|
@ -72,15 +63,16 @@ fn class_with_docs_and_signature() {
|
|||
|
||||
#[test]
|
||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
||||
fn class_with_signature() {
|
||||
fn class_with_docs_and_signature() {
|
||||
/// docs line1
|
||||
#[pyclass]
|
||||
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
|
||||
/// docs line2
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(signature = (a, b=None, *, c=42))]
|
||||
#[pyo3(signature = (a, b=None, *, c=42), text_signature = "(a, b=None, *, c=42)")]
|
||||
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
|
@ -90,11 +82,7 @@ fn class_with_signature() {
|
|||
Python::with_gil(|py| {
|
||||
let typeobj = py.get_type::<MyClass>();
|
||||
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
"typeobj.__doc__ is None or typeobj.__doc__ == ''"
|
||||
);
|
||||
py_assert!(py, typeobj, "typeobj.__doc__ == 'docs line1\\ndocs line2'");
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
|
@ -209,6 +197,12 @@ fn test_auto_test_signature_method() {
|
|||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
fn new(a: i32, b: i32, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn method(&self, a: i32, b: i32, c: i32) {
|
||||
let _ = (a, b, c);
|
||||
}
|
||||
|
@ -244,6 +238,7 @@ fn test_auto_test_signature_method() {
|
|||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type::<MyClass>();
|
||||
py_assert!(py, cls, "cls.__text_signature__ == '(a, b, c)'");
|
||||
py_assert!(
|
||||
py,
|
||||
cls,
|
||||
|
@ -289,6 +284,13 @@ fn test_auto_test_signature_opt_out() {
|
|||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(text_signature = None)]
|
||||
fn new(a: i32, b: i32, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
}
|
||||
|
||||
#[pyo3(text_signature = None)]
|
||||
fn method(&self, a: i32, b: i32, c: i32) {
|
||||
let _ = (a, b, c);
|
||||
|
@ -320,6 +322,7 @@ fn test_auto_test_signature_opt_out() {
|
|||
py_assert!(py, f, "f.__text_signature__ == None");
|
||||
|
||||
let cls = py.get_type::<MyClass>();
|
||||
py_assert!(py, cls, "cls.__text_signature__ == None");
|
||||
py_assert!(py, cls, "cls.method.__text_signature__ == None");
|
||||
py_assert!(py, cls, "cls.method_2.__text_signature__ == None");
|
||||
py_assert!(py, cls, "cls.staticmethod.__text_signature__ == None");
|
||||
|
@ -407,7 +410,6 @@ fn test_methods() {
|
|||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
||||
fn test_raw_identifiers() {
|
||||
#[pyclass]
|
||||
#[pyo3(text_signature = "($self)")]
|
||||
struct r#MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -416,14 +418,13 @@ fn test_raw_identifiers() {
|
|||
fn new() -> MyClass {
|
||||
MyClass {}
|
||||
}
|
||||
#[pyo3(text_signature = "($self)")]
|
||||
fn r#method(&self) {}
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let typeobj = py.get_type::<MyClass>();
|
||||
|
||||
py_assert!(py, typeobj, "typeobj.__text_signature__ == '($self)'");
|
||||
py_assert!(py, typeobj, "typeobj.__text_signature__ == '()'");
|
||||
|
||||
py_assert!(
|
||||
py,
|
||||
|
@ -432,3 +433,111 @@ fn test_raw_identifiers() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
mod deprecated {
|
||||
use crate::py_assert;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
||||
fn class_with_docs_and_signature() {
|
||||
/// docs line1
|
||||
#[pyclass]
|
||||
/// docs line2
|
||||
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
|
||||
/// docs line3
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(signature = (a, b=None, *, c=42))]
|
||||
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
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]
|
||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
||||
fn class_with_deprecated_text_signature() {
|
||||
#[pyclass]
|
||||
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(signature = (a, b=None, *, c=42))]
|
||||
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let typeobj = py.get_type::<MyClass>();
|
||||
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
"typeobj.__doc__ is None or typeobj.__doc__ == ''"
|
||||
);
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
"typeobj.__text_signature__ == '(a, b=None, *, c=42)'"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
||||
fn class_with_deprecated_text_signature_and_on_new() {
|
||||
#[pyclass(text_signature = "(a, b=None, *, c=42)")]
|
||||
struct MyClass {}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(signature = (a, b=None, *, c=42), text_signature = "(NOT, THIS, ONE)")]
|
||||
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
|
||||
let _ = (a, b, c);
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let typeobj = py.get_type::<MyClass>();
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
"typeobj.__doc__ is None or typeobj.__doc__ == ''"
|
||||
);
|
||||
// Deprecated `#[pyclass(text_signature)]` attribute will be preferred
|
||||
// for backwards-compatibility.
|
||||
py_assert!(
|
||||
py,
|
||||
typeobj,
|
||||
"typeobj.__text_signature__ == '(a, b=None, *, c=42)'"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,13 +45,6 @@ impl MyClass {
|
|||
fn setter_without_receiver() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
#[pyo3(text_signature = "()")]
|
||||
fn text_signature_on_new() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[pyo3(name = "__call__", text_signature = "()")]
|
||||
|
|
|
@ -34,115 +34,120 @@ error: expected receiver for #[setter]
|
|||
45 | fn setter_without_receiver() {}
|
||||
| ^^
|
||||
|
||||
error: `text_signature` not allowed on `__new__`; if you want to add a signature on `__new__`, put it on the struct definition instead
|
||||
--> tests/ui/invalid_pymethods.rs:51:12
|
||||
|
|
||||
51 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: static method needs #[staticmethod] attribute
|
||||
--> tests/ui/invalid_pymethods.rs:58:5
|
||||
--> tests/ui/invalid_pymethods.rs:51:5
|
||||
|
|
||||
58 | fn text_signature_on_call() {}
|
||||
51 | fn text_signature_on_call() {}
|
||||
| ^^
|
||||
|
||||
error: `text_signature` not allowed with `getter`
|
||||
--> tests/ui/invalid_pymethods.rs:57:12
|
||||
|
|
||||
57 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: `text_signature` not allowed with `setter`
|
||||
--> tests/ui/invalid_pymethods.rs:64:12
|
||||
|
|
||||
64 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: `text_signature` not allowed with `setter`
|
||||
error: `text_signature` not allowed with `classattr`
|
||||
--> tests/ui/invalid_pymethods.rs:71:12
|
||||
|
|
||||
71 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: `text_signature` not allowed with `classattr`
|
||||
--> tests/ui/invalid_pymethods.rs:78:12
|
||||
|
|
||||
78 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: expected a string literal or `None`
|
||||
--> tests/ui/invalid_pymethods.rs:84:30
|
||||
--> tests/ui/invalid_pymethods.rs:77:30
|
||||
|
|
||||
84 | #[pyo3(text_signature = 1)]
|
||||
77 | #[pyo3(text_signature = 1)]
|
||||
| ^
|
||||
|
||||
error: `text_signature` may only be specified once
|
||||
--> tests/ui/invalid_pymethods.rs:91:12
|
||||
--> tests/ui/invalid_pymethods.rs:84:12
|
||||
|
|
||||
91 | #[pyo3(text_signature = None)]
|
||||
84 | #[pyo3(text_signature = None)]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: `signature` not allowed with `getter`
|
||||
--> tests/ui/invalid_pymethods.rs:91:12
|
||||
|
|
||||
91 | #[pyo3(signature = ())]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: `signature` not allowed with `setter`
|
||||
--> tests/ui/invalid_pymethods.rs:98:12
|
||||
|
|
||||
98 | #[pyo3(signature = ())]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: `signature` not allowed with `setter`
|
||||
error: `signature` not allowed with `classattr`
|
||||
--> tests/ui/invalid_pymethods.rs:105:12
|
||||
|
|
||||
105 | #[pyo3(signature = ())]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: `signature` not allowed with `classattr`
|
||||
--> tests/ui/invalid_pymethods.rs:112:12
|
||||
|
|
||||
112 | #[pyo3(signature = ())]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: cannot specify a second method type
|
||||
--> tests/ui/invalid_pymethods.rs:119:7
|
||||
--> tests/ui/invalid_pymethods.rs:112:7
|
||||
|
|
||||
119 | #[staticmethod]
|
||||
112 | #[staticmethod]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: Python functions cannot have generic type parameters
|
||||
--> tests/ui/invalid_pymethods.rs:125:23
|
||||
--> tests/ui/invalid_pymethods.rs:118:23
|
||||
|
|
||||
125 | fn generic_method<T>(value: T) {}
|
||||
118 | fn generic_method<T>(value: T) {}
|
||||
| ^
|
||||
|
||||
error: Python functions cannot have `impl Trait` arguments
|
||||
--> tests/ui/invalid_pymethods.rs:130:48
|
||||
--> tests/ui/invalid_pymethods.rs:123:48
|
||||
|
|
||||
130 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
|
||||
123 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
|
||||
| ^^^^
|
||||
|
||||
error: Python functions cannot have `impl Trait` arguments
|
||||
--> tests/ui/invalid_pymethods.rs:135:56
|
||||
--> tests/ui/invalid_pymethods.rs:128:56
|
||||
|
|
||||
135 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
|
||||
128 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
|
||||
| ^^^^
|
||||
|
||||
error: `async fn` is not yet supported for Python functions.
|
||||
|
||||
Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632
|
||||
--> tests/ui/invalid_pymethods.rs:140:5
|
||||
--> tests/ui/invalid_pymethods.rs:133:5
|
||||
|
|
||||
140 | async fn async_method(&self) {}
|
||||
133 | async fn async_method(&self) {}
|
||||
| ^^^^^
|
||||
|
||||
error: `pass_module` cannot be used on Python methods
|
||||
--> tests/ui/invalid_pymethods.rs:145:12
|
||||
--> tests/ui/invalid_pymethods.rs:138:12
|
||||
|
|
||||
145 | #[pyo3(pass_module)]
|
||||
138 | #[pyo3(pass_module)]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
|
||||
Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.
|
||||
--> tests/ui/invalid_pymethods.rs:151:29
|
||||
--> tests/ui/invalid_pymethods.rs:144:29
|
||||
|
|
||||
151 | fn method_self_by_value(self) {}
|
||||
144 | fn method_self_by_value(self) {}
|
||||
| ^^^^
|
||||
|
||||
error[E0592]: duplicate definitions with name `__pymethod___new____`
|
||||
--> tests/ui/invalid_pymethods.rs:156:1
|
||||
error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature<TwoNew>` for type `pyo3::impl_::pyclass::PyClassImplCollector<TwoNew>`
|
||||
--> tests/ui/invalid_pymethods.rs:149:1
|
||||
|
|
||||
156 | #[pymethods]
|
||||
149 | #[pymethods]
|
||||
| ^^^^^^^^^^^^
|
||||
| |
|
||||
| first implementation here
|
||||
| conflicting implementation for `pyo3::impl_::pyclass::PyClassImplCollector<TwoNew>`
|
||||
|
|
||||
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0592]: duplicate definitions with name `__pymethod___new____`
|
||||
--> tests/ui/invalid_pymethods.rs:149:1
|
||||
|
|
||||
149 | #[pymethods]
|
||||
| ^^^^^^^^^^^^
|
||||
| |
|
||||
| duplicate definitions for `__pymethod___new____`
|
||||
|
@ -151,9 +156,9 @@ error[E0592]: duplicate definitions with name `__pymethod___new____`
|
|||
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0592]: duplicate definitions with name `__pymethod_func__`
|
||||
--> tests/ui/invalid_pymethods.rs:171:1
|
||||
--> tests/ui/invalid_pymethods.rs:164:1
|
||||
|
|
||||
171 | #[pymethods]
|
||||
164 | #[pymethods]
|
||||
| ^^^^^^^^^^^^
|
||||
| |
|
||||
| duplicate definitions for `__pymethod_func__`
|
||||
|
|
Loading…
Reference in New Issue