[red-knot] Move relation methods from `CallableType` to `Signature` (#17365)

## Summary

This PR moves all the relation methods from `CallableType` to
`Signature`.

The main reason for this is that `Signature` is going to be the common
denominator between normal and overloaded callables and the core logic
to check a certain relationship is going to just require the information
that would exists on `Signature`. For example, to check whether an
overloaded callable is a subtype of a normal callable, we need to check
whether _every_ overloaded signature is a subtype of the normal
callable's signature. This "every" logic would become part of the
`CallableType` and the core logic of checking the subtyping would exists
on `Signature`.
This commit is contained in:
Dhruv Manilawala 2025-04-15 03:32:25 +05:30 committed by GitHub
parent 014bb526f4
commit b5d529e976
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 530 additions and 537 deletions

View File

@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::slice::Iter;
use std::str::FromStr;
@ -6,7 +5,6 @@ use bitflags::bitflags;
use call::{CallDunderError, CallError, CallErrorKind};
use context::InferContext;
use diagnostic::{CALL_POSSIBLY_UNBOUND_METHOD, INVALID_CONTEXT_MANAGER, NOT_ITERABLE};
use itertools::EitherOrBoth;
use ruff_db::files::{File, FileRange};
use ruff_python_ast as ast;
use ruff_python_ast::name::Name;
@ -39,7 +37,7 @@ use crate::types::generics::Specialization;
use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator};
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
use crate::{Db, FxOrderSet, Module, Program};
pub(crate) use class::{
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, InstanceType, KnownClass,
@ -957,9 +955,9 @@ impl<'db> Type<'db> {
.to_instance(db)
.is_subtype_of(db, target),
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
self_callable.is_subtype_of(db, other_callable)
}
(Type::Callable(self_callable), Type::Callable(other_callable)) => self_callable
.signature(db)
.is_subtype_of(db, other_callable.signature(db)),
(Type::Callable(_), _) => {
// TODO: Implement subtyping between callable types and other types like
@ -1276,9 +1274,9 @@ impl<'db> Type<'db> {
)
}
(Type::Callable(self_callable), Type::Callable(target_callable)) => {
self_callable.is_assignable_to(db, target_callable)
}
(Type::Callable(self_callable), Type::Callable(target_callable)) => self_callable
.signature(db)
.is_assignable_to(db, target_callable.signature(db)),
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
self_function_literal
@ -1305,7 +1303,9 @@ impl<'db> Type<'db> {
left.is_equivalent_to(db, right)
}
(Type::Tuple(left), Type::Tuple(right)) => left.is_equivalent_to(db, right),
(Type::Callable(left), Type::Callable(right)) => left.is_equivalent_to(db, right),
(Type::Callable(left), Type::Callable(right)) => {
left.signature(db).is_equivalent_to(db, right.signature(db))
}
_ => self == other && self.is_fully_static(db) && other.is_fully_static(db),
}
}
@ -1366,9 +1366,9 @@ impl<'db> Type<'db> {
first.is_gradual_equivalent_to(db, second)
}
(Type::Callable(first), Type::Callable(second)) => {
first.is_gradual_equivalent_to(db, second)
}
(Type::Callable(first), Type::Callable(second)) => first
.signature(db)
.is_gradual_equivalent_to(db, second.signature(db)),
_ => false,
}
@ -1803,7 +1803,7 @@ impl<'db> Type<'db> {
.elements(db)
.iter()
.all(|elem| elem.is_fully_static(db)),
Type::Callable(callable) => callable.is_fully_static(db),
Type::Callable(callable) => callable.signature(db).is_fully_static(db),
}
}
@ -5687,529 +5687,6 @@ impl<'db> CallableType<'db> {
signature.apply_specialization(db, specialization);
Self::new(db, signature)
}
/// Returns `true` if this is a fully static callable type.
///
/// A callable type is fully static if all of its parameters and return type are fully static
/// and if it does not use gradual form (`...`) for its parameters.
pub(crate) fn is_fully_static(self, db: &'db dyn Db) -> bool {
let signature = self.signature(db);
if signature.parameters().is_gradual() {
return false;
}
if signature.parameters().iter().any(|parameter| {
parameter
.annotated_type()
.is_none_or(|annotated_type| !annotated_type.is_fully_static(db))
}) {
return false;
}
signature
.return_ty
.is_some_and(|return_type| return_type.is_fully_static(db))
}
/// Return `true` if `self` has exactly the same set of possible static materializations as
/// `other` (if `self` represents the same set of possible sets of possible runtime objects as
/// `other`).
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.is_equivalent_to_impl(db, other, |self_type, other_type| {
self_type
.unwrap_or(Type::unknown())
.is_gradual_equivalent_to(db, other_type.unwrap_or(Type::unknown()))
})
}
/// Return `true` if `self` represents the exact same set of possible runtime objects as `other`.
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.is_equivalent_to_impl(db, other, |self_type, other_type| {
match (self_type, other_type) {
(Some(self_type), Some(other_type)) => self_type.is_equivalent_to(db, other_type),
// We need the catch-all case here because it's not guaranteed that this is a fully
// static type.
_ => false,
}
})
}
/// Implementation for the [`is_equivalent_to`] and [`is_gradual_equivalent_to`] for callable
/// types.
///
/// [`is_equivalent_to`]: Self::is_equivalent_to
/// [`is_gradual_equivalent_to`]: Self::is_gradual_equivalent_to
fn is_equivalent_to_impl<F>(self, db: &'db dyn Db, other: Self, check_types: F) -> bool
where
F: Fn(Option<Type<'db>>, Option<Type<'db>>) -> bool,
{
let self_signature = self.signature(db);
let other_signature = other.signature(db);
// N.B. We don't need to explicitly check for the use of gradual form (`...`) in the
// parameters because it is internally represented by adding `*Any` and `**Any` to the
// parameter list.
let self_parameters = self_signature.parameters();
let other_parameters = other_signature.parameters();
if self_parameters.len() != other_parameters.len() {
return false;
}
if !check_types(self_signature.return_ty, other_signature.return_ty) {
return false;
}
for (self_parameter, other_parameter) in self_parameters.iter().zip(other_parameters) {
match (self_parameter.kind(), other_parameter.kind()) {
(
ParameterKind::PositionalOnly {
default_type: self_default,
..
},
ParameterKind::PositionalOnly {
default_type: other_default,
..
},
) if self_default.is_some() == other_default.is_some() => {}
(
ParameterKind::PositionalOrKeyword {
name: self_name,
default_type: self_default,
},
ParameterKind::PositionalOrKeyword {
name: other_name,
default_type: other_default,
},
) if self_default.is_some() == other_default.is_some()
&& self_name == other_name => {}
(ParameterKind::Variadic { .. }, ParameterKind::Variadic { .. }) => {}
(
ParameterKind::KeywordOnly {
name: self_name,
default_type: self_default,
},
ParameterKind::KeywordOnly {
name: other_name,
default_type: other_default,
},
) if self_default.is_some() == other_default.is_some()
&& self_name == other_name => {}
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
_ => return false,
}
if !check_types(
self_parameter.annotated_type(),
other_parameter.annotated_type(),
) {
return false;
}
}
true
}
/// Return `true` if `self` is assignable to `other`.
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
self.is_assignable_to_impl(db, other, |type1, type2| {
// In the context of a callable type, the `None` variant represents an `Unknown` type.
type1
.unwrap_or(Type::unknown())
.is_assignable_to(db, type2.unwrap_or(Type::unknown()))
})
}
/// Return `true` if `self` is a subtype of `other`.
///
/// # Panics
///
/// Panics if `self` or `other` is not a fully static type.
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
self.is_assignable_to_impl(db, other, |type1, type2| {
// SAFETY: Subtype relation is only checked for fully static types.
type1.unwrap().is_subtype_of(db, type2.unwrap())
})
}
/// Implementation for the [`is_assignable_to`] and [`is_subtype_of`] for callable types.
///
/// [`is_assignable_to`]: Self::is_assignable_to
/// [`is_subtype_of`]: Self::is_subtype_of
fn is_assignable_to_impl<F>(self, db: &'db dyn Db, other: Self, check_types: F) -> bool
where
F: Fn(Option<Type<'db>>, Option<Type<'db>>) -> 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<EitherOrBoth<&'a Parameter<'db>, &'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<Item = &'a Parameter<'db>>,
impl Iterator<Item = &'a Parameter<'db>>,
) {
(
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);
// Return types are covariant.
if !check_types(self_signature.return_ty, other_signature.return_ty) {
return false;
}
if self_signature.parameters().is_gradual() || other_signature.parameters().is_gradual() {
// If either of the parameter lists contains a gradual form (`...`), then it is
// assignable / subtype to and from any other callable type.
return true;
}
let mut parameters = ParametersZip {
current_self: None,
current_other: None,
iter_self: self_signature.parameters().iter(),
iter_other: other_signature.parameters().iter(),
};
// Collect all the standard parameters that have only been matched against a variadic
// parameter which means that the keyword variant is still unmatched.
let mut other_keywords = Vec::new();
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::KeywordOnly { .. } | ParameterKind::KeywordVariadic { .. }
if !other_keywords.is_empty() =>
{
// If there are any unmatched keyword parameters in `other`, they need to
// be checked against the keyword-only / keyword-variadic parameters that
// will be done after this loop.
break;
}
ParameterKind::PositionalOnly { default_type, .. }
| ParameterKind::PositionalOrKeyword { default_type, .. }
| ParameterKind::KeywordOnly { default_type, .. } => {
// 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_type.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_type: self_default,
..
}
| ParameterKind::PositionalOrKeyword {
default_type: self_default,
..
},
ParameterKind::PositionalOnly {
default_type: other_default,
..
},
) => {
if self_default.is_none() && other_default.is_some() {
return false;
}
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
}
(
ParameterKind::PositionalOrKeyword {
name: self_name,
default_type: self_default,
},
ParameterKind::PositionalOrKeyword {
name: other_name,
default_type: 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 !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
}
(
ParameterKind::Variadic { .. },
ParameterKind::PositionalOnly { .. }
| ParameterKind::PositionalOrKeyword { .. },
) => {
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
if matches!(
other_parameter.kind(),
ParameterKind::PositionalOrKeyword { .. }
) {
other_keywords.push(other_parameter);
}
// 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;
};
match other_parameter.kind() {
ParameterKind::PositionalOrKeyword { .. } => {
other_keywords.push(other_parameter);
}
ParameterKind::PositionalOnly { .. }
| ParameterKind::Variadic { .. } => {}
_ => {
// Any other parameter kind cannot be checked against a
// variadic parameter and is deferred to the next iteration.
break;
}
}
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
parameters.next_other();
}
}
(ParameterKind::Variadic { .. }, ParameterKind::Variadic { .. }) => {
if !check_types(
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<Option<Type<'db>>> = 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_keywords.into_iter().chain(other_parameters) {
match other_parameter.kind() {
ParameterKind::KeywordOnly {
name: other_name,
default_type: other_default,
}
| ParameterKind::PositionalOrKeyword {
name: other_name,
default_type: other_default,
} => {
if let Some(self_parameter) = self_keywords.remove(other_name) {
match self_parameter.kind() {
ParameterKind::PositionalOrKeyword {
default_type: self_default,
..
}
| ParameterKind::KeywordOnly {
default_type: self_default,
..
} => {
if self_default.is_none() && other_default.is_some() {
return false;
}
if !check_types(
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 !check_types(
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 !check_types(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
}
}
/// Represents a specific instance of `types.MethodWrapperType`

View File

@ -10,6 +10,9 @@
//! argument types and return types. For each callable type in the union, the call expression's
//! arguments must match _at least one_ overload.
use std::{collections::HashMap, slice::Iter};
use itertools::EitherOrBoth;
use smallvec::{smallvec, SmallVec};
use super::{definition_expression_type, DynamicType, Type};
@ -284,6 +287,519 @@ impl<'db> Signature<'db> {
return_ty: self.return_ty,
}
}
/// Returns `true` if this is a fully static signature.
///
/// A signature is fully static if all of its parameters and return type are fully static and
/// if it does not use gradual form (`...`) for its parameters.
pub(crate) fn is_fully_static(&self, db: &'db dyn Db) -> bool {
if self.parameters.is_gradual() {
return false;
}
if self.parameters.iter().any(|parameter| {
parameter
.annotated_type()
.is_none_or(|annotated_type| !annotated_type.is_fully_static(db))
}) {
return false;
}
self.return_ty
.is_some_and(|return_type| return_type.is_fully_static(db))
}
/// Return `true` if `self` has exactly the same set of possible static materializations as
/// `other` (if `self` represents the same set of possible sets of possible runtime objects as
/// `other`).
pub(crate) fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Signature<'db>) -> bool {
self.is_equivalent_to_impl(other, |self_type, other_type| {
self_type
.unwrap_or(Type::unknown())
.is_gradual_equivalent_to(db, other_type.unwrap_or(Type::unknown()))
})
}
/// Return `true` if `self` represents the exact same set of possible runtime objects as `other`.
pub(crate) fn is_equivalent_to(&self, db: &'db dyn Db, other: &Signature<'db>) -> bool {
self.is_equivalent_to_impl(other, |self_type, other_type| {
match (self_type, other_type) {
(Some(self_type), Some(other_type)) => self_type.is_equivalent_to(db, other_type),
// We need the catch-all case here because it's not guaranteed that this is a fully
// static type.
_ => false,
}
})
}
/// Implementation for the [`is_equivalent_to`] and [`is_gradual_equivalent_to`] for signature.
///
/// [`is_equivalent_to`]: Self::is_equivalent_to
/// [`is_gradual_equivalent_to`]: Self::is_gradual_equivalent_to
fn is_equivalent_to_impl<F>(&self, other: &Signature<'db>, check_types: F) -> bool
where
F: Fn(Option<Type<'db>>, Option<Type<'db>>) -> bool,
{
// N.B. We don't need to explicitly check for the use of gradual form (`...`) in the
// parameters because it is internally represented by adding `*Any` and `**Any` to the
// parameter list.
if self.parameters.len() != other.parameters.len() {
return false;
}
if !check_types(self.return_ty, other.return_ty) {
return false;
}
for (self_parameter, other_parameter) in self.parameters.iter().zip(&other.parameters) {
match (self_parameter.kind(), other_parameter.kind()) {
(
ParameterKind::PositionalOnly {
default_type: self_default,
..
},
ParameterKind::PositionalOnly {
default_type: other_default,
..
},
) if self_default.is_some() == other_default.is_some() => {}
(
ParameterKind::PositionalOrKeyword {
name: self_name,
default_type: self_default,
},
ParameterKind::PositionalOrKeyword {
name: other_name,
default_type: other_default,
},
) if self_default.is_some() == other_default.is_some()
&& self_name == other_name => {}
(ParameterKind::Variadic { .. }, ParameterKind::Variadic { .. }) => {}
(
ParameterKind::KeywordOnly {
name: self_name,
default_type: self_default,
},
ParameterKind::KeywordOnly {
name: other_name,
default_type: other_default,
},
) if self_default.is_some() == other_default.is_some()
&& self_name == other_name => {}
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
_ => return false,
}
if !check_types(
self_parameter.annotated_type(),
other_parameter.annotated_type(),
) {
return false;
}
}
true
}
/// Return `true` if a callable with signature `self` is assignable to a callable with
/// signature `other`.
pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Signature<'db>) -> bool {
self.is_assignable_to_impl(other, |type1, type2| {
// In the context of a callable type, the `None` variant represents an `Unknown` type.
type1
.unwrap_or(Type::unknown())
.is_assignable_to(db, type2.unwrap_or(Type::unknown()))
})
}
/// Return `true` if a callable with signature `self` is a subtype of a callable with signature
/// `other`.
///
/// # Panics
///
/// Panics if `self` or `other` is not a fully static signature.
pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Signature<'db>) -> bool {
self.is_assignable_to_impl(other, |type1, type2| {
// SAFETY: Subtype relation is only checked for fully static types.
type1.unwrap().is_subtype_of(db, type2.unwrap())
})
}
/// Implementation for the [`is_assignable_to`] and [`is_subtype_of`] for signature.
///
/// [`is_assignable_to`]: Self::is_assignable_to
/// [`is_subtype_of`]: Self::is_subtype_of
fn is_assignable_to_impl<F>(&self, other: &Signature<'db>, check_types: F) -> bool
where
F: Fn(Option<Type<'db>>, Option<Type<'db>>) -> 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<EitherOrBoth<&'a Parameter<'db>, &'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<Item = &'a Parameter<'db>>,
impl Iterator<Item = &'a Parameter<'db>>,
) {
(
self.current_self.into_iter().chain(self.iter_self),
self.current_other.into_iter().chain(self.iter_other),
)
}
}
// Return types are covariant.
if !check_types(self.return_ty, other.return_ty) {
return false;
}
if self.parameters.is_gradual() || other.parameters.is_gradual() {
// If either of the parameter lists contains a gradual form (`...`), then it is
// assignable / subtype to and from any other callable type.
return true;
}
let mut parameters = ParametersZip {
current_self: None,
current_other: None,
iter_self: self.parameters.iter(),
iter_other: other.parameters.iter(),
};
// Collect all the standard parameters that have only been matched against a variadic
// parameter which means that the keyword variant is still unmatched.
let mut other_keywords = Vec::new();
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::KeywordOnly { .. } | ParameterKind::KeywordVariadic { .. }
if !other_keywords.is_empty() =>
{
// If there are any unmatched keyword parameters in `other`, they need to
// be checked against the keyword-only / keyword-variadic parameters that
// will be done after this loop.
break;
}
ParameterKind::PositionalOnly { default_type, .. }
| ParameterKind::PositionalOrKeyword { default_type, .. }
| ParameterKind::KeywordOnly { default_type, .. } => {
// 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_type.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_type: self_default,
..
}
| ParameterKind::PositionalOrKeyword {
default_type: self_default,
..
},
ParameterKind::PositionalOnly {
default_type: other_default,
..
},
) => {
if self_default.is_none() && other_default.is_some() {
return false;
}
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
}
(
ParameterKind::PositionalOrKeyword {
name: self_name,
default_type: self_default,
},
ParameterKind::PositionalOrKeyword {
name: other_name,
default_type: 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 !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
}
(
ParameterKind::Variadic { .. },
ParameterKind::PositionalOnly { .. }
| ParameterKind::PositionalOrKeyword { .. },
) => {
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
if matches!(
other_parameter.kind(),
ParameterKind::PositionalOrKeyword { .. }
) {
other_keywords.push(other_parameter);
}
// 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;
};
match other_parameter.kind() {
ParameterKind::PositionalOrKeyword { .. } => {
other_keywords.push(other_parameter);
}
ParameterKind::PositionalOnly { .. }
| ParameterKind::Variadic { .. } => {}
_ => {
// Any other parameter kind cannot be checked against a
// variadic parameter and is deferred to the next iteration.
break;
}
}
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
return false;
}
parameters.next_other();
}
}
(ParameterKind::Variadic { .. }, ParameterKind::Variadic { .. }) => {
if !check_types(
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<Option<Type<'db>>> = 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_keywords.into_iter().chain(other_parameters) {
match other_parameter.kind() {
ParameterKind::KeywordOnly {
name: other_name,
default_type: other_default,
}
| ParameterKind::PositionalOrKeyword {
name: other_name,
default_type: other_default,
} => {
if let Some(self_parameter) = self_keywords.remove(other_name) {
match self_parameter.kind() {
ParameterKind::PositionalOrKeyword {
default_type: self_default,
..
}
| ParameterKind::KeywordOnly {
default_type: self_default,
..
} => {
if self_default.is_none() && other_default.is_some() {
return false;
}
if !check_types(
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 !check_types(
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 !check_types(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
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]