Initial implementation of single starred arg in overload call

This commit is contained in:
Dhruv Manilawala 2025-09-19 16:09:54 +05:30
parent 990202768e
commit 4cd7de1fa1
2 changed files with 77 additions and 64 deletions

View file

@ -139,8 +139,7 @@ reveal_type(f(A())) # revealed: A
reveal_type(f(*(A(),))) # revealed: A
reveal_type(f(B())) # revealed: A
# TODO: revealed: A
reveal_type(f(*(B(),))) # revealed: Unknown
reveal_type(f(*(B(),))) # revealed: A
# But, in this case, the arity check filters out the first overload, so we only have one match:
reveal_type(f(B(), 1)) # revealed: B
@ -551,16 +550,13 @@ from overloaded import MyEnumSubclass, ActualEnum, f
def _(actual_enum: ActualEnum, my_enum_instance: MyEnumSubclass):
reveal_type(f(actual_enum)) # revealed: Both
# TODO: revealed: Both
reveal_type(f(*(actual_enum,))) # revealed: Unknown
reveal_type(f(*(actual_enum,))) # revealed: Both
reveal_type(f(ActualEnum.A)) # revealed: OnlyA
# TODO: revealed: OnlyA
reveal_type(f(*(ActualEnum.A,))) # revealed: Unknown
reveal_type(f(*(ActualEnum.A,))) # revealed: OnlyA
reveal_type(f(ActualEnum.B)) # revealed: OnlyB
# TODO: revealed: OnlyB
reveal_type(f(*(ActualEnum.B,))) # revealed: Unknown
reveal_type(f(*(ActualEnum.B,))) # revealed: OnlyB
reveal_type(f(my_enum_instance)) # revealed: MyEnumSubclass
reveal_type(f(*(my_enum_instance,))) # revealed: MyEnumSubclass
@ -969,12 +965,10 @@ reveal_type(f(*(1,))) # revealed: str
def _(list_int: list[int], list_any: list[Any]):
reveal_type(f(list_int)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(*(list_int,))) # revealed: int
reveal_type(f(list_any)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(list_any,))) # revealed: Unknown
reveal_type(f(*(list_any,))) # revealed: int
```
### Single list argument (ambiguous)
@ -1008,8 +1002,7 @@ def _(list_int: list[int], list_any: list[Any]):
# All materializations of `list[int]` are assignable to `list[int]`, so it matches the first
# overload.
reveal_type(f(list_int)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(*(list_int,))) # revealed: int
# All materializations of `list[Any]` are assignable to `list[int]` and `list[Any]`, but the
# return type of first and second overloads are not equivalent, so the overload matching
@ -1042,25 +1035,21 @@ reveal_type(f("a")) # revealed: str
reveal_type(f(*("a",))) # revealed: str
reveal_type(f((1, "b"))) # revealed: int
# TODO: revealed: int
reveal_type(f(*((1, "b"),))) # revealed: Unknown
reveal_type(f(*((1, "b"),))) # revealed: int
reveal_type(f((1, 2))) # revealed: int
# TODO: revealed: int
reveal_type(f(*((1, 2),))) # revealed: Unknown
reveal_type(f(*((1, 2),))) # revealed: int
def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]):
# All materializations are assignable to first overload, so second and third overloads are
# eliminated
reveal_type(f(int_str)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(int_str,))) # revealed: Unknown
reveal_type(f(*(int_str,))) # revealed: int
# All materializations are assignable to second overload, so the third overload is eliminated;
# the return type of first and second overload is equivalent
reveal_type(f(int_any)) # revealed: int
# TODO: revealed: int
reveal_type(f(*(int_any,))) # revealed: Unknown
reveal_type(f(*(int_any,))) # revealed: int
# All materializations of `tuple[Any, Any]` are assignable to the parameters of all the
# overloads, but the return types aren't equivalent, so the overload matching is ambiguous
@ -1188,8 +1177,7 @@ from overloaded import f
def _(literal: LiteralString, string: str, any: Any):
reveal_type(f(literal)) # revealed: LiteralString
# TODO: revealed: LiteralString
reveal_type(f(*(literal,))) # revealed: Unknown
reveal_type(f(*(literal,))) # revealed: LiteralString
reveal_type(f(string)) # revealed: str
reveal_type(f(*(string,))) # revealed: str
@ -1227,12 +1215,10 @@ from overloaded import f
def _(list_int: list[int], list_str: list[str], list_any: list[Any], any: Any):
reveal_type(f(list_int)) # revealed: A
# TODO: revealed: A
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(*(list_int,))) # revealed: A
reveal_type(f(list_str)) # revealed: str
# TODO: Should be `str`
reveal_type(f(*(list_str,))) # revealed: Unknown
reveal_type(f(*(list_str,))) # revealed: str
reveal_type(f(list_any)) # revealed: Unknown
reveal_type(f(*(list_any,))) # revealed: Unknown
@ -1433,12 +1419,10 @@ def _(any: Any):
reveal_type(f(*(any,), flag=False)) # revealed: str
def _(args: tuple[Any, Literal[True]]):
# TODO: revealed: int
reveal_type(f(*args)) # revealed: Unknown
reveal_type(f(*args)) # revealed: int
def _(args: tuple[Any, Literal[False]]):
# TODO: revealed: str
reveal_type(f(*args)) # revealed: Unknown
reveal_type(f(*args)) # revealed: str
```
### Argument type expansion

View file

@ -1522,50 +1522,79 @@ impl<'db> CallableBinding<'db> {
arguments: &CallArguments<'_, 'db>,
matching_overload_indexes: &[usize],
) {
// These are the parameter indexes that matches the arguments that participate in the
// filtering process.
//
// The parameter types at these indexes have at least one overload where the type isn't
// gradual equivalent to the parameter types at the same index for other overloads.
// These are the parameter indexes that will participate in the filtering process. The
// parameter types at these indexes have at least one overload where the type isn't
// equivalent to the parameter types at the same index for other overloads. For example, if
// all parameter types at a specific index for the remaining overloads are equivalent, they
// aren't useful to filter the overloads, so we don't include them here.
let mut participating_parameter_indexes = HashSet::new();
let mut current_parameter_index = 0;
loop {
// TODO: Should the parameter kind be considered here? For example, even though the
// types are equivalent, what if the parameter kinds aren't?
let mut first_parameter_type: Option<Type<'db>> = None;
for &overload_index in matching_overload_indexes {
let overload = &self.overloads[overload_index];
let Some(parameter) = overload.signature.parameters().get(current_parameter_index)
else {
// There's no parameter at this index for this overload, but we can't stop here
// because other overloads might have a parameter at this index.
continue;
};
// TODO: For an unannotated `self` / `cls` parameter, the type should be
// `typing.Self` / `type[typing.Self]`
let parameter_type = parameter.annotated_type().unwrap_or(Type::unknown());
if let Some(first_parameter_type) = first_parameter_type {
if !first_parameter_type.is_equivalent_to(db, parameter_type) {
participating_parameter_indexes.insert(current_parameter_index);
break;
}
} else {
first_parameter_type = Some(parameter_type);
}
}
current_parameter_index += 1;
if first_parameter_type.is_none() {
// None of the overloads had a parameter at this index, so we can stop here.
break;
}
}
if participating_parameter_indexes.is_empty() {
return;
}
// These only contain the top materialized argument types for the corresponding
// participating parameter indexes.
let mut top_materialized_argument_types = vec![];
for (argument_index, argument_type) in arguments.iter_types().enumerate() {
let mut first_parameter_type: Option<Type<'db>> = None;
let mut participating_parameter_index = None;
'overload: for overload_index in matching_overload_indexes {
let overload = &self.overloads[*overload_index];
for parameter_index in &overload.argument_matches[argument_index].parameters {
// TODO: For an unannotated `self` / `cls` parameter, the type should be
// `typing.Self` / `type[typing.Self]`
let current_parameter_type = overload.signature.parameters()[*parameter_index]
.annotated_type()
.unwrap_or(Type::unknown());
if let Some(first_parameter_type) = first_parameter_type {
if !first_parameter_type.is_equivalent_to(db, current_parameter_type) {
participating_parameter_index = Some(*parameter_index);
break 'overload;
}
} else {
first_parameter_type = Some(current_parameter_type);
for (argument_index, (argument, argument_type)) in arguments.iter().enumerate() {
let argument_type = argument_type.unwrap_or_else(Type::unknown);
// TODO: Add support for `**kwargs`. Should we avoid doing any filtering if `**kwargs`
// is present to any incorrect filtering?
if matches!(argument, Argument::Variadic(_)) {
for (index, &unpacked_argument_type) in
argument_type.iterate(db).all_elements().enumerate()
{
let adjusted_index = argument_index + index;
if !participating_parameter_indexes.contains(&adjusted_index) {
continue;
}
top_materialized_argument_types
.push(unpacked_argument_type.top_materialization(db));
}
} else {
if !participating_parameter_indexes.contains(&argument_index) {
continue;
}
}
if let Some(parameter_index) = participating_parameter_index {
participating_parameter_indexes.insert(parameter_index);
top_materialized_argument_types.push(argument_type.top_materialization(db));
}
}
if top_materialized_argument_types.is_empty() {
return;
}
let top_materialized_argument_type =
Type::heterogeneous_tuple(db, top_materialized_argument_types);