Skip to content

Commit 776bda6

Browse files
authored
Merge pull request RustPython#4886 from youknowone/pyexception
Merge define_exception into pyexception
2 parents 322aa68 + f256934 commit 776bda6

File tree

15 files changed

+603
-618
lines changed

15 files changed

+603
-618
lines changed

derive-impl/src/lib.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,18 @@ pub fn derive_from_args(input: DeriveInput) -> TokenStream {
4040

4141
pub fn pyclass(attr: AttributeArgs, item: Item) -> TokenStream {
4242
if matches!(item, syn::Item::Impl(_) | syn::Item::Trait(_)) {
43-
result_to_tokens(pyclass::impl_pyimpl(attr, item))
43+
result_to_tokens(pyclass::impl_pyclass_impl(attr, item))
4444
} else {
4545
result_to_tokens(pyclass::impl_pyclass(attr, item))
4646
}
4747
}
4848

49-
pub use pyclass::PyExceptionDef;
50-
pub fn define_exception(exc_def: PyExceptionDef) -> TokenStream {
51-
result_to_tokens(pyclass::impl_define_exception(exc_def))
52-
}
53-
5449
pub fn pyexception(attr: AttributeArgs, item: Item) -> TokenStream {
55-
result_to_tokens(pyclass::impl_pyexception(attr, item))
50+
if matches!(item, syn::Item::Impl(_)) {
51+
result_to_tokens(pyclass::impl_pyexception_impl(attr, item))
52+
} else {
53+
result_to_tokens(pyclass::impl_pyexception(attr, item))
54+
}
5655
}
5756

5857
pub fn pymodule(attr: AttributeArgs, item: Item) -> TokenStream {

derive-impl/src/pyclass.rs

Lines changed: 131 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
use super::Diagnostic;
22
use crate::util::{
3-
format_doc, pyclass_ident_and_attrs, text_signature, ClassItemMeta, ContentItem,
4-
ContentItemInner, ErrorVec, ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta,
5-
ALL_ALLOWED_NAMES,
3+
format_doc, pyclass_ident_and_attrs, pyexception_ident_and_attrs, text_signature,
4+
ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta, ItemMeta,
5+
ItemMetaInner, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES,
66
};
77
use proc_macro2::{Span, TokenStream};
88
use quote::{quote, quote_spanned, ToTokens};
99
use std::collections::HashMap;
1010
use std::str::FromStr;
1111
use syn::{
12-
parse::{Parse, ParseStream, Result as ParsingResult},
13-
parse_quote,
14-
spanned::Spanned,
15-
Attribute, AttributeArgs, Ident, Item, LitStr, Meta, NestedMeta, Result, Token,
12+
parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Meta, NestedMeta, Result,
1613
};
1714
use syn_ext::ext::*;
1815

@@ -99,7 +96,7 @@ fn extract_items_into_context<'a, Item>(
9996
context.errors.ok_or_push(context.member_items.validate());
10097
}
10198

102-
pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
99+
pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
103100
let mut context = ImplContext::default();
104101
let mut tokens = match item {
105102
Item::Impl(mut imp) => {
@@ -340,8 +337,8 @@ fn generate_class_def(
340337
let base_class = if is_pystruct {
341338
Some(quote! { rustpython_vm::builtins::PyTuple })
342339
} else {
343-
base.map(|typ| {
344-
let typ = Ident::new(&typ, ident.span());
340+
base.as_ref().map(|typ| {
341+
let typ = Ident::new(typ, ident.span());
345342
quote_spanned! { ident.span() => #typ }
346343
})
347344
}
@@ -364,6 +361,13 @@ fn generate_class_def(
364361
}
365362
});
366363

364+
let base_or_object = if let Some(base) = base {
365+
let base = Ident::new(&base, ident.span());
366+
quote! { #base }
367+
} else {
368+
quote! { ::rustpython_vm::builtins::PyBaseObject }
369+
};
370+
367371
let tokens = quote! {
368372
impl ::rustpython_vm::class::PyClassDef for #ident {
369373
const NAME: &'static str = #name;
@@ -372,6 +376,8 @@ fn generate_class_def(
372376
const DOC: Option<&'static str> = #doc;
373377
const BASICSIZE: usize = #basicsize;
374378
const UNHASHABLE: bool = #unhashable;
379+
380+
type Base = #base_or_object;
375381
}
376382

377383
impl ::rustpython_vm::class::StaticType for #ident {
@@ -462,11 +468,38 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
462468
}
463469
};
464470

471+
let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? {
472+
let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span
473+
474+
// We need this to make extend mechanism work:
475+
quote! {
476+
impl ::rustpython_vm::PyPayload for #ident {
477+
fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
478+
ctx.types.#ctx_type_ident
479+
}
480+
}
481+
}
482+
} else {
483+
quote! {}
484+
};
485+
486+
let empty_impl = if let Some(attrs) = class_meta.impl_attrs()? {
487+
let attrs: Meta = parse_quote! (#attrs);
488+
quote! {
489+
#[pyclass(#attrs)]
490+
impl #ident {}
491+
}
492+
} else {
493+
quote! {}
494+
};
495+
465496
let ret = quote! {
466497
#derive_trace
467498
#item
468499
#maybe_trace_code
469500
#class_def
501+
#impl_payload
502+
#empty_impl
470503
};
471504
Ok(ret)
472505
}
@@ -480,87 +513,125 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
480513
/// to add non-literal attributes to `pyclass`.
481514
/// That's why we have to use this proxy.
482515
pub(crate) fn impl_pyexception(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
483-
let class_name = parse_vec_ident(&attr, &item, 0, "first 'class_name'")?;
484-
let base_class_name = parse_vec_ident(&attr, &item, 1, "second 'base_class_name'")?;
516+
let (ident, _attrs) = pyexception_ident_and_attrs(&item)?;
517+
let fake_ident = Ident::new("pyclass", item.span());
518+
let class_meta = ExceptionItemMeta::from_nested(ident.clone(), fake_ident, attr.into_iter())?;
519+
let class_name = class_meta.class_name()?;
485520

486-
// We also need to strip `Py` prefix from `class_name`,
487-
// due to implementation and Python naming conventions mismatch:
488-
// `PyKeyboardInterrupt` -> `KeyboardInterrupt`
489-
let class_name = class_name
490-
.strip_prefix("Py")
491-
.ok_or_else(|| err_span!(item, "We require 'class_name' to have 'Py' prefix"))?;
521+
let base_class_name = class_meta.base()?;
522+
let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? {
523+
let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span
524+
525+
// We need this to make extend mechanism work:
526+
quote! {
527+
impl ::rustpython_vm::PyPayload for #ident {
528+
fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
529+
ctx.exceptions.#ctx_type_ident
530+
}
531+
}
532+
}
533+
} else {
534+
quote! {}
535+
};
536+
let impl_pyclass = if class_meta.has_impl()? {
537+
quote! {
538+
#[pyexception]
539+
impl #ident {}
540+
}
541+
} else {
542+
quote! {}
543+
};
492544

493-
// We just "proxy" it into `pyclass` macro, because, exception is a class.
494545
let ret = quote! {
495546
#[pyclass(module = false, name = #class_name, base = #base_class_name)]
496547
#item
548+
#impl_payload
549+
#impl_pyclass
497550
};
498551
Ok(ret)
499552
}
500553

501-
pub(crate) fn impl_define_exception(exc_def: PyExceptionDef) -> Result<TokenStream> {
502-
let PyExceptionDef {
503-
class_name,
504-
base_class,
505-
ctx_name,
506-
docs,
507-
slot_new,
508-
init,
509-
} = exc_def;
510-
511-
// We need this method, because of how `CPython` copies `__new__`
512-
// from `BaseException` in `SimpleExtendsException` macro.
513-
// See: `BaseException_new`
514-
let slot_new_impl = match slot_new {
515-
Some(slot_call) => quote! { #slot_call(cls, args, vm) },
516-
None => quote! { #base_class::slot_new(cls, args, vm) },
554+
pub(crate) fn impl_pyexception_impl(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
555+
let Item::Impl(imp) = item else {
556+
return Ok(item.into_token_stream());
517557
};
518558

519-
// We need this method, because of how `CPython` copies `__init__`
520-
// from `BaseException` in `SimpleExtendsException` macro.
521-
// See: `(initproc)BaseException_init`
522-
// spell-checker:ignore initproc
523-
let init_method = match init {
524-
Some(init_def) => quote! { #init_def(zelf, args, vm) },
525-
None => quote! { #base_class::slot_init(zelf, args, vm) },
526-
};
527-
528-
let ret = quote! {
529-
#[pyexception(#class_name, #base_class)]
530-
#[derive(Debug)]
531-
#[doc = #docs]
532-
pub struct #class_name {}
559+
if !attr.is_empty() {
560+
return Err(syn::Error::new_spanned(
561+
&attr[0],
562+
"#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.",
563+
));
564+
}
533565

534-
// We need this to make extend mechanism work:
535-
impl ::rustpython_vm::PyPayload for #class_name {
536-
fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
537-
ctx.exceptions.#ctx_name
566+
let mut has_slot_new = false;
567+
let mut has_slot_init = false;
568+
let syn::ItemImpl {
569+
generics,
570+
self_ty,
571+
items,
572+
..
573+
} = &imp;
574+
for item in items {
575+
// FIXME: better detection or correct wrapper implementation
576+
let Some(ident) = item.get_ident() else {
577+
continue;
578+
};
579+
let item_name = ident.to_string();
580+
match item_name.as_str() {
581+
"slot_new" => {
582+
has_slot_new = true;
583+
}
584+
"slot_init" => {
585+
has_slot_init = true;
538586
}
587+
_ => continue,
539588
}
589+
}
540590

541-
#[pyclass(flags(BASETYPE, HAS_DICT))]
542-
impl #class_name {
591+
let slot_new = if has_slot_new {
592+
quote!()
593+
} else {
594+
quote! {
543595
#[pyslot]
544596
pub(crate) fn slot_new(
545597
cls: ::rustpython_vm::builtins::PyTypeRef,
546598
args: ::rustpython_vm::function::FuncArgs,
547599
vm: &::rustpython_vm::VirtualMachine,
548600
) -> ::rustpython_vm::PyResult {
549-
#slot_new_impl
601+
<Self as ::rustpython_vm::class::PyClassDef>::Base::slot_new(cls, args, vm)
550602
}
603+
}
604+
};
551605

606+
// We need this method, because of how `CPython` copies `__init__`
607+
// from `BaseException` in `SimpleExtendsException` macro.
608+
// See: `(initproc)BaseException_init`
609+
// spell-checker:ignore initproc
610+
let slot_init = if has_slot_init {
611+
quote!()
612+
} else {
613+
// FIXME: this is a generic logic for types not only for exceptions
614+
quote! {
552615
#[pyslot]
553616
#[pymethod(name="__init__")]
554617
pub(crate) fn slot_init(
555-
zelf: PyObjectRef,
618+
zelf: ::rustpython_vm::PyObjectRef,
556619
args: ::rustpython_vm::function::FuncArgs,
557620
vm: &::rustpython_vm::VirtualMachine,
558621
) -> ::rustpython_vm::PyResult<()> {
559-
#init_method
622+
<Self as ::rustpython_vm::class::PyClassDef>::Base::slot_init(zelf, args, vm)
560623
}
561624
}
562625
};
563-
Ok(ret)
626+
Ok(quote! {
627+
#[pyclass(flags(BASETYPE, HAS_DICT))]
628+
impl #generics #self_ty {
629+
#(#items)*
630+
631+
#slot_new
632+
#slot_init
633+
}
634+
})
564635
}
565636

566637
/// #[pymethod] and #[pyclassmethod]
@@ -1476,50 +1547,7 @@ where
14761547
Ok((result, cfgs))
14771548
}
14781549

1479-
#[derive(Debug)]
1480-
pub struct PyExceptionDef {
1481-
pub class_name: Ident,
1482-
pub base_class: Ident,
1483-
pub ctx_name: Ident,
1484-
pub docs: LitStr,
1485-
1486-
/// Holds optional `slot_new` slot to be used instead of a default one:
1487-
pub slot_new: Option<Ident>,
1488-
/// We also store `__init__` magic method, that can
1489-
pub init: Option<Ident>,
1490-
}
1491-
1492-
impl Parse for PyExceptionDef {
1493-
fn parse(input: ParseStream) -> ParsingResult<Self> {
1494-
let class_name: Ident = input.parse()?;
1495-
input.parse::<Token![,]>()?;
1496-
1497-
let base_class: Ident = input.parse()?;
1498-
input.parse::<Token![,]>()?;
1499-
1500-
let ctx_name: Ident = input.parse()?;
1501-
input.parse::<Token![,]>()?;
1502-
1503-
let docs: LitStr = input.parse()?;
1504-
input.parse::<Option<Token![,]>>()?;
1505-
1506-
let slot_new: Option<Ident> = input.parse()?;
1507-
input.parse::<Option<Token![,]>>()?;
1508-
1509-
let init: Option<Ident> = input.parse()?;
1510-
input.parse::<Option<Token![,]>>()?; // leading `,`
1511-
1512-
Ok(PyExceptionDef {
1513-
class_name,
1514-
base_class,
1515-
ctx_name,
1516-
docs,
1517-
slot_new,
1518-
init,
1519-
})
1520-
}
1521-
}
1522-
1550+
#[allow(dead_code)]
15231551
fn parse_vec_ident(
15241552
attr: &[NestedMeta],
15251553
item: &Item,

0 commit comments

Comments
 (0)