1
1
use super :: Diagnostic ;
2
2
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 ,
6
6
} ;
7
7
use proc_macro2:: { Span , TokenStream } ;
8
8
use quote:: { quote, quote_spanned, ToTokens } ;
9
9
use std:: collections:: HashMap ;
10
10
use std:: str:: FromStr ;
11
11
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 ,
16
13
} ;
17
14
use syn_ext:: ext:: * ;
18
15
@@ -99,7 +96,7 @@ fn extract_items_into_context<'a, Item>(
99
96
context. errors . ok_or_push ( context. member_items . validate ( ) ) ;
100
97
}
101
98
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 > {
103
100
let mut context = ImplContext :: default ( ) ;
104
101
let mut tokens = match item {
105
102
Item :: Impl ( mut imp) => {
@@ -340,8 +337,8 @@ fn generate_class_def(
340
337
let base_class = if is_pystruct {
341
338
Some ( quote ! { rustpython_vm:: builtins:: PyTuple } )
342
339
} 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 ( ) ) ;
345
342
quote_spanned ! { ident. span( ) => #typ }
346
343
} )
347
344
}
@@ -364,6 +361,13 @@ fn generate_class_def(
364
361
}
365
362
} ) ;
366
363
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
+
367
371
let tokens = quote ! {
368
372
impl :: rustpython_vm:: class:: PyClassDef for #ident {
369
373
const NAME : & ' static str = #name;
@@ -372,6 +376,8 @@ fn generate_class_def(
372
376
const DOC : Option <& ' static str > = #doc;
373
377
const BASICSIZE : usize = #basicsize;
374
378
const UNHASHABLE : bool = #unhashable;
379
+
380
+ type Base = #base_or_object;
375
381
}
376
382
377
383
impl :: rustpython_vm:: class:: StaticType for #ident {
@@ -462,11 +468,38 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
462
468
}
463
469
} ;
464
470
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
+
465
496
let ret = quote ! {
466
497
#derive_trace
467
498
#item
468
499
#maybe_trace_code
469
500
#class_def
501
+ #impl_payload
502
+ #empty_impl
470
503
} ;
471
504
Ok ( ret)
472
505
}
@@ -480,87 +513,125 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
480
513
/// to add non-literal attributes to `pyclass`.
481
514
/// That's why we have to use this proxy.
482
515
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 ( ) ?;
485
520
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
+ } ;
492
544
493
- // We just "proxy" it into `pyclass` macro, because, exception is a class.
494
545
let ret = quote ! {
495
546
#[ pyclass( module = false , name = #class_name, base = #base_class_name) ]
496
547
#item
548
+ #impl_payload
549
+ #impl_pyclass
497
550
} ;
498
551
Ok ( ret)
499
552
}
500
553
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 ( ) ) ;
517
557
} ;
518
558
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
+ }
533
565
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 ;
538
586
}
587
+ _ => continue ,
539
588
}
589
+ }
540
590
541
- #[ pyclass( flags( BASETYPE , HAS_DICT ) ) ]
542
- impl #class_name {
591
+ let slot_new = if has_slot_new {
592
+ quote ! ( )
593
+ } else {
594
+ quote ! {
543
595
#[ pyslot]
544
596
pub ( crate ) fn slot_new(
545
597
cls: :: rustpython_vm:: builtins:: PyTypeRef ,
546
598
args: :: rustpython_vm:: function:: FuncArgs ,
547
599
vm: & :: rustpython_vm:: VirtualMachine ,
548
600
) -> :: rustpython_vm:: PyResult {
549
- #slot_new_impl
601
+ < Self as :: rustpython_vm :: class :: PyClassDef > :: Base :: slot_new ( cls , args , vm )
550
602
}
603
+ }
604
+ } ;
551
605
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 ! {
552
615
#[ pyslot]
553
616
#[ pymethod( name="__init__" ) ]
554
617
pub ( crate ) fn slot_init(
555
- zelf: PyObjectRef ,
618
+ zelf: :: rustpython_vm :: PyObjectRef ,
556
619
args: :: rustpython_vm:: function:: FuncArgs ,
557
620
vm: & :: rustpython_vm:: VirtualMachine ,
558
621
) -> :: rustpython_vm:: PyResult <( ) > {
559
- #init_method
622
+ < Self as :: rustpython_vm :: class :: PyClassDef > :: Base :: slot_init ( zelf , args , vm )
560
623
}
561
624
}
562
625
} ;
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
+ } )
564
635
}
565
636
566
637
/// #[pymethod] and #[pyclassmethod]
@@ -1476,50 +1547,7 @@ where
1476
1547
Ok ( ( result, cfgs) )
1477
1548
}
1478
1549
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) ]
1523
1551
fn parse_vec_ident (
1524
1552
attr : & [ NestedMeta ] ,
1525
1553
item : & Item ,
0 commit comments