Merge pull request #5909 from youknowone/typing-parameters

typing __parameters__ __type_params__
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-07-08 00:08:45 +09:00
commit ec577e556d
20 files changed
+295 -322

No files matched your search

-18
View File
@@ -1,18 +0,0 @@
"""Used to test `get_type_hints()` on a cross-module inherited `TypedDict` class
This script uses future annotations to postpone a type that won't be available
on the module inheriting from to `Foo`. The subclass in the other module should
look something like this:
class Bar(_typed_dict_helper.Foo, total=False):
b: int
"""
from __future__ import annotations
from typing import Optional, TypedDict
OptionalIntType = Optional[int]
class Foo(TypedDict):
a: OptionalIntType
-62
View File
@@ -1,62 +0,0 @@
"""
The module for testing variable annotations.
Empty lines above are for good reason (testing for correct line numbers)
"""
from typing import Optional
from functools import wraps
__annotations__[1] = 2
class C:
x = 5; y: Optional['C'] = None
from typing import Tuple
x: int = 5; y: str = x; f: Tuple[int, int]
class M(type):
__annotations__['123'] = 123
o: type = object
(pars): bool = True
class D(C):
j: str = 'hi'; k: str= 'bye'
from types import new_class
h_class = new_class('H', (C,))
j_class = new_class('J')
class F():
z: int = 5
def __init__(self, x):
pass
class Y(F):
def __init__(self):
super(F, self).__init__(123)
class Meta(type):
def __new__(meta, name, bases, namespace):
return super().__new__(meta, name, bases, namespace)
class S(metaclass = Meta):
x: str = 'something'
y: str = 'something else'
def foo(x: int = 10):
def bar(y: List[str]):
x: str = 'yes'
bar()
def dec(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
u: int | float
-36
View File
@@ -1,36 +0,0 @@
"""
Some correct syntax for variable annotation here.
More examples are in test_grammar and test_parser.
"""
from typing import no_type_check, ClassVar
i: int = 1
j: int
x: float = i/10
def f():
class C: ...
return C()
f().new_attr: object = object()
class C:
def __init__(self, x: int) -> None:
self.x = x
c = C(5)
c.new_attr: int = 10
__annotations__ = {}
@no_type_check
class NTC:
def meth(self, param: complex) -> None:
...
class CV:
var: ClassVar['CV']
CV.var = CV()
-18
View File
@@ -1,18 +0,0 @@
"""
Correct syntax for variable annotation that should fail at runtime
in a certain manner. More examples are in test_grammar and test_parser.
"""
def f_bad_ann():
__annotations__[1] = 2
class C_OK:
def __init__(self, x: int) -> None:
self.x: no_such_name = x # This one is OK as proposed by Guido
class D_bad_ann:
def __init__(self, x: int) -> None:
sfel.y: int = 0
def g_bad_ann():
no_such_name.attr: int = 0
-5
View File
@@ -1,5 +0,0 @@
# This ann_module isn't for test_typing,
# it's for test_module
a:int=3
b:str=4
-10
View File
@@ -1,10 +0,0 @@
# Used by test_typing to verify that Final wrapped in ForwardRef works.
from __future__ import annotations
from typing import Final
name: Final[str] = "final"
class MyClass:
value: Final = 3000
-7
View File
@@ -1,7 +0,0 @@
# Tests that top-level ClassVar is not allowed
from __future__ import annotations
from typing import ClassVar
wrong: ClassVar[int] = 1
-11
View File
@@ -1,11 +0,0 @@
# Tests class have ``__text_signature__``
from __future__ import annotations
DEFAULT_BUFFER_SIZE = 8192
class BufferedReader(object):
"""BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE)\n--\n\n
Create a new buffered reader using the given readable raw IO object.
"""
pass
-53
View File
@@ -1,53 +0,0 @@
"""Module for testing the behavior of generics across different modules."""
import sys
from textwrap import dedent
from typing import TypeVar, Generic, Optional
if sys.version_info[:2] >= (3, 6):
exec(dedent("""
default_a: Optional['A'] = None
default_b: Optional['B'] = None
T = TypeVar('T')
class A(Generic[T]):
some_b: 'B'
class B(Generic[T]):
class A(Generic[T]):
pass
my_inner_a1: 'B.A'
my_inner_a2: A
my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
"""))
else: # This should stay in sync with the syntax above.
__annotations__ = dict(
default_a=Optional['A'],
default_b=Optional['B'],
)
default_a = None
default_b = None
T = TypeVar('T')
class A(Generic[T]):
__annotations__ = dict(
some_b='B'
)
class B(Generic[T]):
class A(Generic[T]):
pass
__annotations__ = dict(
my_inner_a1='B.A',
my_inner_a2=A,
my_outer_a='A' # unless somebody calls get_type_hints with localns=B.__dict__
)
+1 -1
View File
@@ -400,7 +400,7 @@ class GrammarTests(unittest.TestCase):
def test_var_annot_in_module(self):
# check that functions fail the same way when executed
# outside of module where they were defined
from test.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann
from test.typinganndata.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann
with self.assertRaises(NameError):
f_bad_ann()
with self.assertRaises(NameError):
+1 -1
View File
@@ -334,7 +334,7 @@ a = A(destroyed)"""
del foo.__annotations__
def test_annotations_are_created_correctly(self):
ann_module4 = import_helper.import_fresh_module('test.ann_module4')
ann_module4 = import_helper.import_fresh_module('test.typinganndata.ann_module4')
self.assertTrue("__annotations__" in ann_module4.__dict__)
del ann_module4.__annotations__
self.assertFalse("__annotations__" in ann_module4.__dict__)
-19
View File
@@ -629,8 +629,6 @@ class TypeParameterDefaultsTests(BaseTestCase):
class A(Generic[T]): ...
Alias = Optional[T]
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_typevar_none(self):
U = TypeVar('U')
U_None = TypeVar('U_None', default=None)
@@ -1210,8 +1208,6 @@ class UnpackTests(BaseTestCase):
self.assertEqual(repr(foo.__annotations__['kwargs']),
f"typing.Unpack[{__name__}.Movie]")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_builtin_tuple(self):
Ts = TypeVarTuple("Ts")
@@ -2636,11 +2632,6 @@ class TypingCallableTests(BaseCallableTests, BaseTestCase):
class CollectionsCallableTests(BaseCallableTests, BaseTestCase):
Callable = collections.abc.Callable
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_errors(self):
super().test_errors()
class LiteralTests(BaseTestCase):
def test_basics(self):
@@ -3608,8 +3599,6 @@ class GenericTests(BaseTestCase):
c.bar = 'abc'
self.assertEqual(c.__dict__, {'bar': 'abc'})
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_setattr_exceptions(self):
class Immutable[T]:
def __setattr__(self, key, value):
@@ -3753,8 +3742,6 @@ class GenericTests(BaseTestCase):
self.assertEqual(Union[T, int][Meta], Union[Meta, int])
self.assertEqual(Callable[..., Meta].__args__, (Ellipsis, Meta))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_generic_hashes(self):
class A(Generic[T]):
...
@@ -4302,8 +4289,6 @@ class GenericTests(BaseTestCase):
self.assertEqual(A[T], A[T])
self.assertNotEqual(A[T], B[T])
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_multiple_inheritance(self):
class A(Generic[T, VT]):
@@ -6106,8 +6091,6 @@ class GetUtilitiesTestCase(TestCase):
self.assertIs(get_origin((*tuple[*Ts],)[0]), tuple)
self.assertIs(get_origin(Unpack[Tuple[Unpack[Ts]]]), Unpack)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_get_args(self):
T = TypeVar('T')
class C(Generic[T]): pass
@@ -7599,8 +7582,6 @@ class TypedDictTests(BaseTestCase):
self.assertEqual(A[str].__parameters__, ())
self.assertEqual(A[str].__args__, (str,))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_generic_inheritance(self):
class A(TypedDict, Generic[T]):
a: T
+13 -15
View File
@@ -2,25 +2,23 @@
from typing import TypeVar, Generic, Optional, TypeAliasType
# TODO: RUSTPYTHON
default_a: Optional['A'] = None
default_b: Optional['B'] = None
# default_a: Optional['A'] = None
# default_b: Optional['B'] = None
# T = TypeVar('T')
T = TypeVar('T')
# class A(Generic[T]):
# some_b: 'B'
class A(Generic[T]):
some_b: 'B'
# class B(Generic[T]):
# class A(Generic[T]):
# pass
class B(Generic[T]):
class A(Generic[T]):
pass
# my_inner_a1: 'B.A'
# my_inner_a2: A
# my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
my_inner_a1: 'B.A'
my_inner_a2: A
my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
# type Alias = int
# OldStyle = TypeAliasType("OldStyle", int)
type Alias = int
OldStyle = TypeAliasType("OldStyle", int)
+87 -14
View File
@@ -1215,38 +1215,83 @@ impl Compiler<'_> {
// First, compile each type parameter and store it
for type_param in &type_params.type_params {
match type_param {
TypeParam::TypeVar(TypeParamTypeVar { name, bound, .. }) => {
TypeParam::TypeVar(TypeParamTypeVar {
name,
bound,
default,
..
}) => {
if let Some(expr) = &bound {
self.compile_expression(expr)?;
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::TypeVarWithBound);
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
} else {
// self.store_name(type_name.as_str())?;
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::TypeVar);
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
// Handle default value if present (PEP 695)
if let Some(default_expr) = default {
// Compile the default expression
self.compile_expression(default_expr)?;
emit!(
self,
Instruction::CallIntrinsic2 {
func: bytecode::IntrinsicFunction2::SetTypeparamDefault
}
);
}
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
TypeParam::ParamSpec(TypeParamParamSpec { name, .. }) => {
TypeParam::ParamSpec(TypeParamParamSpec { name, default, .. }) => {
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::ParamSpec);
// Handle default value if present (PEP 695)
if let Some(default_expr) = default {
// Compile the default expression
self.compile_expression(default_expr)?;
emit!(
self,
Instruction::CallIntrinsic2 {
func: bytecode::IntrinsicFunction2::SetTypeparamDefault
}
);
}
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, .. }) => {
TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, default, .. }) => {
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::TypeVarTuple);
// Handle default value if present (PEP 695)
if let Some(default_expr) = default {
// Compile the default expression
self.compile_expression(default_expr)?;
// Handle starred expression (*default)
emit!(
self,
Instruction::CallIntrinsic2 {
func: bytecode::IntrinsicFunction2::SetTypeparamDefault
}
);
}
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
@@ -1759,13 +1804,41 @@ impl Compiler<'_> {
self.emit_load_const(ConstantData::Str { value: name.into() });
// Call the __build_class__ builtin
let call = if let Some(arguments) = arguments {
self.compile_call_inner(2, arguments)?
// For PEP 695 classes: handle Generic base creation
if type_params.is_some() {
if let Some(arguments) = arguments {
// Has explicit bases - use them as is, don't add Generic
// CPython doesn't add Generic when explicit bases are present
let call = self.compile_call_inner(2, arguments)?;
self.compile_normal_call(call);
} else {
// No explicit bases, add Generic[*type_params] as the only base
// Stack currently: [function, class_name]
// Load .type_params for creating Generic base
let dot_type_params = self.name(".type_params");
emit!(self, Instruction::LoadNameAny(dot_type_params));
// Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params]
emit!(
self,
Instruction::CallIntrinsic1 {
func: bytecode::IntrinsicFunction1::SubscriptGeneric
}
);
// Call __build_class__ with 3 positional args: function, class_name, Generic[T]
emit!(self, Instruction::CallFunctionPositional { nargs: 3 });
}
} else {
CallType::Positional { nargs: 2 }
};
self.compile_normal_call(call);
// No type params, normal compilation
let call = if let Some(arguments) = arguments {
self.compile_call_inner(2, arguments)?
} else {
CallType::Positional { nargs: 2 }
};
self.compile_normal_call(call);
}
// Pop the special type params symbol table
if type_params.is_some() {
+53
View File
@@ -375,6 +375,49 @@ op_arg_enum!(
}
);
op_arg_enum!(
/// Intrinsic function for CALL_INTRINSIC_1
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum IntrinsicFunction1 {
/// Import * special case
// ImportStar = 0,
/// Set stop iteration value
// StopAsyncIteration = 1,
/// Unary operators
// UnaryPositive = 2,
// UnaryNegative = 3,
// UnaryNot = 4,
// UnaryInvert = 5,
/// Exit init subclass
// ExitInitCheck = 6,
/// Create a new list from an iterator
// ListToTupleForCall = 7,
/// Type parameter related
// TypeVar = 8,
// TypeVarTuple = 9,
// ParamSpec = 10,
/// Generic subscript for PEP 695
SubscriptGeneric = 10,
// TypeAlias = 12,
// TypeParams = 13,
}
);
op_arg_enum!(
/// Intrinsic function for CALL_INTRINSIC_2
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum IntrinsicFunction2 {
// PrepReraiseS tar = 1,
// TypeVarWithBound = 2,
// TypeVarWithConstraints = 3,
// SetFunctionTypeParams = 4,
/// Set default value for type parameter (PEP 695)
SetTypeparamDefault = 5,
}
);
pub type NameIdx = u32;
/// A Single bytecode instruction.
@@ -454,6 +497,12 @@ pub enum Instruction {
Duplicate2,
GetIter,
GetLen,
CallIntrinsic1 {
func: Arg<IntrinsicFunction1>,
},
CallIntrinsic2 {
func: Arg<IntrinsicFunction2>,
},
Continue {
target: Arg<Label>,
},
@@ -1235,6 +1284,8 @@ impl Instruction {
Duplicate2 => 2,
GetIter => 0,
GetLen => 1,
CallIntrinsic1 { .. } => 0, // Takes 1, pushes 1
CallIntrinsic2 { .. } => -1, // Takes 2, pushes 1
Continue { .. } => 0,
Break { .. } => 0,
Jump { .. } => 0,
@@ -1444,6 +1495,8 @@ impl Instruction {
GetIter => w!(GetIter),
// GET_LEN
GetLen => w!(GetLen),
CallIntrinsic1 { func } => w!(CallIntrinsic1, ?func),
CallIntrinsic2 { func } => w!(CallIntrinsic2, ?func),
Continue { target } => w!(Continue, target),
Break { target } => w!(Break, target),
Jump { target } => w!(Jump, target),
+19
View File
@@ -613,6 +613,25 @@ impl Iterable for PyGenericAlias {
}
}
/// Creates a GenericAlias from type parameters, equivalent to CPython's _Py_subscript_generic
/// This is used for PEP 695 classes to create Generic[T] from type parameters
// _Py_subscript_generic
pub fn subscript_generic(type_params: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// Get typing.Generic type
let typing_module = vm.import("typing", 0)?;
let generic_type = typing_module.get_attr("Generic", vm)?;
let generic_type = PyTypeRef::try_from_object(vm, generic_type)?;
// Create GenericAlias: Generic[type_params]
let args = if let Ok(tuple) = type_params.try_to_ref::<PyTuple>(vm) {
tuple.to_owned()
} else {
PyTuple::new_ref(vec![type_params], &vm.ctx)
};
Ok(PyGenericAlias::new(generic_type, args, false, vm).into_pyobject(vm))
}
pub fn init(context: &Context) {
let generic_alias_type = &context.types.generic_alias_type;
PyGenericAlias::extend_class(context, generic_alias_type);
+42 -1
View File
@@ -1050,6 +1050,19 @@ impl ExecutingFrame<'_> {
self.push_value(vm.ctx.new_int(len).into());
Ok(None)
}
bytecode::Instruction::CallIntrinsic1 { func } => {
let value = self.pop_value();
let result = self.call_intrinsic_1(func.get(arg), value, vm)?;
self.push_value(result);
Ok(None)
}
bytecode::Instruction::CallIntrinsic2 { func } => {
let value2 = self.pop_value();
let value1 = self.pop_value();
let result = self.call_intrinsic_2(func.get(arg), value1, value2, vm)?;
self.push_value(result);
Ok(None)
}
bytecode::Instruction::GetAwaitable => {
let awaited_obj = self.pop_value();
let awaitable = if awaited_obj.payload_is::<PyCoroutine>() {
@@ -1288,7 +1301,7 @@ impl ExecutingFrame<'_> {
}
bytecode::Instruction::ParamSpec => {
let param_spec_name = self.pop_value();
let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone())
let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone(), vm)
.into_ref(&vm.ctx)
.into();
self.push_value(param_spec);
@@ -2244,6 +2257,34 @@ impl ExecutingFrame<'_> {
}
}
fn call_intrinsic_1(
&mut self,
func: bytecode::IntrinsicFunction1,
arg: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
match func {
bytecode::IntrinsicFunction1::SubscriptGeneric => {
// Used for PEP 695: Generic[*type_params]
crate::builtins::genericalias::subscript_generic(arg, vm)
}
}
}
fn call_intrinsic_2(
&mut self,
func: bytecode::IntrinsicFunction2,
arg1: PyObjectRef,
arg2: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
match func {
bytecode::IntrinsicFunction2::SetTypeparamDefault => {
crate::stdlib::typing::set_typeparam_default(arg1, arg2, vm)
}
}
}
fn pop_multiple(&mut self, count: usize) -> crate::common::boxvec::Drain<'_, PyObjectRef> {
let stack_len = self.state.stack.len();
self.state.stack.drain(stack_len - count..)
+3 -1
View File
@@ -1009,7 +1009,9 @@ mod builtins {
{
if let Some(type_params_tuple) = type_params.downcast_ref::<PyTuple>() {
if !type_params_tuple.is_empty() {
class.set_attr(identifier!(vm, __type_params__), type_params, vm)?;
class.set_attr(identifier!(vm, __type_params__), type_params.clone(), vm)?;
// Also set __parameters__ for compatibility with typing module
class.set_attr(identifier!(vm, __parameters__), type_params, vm)?;
}
}
}
+75 -50
View File
@@ -2,6 +2,7 @@
use crate::{
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr},
common::lock::PyMutex,
function::{FuncArgs, IntoFuncArgs, PyComparisonValue},
protocol::PyNumberMethods,
types::{AsNumber, Comparable, Constructor, Iterable, PyComparisonOp, Representable},
@@ -76,7 +77,7 @@ pub struct TypeVar {
constraints: parking_lot::Mutex<PyObjectRef>,
evaluate_constraints: PyObjectRef,
default_value: parking_lot::Mutex<PyObjectRef>,
evaluate_default: PyObjectRef,
evaluate_default: PyMutex<PyObjectRef>,
covariant: bool,
contravariant: bool,
infer_variance: bool,
@@ -145,8 +146,9 @@ impl TypeVar {
if !default_value.is(&vm.ctx.typing_no_default) {
return Ok(default_value.clone());
}
if !vm.is_none(&self.evaluate_default) {
*default_value = self.evaluate_default.call((), vm)?;
let evaluate_default = self.evaluate_default.lock();
if !vm.is_none(&evaluate_default) {
*default_value = evaluate_default.call((), vm)?;
Ok(default_value.clone())
} else {
// Return NoDefault singleton
@@ -171,7 +173,7 @@ impl TypeVar {
#[pymethod]
fn has_default(&self, vm: &VirtualMachine) -> bool {
if !vm.is_none(&self.evaluate_default) {
if !vm.is_none(&self.evaluate_default.lock()) {
return true;
}
let default_value = self.default_value.lock();
@@ -358,7 +360,7 @@ impl Constructor for TypeVar {
constraints: parking_lot::Mutex::new(constraints_obj),
evaluate_constraints,
default_value: parking_lot::Mutex::new(default_value),
evaluate_default,
evaluate_default: PyMutex::new(evaluate_default),
covariant,
contravariant,
infer_variance,
@@ -384,8 +386,8 @@ impl TypeVar {
evaluate_bound,
constraints: parking_lot::Mutex::new(vm.ctx.none()),
evaluate_constraints,
default_value: parking_lot::Mutex::new(vm.ctx.none()),
evaluate_default: vm.ctx.none(),
default_value: parking_lot::Mutex::new(vm.ctx.typing_no_default.clone().into()),
evaluate_default: PyMutex::new(vm.ctx.none()),
covariant: false,
contravariant: false,
infer_variance: false,
@@ -399,8 +401,8 @@ impl TypeVar {
pub struct ParamSpec {
name: PyObjectRef,
bound: Option<PyObjectRef>,
default_value: Option<PyObjectRef>,
evaluate_default: Option<PyObjectRef>,
default_value: PyObjectRef,
evaluate_default: PyMutex<PyObjectRef>,
covariant: bool,
contravariant: bool,
infer_variance: bool,
@@ -461,14 +463,13 @@ impl ParamSpec {
#[pygetset]
fn __default__(&self, vm: &VirtualMachine) -> PyResult {
if let Some(ref default_value) = self.default_value {
// Check if default_value is NoDefault (not just None)
if !default_value.is(&vm.ctx.typing_no_default) {
return Ok(default_value.clone());
}
// Check if default_value is NoDefault (not just None)
if !self.default_value.is(&vm.ctx.typing_no_default) {
return Ok(self.default_value.clone());
}
// handle evaluate_default
if let Some(evaluate_default) = self.evaluate_default.clone() {
let evaluate_default = self.evaluate_default.lock();
if !vm.is_none(&evaluate_default) {
let default_value = evaluate_default.call((), vm)?;
return Ok(default_value);
}
@@ -477,11 +478,8 @@ impl ParamSpec {
}
#[pygetset]
fn evaluate_default(&self, vm: &VirtualMachine) -> PyObjectRef {
if let Some(evaluate_default) = self.evaluate_default.clone() {
return evaluate_default;
}
vm.ctx.none()
fn evaluate_default(&self, _vm: &VirtualMachine) -> PyObjectRef {
self.evaluate_default.lock().clone()
}
#[pymethod]
@@ -491,15 +489,11 @@ impl ParamSpec {
#[pymethod]
fn has_default(&self, vm: &VirtualMachine) -> bool {
if self.evaluate_default.is_some() {
if !vm.is_none(&self.evaluate_default.lock()) {
return true;
}
if let Some(ref default_value) = self.default_value {
// Check if default_value is not NoDefault
!default_value.is(&vm.ctx.typing_no_default)
} else {
false
}
// Check if default_value is not NoDefault
!self.default_value.is(&vm.ctx.typing_no_default)
}
#[pymethod]
@@ -594,18 +588,13 @@ impl Constructor for ParamSpec {
}
// Handle default value
let default_value = if let Some(default) = default {
Some(default)
} else {
// If no default provided, use NoDefault singleton
Some(vm.ctx.typing_no_default.clone().into())
};
let default_value = default.unwrap_or_else(|| vm.ctx.typing_no_default.clone().into());
let paramspec = Self {
name,
bound,
default_value,
evaluate_default: None,
evaluate_default: PyMutex::new(vm.ctx.none()),
covariant,
contravariant,
infer_variance,
@@ -627,12 +616,12 @@ impl Representable for ParamSpec {
}
impl ParamSpec {
pub const fn new(name: PyObjectRef) -> Self {
pub fn new(name: PyObjectRef, vm: &VirtualMachine) -> Self {
Self {
name,
bound: None,
default_value: None,
evaluate_default: None,
default_value: vm.ctx.typing_no_default.clone().into(),
evaluate_default: PyMutex::new(vm.ctx.none()),
covariant: false,
contravariant: false,
infer_variance: false,
@@ -646,7 +635,7 @@ impl ParamSpec {
pub struct TypeVarTuple {
name: PyObjectRef,
default_value: parking_lot::Mutex<PyObjectRef>,
evaluate_default: PyObjectRef,
evaluate_default: PyMutex<PyObjectRef>,
}
#[pyclass(flags(HAS_DICT), with(Constructor, Representable, Iterable))]
impl TypeVarTuple {
@@ -662,8 +651,9 @@ impl TypeVarTuple {
if !default_value.is(&vm.ctx.typing_no_default) {
return Ok(default_value.clone());
}
if !vm.is_none(&self.evaluate_default) {
*default_value = self.evaluate_default.call((), vm)?;
let evaluate_default = self.evaluate_default.lock();
if !vm.is_none(&evaluate_default) {
*default_value = evaluate_default.call((), vm)?;
Ok(default_value.clone())
} else {
// Return NoDefault singleton
@@ -673,7 +663,7 @@ impl TypeVarTuple {
#[pymethod]
fn has_default(&self, vm: &VirtualMachine) -> bool {
if !vm.is_none(&self.evaluate_default) {
if !vm.is_none(&self.evaluate_default.lock()) {
return true;
}
let default_value = self.default_value.lock();
@@ -765,7 +755,7 @@ impl Constructor for TypeVarTuple {
let typevartuple = Self {
name,
default_value: parking_lot::Mutex::new(default_value),
evaluate_default,
evaluate_default: PyMutex::new(evaluate_default),
};
let obj = typevartuple.into_ref_with_type(vm, cls)?;
@@ -788,7 +778,7 @@ impl TypeVarTuple {
Self {
name,
default_value: parking_lot::Mutex::new(vm.ctx.typing_no_default.clone().into()),
evaluate_default: vm.ctx.none(),
evaluate_default: PyMutex::new(vm.ctx.none()),
}
}
}
@@ -981,13 +971,8 @@ pub struct Generic {}
#[pyclass(flags(BASETYPE))]
impl Generic {
#[pyclassmethod]
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// Convert single arg to FuncArgs
let func_args = FuncArgs {
args: vec![args],
kwargs: Default::default(),
};
call_typing_args_kwargs("_generic_class_getitem", cls, func_args, vm)
fn __class_getitem__(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
call_typing_args_kwargs("_generic_class_getitem", cls, args, vm)
}
#[pyclassmethod]
@@ -995,3 +980,43 @@ impl Generic {
call_typing_args_kwargs("_generic_init_subclass", cls, args, vm)
}
}
/// Sets the default value for a type parameter, equivalent to CPython's _Py_set_typeparam_default
/// This is used by the CALL_INTRINSIC_2 SetTypeparamDefault instruction
pub fn set_typeparam_default(
type_param: PyObjectRef,
evaluate_default: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
// Inner function to handle common pattern of setting evaluate_default
fn try_set_default<T>(
obj: &PyObjectRef,
evaluate_default: &PyObjectRef,
get_field: impl FnOnce(&T) -> &PyMutex<PyObjectRef>,
) -> bool
where
T: PyPayload,
{
if let Some(typed_obj) = obj.downcast_ref::<T>() {
*get_field(typed_obj).lock() = evaluate_default.clone();
true
} else {
false
}
}
// Try each type parameter type
if try_set_default::<TypeVar>(&type_param, &evaluate_default, |tv| &tv.evaluate_default)
|| try_set_default::<ParamSpec>(&type_param, &evaluate_default, |ps| &ps.evaluate_default)
|| try_set_default::<TypeVarTuple>(&type_param, &evaluate_default, |tvt| {
&tvt.evaluate_default
})
{
Ok(type_param)
} else {
Err(vm.new_type_error(format!(
"Expected a type param, got {}",
type_param.class().name()
)))
}
}
+1
View File
@@ -3,6 +3,7 @@ use crate::{PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModu
pub use crate::stdlib::typevar::{
Generic, ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVar, TypeVarTuple,
set_typeparam_default,
};
pub use decl::*;