From f256934f93be1417c5b5ea8557e822c029b71671 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 18 Apr 2023 03:43:00 +0900 Subject: [PATCH] Merge define_exception into pyexception --- derive-impl/src/lib.rs | 13 +- derive-impl/src/pyclass.rs | 234 +++--- derive-impl/src/util.rs | 78 ++ derive/src/lib.rs | 16 +- extra_tests/snippets/builtin_exceptions.py | 6 +- vm/src/builtins/type.rs | 7 +- vm/src/class.rs | 18 +- vm/src/exceptions.rs | 805 +++++++++------------ vm/src/macros.rs | 3 +- vm/src/protocol/callable.rs | 6 +- vm/src/stdlib/io.rs | 5 +- vm/src/types/slot.rs | 17 +- vm/src/vm/method.rs | 9 +- vm/src/vm/vm_object.rs | 2 +- vm/src/vm/vm_ops.rs | 2 +- 15 files changed, 603 insertions(+), 618 deletions(-) diff --git a/derive-impl/src/lib.rs b/derive-impl/src/lib.rs index 3b38483419..35292e7de0 100644 --- a/derive-impl/src/lib.rs +++ b/derive-impl/src/lib.rs @@ -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 { diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs index 3eab18d41d..2b8689669d 100644 --- a/derive-impl/src/pyclass.rs +++ b/derive-impl/src/pyclass.rs @@ -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 { +pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result { 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 &'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 Result { - 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 { - 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 { + 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) }, - }; - - let ret = quote! { - #[pyexception(#class_name, #base_class)] - #[derive(Debug)] - #[doc = #docs] - pub struct #class_name {} + if !attr.is_empty() { + return Err(syn::Error::new_spanned( + &attr[0], + "#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.", + )); + } - // 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 + ::Base::slot_new(cls, args, vm) } + } + }; + // 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: PyObjectRef, + zelf: ::rustpython_vm::PyObjectRef, args: ::rustpython_vm::function::FuncArgs, vm: &::rustpython_vm::VirtualMachine, ) -> ::rustpython_vm::PyResult<()> { - #init_method + ::Base::slot_init(zelf, args, vm) } } }; - Ok(ret) + 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, - /// We also store `__init__` magic method, that can - pub init: Option, -} - -impl Parse for PyExceptionDef { - fn parse(input: ParseStream) -> ParsingResult { - let class_name: Ident = input.parse()?; - input.parse::()?; - - let base_class: Ident = input.parse()?; - input.parse::()?; - - let ctx_name: Ident = input.parse()?; - input.parse::()?; - - let docs: LitStr = input.parse()?; - input.parse::>()?; - - let slot_new: Option = input.parse()?; - input.parse::>()?; - - let init: Option = input.parse()?; - input.parse::>()?; // leading `,` - - Ok(PyExceptionDef { - class_name, - base_class, - ctx_name, - docs, - slot_new, - init, - }) - } -} - +#[allow(dead_code)] fn parse_vec_ident( attr: &[NestedMeta], item: &Item, diff --git a/derive-impl/src/util.rs b/derive-impl/src/util.rs index 51bafb7638..97a6aaf097 100644 --- a/derive-impl/src/util.rs +++ b/derive-impl/src/util.rs @@ -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> { + self.inner()._optional_str("ctx") + } + pub fn base(&self) -> Result> { self.inner()._optional_str("base") } @@ -349,6 +354,10 @@ impl ClassItemMeta { Ok(value) } + pub fn impl_attrs(&self) -> Result> { + self.inner()._optional_str("impl") + } + // pub fn mandatory_module(&self) -> Result { // 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 { + 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 { + 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; 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; fn into_result(self) -> Result<()> { diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 4b9dcb4525..76b7e23488 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -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`. /// -/// -/// 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); diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 5f92f044ee..4bff9c0096 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -239,13 +239,13 @@ class SubError(MyError): 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__ == {} diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index fc4a745513..0278a4f67f 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -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), diff --git a/vm/src/class.rs b/vm/src/class.rs index eef9d94523..a9a00c4a8d 100644 --- a/vm/src/class.rs +++ b/vm/src/class.rs @@ -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()); diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 3885dbab98..3762ee5d06 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1162,502 +1162,373 @@ pub(super) mod types { pub(super) args: PyRwLock, } - 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." - } - - // Base `Exception` type - define_exception! { - PyException, - PyBaseException, - exception_type, - "Common base class for all non-exit exceptions." - } - - 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(()) - } - - define_exception! { - PyStopAsyncIteration, - PyException, - stop_async_iteration, - "Signal the end from iterator.__anext__()." + #[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(()) + } } - 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 = "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! { - 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 = "PyImportError", ctx = "module_not_found_error", impl)] + #[derive(Debug)] + pub struct PyModuleNotFoundError {} - 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 = "lookup_error", impl)] + #[derive(Debug)] + pub struct PyLookupError {} - fn base_exception_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - PyBaseException::slot_new(cls, args, vm) - } + #[pyexception(name, base = "PyLookupError", ctx = "index_error", impl)] + #[derive(Debug)] + pub struct PyIndexError {} - 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 = "PyLookupError", ctx = "key_error", impl)] + #[derive(Debug)] + pub struct PyKeyError {} - define_exception! { - PyModuleNotFoundError, - PyImportError, - module_not_found_error, - "Module not found." - } + #[pyexception(name, base = "PyException", ctx = "memory_error", impl)] + #[derive(Debug)] + pub struct PyMemoryError {} - 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." - } + #[pyexception(name, base = "PyException", ctx = "name_error", impl)] + #[derive(Debug)] + pub struct PyNameError {} - define_exception! { - PyMemoryError, - PyException, - memory_error, - "Out of memory." - } + #[pyexception(name, base = "PyNameError", ctx = "unbound_local_error", impl)] + #[derive(Debug)] + pub struct PyUnboundLocalError {} - 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, - vm: &VirtualMachine, - ) -> Option { - let len = args.len(); - if (2..=5).contains(&len) { - let errno = &args[0]; - errno - .payload_if_subclass::(vm) - .and_then(|errno| errno.try_to_primitive::(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 + #[pyexception] + impl PyOSError { + #[cfg(not(target_arch = "wasm32"))] + fn optional_new(args: Vec, vm: &VirtualMachine) -> Option { + let len = args.len(); + if (2..=5).contains(&len) { + let errno = &args[0]; + errno + .payload_if_subclass::(vm) + .and_then(|errno| errno.try_to_primitive::(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), + #[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) } - } else { + } + #[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 {} + + #[pyexception(name, base = "PyOSError", ctx = "child_process_error", impl)] + #[derive(Debug)] + pub struct PyChildProcessError {} + + #[pyexception(name, base = "PyOSError", ctx = "connection_error", impl)] + #[derive(Debug)] + pub struct PyConnectionError {} + + #[pyexception(name, base = "PyConnectionError", ctx = "broken_pipe_error", impl)] + #[derive(Debug)] + pub struct PyBrokenPipeError {} + + #[pyexception( + name, + base = "PyConnectionError", + ctx = "connection_aborted_error", + impl + )] + #[derive(Debug)] + pub struct PyConnectionAbortedError {} + + #[pyexception( + name, + base = "PyConnectionError", + ctx = "connection_refused_error", + impl + )] + #[derive(Debug)] + pub struct PyConnectionRefusedError {} + + #[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")] + #[pyexception(name, base = "PyException", ctx = "jit_error", impl)] + #[derive(Debug)] + pub struct PyJitError {} - define_exception! { - PyReferenceError, - PyException, - reference_error, - "Weak ref proxy used after referent went away." - } + // Warnings + #[pyexception(name, base = "PyException", ctx = "warning", impl)] + #[derive(Debug)] + pub struct PyWarning {} - 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 = "PyWarning", ctx = "deprecation_warning", impl)] + #[derive(Debug)] + pub struct PyDeprecationWarning {} - 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 = "PyWarning", ctx = "pending_deprecation_warning", impl)] + #[derive(Debug)] + pub struct PyPendingDeprecationWarning {} - 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 = "PyWarning", ctx = "runtime_warning", impl)] + #[derive(Debug)] + pub struct PyRuntimeWarning {} - define_exception! { - PyTypeError, - PyException, - type_error, - "Inappropriate argument type." - } + #[pyexception(name, base = "PyWarning", ctx = "syntax_warning", impl)] + #[derive(Debug)] + pub struct PySyntaxWarning {} - 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 = "PyWarning", ctx = "user_warning", impl)] + #[derive(Debug)] + pub struct PyUserWarning {} - #[cfg(feature = "jit")] - define_exception! { - PyJitError, - PyException, - jit_error, - "JIT error." - } + #[pyexception(name, base = "PyWarning", ctx = "future_warning", impl)] + #[derive(Debug)] + pub struct PyFutureWarning {} - // 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 = "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 { diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 5f990d1a4a..2cb255088f 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -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));)* diff --git a/vm/src/protocol/callable.rs b/vm/src/protocol/callable.rs index 481e4559f1..eb404d0214 100644 --- a/vm/src/protocol/callable.rs +++ b/vm/src/protocol/callable.rs @@ -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 } diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 9aa61c6a9a..4b64e677f7 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -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, ) diff --git a/vm/src/types/slot.rs b/vm/src/types/slot.rs index b06db403d8..0b8ddb6fce 100644 --- a/vm/src/types/slot.rs +++ b/vm/src/types/slot.rs @@ -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] diff --git a/vm/src/vm/method.rs b/vm/src/vm/method.rs index 9f55e28f86..3d09a95793 100644 --- a/vm/src/vm/method.rs +++ b/vm/src/vm/method.rs @@ -85,13 +85,18 @@ impl PyMethod { } } - pub(crate) fn get_special( + pub(crate) fn get_special( obj: &PyObject, name: &'static PyStrInterned, vm: &VirtualMachine, ) -> PyResult> { 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); diff --git a/vm/src/vm/vm_object.rs b/vm/src/vm/vm_object.rs index 056ded9bfd..a957ed66ca 100644 --- a/vm/src/vm/vm_object.rs +++ b/vm/src/vm/vm_object.rs @@ -135,7 +135,7 @@ impl VirtualMachine { obj: &PyObject, method: &'static PyStrInterned, ) -> PyResult> { - PyMethod::get_special(obj, method, self) + PyMethod::get_special::(obj, method, self) } /// NOT PUBLIC API diff --git a/vm/src/vm/vm_ops.rs b/vm/src/vm/vm_ops.rs index 65be5d415d..0fc9a1953e 100644 --- a/vm/src/vm/vm_ops.rs +++ b/vm/src/vm/vm_ops.rs @@ -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::(haystack, identifier!(self, __contains__), self)? { Some(method) => method.invoke((needle,), self), None => self ._membership_iter_search(haystack, needle)