diff --git a/crates/vm/src/buffer.rs b/crates/vm/src/buffer.rs index 9a15598ee..c3ad10e89 100644 --- a/crates/vm/src/buffer.rs +++ b/crates/vm/src/buffer.rs @@ -18,7 +18,7 @@ type UnpackFunc = fn(&VirtualMachine, &[u8]) -> PyObjectRef; static OVERFLOW_MSG: &str = "total struct size too long"; // not a const to reduce code size -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Endianness { Native, Little, @@ -40,7 +40,12 @@ impl Endianness { Some(b'>' | b'!') => Self::Big, _ => return Self::Native, }; - chars.next().unwrap(); + + // SAFETY: + // We just ensured with `chars.peek()` that this is safe + unsafe { + let _ = chars.next().unwrap_unchecked(); + } e } } @@ -48,13 +53,17 @@ impl Endianness { trait ByteOrder { fn convert(i: I) -> I; } + enum BigEndian {} + impl ByteOrder for BigEndian { fn convert(i: I) -> I { i.to_be() } } + enum LittleEndian {} + impl ByteOrder for LittleEndian { fn convert(i: I) -> I { i.to_le() @@ -66,7 +75,7 @@ type NativeEndian = cfg_select! { target_endian = "little" => LittleEndian, }; -#[derive(Copy, Clone, num_enum::TryFromPrimitive)] +#[derive(Copy, Clone, num_enum::TryFromPrimitive, Eq, PartialEq)] #[repr(u8)] pub(crate) enum FormatType { Pad = b'x', @@ -105,6 +114,7 @@ impl FormatType { fn info(self, e: Endianness) -> &'static FormatInfo { use FormatType::*; use mem::{align_of, size_of}; + macro_rules! native_info { ($t:ty) => {{ &FormatInfo { @@ -115,6 +125,7 @@ impl FormatType { } }}; } + macro_rules! nonnative_info { ($t:ty, $end:ty) => {{ &FormatInfo { @@ -125,6 +136,7 @@ impl FormatType { } }}; } + macro_rules! match_nonnative { ($zelf:expr, $end:ty) => {{ match $zelf { @@ -158,6 +170,7 @@ impl FormatType { } }}; } + match e { Endianness::Native => match self { Pad | Str | Pascal => &FormatInfo { @@ -381,6 +394,7 @@ pub(crate) struct FormatInfo { pub pack: Option, pub unpack: Option, } + impl fmt::Debug for FormatInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FormatInfo") diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index a2bab0acf..4bb980d71 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -37,8 +37,7 @@ impl PyObjectRef { pub fn try_to_bool(self, vm: &VirtualMachine) -> PyResult { if self.is(&vm.ctx.true_value) { return Ok(true); - } - if self.is(&vm.ctx.false_value) { + } else if self.is(&vm.ctx.false_value) { return Ok(false); } @@ -83,10 +82,9 @@ impl Constructor for PyBool { fn slot_new(zelf: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { let x: Self::Args = args.bind(vm)?; if !zelf.fast_isinstance(vm.ctx.types.type_type) { - let actual_class = zelf.class(); - let actual_type = &actual_class.name(); return Err(vm.new_type_error(format!( - "requires a 'type' object but received a '{actual_type}'" + "requires a 'type' object but received a '{}'", + zelf.class().name() ))); } let val = x.map_or(Ok(false), |val| val.try_to_bool(vm))?; diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 62af01766..0cb16b235 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -43,19 +43,19 @@ pub(crate) mod stack_analysis { } impl Kind { - fn from_i64(v: i64) -> Option { - match v { - 1 => Some(Self::Iterator), - 2 => Some(Self::Except), - 3 => Some(Self::Object), - 4 => Some(Self::Null), - 5 => Some(Self::Lasti), - _ => None, - } + const fn from_i64(v: i64) -> Option { + Some(match v { + 1 => Self::Iterator, + 2 => Self::Except, + 3 => Self::Object, + 4 => Self::Null, + 5 => Self::Lasti, + _ => return None, + }) } } - pub(crate) fn push_value(stack: i64, kind: i64) -> i64 { + pub(crate) const fn push_value(stack: i64, kind: i64) -> i64 { if (stack as u64) >= WILL_OVERFLOW { OVERFLOWED } else { @@ -63,20 +63,20 @@ pub(crate) mod stack_analysis { } } - pub(crate) fn pop_value(stack: i64) -> i64 { + pub(crate) const fn pop_value(stack: i64) -> i64 { stack >> BITS_PER_BLOCK } - pub(crate) fn top_of_stack(stack: i64) -> i64 { + pub(crate) const fn top_of_stack(stack: i64) -> i64 { stack & MASK } - fn peek(stack: i64, n: u32) -> i64 { + const fn peek(stack: i64, n: u32) -> i64 { debug_assert!(n >= 1); (stack >> (BITS_PER_BLOCK * (n - 1))) & MASK } - fn stack_swap(stack: i64, n: u32) -> i64 { + const fn stack_swap(stack: i64, n: u32) -> i64 { debug_assert!(n >= 1); let to_swap = peek(stack, n); let top = top_of_stack(stack); @@ -85,7 +85,7 @@ pub(crate) mod stack_analysis { (replaced_low & !MASK) | to_swap } - fn pop_to_level(mut stack: i64, level: u32) -> i64 { + const fn pop_to_level(mut stack: i64, level: u32) -> i64 { if level == 0 { return EMPTY_STACK; } @@ -97,20 +97,21 @@ pub(crate) mod stack_analysis { stack } - fn compatible_kind(from: i64, to: i64) -> bool { + #[must_use] + const fn compatible_kind(from: i64, to: i64) -> bool { if to == 0 { - return false; + false + } else if to == Kind::Object as i64 { + from != Kind::Null as i64 + } else if to == Kind::Null as i64 { + true + } else { + from == to } - if to == Kind::Object as i64 { - return from != Kind::Null as i64; - } - if to == Kind::Null as i64 { - return true; - } - from == to } - pub(crate) fn compatible_stack(from_stack: i64, to_stack: i64) -> bool { + #[must_use] + pub(crate) const fn compatible_stack(from_stack: i64, to_stack: i64) -> bool { if from_stack < 0 || to_stack < 0 { return false; } @@ -131,14 +132,17 @@ pub(crate) mod stack_analysis { to == 0 } - pub(crate) fn explain_incompatible_stack(to_stack: i64) -> &'static str { + pub(crate) const fn explain_incompatible_stack(to_stack: i64) -> &'static str { debug_assert!(to_stack != 0); + if to_stack == OVERFLOWED { return "stack is too deep to analyze"; } + if to_stack == UNINITIALIZED { return "can't jump into an exception handler, or code may be unreachable"; } + match Kind::from_i64(top_of_stack(to_stack)) { Some(Kind::Except) => "can't jump into an 'except' block as there's no exception", Some(Kind::Lasti) => "can't jump into a re-raising block as there's no location", diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 5b12401f5..bea842811 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -33,11 +33,13 @@ fn format_missing_args( missing: &mut Vec, ) -> String { let count = missing.len(); + let last = if missing.len() > 1 { missing.pop() } else { None }; + let (and, right): (&str, String) = if let Some(last) = last { ( if missing.len() == 1 { @@ -45,11 +47,12 @@ fn format_missing_args( } else { "', and '" }, - format!("{last}"), + last.to_string(), ) } else { ("", String::new()) }; + format!( "{qualname}() missing {count} required {kind} argument{}: '{}{}{right}'", if count == 1 { "" } else { "s" }, @@ -1268,12 +1271,12 @@ impl PyBoundMethod { } #[inline] - pub(crate) fn function_obj(&self) -> &PyObjectRef { + pub(crate) const fn function_obj(&self) -> &PyObjectRef { &self.function } #[inline] - pub(crate) fn self_obj(&self) -> &PyObjectRef { + pub(crate) const fn self_obj(&self) -> &PyObjectRef { &self.object } @@ -1398,6 +1401,7 @@ impl Representable for PyBoundMethod { pub(crate) struct PyCell { contents: PyMutex>, } + pub(crate) type PyCellRef = PyRef; impl PyPayload for PyCell { @@ -1426,6 +1430,7 @@ impl PyCell { pub(crate) fn get(&self) -> Option { self.contents.lock().clone() } + pub(crate) fn set(&self, x: Option) { *self.contents.lock() = x; } @@ -1435,6 +1440,7 @@ impl PyCell { self.get() .ok_or_else(|| vm.new_value_error("Cell is empty")) } + #[pygetset(setter)] fn set_cell_contents(&self, x: PySetterValue) { match x { @@ -1488,6 +1494,7 @@ pub(crate) fn vectorcall_function( args.truncate(nargs); FuncArgs::from(args) }; + zelf.invoke(func_args, vm) } diff --git a/crates/vm/src/builtins/super.rs b/crates/vm/src/builtins/super.rs index f75b9b363..c44b61d71 100644 --- a/crates/vm/src/builtins/super.rs +++ b/crates/vm/src/builtins/super.rs @@ -86,6 +86,7 @@ impl Initializer for PySuper { if frame.code.arg_count == 0 { return Err(vm.new_runtime_error("super(): no arguments")); } + // SAFETY: Frame is current and not concurrently mutated. use rustpython_compiler_core::bytecode::CO_FAST_CELL; let obj = unsafe { frame.fastlocals() }[0] @@ -165,9 +166,9 @@ impl GetAttr for PySuper { Some(o) => o.clone(), None => return skip(zelf, name), }; + // We want __class__ to return the class of the super object // (i.e. super, or a subclass), not the class of su->obj. - if name.as_bytes() == b"__class__" { return skip(zelf, name); } @@ -280,21 +281,23 @@ pub(crate) fn init(context: &'static Context) { let super_type = &context.types.super_type; PySuper::extend_class(context, super_type); - let super_doc = "super() -> same as super(__class__, )\n\ - super(type) -> unbound super object\n\ - super(type, obj) -> bound super object; requires isinstance(obj, type)\n\ - super(type, type2) -> bound super object; requires issubclass(type2, type)\n\ - Typical use to call a cooperative superclass method:\n\ - class C(B):\n \ - def meth(self, arg):\n \ - super().meth(arg)\n\ - This works for class methods too:\n\ - class C(B):\n \ - @classmethod\n \ - def cmeth(cls, arg):\n \ - super().cmeth(arg)\n"; + const SUPER_DOC: &str = "\ +super() -> same as super(__class__, ) +super(type) -> unbound super object +super(type, obj) -> bound super object; requires isinstance(obj, type) +super(type, type2) -> bound super object; requires issubclass(type2, type) +Typical use to call a cooperative superclass method: +class C(B): + def meth(self, arg): + super().meth(arg) +This works for class methods too: +class C(B): + @classmethod + def cmeth(cls, arg): + super().cmeth(arg) +"; extend_class!(context, super_type, { - "__doc__" => context.new_str(super_doc), + "__doc__" => context.new_str(SUPER_DOC), }); } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 81e7ce6f9..4606509fd 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -1,3 +1,5 @@ +// cspell:ignore pyhash + use super::{ PositionIterInternal, PyGenericAlias, PyStrRef, PyType, PyTypeRef, iter::builtins_iter, }; @@ -296,13 +298,13 @@ impl PyTuple { #[inline] #[must_use] - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.elements.len() } #[inline] #[must_use] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.elements.is_empty() } @@ -725,23 +727,29 @@ pub(crate) fn init(context: &'static Context) { } pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyResult { - #[cfg(target_pointer_width = "64")] - const PRIME1: PyUHash = 11400714785074694791; - #[cfg(target_pointer_width = "64")] - const PRIME2: PyUHash = 14029467366897019727; - #[cfg(target_pointer_width = "64")] - const PRIME5: PyUHash = 2870177450012600261; - #[cfg(target_pointer_width = "64")] - const ROTATE: u32 = 31; + const PRIME1: PyUHash = cfg_select! { + target_pointer_width = "64" => 11400714785074694791, + target_pointer_width = "32" => 2654435761, + _ => unreachable!(), + }; - #[cfg(target_pointer_width = "32")] - const PRIME1: PyUHash = 2654435761; - #[cfg(target_pointer_width = "32")] - const PRIME2: PyUHash = 2246822519; - #[cfg(target_pointer_width = "32")] - const PRIME5: PyUHash = 374761393; - #[cfg(target_pointer_width = "32")] - const ROTATE: u32 = 13; + const PRIME2: PyUHash = cfg_select! { + target_pointer_width = "64" => 14029467366897019727, + target_pointer_width = "32" => 2246822519, + _ => unreachable!(), + }; + + const PRIME5: PyUHash = cfg_select! { + target_pointer_width = "64" => 2870177450012600261, + target_pointer_width = "32" => 374761393, + _ => unreachable!(), + }; + + const ROTATE: u32 = cfg_select! { + target_pointer_width = "64" => 31, + target_pointer_width = "32" => 13, + _ => unreachable!(), + }; let mut acc = PRIME5; let len = elements.len() as PyUHash; @@ -755,8 +763,10 @@ pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyRes acc = acc.wrapping_add(len ^ (PRIME5 ^ 3527539)); - if acc as PyHash == -1 { + let acc_pyhash = acc as PyHash; + if acc_pyhash == -1 { return Ok(1546275796); } - Ok(acc as PyHash) + + Ok(acc_pyhash) } diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 8cdc206db..077e8c349 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -136,7 +136,7 @@ impl IterNext for PyWeakProxy { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { let obj = zelf.try_upgrade(vm)?; if obj.class().slots.iternext.load().is_none() { - return Err(vm.new_type_error("Weakref proxy referenced a non-iterator".to_owned())); + return Err(vm.new_type_error("Weakref proxy referenced a non-iterator")); } PyIter::new(obj).next(vm) } diff --git a/crates/vm/src/builtins/weakref.rs b/crates/vm/src/builtins/weakref.rs index c7c4466a1..dbf2941c2 100644 --- a/crates/vm/src/builtins/weakref.rs +++ b/crates/vm/src/builtins/weakref.rs @@ -32,6 +32,7 @@ impl PyPayload for PyWeak { impl Callable for PyWeak { type Args = (); + #[inline] fn call(zelf: &Py, _: Self::Args, vm: &VirtualMachine) -> PyResult { Ok(vm.unwrap_or_none(zelf.upgrade())) @@ -50,10 +51,10 @@ impl Constructor for PyWeak { .ok_or_else(|| vm.new_type_error("__new__ expected at least 1 argument, got 0"))?; let callback = positional.next(); if let Some(_extra) = positional.next() { - return Err(vm.new_type_error(format!( - "__new__ expected at most 2 arguments, got {}", - 3 + positional.count() - ))); + let got = positional.count() + 3; + return Err( + vm.new_type_error(format!("__new__ expected at most 2 arguments, got {got}")) + ); } let weak = referent.downgrade_with_typ(callback, cls, vm)?; Ok(weak.into()) @@ -151,7 +152,7 @@ impl Representable for PyWeak { #[inline] fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { let id = zelf.get_id(); - let repr = if let Some(o) = zelf.upgrade() { + Ok(if let Some(o) = zelf.upgrade() { format!( "", id, @@ -160,8 +161,7 @@ impl Representable for PyWeak { ) } else { format!("") - }; - Ok(repr) + }) } }