diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 9eb0165451..6cd6634c09 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -478,5 +478,515 @@ static_assert(not is_subtype_of(Intersection[Unknown, int], int)) static_assert(not is_subtype_of(tuple[int, int], tuple[int, Unknown])) ``` +## Callable + +The general principle is that a callable type is a subtype of another if it's more flexible in what +it accepts and more specific in what it returns. + +References: + +- +- + +### Return type + +Return types are covariant. + +```py +from typing import Callable +from knot_extensions import is_subtype_of, static_assert + +static_assert(is_subtype_of(Callable[[], int], Callable[[], float])) +static_assert(not is_subtype_of(Callable[[], float], Callable[[], int])) +``` + +### Parameter types + +Parameter types are contravariant. + +#### Positional-only + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def float_param(a: float, /) -> None: ... +def int_param(a: int, /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[float_param], CallableTypeFromFunction[int_param])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[float_param])) +``` + +Parameter name is not required to be the same for positional-only parameters at the same position: + +```py +def int_param_different_name(b: int, /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[int_param_different_name])) +static_assert(is_subtype_of(CallableTypeFromFunction[int_param_different_name], CallableTypeFromFunction[int_param])) +``` + +Multiple positional-only parameters are checked in order: + +```py +def multi_param1(a: float, b: int, c: str, /) -> None: ... +def multi_param2(b: int, c: bool, a: str, /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[multi_param1], CallableTypeFromFunction[multi_param2])) +static_assert(not is_subtype_of(CallableTypeFromFunction[multi_param2], CallableTypeFromFunction[multi_param1])) +``` + +#### Positional-only with default value + +If the parameter has a default value, it's treated as optional. This means that the parameter at the +corresponding position in the supertype does not need to have a default value. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def float_with_default(a: float = 1, /) -> None: ... +def int_with_default(a: int = 1, /) -> None: ... +def int_without_default(a: int, /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default])) + +static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_without_default])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_without_default], CallableTypeFromFunction[int_with_default])) +``` + +As the parameter itself is optional, it can be omitted in the supertype: + +```py +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_without_default], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default])) +``` + +The subtype can include any number of positional-only parameters as long as they have the default +value: + +```py +def multi_param(a: float = 1, b: int = 2, c: str = "3", /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[multi_param], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[multi_param])) +``` + +#### Positional-only with other kinds + +If a parameter is declared as positional-only, then the corresponding parameter in the supertype +cannot be any other parameter kind. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def positional_only(a: int, /) -> None: ... +def standard(a: int) -> None: ... +def keyword_only(*, a: int) -> None: ... +def variadic(*a: int) -> None: ... +def keyword_variadic(**a: int) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[standard])) +static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[keyword_only])) +static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[variadic])) +static_assert(not is_subtype_of(CallableTypeFromFunction[positional_only], CallableTypeFromFunction[keyword_variadic])) +``` + +#### Standard + +A standard parameter is either a positional or a keyword parameter. + +Unlike positional-only parameters, standard parameters should have the same name in the subtype. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def int_param_a(a: int) -> None: ... +def int_param_b(b: int) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[int_param_a], CallableTypeFromFunction[int_param_b])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_param_b], CallableTypeFromFunction[int_param_a])) +``` + +Apart from the name, it behaves the same as positional-only parameters. + +```py +def float_param(a: float) -> None: ... +def int_param(a: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[float_param], CallableTypeFromFunction[int_param])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[float_param])) +``` + +With the same rules for default values as well. + +```py +def float_with_default(a: float = 1) -> None: ... +def int_with_default(a: int = 1) -> None: ... +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default])) + +static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_param])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_param], CallableTypeFromFunction[int_with_default])) + +static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default])) +``` + +Multiple standard parameters are checked in order along with their names: + +```py +def multi_param1(a: float, b: int, c: str) -> None: ... +def multi_param2(a: int, b: bool, c: str) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[multi_param1], CallableTypeFromFunction[multi_param2])) +static_assert(not is_subtype_of(CallableTypeFromFunction[multi_param2], CallableTypeFromFunction[multi_param1])) +``` + +The subtype can include as many standard parameters as long as they have the default value: + +```py +def multi_param_default(a: float = 1, b: int = 2, c: str = "s") -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[multi_param_default], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[multi_param_default])) +``` + +#### Standard with keyword-only + +A keyword-only parameter in the supertype can be substituted with the corresponding standard +parameter in the subtype with the same name. This is because a standard parameter is more flexible +than a keyword-only parameter. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def standard_a(a: int) -> None: ... +def keyword_b(*, b: int) -> None: ... + +# The name of the parameters are different +static_assert(not is_subtype_of(CallableTypeFromFunction[standard_a], CallableTypeFromFunction[keyword_b])) + +def standard_float(a: float) -> None: ... +def keyword_int(*, a: int) -> None: ... + +# Here, the name of the parameters are the same +static_assert(is_subtype_of(CallableTypeFromFunction[standard_float], CallableTypeFromFunction[keyword_int])) + +def standard_with_default(a: int = 1) -> None: ... +def keyword_with_default(*, a: int = 1) -> None: ... +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[keyword_with_default])) +static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[empty])) +``` + +The position of the keyword-only parameters does not matter: + +```py +def multi_standard(a: float, b: int, c: str) -> None: ... +def multi_keyword(*, b: bool, c: str, a: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[multi_standard], CallableTypeFromFunction[multi_keyword])) +``` + +#### Standard with positional-only + +A positional-only parameter in the supertype can be substituted with the corresponding standard +parameter in the subtype at the same position. This is because a standard parameter is more flexible +than a positional-only parameter. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def standard_a(a: int) -> None: ... +def positional_b(b: int, /) -> None: ... + +# The names are not important in this context +static_assert(is_subtype_of(CallableTypeFromFunction[standard_a], CallableTypeFromFunction[positional_b])) + +def standard_float(a: float) -> None: ... +def positional_int(a: int, /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[standard_float], CallableTypeFromFunction[positional_int])) + +def standard_with_default(a: int = 1) -> None: ... +def positional_with_default(a: int = 1, /) -> None: ... +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[positional_with_default])) +static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[empty])) +``` + +The position of the positional-only parameters matter: + +```py +def multi_standard(a: float, b: int, c: str) -> None: ... +def multi_positional1(b: int, c: bool, a: str, /) -> None: ... + +# Here, the type of the parameter `a` makes the subtype relation invalid +def multi_positional2(b: int, a: float, c: str, /) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[multi_standard], CallableTypeFromFunction[multi_positional1])) +static_assert(not is_subtype_of(CallableTypeFromFunction[multi_standard], CallableTypeFromFunction[multi_positional2])) +``` + +#### Standard with variadic + +A variadic or keyword-variadic parameter in the supertype cannot be substituted with a standard +parameter in the subtype. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def standard(a: int) -> None: ... +def variadic(*a: int) -> None: ... +def keyword_variadic(**a: int) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[variadic])) +static_assert(not is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[keyword_variadic])) +``` + +#### Variadic + +The name of the variadic parameter does not need to be the same in the subtype. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def variadic_float(*args2: float) -> None: ... +def variadic_int(*args1: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[variadic_float], CallableTypeFromFunction[variadic_int])) +static_assert(not is_subtype_of(CallableTypeFromFunction[variadic_int], CallableTypeFromFunction[variadic_float])) +``` + +The variadic parameter does not need to be present in the supertype: + +```py +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[variadic_int], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[variadic_int])) +``` + +#### Variadic with positional-only + +If the subtype has a variadic parameter then any unmatched positional-only parameter from the +supertype should be checked against the variadic parameter. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def variadic(a: int, /, *args: float) -> None: ... + +# Here, the parameter `b` and `c` are unmatched +def positional_only(a: int, b: float, c: int, /) -> None: ... + +# Here, the parameter `b` is unmatched and there's also a variadic parameter +def positional_variadic(a: int, b: float, /, *args: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[positional_only])) +static_assert(is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[positional_variadic])) +``` + +#### Variadic with other kinds + +Variadic parameter in a subtype can only be used to match against an unmatched positional-only +parameters from the supertype, not any other parameter kind. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def variadic(*args: int) -> None: ... + +# Both positional-only parameters are unmatched so uses the variadic parameter but the other +# parameter `c` remains and cannot be matched. +def standard(a: int, b: float, /, c: int) -> None: ... + +# Similarly, for other kinds +def keyword_only(a: int, /, *, b: int) -> None: ... +def keyword_variadic(a: int, /, **kwargs: int) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[standard])) +static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[keyword_only])) +static_assert(not is_subtype_of(CallableTypeFromFunction[variadic], CallableTypeFromFunction[keyword_variadic])) +``` + +#### Keyword-only + +For keyword-only parameters, the name should be the same: + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def keyword_int(*, a: int) -> None: ... +def keyword_float(*, a: float) -> None: ... +def keyword_b(*, b: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[keyword_float], CallableTypeFromFunction[keyword_int])) +static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_int], CallableTypeFromFunction[keyword_float])) +static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_int], CallableTypeFromFunction[keyword_b])) +``` + +But, the order of the keyword-only parameters is not required to be the same: + +```py +def keyword_ab(*, a: float, b: float) -> None: ... +def keyword_ba(*, b: int, a: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[keyword_ab], CallableTypeFromFunction[keyword_ba])) +static_assert(not is_subtype_of(CallableTypeFromFunction[keyword_ba], CallableTypeFromFunction[keyword_ab])) +``` + +#### Keyword-only with default + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def float_with_default(*, a: float = 1) -> None: ... +def int_with_default(*, a: int = 1) -> None: ... +def int_keyword(*, a: int) -> None: ... +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[float_with_default], CallableTypeFromFunction[int_with_default])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[float_with_default])) + +static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[int_keyword])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_keyword], CallableTypeFromFunction[int_with_default])) + +static_assert(is_subtype_of(CallableTypeFromFunction[int_with_default], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[int_with_default])) +``` + +Keyword-only parameters with default values can be mixed with the ones without default values in any +order: + +```py +# A keyword-only parameter with a default value follows the one without a default value (it's valid) +def mixed(*, b: int = 1, a: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[int_keyword])) +static_assert(not is_subtype_of(CallableTypeFromFunction[int_keyword], CallableTypeFromFunction[mixed])) +``` + +#### Keyword-only with standard + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def keywords1(*, a: int, b: int) -> None: ... +def standard(b: float, a: float) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[keywords1], CallableTypeFromFunction[standard])) +static_assert(is_subtype_of(CallableTypeFromFunction[standard], CallableTypeFromFunction[keywords1])) +``` + +The subtype can include additional standard parameters as long as it has the default value: + +```py +def standard_with_default(b: float, a: float, c: float = 1) -> None: ... +def standard_without_default(b: float, a: float, c: float) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[standard_without_default], CallableTypeFromFunction[keywords1])) +static_assert(is_subtype_of(CallableTypeFromFunction[standard_with_default], CallableTypeFromFunction[keywords1])) +``` + +Here, we mix keyword-only parameters with standard parameters: + +```py +def keywords2(*, a: int, c: int, b: int) -> None: ... +def mixed(b: float, a: float, *, c: float) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[keywords2], CallableTypeFromFunction[mixed])) +static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[keywords2])) +``` + +But, we shouldn't consider any unmatched positional-only parameters: + +```py +def mixed_positional(b: float, /, a: float, *, c: float) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[mixed_positional], CallableTypeFromFunction[keywords2])) +``` + +But, an unmatched variadic parameter is still valid: + +```py +def mixed_variadic(*args: float, a: float, b: float, c: float, **kwargs: float) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[mixed_variadic], CallableTypeFromFunction[keywords2])) +``` + +#### Keyword-variadic + +The name of the keyword-variadic parameter does not need to be the same in the subtype. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def kwargs_float(**kwargs2: float) -> None: ... +def kwargs_int(**kwargs1: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[kwargs_float], CallableTypeFromFunction[kwargs_int])) +static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs_int], CallableTypeFromFunction[kwargs_float])) +``` + +A variadic parameter can be omitted in the subtype: + +```py +def empty() -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[kwargs_int], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[kwargs_int])) +``` + +#### Keyword-variadic with keyword-only + +If the subtype has a keyword-variadic parameter then any unmatched keyword-only parameter from the +supertype should be checked against the keyword-variadic parameter. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def kwargs(**kwargs: float) -> None: ... +def keyword_only(*, a: int, b: float, c: bool) -> None: ... +def keyword_variadic(*, a: int, **kwargs: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[keyword_only])) +static_assert(is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[keyword_variadic])) +``` + +This is valid only for keyword-only parameters, not any other parameter kind: + +```py +def mixed1(a: int, *, b: int) -> None: ... + +# Same as above but with the default value +def mixed2(a: int = 1, *, b: int) -> None: ... + +static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[mixed1])) +static_assert(not is_subtype_of(CallableTypeFromFunction[kwargs], CallableTypeFromFunction[mixed2])) +``` + +#### Empty + +When the supertype has an empty list of parameters, then the subtype can have any kind of parameters +as long as they contain the default values for non-variadic parameters. + +```py +from knot_extensions import CallableTypeFromFunction, is_subtype_of, static_assert + +def empty() -> None: ... +def mixed(a: int = 1, /, b: int = 2, *args: int, c: int = 3, **kwargs: int) -> None: ... + +static_assert(is_subtype_of(CallableTypeFromFunction[mixed], CallableTypeFromFunction[empty])) +static_assert(not is_subtype_of(CallableTypeFromFunction[empty], CallableTypeFromFunction[mixed])) +``` + [special case for float and complex]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex [typing documentation]: https://typing.readthedocs.io/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4ce91400d7..cd94d3d95d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1,10 +1,12 @@ -use std::hash::Hash; +use std::collections::HashMap; +use std::slice::Iter; use std::str::FromStr; use bitflags::bitflags; use call::{CallDunderError, CallError, CallErrorKind}; use context::InferContext; use diagnostic::{INVALID_CONTEXT_MANAGER, NOT_ITERABLE}; +use itertools::EitherOrBoth; use ruff_db::files::File; use ruff_python_ast as ast; use ruff_python_ast::name::Name; @@ -654,8 +656,14 @@ impl<'db> Type<'db> { .is_subtype_of(db, target) } + ( + Type::Callable(CallableType::General(self_callable)), + Type::Callable(CallableType::General(other_callable)), + ) => self_callable.is_subtype_of(db, other_callable), + (Type::Callable(CallableType::General(_)), _) => { - // TODO: Implement subtyping for general callable types + // TODO: Implement subtyping between general callable types and other types like + // function literals, bound methods, class literals, `type[]`, etc.) false } @@ -4738,6 +4746,342 @@ impl<'db> GeneralCallableType<'db> { ) }) } + + /// Return `true` if `self` is a subtype of `other`. + pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + /// A helper struct to zip two slices of parameters together that provides control over the + /// two iterators individually. It also keeps track of the current parameter in each + /// iterator. + struct ParametersZip<'a, 'db> { + current_self: Option<&'a Parameter<'db>>, + current_other: Option<&'a Parameter<'db>>, + iter_self: Iter<'a, Parameter<'db>>, + iter_other: Iter<'a, Parameter<'db>>, + } + + impl<'a, 'db> ParametersZip<'a, 'db> { + /// Move to the next parameter in both the `self` and `other` parameter iterators, + /// [`None`] if both iterators are exhausted. + fn next(&mut self) -> Option, &'a Parameter<'db>>> { + match (self.next_self(), self.next_other()) { + (Some(self_param), Some(other_param)) => { + Some(EitherOrBoth::Both(self_param, other_param)) + } + (Some(self_param), None) => Some(EitherOrBoth::Left(self_param)), + (None, Some(other_param)) => Some(EitherOrBoth::Right(other_param)), + (None, None) => None, + } + } + + /// Move to the next parameter in the `self` parameter iterator, [`None`] if the + /// iterator is exhausted. + fn next_self(&mut self) -> Option<&'a Parameter<'db>> { + self.current_self = self.iter_self.next(); + self.current_self + } + + /// Move to the next parameter in the `other` parameter iterator, [`None`] if the + /// iterator is exhausted. + fn next_other(&mut self) -> Option<&'a Parameter<'db>> { + self.current_other = self.iter_other.next(); + self.current_other + } + + /// Peek at the next parameter in the `other` parameter iterator without consuming it. + fn peek_other(&mut self) -> Option<&'a Parameter<'db>> { + self.iter_other.clone().next() + } + + /// Consumes the `ParametersZip` and returns a two-element tuple containing the + /// remaining parameters in the `self` and `other` iterators respectively. + /// + /// The returned iterators starts with the current parameter, if any, followed by the + /// remaining parameters in the respective iterators. + fn into_remaining( + self, + ) -> ( + impl Iterator>, + impl Iterator>, + ) { + ( + self.current_self.into_iter().chain(self.iter_self), + self.current_other.into_iter().chain(self.iter_other), + ) + } + } + + let self_signature = self.signature(db); + let other_signature = other.signature(db); + + // Check if `type1` is a subtype of `type2`. This is mainly to avoid `unwrap` calls + // scattered throughout the function. + let is_subtype = |type1: Option>, type2: Option>| { + // SAFETY: Subtype relation is only checked for fully static types. + type1.unwrap().is_subtype_of(db, type2.unwrap()) + }; + + // Return types are covariant. + if !is_subtype(self_signature.return_ty, other_signature.return_ty) { + return false; + } + + let mut parameters = ParametersZip { + current_self: None, + current_other: None, + iter_self: self_signature.parameters().iter(), + iter_other: other_signature.parameters().iter(), + }; + + loop { + let Some(next_parameter) = parameters.next() else { + // All parameters have been checked or both the parameter lists were empty. In + // either case, `self` is a subtype of `other`. + return true; + }; + + match next_parameter { + EitherOrBoth::Left(self_parameter) => match self_parameter.kind() { + ParameterKind::PositionalOnly { default_ty, .. } + | ParameterKind::PositionalOrKeyword { default_ty, .. } + | ParameterKind::KeywordOnly { default_ty, .. } => { + // For `self <: other` to be valid, if there are no more parameters in + // `other`, then the non-variadic parameters in `self` must have a default + // value. + if default_ty.is_none() { + return false; + } + } + ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { + // Variadic parameters don't have any restrictions in this context, so + // we'll just continue to the next parameter set. + } + }, + + EitherOrBoth::Right(_) => { + // If there are more parameters in `other` than in `self`, then `self` is not a + // subtype of `other`. + return false; + } + + EitherOrBoth::Both(self_parameter, other_parameter) => { + match (self_parameter.kind(), other_parameter.kind()) { + ( + ParameterKind::PositionalOnly { + default_ty: self_default, + .. + } + | ParameterKind::PositionalOrKeyword { + default_ty: self_default, + .. + }, + ParameterKind::PositionalOnly { + default_ty: other_default, + .. + }, + ) => { + if self_default.is_none() && other_default.is_some() { + return false; + } + if !is_subtype( + other_parameter.annotated_type(), + self_parameter.annotated_type(), + ) { + return false; + } + } + + ( + ParameterKind::PositionalOrKeyword { + name: self_name, + default_ty: self_default, + }, + ParameterKind::PositionalOrKeyword { + name: other_name, + default_ty: other_default, + }, + ) => { + if self_name != other_name { + return false; + } + // The following checks are the same as positional-only parameters. + if self_default.is_none() && other_default.is_some() { + return false; + } + if !is_subtype( + other_parameter.annotated_type(), + self_parameter.annotated_type(), + ) { + return false; + } + } + + (ParameterKind::Variadic { .. }, ParameterKind::PositionalOnly { .. }) => { + if !is_subtype( + other_parameter.annotated_type(), + self_parameter.annotated_type(), + ) { + return false; + } + + // We've reached a variadic parameter in `self` which means there can + // be no more positional parameters after this in a valid AST. But, the + // current parameter in `other` is a positional-only which means there + // can be more positional parameters after this which could be either + // more positional-only parameters, standard parameters or a variadic + // parameter. + // + // So, any remaining positional parameters in `other` would need to be + // checked against the variadic parameter in `self`. This loop does + // that by only moving the `other` iterator forward. + loop { + let Some(other_parameter) = parameters.peek_other() else { + break; + }; + if !matches!( + other_parameter.kind(), + ParameterKind::PositionalOnly { .. } + | ParameterKind::Variadic { .. } + ) { + // Any other parameter kind cannot be checked against a + // variadic parameter and is deferred to the next iteration. + break; + } + if !is_subtype( + other_parameter.annotated_type(), + self_parameter.annotated_type(), + ) { + return false; + } + parameters.next_other(); + } + } + + (ParameterKind::Variadic { .. }, ParameterKind::Variadic { .. }) => { + if !is_subtype( + other_parameter.annotated_type(), + self_parameter.annotated_type(), + ) { + return false; + } + } + + ( + _, + ParameterKind::KeywordOnly { .. } + | ParameterKind::KeywordVariadic { .. }, + ) => { + // Keyword parameters are not considered in this loop as the order of + // parameters is not important for them and so they are checked by + // doing name-based lookups. + break; + } + + _ => return false, + } + } + } + } + + // At this point, the remaining parameters in `other` are keyword-only or keyword variadic. + // But, `self` could contain any unmatched positional parameters. + let (self_parameters, other_parameters) = parameters.into_remaining(); + + // Collect all the keyword-only parameters and the unmatched standard parameters. + let mut self_keywords = HashMap::new(); + + // Type of the variadic keyword parameter in `self`. + // + // This is a nested option where the outer option represents the presence of a keyword + // variadic parameter in `self` and the inner option represents the annotated type of the + // keyword variadic parameter. + let mut self_keyword_variadic: Option>> = None; + + for self_parameter in self_parameters { + match self_parameter.kind() { + ParameterKind::KeywordOnly { name, .. } + | ParameterKind::PositionalOrKeyword { name, .. } => { + self_keywords.insert(name.clone(), self_parameter); + } + ParameterKind::KeywordVariadic { .. } => { + self_keyword_variadic = Some(self_parameter.annotated_type()); + } + ParameterKind::PositionalOnly { .. } => { + // These are the unmatched positional-only parameters in `self` from the + // previous loop. They cannot be matched against any parameter in `other` which + // only contains keyword-only and keyword-variadic parameters so the subtype + // relation is invalid. + return false; + } + ParameterKind::Variadic { .. } => {} + } + } + + for other_parameter in other_parameters { + match other_parameter.kind() { + ParameterKind::KeywordOnly { + name: other_name, + default_ty: other_default, + } => { + if let Some(self_parameter) = self_keywords.remove(other_name) { + match self_parameter.kind() { + ParameterKind::PositionalOrKeyword { + default_ty: self_default, + .. + } + | ParameterKind::KeywordOnly { + default_ty: self_default, + .. + } => { + if self_default.is_none() && other_default.is_some() { + return false; + } + if !is_subtype( + other_parameter.annotated_type(), + self_parameter.annotated_type(), + ) { + return false; + } + } + _ => unreachable!( + "`self_keywords` should only contain keyword-only or standard parameters" + ), + } + } else if let Some(self_keyword_variadic_type) = self_keyword_variadic { + if !is_subtype(other_parameter.annotated_type(), self_keyword_variadic_type) + { + return false; + } + } else { + return false; + } + } + ParameterKind::KeywordVariadic { .. } => { + let Some(self_keyword_variadic_type) = self_keyword_variadic else { + // For a `self <: other` relationship, if `other` has a keyword variadic + // parameter, `self` must also have a keyword variadic parameter. + return false; + }; + if !is_subtype(other_parameter.annotated_type(), self_keyword_variadic_type) { + return false; + } + } + _ => { + // This can only occur in case of a syntax error. + return false; + } + } + } + + // If there are still unmatched keyword parameters from `self`, then they should be + // optional otherwise the subtype relation is invalid. + for (_, self_parameter) in self_keywords { + if self_parameter.default_type().is_none() { + return false; + } + } + + true + } } /// A type that represents callable objects.