From bb72418d9ecdbcad22965ef6b6b5b4851d74d3a7 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 19 Jan 2026 19:02:09 +0900 Subject: [PATCH] _typing.Union --- Lib/copyreg.py | 4 +- crates/vm/src/builtins/genericalias.rs | 6 +- crates/vm/src/builtins/type.rs | 11 +- crates/vm/src/builtins/union.rs | 285 +++++++++++++++++++++---- crates/vm/src/stdlib/_abc.rs | 87 +++----- crates/vm/src/stdlib/typevar.rs | 4 +- crates/vm/src/stdlib/typing.rs | 4 +- crates/vm/src/types/slot.rs | 2 +- 8 files changed, 293 insertions(+), 110 deletions(-) diff --git a/Lib/copyreg.py b/Lib/copyreg.py index 578392409..c9da81a68 100644 --- a/Lib/copyreg.py +++ b/Lib/copyreg.py @@ -31,8 +31,8 @@ def pickle_complex(c): pickle(complex, pickle_complex, complex) def pickle_union(obj): - import functools, operator - return functools.reduce, (operator.or_, obj.__args__) + import typing, operator + return operator.getitem, (typing.Union, obj.__args__) pickle(type(int | str), pickle_union) diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index e9150e4c0..21034e08f 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -235,11 +235,11 @@ impl PyGenericAlias { Err(vm.new_type_error("issubclass() argument 2 cannot be a parameterized generic")) } - fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { type_::or_(other, zelf, vm) } - fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { type_::or_(zelf, other, vm) } } @@ -509,7 +509,7 @@ impl AsMapping for PyGenericAlias { impl AsNumber for PyGenericAlias { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|a, b, vm| Ok(PyGenericAlias::__or__(a.to_owned(), b.to_owned(), vm))), + or: Some(|a, b, vm| PyGenericAlias::__or__(a.to_owned(), b.to_owned(), vm)), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 829f7d443..510c3fb84 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -20,7 +20,6 @@ use crate::{ borrow::BorrowedValue, lock::{PyRwLock, PyRwLockReadGuard}, }, - convert::ToPyResult, function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue}, object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyNumberMethods}, @@ -1038,11 +1037,11 @@ impl PyType { ) } - pub fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { or_(other, zelf, vm) } - pub fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { or_(zelf, other, vm) } @@ -1850,7 +1849,7 @@ impl Callable for PyType { impl AsNumber for PyType { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|a, b, vm| or_(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)), + or: Some(|a, b, vm| or_(a.to_owned(), b.to_owned(), vm)), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER @@ -2013,9 +2012,9 @@ pub(crate) fn call_slot_new( slot_new(subtype, args, vm) } -pub(crate) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !union_::is_unionable(zelf.clone(), vm) || !union_::is_unionable(other.clone(), vm) { - return vm.ctx.not_implemented(); + return Ok(vm.ctx.not_implemented()); } let tuple = PyTuple::new_ref(vec![zelf, other], &vm.ctx); diff --git a/crates/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs index b5e12dcb3..9856235ec 100644 --- a/crates/vm/src/builtins/union.rs +++ b/crates/vm/src/builtins/union.rs @@ -2,10 +2,10 @@ use super::{genericalias, type_}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, - builtins::{PyFrozenSet, PyGenericAlias, PyStr, PyTuple, PyTupleRef, PyType}, + builtins::{PyFrozenSet, PySet, PyStr, PyTuple, PyTupleRef, PyType}, class::PyClassImpl, common::hash, - convert::{ToPyObject, ToPyResult}, + convert::ToPyObject, function::PyComparisonValue, protocol::{PyMappingMethods, PyNumberMethods}, stdlib::typing::TypeAliasType, @@ -16,9 +16,13 @@ use std::sync::LazyLock; const CLS_ATTRS: &[&str] = &["__module__"]; -#[pyclass(module = "types", name = "UnionType", traverse)] +#[pyclass(module = "typing", name = "Union", traverse)] pub struct PyUnion { args: PyTupleRef, + /// Frozenset of hashable args, or None if all args were hashable + hashable_args: Option>, + /// Tuple of initially unhashable args, or None if all args were hashable + unhashable_args: Option, parameters: PyTupleRef, } @@ -36,9 +40,15 @@ impl PyPayload for PyUnion { } impl PyUnion { - pub fn new(args: PyTupleRef, vm: &VirtualMachine) -> Self { - let parameters = make_parameters(&args, vm); - Self { args, parameters } + /// Create a new union from dedup result (internal use) + fn from_components(result: UnionComponents, vm: &VirtualMachine) -> PyResult { + let parameters = make_parameters(&result.args, vm)?; + Ok(Self { + args: result.args, + hashable_args: result.hashable_args, + unhashable_args: result.unhashable_args, + parameters, + }) } /// Direct access to args field, matching CPython's _Py_union_args @@ -88,10 +98,25 @@ impl PyUnion { } #[pyclass( - flags(BASETYPE), + flags(DISALLOW_INSTANTIATION), with(Hashable, Comparable, AsMapping, AsNumber, Representable) )] impl PyUnion { + #[pygetset] + fn __name__(&self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.new_str("Union").into() + } + + #[pygetset] + fn __qualname__(&self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.new_str("Union").into() + } + + #[pygetset] + fn __origin__(&self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.types.union_type.to_owned().into() + } + #[pygetset] fn __parameters__(&self) -> PyObjectRef { self.parameters.clone().into() @@ -136,17 +161,35 @@ impl PyUnion { } } - fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { type_::or_(zelf, other, vm) } + #[pymethod] + fn __mro_entries__(zelf: PyRef, _args: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error(format!("Cannot subclass {}", zelf.repr(vm)?))) + } + #[pyclassmethod] fn __class_getitem__( - cls: crate::builtins::PyTypeRef, + _cls: crate::builtins::PyTypeRef, args: PyObjectRef, vm: &VirtualMachine, - ) -> PyGenericAlias { - PyGenericAlias::from_args(cls, args, vm) + ) -> PyResult { + // Convert args to tuple if not already + let args_tuple = if let Some(tuple) = args.downcast_ref::() { + tuple.to_owned() + } else { + PyTuple::new_ref(vec![args], &vm.ctx) + }; + + // Check for empty union + if args_tuple.is_empty() { + return Err(vm.new_type_error("Cannot create empty Union")); + } + + // Create union using make_union to properly handle None -> NoneType conversion + make_union(&args_tuple, vm) } } @@ -159,9 +202,10 @@ pub fn is_unionable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { || obj.downcast_ref::().is_some() } -fn make_parameters(args: &Py, vm: &VirtualMachine) -> PyTupleRef { +fn make_parameters(args: &Py, vm: &VirtualMachine) -> PyResult { let parameters = genericalias::make_parameters(args, vm); - dedup_and_flatten_args(¶meters, vm) + let result = dedup_and_flatten_args(¶meters, vm)?; + Ok(result.args) } fn flatten_args(args: &Py, vm: &VirtualMachine) -> PyTupleRef { @@ -180,6 +224,12 @@ fn flatten_args(args: &Py, vm: &VirtualMachine) -> PyTupleRef { flattened_args.extend(pyref.args.iter().cloned()); } else if vm.is_none(arg) { flattened_args.push(vm.ctx.types.none_type.to_owned().into()); + } else if arg.downcast_ref::().is_some() { + // Convert string to ForwardRef + match string_to_forwardref(arg.clone(), vm) { + Ok(fr) => flattened_args.push(fr), + Err(_) => flattened_args.push(arg.clone()), + } } else { flattened_args.push(arg.clone()); }; @@ -188,31 +238,105 @@ fn flatten_args(args: &Py, vm: &VirtualMachine) -> PyTupleRef { PyTuple::new_ref(flattened_args, &vm.ctx) } -fn dedup_and_flatten_args(args: &Py, vm: &VirtualMachine) -> PyTupleRef { +fn string_to_forwardref(arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Import annotationlib.ForwardRef and create a ForwardRef + let annotationlib = vm.import("annotationlib", 0)?; + let forwardref_cls = annotationlib.get_attr("ForwardRef", vm)?; + forwardref_cls.call((arg,), vm) +} + +/// Components for creating a PyUnion after deduplication +struct UnionComponents { + /// All unique args in order + args: PyTupleRef, + /// Frozenset of hashable args (for fast equality comparison) + hashable_args: Option>, + /// Tuple of unhashable args at creation time (for hash error message) + unhashable_args: Option, +} + +fn dedup_and_flatten_args(args: &Py, vm: &VirtualMachine) -> PyResult { let args = flatten_args(args, vm); + // Use set-based deduplication like CPython: + // - For hashable elements: use Python's set semantics (hash + equality) + // - For unhashable elements: use equality comparison + // + // This avoids calling __eq__ when hashes differ, matching CPython behavior + // where `int | BadType` doesn't raise even if BadType.__eq__ raises. + let mut new_args: Vec = Vec::with_capacity(args.len()); + + // Track hashable elements using a Python set (uses hash + equality) + let hashable_set = PySet::default().into_ref(&vm.ctx); + let mut hashable_list: Vec = Vec::new(); + let mut unhashable_list: Vec = Vec::new(); + for arg in &*args { - if !new_args.iter().any(|param| { - param - .rich_compare_bool(arg, PyComparisonOp::Eq, vm) - .unwrap_or_default() - }) { - new_args.push(arg.clone()); + // Try to hash the element first + match arg.hash(vm) { + Ok(_) => { + // Element is hashable - use set for deduplication + // Set membership uses hash first, then equality only if hashes match + let contains = vm + .call_method(hashable_set.as_ref(), "__contains__", (arg.clone(),)) + .and_then(|r| r.try_to_bool(vm))?; + if !contains { + hashable_set.add(arg.clone(), vm)?; + hashable_list.push(arg.clone()); + new_args.push(arg.clone()); + } + } + Err(_) => { + // Element is unhashable - use equality comparison + let mut is_duplicate = false; + for existing in &unhashable_list { + match existing.rich_compare_bool(arg, PyComparisonOp::Eq, vm) { + Ok(true) => { + is_duplicate = true; + break; + } + Ok(false) => continue, + Err(e) => return Err(e), + } + } + if !is_duplicate { + unhashable_list.push(arg.clone()); + new_args.push(arg.clone()); + } + } } } new_args.shrink_to_fit(); - PyTuple::new_ref(new_args, &vm.ctx) + // Create hashable_args frozenset if there are hashable elements + let hashable_args = if !hashable_list.is_empty() { + Some(PyFrozenSet::from_iter(vm, hashable_list.into_iter())?.into_ref(&vm.ctx)) + } else { + None + }; + + // Create unhashable_args tuple if there are unhashable elements + let unhashable_args = if !unhashable_list.is_empty() { + Some(PyTuple::new_ref(unhashable_list, &vm.ctx)) + } else { + None + }; + + Ok(UnionComponents { + args: PyTuple::new_ref(new_args, &vm.ctx), + hashable_args, + unhashable_args, + }) } -pub fn make_union(args: &Py, vm: &VirtualMachine) -> PyObjectRef { - let args = dedup_and_flatten_args(args, vm); - match args.len() { - 1 => args[0].to_owned(), - _ => PyUnion::new(args, vm).to_pyobject(vm), - } +pub fn make_union(args: &Py, vm: &VirtualMachine) -> PyResult { + let result = dedup_and_flatten_args(args, vm)?; + Ok(match result.args.len() { + 1 => result.args[0].to_owned(), + _ => PyUnion::from_components(result, vm)?.to_pyobject(vm), + }) } impl PyUnion { @@ -224,14 +348,15 @@ impl PyUnion { needle, vm, )?; - let mut res; + let res; if new_args.is_empty() { - res = make_union(&new_args, vm); + res = make_union(&new_args, vm)?; } else { - res = new_args[0].to_owned(); + let mut tmp = new_args[0].to_owned(); for arg in new_args.iter().skip(1) { - res = vm._or(&res, arg)?; + tmp = vm._or(&tmp, arg)?; } + res = tmp; } Ok(res) @@ -254,7 +379,7 @@ impl AsMapping for PyUnion { impl AsNumber for PyUnion { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|a, b, vm| PyUnion::__or__(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)), + or: Some(|a, b, vm| PyUnion::__or__(a.to_owned(), b.to_owned(), vm)), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER @@ -270,15 +395,62 @@ impl Comparable for PyUnion { ) -> PyResult { op.eq_only(|| { let other = class_or_notimplemented!(Self, other); - let a = PyFrozenSet::from_iter(vm, zelf.args.into_iter().cloned())?; - let b = PyFrozenSet::from_iter(vm, other.args.into_iter().cloned())?; - Ok(PyComparisonValue::Implemented( - a.into_pyobject(vm).as_object().rich_compare_bool( - b.into_pyobject(vm).as_object(), - PyComparisonOp::Eq, - vm, - )?, - )) + + // Check if lengths are equal + if zelf.args.len() != other.args.len() { + return Ok(PyComparisonValue::Implemented(false)); + } + + // Fast path: if both unions have all hashable args, compare frozensets directly + // Always use Eq here since eq_only handles Ne by negating the result + if zelf.unhashable_args.is_none() + && other.unhashable_args.is_none() + && let (Some(a), Some(b)) = (&zelf.hashable_args, &other.hashable_args) + { + let eq = a + .as_object() + .rich_compare_bool(b.as_object(), PyComparisonOp::Eq, vm)?; + return Ok(PyComparisonValue::Implemented(eq)); + } + + // Slow path: O(n^2) nested loop comparison for unhashable elements + // Check if all elements in zelf.args are in other.args + for arg_a in &*zelf.args { + let mut found = false; + for arg_b in &*other.args { + match arg_a.rich_compare_bool(arg_b, PyComparisonOp::Eq, vm) { + Ok(true) => { + found = true; + break; + } + Ok(false) => continue, + Err(e) => return Err(e), // Propagate comparison errors + } + } + if !found { + return Ok(PyComparisonValue::Implemented(false)); + } + } + + // Check if all elements in other.args are in zelf.args (for symmetry) + for arg_b in &*other.args { + let mut found = false; + for arg_a in &*zelf.args { + match arg_b.rich_compare_bool(arg_a, PyComparisonOp::Eq, vm) { + Ok(true) => { + found = true; + break; + } + Ok(false) => continue, + Err(e) => return Err(e), // Propagate comparison errors + } + } + if !found { + return Ok(PyComparisonValue::Implemented(false)); + } + } + + Ok(PyComparisonValue::Implemented(true)) }) } } @@ -286,7 +458,36 @@ impl Comparable for PyUnion { impl Hashable for PyUnion { #[inline] fn hash(zelf: &Py, vm: &VirtualMachine) -> PyResult { - let set = PyFrozenSet::from_iter(vm, zelf.args.into_iter().cloned())?; + // If there are any unhashable args from creation time, the union is unhashable + if let Some(ref unhashable_args) = zelf.unhashable_args { + let n = unhashable_args.len(); + // Try to hash each previously unhashable arg to get an error + for arg in unhashable_args.iter() { + arg.hash(vm)?; + } + // All previously unhashable args somehow became hashable + // But still raise an error to maintain consistent hashing + return Err(vm.new_type_error(format!( + "union contains {} unhashable element{}", + n, + if n > 1 { "s" } else { "" } + ))); + } + + // If we have a stored frozenset of hashable args, use that + if let Some(ref hashable_args) = zelf.hashable_args { + return PyFrozenSet::hash(hashable_args, vm); + } + + // Fallback: compute hash from args + let mut args_to_hash = Vec::new(); + for arg in &*zelf.args { + match arg.hash(vm) { + Ok(_) => args_to_hash.push(arg.clone()), + Err(e) => return Err(e), + } + } + let set = PyFrozenSet::from_iter(vm, args_to_hash.into_iter())?; PyFrozenSet::hash(&set.into_ref(&vm.ctx), vm) } } diff --git a/crates/vm/src/stdlib/_abc.rs b/crates/vm/src/stdlib/_abc.rs index 06b403206..ef528d957 100644 --- a/crates/vm/src/stdlib/_abc.rs +++ b/crates/vm/src/stdlib/_abc.rs @@ -149,29 +149,24 @@ mod _abc { let items = vm.call_method(&ns, "items", ())?; let iter = items.get_iter(vm)?; - loop { - match iter.next(vm)? { - PyIterReturn::Return(item) => { - let tuple: PyTupleRef = item - .downcast() - .map_err(|_| vm.new_type_error("items() returned non-tuple".to_owned()))?; - let elements = tuple.as_slice(); - if elements.len() != 2 { - return Err(vm.new_type_error( - "items() returned item which size is not 2".to_owned(), - )); - } - let key = &elements[0]; - let value = &elements[1]; + while let PyIterReturn::Return(item) = iter.next(vm)? { + let tuple: PyTupleRef = item + .downcast() + .map_err(|_| vm.new_type_error("items() returned non-tuple".to_owned()))?; + let elements = tuple.as_slice(); + if elements.len() != 2 { + return Err( + vm.new_type_error("items() returned item which size is not 2".to_owned()) + ); + } + let key = &elements[0]; + let value = &elements[1]; - // Check if value has __isabstractmethod__ = True - if let Ok(is_abstract) = value.get_attr("__isabstractmethod__", vm) { - if is_abstract.try_to_bool(vm)? { - abstracts.push(key.clone()); - } - } - } - PyIterReturn::StopIteration(_) => break, + // Check if value has __isabstractmethod__ = True + if let Ok(is_abstract) = value.get_attr("__isabstractmethod__", vm) + && is_abstract.try_to_bool(vm)? + { + abstracts.push(key.clone()); } } @@ -184,25 +179,14 @@ mod _abc { for base in bases.iter() { if let Ok(base_abstracts) = base.get_attr("__abstractmethods__", vm) { let iter = base_abstracts.get_iter(vm)?; - loop { - match iter.next(vm)? { - PyIterReturn::Return(key) => { - // Try to get the attribute from cls - key should be a string - if let Some(key_str) = key.downcast_ref::() { - if let Some(value) = - vm.get_attribute_opt(cls.to_owned(), key_str)? - { - if let Ok(is_abstract) = - value.get_attr("__isabstractmethod__", vm) - { - if is_abstract.try_to_bool(vm)? { - abstracts.push(key); - } - } - } - } - } - PyIterReturn::StopIteration(_) => break, + while let PyIterReturn::Return(key) = iter.next(vm)? { + // Try to get the attribute from cls - key should be a string + if let Some(key_str) = key.downcast_ref::() + && let Some(value) = vm.get_attribute_opt(cls.to_owned(), key_str)? + && let Ok(is_abstract) = value.get_attr("__isabstractmethod__", vm) + && is_abstract.try_to_bool(vm)? + { + abstracts.push(key); } } } @@ -279,10 +263,10 @@ mod _abc { let subtype: PyObjectRef = instance.class().to_owned().into(); if subtype.is(&subclass) { let invalidation_counter = get_invalidation_counter(); - if impl_data.get_cache_version() == invalidation_counter { - if in_weak_set(&impl_data.negative_cache, &subclass, vm)? { - return Ok(vm.ctx.false_value.clone().into()); - } + if impl_data.get_cache_version() == invalidation_counter + && in_weak_set(&impl_data.negative_cache, &subclass, vm)? + { + return Ok(vm.ctx.false_value.clone().into()); } // Fall back to __subclasscheck__ return vm.call_method(&cls, "__subclasscheck__", (subclass,)); @@ -323,13 +307,12 @@ mod _abc { let registry_copy = PyFrozenSet::from_iter(vm, registry.elements().into_iter())?; for weak_ref_obj in registry_copy.elements() { - if let Ok(weak_ref) = weak_ref_obj.downcast::() { - if let Some(rkey) = weak_ref.upgrade() { - if subclass.to_owned().is_subclass(&rkey, vm)? { - add_to_weak_set(&impl_data.cache, subclass, vm)?; - return Ok(Some(true)); - } - } + if let Ok(weak_ref) = weak_ref_obj.downcast::() + && let Some(rkey) = weak_ref.upgrade() + && subclass.to_owned().is_subclass(&rkey, vm)? + { + add_to_weak_set(&impl_data.cache, subclass, vm)?; + return Ok(Some(true)); } } diff --git a/crates/vm/src/stdlib/typevar.rs b/crates/vm/src/stdlib/typevar.rs index 5d5b1fc7b..e83eaf835 100644 --- a/crates/vm/src/stdlib/typevar.rs +++ b/crates/vm/src/stdlib/typevar.rs @@ -251,7 +251,7 @@ impl AsNumber for TypeVar { static AS_NUMBER: PyNumberMethods = PyNumberMethods { or: Some(|a, b, vm| { let args = PyTuple::new_ref(vec![a.to_owned(), b.to_owned()], &vm.ctx); - Ok(make_union(&args, vm)) + make_union(&args, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED }; @@ -527,7 +527,7 @@ impl AsNumber for ParamSpec { static AS_NUMBER: PyNumberMethods = PyNumberMethods { or: Some(|a, b, vm| { let args = PyTuple::new_ref(vec![a.to_owned(), b.to_owned()], &vm.ctx); - Ok(make_union(&args, vm)) + make_union(&args, vm) }), ..PyNumberMethods::NOT_IMPLEMENTED }; diff --git a/crates/vm/src/stdlib/typing.rs b/crates/vm/src/stdlib/typing.rs index f11acce34..b8048b16d 100644 --- a/crates/vm/src/stdlib/typing.rs +++ b/crates/vm/src/stdlib/typing.rs @@ -28,6 +28,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { "ParamSpecArgs" => ParamSpecArgs::class(&vm.ctx).to_owned(), "ParamSpecKwargs" => ParamSpecKwargs::class(&vm.ctx).to_owned(), "Generic" => Generic::class(&vm.ctx).to_owned(), + "Union" => vm.ctx.types.union_type.to_owned(), }); module } @@ -37,7 +38,6 @@ pub(crate) mod decl { use crate::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr, type_}, - convert::ToPyResult, function::{FuncArgs, IntoFuncArgs}, protocol::PyNumberMethods, types::{AsNumber, Constructor, Representable}, @@ -188,7 +188,7 @@ pub(crate) mod decl { impl AsNumber for TypeAliasType { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|a, b, vm| type_::or_(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)), + or: Some(|a, b, vm| type_::or_(a.to_owned(), b.to_owned(), vm)), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index b637bfc40..2f45d9dcf 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -483,7 +483,7 @@ fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult { /// Marks a type as unhashable. Similar to PyObject_HashNotImplemented in CPython pub fn hash_not_implemented(zelf: &PyObject, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error(format!("unhashable type: {}", zelf.class().name()))) + Err(vm.new_type_error(format!("unhashable type: '{}'", zelf.class().name()))) } fn call_wrapper(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {