macros: support `#[pyo3(name = "...")]`
This commit is contained in:
parent
4613b3dd7e
commit
4d46abde73
|
@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Add FFI definition `_Py_InitializeMain`. [#1473](https://github.com/PyO3/pyo3/pull/1473)
|
||||
- Add FFI definitions from `cpython/import.h`.[#1475](https://github.com/PyO3/pyo3/pull/1475)
|
||||
- Add tuple and unit struct support for `#[pyclass]` macro. [#1504](https://github.com/PyO3/pyo3/pull/1504)
|
||||
- Add `#[pyo3(name = "...")]` syntax for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
- Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572)
|
||||
- Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591)
|
||||
|
||||
|
@ -40,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- `PyMappingProtocol::__reversed__`
|
||||
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
|
||||
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
|
||||
- Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||
|
||||
### Removed
|
||||
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
||||
|
|
|
@ -475,7 +475,7 @@ pub fn solve<T: Model>(model: &mut T) {
|
|||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[name = "solve"]
|
||||
#[pyo3(name = "solve")]
|
||||
pub fn solve_wrapper(model: &mut UserModel) {
|
||||
solve(model);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
Attribute, ExprPath, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
pub mod kw {
|
||||
syn::custom_keyword!(annotation);
|
||||
syn::custom_keyword!(attribute);
|
||||
syn::custom_keyword!(from_py_with);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FromPyWithAttribute(pub ExprPath);
|
||||
|
||||
impl Parse for FromPyWithAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let _: kw::from_py_with = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(FromPyWithAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NameAttribute(pub Ident);
|
||||
|
||||
impl Parse for NameAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let _: kw::name = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(NameAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pyo3_attribute<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
|
||||
if attribute_ident_is(attr, "pyo3") {
|
||||
attr.parse_args_with(Punctuated::parse_terminated).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attribute_ident_is(attr: &syn::Attribute, name: &str) -> bool {
|
||||
if let Some(path_segment) = attr.path.segments.last() {
|
||||
attr.path.segments.len() == 1 && path_segment.ident == name
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes attributes from an attribute vector.
|
||||
///
|
||||
/// For each attribute in `attrs`, `extractor` is called. If `extractor` returns `Ok(true)`, then
|
||||
/// the attribute will be removed from the vector.
|
||||
///
|
||||
/// This is similar to `Vec::retain` except the closure is fallible and the condition is reversed.
|
||||
/// (In `retain`, returning `true` keeps the element, here it removes it.)
|
||||
pub fn take_attributes(
|
||||
attrs: &mut Vec<Attribute>,
|
||||
mut extractor: impl FnMut(&Attribute) -> Result<bool>,
|
||||
) -> Result<()> {
|
||||
*attrs = attrs
|
||||
.drain(..)
|
||||
.filter_map(|attr| {
|
||||
extractor(&attr)
|
||||
.map(move |attribute_handled| if attribute_handled { None } else { Some(attr) })
|
||||
.transpose()
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_deprecated_name_attribute(attr: &syn::Attribute) -> syn::Result<Option<NameAttribute>> {
|
||||
match attr.parse_meta() {
|
||||
Ok(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
path,
|
||||
lit: syn::Lit::Str(s),
|
||||
..
|
||||
})) if path.is_ident("name") => {
|
||||
let mut ident: syn::Ident = s.parse()?;
|
||||
// This span is the whole attribute span, which is nicer for reporting errors.
|
||||
ident.set_span(attr.span());
|
||||
Ok(Some(NameAttribute(ident)))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use syn::spanned::Spanned;
|
||||
use syn::{ExprPath, Lit, Meta, MetaNameValue, Result};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FromPyWithAttribute(pub ExprPath);
|
||||
|
||||
impl FromPyWithAttribute {
|
||||
pub fn from_meta(meta: Meta) -> Result<Self> {
|
||||
let string_literal = match meta {
|
||||
Meta::NameValue(MetaNameValue {
|
||||
lit: Lit::Str(string_literal),
|
||||
..
|
||||
}) => string_literal,
|
||||
meta => {
|
||||
bail_spanned!(meta.span() => "expected a name-value: `pyo3(from_py_with = \"func\")`")
|
||||
}
|
||||
};
|
||||
|
||||
let expr_path = string_literal.parse::<ExprPath>()?;
|
||||
Ok(FromPyWithAttribute(expr_path))
|
||||
}
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
use crate::attrs::FromPyWithAttribute;
|
||||
use crate::attributes::{self, get_pyo3_attribute, FromPyWithAttribute};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Meta, MetaList, Result};
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
Attribute, DataEnum, DeriveInput, Fields, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
/// Describes derivation input of an enum.
|
||||
#[derive(Debug)]
|
||||
|
@ -26,7 +31,7 @@ impl<'a> Enum<'a> {
|
|||
.variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
let attrs = ContainerAttribute::parse_attrs(&variant.attrs)?;
|
||||
let attrs = ContainerOptions::from_attrs(&variant.attrs)?;
|
||||
let var_ident = &variant.ident;
|
||||
Container::new(
|
||||
&variant.fields,
|
||||
|
@ -86,7 +91,7 @@ enum ContainerType<'a> {
|
|||
/// Struct Container, e.g. `struct Foo { a: String }`
|
||||
///
|
||||
/// Variant contains the list of field identifiers and the corresponding extraction call.
|
||||
Struct(Vec<(&'a Ident, FieldAttributes)>),
|
||||
Struct(Vec<(&'a Ident, FieldPyO3Attributes)>),
|
||||
/// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }`
|
||||
///
|
||||
/// The field specified by the identifier is extracted directly from the object.
|
||||
|
@ -119,20 +124,20 @@ impl<'a> Container<'a> {
|
|||
fn new(
|
||||
fields: &'a Fields,
|
||||
path: syn::Path,
|
||||
attrs: Vec<ContainerAttribute>,
|
||||
options: ContainerOptions,
|
||||
is_enum_variant: bool,
|
||||
) -> Result<Self> {
|
||||
ensure_spanned!(
|
||||
!fields.is_empty(),
|
||||
fields.span() => "cannot derive FromPyObject for empty structs and variants"
|
||||
);
|
||||
let transparent = attrs
|
||||
.iter()
|
||||
.any(|attr| *attr == ContainerAttribute::Transparent);
|
||||
if transparent {
|
||||
Self::check_transparent_len(fields)?;
|
||||
if options.transparent {
|
||||
ensure_spanned!(
|
||||
fields.len() == 1,
|
||||
fields.span() => "transparent structs and variants can only have 1 field"
|
||||
);
|
||||
}
|
||||
let style = match (fields, transparent) {
|
||||
let style = match (fields, options.transparent) {
|
||||
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
|
||||
(Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() {
|
||||
1 => ContainerType::TupleNewtype,
|
||||
|
@ -157,17 +162,17 @@ impl<'a> Container<'a> {
|
|||
.ident
|
||||
.as_ref()
|
||||
.expect("Named fields should have identifiers");
|
||||
let attrs = FieldAttributes::parse_attrs(&field.attrs)?;
|
||||
let attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?;
|
||||
fields.push((ident, attrs))
|
||||
}
|
||||
ContainerType::Struct(fields)
|
||||
}
|
||||
(Fields::Unit, _) => unreachable!(), // covered by length check above
|
||||
};
|
||||
let err_name = attrs
|
||||
.iter()
|
||||
.find_map(|a| a.annotation().map(syn::LitStr::value))
|
||||
.unwrap_or_else(|| path.segments.last().unwrap().ident.to_string());
|
||||
let err_name = options.annotation.map_or_else(
|
||||
|| path.segments.last().unwrap().ident.to_string(),
|
||||
|lit_str| lit_str.value(),
|
||||
);
|
||||
|
||||
let v = Container {
|
||||
path,
|
||||
|
@ -178,18 +183,6 @@ impl<'a> Container<'a> {
|
|||
Ok(v)
|
||||
}
|
||||
|
||||
fn verify_struct_container_attrs(attrs: &'a [ContainerAttribute]) -> Result<()> {
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
ContainerAttribute::Transparent => {}
|
||||
ContainerAttribute::ErrorAnnotation(annotation) => bail_spanned!(
|
||||
annotation.span() => "annotation is not supported for structs"
|
||||
),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build derivation body for a struct.
|
||||
fn build(&self) -> TokenStream {
|
||||
match &self.ty {
|
||||
|
@ -235,7 +228,7 @@ impl<'a> Container<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
fn build_struct(&self, tups: &[(&Ident, FieldAttributes)]) -> TokenStream {
|
||||
fn build_struct(&self, tups: &[(&Ident, FieldPyO3Attributes)]) -> TokenStream {
|
||||
let self_ty = &self.path;
|
||||
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
||||
for (ident, attrs) in tups {
|
||||
|
@ -256,67 +249,73 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
quote!(Ok(#self_ty{#fields}))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_transparent_len(fields: &Fields) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
fields.len() == 1,
|
||||
fields.span() => "transparent structs and variants can only have 1 field"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
struct ContainerOptions {
|
||||
transparent: bool,
|
||||
annotation: Option<syn::LitStr>,
|
||||
}
|
||||
|
||||
/// Attributes for deriving FromPyObject scoped on containers.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum ContainerAttribute {
|
||||
enum ContainerPyO3Attribute {
|
||||
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
|
||||
Transparent,
|
||||
Transparent(attributes::kw::transparent),
|
||||
/// Change the name of an enum variant in the generated error message.
|
||||
ErrorAnnotation(syn::LitStr),
|
||||
ErrorAnnotation(LitStr),
|
||||
}
|
||||
|
||||
impl ContainerAttribute {
|
||||
/// Convenience method to access `ErrorAnnotation`.
|
||||
fn annotation(&self) -> Option<&syn::LitStr> {
|
||||
match self {
|
||||
ContainerAttribute::ErrorAnnotation(s) => Some(s),
|
||||
_ => None,
|
||||
impl Parse for ContainerPyO3Attribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::transparent) {
|
||||
let kw: attributes::kw::transparent = input.parse()?;
|
||||
Ok(ContainerPyO3Attribute::Transparent(kw))
|
||||
} else if lookahead.peek(attributes::kw::annotation) {
|
||||
let _: attributes::kw::annotation = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
input.parse().map(ContainerPyO3Attribute::ErrorAnnotation)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse valid container arguments
|
||||
///
|
||||
/// Fails if any are invalid.
|
||||
fn parse_attrs(value: &[Attribute]) -> Result<Vec<Self>> {
|
||||
get_pyo3_meta_list(value)?
|
||||
.nested
|
||||
.into_iter()
|
||||
.map(|meta| {
|
||||
if let syn::NestedMeta::Meta(metaitem) = &meta {
|
||||
match metaitem {
|
||||
Meta::Path(p) if p.is_ident("transparent") => {
|
||||
return Ok(ContainerAttribute::Transparent);
|
||||
impl ContainerOptions {
|
||||
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
|
||||
let mut options = ContainerOptions {
|
||||
transparent: false,
|
||||
annotation: None,
|
||||
};
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
match pyo3_attr {
|
||||
ContainerPyO3Attribute::Transparent(kw) => {
|
||||
ensure_spanned!(
|
||||
!options.transparent,
|
||||
kw.span() => "`transparent` may only be provided once"
|
||||
);
|
||||
options.transparent = true;
|
||||
}
|
||||
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
|
||||
if let syn::Lit::Str(s) = &nv.lit {
|
||||
return Ok(ContainerAttribute::ErrorAnnotation(s.clone()));
|
||||
} else {
|
||||
bail_spanned!(nv.lit.span() => "expected string literal for annotation");
|
||||
}
|
||||
ContainerPyO3Attribute::ErrorAnnotation(lit_str) => {
|
||||
ensure_spanned!(
|
||||
options.annotation.is_none(),
|
||||
lit_str.span() => "`annotation` may only be provided once"
|
||||
);
|
||||
options.annotation = Some(lit_str);
|
||||
}
|
||||
_ => {} // return Err below
|
||||
}
|
||||
}
|
||||
|
||||
bail_spanned!(meta.span() => "unknown `pyo3` container attribute");
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attributes for deriving FromPyObject scoped on fields.
|
||||
#[derive(Clone, Debug)]
|
||||
struct FieldAttributes {
|
||||
struct FieldPyO3Attributes {
|
||||
getter: FieldGetter,
|
||||
from_py_with: Option<FromPyWithAttribute>,
|
||||
}
|
||||
|
@ -324,121 +323,96 @@ struct FieldAttributes {
|
|||
#[derive(Clone, Debug)]
|
||||
enum FieldGetter {
|
||||
GetItem(Option<syn::Lit>),
|
||||
GetAttr(Option<syn::LitStr>),
|
||||
GetAttr(Option<LitStr>),
|
||||
}
|
||||
|
||||
impl FieldAttributes {
|
||||
enum FieldPyO3Attribute {
|
||||
Getter(FieldGetter),
|
||||
FromPyWith(FromPyWithAttribute),
|
||||
}
|
||||
|
||||
impl Parse for FieldPyO3Attribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::attribute) {
|
||||
let _: attributes::kw::attribute = input.parse()?;
|
||||
if input.peek(syn::token::Paren) {
|
||||
let content;
|
||||
let _ = parenthesized!(content in input);
|
||||
let attr_name: LitStr = content.parse()?;
|
||||
if !content.is_empty() {
|
||||
return Err(content.error(
|
||||
"expected at most one argument: `attribute` or `attribute(\"name\")`",
|
||||
));
|
||||
}
|
||||
ensure_spanned!(
|
||||
!attr_name.value().is_empty(),
|
||||
attr_name.span() => "attribute name cannot be empty"
|
||||
);
|
||||
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetAttr(Some(
|
||||
attr_name,
|
||||
))))
|
||||
} else {
|
||||
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetAttr(None)))
|
||||
}
|
||||
} else if lookahead.peek(attributes::kw::item) {
|
||||
let _: attributes::kw::item = input.parse()?;
|
||||
if input.peek(syn::token::Paren) {
|
||||
let content;
|
||||
let _ = parenthesized!(content in input);
|
||||
let key = content.parse()?;
|
||||
if !content.is_empty() {
|
||||
return Err(
|
||||
content.error("expected at most one argument: `item` or `item(key)`")
|
||||
);
|
||||
}
|
||||
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetItem(Some(key))))
|
||||
} else {
|
||||
Ok(FieldPyO3Attribute::Getter(FieldGetter::GetItem(None)))
|
||||
}
|
||||
} else if lookahead.peek(attributes::kw::from_py_with) {
|
||||
input.parse().map(FieldPyO3Attribute::FromPyWith)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldPyO3Attributes {
|
||||
/// Extract the field attributes.
|
||||
///
|
||||
fn parse_attrs(attrs: &[Attribute]) -> Result<Self> {
|
||||
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
|
||||
let mut getter = None;
|
||||
let mut from_py_with = None;
|
||||
|
||||
let list = get_pyo3_meta_list(attrs)?;
|
||||
|
||||
for meta_item in list.nested {
|
||||
let meta = match meta_item {
|
||||
syn::NestedMeta::Meta(meta) => meta,
|
||||
syn::NestedMeta::Lit(lit) => bail_spanned!(
|
||||
lit.span() =>
|
||||
"expected `attribute`, `item` or `from_py_with`, got a literal"
|
||||
),
|
||||
};
|
||||
let path = meta.path();
|
||||
|
||||
if path.is_ident("attribute") {
|
||||
ensure_spanned!(
|
||||
getter.is_none(),
|
||||
meta.span() => "only one of `attribute` or `item` can be provided"
|
||||
);
|
||||
getter = Some(FieldGetter::GetAttr(Self::attribute_arg(meta)?))
|
||||
} else if path.is_ident("item") {
|
||||
ensure_spanned!(
|
||||
getter.is_none(),
|
||||
meta.span() => "only one of `attribute` or `item` can be provided"
|
||||
);
|
||||
getter = Some(FieldGetter::GetItem(Self::item_arg(meta)?))
|
||||
} else if path.is_ident("from_py_with") {
|
||||
from_py_with = Some(Self::from_py_with_arg(meta)?)
|
||||
} else {
|
||||
bail_spanned!(meta.span() => "expected `attribute`, `item` or `from_py_with`")
|
||||
};
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
match pyo3_attr {
|
||||
FieldPyO3Attribute::Getter(field_getter) => {
|
||||
ensure_spanned!(
|
||||
getter.is_none(),
|
||||
attr.span() => "only one of `attribute` or `item` can be provided"
|
||||
);
|
||||
getter = Some(field_getter)
|
||||
}
|
||||
FieldPyO3Attribute::FromPyWith(from_py_with_attr) => {
|
||||
ensure_spanned!(
|
||||
from_py_with.is_none(),
|
||||
attr.span() => "`from_py_with` may only be provided once"
|
||||
);
|
||||
from_py_with = Some(from_py_with_attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FieldAttributes {
|
||||
Ok(FieldPyO3Attributes {
|
||||
getter: getter.unwrap_or(FieldGetter::GetAttr(None)),
|
||||
from_py_with,
|
||||
})
|
||||
}
|
||||
|
||||
fn attribute_arg(meta: Meta) -> syn::Result<Option<syn::LitStr>> {
|
||||
let mut arg_list = match meta {
|
||||
Meta::List(list) => list,
|
||||
Meta::Path(_) => return Ok(None),
|
||||
Meta::NameValue(nv) => bail_spanned!(
|
||||
nv.span() =>
|
||||
"expected a string literal or no argument: `pyo3(attribute(\"name\")` or \
|
||||
`pyo3(attribute)`"
|
||||
),
|
||||
};
|
||||
|
||||
if arg_list.nested.len() == 1 {
|
||||
let arg = arg_list.nested.pop().unwrap().into_value();
|
||||
|
||||
if let syn::NestedMeta::Lit(syn::Lit::Str(litstr)) = arg {
|
||||
ensure_spanned!(
|
||||
!litstr.value().is_empty(),
|
||||
litstr.span() => "attribute name cannot be empty"
|
||||
);
|
||||
return Ok(Some(litstr));
|
||||
}
|
||||
}
|
||||
|
||||
bail_spanned!(arg_list.span() => "expected a single string literal argument");
|
||||
}
|
||||
|
||||
fn item_arg(meta: Meta) -> syn::Result<Option<syn::Lit>> {
|
||||
let mut arg_list = match meta {
|
||||
Meta::List(list) => list,
|
||||
Meta::Path(_) => return Ok(None),
|
||||
Meta::NameValue(nv) => bail_spanned!(
|
||||
nv.span() => "expected a literal or no argument: `pyo3(item(key)` or `pyo3(item)`"
|
||||
),
|
||||
};
|
||||
|
||||
if arg_list.nested.len() == 1 {
|
||||
let arg = arg_list.nested.pop().unwrap().into_value();
|
||||
if let syn::NestedMeta::Lit(lit) = arg {
|
||||
return Ok(Some(lit));
|
||||
}
|
||||
}
|
||||
|
||||
bail_spanned!(arg_list.span() => "expected a single literal argument");
|
||||
}
|
||||
|
||||
fn from_py_with_arg(meta: Meta) -> syn::Result<FromPyWithAttribute> {
|
||||
FromPyWithAttribute::from_meta(meta)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract pyo3 metalist, flattens multiple lists into a single one.
|
||||
fn get_pyo3_meta_list(attrs: &[Attribute]) -> Result<MetaList> {
|
||||
let mut list: Punctuated<syn::NestedMeta, syn::Token![,]> = Punctuated::new();
|
||||
for value in attrs {
|
||||
match value.parse_meta()? {
|
||||
Meta::List(ml) if value.path.is_ident("pyo3") => {
|
||||
for meta in ml.nested {
|
||||
list.push(meta);
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(MetaList {
|
||||
path: parse_quote!(pyo3),
|
||||
paren_token: syn::token::Paren::default(),
|
||||
nested: list,
|
||||
})
|
||||
}
|
||||
|
||||
fn verify_and_get_lifetime(generics: &syn::Generics) -> Result<Option<&syn::LifetimeDef>> {
|
||||
|
@ -481,10 +455,12 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
|||
en.build()
|
||||
}
|
||||
syn::Data::Struct(st) => {
|
||||
let attrs = ContainerAttribute::parse_attrs(&tokens.attrs)?;
|
||||
Container::verify_struct_container_attrs(&attrs)?;
|
||||
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
|
||||
if let Some(lit_str) = &options.annotation {
|
||||
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
|
||||
}
|
||||
let ident = &tokens.ident;
|
||||
let st = Container::new(&st.fields, parse_quote!(#ident), attrs, false)?;
|
||||
let st = Container::new(&st.fields, parse_quote!(#ident), options, false)?;
|
||||
st.build()
|
||||
}
|
||||
syn::Data::Union(_) => bail_spanned!(
|
||||
|
|
|
@ -1,34 +1,99 @@
|
|||
use crate::pyfunction::parse_name_attribute;
|
||||
use syn::ext::IdentExt;
|
||||
use crate::attributes::{
|
||||
self, attribute_ident_is, get_deprecated_name_attribute, get_pyo3_attribute, take_attributes,
|
||||
NameAttribute,
|
||||
};
|
||||
use crate::utils;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Result,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct ConstSpec {
|
||||
pub is_class_attr: bool,
|
||||
pub python_name: syn::Ident,
|
||||
pub rust_ident: syn::Ident,
|
||||
pub attributes: ConstAttributes,
|
||||
}
|
||||
|
||||
impl ConstSpec {
|
||||
// For now, the only valid attribute is `#[classattr]`.
|
||||
pub fn parse(name: &syn::Ident, attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConstSpec> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut is_class_attr = false;
|
||||
|
||||
for attr in attrs.iter() {
|
||||
if let syn::Meta::Path(name) = attr.parse_meta()? {
|
||||
if name.is_ident("classattr") {
|
||||
is_class_attr = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
new_attrs.push(attr.clone());
|
||||
/// Null-terminated Python name
|
||||
pub fn python_name_with_deprecation(&self) -> TokenStream {
|
||||
if let Some(name) = &self.attributes.name {
|
||||
let deprecation =
|
||||
utils::name_deprecation_token(name.0.span(), self.attributes.name_is_deprecated);
|
||||
let name = format!("{}\0", name.0);
|
||||
quote!({#deprecation #name})
|
||||
} else {
|
||||
let name = format!("{}\0", self.rust_ident.unraw().to_string());
|
||||
quote!(#name)
|
||||
}
|
||||
|
||||
attrs.clear();
|
||||
attrs.extend(new_attrs);
|
||||
|
||||
Ok(ConstSpec {
|
||||
is_class_attr,
|
||||
python_name: parse_name_attribute(attrs)?.unwrap_or_else(|| name.unraw()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstAttributes {
|
||||
pub is_class_attr: bool,
|
||||
pub name: Option<NameAttribute>,
|
||||
pub name_is_deprecated: bool,
|
||||
}
|
||||
|
||||
pub enum PyO3ConstAttribute {
|
||||
Name(NameAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyO3ConstAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(PyO3ConstAttribute::Name)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstAttributes {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut attributes = ConstAttributes {
|
||||
is_class_attr: false,
|
||||
name: None,
|
||||
name_is_deprecated: false,
|
||||
};
|
||||
|
||||
take_attributes(attrs, |attr| {
|
||||
if attribute_ident_is(attr, "classattr") {
|
||||
ensure_spanned!(
|
||||
attr.tokens.is_empty(),
|
||||
attr.span() => "`#[classattr]` does not take any arguments"
|
||||
);
|
||||
attributes.is_class_attr = true;
|
||||
Ok(true)
|
||||
} else if let Some(pyo3_attributes) = get_pyo3_attribute(attr)? {
|
||||
for pyo3_attr in pyo3_attributes {
|
||||
match pyo3_attr {
|
||||
PyO3ConstAttribute::Name(name) => attributes.set_name(name)?,
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
} else if let Some(name) = get_deprecated_name_attribute(attr)? {
|
||||
attributes.set_name(name)?;
|
||||
attributes.name_is_deprecated = true;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(attributes)
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: NameAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.name.is_none(),
|
||||
name.0.span() => "`name` may only be specified once"
|
||||
);
|
||||
self.name = Some(name);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod attrs;
|
||||
mod attributes;
|
||||
mod defs;
|
||||
mod from_pyobject;
|
||||
mod konst;
|
||||
|
@ -22,9 +22,9 @@ mod pymethod;
|
|||
mod pyproto;
|
||||
|
||||
pub use from_pyobject::build_derive_from_pyobject;
|
||||
pub use module::{add_fn_to_module, process_functions_in_module, py_init};
|
||||
pub use module::{process_functions_in_module, py_init};
|
||||
pub use pyclass::{build_py_class, PyClassArgs};
|
||||
pub use pyfunction::{build_py_function, PyFunctionAttr};
|
||||
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
||||
pub use pyimpl::{build_py_methods, PyClassMethodsType};
|
||||
pub use pyproto::build_py_proto;
|
||||
pub use utils::get_doc;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::pyfunction::Argument;
|
||||
use crate::pyfunction::{parse_name_attribute, PyFunctionArgAttrs, PyFunctionAttr};
|
||||
use crate::pyfunction::PyFunctionOptions;
|
||||
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
|
||||
use crate::utils;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
|
@ -17,7 +18,7 @@ pub struct FnArg<'a> {
|
|||
pub ty: &'a syn::Type,
|
||||
pub optional: Option<&'a syn::Type>,
|
||||
pub py: bool,
|
||||
pub attrs: PyFunctionArgAttrs,
|
||||
pub attrs: PyFunctionArgPyO3Attributes,
|
||||
}
|
||||
|
||||
impl<'a> FnArg<'a> {
|
||||
|
@ -32,7 +33,7 @@ impl<'a> FnArg<'a> {
|
|||
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
|
||||
}
|
||||
|
||||
let arg_attrs = PyFunctionArgAttrs::from_attrs(&mut cap.attrs)?;
|
||||
let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
|
||||
let (ident, by_ref, mutability) = match *cap.pat {
|
||||
syn::Pat::Ident(syn::PatIdent {
|
||||
ref ident,
|
||||
|
@ -133,6 +134,7 @@ pub struct FnSpec<'a> {
|
|||
pub args: Vec<FnArg<'a>>,
|
||||
pub output: syn::Type,
|
||||
pub doc: syn::LitStr,
|
||||
pub name_is_deprecated: bool,
|
||||
}
|
||||
|
||||
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
||||
|
@ -161,13 +163,29 @@ impl<'a> FnSpec<'a> {
|
|||
pub fn parse(
|
||||
sig: &'a mut syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
allow_custom_name: bool,
|
||||
options: PyFunctionOptions,
|
||||
) -> syn::Result<FnSpec<'a>> {
|
||||
let MethodAttributes {
|
||||
ty: fn_type_attr,
|
||||
args: fn_attrs,
|
||||
mut python_name,
|
||||
} = parse_method_attributes(meth_attrs, allow_custom_name)?;
|
||||
} = parse_method_attributes(meth_attrs, options.name.map(|name| name.0))?;
|
||||
|
||||
match fn_type_attr {
|
||||
Some(MethodTypeAttribute::New) => {
|
||||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
|
||||
}
|
||||
python_name = Some(syn::Ident::new("__new__", proc_macro2::Span::call_site()))
|
||||
}
|
||||
Some(MethodTypeAttribute::Call) => {
|
||||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[call]`");
|
||||
}
|
||||
python_name = Some(syn::Ident::new("__call__", proc_macro2::Span::call_site()))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let (fn_type, skip_first_arg) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
|
||||
|
||||
|
@ -199,9 +217,17 @@ impl<'a> FnSpec<'a> {
|
|||
args: arguments,
|
||||
output: ty,
|
||||
doc,
|
||||
name_is_deprecated: options.name_is_deprecated,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn python_name_with_deprecation(&self) -> TokenStream {
|
||||
let deprecation =
|
||||
utils::name_deprecation_token(self.python_name.span(), self.name_is_deprecated);
|
||||
let name = format!("{}\0", self.python_name);
|
||||
quote!({#deprecation #name})
|
||||
}
|
||||
|
||||
fn parse_text_signature(
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
fn_type: &FnType,
|
||||
|
@ -362,12 +388,11 @@ struct MethodAttributes {
|
|||
|
||||
fn parse_method_attributes(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
allow_custom_name: bool,
|
||||
mut python_name: Option<syn::Ident>,
|
||||
) -> syn::Result<MethodAttributes> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut args = Vec::new();
|
||||
let mut ty: Option<MethodTypeAttribute> = None;
|
||||
let mut property_name = None;
|
||||
|
||||
macro_rules! set_ty {
|
||||
($new_ty:expr, $ident:expr) => {
|
||||
|
@ -434,7 +459,12 @@ fn parse_method_attributes(
|
|||
set_ty!(MethodTypeAttribute::Getter, path);
|
||||
};
|
||||
|
||||
property_name = match nested.pop().unwrap().into_value() {
|
||||
ensure_spanned!(
|
||||
python_name.is_none(),
|
||||
python_name.span() => "`name` may only be specified once"
|
||||
);
|
||||
|
||||
python_name = match nested.pop().unwrap().into_value() {
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(w)) if w.segments.len() == 1 => {
|
||||
Some(w.segments[0].ident.clone())
|
||||
}
|
||||
|
@ -455,7 +485,7 @@ fn parse_method_attributes(
|
|||
}
|
||||
};
|
||||
} else if path.is_ident("args") {
|
||||
let attrs = PyFunctionAttr::from_meta(&nested)?;
|
||||
let attrs = PyFunctionSignature::from_meta(&nested)?;
|
||||
args.extend(attrs.arguments)
|
||||
} else {
|
||||
new_attrs.push(attr)
|
||||
|
@ -467,21 +497,6 @@ fn parse_method_attributes(
|
|||
|
||||
*attrs = new_attrs;
|
||||
|
||||
let python_name = if allow_custom_name {
|
||||
match parse_method_name_attribute(ty.as_ref(), attrs)? {
|
||||
Some(python_name) if property_name.is_some() => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
python_name,
|
||||
"name cannot be specified twice",
|
||||
));
|
||||
}
|
||||
Some(python_name) => Some(python_name),
|
||||
None => property_name,
|
||||
}
|
||||
} else {
|
||||
property_name
|
||||
};
|
||||
|
||||
Ok(MethodAttributes {
|
||||
ty,
|
||||
args,
|
||||
|
@ -489,26 +504,4 @@ fn parse_method_attributes(
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_method_name_attribute(
|
||||
ty: Option<&MethodTypeAttribute>,
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> syn::Result<Option<syn::Ident>> {
|
||||
use MethodTypeAttribute::*;
|
||||
let name = parse_name_attribute(attrs)?;
|
||||
|
||||
// Reject some invalid combinations
|
||||
if let (Some(name), Some(ty)) = (&name, ty) {
|
||||
if let New | Call = ty {
|
||||
bail_spanned!(name.span() => "name not allowed with this method type");
|
||||
}
|
||||
}
|
||||
|
||||
// Thanks to check above we can be sure that this generates the right python name
|
||||
Ok(match ty {
|
||||
Some(New) => Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())),
|
||||
Some(Call) => Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())),
|
||||
_ => name,
|
||||
})
|
||||
}
|
||||
|
||||
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
//! Code generation for the function that initializes a python module and adds classes and function.
|
||||
|
||||
use crate::method::{self, FnArg};
|
||||
use crate::pyfunction::PyFunctionAttr;
|
||||
use crate::pymethod::{check_generic, get_arg_names, impl_arg_params};
|
||||
use crate::utils;
|
||||
use crate::attributes::{attribute_ident_is, take_attributes, NameAttribute};
|
||||
use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{spanned::Spanned, Ident, Result};
|
||||
use quote::quote;
|
||||
use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
|
||||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
|
@ -35,15 +33,13 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
|
|||
|
||||
for stmt in func.block.stmts.iter_mut() {
|
||||
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
|
||||
if let Some((module_name, python_name, pyfn_attrs)) =
|
||||
extract_pyfn_attrs(&mut func.attrs)?
|
||||
{
|
||||
let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?;
|
||||
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
|
||||
if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? {
|
||||
let module_name = pyfn_args.modname;
|
||||
let (ident, wrapped_function) = impl_wrap_pyfunction(func, pyfn_args.options)?;
|
||||
let item: syn::ItemFn = syn::parse_quote! {
|
||||
fn block_wrapper() {
|
||||
#function_to_python
|
||||
#module_name.add_function(#function_wrapper_ident(#module_name)?)?;
|
||||
#wrapped_function
|
||||
#module_name.add_function(#ident(#module_name)?)?;
|
||||
}
|
||||
};
|
||||
stmts.extend(item.block.stmts.into_iter());
|
||||
|
@ -56,190 +52,49 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub struct PyFnArgs {
|
||||
modname: Path,
|
||||
options: PyFunctionOptions,
|
||||
}
|
||||
|
||||
impl Parse for PyFnArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let modname = input.parse()?;
|
||||
let _: Comma = input.parse()?;
|
||||
let fnname_literal: syn::LitStr = input.parse()?;
|
||||
let fnname = fnname_literal.parse()?;
|
||||
if input.is_empty() {
|
||||
let mut options = PyFunctionOptions::default();
|
||||
options.set_name(NameAttribute(fnname))?;
|
||||
return Ok(Self { modname, options });
|
||||
}
|
||||
let _: Comma = input.parse()?;
|
||||
let mut options: PyFunctionOptions = input.parse()?;
|
||||
options.set_name(NameAttribute(fnname))?;
|
||||
Ok(Self { modname, options })
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the data from the #[pyfn(...)] attribute of a function
|
||||
fn extract_pyfn_attrs(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> syn::Result<Option<(syn::Path, Ident, PyFunctionAttr)>> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut fnname = None;
|
||||
let mut modname = None;
|
||||
let mut fn_attrs = PyFunctionAttr::default();
|
||||
fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs>> {
|
||||
let mut pyfn_args: Option<PyFnArgs> = None;
|
||||
|
||||
for attr in attrs.drain(..) {
|
||||
match attr.parse_meta() {
|
||||
Ok(syn::Meta::List(list)) if list.path.is_ident("pyfn") => {
|
||||
let meta: Vec<_> = list.nested.iter().cloned().collect();
|
||||
if meta.len() >= 2 {
|
||||
// read module name
|
||||
match &meta[0] {
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
modname = Some(path.clone())
|
||||
}
|
||||
_ => bail_spanned!(
|
||||
meta[0].span() => "the first parameter of pyfn must be a MetaItem"
|
||||
),
|
||||
}
|
||||
// read Python function name
|
||||
match &meta[1] {
|
||||
syn::NestedMeta::Lit(syn::Lit::Str(lits)) => {
|
||||
fnname = Some(syn::Ident::new(&lits.value(), lits.span()));
|
||||
}
|
||||
_ => bail_spanned!(
|
||||
meta[1].span() => "the second parameter of pyfn must be a Literal"
|
||||
),
|
||||
}
|
||||
// Read additional arguments
|
||||
if list.nested.len() >= 3 {
|
||||
fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()])?;
|
||||
}
|
||||
} else {
|
||||
bail_spanned!(
|
||||
attr.span() => format!("can not parse 'pyfn' params {:?}", attr)
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => new_attrs.push(attr),
|
||||
take_attributes(attrs, |attr| {
|
||||
if attribute_ident_is(attr, "pyfn") {
|
||||
ensure_spanned!(
|
||||
pyfn_args.is_none(),
|
||||
attr.span() => "`#[pyfn] may only be specified once"
|
||||
);
|
||||
pyfn_args = Some(attr.parse_args()?);
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(pyfn_args) = &mut pyfn_args {
|
||||
pyfn_args.options.take_pyo3_attributes(attrs)?;
|
||||
}
|
||||
|
||||
*attrs = new_attrs;
|
||||
match (modname, fnname) {
|
||||
(Some(modname), Some(fnname)) => Ok(Some((modname, fnname, fn_attrs))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Coordinates the naming of a the add-function-to-python-module function
|
||||
fn function_wrapper_ident(name: &Ident) -> Ident {
|
||||
// Make sure this ident matches the one of wrap_pyfunction
|
||||
format_ident!("__pyo3_get_function_{}", name)
|
||||
}
|
||||
|
||||
/// 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: &mut syn::ItemFn,
|
||||
python_name: Ident,
|
||||
pyfn_attrs: PyFunctionAttr,
|
||||
) -> syn::Result<TokenStream> {
|
||||
check_generic(&func.sig)?;
|
||||
|
||||
let mut arguments = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter_mut()
|
||||
.map(FnArg::parse)
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
if pyfn_attrs.pass_module {
|
||||
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
|
||||
ensure_spanned!(
|
||||
!arguments.is_empty(),
|
||||
func.span() => PASS_MODULE_ERR
|
||||
);
|
||||
let arg = arguments.remove(0);
|
||||
ensure_spanned!(
|
||||
type_is_pymodule(arg.ty),
|
||||
arg.ty.span() => PASS_MODULE_ERR
|
||||
);
|
||||
}
|
||||
|
||||
let ty = method::get_return_info(&func.sig.output);
|
||||
|
||||
let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?;
|
||||
let doc = utils::get_doc(&func.attrs, text_signature, true)?;
|
||||
|
||||
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp: method::FnType::FnStatic,
|
||||
name: &function_wrapper_ident,
|
||||
python_name,
|
||||
attrs: pyfn_attrs.arguments,
|
||||
args: arguments,
|
||||
output: ty,
|
||||
doc,
|
||||
};
|
||||
|
||||
let doc = &spec.doc;
|
||||
let python_name = &spec.python_name;
|
||||
|
||||
let name = &func.sig.ident;
|
||||
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
|
||||
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, pyfn_attrs.pass_module)?;
|
||||
Ok(quote! {
|
||||
#wrapper
|
||||
pub(crate) fn #function_wrapper_ident<'a>(
|
||||
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
|
||||
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
|
||||
let name = concat!(stringify!(#python_name), "\0");
|
||||
pyo3::types::PyCFunction::internal_new(
|
||||
pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
|
||||
name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper_ident),
|
||||
#doc,
|
||||
),
|
||||
args.into(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn type_is_pymodule(ty: &syn::Type) -> bool {
|
||||
if let syn::Type::Reference(tyref) = ty {
|
||||
if let syn::Type::Path(typath) = tyref.elem.as_ref() {
|
||||
if typath
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.map(|seg| seg.ident == "PyModule")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
fn function_c_wrapper(
|
||||
name: &Ident,
|
||||
wrapper_ident: &Ident,
|
||||
spec: &method::FnSpec<'_>,
|
||||
pass_module: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let names: Vec<Ident> = get_arg_names(&spec);
|
||||
let cb;
|
||||
let slf_module;
|
||||
if pass_module {
|
||||
cb = quote! {
|
||||
pyo3::callback::convert(_py, #name(_slf, #(#names),*))
|
||||
};
|
||||
slf_module = Some(quote! {
|
||||
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
|
||||
});
|
||||
} else {
|
||||
cb = quote! {
|
||||
pyo3::callback::convert(_py, #name(#(#names),*))
|
||||
};
|
||||
slf_module = None;
|
||||
};
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, None, cb, &py)?;
|
||||
Ok(quote! {
|
||||
unsafe extern "C" fn #wrapper_ident(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf_module
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
})
|
||||
Ok(pyfn_args)
|
||||
}
|
||||
|
|
|
@ -499,21 +499,21 @@ fn impl_descriptors(
|
|||
.flat_map(|(field, fns)| {
|
||||
fns.iter()
|
||||
.map(|desc| {
|
||||
if let Some(name) = field.ident.as_ref().map(|ident| ident.unraw()) {
|
||||
let doc = utils::get_doc(&field.attrs, None, true)
|
||||
.unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span()));
|
||||
let property_type = PropertyType::Descriptor(&field);
|
||||
match desc {
|
||||
FnType::Getter(self_ty) => {
|
||||
impl_py_getter_def(cls, property_type, self_ty, &name, &doc)
|
||||
}
|
||||
FnType::Setter(self_ty) => {
|
||||
impl_py_setter_def(cls, property_type, self_ty, &name, &doc)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
let doc = utils::get_doc(&field.attrs, None, true)
|
||||
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
|
||||
let property_type = PropertyType::Descriptor(
|
||||
field.ident.as_ref().ok_or_else(
|
||||
|| err_spanned!(field.span() => "`#[pyo3(get, set)]` is not supported on tuple struct fields")
|
||||
)?
|
||||
);
|
||||
match desc {
|
||||
FnType::Getter(self_ty) => {
|
||||
impl_py_getter_def(cls, property_type, self_ty, &doc)
|
||||
}
|
||||
} else {
|
||||
bail_spanned!(field.span() => "get/set are not supported on tuple struct field");
|
||||
FnType::Setter(self_ty) => {
|
||||
impl_py_setter_def(cls, property_type, self_ty, &doc)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<syn::Result<TokenStream>>>()
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::attrs::FromPyWithAttribute;
|
||||
use crate::module::add_fn_to_module;
|
||||
use proc_macro2::TokenStream;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::ParseBuffer;
|
||||
use crate::{
|
||||
attributes::{
|
||||
self, get_deprecated_name_attribute, get_pyo3_attribute, take_attributes,
|
||||
FromPyWithAttribute, NameAttribute,
|
||||
},
|
||||
method::{self, FnArg, FnSpec},
|
||||
pymethod::{check_generic, get_arg_names, impl_arg_params},
|
||||
utils,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{NestedMeta, Path};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, Ident, NestedMeta, Path, Result};
|
||||
use syn::{
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
token::Comma,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Argument {
|
||||
|
@ -20,29 +29,69 @@ pub enum Argument {
|
|||
|
||||
/// The attributes of the pyfunction macro
|
||||
#[derive(Default)]
|
||||
pub struct PyFunctionAttr {
|
||||
pub struct PyFunctionSignature {
|
||||
pub arguments: Vec<Argument>,
|
||||
has_kw: bool,
|
||||
has_varargs: bool,
|
||||
has_kwargs: bool,
|
||||
pub pass_module: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct PyFunctionArgAttrs {
|
||||
pub struct PyFunctionArgPyO3Attributes {
|
||||
pub from_py_with: Option<FromPyWithAttribute>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for PyFunctionAttr {
|
||||
enum PyFunctionArgPyO3Attribute {
|
||||
FromPyWith(FromPyWithAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyFunctionArgPyO3Attribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::from_py_with) {
|
||||
input.parse().map(PyFunctionArgPyO3Attribute::FromPyWith)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFunctionArgPyO3Attributes {
|
||||
/// Parses #[pyo3(from_python_with = "func")]
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None };
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
|
||||
for attr in pyo3_attrs {
|
||||
match attr {
|
||||
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
|
||||
ensure_spanned!(
|
||||
attributes.from_py_with.is_none(),
|
||||
from_py_with.0.span() => "`from_py_with` may only be specified once per argument"
|
||||
);
|
||||
attributes.from_py_with = Some(from_py_with);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
Ok(attributes)
|
||||
}
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for PyFunctionSignature {
|
||||
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
|
||||
let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?;
|
||||
Self::from_meta(&attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFunctionAttr {
|
||||
impl PyFunctionSignature {
|
||||
pub fn from_meta<'a>(iter: impl IntoIterator<Item = &'a NestedMeta>) -> syn::Result<Self> {
|
||||
let mut slf = PyFunctionAttr::default();
|
||||
let mut slf = PyFunctionSignature::default();
|
||||
|
||||
for item in iter {
|
||||
slf.add_item(item)?
|
||||
|
@ -52,9 +101,6 @@ impl PyFunctionAttr {
|
|||
|
||||
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
|
||||
match item {
|
||||
NestedMeta::Meta(syn::Meta::Path(ident)) if ident.is_ident("pass_module") => {
|
||||
self.pass_module = true;
|
||||
}
|
||||
NestedMeta::Meta(syn::Meta::Path(ident)) => self.add_work(item, ident)?,
|
||||
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
|
||||
self.add_name_value(item, nv)?;
|
||||
|
@ -159,92 +205,286 @@ impl PyFunctionAttr {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<syn::Ident>> {
|
||||
let mut name_attrs = Vec::new();
|
||||
|
||||
// Using retain will extract all name attributes from the attribute list
|
||||
attrs.retain(|attr| match attr.parse_meta() {
|
||||
Ok(syn::Meta::NameValue(nv)) if nv.path.is_ident("name") => {
|
||||
name_attrs.push((nv.lit, attr.span()));
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
|
||||
match name_attrs.as_slice() {
|
||||
[] => Ok(None),
|
||||
[(syn::Lit::Str(s), span)] => {
|
||||
let mut ident: syn::Ident = s.parse()?;
|
||||
// This span is the whole attribute span, which is nicer for reporting errors.
|
||||
ident.set_span(*span);
|
||||
Ok(Some(ident))
|
||||
}
|
||||
[(_, span)] => bail_spanned!(*span => "expected string literal for #[name] argument"),
|
||||
slice => bail_spanned!(
|
||||
slice[1].1 => "#[name] can not be specified multiple times"
|
||||
),
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct PyFunctionOptions {
|
||||
pub pass_module: bool,
|
||||
pub name: Option<NameAttribute>,
|
||||
pub name_is_deprecated: bool,
|
||||
pub signature: Option<PyFunctionSignature>,
|
||||
}
|
||||
|
||||
pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result<TokenStream> {
|
||||
let python_name =
|
||||
parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw());
|
||||
add_fn_to_module(ast, python_name, args)
|
||||
}
|
||||
impl Parse for PyFunctionOptions {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut options = PyFunctionOptions {
|
||||
pass_module: false,
|
||||
name: None,
|
||||
name_is_deprecated: false,
|
||||
signature: None,
|
||||
};
|
||||
|
||||
fn extract_pyo3_metas(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Vec<syn::NestedMeta>> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut metas = Vec::new();
|
||||
|
||||
for attr in attrs.drain(..) {
|
||||
if let syn::Meta::List(meta_list) = attr.parse_meta()? {
|
||||
if meta_list.path.is_ident("pyo3") {
|
||||
for meta in meta_list.nested {
|
||||
metas.push(meta);
|
||||
while !input.is_empty() {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name)
|
||||
|| lookahead.peek(attributes::kw::pass_module)
|
||||
|| lookahead.peek(attributes::kw::signature)
|
||||
{
|
||||
options.add_attributes(std::iter::once(input.parse()?))?;
|
||||
if !input.is_empty() {
|
||||
let _: Comma = input.parse()?;
|
||||
}
|
||||
} else {
|
||||
new_attrs.push(attr)
|
||||
// If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)]
|
||||
//
|
||||
// TODO deprecate in favour of #[pyfunction(signature = (a, b), name = "foo")]
|
||||
options.signature = Some(input.parse()?);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*attrs = new_attrs;
|
||||
|
||||
Ok(metas)
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFunctionArgAttrs {
|
||||
/// Parses #[pyo3(from_python_with = "func")]
|
||||
pub enum PyFunctionOption {
|
||||
Name(NameAttribute),
|
||||
PassModule(attributes::kw::pass_module),
|
||||
Signature(PyFunctionSignature),
|
||||
}
|
||||
|
||||
impl Parse for PyFunctionOption {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(PyFunctionOption::Name)
|
||||
} else if lookahead.peek(attributes::kw::pass_module) {
|
||||
input.parse().map(PyFunctionOption::PassModule)
|
||||
} else if lookahead.peek(attributes::kw::signature) {
|
||||
input.parse().map(PyFunctionOption::Signature)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFunctionOptions {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
let mut from_py_with = None;
|
||||
let mut options = PyFunctionOptions::default();
|
||||
options.take_pyo3_attributes(attrs)?;
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
for meta in extract_pyo3_metas(attrs)? {
|
||||
let meta = match meta {
|
||||
NestedMeta::Meta(meta) => meta,
|
||||
NestedMeta::Lit(lit) => {
|
||||
bail_spanned!(lit.span() => "expected `from_py_with`, got a literal")
|
||||
}
|
||||
};
|
||||
|
||||
if meta.path().is_ident("from_py_with") {
|
||||
from_py_with = Some(FromPyWithAttribute::from_meta(meta)?);
|
||||
pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||
take_attributes(attrs, |attr| {
|
||||
if let Some(pyo3_attributes) = get_pyo3_attribute(attr)? {
|
||||
self.add_attributes(pyo3_attributes)?;
|
||||
Ok(true)
|
||||
} else if let Some(name) = get_deprecated_name_attribute(attr)? {
|
||||
self.set_name(name)?;
|
||||
self.name_is_deprecated = true;
|
||||
Ok(true)
|
||||
} else {
|
||||
bail_spanned!(meta.span() => "only `from_py_with` is supported")
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_attributes(
|
||||
&mut self,
|
||||
attrs: impl IntoIterator<Item = PyFunctionOption>,
|
||||
) -> Result<()> {
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
PyFunctionOption::Name(name) => self.set_name(name)?,
|
||||
PyFunctionOption::PassModule(kw) => {
|
||||
ensure_spanned!(
|
||||
!self.pass_module,
|
||||
kw.span() => "`pass_module` may only be specified once"
|
||||
);
|
||||
self.pass_module = true;
|
||||
}
|
||||
PyFunctionOption::Signature(signature) => {
|
||||
ensure_spanned!(
|
||||
self.signature.is_none(),
|
||||
// FIXME: improve the span of this error message
|
||||
Span::call_site() => "`signature` may only be specified once"
|
||||
);
|
||||
self.signature = Some(signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PyFunctionArgAttrs { from_py_with })
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: NameAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.name.is_none(),
|
||||
name.0.span() => "`name` may only be specified once"
|
||||
);
|
||||
self.name = Some(name);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_py_function(
|
||||
ast: &mut syn::ItemFn,
|
||||
mut options: PyFunctionOptions,
|
||||
) -> syn::Result<TokenStream> {
|
||||
options.take_pyo3_attributes(&mut ast.attrs)?;
|
||||
Ok(impl_wrap_pyfunction(ast, options)?.1)
|
||||
}
|
||||
|
||||
/// Coordinates the naming of a the add-function-to-python-module function
|
||||
fn function_wrapper_ident(name: &Ident) -> Ident {
|
||||
// Make sure this ident matches the one of wrap_pyfunction
|
||||
format_ident!("__pyo3_get_function_{}", name)
|
||||
}
|
||||
|
||||
/// Generates python wrapper over a function that allows adding it to a python module as a python
|
||||
/// function
|
||||
pub fn impl_wrap_pyfunction(
|
||||
func: &mut syn::ItemFn,
|
||||
options: PyFunctionOptions,
|
||||
) -> syn::Result<(Ident, TokenStream)> {
|
||||
check_generic(&func.sig)?;
|
||||
|
||||
let python_name = options
|
||||
.name
|
||||
.map_or_else(|| func.sig.ident.unraw(), |name| name.0);
|
||||
|
||||
let signature = options.signature.unwrap_or_default();
|
||||
|
||||
let mut arguments = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter_mut()
|
||||
.map(FnArg::parse)
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
||||
if options.pass_module {
|
||||
const PASS_MODULE_ERR: &str = "expected &PyModule as first argument with `pass_module`";
|
||||
ensure_spanned!(
|
||||
!arguments.is_empty(),
|
||||
func.span() => PASS_MODULE_ERR
|
||||
);
|
||||
let arg = arguments.remove(0);
|
||||
ensure_spanned!(
|
||||
type_is_pymodule(arg.ty),
|
||||
arg.ty.span() => PASS_MODULE_ERR
|
||||
);
|
||||
}
|
||||
|
||||
let ty = method::get_return_info(&func.sig.output);
|
||||
|
||||
let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?;
|
||||
let doc = utils::get_doc(&func.attrs, text_signature, true)?;
|
||||
|
||||
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp: method::FnType::FnStatic,
|
||||
name: &function_wrapper_ident,
|
||||
python_name,
|
||||
attrs: signature.arguments,
|
||||
args: arguments,
|
||||
output: ty,
|
||||
doc,
|
||||
name_is_deprecated: options.name_is_deprecated,
|
||||
};
|
||||
|
||||
let doc = &spec.doc;
|
||||
let python_name = spec.python_name_with_deprecation();
|
||||
|
||||
let name = &func.sig.ident;
|
||||
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
|
||||
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module)?;
|
||||
let wrapped_pyfunction = quote! {
|
||||
#wrapper
|
||||
pub(crate) fn #function_wrapper_ident<'a>(
|
||||
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
|
||||
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
|
||||
pyo3::types::PyCFunction::internal_new(
|
||||
pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper_ident),
|
||||
#doc,
|
||||
),
|
||||
args.into(),
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok((function_wrapper_ident, wrapped_pyfunction))
|
||||
}
|
||||
|
||||
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
fn function_c_wrapper(
|
||||
name: &Ident,
|
||||
wrapper_ident: &Ident,
|
||||
spec: &FnSpec<'_>,
|
||||
pass_module: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let names: Vec<Ident> = get_arg_names(&spec);
|
||||
let cb;
|
||||
let slf_module;
|
||||
if pass_module {
|
||||
cb = quote! {
|
||||
pyo3::callback::convert(_py, #name(_slf, #(#names),*))
|
||||
};
|
||||
slf_module = Some(quote! {
|
||||
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
|
||||
});
|
||||
} else {
|
||||
cb = quote! {
|
||||
pyo3::callback::convert(_py, #name(#(#names),*))
|
||||
};
|
||||
slf_module = None;
|
||||
};
|
||||
let py = syn::Ident::new("_py", Span::call_site());
|
||||
let body = impl_arg_params(spec, None, cb, &py)?;
|
||||
Ok(quote! {
|
||||
unsafe extern "C" fn #wrapper_ident(
|
||||
_slf: *mut pyo3::ffi::PyObject,
|
||||
_args: *mut pyo3::ffi::PyObject,
|
||||
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
|
||||
{
|
||||
pyo3::callback::handle_panic(|#py| {
|
||||
#slf_module
|
||||
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
#body
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn type_is_pymodule(ty: &syn::Type) -> bool {
|
||||
if let syn::Type::Reference(tyref) = ty {
|
||||
if let syn::Type::Path(typath) = tyref.elem.as_ref() {
|
||||
if typath
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.map(|seg| seg.ident == "PyModule")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Argument, PyFunctionAttr};
|
||||
use super::{Argument, PyFunctionSignature};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
|
||||
fn items(input: TokenStream) -> syn::Result<Vec<Argument>> {
|
||||
let py_fn_attr: PyFunctionAttr = syn::parse2(input)?;
|
||||
let py_fn_attr: PyFunctionSignature = syn::parse2(input)?;
|
||||
Ok(py_fn_attr.arguments)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::pymethod;
|
||||
use crate::{
|
||||
konst::{ConstAttributes, ConstSpec},
|
||||
pyfunction::PyFunctionOptions,
|
||||
pymethod,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use pymethod::GeneratedPyMethod;
|
||||
use quote::quote;
|
||||
|
@ -39,7 +43,8 @@ pub fn impl_methods(
|
|||
for iimpl in impls.iter_mut() {
|
||||
match iimpl {
|
||||
syn::ImplItem::Method(meth) => {
|
||||
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs)? {
|
||||
let options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
|
||||
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, options)? {
|
||||
GeneratedPyMethod::Method(token_stream) => {
|
||||
let attrs = get_cfg_attributes(&meth.attrs);
|
||||
methods.push(quote!(#(#attrs)* #token_stream));
|
||||
|
@ -55,8 +60,14 @@ pub fn impl_methods(
|
|||
}
|
||||
}
|
||||
syn::ImplItem::Const(konst) => {
|
||||
if let Some(meth) = pymethod::gen_py_const(ty, &konst.ident, &mut konst.attrs)? {
|
||||
let attributes = ConstAttributes::from_attrs(&mut konst.attrs)?;
|
||||
if attributes.is_class_attr {
|
||||
let spec = ConstSpec {
|
||||
rust_ident: konst.ident.clone(),
|
||||
attributes,
|
||||
};
|
||||
let attrs = get_cfg_attributes(&konst.attrs);
|
||||
let meth = pymethod::gen_py_const(ty, &spec);
|
||||
methods.push(quote!(#(#attrs)* #meth));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use crate::attrs::FromPyWithAttribute;
|
||||
use crate::konst::ConstSpec;
|
||||
use crate::method::{FnArg, FnSpec, FnType, SelfType};
|
||||
use crate::utils;
|
||||
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
|
||||
use crate::{
|
||||
method::{FnArg, FnSpec, FnType, SelfType},
|
||||
pyfunction::PyFunctionOptions,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, Result};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PropertyType<'a> {
|
||||
Descriptor(&'a syn::Field),
|
||||
Descriptor(&'a syn::Ident),
|
||||
Function(&'a FnSpec<'a>),
|
||||
}
|
||||
|
||||
|
@ -22,9 +25,10 @@ pub fn gen_py_method(
|
|||
cls: &syn::Type,
|
||||
sig: &mut syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
options: PyFunctionOptions,
|
||||
) -> Result<GeneratedPyMethod> {
|
||||
check_generic(sig)?;
|
||||
let spec = FnSpec::parse(sig, &mut *meth_attrs, true)?;
|
||||
let spec = FnSpec::parse(sig, &mut *meth_attrs, options)?;
|
||||
|
||||
Ok(match &spec.tp {
|
||||
FnType::Fn(self_ty) => {
|
||||
|
@ -43,14 +47,12 @@ pub fn gen_py_method(
|
|||
cls,
|
||||
PropertyType::Function(&spec),
|
||||
self_ty,
|
||||
&spec.python_name,
|
||||
&spec.doc,
|
||||
)?),
|
||||
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||
cls,
|
||||
PropertyType::Function(&spec),
|
||||
self_ty,
|
||||
&spec.python_name,
|
||||
&spec.doc,
|
||||
)?),
|
||||
})
|
||||
|
@ -68,22 +70,15 @@ pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_py_const(
|
||||
cls: &syn::Type,
|
||||
name: &syn::Ident,
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> syn::Result<Option<TokenStream>> {
|
||||
let spec = ConstSpec::parse(name, attrs)?;
|
||||
if spec.is_class_attr {
|
||||
let wrapper = quote! {{
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
pyo3::IntoPy::into_py(#cls::#name, py)
|
||||
}
|
||||
__wrap
|
||||
}};
|
||||
return Ok(Some(impl_py_const_class_attribute(&spec, &wrapper)));
|
||||
}
|
||||
Ok(None)
|
||||
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
|
||||
let member = &spec.rust_ident;
|
||||
let wrapper = quote! {{
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
pyo3::IntoPy::into_py(#cls::#member, py)
|
||||
}
|
||||
__wrap
|
||||
}};
|
||||
impl_py_const_class_attribute(&spec, &wrapper)
|
||||
}
|
||||
|
||||
/// Generate function wrapper for PyCFunctionWithKeywords
|
||||
|
@ -255,12 +250,9 @@ pub(crate) fn impl_wrap_getter(
|
|||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let getter_impl = match property_type {
|
||||
PropertyType::Descriptor(field) => {
|
||||
let name = field.ident.as_ref().unwrap();
|
||||
quote!({
|
||||
_slf.#name.clone()
|
||||
})
|
||||
let getter_impl = match &property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
quote!(_slf.#ident.clone())
|
||||
}
|
||||
PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
|
||||
};
|
||||
|
@ -307,10 +299,9 @@ pub(crate) fn impl_wrap_setter(
|
|||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let setter_impl = match property_type {
|
||||
PropertyType::Descriptor(field) => {
|
||||
let name = field.ident.as_ref().unwrap();
|
||||
quote!({ _slf.#name = _val; })
|
||||
let setter_impl = match &property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
quote!({ _slf.#ident = _val; })
|
||||
}
|
||||
PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
|
||||
};
|
||||
|
@ -584,14 +575,14 @@ pub fn impl_py_method_def(
|
|||
flags: Option<TokenStream>,
|
||||
) -> Result<TokenStream> {
|
||||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
||||
let python_name = &spec.python_name;
|
||||
let python_name = spec.python_name_with_deprecation();
|
||||
let doc = &spec.doc;
|
||||
if spec.args.is_empty() {
|
||||
let wrapper = impl_wrap_noargs(cls, spec, self_ty);
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef::noargs(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunction(#wrapper),
|
||||
#doc
|
||||
)
|
||||
|
@ -604,7 +595,7 @@ pub fn impl_py_method_def(
|
|||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Method({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
)
|
||||
|
@ -627,12 +618,12 @@ pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStr
|
|||
|
||||
pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper = impl_wrap_class(cls, &spec)?;
|
||||
let python_name = &spec.python_name;
|
||||
let python_name = spec.python_name_with_deprecation();
|
||||
let doc = &spec.doc;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Class({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
).flags(pyo3::ffi::METH_CLASS)
|
||||
|
@ -642,12 +633,12 @@ pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenS
|
|||
|
||||
pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
let wrapper = impl_wrap_static(cls, &spec)?;
|
||||
let python_name = &spec.python_name;
|
||||
let python_name = spec.python_name_with_deprecation();
|
||||
let doc = &spec.doc;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Static({
|
||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||
#doc
|
||||
).flags(pyo3::ffi::METH_STATIC)
|
||||
|
@ -657,11 +648,11 @@ pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<Token
|
|||
|
||||
pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
|
||||
let wrapper = impl_wrap_class_attribute(cls, &spec);
|
||||
let python_name = &spec.python_name;
|
||||
let python_name = spec.python_name_with_deprecation();
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
||||
)
|
||||
})
|
||||
|
@ -669,14 +660,16 @@ pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenSt
|
|||
}
|
||||
|
||||
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
|
||||
let python_name = &spec.python_name;
|
||||
let python_name = &spec.python_name_with_deprecation();
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
||||
)
|
||||
})
|
||||
{
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
pyo3::class::PyClassAttributeDef::new(
|
||||
#python_name,
|
||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -699,14 +692,20 @@ pub(crate) fn impl_py_setter_def(
|
|||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
python_name: &syn::Ident,
|
||||
doc: &syn::LitStr,
|
||||
) -> Result<TokenStream> {
|
||||
let python_name = match property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let formatted_name = format!("{}\0", ident.unraw());
|
||||
quote!(#formatted_name)
|
||||
}
|
||||
PropertyType::Function(spec) => spec.python_name_with_deprecation(),
|
||||
};
|
||||
let wrapper = impl_wrap_setter(cls, property_type, self_ty)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Setter({
|
||||
pyo3::class::PySetterDef::new(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PySetter(#wrapper),
|
||||
#doc
|
||||
)
|
||||
|
@ -718,14 +717,20 @@ pub(crate) fn impl_py_getter_def(
|
|||
cls: &syn::Type,
|
||||
property_type: PropertyType,
|
||||
self_ty: &SelfType,
|
||||
python_name: &syn::Ident,
|
||||
doc: &syn::LitStr,
|
||||
) -> Result<TokenStream> {
|
||||
let python_name = match property_type {
|
||||
PropertyType::Descriptor(ident) => {
|
||||
let formatted_name = format!("{}\0", ident.unraw());
|
||||
quote!(#formatted_name)
|
||||
}
|
||||
PropertyType::Function(spec) => spec.python_name_with_deprecation(),
|
||||
};
|
||||
let wrapper = impl_wrap_getter(cls, property_type, self_ty)?;
|
||||
Ok(quote! {
|
||||
pyo3::class::PyMethodDefType::Getter({
|
||||
pyo3::class::PyGetterDef::new(
|
||||
concat!(stringify!(#python_name), "\0"),
|
||||
#python_name,
|
||||
pyo3::class::methods::PyGetter(#wrapper),
|
||||
#doc
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use crate::defs;
|
||||
use crate::method::{FnSpec, FnType};
|
||||
use crate::proto_method::impl_method_proto;
|
||||
use crate::pyfunction::PyFunctionOptions;
|
||||
use crate::pymethod;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
|
@ -62,7 +63,8 @@ fn impl_proto_impl(
|
|||
}
|
||||
// Add non-slot methods to inventory like `#[pymethods]`
|
||||
if let Some(m) = proto.get_method(&met.sig.ident) {
|
||||
let fn_spec = FnSpec::parse(&mut met.sig, &mut met.attrs, false)?;
|
||||
let fn_spec =
|
||||
FnSpec::parse(&mut met.sig, &mut met.attrs, PyFunctionOptions::default())?;
|
||||
|
||||
let flags = if m.can_coexist {
|
||||
// We need METH_COEXIST here to prevent __add__ from overriding __radd__
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use proc_macro2::Span;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote_spanned;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
|
||||
|
@ -164,3 +165,14 @@ pub fn get_doc(
|
|||
|
||||
Ok(syn::LitStr::new(&doc, span))
|
||||
}
|
||||
|
||||
pub fn name_deprecation_token(span: Span, name_is_deprecated: bool) -> Option<TokenStream> {
|
||||
if name_is_deprecated {
|
||||
Some(quote_spanned!(
|
||||
span =>
|
||||
let _ = pyo3::impl_::deprecations::NAME_ATTRIBUTE;
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use proc_macro::TokenStream;
|
|||
use pyo3_macros_backend::{
|
||||
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,
|
||||
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
|
||||
PyFunctionAttr,
|
||||
PyFunctionOptions,
|
||||
};
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
|
@ -217,9 +217,9 @@ pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStre
|
|||
#[proc_macro_attribute]
|
||||
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as syn::ItemFn);
|
||||
let args = parse_macro_input!(attr as PyFunctionAttr);
|
||||
let options = parse_macro_input!(attr as PyFunctionOptions);
|
||||
|
||||
let expanded = build_py_function(&mut ast, args).unwrap_or_else(|e| e.to_compile_error());
|
||||
let expanded = build_py_function(&mut ast, options).unwrap_or_else(|e| e.to_compile_error());
|
||||
|
||||
quote!(
|
||||
#ast
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//! Internals of PyO3 which are accessed by code expanded from PyO3's procedural macros. Usage of
|
||||
//! any of these APIs in downstream code is implicitly acknowledging that these APIs may change at
|
||||
//! any time without documentation in the CHANGELOG and without breaking semver guarantees.
|
||||
|
||||
/// Symbols to represent deprecated uses of PyO3's macros.
|
||||
pub mod deprecations {
|
||||
#[doc(hidden)]
|
||||
#[deprecated(
|
||||
since = "0.14.0",
|
||||
note = "use `#[pyo3(name = \"...\")]` instead of `#[name = \"...\"]`"
|
||||
)]
|
||||
pub const NAME_ATTRIBUTE: () = ();
|
||||
}
|
|
@ -177,6 +177,7 @@ pub mod exceptions;
|
|||
pub mod ffi;
|
||||
pub mod freelist;
|
||||
mod gil;
|
||||
pub mod impl_;
|
||||
mod instance;
|
||||
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
|
|
|
@ -19,13 +19,17 @@ impl Foo {
|
|||
#[classattr]
|
||||
const MY_CONST: &'static str = "foobar";
|
||||
|
||||
#[classattr]
|
||||
#[pyo3(name = "RENAMED_CONST")]
|
||||
const MY_CONST_2: &'static str = "foobar_2";
|
||||
|
||||
#[classattr]
|
||||
fn a() -> i32 {
|
||||
5
|
||||
}
|
||||
|
||||
#[classattr]
|
||||
#[name = "B"]
|
||||
#[pyo3(name = "B")]
|
||||
fn b() -> String {
|
||||
"bar".to_string()
|
||||
}
|
||||
|
@ -46,9 +50,11 @@ fn class_attributes() {
|
|||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let foo_obj = py.get_type::<Foo>();
|
||||
py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'");
|
||||
py_assert!(py, foo_obj, "foo_obj.RENAMED_CONST == 'foobar_2'");
|
||||
py_assert!(py, foo_obj, "foo_obj.a == 5");
|
||||
py_assert!(py, foo_obj, "foo_obj.B == 'bar'");
|
||||
py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'");
|
||||
py_assert!(py, foo_obj, "foo_obj.foo.x == 1");
|
||||
}
|
||||
|
||||
// Ignored because heap types are not immutable:
|
||||
|
|
|
@ -86,15 +86,15 @@ struct EmptyClass2 {}
|
|||
|
||||
#[pymethods]
|
||||
impl EmptyClass2 {
|
||||
#[name = "custom_fn"]
|
||||
#[pyo3(name = "custom_fn")]
|
||||
fn bar(&self) {}
|
||||
|
||||
#[staticmethod]
|
||||
#[name = "custom_static"]
|
||||
#[pyo3(name = "custom_static")]
|
||||
fn bar_static() {}
|
||||
|
||||
#[getter]
|
||||
#[name = "custom_getter"]
|
||||
#[pyo3(name = "custom_getter")]
|
||||
fn foo(&self) -> i32 {
|
||||
5
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ fn test_compile_errors() {
|
|||
|
||||
#[rustversion::since(1.49)]
|
||||
fn tests_rust_1_49(t: &trybuild::TestCases) {
|
||||
t.compile_fail("tests/ui/deprecations.rs");
|
||||
t.compile_fail("tests/ui/invalid_frompy_derive.rs");
|
||||
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
|
||||
t.compile_fail("tests/ui/pyclass_send.rs");
|
||||
|
|
|
@ -192,7 +192,7 @@ fn test_raw_idents() {
|
|||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[name = "foobar"]
|
||||
#[pyo3(name = "foobar")]
|
||||
fn custom_named_fn() -> usize {
|
||||
42
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
#![deny(deprecated)]
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct TestClass {
|
||||
num: u32,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl TestClass {
|
||||
#[classattr]
|
||||
#[name = "num"]
|
||||
const DEPRECATED_NAME_CONSTANT: i32 = 0;
|
||||
|
||||
#[name = "num"]
|
||||
fn deprecated_name_pymethod(&self) { }
|
||||
|
||||
#[staticmethod]
|
||||
#[name = "custom_static"]
|
||||
fn deprecated_name_staticmethod() {}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[name = "foo"]
|
||||
fn deprecated_name_pyfunction() { }
|
||||
|
||||
fn main() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO: ensure name deprecated on #[pyfunction] and #[pymodule]
|
|
@ -0,0 +1,29 @@
|
|||
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
|
||||
--> $DIR/deprecations.rs:13:5
|
||||
|
|
||||
13 | #[name = "num"]
|
||||
| ^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/deprecations.rs:1:9
|
||||
|
|
||||
1 | #![deny(deprecated)]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
|
||||
--> $DIR/deprecations.rs:16:5
|
||||
|
|
||||
16 | #[name = "num"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
|
||||
--> $DIR/deprecations.rs:20:5
|
||||
|
|
||||
20 | #[name = "custom_static"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
|
||||
--> $DIR/deprecations.rs:25:1
|
||||
|
|
||||
25 | #[name = "foo"]
|
||||
| ^
|
|
@ -1,22 +1,22 @@
|
|||
error: only `from_py_with` is supported
|
||||
error: expected `from_py_with`
|
||||
--> $DIR/invalid_argument_attributes.rs:4:29
|
||||
|
|
||||
4 | fn invalid_attribute(#[pyo3(get)] param: String) {}
|
||||
| ^^^
|
||||
|
||||
error: expected a name-value: `pyo3(from_py_with = "func")`
|
||||
--> $DIR/invalid_argument_attributes.rs:7:33
|
||||
error: expected `=`
|
||||
--> $DIR/invalid_argument_attributes.rs:7:32
|
||||
|
|
||||
7 | fn from_py_with_no_value(#[pyo3(from_py_with)] param: String) {}
|
||||
| ^^^^^^^^^^^^
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: expected `from_py_with`, got a literal
|
||||
error: expected `from_py_with`
|
||||
--> $DIR/invalid_argument_attributes.rs:10:31
|
||||
|
|
||||
10 | fn from_py_with_string(#[pyo3("from_py_with")] param: String) {}
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: expected literal
|
||||
error: expected string literal
|
||||
--> $DIR/invalid_argument_attributes.rs:13:58
|
||||
|
|
||||
13 | fn from_py_with_value_not_a_string(#[pyo3(from_py_with = func)] param: String) {}
|
||||
|
|
|
@ -84,23 +84,23 @@ error: transparent structs and variants can only have 1 field
|
|||
70 | | },
|
||||
| |_____^
|
||||
|
||||
error: expected `attribute`, `item` or `from_py_with`
|
||||
error: expected one of: `attribute`, `item`, `from_py_with`
|
||||
--> $DIR/invalid_frompy_derive.rs:76:12
|
||||
|
|
||||
76 | #[pyo3(attr)]
|
||||
| ^^^^
|
||||
|
||||
error: expected a single string literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:82:12
|
||||
error: expected string literal
|
||||
--> $DIR/invalid_frompy_derive.rs:82:22
|
||||
|
|
||||
82 | #[pyo3(attribute(1))]
|
||||
| ^^^^^^^^^
|
||||
| ^
|
||||
|
||||
error: expected a single string literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:88:12
|
||||
error: expected at most one argument: `attribute` or `attribute("name")`
|
||||
--> $DIR/invalid_frompy_derive.rs:88:25
|
||||
|
|
||||
88 | #[pyo3(attribute("a", "b"))]
|
||||
| ^^^^^^^^^
|
||||
| ^
|
||||
|
||||
error: attribute name cannot be empty
|
||||
--> $DIR/invalid_frompy_derive.rs:94:22
|
||||
|
@ -108,43 +108,43 @@ error: attribute name cannot be empty
|
|||
94 | #[pyo3(attribute(""))]
|
||||
| ^^
|
||||
|
||||
error: expected a single string literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:100:12
|
||||
error: unexpected end of input, expected string literal
|
||||
--> $DIR/invalid_frompy_derive.rs:100:21
|
||||
|
|
||||
100 | #[pyo3(attribute())]
|
||||
| ^^^^^^^^^
|
||||
| ^^
|
||||
|
||||
error: expected a single literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:106:12
|
||||
error: expected at most one argument: `item` or `item(key)`
|
||||
--> $DIR/invalid_frompy_derive.rs:106:20
|
||||
|
|
||||
106 | #[pyo3(item("a", "b"))]
|
||||
| ^^^^
|
||||
| ^
|
||||
|
||||
error: expected a single literal argument
|
||||
--> $DIR/invalid_frompy_derive.rs:112:12
|
||||
error: unexpected end of input, expected literal
|
||||
--> $DIR/invalid_frompy_derive.rs:112:16
|
||||
|
|
||||
112 | #[pyo3(item())]
|
||||
| ^^^^
|
||||
| ^^
|
||||
|
||||
error: only one of `attribute` or `item` can be provided
|
||||
--> $DIR/invalid_frompy_derive.rs:118:18
|
||||
--> $DIR/invalid_frompy_derive.rs:118:5
|
||||
|
|
||||
118 | #[pyo3(item, attribute)]
|
||||
| ^^^^^^^^^
|
||||
| ^
|
||||
|
||||
error: unknown `pyo3` container attribute
|
||||
error: expected `transparent` or `annotation`
|
||||
--> $DIR/invalid_frompy_derive.rs:123:8
|
||||
|
|
||||
123 | #[pyo3(unknown = "should not work")]
|
||||
| ^^^^^^^
|
||||
|
||||
error: annotation is not supported for structs
|
||||
error: `annotation` is unsupported for structs
|
||||
--> $DIR/invalid_frompy_derive.rs:129:21
|
||||
|
|
||||
129 | #[pyo3(annotation = "should not work")]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: expected string literal for annotation
|
||||
error: expected string literal
|
||||
--> $DIR/invalid_frompy_derive.rs:136:25
|
||||
|
|
||||
136 | #[pyo3(annotation = 1)]
|
||||
|
@ -170,13 +170,13 @@ error: cannot derive FromPyObject for empty structs and variants
|
|||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected a name-value: `pyo3(from_py_with = "func")`
|
||||
--> $DIR/invalid_frompy_derive.rs:158:12
|
||||
error: expected `=`
|
||||
--> $DIR/invalid_frompy_derive.rs:158:11
|
||||
|
|
||||
158 | #[pyo3(from_py_with)]
|
||||
| ^^^^^^^^^^^^
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: expected literal
|
||||
error: expected string literal
|
||||
--> $DIR/invalid_frompy_derive.rs:164:27
|
||||
|
|
||||
164 | #[pyo3(from_py_with = func)]
|
||||
|
|
|
@ -16,7 +16,7 @@ error: setter function can have at most two arguments ([pyo3::Python,] and value
|
|||
24 | fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {}
|
||||
| ^^^
|
||||
|
||||
error: get/set are not supported on tuple struct field
|
||||
error: `#[pyo3(get, set)]` is not supported on tuple struct fields
|
||||
--> $DIR/invalid_property_args.rs:28:44
|
||||
|
|
||||
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);
|
||||
|
|
|
@ -7,21 +7,21 @@ struct TestClass {
|
|||
|
||||
#[pymethods]
|
||||
impl TestClass {
|
||||
#[name = "num"]
|
||||
#[pyo3(name = "num")]
|
||||
#[getter(number)]
|
||||
fn get_num(&self) -> u32 { self.num }
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl TestClass {
|
||||
#[name = "foo"]
|
||||
#[name = "bar"]
|
||||
#[pyo3(name = "foo")]
|
||||
#[pyo3(name = "bar")]
|
||||
fn qux(&self) -> u32 { self.num }
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl TestClass {
|
||||
#[name = "makenew"]
|
||||
#[pyo3(name = "makenew")]
|
||||
#[new]
|
||||
fn new(&self) -> Self { Self { num: 0 } }
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
error: name cannot be specified twice
|
||||
--> $DIR/invalid_pymethod_names.rs:10:5
|
||||
error: `name` may only be specified once
|
||||
--> $DIR/invalid_pymethod_names.rs:10:19
|
||||
|
|
||||
10 | #[name = "num"]
|
||||
| ^
|
||||
10 | #[pyo3(name = "num")]
|
||||
| ^^^^^
|
||||
|
||||
error: #[name] can not be specified multiple times
|
||||
--> $DIR/invalid_pymethod_names.rs:18:5
|
||||
error: `name` may only be specified once
|
||||
--> $DIR/invalid_pymethod_names.rs:18:19
|
||||
|
|
||||
18 | #[name = "bar"]
|
||||
| ^
|
||||
18 | #[pyo3(name = "bar")]
|
||||
| ^^^^^
|
||||
|
||||
error: name not allowed with this method type
|
||||
--> $DIR/invalid_pymethod_names.rs:24:5
|
||||
error: `name` not allowed with `#[new]`
|
||||
--> $DIR/invalid_pymethod_names.rs:24:19
|
||||
|
|
||||
24 | #[name = "makenew"]
|
||||
| ^
|
||||
24 | #[pyo3(name = "makenew")]
|
||||
| ^^^^^^^^^
|
||||
|
|
|
@ -9,6 +9,12 @@ impl MyClass {
|
|||
fn class_attr_with_args(foo: i32) {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[classattr(foobar)]
|
||||
const CLASS_ATTR_WITH_ATTRIBUTE_ARG: i32 = 3;
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
fn staticmethod_without_attribute() {}
|
||||
|
|
|
@ -4,80 +4,86 @@ error: class attribute methods cannot take arguments
|
|||
9 | fn class_attr_with_args(foo: i32) {}
|
||||
| ^^^
|
||||
|
||||
error: static method needs #[staticmethod] attribute
|
||||
error: `#[classattr]` does not take any arguments
|
||||
--> $DIR/invalid_pymethods.rs:14:5
|
||||
|
|
||||
14 | fn staticmethod_without_attribute() {}
|
||||
14 | #[classattr(foobar)]
|
||||
| ^
|
||||
|
||||
error: static method needs #[staticmethod] attribute
|
||||
--> $DIR/invalid_pymethods.rs:20:5
|
||||
|
|
||||
20 | fn staticmethod_without_attribute() {}
|
||||
| ^^
|
||||
|
||||
error: unexpected receiver
|
||||
--> $DIR/invalid_pymethods.rs:20:35
|
||||
--> $DIR/invalid_pymethods.rs:26:35
|
||||
|
|
||||
20 | fn staticmethod_with_receiver(&self) {}
|
||||
26 | fn staticmethod_with_receiver(&self) {}
|
||||
| ^
|
||||
|
||||
error: expected receiver for #[getter]
|
||||
--> $DIR/invalid_pymethods.rs:33:5
|
||||
--> $DIR/invalid_pymethods.rs:39:5
|
||||
|
|
||||
33 | fn getter_without_receiver() {}
|
||||
39 | fn getter_without_receiver() {}
|
||||
| ^^
|
||||
|
||||
error: expected receiver for #[setter]
|
||||
--> $DIR/invalid_pymethods.rs:39:5
|
||||
--> $DIR/invalid_pymethods.rs:45:5
|
||||
|
|
||||
39 | fn setter_without_receiver() {}
|
||||
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
|
||||
--> $DIR/invalid_pymethods.rs:45:24
|
||||
--> $DIR/invalid_pymethods.rs:51:24
|
||||
|
|
||||
45 | #[text_signature = "()"]
|
||||
51 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:52:24
|
||||
--> $DIR/invalid_pymethods.rs:58:24
|
||||
|
|
||||
52 | #[text_signature = "()"]
|
||||
58 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:59:24
|
||||
--> $DIR/invalid_pymethods.rs:65:24
|
||||
|
|
||||
59 | #[text_signature = "()"]
|
||||
65 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:66:24
|
||||
--> $DIR/invalid_pymethods.rs:72:24
|
||||
|
|
||||
66 | #[text_signature = "()"]
|
||||
72 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> $DIR/invalid_pymethods.rs:73:24
|
||||
--> $DIR/invalid_pymethods.rs:79:24
|
||||
|
|
||||
73 | #[text_signature = "()"]
|
||||
79 | #[text_signature = "()"]
|
||||
| ^^^^
|
||||
|
||||
error: cannot specify a second method type
|
||||
--> $DIR/invalid_pymethods.rs:80:7
|
||||
--> $DIR/invalid_pymethods.rs:86:7
|
||||
|
|
||||
80 | #[staticmethod]
|
||||
86 | #[staticmethod]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: Python functions cannot have generic type parameters
|
||||
--> $DIR/invalid_pymethods.rs:86:23
|
||||
--> $DIR/invalid_pymethods.rs:92:23
|
||||
|
|
||||
86 | fn generic_method<T>(value: T) {}
|
||||
92 | fn generic_method<T>(value: T) {}
|
||||
| ^
|
||||
|
||||
error: Python functions cannot have `impl Trait` arguments
|
||||
--> $DIR/invalid_pymethods.rs:92:48
|
||||
--> $DIR/invalid_pymethods.rs:98:48
|
||||
|
|
||||
92 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
|
||||
98 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
|
||||
| ^^^^
|
||||
|
||||
error: Python functions cannot have `impl Trait` arguments
|
||||
--> $DIR/invalid_pymethods.rs:97:56
|
||||
|
|
||||
97 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
|
||||
| ^^^^
|
||||
--> $DIR/invalid_pymethods.rs:103:56
|
||||
|
|
||||
103 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
|
||||
| ^^^^
|
||||
|
|
Loading…
Reference in New Issue