Handle unions with GenericAlias subclasses without TypeError (#6540)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jeong YunWon <jeong@youknowone.org>
This commit is contained in:
Copilot
2025-12-27 01:13:20 +09:00
committed by GitHub
parent b704f42158
commit a7d7f81ca7
5 changed files with 27 additions and 10 deletions

View File

@@ -838,8 +838,6 @@ class UnionTests(unittest.TestCase):
self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T])
self.assertEqual((list[T] | list[S])[int, int], list[int])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_union_parameter_substitution(self):
def eq(actual, expected, typed=True):
self.assertEqual(actual, expected)

View File

@@ -1963,7 +1963,7 @@ pub(crate) fn call_slot_new(
slot_new(subtype, args, vm)
}
pub(super) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
pub(crate) fn or_(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if !union_::is_unionable(zelf.clone(), vm) || !union_::is_unionable(other.clone(), vm) {
return vm.ctx.not_implemented();
}

View File

@@ -8,6 +8,7 @@ use crate::{
convert::{ToPyObject, ToPyResult},
function::PyComparisonValue,
protocol::{PyMappingMethods, PyNumberMethods},
stdlib::typing::TypeAliasType,
types::{AsMapping, AsNumber, Comparable, GetAttr, Hashable, PyComparisonOp, Representable},
};
use std::fmt;
@@ -152,10 +153,12 @@ impl PyUnion {
}
pub fn is_unionable(obj: PyObjectRef, vm: &VirtualMachine) -> bool {
obj.class().is(vm.ctx.types.none_type)
let cls = obj.class();
cls.is(vm.ctx.types.none_type)
|| obj.downcastable::<PyType>()
|| obj.class().is(vm.ctx.types.generic_alias_type)
|| obj.class().is(vm.ctx.types.union_type)
|| cls.fast_issubclass(vm.ctx.types.generic_alias_type)
|| cls.is(vm.ctx.types.union_type)
|| obj.downcast_ref::<TypeAliasType>().is_some()
}
fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
@@ -195,7 +198,7 @@ fn dedup_and_flatten_args(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef
if !new_args.iter().any(|param| {
param
.rich_compare_bool(arg, PyComparisonOp::Eq, vm)
.expect("types are always comparable")
.unwrap_or_default()
}) {
new_args.push(arg.clone());
}

View File

@@ -36,9 +36,11 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
pub(crate) mod decl {
use crate::{
Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr},
builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr, type_},
convert::ToPyResult,
function::{FuncArgs, IntoFuncArgs},
types::{Constructor, Representable},
protocol::PyNumberMethods,
types::{AsNumber, Constructor, Representable},
};
pub(crate) fn _call_typing_func_object<'a>(
@@ -109,7 +111,7 @@ pub(crate) mod decl {
// compute_value: PyObjectRef,
// module: PyObjectRef,
}
#[pyclass(with(Constructor, Representable), flags(BASETYPE))]
#[pyclass(with(Constructor, Representable, AsNumber), flags(BASETYPE))]
impl TypeAliasType {
pub const fn new(name: PyStrRef, type_params: PyTupleRef, value: PyObjectRef) -> Self {
Self {
@@ -183,6 +185,16 @@ 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)),
..PyNumberMethods::NOT_IMPLEMENTED
};
&AS_NUMBER
}
}
// impl AsMapping for Generic {
// fn as_mapping() -> &'static PyMappingMethods {
// static AS_MAPPING: Lazy<PyMappingMethods> = Lazy::new(|| PyMappingMethods {

View File

@@ -8,3 +8,7 @@ def abort_signal_handler(
fn: Callable[[], Awaitable[T]], on_abort: Callable[[], None] | None = None
) -> T:
pass
# Ensure PEP 604 unions work with typing.Callable aliases.
TracebackFilter = bool | Callable[[int], int]