diff --git a/derive/src/pymodule.rs b/derive/src/pymodule.rs index d706da7c6..28124aeca 100644 --- a/derive/src/pymodule.rs +++ b/derive/src/pymodule.rs @@ -1,7 +1,7 @@ use crate::error::Diagnostic; use crate::util::{ - iter_use_idents, pyclass_ident_and_attrs, text_signature, AttributeExt, ClassItemMeta, - ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery, SimpleItemMeta, + iter_use_idents, pyclass_ident_and_attrs, text_signature, AttrItemMeta, AttributeExt, + ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES, }; use proc_macro2::TokenStream; @@ -241,7 +241,7 @@ impl ContentItem for AttributeItem { } struct ModuleItemArgs<'a> { - item: &'a Item, + item: &'a mut Item, attrs: &'a mut Vec, context: &'a mut ModuleContext, cfgs: &'a [Attribute], @@ -370,15 +370,36 @@ impl ModuleItem for AttributeItem { fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> 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) = match args.item { - Item::Fn(syn::ItemFn { sig, .. }) => { + Item::Fn(syn::ItemFn { sig, block, .. }) => { let ident = &sig.ident; - let py_name = get_py_name(&attr, ident)?; + // If `once` keyword is in #[pyattr], + // wrapping it with static_cell for preventing it from using it as function + let attr_meta = AttrItemMeta::from_attr(ident.clone(), &attr)?; + if attr_meta.inner()._bool("once")? { + let stmts = &block.stmts; + let return_type = match &sig.output { + syn::ReturnType::Default => { + unreachable!("#[pyattr] attached function must have return type.") + } + syn::ReturnType::Type(_, ty) => ty, + }; + let stmt: syn::Stmt = parse_quote! { + { + rustpython_common::static_cell! { + static ERROR: #return_type; + } + ERROR + .get_or_init(|| { + #(#stmts)* + }) + .clone() + } + }; + block.stmts = vec![stmt]; + } + + let py_name = attr_meta.simple_name()?; ( py_name.clone(), quote_spanned! { ident.span() => @@ -387,7 +408,8 @@ impl ModuleItem for AttributeItem { ) } Item::Const(syn::ItemConst { ident, .. }) => { - let py_name = get_py_name(&attr, ident)?; + let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?; + let py_name = item_meta.simple_name()?; ( py_name.clone(), quote_spanned! { ident.span() => diff --git a/derive/src/util.rs b/derive/src/util.rs index ed790d810..f104c03c9 100644 --- a/derive/src/util.rs +++ b/derive/src/util.rs @@ -236,6 +236,19 @@ impl ItemMeta for SimpleItemMeta { } } +pub(crate) struct AttrItemMeta(pub ItemMetaInner); + +impl ItemMeta for AttrItemMeta { + const ALLOWED_NAMES: &'static [&'static str] = &["name", "once"]; + + fn from_inner(inner: ItemMetaInner) -> Self { + Self(inner) + } + fn inner(&self) -> &ItemMetaInner { + &self.0 + } +} + pub(crate) struct ClassItemMeta(ItemMetaInner); impl ItemMeta for ClassItemMeta { diff --git a/stdlib/src/binascii.rs b/stdlib/src/binascii.rs index 4e3a53635..2130b5ddc 100644 --- a/stdlib/src/binascii.rs +++ b/stdlib/src/binascii.rs @@ -11,38 +11,18 @@ mod decl { }; use itertools::Itertools; - #[pyattr(name = "Error")] + #[pyattr(name = "Error", once)] fn error_type(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static BINASCII_ERROR: PyTypeRef; - } - BINASCII_ERROR - .get_or_init(|| { - vm.ctx.new_class( - Some("binascii"), - "Error", - &vm.ctx.exceptions.value_error, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type( + "binascii", + "Error", + Some(vec![vm.ctx.exceptions.value_error.clone()]), + ) } - #[pyattr(name = "Incomplete")] + #[pyattr(name = "Incomplete", once)] fn incomplete_type(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static BINASCII_INCOMPLTE: PyTypeRef; - } - BINASCII_INCOMPLTE - .get_or_init(|| { - vm.ctx.new_class( - Some("binascii"), - "Incomplete", - &vm.ctx.exceptions.exception_type, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type("binascii", "Incomplete", None) } fn hex_nibble(n: u8) -> u8 { diff --git a/stdlib/src/csv.rs b/stdlib/src/csv.rs index f89238ed1..a9fc1f1c9 100644 --- a/stdlib/src/csv.rs +++ b/stdlib/src/csv.rs @@ -11,7 +11,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { mod _csv { use crate::common::lock::PyMutex; use crate::vm::{ - builtins::{PyStr, PyStrRef, PyType, PyTypeRef}, + builtins::{PyStr, PyStrRef, PyTypeRef}, function::{ArgIterable, ArgumentError, FromArgs, FuncArgs}, match_class, protocol::{PyIter, PyIterReturn}, @@ -30,9 +30,13 @@ mod _csv { #[pyattr] const QUOTE_NONE: i32 = QuoteStyle::None as i32; - #[pyattr(name = "Error")] + #[pyattr(name = "Error", once)] fn error(vm: &VirtualMachine) -> PyTypeRef { - PyType::new_simple_ref("_csv.Error", &vm.ctx.exceptions.exception_type).unwrap() + vm.ctx.new_exception_type( + "_csv", + "Error", + Some(vec![vm.ctx.exceptions.exception_type.clone()]), + ) } #[pyfunction] diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index 4de71fdd0..9543c02b2 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -80,53 +80,29 @@ mod _socket { vm.ctx.exceptions.os_error.clone() } - #[pyattr] + #[pyattr(once)] fn timeout(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - vm.ctx.new_class( - Some("socket"), - "timeout", - &vm.ctx.exceptions.os_error, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type( + "socket", + "timeout", + Some(vec![vm.ctx.exceptions.os_error.clone()]), + ) } - #[pyattr] + #[pyattr(once)] fn herror(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - vm.ctx.new_class( - Some("socket"), - "herror", - &vm.ctx.exceptions.os_error, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type( + "socket", + "herror", + Some(vec![vm.ctx.exceptions.os_error.clone()]), + ) } - #[pyattr] + #[pyattr(once)] fn gaierror(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - vm.ctx.new_class( - Some("socket"), - "gaierror", - &vm.ctx.exceptions.os_error, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type( + "socket", + "gaierror", + Some(vec![vm.ctx.exceptions.os_error.clone()]), + ) } #[pyfunction] diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index 8709839d2..5230ba865 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -30,7 +30,7 @@ mod _ssl { }, socket::{self, PySocket}, vm::{ - builtins::{PyBaseException, PyBaseExceptionRef, PyStrRef, PyType, PyTypeRef, PyWeak}, + builtins::{PyBaseExceptionRef, PyStrRef, PyType, PyTypeRef, PyWeak}, exceptions, function::{ ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, IntoPyException, @@ -39,7 +39,7 @@ mod _ssl { stdlib::os::PyPathLike, types::Constructor, utils::{Either, ToCString}, - ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, VirtualMachine, + ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue, VirtualMachine, }, }; use crossbeam_utils::atomic::AtomicCell; @@ -174,90 +174,58 @@ mod _ssl { parse_version_info(openssl_api_version) } - #[pyattr(name = "SSLError")] + /// An error occurred in the SSL implementation. + #[pyattr(name = "SSLError", once)] fn ssl_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - PyType::new_simple_ref("ssl.SSLError", &vm.ctx.exceptions.os_error).unwrap() - }) - .clone() + vm.ctx.new_exception_type( + "ssl", + "SSLError", + Some(vec![vm.ctx.exceptions.os_error.clone()]), + ) } - #[pyattr(name = "SSLCertVerificationError")] + /// A certificate could not be verified. + #[pyattr(name = "SSLCertVerificationError", once)] fn ssl_cert_verification_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - let ssl_error = ssl_error(vm); - PyType::new_ref( - "ssl.SSLCertVerificationError", - vec![ssl_error, vm.ctx.exceptions.value_error.clone()], - Default::default(), - PyBaseException::make_slots(), - vm.ctx.types.type_type.clone(), - ) - .unwrap() - }) - .clone() + vm.ctx.new_exception_type( + "ssl", + "SSLCertVerificationError", + Some(vec![ssl_error(vm), vm.ctx.exceptions.value_error.clone()]), + ) } - #[pyattr(name = "SSLZeroReturnError")] + /// SSL/TLS session closed cleanly. + #[pyattr(name = "SSLZeroReturnError", once)] fn ssl_zero_return_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - PyType::new_simple_ref("ssl.SSLZeroReturnError", &ssl_error(vm)).unwrap() - }) - .clone() + vm.ctx + .new_exception_type("ssl", "SSLZeroReturnError", Some(vec![ssl_error(vm)])) } - #[pyattr(name = "SSLWantReadError")] + /// Non-blocking SSL socket needs to read more data before the requested operation can be completed. + #[pyattr(name = "SSLWantReadError", once)] fn ssl_want_read_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| PyType::new_simple_ref("ssl.SSLWantReadError", &ssl_error(vm)).unwrap()) - .clone() + vm.ctx + .new_exception_type("ssl", "SSLWantReadError", Some(vec![ssl_error(vm)])) } - #[pyattr(name = "SSLWantWriteError")] + /// Non-blocking SSL socket needs to write more data before the requested operation can be completed. + #[pyattr(name = "SSLWantWriteError", once)] fn ssl_want_write_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| { - PyType::new_simple_ref("ssl.SSLWantWriteError", &ssl_error(vm)).unwrap() - }) - .clone() + vm.ctx + .new_exception_type("ssl", "SSLWantWriteError", Some(vec![ssl_error(vm)])) } - #[pyattr(name = "SSLSyscallError")] + /// System error when attempting SSL operation. + #[pyattr(name = "SSLSyscallError", once)] fn ssl_syscall_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| PyType::new_simple_ref("ssl.SSLSyscallError", &ssl_error(vm)).unwrap()) - .clone() + vm.ctx + .new_exception_type("ssl", "SSLSyscallError", Some(vec![ssl_error(vm)])) } - #[pyattr(name = "SSLEOFError")] + /// SSL/TLS connection terminated abruptly. + #[pyattr(name = "SSLEOFError", once)] fn ssl_eof_error(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static ERROR: PyTypeRef; - } - ERROR - .get_or_init(|| PyType::new_simple_ref("ssl.SSLEOFError", &ssl_error(vm)).unwrap()) - .clone() + PyType::new_simple_ref("ssl.SSLEOFError", &ssl_error(vm)).unwrap() } type OpensslVersionInfo = (u8, u8, u8, u8, u8); diff --git a/stdlib/src/termios.rs b/stdlib/src/termios.rs index 97ccdac29..507a655fa 100644 --- a/stdlib/src/termios.rs +++ b/stdlib/src/termios.rs @@ -100,20 +100,12 @@ mod termios { ) } - #[pyattr(name = "error")] + #[pyattr(name = "error", once)] fn error_type(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static TERMIOS_ERROR: PyTypeRef; - } - TERMIOS_ERROR - .get_or_init(|| { - vm.ctx.new_class( - Some("termios"), - "error", - &vm.ctx.exceptions.os_error, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type( + "termios", + "error", + Some(vec![vm.ctx.exceptions.os_error.clone()]), + ) } } diff --git a/stdlib/src/zlib.rs b/stdlib/src/zlib.rs index 492fc60e3..d2f44de20 100644 --- a/stdlib/src/zlib.rs +++ b/stdlib/src/zlib.rs @@ -4,7 +4,7 @@ pub(crate) use zlib::make_module; mod zlib { use crate::common::lock::PyMutex; use crate::vm::{ - builtins::{PyBaseExceptionRef, PyBytes, PyBytesRef, PyIntRef, PyType, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyBytes, PyBytesRef, PyIntRef, PyTypeRef}, function::{ArgBytesLike, OptionalArg}, IntoPyRef, PyResult, PyValue, VirtualMachine, }; @@ -53,9 +53,13 @@ mod zlib { #[pyattr] const DEF_MEM_LEVEL: u8 = 8; - #[pyattr] + #[pyattr(once)] fn error(vm: &VirtualMachine) -> PyTypeRef { - PyType::new_simple_ref("zlib.error", &vm.ctx.exceptions.exception_type).unwrap() + vm.ctx.new_exception_type( + "zlib", + "error", + Some(vec![vm.ctx.exceptions.exception_type.clone()]), + ) } /// Compute an Adler-32 checksum of data. diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d7cff5d60..0d78ba471 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -12,9 +12,9 @@ use crate::{ builtinfunc::{PyBuiltinFunction, PyBuiltinMethod, PyNativeFuncDef}, bytes, getset::{IntoPyGetterFunc, IntoPySetterFunc, PyGetSet}, - object, pystr, PyBaseExceptionRef, PyBoundMethod, PyDict, PyDictRef, PyEllipsis, PyFloat, - PyFrozenSet, PyGenericAlias, PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, - PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef, + object, pystr, PyBaseException, PyBaseExceptionRef, PyBoundMethod, PyDict, PyDictRef, + PyEllipsis, PyFloat, PyFrozenSet, PyGenericAlias, PyInt, PyIntRef, PyList, PyListRef, + PyNone, PyNotImplemented, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef, }, dictdatatype::Dict, exceptions, @@ -242,6 +242,30 @@ impl PyContext { .unwrap() } + pub fn new_exception_type( + &self, + module: &str, + name: &str, + bases: Option>, + ) -> PyTypeRef { + let bases = if let Some(bases) = bases { + bases + } else { + vec![self.exceptions.exception_type.clone()] + }; + let mut attrs = PyAttributes::default(); + attrs.insert("__module__".to_string(), self.new_str(module).into()); + + PyType::new_ref( + name, + bases, + attrs, + PyBaseException::make_slots(), + self.types.type_type.clone(), + ) + .unwrap() + } + #[inline] pub fn make_funcdef(&self, name: impl Into, f: F) -> PyNativeFuncDef where diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 7530a303c..bec416b06 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -960,21 +960,9 @@ pub(crate) mod _struct { #[pyfunction] fn _clearcache() {} - #[pyattr(name = "error")] + #[pyattr(name = "error", once)] fn error_type(vm: &VirtualMachine) -> PyTypeRef { - rustpython_common::static_cell! { - static STRUCT_ERROR: PyTypeRef; - } - STRUCT_ERROR - .get_or_init(|| { - vm.ctx.new_class( - Some("struct"), - "error", - &vm.ctx.exceptions.exception_type, - Default::default(), - ) - }) - .clone() + vm.ctx.new_exception_type("struct", "error", None) } fn new_struct_error(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef {