Merge define_exception into pyexception

This commit is contained in:
Jeong YunWon
2023-04-18 03:43:00 +09:00
parent 4e0890a50d
commit f256934f93
15 changed files with 610 additions and 625 deletions

View File

@@ -40,19 +40,18 @@ pub fn derive_from_args(input: DeriveInput) -> TokenStream {
pub fn pyclass(attr: AttributeArgs, item: Item) -> TokenStream {
if matches!(item, syn::Item::Impl(_) | syn::Item::Trait(_)) {
result_to_tokens(pyclass::impl_pyimpl(attr, item))
result_to_tokens(pyclass::impl_pyclass_impl(attr, item))
} else {
result_to_tokens(pyclass::impl_pyclass(attr, item))
}
}
pub use pyclass::PyExceptionDef;
pub fn define_exception(exc_def: PyExceptionDef) -> TokenStream {
result_to_tokens(pyclass::impl_define_exception(exc_def))
}
pub fn pyexception(attr: AttributeArgs, item: Item) -> TokenStream {
result_to_tokens(pyclass::impl_pyexception(attr, item))
if matches!(item, syn::Item::Impl(_)) {
result_to_tokens(pyclass::impl_pyexception_impl(attr, item))
} else {
result_to_tokens(pyclass::impl_pyexception(attr, item))
}
}
pub fn pymodule(attr: AttributeArgs, item: Item) -> TokenStream {

View File

@@ -1,18 +1,15 @@
use super::Diagnostic;
use crate::util::{
format_doc, pyclass_ident_and_attrs, text_signature, ClassItemMeta, ContentItem,
ContentItemInner, ErrorVec, ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta,
ALL_ALLOWED_NAMES,
format_doc, pyclass_ident_and_attrs, pyexception_ident_and_attrs, text_signature,
ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta, ItemMeta,
ItemMetaInner, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use std::collections::HashMap;
use std::str::FromStr;
use syn::{
parse::{Parse, ParseStream, Result as ParsingResult},
parse_quote,
spanned::Spanned,
Attribute, AttributeArgs, Ident, Item, LitStr, Meta, NestedMeta, Result, Token,
parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Meta, NestedMeta, Result,
};
use syn_ext::ext::*;
@@ -99,7 +96,7 @@ fn extract_items_into_context<'a, Item>(
context.errors.ok_or_push(context.member_items.validate());
}
pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
let mut context = ImplContext::default();
let mut tokens = match item {
Item::Impl(mut imp) => {
@@ -340,8 +337,8 @@ fn generate_class_def(
let base_class = if is_pystruct {
Some(quote! { rustpython_vm::builtins::PyTuple })
} else {
base.map(|typ| {
let typ = Ident::new(&typ, ident.span());
base.as_ref().map(|typ| {
let typ = Ident::new(typ, ident.span());
quote_spanned! { ident.span() => #typ }
})
}
@@ -364,6 +361,13 @@ fn generate_class_def(
}
});
let base_or_object = if let Some(base) = base {
let base = Ident::new(&base, ident.span());
quote! { #base }
} else {
quote! { ::rustpython_vm::builtins::PyBaseObject }
};
let tokens = quote! {
impl ::rustpython_vm::class::PyClassDef for #ident {
const NAME: &'static str = #name;
@@ -372,6 +376,8 @@ fn generate_class_def(
const DOC: Option<&'static str> = #doc;
const BASICSIZE: usize = #basicsize;
const UNHASHABLE: bool = #unhashable;
type Base = #base_or_object;
}
impl ::rustpython_vm::class::StaticType for #ident {
@@ -462,11 +468,38 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
}
};
let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? {
let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span
// We need this to make extend mechanism work:
quote! {
impl ::rustpython_vm::PyPayload for #ident {
fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
ctx.types.#ctx_type_ident
}
}
}
} else {
quote! {}
};
let empty_impl = if let Some(attrs) = class_meta.impl_attrs()? {
let attrs: Meta = parse_quote! (#attrs);
quote! {
#[pyclass(#attrs)]
impl #ident {}
}
} else {
quote! {}
};
let ret = quote! {
#derive_trace
#item
#maybe_trace_code
#class_def
#impl_payload
#empty_impl
};
Ok(ret)
}
@@ -480,87 +513,125 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
/// to add non-literal attributes to `pyclass`.
/// That's why we have to use this proxy.
pub(crate) fn impl_pyexception(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
let class_name = parse_vec_ident(&attr, &item, 0, "first 'class_name'")?;
let base_class_name = parse_vec_ident(&attr, &item, 1, "second 'base_class_name'")?;
let (ident, _attrs) = pyexception_ident_and_attrs(&item)?;
let fake_ident = Ident::new("pyclass", item.span());
let class_meta = ExceptionItemMeta::from_nested(ident.clone(), fake_ident, attr.into_iter())?;
let class_name = class_meta.class_name()?;
// We also need to strip `Py` prefix from `class_name`,
// due to implementation and Python naming conventions mismatch:
// `PyKeyboardInterrupt` -> `KeyboardInterrupt`
let class_name = class_name
.strip_prefix("Py")
.ok_or_else(|| err_span!(item, "We require 'class_name' to have 'Py' prefix"))?;
let base_class_name = class_meta.base()?;
let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? {
let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span
// We need this to make extend mechanism work:
quote! {
impl ::rustpython_vm::PyPayload for #ident {
fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
ctx.exceptions.#ctx_type_ident
}
}
}
} else {
quote! {}
};
let impl_pyclass = if class_meta.has_impl()? {
quote! {
#[pyexception]
impl #ident {}
}
} else {
quote! {}
};
// We just "proxy" it into `pyclass` macro, because, exception is a class.
let ret = quote! {
#[pyclass(module = false, name = #class_name, base = #base_class_name)]
#item
#impl_payload
#impl_pyclass
};
Ok(ret)
}
pub(crate) fn impl_define_exception(exc_def: PyExceptionDef) -> Result<TokenStream> {
let PyExceptionDef {
class_name,
base_class,
ctx_name,
docs,
slot_new,
init,
} = exc_def;
// We need this method, because of how `CPython` copies `__new__`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
let slot_new_impl = match slot_new {
Some(slot_call) => quote! { #slot_call(cls, args, vm) },
None => quote! { #base_class::slot_new(cls, args, vm) },
pub(crate) fn impl_pyexception_impl(attr: AttributeArgs, item: Item) -> Result<TokenStream> {
let Item::Impl(imp) = item else {
return Ok(item.into_token_stream());
};
// We need this method, because of how `CPython` copies `__init__`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `(initproc)BaseException_init`
// spell-checker:ignore initproc
let init_method = match init {
Some(init_def) => quote! { #init_def(zelf, args, vm) },
None => quote! { #base_class::slot_init(zelf, args, vm) },
};
if !attr.is_empty() {
return Err(syn::Error::new_spanned(
&attr[0],
"#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.",
));
}
let ret = quote! {
#[pyexception(#class_name, #base_class)]
#[derive(Debug)]
#[doc = #docs]
pub struct #class_name {}
// We need this to make extend mechanism work:
impl ::rustpython_vm::PyPayload for #class_name {
fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
ctx.exceptions.#ctx_name
let mut has_slot_new = false;
let mut has_slot_init = false;
let syn::ItemImpl {
generics,
self_ty,
items,
..
} = &imp;
for item in items {
// FIXME: better detection or correct wrapper implementation
let Some(ident) = item.get_ident() else {
continue;
};
let item_name = ident.to_string();
match item_name.as_str() {
"slot_new" => {
has_slot_new = true;
}
"slot_init" => {
has_slot_init = true;
}
_ => continue,
}
}
#[pyclass(flags(BASETYPE, HAS_DICT))]
impl #class_name {
let slot_new = if has_slot_new {
quote!()
} else {
quote! {
#[pyslot]
pub(crate) fn slot_new(
cls: ::rustpython_vm::builtins::PyTypeRef,
args: ::rustpython_vm::function::FuncArgs,
vm: &::rustpython_vm::VirtualMachine,
) -> ::rustpython_vm::PyResult {
#slot_new_impl
}
#[pyslot]
#[pymethod(name="__init__")]
pub(crate) fn slot_init(
zelf: PyObjectRef,
args: ::rustpython_vm::function::FuncArgs,
vm: &::rustpython_vm::VirtualMachine,
) -> ::rustpython_vm::PyResult<()> {
#init_method
<Self as ::rustpython_vm::class::PyClassDef>::Base::slot_new(cls, args, vm)
}
}
};
Ok(ret)
// We need this method, because of how `CPython` copies `__init__`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `(initproc)BaseException_init`
// spell-checker:ignore initproc
let slot_init = if has_slot_init {
quote!()
} else {
// FIXME: this is a generic logic for types not only for exceptions
quote! {
#[pyslot]
#[pymethod(name="__init__")]
pub(crate) fn slot_init(
zelf: ::rustpython_vm::PyObjectRef,
args: ::rustpython_vm::function::FuncArgs,
vm: &::rustpython_vm::VirtualMachine,
) -> ::rustpython_vm::PyResult<()> {
<Self as ::rustpython_vm::class::PyClassDef>::Base::slot_init(zelf, args, vm)
}
}
};
Ok(quote! {
#[pyclass(flags(BASETYPE, HAS_DICT))]
impl #generics #self_ty {
#(#items)*
#slot_new
#slot_init
}
})
}
/// #[pymethod] and #[pyclassmethod]
@@ -1476,50 +1547,7 @@ where
Ok((result, cfgs))
}
#[derive(Debug)]
pub struct PyExceptionDef {
pub class_name: Ident,
pub base_class: Ident,
pub ctx_name: Ident,
pub docs: LitStr,
/// Holds optional `slot_new` slot to be used instead of a default one:
pub slot_new: Option<Ident>,
/// We also store `__init__` magic method, that can
pub init: Option<Ident>,
}
impl Parse for PyExceptionDef {
fn parse(input: ParseStream) -> ParsingResult<Self> {
let class_name: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let base_class: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let ctx_name: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let docs: LitStr = input.parse()?;
input.parse::<Option<Token![,]>>()?;
let slot_new: Option<Ident> = input.parse()?;
input.parse::<Option<Token![,]>>()?;
let init: Option<Ident> = input.parse()?;
input.parse::<Option<Token![,]>>()?; // leading `,`
Ok(PyExceptionDef {
class_name,
base_class,
ctx_name,
docs,
slot_new,
init,
})
}
}
#[allow(dead_code)]
fn parse_vec_ident(
attr: &[NestedMeta],
item: &Item,

View File

@@ -273,6 +273,7 @@ impl ItemMeta for ClassItemMeta {
"base",
"metaclass",
"unhashable",
"impl",
"traverse",
];
@@ -306,6 +307,10 @@ impl ClassItemMeta {
)
}
pub fn ctx_name(&self) -> Result<Option<String>> {
self.inner()._optional_str("ctx")
}
pub fn base(&self) -> Result<Option<String>> {
self.inner()._optional_str("base")
}
@@ -349,6 +354,10 @@ impl ClassItemMeta {
Ok(value)
}
pub fn impl_attrs(&self) -> Result<Option<String>> {
self.inner()._optional_str("impl")
}
// pub fn mandatory_module(&self) -> Result<String> {
// let inner = self.inner();
// let value = self.module().ok().flatten().
@@ -361,6 +370,64 @@ impl ClassItemMeta {
// }
}
pub(crate) struct ExceptionItemMeta(ClassItemMeta);
impl ItemMeta for ExceptionItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "base", "unhashable", "ctx", "impl"];
fn from_inner(inner: ItemMetaInner) -> Self {
Self(ClassItemMeta(inner))
}
fn inner(&self) -> &ItemMetaInner {
&self.0 .0
}
}
impl ExceptionItemMeta {
pub fn class_name(&self) -> Result<String> {
const KEY: &str = "name";
let inner = self.inner();
if let Some((_, meta)) = inner.meta_map.get(KEY) {
match meta {
Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit),
..
}) => return Ok(lit.value()),
Meta::Path(_) => {
return Ok({
let type_name = inner.item_name();
let Some(py_name) = type_name.as_str().strip_prefix("Py") else {
bail_span!(
inner.item_ident,
"#[pyexception] expects its underlying type to be named `Py` prefixed"
)
};
py_name.to_string()
})
}
_ => {}
}
}
bail_span!(
inner.meta_ident,
"#[{attr_name}(name = ...)] must exist as a string. Try \
#[{attr_name}(name)] to use rust type name.",
attr_name = inner.meta_name()
)
}
pub fn has_impl(&self) -> Result<bool> {
self.inner()._bool("impl")
}
}
impl std::ops::Deref for ExceptionItemMeta {
type Target = ClassItemMeta;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub(crate) trait AttributeExt: SynAttributeExt {
fn promoted_nested(&self) -> Result<PunctuatedNestedMeta>;
fn ident_and_promoted_nested(&self) -> Result<(&Ident, PunctuatedNestedMeta)>;
@@ -468,6 +535,17 @@ pub(crate) fn pyclass_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Att
})
}
pub(crate) fn pyexception_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Attribute])> {
use syn::Item::*;
Ok(match item {
Struct(syn::ItemStruct { ident, attrs, .. }) => (ident, attrs),
Enum(syn::ItemEnum { ident, attrs, .. }) => (ident, attrs),
other => {
bail_span!(other, "#[pyexception] can only be on a struct or enum",)
}
})
}
pub(crate) trait ErrorVec: Sized {
fn into_error(self) -> Option<syn::Error>;
fn into_result(self) -> Result<()> {

View File

@@ -19,24 +19,14 @@ pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
derive_impl::pyclass(attr, item).into()
}
/// Helper macro to define `Exception` types.
/// More-or-less is an alias to `pyclass` macro.
///
/// This macro serves a goal of generating multiple
/// `BaseException` / `Exception`
/// subtypes in a uniform and convenient manner.
/// It looks like `SimpleExtendsException` in `CPython`.
/// <https://github.com/python/cpython/blob/main/Objects/exceptions.c>
///
/// We need `ctx` to be ready to add
/// `properties` / `custom` constructors / slots / methods etc.
/// So, we use `extend_class!` macro as the second
/// step in exception type definition.
#[proc_macro]
pub fn define_exception(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);
derive_impl::define_exception(input).into()
}
/// Helper macro to define `Exception` types.
/// More-or-less is an alias to `pyclass` macro.
#[proc_macro_attribute]
pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr);

View File

@@ -239,13 +239,13 @@ assert BaseException.__new__.__qualname__ == 'BaseException.__new__'
assert BaseException.__init__.__qualname__ == 'BaseException.__init__'
assert BaseException().__dict__ == {}
assert Exception.__new__.__qualname__ == 'Exception.__new__'
assert Exception.__init__.__qualname__ == 'Exception.__init__'
assert Exception.__new__.__qualname__ == 'Exception.__new__', Exception.__new__.__qualname__
assert Exception.__init__.__qualname__ == 'Exception.__init__', Exception.__init__.__qualname__
assert Exception().__dict__ == {}
# Extends `BaseException`, simple:
assert KeyboardInterrupt.__new__.__qualname__ == 'KeyboardInterrupt.__new__'
assert KeyboardInterrupt.__new__.__qualname__ == 'KeyboardInterrupt.__new__', KeyboardInterrupt.__new__.__qualname__
assert KeyboardInterrupt.__init__.__qualname__ == 'KeyboardInterrupt.__init__'
assert KeyboardInterrupt().__dict__ == {}

View File

@@ -774,12 +774,11 @@ impl PyType {
base.slots.member_count + heaptype_slots.as_ref().map(|x| x.len()).unwrap_or(0);
let flags = PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT;
let (slots, heaptype_ext) = unsafe {
// # Safety
// `slots.name` live long enough because `heaptype_ext` is alive.
let (slots, heaptype_ext) = {
let slots = PyTypeSlots {
member_count,
..PyTypeSlots::new(&*(name.as_str() as *const _), flags)
flags,
..PyTypeSlots::heap_default()
};
let heaptype_ext = HeapTypeExt {
name: PyRwLock::new(name),

View File

@@ -60,6 +60,10 @@ pub trait PyClassDef {
const DOC: Option<&'static str> = None;
const BASICSIZE: usize;
const UNHASHABLE: bool = false;
// due to restriction of rust trait system, object.__base__ is None
// but PyBaseObject::Base will be PyBaseObject.
type Base: PyClassDef;
}
pub trait PyClassImpl: PyClassDef {
@@ -98,14 +102,12 @@ pub trait PyClassImpl: PyClassDef {
ctx.new_str(module_name).into(),
);
}
if class.slots.new.load().is_some() {
let bound = PyBoundMethod::new_ref(
class.to_owned().into(),
ctx.slot_new_wrapper.clone().into(),
ctx,
);
class.set_attr(identifier!(ctx, __new__), bound.into());
}
let bound_new = PyBoundMethod::new_ref(
class.to_owned().into(),
ctx.slot_new_wrapper.clone().into(),
ctx,
);
class.set_attr(identifier!(ctx, __new__), bound_new.into());
if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
class.set_attr(ctx.names.__hash__, ctx.none.clone().into());

View File

@@ -1162,502 +1162,373 @@ pub(super) mod types {
pub(super) args: PyRwLock<PyTupleRef>,
}
define_exception! {
PySystemExit,
PyBaseException,
system_exit,
"Request to exit from the interpreter."
}
define_exception! {
PyBaseExceptionGroup,
PyBaseException,
base_exception_group,
"A combination of multiple unrelated exceptions."
}
define_exception! {
PyGeneratorExit,
PyBaseException,
generator_exit,
"Request that a generator exit."
}
define_exception! {
PyKeyboardInterrupt,
PyBaseException,
keyboard_interrupt,
"Program interrupted by user."
#[pyexception(name, base = "PyBaseException", ctx = "system_exit", impl)]
#[derive(Debug)]
pub struct PySystemExit {}
#[pyexception(name, base = "PyBaseException", ctx = "base_exception_group", impl)]
#[derive(Debug)]
pub struct PyBaseExceptionGroup {}
#[pyexception(name, base = "PyBaseException", ctx = "generator_exit", impl)]
#[derive(Debug)]
pub struct PyGeneratorExit {}
#[pyexception(name, base = "PyBaseException", ctx = "keyboard_interrupt", impl)]
#[derive(Debug)]
pub struct PyKeyboardInterrupt {}
#[pyexception(name, base = "PyBaseException", ctx = "exception_type", impl)]
#[derive(Debug)]
pub struct PyException {}
#[pyexception(name, base = "PyException", ctx = "stop_iteration")]
#[derive(Debug)]
pub struct PyStopIteration {}
#[pyexception]
impl PyStopIteration {
#[pyslot]
#[pymethod(name = "__init__")]
pub(crate) fn slot_init(
zelf: PyObjectRef,
args: ::rustpython_vm::function::FuncArgs,
vm: &::rustpython_vm::VirtualMachine,
) -> ::rustpython_vm::PyResult<()> {
zelf.set_attr("value", vm.unwrap_or_none(args.args.get(0).cloned()), vm)?;
Ok(())
}
}
// Base `Exception` type
define_exception! {
PyException,
PyBaseException,
exception_type,
"Common base class for all non-exit exceptions."
#[pyexception(name, base = "PyException", ctx = "stop_async_iteration", impl)]
#[derive(Debug)]
pub struct PyStopAsyncIteration {}
#[pyexception(name, base = "PyException", ctx = "arithmetic_error", impl)]
#[derive(Debug)]
pub struct PyArithmeticError {}
#[pyexception(name, base = "PyArithmeticError", ctx = "floating_point_error", impl)]
#[derive(Debug)]
pub struct PyFloatingPointError {}
#[pyexception(name, base = "PyArithmeticError", ctx = "overflow_error", impl)]
#[derive(Debug)]
pub struct PyOverflowError {}
#[pyexception(name, base = "PyArithmeticError", ctx = "zero_division_error", impl)]
#[derive(Debug)]
pub struct PyZeroDivisionError {}
#[pyexception(name, base = "PyException", ctx = "assertion_error", impl)]
#[derive(Debug)]
pub struct PyAssertionError {}
#[pyexception(name, base = "PyException", ctx = "attribute_error", impl)]
#[derive(Debug)]
pub struct PyAttributeError {}
#[pyexception(name, base = "PyException", ctx = "buffer_error", impl)]
#[derive(Debug)]
pub struct PyBufferError {}
#[pyexception(name, base = "PyException", ctx = "eof_error", impl)]
#[derive(Debug)]
pub struct PyEOFError {}
#[pyexception(name, base = "PyException", ctx = "import_error")]
#[derive(Debug)]
pub struct PyImportError {}
#[pyexception]
impl PyImportError {
#[pyslot]
#[pymethod(name = "__init__")]
pub(crate) fn slot_init(
zelf: PyObjectRef,
args: ::rustpython_vm::function::FuncArgs,
vm: &::rustpython_vm::VirtualMachine,
) -> ::rustpython_vm::PyResult<()> {
zelf.set_attr(
"name",
vm.unwrap_or_none(args.kwargs.get("name").cloned()),
vm,
)?;
zelf.set_attr(
"path",
vm.unwrap_or_none(args.kwargs.get("path").cloned()),
vm,
)?;
Ok(())
}
}
define_exception! {
PyStopIteration,
PyException,
stop_iteration,
"Signal the end from iterator.__next__().",
base_exception_new,
stop_iteration_init
}
fn stop_iteration_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
zelf.set_attr("value", vm.unwrap_or_none(args.args.get(0).cloned()), vm)?;
Ok(())
}
#[pyexception(name, base = "PyImportError", ctx = "module_not_found_error", impl)]
#[derive(Debug)]
pub struct PyModuleNotFoundError {}
define_exception! {
PyStopAsyncIteration,
PyException,
stop_async_iteration,
"Signal the end from iterator.__anext__()."
}
#[pyexception(name, base = "PyException", ctx = "lookup_error", impl)]
#[derive(Debug)]
pub struct PyLookupError {}
define_exception! {
PyArithmeticError,
PyException,
arithmetic_error,
"Base class for arithmetic errors."
}
define_exception! {
PyFloatingPointError,
PyArithmeticError,
floating_point_error,
"Floating point operation failed."
}
define_exception! {
PyOverflowError,
PyArithmeticError,
overflow_error,
"Result too large to be represented."
}
define_exception! {
PyZeroDivisionError,
PyArithmeticError,
zero_division_error,
"Second argument to a division or modulo operation was zero."
}
#[pyexception(name, base = "PyLookupError", ctx = "index_error", impl)]
#[derive(Debug)]
pub struct PyIndexError {}
define_exception! {
PyAssertionError,
PyException,
assertion_error,
"Assertion failed."
}
define_exception! {
PyAttributeError,
PyException,
attribute_error,
"Attribute not found."
}
define_exception! {
PyBufferError,
PyException,
buffer_error,
"Buffer error."
}
define_exception! {
PyEOFError,
PyException,
eof_error,
"Read beyond end of file."
}
#[pyexception(name, base = "PyLookupError", ctx = "key_error", impl)]
#[derive(Debug)]
pub struct PyKeyError {}
define_exception! {
PyImportError,
PyException,
import_error,
"Import can't find module, or can't find name in module.",
base_exception_new,
import_error_init,
}
#[pyexception(name, base = "PyException", ctx = "memory_error", impl)]
#[derive(Debug)]
pub struct PyMemoryError {}
fn base_exception_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
PyBaseException::slot_new(cls, args, vm)
}
#[pyexception(name, base = "PyException", ctx = "name_error", impl)]
#[derive(Debug)]
pub struct PyNameError {}
fn import_error_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
zelf.set_attr(
"name",
vm.unwrap_or_none(args.kwargs.get("name").cloned()),
vm,
)?;
zelf.set_attr(
"path",
vm.unwrap_or_none(args.kwargs.get("path").cloned()),
vm,
)?;
Ok(())
}
#[pyexception(name, base = "PyNameError", ctx = "unbound_local_error", impl)]
#[derive(Debug)]
pub struct PyUnboundLocalError {}
define_exception! {
PyModuleNotFoundError,
PyImportError,
module_not_found_error,
"Module not found."
}
define_exception! {
PyLookupError,
PyException,
lookup_error,
"Base class for lookup errors."
}
define_exception! {
PyIndexError,
PyLookupError,
index_error,
"Sequence index out of range."
}
define_exception! {
PyKeyError,
PyLookupError,
key_error,
"Mapping key not found."
}
define_exception! {
PyMemoryError,
PyException,
memory_error,
"Out of memory."
}
define_exception! {
PyNameError,
PyException,
name_error,
"Name not found globally."
}
define_exception! {
PyUnboundLocalError,
PyNameError,
unbound_local_error,
"Local name referenced but not bound to a value."
}
#[pyexception(name, base = "PyException", ctx = "os_error")]
#[derive(Debug)]
pub struct PyOSError {}
// OS Errors:
define_exception! {
PyOSError,
PyException,
os_error,
"Base class for I/O related errors.",
os_error_new,
os_error_init,
}
#[cfg(not(target_arch = "wasm32"))]
fn os_error_optional_new(
args: Vec<PyObjectRef>,
vm: &VirtualMachine,
) -> Option<PyBaseExceptionRef> {
let len = args.len();
if (2..=5).contains(&len) {
let errno = &args[0];
errno
.payload_if_subclass::<PyInt>(vm)
.and_then(|errno| errno.try_to_primitive::<i32>(vm).ok())
.and_then(|errno| super::raw_os_error_to_exc_type(errno, vm))
.and_then(|typ| vm.invoke_exception(typ.to_owned(), args.to_vec()).ok())
} else {
None
}
}
#[cfg(not(target_arch = "wasm32"))]
fn os_error_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// We need this method, because of how `CPython` copies `init`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
if *cls.name() == *vm.ctx.exceptions.os_error.name() {
match os_error_optional_new(args.args.to_vec(), vm) {
Some(error) => error.to_pyresult(vm),
None => PyBaseException::slot_new(cls, args, vm),
#[pyexception]
impl PyOSError {
#[cfg(not(target_arch = "wasm32"))]
fn optional_new(args: Vec<PyObjectRef>, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
let len = args.len();
if (2..=5).contains(&len) {
let errno = &args[0];
errno
.payload_if_subclass::<PyInt>(vm)
.and_then(|errno| errno.try_to_primitive::<i32>(vm).ok())
.and_then(|errno| super::raw_os_error_to_exc_type(errno, vm))
.and_then(|typ| vm.invoke_exception(typ.to_owned(), args.to_vec()).ok())
} else {
None
}
} else {
}
#[cfg(not(target_arch = "wasm32"))]
#[pyslot]
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// We need this method, because of how `CPython` copies `init`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
if *cls.name() == *vm.ctx.exceptions.os_error.name() {
match Self::optional_new(args.args.to_vec(), vm) {
Some(error) => error.to_pyresult(vm),
None => PyBaseException::slot_new(cls, args, vm),
}
} else {
PyBaseException::slot_new(cls, args, vm)
}
}
#[cfg(target_arch = "wasm32")]
#[pyslot]
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
PyBaseException::slot_new(cls, args, vm)
}
}
#[cfg(target_arch = "wasm32")]
fn os_error_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
PyBaseException::slot_new(cls, args, vm)
}
fn os_error_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
let len = args.args.len();
let mut new_args = args;
if (3..=5).contains(&len) {
zelf.set_attr("filename", new_args.args[2].clone(), vm)?;
if len == 5 {
zelf.set_attr("filename2", new_args.args[4].clone(), vm)?;
#[pyslot]
#[pymethod(name = "__init__")]
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
let len = args.args.len();
let mut new_args = args;
if (3..=5).contains(&len) {
zelf.set_attr("filename", new_args.args[2].clone(), vm)?;
if len == 5 {
zelf.set_attr("filename2", new_args.args[4].clone(), vm)?;
}
new_args.args.truncate(2);
}
new_args.args.truncate(2);
PyBaseException::slot_init(zelf, new_args, vm)
}
PyBaseException::slot_init(zelf, new_args, vm)
}
define_exception! {
PyBlockingIOError,
PyOSError,
blocking_io_error,
"I/O operation would block."
}
define_exception! {
PyChildProcessError,
PyOSError,
child_process_error,
"Child process error."
}
define_exception! {
PyConnectionError,
PyOSError,
connection_error,
"Connection error."
}
define_exception! {
PyBrokenPipeError,
PyConnectionError,
broken_pipe_error,
"Broken pipe."
}
define_exception! {
PyConnectionAbortedError,
PyConnectionError,
connection_aborted_error,
"Connection aborted."
}
define_exception! {
PyConnectionRefusedError,
PyConnectionError,
connection_refused_error,
"Connection refused."
}
define_exception! {
PyConnectionResetError,
PyConnectionError,
connection_reset_error,
"Connection reset."
}
define_exception! {
PyFileExistsError,
PyOSError,
file_exists_error,
"File already exists."
}
define_exception! {
PyFileNotFoundError,
PyOSError,
file_not_found_error,
"File not found."
}
define_exception! {
PyInterruptedError,
PyOSError,
interrupted_error,
"Interrupted by signal."
}
define_exception! {
PyIsADirectoryError,
PyOSError,
is_a_directory_error,
"Operation doesn't work on directories."
}
define_exception! {
PyNotADirectoryError,
PyOSError,
not_a_directory_error,
"Operation only works on directories."
}
define_exception! {
PyPermissionError,
PyOSError,
permission_error,
"Not enough permissions."
}
define_exception! {
PyProcessLookupError,
PyOSError,
process_lookup_error,
"Process not found."
}
define_exception! {
PyTimeoutError,
PyOSError,
timeout_error,
"Timeout expired."
}
#[pyexception(name, base = "PyOSError", ctx = "blocking_io_error", impl)]
#[derive(Debug)]
pub struct PyBlockingIOError {}
define_exception! {
PyReferenceError,
PyException,
reference_error,
"Weak ref proxy used after referent went away."
}
#[pyexception(name, base = "PyOSError", ctx = "child_process_error", impl)]
#[derive(Debug)]
pub struct PyChildProcessError {}
define_exception! {
PyRuntimeError,
PyException,
runtime_error,
"Unspecified run-time error."
}
define_exception! {
PyNotImplementedError,
PyRuntimeError,
not_implemented_error,
"Method or function hasn't been implemented yet."
}
define_exception! {
PyRecursionError,
PyRuntimeError,
recursion_error,
"Recursion limit exceeded."
}
#[pyexception(name, base = "PyOSError", ctx = "connection_error", impl)]
#[derive(Debug)]
pub struct PyConnectionError {}
define_exception! {
PySyntaxError,
PyException,
syntax_error,
"Invalid syntax."
}
define_exception! {
PyIndentationError,
PySyntaxError,
indentation_error,
"Improper indentation."
}
define_exception! {
PyTabError,
PyIndentationError,
tab_error,
"Improper mixture of spaces and tabs."
}
#[pyexception(name, base = "PyConnectionError", ctx = "broken_pipe_error", impl)]
#[derive(Debug)]
pub struct PyBrokenPipeError {}
define_exception! {
PySystemError,
PyException,
system_error,
"Internal error in the Python interpreter.\n\nPlease report this to the Python maintainer, along with the traceback,\nthe Python version, and the hardware/OS platform and version."
}
#[pyexception(
name,
base = "PyConnectionError",
ctx = "connection_aborted_error",
impl
)]
#[derive(Debug)]
pub struct PyConnectionAbortedError {}
define_exception! {
PyTypeError,
PyException,
type_error,
"Inappropriate argument type."
}
#[pyexception(
name,
base = "PyConnectionError",
ctx = "connection_refused_error",
impl
)]
#[derive(Debug)]
pub struct PyConnectionRefusedError {}
define_exception! {
PyValueError,
PyException,
value_error,
"Inappropriate argument value (of correct type)."
}
define_exception! {
PyUnicodeError,
PyValueError,
unicode_error,
"Unicode related error."
}
define_exception! {
PyUnicodeDecodeError,
PyUnicodeError,
unicode_decode_error,
"Unicode decoding error."
}
define_exception! {
PyUnicodeEncodeError,
PyUnicodeError,
unicode_encode_error,
"Unicode encoding error."
}
define_exception! {
PyUnicodeTranslateError,
PyUnicodeError,
unicode_translate_error,
"Unicode translation error."
}
#[pyexception(name, base = "PyConnectionError", ctx = "connection_reset_error", impl)]
#[derive(Debug)]
pub struct PyConnectionResetError {}
#[pyexception(name, base = "PyOSError", ctx = "file_exists_error", impl)]
#[derive(Debug)]
pub struct PyFileExistsError {}
#[pyexception(name, base = "PyOSError", ctx = "file_not_found_error", impl)]
#[derive(Debug)]
pub struct PyFileNotFoundError {}
#[pyexception(name, base = "PyOSError", ctx = "interrupted_error", impl)]
#[derive(Debug)]
pub struct PyInterruptedError {}
#[pyexception(name, base = "PyOSError", ctx = "is_a_directory_error", impl)]
#[derive(Debug)]
pub struct PyIsADirectoryError {}
#[pyexception(name, base = "PyOSError", ctx = "not_a_directory_error", impl)]
#[derive(Debug)]
pub struct PyNotADirectoryError {}
#[pyexception(name, base = "PyOSError", ctx = "permission_error", impl)]
#[derive(Debug)]
pub struct PyPermissionError {}
#[pyexception(name, base = "PyOSError", ctx = "process_lookup_error", impl)]
#[derive(Debug)]
pub struct PyProcessLookupError {}
#[pyexception(name, base = "PyOSError", ctx = "timeout_error", impl)]
#[derive(Debug)]
pub struct PyTimeoutError {}
#[pyexception(name, base = "PyException", ctx = "reference_error", impl)]
#[derive(Debug)]
pub struct PyReferenceError {}
#[pyexception(name, base = "PyException", ctx = "runtime_error", impl)]
#[derive(Debug)]
pub struct PyRuntimeError {}
#[pyexception(name, base = "PyRuntimeError", ctx = "not_implemented_error", impl)]
#[derive(Debug)]
pub struct PyNotImplementedError {}
#[pyexception(name, base = "PyRuntimeError", ctx = "recursion_error", impl)]
#[derive(Debug)]
pub struct PyRecursionError {}
#[pyexception(name, base = "PyException", ctx = "syntax_error", impl)]
#[derive(Debug)]
pub struct PySyntaxError {}
#[pyexception(name, base = "PySyntaxError", ctx = "indentation_error", impl)]
#[derive(Debug)]
pub struct PyIndentationError {}
#[pyexception(name, base = "PyIndentationError", ctx = "tab_error", impl)]
#[derive(Debug)]
pub struct PyTabError {}
#[pyexception(name, base = "PyException", ctx = "system_error", impl)]
#[derive(Debug)]
pub struct PySystemError {}
#[pyexception(name, base = "PyException", ctx = "type_error", impl)]
#[derive(Debug)]
pub struct PyTypeError {}
#[pyexception(name, base = "PyException", ctx = "value_error", impl)]
#[derive(Debug)]
pub struct PyValueError {}
#[pyexception(name, base = "PyValueError", ctx = "unicode_error", impl)]
#[derive(Debug)]
pub struct PyUnicodeError {}
#[pyexception(name, base = "PyUnicodeError", ctx = "unicode_decode_error", impl)]
#[derive(Debug)]
pub struct PyUnicodeDecodeError {}
#[pyexception(name, base = "PyUnicodeError", ctx = "unicode_encode_error", impl)]
#[derive(Debug)]
pub struct PyUnicodeEncodeError {}
#[pyexception(name, base = "PyUnicodeError", ctx = "unicode_translate_error", impl)]
#[derive(Debug)]
pub struct PyUnicodeTranslateError {}
/// JIT error.
#[cfg(feature = "jit")]
define_exception! {
PyJitError,
PyException,
jit_error,
"JIT error."
}
#[pyexception(name, base = "PyException", ctx = "jit_error", impl)]
#[derive(Debug)]
pub struct PyJitError {}
// Warnings
define_exception! {
PyWarning,
PyException,
warning,
"Base class for warning categories."
}
define_exception! {
PyDeprecationWarning,
PyWarning,
deprecation_warning,
"Base class for warnings about deprecated features."
}
define_exception! {
PyPendingDeprecationWarning,
PyWarning,
pending_deprecation_warning,
"Base class for warnings about features which will be deprecated\nin the future."
}
define_exception! {
PyRuntimeWarning,
PyWarning,
runtime_warning,
"Base class for warnings about dubious runtime behavior."
}
define_exception! {
PySyntaxWarning,
PyWarning,
syntax_warning,
"Base class for warnings about dubious syntax."
}
define_exception! {
PyUserWarning,
PyWarning,
user_warning,
"Base class for warnings generated by user code."
}
define_exception! {
PyFutureWarning,
PyWarning,
future_warning,
"Base class for warnings about constructs that will change semantically\nin the future."
}
define_exception! {
PyImportWarning,
PyWarning,
import_warning,
"Base class for warnings about probable mistakes in module imports."
}
define_exception! {
PyUnicodeWarning,
PyWarning,
unicode_warning,
"Base class for warnings about Unicode related problems, mostly\nrelated to conversion problems."
}
define_exception! {
PyBytesWarning,
PyWarning,
bytes_warning,
"Base class for warnings about bytes and buffer related problems, mostly\nrelated to conversion from str or comparing to str."
}
define_exception! {
PyResourceWarning,
PyWarning,
resource_warning,
"Base class for warnings about resource usage."
}
define_exception! {
PyEncodingWarning,
PyWarning,
encoding_warning,
"Base class for warnings about encodings."
}
#[pyexception(name, base = "PyException", ctx = "warning", impl)]
#[derive(Debug)]
pub struct PyWarning {}
#[pyexception(name, base = "PyWarning", ctx = "deprecation_warning", impl)]
#[derive(Debug)]
pub struct PyDeprecationWarning {}
#[pyexception(name, base = "PyWarning", ctx = "pending_deprecation_warning", impl)]
#[derive(Debug)]
pub struct PyPendingDeprecationWarning {}
#[pyexception(name, base = "PyWarning", ctx = "runtime_warning", impl)]
#[derive(Debug)]
pub struct PyRuntimeWarning {}
#[pyexception(name, base = "PyWarning", ctx = "syntax_warning", impl)]
#[derive(Debug)]
pub struct PySyntaxWarning {}
#[pyexception(name, base = "PyWarning", ctx = "user_warning", impl)]
#[derive(Debug)]
pub struct PyUserWarning {}
#[pyexception(name, base = "PyWarning", ctx = "future_warning", impl)]
#[derive(Debug)]
pub struct PyFutureWarning {}
#[pyexception(name, base = "PyWarning", ctx = "import_warning", impl)]
#[derive(Debug)]
pub struct PyImportWarning {}
#[pyexception(name, base = "PyWarning", ctx = "unicode_warning", impl)]
#[derive(Debug)]
pub struct PyUnicodeWarning {}
#[pyexception(name, base = "PyWarning", ctx = "bytes_warning", impl)]
#[derive(Debug)]
pub struct PyBytesWarning {}
#[pyexception(name, base = "PyWarning", ctx = "resource_warning", impl)]
#[derive(Debug)]
pub struct PyResourceWarning {}
#[pyexception(name, base = "PyWarning", ctx = "encoding_warning", impl)]
#[derive(Debug)]
pub struct PyEncodingWarning {}
}
impl ToPyException for ReprOverflowError {

View File

@@ -17,7 +17,8 @@ macro_rules! py_class {
( $ctx:expr, $class_name:expr, $class_base:expr, $flags:expr, { $($name:tt => $value:expr),* $(,)* }) => {
{
#[allow(unused_mut)]
let mut slots = $crate::types::PyTypeSlots::new($class_name, $crate::types::PyTypeFlags::DEFAULT | $flags);
let mut slots = $crate::types::PyTypeSlots::heap_default();
slots.flags = $flags;
$($crate::py_class!(@extract_slots($ctx, &mut slots, $name, $value));)*
let py_class = $ctx.new_class(None, $class_name, $class_base, slots);
$($crate::py_class!(@extract_attrs($ctx, &py_class, $name, $value));)*

View File

@@ -17,7 +17,8 @@ impl PyObject {
/// PyObject_Call*Arg* series
pub fn call(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
self.call_with_args(args.into_args(vm), vm)
let args = args.into_args(vm);
self.call_with_args(args, vm)
}
/// PyObject_Call
@@ -45,8 +46,9 @@ impl<'a> PyCallable<'a> {
}
pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
let args = args.into_args(vm);
vm.trace_event(TraceEvent::Call)?;
let result = (self.call)(self.obj, args.into_args(vm), vm);
let result = (self.call)(self.obj, args, vm);
vm.trace_event(TraceEvent::Return)?;
result
}

View File

@@ -379,7 +379,7 @@ mod _io {
#[pyattr]
#[pyclass(name = "_IOBase")]
#[derive(Debug, PyPayload)]
struct _IOBase;
pub struct _IOBase;
#[pyclass(with(IterNext, Destructor), flags(BASETYPE, HAS_DICT))]
impl _IOBase {
@@ -3637,6 +3637,7 @@ mod _io {
}
pub(super) fn make_unsupportedop(ctx: &Context) -> PyTypeRef {
use crate::types::PyTypeSlots;
PyType::new_heap(
"UnsupportedOperation",
vec![
@@ -3644,7 +3645,7 @@ mod _io {
ctx.exceptions.value_error.to_owned(),
],
Default::default(),
Default::default(),
PyTypeSlots::heap_default(),
ctx.types.type_type.to_owned(),
ctx,
)

View File

@@ -98,6 +98,13 @@ impl PyTypeSlots {
..Default::default()
}
}
pub fn heap_default() -> Self {
Self {
// init: AtomicCell::new(Some(init_wrapper)),
..Default::default()
}
}
}
impl std::fmt::Debug for PyTypeSlots {
@@ -815,10 +822,12 @@ pub trait Callable: PyPayload {
#[inline]
#[pyslot]
fn slot_call(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let zelf = zelf
.downcast_ref()
.ok_or_else(|| vm.new_type_error("unexpected payload for __call__".to_owned()))?;
Self::call(zelf, args.bind(vm)?, vm)
let Some(zelf) = zelf.downcast_ref() else {
let err = vm.new_downcast_type_error(Self::class(&vm.ctx), zelf);
return Err(err);
};
let args = args.bind(vm)?;
Self::call(zelf, args, vm)
}
#[inline]

View File

@@ -85,13 +85,18 @@ impl PyMethod {
}
}
pub(crate) fn get_special(
pub(crate) fn get_special<const DIRECT: bool>(
obj: &PyObject,
name: &'static PyStrInterned,
vm: &VirtualMachine,
) -> PyResult<Option<Self>> {
let obj_cls = obj.class();
let func = match obj_cls.get_attr(name) {
let attr = if DIRECT {
obj_cls.get_direct_attr(name)
} else {
obj_cls.get_attr(name)
};
let func = match attr {
Some(f) => f,
None => {
return Ok(None);

View File

@@ -135,7 +135,7 @@ impl VirtualMachine {
obj: &PyObject,
method: &'static PyStrInterned,
) -> PyResult<Option<PyMethod>> {
PyMethod::get_special(obj, method, self)
PyMethod::get_special::<false>(obj, method, self)
}
/// NOT PUBLIC API

View File

@@ -523,7 +523,7 @@ impl VirtualMachine {
}
pub fn _contains(&self, haystack: &PyObject, needle: PyObjectRef) -> PyResult {
match PyMethod::get_special(haystack, identifier!(self, __contains__), self)? {
match PyMethod::get_special::<false>(haystack, identifier!(self, __contains__), self)? {
Some(method) => method.invoke((needle,), self),
None => self
._membership_iter_search(haystack, needle)