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(*(A(),))) # revealed: A
reveal_type(f(B())) # revealed: A reveal_type(f(B())) # revealed: A
# TODO: revealed: A reveal_type(f(*(B(),))) # revealed: A
reveal_type(f(*(B(),))) # revealed: Unknown
# But, in this case, the arity check filters out the first overload, so we only have one match: # 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 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): def _(actual_enum: ActualEnum, my_enum_instance: MyEnumSubclass):
reveal_type(f(actual_enum)) # revealed: Both reveal_type(f(actual_enum)) # revealed: Both
# TODO: revealed: Both reveal_type(f(*(actual_enum,))) # revealed: Both
reveal_type(f(*(actual_enum,))) # revealed: Unknown
reveal_type(f(ActualEnum.A)) # revealed: OnlyA reveal_type(f(ActualEnum.A)) # revealed: OnlyA
# TODO: revealed: OnlyA reveal_type(f(*(ActualEnum.A,))) # revealed: OnlyA
reveal_type(f(*(ActualEnum.A,))) # revealed: Unknown
reveal_type(f(ActualEnum.B)) # revealed: OnlyB reveal_type(f(ActualEnum.B)) # revealed: OnlyB
# TODO: revealed: OnlyB reveal_type(f(*(ActualEnum.B,))) # revealed: OnlyB
reveal_type(f(*(ActualEnum.B,))) # revealed: Unknown
reveal_type(f(my_enum_instance)) # revealed: MyEnumSubclass reveal_type(f(my_enum_instance)) # revealed: MyEnumSubclass
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]): def _(list_int: list[int], list_any: list[Any]):
reveal_type(f(list_int)) # revealed: int reveal_type(f(list_int)) # revealed: int
# TODO: revealed: int reveal_type(f(*(list_int,))) # revealed: int
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(list_any)) # revealed: int reveal_type(f(list_any)) # revealed: int
# TODO: revealed: int reveal_type(f(*(list_any,))) # revealed: int
reveal_type(f(*(list_any,))) # revealed: Unknown
``` ```
### Single list argument (ambiguous) ### 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 # All materializations of `list[int]` are assignable to `list[int]`, so it matches the first
# overload. # overload.
reveal_type(f(list_int)) # revealed: int reveal_type(f(list_int)) # revealed: int
# TODO: revealed: int reveal_type(f(*(list_int,))) # revealed: int
reveal_type(f(*(list_int,))) # revealed: Unknown
# All materializations of `list[Any]` are assignable to `list[int]` and `list[Any]`, but the # 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 # 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(*("a",))) # revealed: str
reveal_type(f((1, "b"))) # revealed: int reveal_type(f((1, "b"))) # revealed: int
# TODO: revealed: int reveal_type(f(*((1, "b"),))) # revealed: int
reveal_type(f(*((1, "b"),))) # revealed: Unknown
reveal_type(f((1, 2))) # revealed: int reveal_type(f((1, 2))) # revealed: int
# TODO: revealed: int reveal_type(f(*((1, 2),))) # revealed: int
reveal_type(f(*((1, 2),))) # revealed: Unknown
def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]): 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 # All materializations are assignable to first overload, so second and third overloads are
# eliminated # eliminated
reveal_type(f(int_str)) # revealed: int reveal_type(f(int_str)) # revealed: int
# TODO: revealed: int reveal_type(f(*(int_str,))) # revealed: int
reveal_type(f(*(int_str,))) # revealed: Unknown
# All materializations are assignable to second overload, so the third overload is eliminated; # All materializations are assignable to second overload, so the third overload is eliminated;
# the return type of first and second overload is equivalent # the return type of first and second overload is equivalent
reveal_type(f(int_any)) # revealed: int reveal_type(f(int_any)) # revealed: int
# TODO: revealed: int reveal_type(f(*(int_any,))) # revealed: int
reveal_type(f(*(int_any,))) # revealed: Unknown
# All materializations of `tuple[Any, Any]` are assignable to the parameters of all the # 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 # 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): def _(literal: LiteralString, string: str, any: Any):
reveal_type(f(literal)) # revealed: LiteralString reveal_type(f(literal)) # revealed: LiteralString
# TODO: revealed: LiteralString reveal_type(f(*(literal,))) # revealed: LiteralString
reveal_type(f(*(literal,))) # revealed: Unknown
reveal_type(f(string)) # revealed: str reveal_type(f(string)) # revealed: str
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): def _(list_int: list[int], list_str: list[str], list_any: list[Any], any: Any):
reveal_type(f(list_int)) # revealed: A reveal_type(f(list_int)) # revealed: A
# TODO: revealed: A reveal_type(f(*(list_int,))) # revealed: A
reveal_type(f(*(list_int,))) # revealed: Unknown
reveal_type(f(list_str)) # revealed: str reveal_type(f(list_str)) # revealed: str
# TODO: Should be `str` reveal_type(f(*(list_str,))) # revealed: str
reveal_type(f(*(list_str,))) # revealed: Unknown
reveal_type(f(list_any)) # revealed: Unknown reveal_type(f(list_any)) # revealed: Unknown
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 reveal_type(f(*(any,), flag=False)) # revealed: str
def _(args: tuple[Any, Literal[True]]): def _(args: tuple[Any, Literal[True]]):
# TODO: revealed: int reveal_type(f(*args)) # revealed: int
reveal_type(f(*args)) # revealed: Unknown
def _(args: tuple[Any, Literal[False]]): def _(args: tuple[Any, Literal[False]]):
# TODO: revealed: str reveal_type(f(*args)) # revealed: str
reveal_type(f(*args)) # revealed: Unknown
``` ```
### Argument type expansion ### Argument type expansion

View file

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