diff --git a/crates/ty_python_semantic/resources/mdtest/call/overloads.md b/crates/ty_python_semantic/resources/mdtest/call/overloads.md index c769f904d6..04ffafa871 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/call/overloads.md @@ -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 diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 4f9b21c3ef..6cb84db676 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -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> = 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> = 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);