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 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 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 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 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)
|
- 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__`
|
- `PyMappingProtocol::__reversed__`
|
||||||
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
|
- `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__`
|
||||||
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
|
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
|
||||||
|
- Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)
|
- 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]
|
#[pyfunction]
|
||||||
#[name = "solve"]
|
#[pyo3(name = "solve")]
|
||||||
pub fn solve_wrapper(model: &mut UserModel) {
|
pub fn solve_wrapper(model: &mut UserModel) {
|
||||||
solve(model);
|
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 proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::{
|
||||||
use syn::spanned::Spanned;
|
parenthesized,
|
||||||
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Meta, MetaList, Result};
|
parse::{Parse, ParseStream},
|
||||||
|
parse_quote,
|
||||||
|
punctuated::Punctuated,
|
||||||
|
spanned::Spanned,
|
||||||
|
Attribute, DataEnum, DeriveInput, Fields, Ident, LitStr, Result, Token,
|
||||||
|
};
|
||||||
|
|
||||||
/// Describes derivation input of an enum.
|
/// Describes derivation input of an enum.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -26,7 +31,7 @@ impl<'a> Enum<'a> {
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
let attrs = ContainerAttribute::parse_attrs(&variant.attrs)?;
|
let attrs = ContainerOptions::from_attrs(&variant.attrs)?;
|
||||||
let var_ident = &variant.ident;
|
let var_ident = &variant.ident;
|
||||||
Container::new(
|
Container::new(
|
||||||
&variant.fields,
|
&variant.fields,
|
||||||
|
@ -86,7 +91,7 @@ enum ContainerType<'a> {
|
||||||
/// Struct Container, e.g. `struct Foo { a: String }`
|
/// Struct Container, e.g. `struct Foo { a: String }`
|
||||||
///
|
///
|
||||||
/// Variant contains the list of field identifiers and the corresponding extraction call.
|
/// 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 }`
|
/// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }`
|
||||||
///
|
///
|
||||||
/// The field specified by the identifier is extracted directly from the object.
|
/// The field specified by the identifier is extracted directly from the object.
|
||||||
|
@ -119,20 +124,20 @@ impl<'a> Container<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
fields: &'a Fields,
|
fields: &'a Fields,
|
||||||
path: syn::Path,
|
path: syn::Path,
|
||||||
attrs: Vec<ContainerAttribute>,
|
options: ContainerOptions,
|
||||||
is_enum_variant: bool,
|
is_enum_variant: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
ensure_spanned!(
|
ensure_spanned!(
|
||||||
!fields.is_empty(),
|
!fields.is_empty(),
|
||||||
fields.span() => "cannot derive FromPyObject for empty structs and variants"
|
fields.span() => "cannot derive FromPyObject for empty structs and variants"
|
||||||
);
|
);
|
||||||
let transparent = attrs
|
if options.transparent {
|
||||||
.iter()
|
ensure_spanned!(
|
||||||
.any(|attr| *attr == ContainerAttribute::Transparent);
|
fields.len() == 1,
|
||||||
if transparent {
|
fields.span() => "transparent structs and variants can only have 1 field"
|
||||||
Self::check_transparent_len(fields)?;
|
);
|
||||||
}
|
}
|
||||||
let style = match (fields, transparent) {
|
let style = match (fields, options.transparent) {
|
||||||
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
|
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
|
||||||
(Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() {
|
(Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() {
|
||||||
1 => ContainerType::TupleNewtype,
|
1 => ContainerType::TupleNewtype,
|
||||||
|
@ -157,17 +162,17 @@ impl<'a> Container<'a> {
|
||||||
.ident
|
.ident
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Named fields should have identifiers");
|
.expect("Named fields should have identifiers");
|
||||||
let attrs = FieldAttributes::parse_attrs(&field.attrs)?;
|
let attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?;
|
||||||
fields.push((ident, attrs))
|
fields.push((ident, attrs))
|
||||||
}
|
}
|
||||||
ContainerType::Struct(fields)
|
ContainerType::Struct(fields)
|
||||||
}
|
}
|
||||||
(Fields::Unit, _) => unreachable!(), // covered by length check above
|
(Fields::Unit, _) => unreachable!(), // covered by length check above
|
||||||
};
|
};
|
||||||
let err_name = attrs
|
let err_name = options.annotation.map_or_else(
|
||||||
.iter()
|
|| path.segments.last().unwrap().ident.to_string(),
|
||||||
.find_map(|a| a.annotation().map(syn::LitStr::value))
|
|lit_str| lit_str.value(),
|
||||||
.unwrap_or_else(|| path.segments.last().unwrap().ident.to_string());
|
);
|
||||||
|
|
||||||
let v = Container {
|
let v = Container {
|
||||||
path,
|
path,
|
||||||
|
@ -178,18 +183,6 @@ impl<'a> Container<'a> {
|
||||||
Ok(v)
|
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.
|
/// Build derivation body for a struct.
|
||||||
fn build(&self) -> TokenStream {
|
fn build(&self) -> TokenStream {
|
||||||
match &self.ty {
|
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 self_ty = &self.path;
|
||||||
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
||||||
for (ident, attrs) in tups {
|
for (ident, attrs) in tups {
|
||||||
|
@ -256,67 +249,73 @@ impl<'a> Container<'a> {
|
||||||
}
|
}
|
||||||
quote!(Ok(#self_ty{#fields}))
|
quote!(Ok(#self_ty{#fields}))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_transparent_len(fields: &Fields) -> Result<()> {
|
struct ContainerOptions {
|
||||||
ensure_spanned!(
|
transparent: bool,
|
||||||
fields.len() == 1,
|
annotation: Option<syn::LitStr>,
|
||||||
fields.span() => "transparent structs and variants can only have 1 field"
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes for deriving FromPyObject scoped on containers.
|
/// Attributes for deriving FromPyObject scoped on containers.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
enum ContainerAttribute {
|
enum ContainerPyO3Attribute {
|
||||||
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
|
/// 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.
|
/// Change the name of an enum variant in the generated error message.
|
||||||
ErrorAnnotation(syn::LitStr),
|
ErrorAnnotation(LitStr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerAttribute {
|
impl Parse for ContainerPyO3Attribute {
|
||||||
/// Convenience method to access `ErrorAnnotation`.
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
fn annotation(&self) -> Option<&syn::LitStr> {
|
let lookahead = input.lookahead1();
|
||||||
match self {
|
if lookahead.peek(attributes::kw::transparent) {
|
||||||
ContainerAttribute::ErrorAnnotation(s) => Some(s),
|
let kw: attributes::kw::transparent = input.parse()?;
|
||||||
_ => None,
|
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
|
impl ContainerOptions {
|
||||||
///
|
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
|
||||||
/// Fails if any are invalid.
|
let mut options = ContainerOptions {
|
||||||
fn parse_attrs(value: &[Attribute]) -> Result<Vec<Self>> {
|
transparent: false,
|
||||||
get_pyo3_meta_list(value)?
|
annotation: None,
|
||||||
.nested
|
};
|
||||||
.into_iter()
|
for attr in attrs {
|
||||||
.map(|meta| {
|
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
|
||||||
if let syn::NestedMeta::Meta(metaitem) = &meta {
|
for pyo3_attr in pyo3_attrs {
|
||||||
match metaitem {
|
match pyo3_attr {
|
||||||
Meta::Path(p) if p.is_ident("transparent") => {
|
ContainerPyO3Attribute::Transparent(kw) => {
|
||||||
return Ok(ContainerAttribute::Transparent);
|
ensure_spanned!(
|
||||||
|
!options.transparent,
|
||||||
|
kw.span() => "`transparent` may only be provided once"
|
||||||
|
);
|
||||||
|
options.transparent = true;
|
||||||
}
|
}
|
||||||
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
|
ContainerPyO3Attribute::ErrorAnnotation(lit_str) => {
|
||||||
if let syn::Lit::Str(s) = &nv.lit {
|
ensure_spanned!(
|
||||||
return Ok(ContainerAttribute::ErrorAnnotation(s.clone()));
|
options.annotation.is_none(),
|
||||||
} else {
|
lit_str.span() => "`annotation` may only be provided once"
|
||||||
bail_spanned!(nv.lit.span() => "expected string literal for annotation");
|
);
|
||||||
}
|
options.annotation = Some(lit_str);
|
||||||
}
|
}
|
||||||
_ => {} // return Err below
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bail_spanned!(meta.span() => "unknown `pyo3` container attribute");
|
}
|
||||||
})
|
Ok(options)
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes for deriving FromPyObject scoped on fields.
|
/// Attributes for deriving FromPyObject scoped on fields.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct FieldAttributes {
|
struct FieldPyO3Attributes {
|
||||||
getter: FieldGetter,
|
getter: FieldGetter,
|
||||||
from_py_with: Option<FromPyWithAttribute>,
|
from_py_with: Option<FromPyWithAttribute>,
|
||||||
}
|
}
|
||||||
|
@ -324,121 +323,96 @@ struct FieldAttributes {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum FieldGetter {
|
enum FieldGetter {
|
||||||
GetItem(Option<syn::Lit>),
|
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.
|
/// Extract the field attributes.
|
||||||
///
|
///
|
||||||
fn parse_attrs(attrs: &[Attribute]) -> Result<Self> {
|
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
|
||||||
let mut getter = None;
|
let mut getter = None;
|
||||||
let mut from_py_with = None;
|
let mut from_py_with = None;
|
||||||
|
|
||||||
let list = get_pyo3_meta_list(attrs)?;
|
for attr in attrs {
|
||||||
|
if let Some(pyo3_attrs) = get_pyo3_attribute(attr)? {
|
||||||
for meta_item in list.nested {
|
for pyo3_attr in pyo3_attrs {
|
||||||
let meta = match meta_item {
|
match pyo3_attr {
|
||||||
syn::NestedMeta::Meta(meta) => meta,
|
FieldPyO3Attribute::Getter(field_getter) => {
|
||||||
syn::NestedMeta::Lit(lit) => bail_spanned!(
|
ensure_spanned!(
|
||||||
lit.span() =>
|
getter.is_none(),
|
||||||
"expected `attribute`, `item` or `from_py_with`, got a literal"
|
attr.span() => "only one of `attribute` or `item` can be provided"
|
||||||
),
|
);
|
||||||
};
|
getter = Some(field_getter)
|
||||||
let path = meta.path();
|
}
|
||||||
|
FieldPyO3Attribute::FromPyWith(from_py_with_attr) => {
|
||||||
if path.is_ident("attribute") {
|
ensure_spanned!(
|
||||||
ensure_spanned!(
|
from_py_with.is_none(),
|
||||||
getter.is_none(),
|
attr.span() => "`from_py_with` may only be provided once"
|
||||||
meta.span() => "only one of `attribute` or `item` can be provided"
|
);
|
||||||
);
|
from_py_with = Some(from_py_with_attr);
|
||||||
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`")
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FieldAttributes {
|
Ok(FieldPyO3Attributes {
|
||||||
getter: getter.unwrap_or(FieldGetter::GetAttr(None)),
|
getter: getter.unwrap_or(FieldGetter::GetAttr(None)),
|
||||||
from_py_with,
|
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>> {
|
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()
|
en.build()
|
||||||
}
|
}
|
||||||
syn::Data::Struct(st) => {
|
syn::Data::Struct(st) => {
|
||||||
let attrs = ContainerAttribute::parse_attrs(&tokens.attrs)?;
|
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
|
||||||
Container::verify_struct_container_attrs(&attrs)?;
|
if let Some(lit_str) = &options.annotation {
|
||||||
|
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
|
||||||
|
}
|
||||||
let ident = &tokens.ident;
|
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()
|
st.build()
|
||||||
}
|
}
|
||||||
syn::Data::Union(_) => bail_spanned!(
|
syn::Data::Union(_) => bail_spanned!(
|
||||||
|
|
|
@ -1,34 +1,99 @@
|
||||||
use crate::pyfunction::parse_name_attribute;
|
use crate::attributes::{
|
||||||
use syn::ext::IdentExt;
|
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 struct ConstSpec {
|
||||||
pub is_class_attr: bool,
|
pub rust_ident: syn::Ident,
|
||||||
pub python_name: syn::Ident,
|
pub attributes: ConstAttributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConstSpec {
|
impl ConstSpec {
|
||||||
// For now, the only valid attribute is `#[classattr]`.
|
/// Null-terminated Python name
|
||||||
pub fn parse(name: &syn::Ident, attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConstSpec> {
|
pub fn python_name_with_deprecation(&self) -> TokenStream {
|
||||||
let mut new_attrs = Vec::new();
|
if let Some(name) = &self.attributes.name {
|
||||||
let mut is_class_attr = false;
|
let deprecation =
|
||||||
|
utils::name_deprecation_token(name.0.span(), self.attributes.name_is_deprecated);
|
||||||
for attr in attrs.iter() {
|
let name = format!("{}\0", name.0);
|
||||||
if let syn::Meta::Path(name) = attr.parse_meta()? {
|
quote!({#deprecation #name})
|
||||||
if name.is_ident("classattr") {
|
} else {
|
||||||
is_class_attr = true;
|
let name = format!("{}\0", self.rust_ident.unraw().to_string());
|
||||||
continue;
|
quote!(#name)
|
||||||
}
|
|
||||||
}
|
|
||||||
new_attrs.push(attr.clone());
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
attrs.clear();
|
}
|
||||||
attrs.extend(new_attrs);
|
|
||||||
|
pub struct ConstAttributes {
|
||||||
Ok(ConstSpec {
|
pub is_class_attr: bool,
|
||||||
is_class_attr,
|
pub name: Option<NameAttribute>,
|
||||||
python_name: parse_name_attribute(attrs)?.unwrap_or_else(|| name.unraw()),
|
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]
|
#[macro_use]
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
mod attrs;
|
mod attributes;
|
||||||
mod defs;
|
mod defs;
|
||||||
mod from_pyobject;
|
mod from_pyobject;
|
||||||
mod konst;
|
mod konst;
|
||||||
|
@ -22,9 +22,9 @@ mod pymethod;
|
||||||
mod pyproto;
|
mod pyproto;
|
||||||
|
|
||||||
pub use from_pyobject::build_derive_from_pyobject;
|
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 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 pyimpl::{build_py_methods, PyClassMethodsType};
|
||||||
pub use pyproto::build_py_proto;
|
pub use pyproto::build_py_proto;
|
||||||
pub use utils::get_doc;
|
pub use utils::get_doc;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||||
|
|
||||||
use crate::pyfunction::Argument;
|
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 crate::utils;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
|
@ -17,7 +18,7 @@ pub struct FnArg<'a> {
|
||||||
pub ty: &'a syn::Type,
|
pub ty: &'a syn::Type,
|
||||||
pub optional: Option<&'a syn::Type>,
|
pub optional: Option<&'a syn::Type>,
|
||||||
pub py: bool,
|
pub py: bool,
|
||||||
pub attrs: PyFunctionArgAttrs,
|
pub attrs: PyFunctionArgPyO3Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FnArg<'a> {
|
impl<'a> FnArg<'a> {
|
||||||
|
@ -32,7 +33,7 @@ impl<'a> FnArg<'a> {
|
||||||
bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
|
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 {
|
let (ident, by_ref, mutability) = match *cap.pat {
|
||||||
syn::Pat::Ident(syn::PatIdent {
|
syn::Pat::Ident(syn::PatIdent {
|
||||||
ref ident,
|
ref ident,
|
||||||
|
@ -133,6 +134,7 @@ pub struct FnSpec<'a> {
|
||||||
pub args: Vec<FnArg<'a>>,
|
pub args: Vec<FnArg<'a>>,
|
||||||
pub output: syn::Type,
|
pub output: syn::Type,
|
||||||
pub doc: syn::LitStr,
|
pub doc: syn::LitStr,
|
||||||
|
pub name_is_deprecated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
||||||
|
@ -161,13 +163,29 @@ impl<'a> FnSpec<'a> {
|
||||||
pub fn parse(
|
pub fn parse(
|
||||||
sig: &'a mut syn::Signature,
|
sig: &'a mut syn::Signature,
|
||||||
meth_attrs: &mut Vec<syn::Attribute>,
|
meth_attrs: &mut Vec<syn::Attribute>,
|
||||||
allow_custom_name: bool,
|
options: PyFunctionOptions,
|
||||||
) -> syn::Result<FnSpec<'a>> {
|
) -> syn::Result<FnSpec<'a>> {
|
||||||
let MethodAttributes {
|
let MethodAttributes {
|
||||||
ty: fn_type_attr,
|
ty: fn_type_attr,
|
||||||
args: fn_attrs,
|
args: fn_attrs,
|
||||||
mut python_name,
|
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)?;
|
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,
|
args: arguments,
|
||||||
output: ty,
|
output: ty,
|
||||||
doc,
|
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(
|
fn parse_text_signature(
|
||||||
meth_attrs: &mut Vec<syn::Attribute>,
|
meth_attrs: &mut Vec<syn::Attribute>,
|
||||||
fn_type: &FnType,
|
fn_type: &FnType,
|
||||||
|
@ -362,12 +388,11 @@ struct MethodAttributes {
|
||||||
|
|
||||||
fn parse_method_attributes(
|
fn parse_method_attributes(
|
||||||
attrs: &mut Vec<syn::Attribute>,
|
attrs: &mut Vec<syn::Attribute>,
|
||||||
allow_custom_name: bool,
|
mut python_name: Option<syn::Ident>,
|
||||||
) -> syn::Result<MethodAttributes> {
|
) -> syn::Result<MethodAttributes> {
|
||||||
let mut new_attrs = Vec::new();
|
let mut new_attrs = Vec::new();
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut ty: Option<MethodTypeAttribute> = None;
|
let mut ty: Option<MethodTypeAttribute> = None;
|
||||||
let mut property_name = None;
|
|
||||||
|
|
||||||
macro_rules! set_ty {
|
macro_rules! set_ty {
|
||||||
($new_ty:expr, $ident:expr) => {
|
($new_ty:expr, $ident:expr) => {
|
||||||
|
@ -434,7 +459,12 @@ fn parse_method_attributes(
|
||||||
set_ty!(MethodTypeAttribute::Getter, path);
|
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 => {
|
syn::NestedMeta::Meta(syn::Meta::Path(w)) if w.segments.len() == 1 => {
|
||||||
Some(w.segments[0].ident.clone())
|
Some(w.segments[0].ident.clone())
|
||||||
}
|
}
|
||||||
|
@ -455,7 +485,7 @@ fn parse_method_attributes(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if path.is_ident("args") {
|
} else if path.is_ident("args") {
|
||||||
let attrs = PyFunctionAttr::from_meta(&nested)?;
|
let attrs = PyFunctionSignature::from_meta(&nested)?;
|
||||||
args.extend(attrs.arguments)
|
args.extend(attrs.arguments)
|
||||||
} else {
|
} else {
|
||||||
new_attrs.push(attr)
|
new_attrs.push(attr)
|
||||||
|
@ -467,21 +497,6 @@ fn parse_method_attributes(
|
||||||
|
|
||||||
*attrs = new_attrs;
|
*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 {
|
Ok(MethodAttributes {
|
||||||
ty,
|
ty,
|
||||||
args,
|
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";
|
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||||
//! Code generation for the function that initializes a python module and adds classes and function.
|
//! Code generation for the function that initializes a python module and adds classes and function.
|
||||||
|
|
||||||
use crate::method::{self, FnArg};
|
use crate::attributes::{attribute_ident_is, take_attributes, NameAttribute};
|
||||||
use crate::pyfunction::PyFunctionAttr;
|
use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions};
|
||||||
use crate::pymethod::{check_generic, get_arg_names, impl_arg_params};
|
|
||||||
use crate::utils;
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::quote;
|
||||||
use syn::{spanned::Spanned, Ident, Result};
|
use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
|
||||||
|
|
||||||
/// Generates the function that is called by the python interpreter to initialize the native
|
/// Generates the function that is called by the python interpreter to initialize the native
|
||||||
/// module
|
/// 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() {
|
for stmt in func.block.stmts.iter_mut() {
|
||||||
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
|
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
|
||||||
if let Some((module_name, python_name, pyfn_attrs)) =
|
if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? {
|
||||||
extract_pyfn_attrs(&mut func.attrs)?
|
let module_name = pyfn_args.modname;
|
||||||
{
|
let (ident, wrapped_function) = impl_wrap_pyfunction(func, pyfn_args.options)?;
|
||||||
let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?;
|
|
||||||
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
|
|
||||||
let item: syn::ItemFn = syn::parse_quote! {
|
let item: syn::ItemFn = syn::parse_quote! {
|
||||||
fn block_wrapper() {
|
fn block_wrapper() {
|
||||||
#function_to_python
|
#wrapped_function
|
||||||
#module_name.add_function(#function_wrapper_ident(#module_name)?)?;
|
#module_name.add_function(#ident(#module_name)?)?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
stmts.extend(item.block.stmts.into_iter());
|
stmts.extend(item.block.stmts.into_iter());
|
||||||
|
@ -56,190 +52,49 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
|
||||||
Ok(())
|
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
|
/// Extracts the data from the #[pyfn(...)] attribute of a function
|
||||||
fn extract_pyfn_attrs(
|
fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs>> {
|
||||||
attrs: &mut Vec<syn::Attribute>,
|
let mut pyfn_args: Option<PyFnArgs> = None;
|
||||||
) -> 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();
|
|
||||||
|
|
||||||
for attr in attrs.drain(..) {
|
take_attributes(attrs, |attr| {
|
||||||
match attr.parse_meta() {
|
if attribute_ident_is(attr, "pyfn") {
|
||||||
Ok(syn::Meta::List(list)) if list.path.is_ident("pyfn") => {
|
ensure_spanned!(
|
||||||
let meta: Vec<_> = list.nested.iter().cloned().collect();
|
pyfn_args.is_none(),
|
||||||
if meta.len() >= 2 {
|
attr.span() => "`#[pyfn] may only be specified once"
|
||||||
// read module name
|
);
|
||||||
match &meta[0] {
|
pyfn_args = Some(attr.parse_args()?);
|
||||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
Ok(true)
|
||||||
modname = Some(path.clone())
|
} else {
|
||||||
}
|
Ok(false)
|
||||||
_ => 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),
|
|
||||||
}
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(pyfn_args) = &mut pyfn_args {
|
||||||
|
pyfn_args.options.take_pyo3_attributes(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
*attrs = new_attrs;
|
Ok(pyfn_args)
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -499,21 +499,21 @@ fn impl_descriptors(
|
||||||
.flat_map(|(field, fns)| {
|
.flat_map(|(field, fns)| {
|
||||||
fns.iter()
|
fns.iter()
|
||||||
.map(|desc| {
|
.map(|desc| {
|
||||||
if let Some(name) = field.ident.as_ref().map(|ident| ident.unraw()) {
|
let doc = utils::get_doc(&field.attrs, None, true)
|
||||||
let doc = utils::get_doc(&field.attrs, None, true)
|
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
|
||||||
.unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span()));
|
let property_type = PropertyType::Descriptor(
|
||||||
let property_type = PropertyType::Descriptor(&field);
|
field.ident.as_ref().ok_or_else(
|
||||||
match desc {
|
|| err_spanned!(field.span() => "`#[pyo3(get, set)]` is not supported on tuple struct fields")
|
||||||
FnType::Getter(self_ty) => {
|
)?
|
||||||
impl_py_getter_def(cls, property_type, self_ty, &name, &doc)
|
);
|
||||||
}
|
match desc {
|
||||||
FnType::Setter(self_ty) => {
|
FnType::Getter(self_ty) => {
|
||||||
impl_py_setter_def(cls, property_type, self_ty, &name, &doc)
|
impl_py_getter_def(cls, property_type, self_ty, &doc)
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
} else {
|
FnType::Setter(self_ty) => {
|
||||||
bail_spanned!(field.span() => "get/set are not supported on tuple struct field");
|
impl_py_setter_def(cls, property_type, self_ty, &doc)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<syn::Result<TokenStream>>>()
|
.collect::<Vec<syn::Result<TokenStream>>>()
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||||
|
|
||||||
use crate::attrs::FromPyWithAttribute;
|
use crate::{
|
||||||
use crate::module::add_fn_to_module;
|
attributes::{
|
||||||
use proc_macro2::TokenStream;
|
self, get_deprecated_name_attribute, get_pyo3_attribute, take_attributes,
|
||||||
use syn::ext::IdentExt;
|
FromPyWithAttribute, NameAttribute,
|
||||||
use syn::parse::ParseBuffer;
|
},
|
||||||
|
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::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::{ext::IdentExt, spanned::Spanned, Ident, NestedMeta, Path, Result};
|
||||||
use syn::{NestedMeta, Path};
|
use syn::{
|
||||||
|
parse::{Parse, ParseBuffer, ParseStream},
|
||||||
|
token::Comma,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Argument {
|
pub enum Argument {
|
||||||
|
@ -20,29 +29,69 @@ pub enum Argument {
|
||||||
|
|
||||||
/// The attributes of the pyfunction macro
|
/// The attributes of the pyfunction macro
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PyFunctionAttr {
|
pub struct PyFunctionSignature {
|
||||||
pub arguments: Vec<Argument>,
|
pub arguments: Vec<Argument>,
|
||||||
has_kw: bool,
|
has_kw: bool,
|
||||||
has_varargs: bool,
|
has_varargs: bool,
|
||||||
has_kwargs: bool,
|
has_kwargs: bool,
|
||||||
pub pass_module: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct PyFunctionArgAttrs {
|
pub struct PyFunctionArgPyO3Attributes {
|
||||||
pub from_py_with: Option<FromPyWithAttribute>,
|
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> {
|
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
|
||||||
let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?;
|
let attr = Punctuated::<NestedMeta, syn::Token![,]>::parse_terminated(input)?;
|
||||||
Self::from_meta(&attr)
|
Self::from_meta(&attr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyFunctionAttr {
|
impl PyFunctionSignature {
|
||||||
pub fn from_meta<'a>(iter: impl IntoIterator<Item = &'a NestedMeta>) -> syn::Result<Self> {
|
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 {
|
for item in iter {
|
||||||
slf.add_item(item)?
|
slf.add_item(item)?
|
||||||
|
@ -52,9 +101,6 @@ impl PyFunctionAttr {
|
||||||
|
|
||||||
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
|
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
|
||||||
match item {
|
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::Path(ident)) => self.add_work(item, ident)?,
|
||||||
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
|
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
|
||||||
self.add_name_value(item, 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>> {
|
#[derive(Default)]
|
||||||
let mut name_attrs = Vec::new();
|
pub struct PyFunctionOptions {
|
||||||
|
pub pass_module: bool,
|
||||||
// Using retain will extract all name attributes from the attribute list
|
pub name: Option<NameAttribute>,
|
||||||
attrs.retain(|attr| match attr.parse_meta() {
|
pub name_is_deprecated: bool,
|
||||||
Ok(syn::Meta::NameValue(nv)) if nv.path.is_ident("name") => {
|
pub signature: Option<PyFunctionSignature>,
|
||||||
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"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result<TokenStream> {
|
impl Parse for PyFunctionOptions {
|
||||||
let python_name =
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw());
|
let mut options = PyFunctionOptions {
|
||||||
add_fn_to_module(ast, python_name, args)
|
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>> {
|
while !input.is_empty() {
|
||||||
let mut new_attrs = Vec::new();
|
let lookahead = input.lookahead1();
|
||||||
let mut metas = Vec::new();
|
if lookahead.peek(attributes::kw::name)
|
||||||
|
|| lookahead.peek(attributes::kw::pass_module)
|
||||||
for attr in attrs.drain(..) {
|
|| lookahead.peek(attributes::kw::signature)
|
||||||
if let syn::Meta::List(meta_list) = attr.parse_meta()? {
|
{
|
||||||
if meta_list.path.is_ident("pyo3") {
|
options.add_attributes(std::iter::once(input.parse()?))?;
|
||||||
for meta in meta_list.nested {
|
if !input.is_empty() {
|
||||||
metas.push(meta);
|
let _: Comma = input.parse()?;
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
pub enum PyFunctionOption {
|
||||||
/// Parses #[pyo3(from_python_with = "func")]
|
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> {
|
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)? {
|
pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
|
||||||
let meta = match meta {
|
take_attributes(attrs, |attr| {
|
||||||
NestedMeta::Meta(meta) => meta,
|
if let Some(pyo3_attributes) = get_pyo3_attribute(attr)? {
|
||||||
NestedMeta::Lit(lit) => {
|
self.add_attributes(pyo3_attributes)?;
|
||||||
bail_spanned!(lit.span() => "expected `from_py_with`, got a literal")
|
Ok(true)
|
||||||
}
|
} else if let Some(name) = get_deprecated_name_attribute(attr)? {
|
||||||
};
|
self.set_name(name)?;
|
||||||
|
self.name_is_deprecated = true;
|
||||||
if meta.path().is_ident("from_py_with") {
|
Ok(true)
|
||||||
from_py_with = Some(FromPyWithAttribute::from_meta(meta)?);
|
|
||||||
} else {
|
} 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(())
|
||||||
Ok(PyFunctionArgAttrs { from_py_with })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{Argument, PyFunctionAttr};
|
use super::{Argument, PyFunctionSignature};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse_quote;
|
use syn::parse_quote;
|
||||||
|
|
||||||
fn items(input: TokenStream) -> syn::Result<Vec<Argument>> {
|
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)
|
Ok(py_fn_attr.arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||||
|
|
||||||
use crate::pymethod;
|
use crate::{
|
||||||
|
konst::{ConstAttributes, ConstSpec},
|
||||||
|
pyfunction::PyFunctionOptions,
|
||||||
|
pymethod,
|
||||||
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use pymethod::GeneratedPyMethod;
|
use pymethod::GeneratedPyMethod;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -39,7 +43,8 @@ pub fn impl_methods(
|
||||||
for iimpl in impls.iter_mut() {
|
for iimpl in impls.iter_mut() {
|
||||||
match iimpl {
|
match iimpl {
|
||||||
syn::ImplItem::Method(meth) => {
|
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) => {
|
GeneratedPyMethod::Method(token_stream) => {
|
||||||
let attrs = get_cfg_attributes(&meth.attrs);
|
let attrs = get_cfg_attributes(&meth.attrs);
|
||||||
methods.push(quote!(#(#attrs)* #token_stream));
|
methods.push(quote!(#(#attrs)* #token_stream));
|
||||||
|
@ -55,8 +60,14 @@ pub fn impl_methods(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syn::ImplItem::Const(konst) => {
|
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 attrs = get_cfg_attributes(&konst.attrs);
|
||||||
|
let meth = pymethod::gen_py_const(ty, &spec);
|
||||||
methods.push(quote!(#(#attrs)* #meth));
|
methods.push(quote!(#(#attrs)* #meth));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// 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::utils;
|
||||||
|
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
|
||||||
|
use crate::{
|
||||||
|
method::{FnArg, FnSpec, FnType, SelfType},
|
||||||
|
pyfunction::PyFunctionOptions,
|
||||||
|
};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, quote_spanned};
|
use quote::{quote, quote_spanned};
|
||||||
use syn::{ext::IdentExt, spanned::Spanned, Result};
|
use syn::{ext::IdentExt, spanned::Spanned, Result};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum PropertyType<'a> {
|
pub enum PropertyType<'a> {
|
||||||
Descriptor(&'a syn::Field),
|
Descriptor(&'a syn::Ident),
|
||||||
Function(&'a FnSpec<'a>),
|
Function(&'a FnSpec<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,9 +25,10 @@ pub fn gen_py_method(
|
||||||
cls: &syn::Type,
|
cls: &syn::Type,
|
||||||
sig: &mut syn::Signature,
|
sig: &mut syn::Signature,
|
||||||
meth_attrs: &mut Vec<syn::Attribute>,
|
meth_attrs: &mut Vec<syn::Attribute>,
|
||||||
|
options: PyFunctionOptions,
|
||||||
) -> Result<GeneratedPyMethod> {
|
) -> Result<GeneratedPyMethod> {
|
||||||
check_generic(sig)?;
|
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 {
|
Ok(match &spec.tp {
|
||||||
FnType::Fn(self_ty) => {
|
FnType::Fn(self_ty) => {
|
||||||
|
@ -43,14 +47,12 @@ pub fn gen_py_method(
|
||||||
cls,
|
cls,
|
||||||
PropertyType::Function(&spec),
|
PropertyType::Function(&spec),
|
||||||
self_ty,
|
self_ty,
|
||||||
&spec.python_name,
|
|
||||||
&spec.doc,
|
&spec.doc,
|
||||||
)?),
|
)?),
|
||||||
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
|
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
|
||||||
cls,
|
cls,
|
||||||
PropertyType::Function(&spec),
|
PropertyType::Function(&spec),
|
||||||
self_ty,
|
self_ty,
|
||||||
&spec.python_name,
|
|
||||||
&spec.doc,
|
&spec.doc,
|
||||||
)?),
|
)?),
|
||||||
})
|
})
|
||||||
|
@ -68,22 +70,15 @@ pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_py_const(
|
pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
|
||||||
cls: &syn::Type,
|
let member = &spec.rust_ident;
|
||||||
name: &syn::Ident,
|
let wrapper = quote! {{
|
||||||
attrs: &mut Vec<syn::Attribute>,
|
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||||
) -> syn::Result<Option<TokenStream>> {
|
pyo3::IntoPy::into_py(#cls::#member, py)
|
||||||
let spec = ConstSpec::parse(name, attrs)?;
|
}
|
||||||
if spec.is_class_attr {
|
__wrap
|
||||||
let wrapper = quote! {{
|
}};
|
||||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
impl_py_const_class_attribute(&spec, &wrapper)
|
||||||
pyo3::IntoPy::into_py(#cls::#name, py)
|
|
||||||
}
|
|
||||||
__wrap
|
|
||||||
}};
|
|
||||||
return Ok(Some(impl_py_const_class_attribute(&spec, &wrapper)));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate function wrapper for PyCFunctionWithKeywords
|
/// Generate function wrapper for PyCFunctionWithKeywords
|
||||||
|
@ -255,12 +250,9 @@ pub(crate) fn impl_wrap_getter(
|
||||||
property_type: PropertyType,
|
property_type: PropertyType,
|
||||||
self_ty: &SelfType,
|
self_ty: &SelfType,
|
||||||
) -> syn::Result<TokenStream> {
|
) -> syn::Result<TokenStream> {
|
||||||
let getter_impl = match property_type {
|
let getter_impl = match &property_type {
|
||||||
PropertyType::Descriptor(field) => {
|
PropertyType::Descriptor(ident) => {
|
||||||
let name = field.ident.as_ref().unwrap();
|
quote!(_slf.#ident.clone())
|
||||||
quote!({
|
|
||||||
_slf.#name.clone()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
|
PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
|
||||||
};
|
};
|
||||||
|
@ -307,10 +299,9 @@ pub(crate) fn impl_wrap_setter(
|
||||||
property_type: PropertyType,
|
property_type: PropertyType,
|
||||||
self_ty: &SelfType,
|
self_ty: &SelfType,
|
||||||
) -> syn::Result<TokenStream> {
|
) -> syn::Result<TokenStream> {
|
||||||
let setter_impl = match property_type {
|
let setter_impl = match &property_type {
|
||||||
PropertyType::Descriptor(field) => {
|
PropertyType::Descriptor(ident) => {
|
||||||
let name = field.ident.as_ref().unwrap();
|
quote!({ _slf.#ident = _val; })
|
||||||
quote!({ _slf.#name = _val; })
|
|
||||||
}
|
}
|
||||||
PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
|
PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
|
||||||
};
|
};
|
||||||
|
@ -584,14 +575,14 @@ pub fn impl_py_method_def(
|
||||||
flags: Option<TokenStream>,
|
flags: Option<TokenStream>,
|
||||||
) -> Result<TokenStream> {
|
) -> Result<TokenStream> {
|
||||||
let add_flags = flags.map(|flags| quote!(.flags(#flags)));
|
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;
|
let doc = &spec.doc;
|
||||||
if spec.args.is_empty() {
|
if spec.args.is_empty() {
|
||||||
let wrapper = impl_wrap_noargs(cls, spec, self_ty);
|
let wrapper = impl_wrap_noargs(cls, spec, self_ty);
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
pyo3::class::PyMethodDefType::Method({
|
pyo3::class::PyMethodDefType::Method({
|
||||||
pyo3::class::PyMethodDef::noargs(
|
pyo3::class::PyMethodDef::noargs(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PyCFunction(#wrapper),
|
pyo3::class::methods::PyCFunction(#wrapper),
|
||||||
#doc
|
#doc
|
||||||
)
|
)
|
||||||
|
@ -604,7 +595,7 @@ pub fn impl_py_method_def(
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
pyo3::class::PyMethodDefType::Method({
|
pyo3::class::PyMethodDefType::Method({
|
||||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||||
#doc
|
#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> {
|
pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||||
let wrapper = impl_wrap_class(cls, &spec)?;
|
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;
|
let doc = &spec.doc;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
pyo3::class::PyMethodDefType::Class({
|
pyo3::class::PyMethodDefType::Class({
|
||||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||||
#doc
|
#doc
|
||||||
).flags(pyo3::ffi::METH_CLASS)
|
).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> {
|
pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||||
let wrapper = impl_wrap_static(cls, &spec)?;
|
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;
|
let doc = &spec.doc;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
pyo3::class::PyMethodDefType::Static({
|
pyo3::class::PyMethodDefType::Static({
|
||||||
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
pyo3::class::PyMethodDef::cfunction_with_keywords(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
||||||
#doc
|
#doc
|
||||||
).flags(pyo3::ffi::METH_STATIC)
|
).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 {
|
pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
|
||||||
let wrapper = impl_wrap_class_attribute(cls, &spec);
|
let wrapper = impl_wrap_class_attribute(cls, &spec);
|
||||||
let python_name = &spec.python_name;
|
let python_name = spec.python_name_with_deprecation();
|
||||||
quote! {
|
quote! {
|
||||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||||
pyo3::class::PyClassAttributeDef::new(
|
pyo3::class::PyClassAttributeDef::new(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
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 {
|
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! {
|
quote! {
|
||||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
{
|
||||||
pyo3::class::PyClassAttributeDef::new(
|
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||||
concat!(stringify!(#python_name), "\0"),
|
pyo3::class::PyClassAttributeDef::new(
|
||||||
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
#python_name,
|
||||||
)
|
pyo3::class::methods::PyClassAttributeFactory(#wrapper)
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,14 +692,20 @@ pub(crate) fn impl_py_setter_def(
|
||||||
cls: &syn::Type,
|
cls: &syn::Type,
|
||||||
property_type: PropertyType,
|
property_type: PropertyType,
|
||||||
self_ty: &SelfType,
|
self_ty: &SelfType,
|
||||||
python_name: &syn::Ident,
|
|
||||||
doc: &syn::LitStr,
|
doc: &syn::LitStr,
|
||||||
) -> Result<TokenStream> {
|
) -> 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)?;
|
let wrapper = impl_wrap_setter(cls, property_type, self_ty)?;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
pyo3::class::PyMethodDefType::Setter({
|
pyo3::class::PyMethodDefType::Setter({
|
||||||
pyo3::class::PySetterDef::new(
|
pyo3::class::PySetterDef::new(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PySetter(#wrapper),
|
pyo3::class::methods::PySetter(#wrapper),
|
||||||
#doc
|
#doc
|
||||||
)
|
)
|
||||||
|
@ -718,14 +717,20 @@ pub(crate) fn impl_py_getter_def(
|
||||||
cls: &syn::Type,
|
cls: &syn::Type,
|
||||||
property_type: PropertyType,
|
property_type: PropertyType,
|
||||||
self_ty: &SelfType,
|
self_ty: &SelfType,
|
||||||
python_name: &syn::Ident,
|
|
||||||
doc: &syn::LitStr,
|
doc: &syn::LitStr,
|
||||||
) -> Result<TokenStream> {
|
) -> 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)?;
|
let wrapper = impl_wrap_getter(cls, property_type, self_ty)?;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
pyo3::class::PyMethodDefType::Getter({
|
pyo3::class::PyMethodDefType::Getter({
|
||||||
pyo3::class::PyGetterDef::new(
|
pyo3::class::PyGetterDef::new(
|
||||||
concat!(stringify!(#python_name), "\0"),
|
#python_name,
|
||||||
pyo3::class::methods::PyGetter(#wrapper),
|
pyo3::class::methods::PyGetter(#wrapper),
|
||||||
#doc
|
#doc
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::defs;
|
use crate::defs;
|
||||||
use crate::method::{FnSpec, FnType};
|
use crate::method::{FnSpec, FnType};
|
||||||
use crate::proto_method::impl_method_proto;
|
use crate::proto_method::impl_method_proto;
|
||||||
|
use crate::pyfunction::PyFunctionOptions;
|
||||||
use crate::pymethod;
|
use crate::pymethod;
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -62,7 +63,8 @@ fn impl_proto_impl(
|
||||||
}
|
}
|
||||||
// Add non-slot methods to inventory like `#[pymethods]`
|
// Add non-slot methods to inventory like `#[pymethods]`
|
||||||
if let Some(m) = proto.get_method(&met.sig.ident) {
|
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 {
|
let flags = if m.can_coexist {
|
||||||
// We need METH_COEXIST here to prevent __add__ from overriding __radd__
|
// We need METH_COEXIST here to prevent __add__ from overriding __radd__
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// 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;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
|
/// 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))
|
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::{
|
use pyo3_macros_backend::{
|
||||||
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,
|
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,
|
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
|
||||||
PyFunctionAttr,
|
PyFunctionOptions,
|
||||||
};
|
};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
@ -217,9 +217,9 @@ pub fn pymethods_with_inventory(_: TokenStream, input: TokenStream) -> TokenStre
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut ast = parse_macro_input!(input as syn::ItemFn);
|
let mut ast = parse_macro_input!(input as syn::ItemFn);
|
||||||
let args = parse_macro_input!(attr as PyFunctionAttr);
|
let 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!(
|
quote!(
|
||||||
#ast
|
#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 ffi;
|
||||||
pub mod freelist;
|
pub mod freelist;
|
||||||
mod gil;
|
mod gil;
|
||||||
|
pub mod impl_;
|
||||||
mod instance;
|
mod instance;
|
||||||
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
|
|
@ -19,13 +19,17 @@ impl Foo {
|
||||||
#[classattr]
|
#[classattr]
|
||||||
const MY_CONST: &'static str = "foobar";
|
const MY_CONST: &'static str = "foobar";
|
||||||
|
|
||||||
|
#[classattr]
|
||||||
|
#[pyo3(name = "RENAMED_CONST")]
|
||||||
|
const MY_CONST_2: &'static str = "foobar_2";
|
||||||
|
|
||||||
#[classattr]
|
#[classattr]
|
||||||
fn a() -> i32 {
|
fn a() -> i32 {
|
||||||
5
|
5
|
||||||
}
|
}
|
||||||
|
|
||||||
#[classattr]
|
#[classattr]
|
||||||
#[name = "B"]
|
#[pyo3(name = "B")]
|
||||||
fn b() -> String {
|
fn b() -> String {
|
||||||
"bar".to_string()
|
"bar".to_string()
|
||||||
}
|
}
|
||||||
|
@ -46,9 +50,11 @@ fn class_attributes() {
|
||||||
let gil = Python::acquire_gil();
|
let gil = Python::acquire_gil();
|
||||||
let py = gil.python();
|
let py = gil.python();
|
||||||
let foo_obj = py.get_type::<Foo>();
|
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.a == 5");
|
||||||
py_assert!(py, foo_obj, "foo_obj.B == 'bar'");
|
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:
|
// Ignored because heap types are not immutable:
|
||||||
|
|
|
@ -86,15 +86,15 @@ struct EmptyClass2 {}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl EmptyClass2 {
|
impl EmptyClass2 {
|
||||||
#[name = "custom_fn"]
|
#[pyo3(name = "custom_fn")]
|
||||||
fn bar(&self) {}
|
fn bar(&self) {}
|
||||||
|
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
#[name = "custom_static"]
|
#[pyo3(name = "custom_static")]
|
||||||
fn bar_static() {}
|
fn bar_static() {}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
#[name = "custom_getter"]
|
#[pyo3(name = "custom_getter")]
|
||||||
fn foo(&self) -> i32 {
|
fn foo(&self) -> i32 {
|
||||||
5
|
5
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ fn test_compile_errors() {
|
||||||
|
|
||||||
#[rustversion::since(1.49)]
|
#[rustversion::since(1.49)]
|
||||||
fn tests_rust_1_49(t: &trybuild::TestCases) {
|
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_frompy_derive.rs");
|
||||||
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
|
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
|
||||||
t.compile_fail("tests/ui/pyclass_send.rs");
|
t.compile_fail("tests/ui/pyclass_send.rs");
|
||||||
|
|
|
@ -192,7 +192,7 @@ fn test_raw_idents() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
#[name = "foobar"]
|
#[pyo3(name = "foobar")]
|
||||||
fn custom_named_fn() -> usize {
|
fn custom_named_fn() -> usize {
|
||||||
42
|
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
|
--> $DIR/invalid_argument_attributes.rs:4:29
|
||||||
|
|
|
|
||||||
4 | fn invalid_attribute(#[pyo3(get)] param: String) {}
|
4 | fn invalid_attribute(#[pyo3(get)] param: String) {}
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
||||||
error: expected a name-value: `pyo3(from_py_with = "func")`
|
error: expected `=`
|
||||||
--> $DIR/invalid_argument_attributes.rs:7:33
|
--> $DIR/invalid_argument_attributes.rs:7:32
|
||||||
|
|
|
|
||||||
7 | fn from_py_with_no_value(#[pyo3(from_py_with)] param: String) {}
|
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
|
--> $DIR/invalid_argument_attributes.rs:10:31
|
||||||
|
|
|
|
||||||
10 | fn from_py_with_string(#[pyo3("from_py_with")] param: String) {}
|
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
|
--> $DIR/invalid_argument_attributes.rs:13:58
|
||||||
|
|
|
|
||||||
13 | fn from_py_with_value_not_a_string(#[pyo3(from_py_with = func)] param: String) {}
|
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 | | },
|
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
|
--> $DIR/invalid_frompy_derive.rs:76:12
|
||||||
|
|
|
|
||||||
76 | #[pyo3(attr)]
|
76 | #[pyo3(attr)]
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
||||||
error: expected a single string literal argument
|
error: expected string literal
|
||||||
--> $DIR/invalid_frompy_derive.rs:82:12
|
--> $DIR/invalid_frompy_derive.rs:82:22
|
||||||
|
|
|
|
||||||
82 | #[pyo3(attribute(1))]
|
82 | #[pyo3(attribute(1))]
|
||||||
| ^^^^^^^^^
|
| ^
|
||||||
|
|
||||||
error: expected a single string literal argument
|
error: expected at most one argument: `attribute` or `attribute("name")`
|
||||||
--> $DIR/invalid_frompy_derive.rs:88:12
|
--> $DIR/invalid_frompy_derive.rs:88:25
|
||||||
|
|
|
|
||||||
88 | #[pyo3(attribute("a", "b"))]
|
88 | #[pyo3(attribute("a", "b"))]
|
||||||
| ^^^^^^^^^
|
| ^
|
||||||
|
|
||||||
error: attribute name cannot be empty
|
error: attribute name cannot be empty
|
||||||
--> $DIR/invalid_frompy_derive.rs:94:22
|
--> $DIR/invalid_frompy_derive.rs:94:22
|
||||||
|
@ -108,43 +108,43 @@ error: attribute name cannot be empty
|
||||||
94 | #[pyo3(attribute(""))]
|
94 | #[pyo3(attribute(""))]
|
||||||
| ^^
|
| ^^
|
||||||
|
|
||||||
error: expected a single string literal argument
|
error: unexpected end of input, expected string literal
|
||||||
--> $DIR/invalid_frompy_derive.rs:100:12
|
--> $DIR/invalid_frompy_derive.rs:100:21
|
||||||
|
|
|
|
||||||
100 | #[pyo3(attribute())]
|
100 | #[pyo3(attribute())]
|
||||||
| ^^^^^^^^^
|
| ^^
|
||||||
|
|
||||||
error: expected a single literal argument
|
error: expected at most one argument: `item` or `item(key)`
|
||||||
--> $DIR/invalid_frompy_derive.rs:106:12
|
--> $DIR/invalid_frompy_derive.rs:106:20
|
||||||
|
|
|
|
||||||
106 | #[pyo3(item("a", "b"))]
|
106 | #[pyo3(item("a", "b"))]
|
||||||
| ^^^^
|
| ^
|
||||||
|
|
||||||
error: expected a single literal argument
|
error: unexpected end of input, expected literal
|
||||||
--> $DIR/invalid_frompy_derive.rs:112:12
|
--> $DIR/invalid_frompy_derive.rs:112:16
|
||||||
|
|
|
|
||||||
112 | #[pyo3(item())]
|
112 | #[pyo3(item())]
|
||||||
| ^^^^
|
| ^^
|
||||||
|
|
||||||
error: only one of `attribute` or `item` can be provided
|
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)]
|
118 | #[pyo3(item, attribute)]
|
||||||
| ^^^^^^^^^
|
| ^
|
||||||
|
|
||||||
error: unknown `pyo3` container attribute
|
error: expected `transparent` or `annotation`
|
||||||
--> $DIR/invalid_frompy_derive.rs:123:8
|
--> $DIR/invalid_frompy_derive.rs:123:8
|
||||||
|
|
|
|
||||||
123 | #[pyo3(unknown = "should not work")]
|
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
|
--> $DIR/invalid_frompy_derive.rs:129:21
|
||||||
|
|
|
|
||||||
129 | #[pyo3(annotation = "should not work")]
|
129 | #[pyo3(annotation = "should not work")]
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: expected string literal for annotation
|
error: expected string literal
|
||||||
--> $DIR/invalid_frompy_derive.rs:136:25
|
--> $DIR/invalid_frompy_derive.rs:136:25
|
||||||
|
|
|
|
||||||
136 | #[pyo3(annotation = 1)]
|
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)
|
= 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")`
|
error: expected `=`
|
||||||
--> $DIR/invalid_frompy_derive.rs:158:12
|
--> $DIR/invalid_frompy_derive.rs:158:11
|
||||||
|
|
|
|
||||||
158 | #[pyo3(from_py_with)]
|
158 | #[pyo3(from_py_with)]
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: expected literal
|
error: expected string literal
|
||||||
--> $DIR/invalid_frompy_derive.rs:164:27
|
--> $DIR/invalid_frompy_derive.rs:164:27
|
||||||
|
|
|
|
||||||
164 | #[pyo3(from_py_with = func)]
|
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) {}
|
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
|
--> $DIR/invalid_property_args.rs:28:44
|
||||||
|
|
|
|
||||||
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);
|
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);
|
||||||
|
|
|
@ -7,21 +7,21 @@ struct TestClass {
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl TestClass {
|
impl TestClass {
|
||||||
#[name = "num"]
|
#[pyo3(name = "num")]
|
||||||
#[getter(number)]
|
#[getter(number)]
|
||||||
fn get_num(&self) -> u32 { self.num }
|
fn get_num(&self) -> u32 { self.num }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl TestClass {
|
impl TestClass {
|
||||||
#[name = "foo"]
|
#[pyo3(name = "foo")]
|
||||||
#[name = "bar"]
|
#[pyo3(name = "bar")]
|
||||||
fn qux(&self) -> u32 { self.num }
|
fn qux(&self) -> u32 { self.num }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl TestClass {
|
impl TestClass {
|
||||||
#[name = "makenew"]
|
#[pyo3(name = "makenew")]
|
||||||
#[new]
|
#[new]
|
||||||
fn new(&self) -> Self { Self { num: 0 } }
|
fn new(&self) -> Self { Self { num: 0 } }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
error: name cannot be specified twice
|
error: `name` may only be specified once
|
||||||
--> $DIR/invalid_pymethod_names.rs:10:5
|
--> $DIR/invalid_pymethod_names.rs:10:19
|
||||||
|
|
|
|
||||||
10 | #[name = "num"]
|
10 | #[pyo3(name = "num")]
|
||||||
| ^
|
| ^^^^^
|
||||||
|
|
||||||
error: #[name] can not be specified multiple times
|
error: `name` may only be specified once
|
||||||
--> $DIR/invalid_pymethod_names.rs:18:5
|
--> $DIR/invalid_pymethod_names.rs:18:19
|
||||||
|
|
|
|
||||||
18 | #[name = "bar"]
|
18 | #[pyo3(name = "bar")]
|
||||||
| ^
|
| ^^^^^
|
||||||
|
|
||||||
error: name not allowed with this method type
|
error: `name` not allowed with `#[new]`
|
||||||
--> $DIR/invalid_pymethod_names.rs:24:5
|
--> $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) {}
|
fn class_attr_with_args(foo: i32) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl MyClass {
|
||||||
|
#[classattr(foobar)]
|
||||||
|
const CLASS_ATTR_WITH_ATTRIBUTE_ARG: i32 = 3;
|
||||||
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl MyClass {
|
impl MyClass {
|
||||||
fn staticmethod_without_attribute() {}
|
fn staticmethod_without_attribute() {}
|
||||||
|
|
|
@ -4,80 +4,86 @@ error: class attribute methods cannot take arguments
|
||||||
9 | fn class_attr_with_args(foo: i32) {}
|
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
|
--> $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
|
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]
|
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]
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
error: Python functions cannot have `impl Trait` arguments
|
||||||
--> $DIR/invalid_pymethods.rs:97:56
|
--> $DIR/invalid_pymethods.rs:103:56
|
||||||
|
|
|
|
||||||
97 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
|
103 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
Loading…
Reference in New Issue