Skip to content

Commit b4f4ffa

Browse files
committed
genericalias
1 parent 3031d5b commit b4f4ffa

File tree

5 files changed

+116
-50
lines changed

5 files changed

+116
-50
lines changed

Lib/test/test_dataclasses.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,8 +1906,6 @@ def new_method(self):
19061906
c = Alias(10, 1.0)
19071907
self.assertEqual(c.new_method(), 1.0)
19081908

1909-
# TODO: RUSTPYTHON
1910-
@unittest.expectedFailure
19111909
def test_generic_dynamic(self):
19121910
T = TypeVar('T')
19131911

Lib/test/test_genericalias.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,6 @@ def test_exposed_type(self):
173173
self.assertEqual(a.__args__, (int,))
174174
self.assertEqual(a.__parameters__, ())
175175

176-
# TODO: RUSTPYTHON
177-
@unittest.expectedFailure
178176
def test_parameters(self):
179177
from typing import List, Dict, Callable
180178
D0 = dict[str, int]
@@ -214,8 +212,6 @@ def test_parameters(self):
214212
self.assertEqual(L5.__args__, (Callable[[K, V], K],))
215213
self.assertEqual(L5.__parameters__, (K, V))
216214

217-
# TODO: RUSTPYTHON
218-
@unittest.expectedFailure
219215
def test_parameter_chaining(self):
220216
from typing import List, Dict, Union, Callable
221217
self.assertEqual(list[T][int], list[int])

Lib/test/test_types.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -825,8 +825,6 @@ def check(arg, expected):
825825
check(x | None, (x, type(None)))
826826
check(None | x, (type(None), x))
827827

828-
# TODO: RUSTPYTHON
829-
@unittest.expectedFailure
830828
def test_union_parameter_chaining(self):
831829
T = typing.TypeVar("T")
832830
S = typing.TypeVar("S")

vm/src/builtins/genericalias.rs

Lines changed: 115 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -212,34 +212,49 @@ impl PyGenericAlias {
212212
}
213213
}
214214

215-
pub(crate) fn is_typevar(obj: &PyObjectRef, vm: &VirtualMachine) -> bool {
216-
let class = obj.class();
217-
"TypeVar" == &*class.slot_name()
218-
&& class
219-
.get_attr(identifier!(vm, __module__))
220-
.and_then(|o| o.downcast_ref::<PyStr>().map(|s| s.as_str() == "typing"))
221-
.unwrap_or(false)
222-
}
223-
224215
pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
225216
let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
217+
let mut iparam = 0;
218+
226219
for arg in args {
227-
if is_typevar(arg, vm) {
228-
if !parameters.iter().any(|param| param.is(arg)) {
229-
parameters.push(arg.clone());
220+
// We don't want __parameters__ descriptor of a bare Python class.
221+
if arg.class().is(vm.ctx.types.type_type) {
222+
continue;
223+
}
224+
225+
// Check for __typing_subst__ attribute (like CPython)
226+
if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() {
227+
// Use tuple_add equivalent logic
228+
if tuple_index_vec(&parameters, arg).is_none() {
229+
if iparam >= parameters.len() {
230+
parameters.resize(iparam + 1, vm.ctx.none());
231+
}
232+
parameters[iparam] = arg.clone();
233+
iparam += 1;
230234
}
231-
} else if let Ok(obj) = arg.get_attr(identifier!(vm, __parameters__), vm) {
232-
if let Ok(sub_params) = obj.try_to_ref::<PyTuple>(vm) {
235+
} else if let Ok(subparams) = arg.get_attr(identifier!(vm, __parameters__), vm) {
236+
if let Ok(sub_params) = subparams.try_to_ref::<PyTuple>(vm) {
237+
let len2 = sub_params.len();
238+
// Resize if needed
239+
if iparam + len2 > parameters.len() {
240+
parameters.resize(iparam + len2, vm.ctx.none());
241+
}
233242
for sub_param in sub_params {
234-
if !parameters.iter().any(|param| param.is(sub_param)) {
235-
parameters.push(sub_param.clone());
243+
// Use tuple_add equivalent logic
244+
if tuple_index_vec(&parameters[..iparam], sub_param).is_none() {
245+
if iparam >= parameters.len() {
246+
parameters.resize(iparam + 1, vm.ctx.none());
247+
}
248+
parameters[iparam] = sub_param.clone();
249+
iparam += 1;
236250
}
237251
}
238252
}
239253
}
240254
}
241-
parameters.shrink_to_fit();
242255

256+
// Resize to actual size
257+
parameters.truncate(iparam);
243258
PyTuple::new_ref(parameters, &vm.ctx)
244259
}
245260

@@ -248,6 +263,11 @@ fn tuple_index(tuple: &PyTupleRef, item: &PyObjectRef) -> Option<usize> {
248263
tuple.iter().position(|element| element.is(item))
249264
}
250265

266+
#[inline]
267+
fn tuple_index_vec(vec: &[PyObjectRef], item: &PyObjectRef) -> Option<usize> {
268+
vec.iter().position(|element| element.is(item))
269+
}
270+
251271
fn subs_tvars(
252272
obj: PyObjectRef,
253273
params: &PyTupleRef,
@@ -261,23 +281,40 @@ fn subs_tvars(
261281
.ok()
262282
.filter(|sub_params| !sub_params.is_empty())
263283
.map(|sub_params| {
264-
let sub_args = sub_params
265-
.iter()
266-
.map(|arg| {
267-
if let Some(idx) = tuple_index(params, arg) {
268-
arg_items[idx].clone()
269-
} else {
270-
arg.clone()
284+
let mut sub_args = Vec::new();
285+
286+
for arg in sub_params.iter() {
287+
if let Some(idx) = tuple_index(params, arg) {
288+
let param = &params[idx];
289+
let substituted_arg = &arg_items[idx];
290+
291+
// Check if this is a TypeVarTuple (has tp_iter)
292+
if param.class().slots.iter.load().is_some()
293+
&& substituted_arg.try_to_ref::<PyTuple>(vm).is_ok()
294+
{
295+
// TypeVarTuple case - extend with tuple elements
296+
if let Ok(tuple) = substituted_arg.try_to_ref::<PyTuple>(vm) {
297+
for elem in tuple.iter() {
298+
sub_args.push(elem.clone());
299+
}
300+
continue;
301+
}
271302
}
272-
})
273-
.collect::<Vec<_>>();
303+
304+
sub_args.push(substituted_arg.clone());
305+
} else {
306+
sub_args.push(arg.clone());
307+
}
308+
}
309+
274310
let sub_args: PyObjectRef = PyTuple::new_ref(sub_args, &vm.ctx).into();
275311
obj.get_item(&*sub_args, vm)
276312
})
277313
})
278314
.unwrap_or(Ok(obj))
279315
}
280316

317+
// _Py_subs_parameters
281318
pub fn subs_parameters<F: Fn(&VirtualMachine) -> PyResult<String>>(
282319
repr: F,
283320
args: PyTupleRef,
@@ -297,26 +334,62 @@ pub fn subs_parameters<F: Fn(&VirtualMachine) -> PyResult<String>>(
297334
};
298335

299336
let num_items = arg_items.len();
300-
if num_params != num_items {
301-
let plural = if num_items > num_params {
302-
"many"
303-
} else {
304-
"few"
305-
};
306-
return Err(vm.new_type_error(format!("Too {} arguments for {}", plural, repr(vm)?)));
337+
338+
// Check if we need to apply default values
339+
if num_items < num_params {
340+
// Count how many parameters have defaults
341+
let mut params_with_defaults = 0;
342+
for param in parameters.iter().rev() {
343+
if let Ok(has_default) = vm.call_method(param, "has_default", ()) {
344+
if has_default.try_to_bool(vm)? {
345+
params_with_defaults += 1;
346+
} else {
347+
break; // No more defaults from this point backwards
348+
}
349+
} else {
350+
break;
351+
}
352+
}
353+
354+
let min_required = num_params - params_with_defaults;
355+
if num_items < min_required {
356+
return Err(vm.new_type_error(format!(
357+
"Too few arguments for {}; actual {}, expected at least {}",
358+
repr(vm)?,
359+
num_items,
360+
min_required
361+
)));
362+
}
363+
} else if num_items > num_params {
364+
return Err(vm.new_type_error(format!(
365+
"Too many arguments for {}; actual {}, expected {}",
366+
repr(vm)?,
367+
num_items,
368+
num_params
369+
)));
307370
}
308371

309-
let new_args = args
310-
.iter()
311-
.map(|arg| {
312-
if is_typevar(arg, vm) {
313-
let idx = tuple_index(&parameters, arg).unwrap();
314-
Ok(arg_items[idx].clone())
372+
let mut new_args = Vec::new();
373+
374+
for arg in args.iter() {
375+
// Check for __typing_subst__ attribute directly (like CPython)
376+
if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) {
377+
let idx = tuple_index(&parameters, arg).unwrap();
378+
if idx < num_items {
379+
// Call __typing_subst__ with the argument
380+
let substituted = subst.call((arg_items[idx].clone(),), vm)?;
381+
new_args.push(substituted);
315382
} else {
316-
subs_tvars(arg.clone(), &parameters, arg_items, vm)
383+
// CPython doesn't support default values in this context
384+
return Err(vm.new_type_error(format!(
385+
"No argument provided for parameter at index {}",
386+
idx
387+
)));
317388
}
318-
})
319-
.collect::<PyResult<Vec<_>>>()?;
389+
} else {
390+
new_args.push(subs_tvars(arg.clone(), &parameters, arg_items, vm)?);
391+
}
392+
}
320393

321394
Ok(PyTuple::new_ref(new_args, &vm.ctx))
322395
}

vm/src/vm/context.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ declare_const_name! {
223223
__sizeof__,
224224
__truediv__,
225225
__trunc__,
226+
__typing_subst__,
226227
__xor__,
227228

228229
// common names

0 commit comments

Comments
 (0)