mirror of https://github.com/astral-sh/ruff
[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:
parent
014bb526f4
commit
b5d529e976
|
|
@ -1,4 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
@ -6,7 +5,6 @@ use bitflags::bitflags;
|
||||||
use call::{CallDunderError, CallError, CallErrorKind};
|
use call::{CallDunderError, CallError, CallErrorKind};
|
||||||
use context::InferContext;
|
use context::InferContext;
|
||||||
use diagnostic::{CALL_POSSIBLY_UNBOUND_METHOD, INVALID_CONTEXT_MANAGER, NOT_ITERABLE};
|
use diagnostic::{CALL_POSSIBLY_UNBOUND_METHOD, INVALID_CONTEXT_MANAGER, NOT_ITERABLE};
|
||||||
use itertools::EitherOrBoth;
|
|
||||||
use ruff_db::files::{File, FileRange};
|
use ruff_db::files::{File, FileRange};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::name::Name;
|
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::infer::infer_unpack_types;
|
||||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
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};
|
use crate::{Db, FxOrderSet, Module, Program};
|
||||||
pub(crate) use class::{
|
pub(crate) use class::{
|
||||||
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, InstanceType, KnownClass,
|
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, InstanceType, KnownClass,
|
||||||
|
|
@ -957,9 +955,9 @@ impl<'db> Type<'db> {
|
||||||
.to_instance(db)
|
.to_instance(db)
|
||||||
.is_subtype_of(db, target),
|
.is_subtype_of(db, target),
|
||||||
|
|
||||||
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
|
(Type::Callable(self_callable), Type::Callable(other_callable)) => self_callable
|
||||||
self_callable.is_subtype_of(db, other_callable)
|
.signature(db)
|
||||||
}
|
.is_subtype_of(db, other_callable.signature(db)),
|
||||||
|
|
||||||
(Type::Callable(_), _) => {
|
(Type::Callable(_), _) => {
|
||||||
// TODO: Implement subtyping between callable types and other types like
|
// 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)) => {
|
(Type::Callable(self_callable), Type::Callable(target_callable)) => self_callable
|
||||||
self_callable.is_assignable_to(db, target_callable)
|
.signature(db)
|
||||||
}
|
.is_assignable_to(db, target_callable.signature(db)),
|
||||||
|
|
||||||
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
||||||
self_function_literal
|
self_function_literal
|
||||||
|
|
@ -1305,7 +1303,9 @@ impl<'db> Type<'db> {
|
||||||
left.is_equivalent_to(db, right)
|
left.is_equivalent_to(db, right)
|
||||||
}
|
}
|
||||||
(Type::Tuple(left), Type::Tuple(right)) => 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),
|
_ => 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)
|
first.is_gradual_equivalent_to(db, second)
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::Callable(first), Type::Callable(second)) => {
|
(Type::Callable(first), Type::Callable(second)) => first
|
||||||
first.is_gradual_equivalent_to(db, second)
|
.signature(db)
|
||||||
}
|
.is_gradual_equivalent_to(db, second.signature(db)),
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
@ -1803,7 +1803,7 @@ impl<'db> Type<'db> {
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
.all(|elem| elem.is_fully_static(db)),
|
.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);
|
signature.apply_specialization(db, specialization);
|
||||||
Self::new(db, signature)
|
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`
|
/// Represents a specific instance of `types.MethodWrapperType`
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
//! argument types and return types. For each callable type in the union, the call expression's
|
//! argument types and return types. For each callable type in the union, the call expression's
|
||||||
//! arguments must match _at least one_ overload.
|
//! arguments must match _at least one_ overload.
|
||||||
|
|
||||||
|
use std::{collections::HashMap, slice::Iter};
|
||||||
|
|
||||||
|
use itertools::EitherOrBoth;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use super::{definition_expression_type, DynamicType, Type};
|
use super::{definition_expression_type, DynamicType, Type};
|
||||||
|
|
@ -284,6 +287,519 @@ impl<'db> Signature<'db> {
|
||||||
return_ty: self.return_ty,
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue