mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-09 22:49:57 +09:00
908 lines
28 KiB
Rust
908 lines
28 KiB
Rust
use super::Diagnostic;
|
|
use crate::util::{
|
|
path_eq, pyclass_ident_and_attrs, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec,
|
|
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES,
|
|
};
|
|
use proc_macro2::TokenStream;
|
|
use quote::{quote, quote_spanned, ToTokens};
|
|
use std::collections::HashMap;
|
|
use syn::{
|
|
parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Meta, NestedMeta, Result,
|
|
};
|
|
use syn_ext::ext::*;
|
|
|
|
#[derive(Default)]
|
|
struct ImplContext {
|
|
impl_extend_items: ItemNursery,
|
|
getset_items: GetSetNursery,
|
|
extend_slots_items: ItemNursery,
|
|
class_extensions: Vec<TokenStream>,
|
|
errors: Vec<syn::Error>,
|
|
}
|
|
|
|
fn extract_items_into_context<'a, Item>(
|
|
context: &mut ImplContext,
|
|
items: impl Iterator<Item = &'a mut Item>,
|
|
) where
|
|
Item: ItemLike + ToTokens + GetIdent + syn_ext::ext::ItemAttrExt + 'a,
|
|
{
|
|
for item in items {
|
|
let r = item.try_split_attr_mut(|attrs, item| {
|
|
let (pyitems, cfgs) = attrs_to_content_items(&attrs, new_impl_item::<Item>)?;
|
|
for pyitem in pyitems.iter().rev() {
|
|
let r = pyitem.gen_impl_item(ImplItemArgs::<Item> {
|
|
item,
|
|
attrs,
|
|
context,
|
|
cfgs: cfgs.as_slice(),
|
|
});
|
|
context.errors.ok_or_push(r);
|
|
}
|
|
Ok(())
|
|
});
|
|
context.errors.ok_or_push(r);
|
|
}
|
|
context.errors.ok_or_push(context.getset_items.validate());
|
|
}
|
|
|
|
pub(crate) fn impl_pyimpl(
|
|
attr: AttributeArgs,
|
|
item: Item,
|
|
) -> std::result::Result<TokenStream, Diagnostic> {
|
|
let mut context = ImplContext::default();
|
|
let mut tokens = match item {
|
|
Item::Impl(mut imp) => {
|
|
extract_items_into_context(&mut context, imp.items.iter_mut());
|
|
|
|
let ExtractedImplAttrs {
|
|
with_impl,
|
|
flags,
|
|
with_slots,
|
|
} = extract_impl_attrs(attr)?;
|
|
|
|
let ty = &imp.self_ty;
|
|
|
|
let getset_impl = &context.getset_items;
|
|
let extend_impl = &context.impl_extend_items;
|
|
let slots_impl = &context.extend_slots_items;
|
|
let class_extensions = &context.class_extensions;
|
|
quote! {
|
|
#imp
|
|
impl ::rustpython_vm::PyClassImpl for #ty {
|
|
const TP_FLAGS: ::rustpython_vm::slots::PyTpFlags = ::rustpython_vm::slots::PyTpFlags::from_bits_truncate(#flags);
|
|
|
|
fn impl_extend_class(
|
|
ctx: &::rustpython_vm::PyContext,
|
|
class: &::rustpython_vm::builtins::PyTypeRef,
|
|
) {
|
|
#getset_impl
|
|
#extend_impl
|
|
#with_impl
|
|
#(#class_extensions)*
|
|
}
|
|
|
|
fn extend_slots(slots: &mut ::rustpython_vm::slots::PyTypeSlots) {
|
|
#with_slots
|
|
#slots_impl
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Item::Trait(mut trai) => {
|
|
let mut context = ImplContext::default();
|
|
extract_items_into_context(&mut context, trai.items.iter_mut());
|
|
|
|
let ExtractedImplAttrs {
|
|
with_impl,
|
|
with_slots,
|
|
..
|
|
} = extract_impl_attrs(attr)?;
|
|
|
|
let getset_impl = &context.getset_items;
|
|
let extend_impl = &context.impl_extend_items;
|
|
let slots_impl = &context.extend_slots_items;
|
|
let class_extensions = &context.class_extensions;
|
|
let extra_methods = iter_chain![
|
|
parse_quote! {
|
|
fn __extend_py_class(
|
|
ctx: &::rustpython_vm::PyContext,
|
|
class: &::rustpython_vm::builtins::PyTypeRef,
|
|
) {
|
|
#getset_impl
|
|
#extend_impl
|
|
#with_impl
|
|
#(#class_extensions)*
|
|
}
|
|
},
|
|
parse_quote! {
|
|
fn __extend_slots(slots: &mut ::rustpython_vm::slots::PyTypeSlots) {
|
|
#with_slots
|
|
#slots_impl
|
|
}
|
|
},
|
|
];
|
|
trai.items.extend(extra_methods);
|
|
|
|
trai.into_token_stream()
|
|
}
|
|
item => item.into_token_stream(),
|
|
};
|
|
if let Some(error) = context.errors.into_error() {
|
|
let error = Diagnostic::from(error);
|
|
tokens = quote! {
|
|
#tokens
|
|
#error
|
|
}
|
|
}
|
|
Ok(tokens)
|
|
}
|
|
|
|
fn generate_class_def(
|
|
ident: &Ident,
|
|
name: &str,
|
|
module_name: Option<&str>,
|
|
base: Option<String>,
|
|
attrs: &[Attribute],
|
|
) -> std::result::Result<TokenStream, Diagnostic> {
|
|
let doc = if let Some(doc) = attrs.doc() {
|
|
quote!(Some(#doc))
|
|
} else {
|
|
quote!(None)
|
|
};
|
|
let module_class_name = if let Some(module_name) = module_name {
|
|
format!("{}.{}", module_name, name)
|
|
} else {
|
|
name.to_owned()
|
|
};
|
|
let module_name = match module_name {
|
|
Some(v) => quote!(Some(#v) ),
|
|
None => quote!(None),
|
|
};
|
|
let is_pystruct = attrs.iter().any(|attr| {
|
|
path_eq(&attr.path, "derive")
|
|
&& if let Ok(Meta::List(l)) = attr.parse_meta() {
|
|
l.nested
|
|
.into_iter()
|
|
.any(|n| n.get_ident().map_or(false, |p| p == "PyStructSequence"))
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
if base.is_some() && is_pystruct {
|
|
return Err(syn::Error::new_spanned(
|
|
ident,
|
|
"PyStructSequence cannot have `base` class attr",
|
|
)
|
|
.into());
|
|
}
|
|
let base = base.map(|name| Ident::new(&name, ident.span()));
|
|
|
|
let base_class = if is_pystruct {
|
|
quote! {
|
|
fn static_baseclass() -> &'static ::rustpython_vm::builtins::PyTypeRef {
|
|
use rustpython_vm::StaticType;
|
|
rustpython_vm::builtins::PyTuple::static_type()
|
|
}
|
|
}
|
|
} else if let Some(base) = base {
|
|
quote! {
|
|
fn static_baseclass() -> &'static ::rustpython_vm::builtins::PyTypeRef {
|
|
use rustpython_vm::StaticType;
|
|
#base::static_type()
|
|
}
|
|
}
|
|
} else {
|
|
quote!()
|
|
};
|
|
|
|
let tokens = quote! {
|
|
impl ::rustpython_vm::PyClassDef for #ident {
|
|
const NAME: &'static str = #name;
|
|
const MODULE_NAME: Option<&'static str> = #module_name;
|
|
const TP_NAME: &'static str = #module_class_name;
|
|
const DOC: Option<&'static str> = #doc;
|
|
}
|
|
|
|
impl ::rustpython_vm::StaticType for #ident {
|
|
fn static_cell() -> &'static ::rustpython_common::static_cell::StaticCell<::rustpython_vm::builtins::PyTypeRef> {
|
|
::rustpython_common::static_cell! {
|
|
static CELL: ::rustpython_vm::builtins::PyTypeRef;
|
|
}
|
|
&CELL
|
|
}
|
|
|
|
#base_class
|
|
}
|
|
};
|
|
Ok(tokens)
|
|
}
|
|
|
|
pub(crate) fn impl_pyclass(
|
|
attr: AttributeArgs,
|
|
item: Item,
|
|
) -> std::result::Result<TokenStream, Diagnostic> {
|
|
let (ident, attrs) = pyclass_ident_and_attrs(&item)?;
|
|
let fake_ident = Ident::new("pyclass", item.span());
|
|
let class_meta = ClassItemMeta::from_nested(ident.clone(), fake_ident, attr.into_iter())?;
|
|
let class_name = class_meta.class_name()?;
|
|
let module_name = class_meta.module()?;
|
|
let base = class_meta.base()?;
|
|
let class_def = generate_class_def(&ident, &class_name, module_name.as_deref(), base, &attrs)?;
|
|
|
|
let ret = quote! {
|
|
#item
|
|
#class_def
|
|
};
|
|
Ok(ret)
|
|
}
|
|
|
|
/// #[pymethod] and #[pyclassmethod]
|
|
struct MethodItem {
|
|
inner: ContentItemInner,
|
|
method_type: String,
|
|
}
|
|
|
|
/// #[pyproperty]
|
|
struct PropertyItem {
|
|
inner: ContentItemInner,
|
|
}
|
|
|
|
/// #[pyslot]
|
|
struct SlotItem {
|
|
inner: ContentItemInner,
|
|
}
|
|
|
|
/// #[pyattr]
|
|
struct AttributeItem {
|
|
inner: ContentItemInner,
|
|
}
|
|
|
|
/// #[extend_class]
|
|
struct ExtendClassItem {
|
|
inner: ContentItemInner,
|
|
}
|
|
|
|
impl ContentItem for MethodItem {
|
|
fn inner(&self) -> &ContentItemInner {
|
|
&self.inner
|
|
}
|
|
}
|
|
impl ContentItem for PropertyItem {
|
|
fn inner(&self) -> &ContentItemInner {
|
|
&self.inner
|
|
}
|
|
}
|
|
impl ContentItem for SlotItem {
|
|
fn inner(&self) -> &ContentItemInner {
|
|
&self.inner
|
|
}
|
|
}
|
|
impl ContentItem for AttributeItem {
|
|
fn inner(&self) -> &ContentItemInner {
|
|
&self.inner
|
|
}
|
|
}
|
|
impl ContentItem for ExtendClassItem {
|
|
fn inner(&self) -> &ContentItemInner {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
struct ImplItemArgs<'a, Item: ItemLike> {
|
|
item: &'a Item,
|
|
attrs: &'a mut Vec<Attribute>,
|
|
context: &'a mut ImplContext,
|
|
cfgs: &'a [Attribute],
|
|
}
|
|
|
|
trait ImplItem<Item>: ContentItem
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()>;
|
|
}
|
|
|
|
impl<Item> ImplItem<Item> for MethodItem
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
|
|
let ident = if args.item.is_function_or_method() {
|
|
Ok(args.item.get_ident().unwrap())
|
|
} else {
|
|
Err(self.new_syn_error(args.item.span(), "can only be on a method"))
|
|
}?;
|
|
|
|
let item_attr = args.attrs.remove(self.index());
|
|
let item_meta = MethodItemMeta::from_attr(ident.clone(), &item_attr)?;
|
|
|
|
let py_name = item_meta.method_name()?;
|
|
let build_func = Ident::new(&format!("build_{}", &self.method_type), args.item.span());
|
|
let tokens = {
|
|
let doc = args.attrs.doc().map_or_else(
|
|
TokenStream::new,
|
|
|doc| quote!(.with_doc(#doc.to_owned(), ctx)),
|
|
);
|
|
quote! {
|
|
class.set_str_attr(
|
|
#py_name,
|
|
ctx.make_funcdef(#py_name, Self::#ident)
|
|
#doc
|
|
.#build_func(ctx),
|
|
);
|
|
}
|
|
};
|
|
|
|
args.context
|
|
.impl_extend_items
|
|
.add_item(py_name, args.cfgs.to_vec(), tokens)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Item> ImplItem<Item> for PropertyItem
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
|
|
let ident = if args.item.is_function_or_method() {
|
|
Ok(args.item.get_ident().unwrap())
|
|
} else {
|
|
Err(self.new_syn_error(args.item.span(), "can only be on a method"))
|
|
}?;
|
|
|
|
let item_attr = args.attrs.remove(self.index());
|
|
let item_meta = PropertyItemMeta::from_attr(ident.clone(), &item_attr)?;
|
|
|
|
let (py_name, kind) = item_meta.property_name()?;
|
|
args.context
|
|
.getset_items
|
|
.add_item(py_name, args.cfgs.to_vec(), kind, ident.clone())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Item> ImplItem<Item> for SlotItem
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
|
|
let ident = if args.item.is_function_or_method() {
|
|
Ok(args.item.get_ident().unwrap())
|
|
} else {
|
|
Err(self.new_syn_error(args.item.span(), "can only be on a method"))
|
|
}?;
|
|
|
|
let item_attr = args.attrs.remove(self.index());
|
|
let item_meta = SlotItemMeta::from_attr(ident.clone(), &item_attr)?;
|
|
|
|
let slot_ident = item_meta.slot_name()?;
|
|
let slot_name = slot_ident.to_string();
|
|
let tokens = {
|
|
let into_func = if slot_name == "new" {
|
|
quote_spanned! {ident.span() =>
|
|
::rustpython_vm::function::IntoPyNativeFunc::into_func(Self::#ident)
|
|
}
|
|
} else {
|
|
quote_spanned! {ident.span() =>
|
|
Self::#ident as _
|
|
}
|
|
};
|
|
if slot_name == "new" || slot_name == "buffer" {
|
|
quote! {
|
|
slots.#slot_ident = Some(#into_func);
|
|
}
|
|
} else {
|
|
quote! {
|
|
slots.#slot_ident.store(Some(#into_func))
|
|
}
|
|
}
|
|
};
|
|
|
|
args.context.extend_slots_items.add_item(
|
|
format!("(slot {})", slot_name),
|
|
args.cfgs.to_vec(),
|
|
tokens,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Item> ImplItem<Item> for AttributeItem
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
|
|
let cfgs = args.cfgs.to_vec();
|
|
let attr = args.attrs.remove(self.index());
|
|
|
|
let get_py_name = |attr: &Attribute, ident: &Ident| -> Result<_> {
|
|
let item_meta = SimpleItemMeta::from_attr(ident.clone(), attr)?;
|
|
let py_name = item_meta.simple_name()?;
|
|
Ok(py_name)
|
|
};
|
|
let (py_name, tokens) = if args.item.is_function_or_method() || args.item.is_const() {
|
|
let ident = args.item.get_ident().unwrap();
|
|
let py_name = get_py_name(&attr, &ident)?;
|
|
|
|
let value = if args.item.is_const() {
|
|
// TODO: ctx.new_value
|
|
quote_spanned!(ident.span() => ctx.new_int(Self::#ident))
|
|
} else {
|
|
quote_spanned!(ident.span() => Self::#ident(ctx))
|
|
};
|
|
(
|
|
py_name.clone(),
|
|
quote! {
|
|
class.set_str_attr(#py_name, #value);
|
|
},
|
|
)
|
|
} else {
|
|
return Err(self.new_syn_error(
|
|
args.item.span(),
|
|
"can only be on a const or an associated method without argument",
|
|
));
|
|
};
|
|
|
|
args.context
|
|
.impl_extend_items
|
|
.add_item(py_name, cfgs, tokens)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Item> ImplItem<Item> for ExtendClassItem
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
fn gen_impl_item(&self, args: ImplItemArgs<'_, Item>) -> Result<()> {
|
|
args.attrs.remove(self.index());
|
|
|
|
let ident = if args.item.is_function_or_method() {
|
|
Ok(args.item.get_ident().unwrap())
|
|
} else {
|
|
Err(self.new_syn_error(args.item.span(), "can only be on a method"))
|
|
}?;
|
|
|
|
args.context.class_extensions.push(quote! {
|
|
Self::#ident(ctx, class);
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
#[allow(clippy::type_complexity)]
|
|
struct GetSetNursery {
|
|
map: HashMap<(String, Vec<Attribute>), (Option<Ident>, Option<Ident>, Option<Ident>)>,
|
|
validated: bool,
|
|
}
|
|
|
|
enum GetSetItemKind {
|
|
Get,
|
|
Set,
|
|
Delete,
|
|
}
|
|
|
|
impl GetSetNursery {
|
|
fn add_item(
|
|
&mut self,
|
|
name: String,
|
|
cfgs: Vec<Attribute>,
|
|
kind: GetSetItemKind,
|
|
item_ident: Ident,
|
|
) -> Result<()> {
|
|
assert!(!self.validated, "new item is not allowed after validation");
|
|
if !matches!(kind, GetSetItemKind::Get) && !cfgs.is_empty() {
|
|
return Err(syn::Error::new_spanned(
|
|
item_ident,
|
|
"Only the getter can have #[cfg]",
|
|
));
|
|
}
|
|
let entry = self.map.entry((name.clone(), cfgs)).or_default();
|
|
let func = match kind {
|
|
GetSetItemKind::Get => &mut entry.0,
|
|
GetSetItemKind::Set => &mut entry.1,
|
|
GetSetItemKind::Delete => &mut entry.2,
|
|
};
|
|
if func.is_some() {
|
|
return Err(syn::Error::new_spanned(
|
|
item_ident,
|
|
format!("Multiple property accessors with name '{}'", name),
|
|
));
|
|
}
|
|
*func = Some(item_ident);
|
|
Ok(())
|
|
}
|
|
|
|
fn validate(&mut self) -> Result<()> {
|
|
let mut errors = Vec::new();
|
|
for ((name, _cfgs), (getter, setter, deleter)) in self.map.iter() {
|
|
if getter.is_none() {
|
|
errors.push(syn::Error::new_spanned(
|
|
setter.as_ref().or_else(|| deleter.as_ref()).unwrap(),
|
|
format!("Property '{}' is missing a getter", name),
|
|
));
|
|
};
|
|
}
|
|
errors.into_result()?;
|
|
self.validated = true;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ToTokens for GetSetNursery {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
assert!(self.validated, "Call `validate()` before token generation");
|
|
let properties = self
|
|
.map
|
|
.iter()
|
|
.map(|((name, cfgs), (getter, setter, deleter))| {
|
|
let setter = match setter {
|
|
Some(setter) => quote_spanned! { setter.span()=> .with_set(&Self::#setter)},
|
|
None => quote! {},
|
|
};
|
|
let deleter = match deleter {
|
|
Some(deleter) => {
|
|
quote_spanned! { deleter.span()=> .with_delete(&Self::#deleter)}
|
|
}
|
|
None => quote! {},
|
|
};
|
|
quote! {
|
|
#( #cfgs )*
|
|
class.set_str_attr(
|
|
#name,
|
|
::rustpython_vm::PyObject::new(
|
|
::rustpython_vm::builtins::PyGetSet::new(#name.into())
|
|
.with_get(&Self::#getter)
|
|
#setter #deleter,
|
|
ctx.types.getset_type.clone(), None)
|
|
);
|
|
}
|
|
});
|
|
tokens.extend(properties);
|
|
}
|
|
}
|
|
|
|
struct MethodItemMeta(ItemMetaInner);
|
|
|
|
impl ItemMeta for MethodItemMeta {
|
|
const ALLOWED_NAMES: &'static [&'static str] = &["name", "magic"];
|
|
|
|
fn from_inner(inner: ItemMetaInner) -> Self {
|
|
Self(inner)
|
|
}
|
|
fn inner(&self) -> &ItemMetaInner {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl MethodItemMeta {
|
|
fn method_name(&self) -> Result<String> {
|
|
let inner = self.inner();
|
|
let name = inner._optional_str("name")?;
|
|
let magic = inner._bool("magic")?;
|
|
Ok(if let Some(name) = name {
|
|
name
|
|
} else {
|
|
let name = inner.item_name();
|
|
if magic {
|
|
format!("__{}__", name)
|
|
} else {
|
|
name
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
struct PropertyItemMeta(ItemMetaInner);
|
|
|
|
impl ItemMeta for PropertyItemMeta {
|
|
const ALLOWED_NAMES: &'static [&'static str] = &["name", "magic", "setter", "deleter"];
|
|
|
|
fn from_inner(inner: ItemMetaInner) -> Self {
|
|
Self(inner)
|
|
}
|
|
fn inner(&self) -> &ItemMetaInner {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl PropertyItemMeta {
|
|
fn property_name(&self) -> Result<(String, GetSetItemKind)> {
|
|
let inner = self.inner();
|
|
let magic = inner._bool("magic")?;
|
|
let kind = match (inner._bool("setter")?, inner._bool("deleter")?) {
|
|
(false, false) => GetSetItemKind::Get,
|
|
(true, false) => GetSetItemKind::Set,
|
|
(false, true) => GetSetItemKind::Delete,
|
|
(true, true) => {
|
|
return Err(syn::Error::new_spanned(
|
|
&inner.meta_ident,
|
|
format!(
|
|
"can't have both setter and deleter on a #[{}] fn",
|
|
inner.meta_name()
|
|
),
|
|
))
|
|
}
|
|
};
|
|
let name = inner._optional_str("name")?;
|
|
let py_name = if let Some(name) = name {
|
|
name
|
|
} else {
|
|
let sig_name = inner.item_name();
|
|
let extract_prefix_name = |prefix, item_typ| {
|
|
if let Some(name) = sig_name.strip_prefix(prefix) {
|
|
if name.is_empty() {
|
|
Err(syn::Error::new_spanned(
|
|
&inner.meta_ident,
|
|
format!(
|
|
"A #[{}({typ})] fn with a {prefix}* name must \
|
|
have something after \"{prefix}\"",
|
|
inner.meta_name(),
|
|
typ = item_typ,
|
|
prefix = prefix
|
|
),
|
|
))
|
|
} else {
|
|
Ok(name.to_owned())
|
|
}
|
|
} else {
|
|
Err(syn::Error::new_spanned(
|
|
&inner.meta_ident,
|
|
format!(
|
|
"A #[{}(setter)] fn must either have a `name` \
|
|
parameter or a fn name along the lines of \"set_*\"",
|
|
inner.meta_name()
|
|
),
|
|
))
|
|
}
|
|
};
|
|
let name = match kind {
|
|
GetSetItemKind::Get => sig_name,
|
|
GetSetItemKind::Set => extract_prefix_name("set_", "setter")?,
|
|
GetSetItemKind::Delete => extract_prefix_name("del_", "deleter")?,
|
|
};
|
|
if magic {
|
|
format!("__{}__", name)
|
|
} else {
|
|
name
|
|
}
|
|
};
|
|
Ok((py_name, kind))
|
|
}
|
|
}
|
|
|
|
struct SlotItemMeta(ItemMetaInner);
|
|
|
|
impl ItemMeta for SlotItemMeta {
|
|
const ALLOWED_NAMES: &'static [&'static str] = &[]; // not used
|
|
|
|
fn from_nested<I>(item_ident: Ident, meta_ident: Ident, mut nested: I) -> Result<Self>
|
|
where
|
|
I: std::iter::Iterator<Item = NestedMeta>,
|
|
{
|
|
let meta_map = if let Some(nmeta) = nested.next() {
|
|
match nmeta {
|
|
NestedMeta::Meta(meta) => {
|
|
Some([("name".to_owned(), (0, meta))].iter().cloned().collect())
|
|
}
|
|
_ => None,
|
|
}
|
|
} else {
|
|
Some(HashMap::default())
|
|
};
|
|
match (meta_map, nested.next()) {
|
|
(Some(meta_map), None) => Ok(Self::from_inner(ItemMetaInner {
|
|
item_ident,
|
|
meta_ident,
|
|
meta_map,
|
|
})),
|
|
_ => Err(syn::Error::new_spanned(
|
|
meta_ident,
|
|
"#[pyslot] must be of the form #[pyslot] or #[pyslot(slotname)]",
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn from_inner(inner: ItemMetaInner) -> Self {
|
|
Self(inner)
|
|
}
|
|
fn inner(&self) -> &ItemMetaInner {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl SlotItemMeta {
|
|
fn slot_name(&self) -> Result<Ident> {
|
|
let inner = self.inner();
|
|
let slot_name = if let Some((_, meta)) = inner.meta_map.get("name") {
|
|
match meta {
|
|
Meta::Path(path) => path.get_ident().cloned(),
|
|
_ => None,
|
|
}
|
|
} else {
|
|
let ident_str = self.inner().item_name();
|
|
let name = if let Some(stripped) = ident_str.strip_prefix("tp_") {
|
|
proc_macro2::Ident::new(stripped, inner.item_ident.span())
|
|
} else {
|
|
inner.item_ident.clone()
|
|
};
|
|
Some(name)
|
|
};
|
|
slot_name.ok_or_else(|| {
|
|
syn::Error::new_spanned(
|
|
&inner.meta_ident,
|
|
"#[pyslot] must be of the form #[pyslot] or #[pyslot(slotname)]",
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
struct ExtractedImplAttrs {
|
|
with_impl: TokenStream,
|
|
with_slots: TokenStream,
|
|
flags: TokenStream,
|
|
}
|
|
|
|
fn extract_impl_attrs(attr: AttributeArgs) -> std::result::Result<ExtractedImplAttrs, Diagnostic> {
|
|
let mut withs = Vec::new();
|
|
let mut with_slots = Vec::new();
|
|
let mut flags = vec![quote! { ::rustpython_vm::slots::PyTpFlags::DEFAULT.bits() }];
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
flags.push(quote! {
|
|
| ::rustpython_vm::slots::PyTpFlags::_CREATED_WITH_FLAGS.bits()
|
|
});
|
|
}
|
|
|
|
for attr in attr {
|
|
match attr {
|
|
NestedMeta::Meta(Meta::List(syn::MetaList { path, nested, .. })) => {
|
|
if path_eq(&path, "with") {
|
|
for meta in nested {
|
|
let path = match meta {
|
|
NestedMeta::Meta(Meta::Path(path)) => path,
|
|
meta => {
|
|
bail_span!(meta, "#[pyimpl(with(...))] arguments should be paths")
|
|
}
|
|
};
|
|
if path_eq(&path, "PyRef") {
|
|
// special handling for PyRef
|
|
withs.push(quote! {
|
|
PyRef::<Self>::impl_extend_class(ctx, class);
|
|
});
|
|
with_slots.push(quote! {
|
|
PyRef::<Self>::extend_slots(slots);
|
|
});
|
|
} else {
|
|
withs.push(quote! {
|
|
<Self as #path>::__extend_py_class(ctx, class);
|
|
});
|
|
with_slots.push(quote! {
|
|
<Self as #path>::__extend_slots(slots);
|
|
});
|
|
}
|
|
}
|
|
} else if path_eq(&path, "flags") {
|
|
for meta in nested {
|
|
match meta {
|
|
NestedMeta::Meta(Meta::Path(path)) => {
|
|
if let Some(ident) = path.get_ident() {
|
|
flags.push(quote! {
|
|
| ::rustpython_vm::slots::PyTpFlags::#ident.bits()
|
|
});
|
|
} else {
|
|
bail_span!(
|
|
path,
|
|
"#[pyimpl(flags(...))] arguments should be ident"
|
|
)
|
|
}
|
|
}
|
|
meta => {
|
|
bail_span!(meta, "#[pyimpl(flags(...))] arguments should be ident")
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
bail_span!(path, "Unknown pyimpl attribute")
|
|
}
|
|
}
|
|
attr => bail_span!(attr, "Unknown pyimpl attribute"),
|
|
}
|
|
}
|
|
|
|
Ok(ExtractedImplAttrs {
|
|
with_impl: quote! {
|
|
#(#withs)*
|
|
},
|
|
flags: quote! {
|
|
#(#flags)*
|
|
},
|
|
with_slots: quote! {
|
|
#(#with_slots)*
|
|
},
|
|
})
|
|
}
|
|
|
|
fn new_impl_item<Item>(index: usize, attr_name: String) -> Box<dyn ImplItem<Item>>
|
|
where
|
|
Item: ItemLike + ToTokens + GetIdent,
|
|
{
|
|
assert!(ALL_ALLOWED_NAMES.contains(&attr_name.as_str()));
|
|
match attr_name.as_str() {
|
|
attr_name @ "pymethod" | attr_name @ "pyclassmethod" => Box::new(MethodItem {
|
|
inner: ContentItemInner {
|
|
index,
|
|
attr_name: attr_name.to_owned(),
|
|
},
|
|
method_type: attr_name.strip_prefix("py").unwrap().to_owned(),
|
|
}),
|
|
"pyproperty" => Box::new(PropertyItem {
|
|
inner: ContentItemInner { index, attr_name },
|
|
}),
|
|
"pyslot" => Box::new(SlotItem {
|
|
inner: ContentItemInner { index, attr_name },
|
|
}),
|
|
"pyattr" => Box::new(AttributeItem {
|
|
inner: ContentItemInner { index, attr_name },
|
|
}),
|
|
"extend_class" => Box::new(ExtendClassItem {
|
|
inner: ContentItemInner { index, attr_name },
|
|
}),
|
|
other => unreachable!("#[pyimpl] doesn't accept #[{}]", other),
|
|
}
|
|
}
|
|
|
|
fn attrs_to_content_items<F, R>(
|
|
attrs: &[Attribute],
|
|
new_item: F,
|
|
) -> Result<(Vec<R>, Vec<Attribute>)>
|
|
where
|
|
F: Fn(usize, String) -> R,
|
|
{
|
|
let mut cfgs: Vec<Attribute> = Vec::new();
|
|
let mut result = Vec::new();
|
|
|
|
let mut iter = attrs.iter().enumerate().peekable();
|
|
while let Some((_, attr)) = iter.peek() {
|
|
// take all cfgs but no py items
|
|
let attr = *attr;
|
|
let attr_name = if let Some(ident) = attr.get_ident() {
|
|
ident.to_string()
|
|
} else {
|
|
continue;
|
|
};
|
|
if attr_name == "cfg" {
|
|
cfgs.push(attr.clone());
|
|
} else if ALL_ALLOWED_NAMES.contains(&attr_name.as_str()) {
|
|
break;
|
|
}
|
|
iter.next();
|
|
}
|
|
|
|
for (i, attr) in iter {
|
|
// take py items but no cfgs
|
|
let attr_name = if let Some(ident) = attr.get_ident() {
|
|
ident.to_string()
|
|
} else {
|
|
continue;
|
|
};
|
|
if attr_name == "cfg" {
|
|
return Err(syn::Error::new_spanned(
|
|
attr,
|
|
"#[py*] items must be placed under `cfgs`",
|
|
));
|
|
}
|
|
if !ALL_ALLOWED_NAMES.contains(&attr_name.as_str()) {
|
|
continue;
|
|
}
|
|
|
|
result.push(new_item(i, attr_name));
|
|
}
|
|
Ok((result, cfgs))
|
|
}
|