11use super :: Diagnostic ;
22use 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} ;
77use proc_macro2:: { Span , TokenStream } ;
88use quote:: { quote, quote_spanned, ToTokens } ;
99use std:: collections:: HashMap ;
1010use std:: str:: FromStr ;
1111use 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} ;
1714use 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.
482515pub ( 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) ]
15231551fn parse_vec_ident (
15241552 attr : & [ NestedMeta ] ,
15251553 item : & Item ,
0 commit comments