[red-knot] Break up call binding into two phases (#16546)

This breaks up call binding into two phases:

- **_Matching parameters_** just looks at the names and kinds
(positional/keyword) of each formal and actual parameters, and matches
them up. Most of the current call binding errors happen during this
phase.

- Once we have matched up formal and actual parameters, we can **_infer
types_** of each actual parameter, and **_check_** that each one is
assignable to the corresponding formal parameter type.

As part of this, we add information to each formal parameter about
whether it is a type form or not. Once [PEP
747](https://peps.python.org/pep-0747/) is finalized, we can hook that
up to this internal type form representation. This replaces the
`ParameterExpectations` type, which did the same thing in a more ad hoc
way.

While we're here, we add a new fluent API for building `Parameter`s,
which makes our signature constructors a bit nicer to read. We also
eliminate a TODO where we were consuming types from the argument list
instead of the bound parameter list when evaluating our special-case
known functions.

Closes #15460

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Douglas Creager 2025-03-21 09:38:11 -04:00 committed by GitHub
parent 4773878ee7
commit c03c28d199
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1164 additions and 1132 deletions

View file

@ -161,3 +161,17 @@ def _(flag: bool):
reveal_type(repr("string")) # revealed: Literal["'string'"]
reveal_type(f("string")) # revealed: Literal["string", "'string'"]
```
## Cannot use an argument as both a value and a type form
```py
from knot_extensions import is_fully_static
def _(flag: bool):
if flag:
f = repr
else:
f = is_fully_static
# error: [conflicting-argument-forms] "Argument is used as both a value and a type form in call"
reveal_type(f(int)) # revealed: str | Literal[True]
```

View file

@ -13,17 +13,16 @@ reveal_type(cast("str", True)) # revealed: str
reveal_type(cast(int | str, 1)) # revealed: int | str
reveal_type(cast(val="foo", typ=int)) # revealed: int
# error: [invalid-type-form]
reveal_type(cast(Literal, True)) # revealed: Unknown
# error: [invalid-type-form]
reveal_type(cast(1, True)) # revealed: Unknown
# TODO: These should be errors
# error: [missing-argument] "No argument provided for required parameter `val` of function `cast`"
cast(str)
# error: [too-many-positional-arguments] "Too many positional arguments to function `cast`: expected 2, got 3"
cast(str, b"ar", "foo")
# TODO: Either support keyword arguments properly,
# or give a comprehensible error message saying they're unsupported
cast(val="foo", typ=int) # error: [unresolved-reference] "Name `foo` used when not defined"
```

File diff suppressed because it is too large Load diff

View file

@ -4,14 +4,14 @@ use crate::Db;
mod arguments;
mod bind;
pub(super) use arguments::{Argument, CallArguments};
pub(super) use arguments::{Argument, CallArgumentTypes, CallArguments};
pub(super) use bind::Bindings;
/// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was
/// unsuccessful.
///
/// The bindings are boxed so that we do not pass around large `Err` variants on the stack.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug)]
pub(crate) struct CallError<'db>(pub(crate) CallErrorKind, pub(crate) Box<Bindings<'db>>);
/// The reason why calling a type failed.
@ -32,7 +32,7 @@ pub(crate) enum CallErrorKind {
PossiblyNotCallable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug)]
pub(super) enum CallDunderError<'db> {
/// The dunder attribute exists but it can't be called with the given arguments.
///

View file

@ -1,88 +1,128 @@
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use super::Type;
/// Typed arguments for a single call, in source order.
/// Arguments for a single call, in source order.
#[derive(Clone, Debug, Default)]
pub(crate) struct CallArguments<'a, 'db>(Vec<Argument<'a, 'db>>);
pub(crate) struct CallArguments<'a>(VecDeque<Argument<'a>>);
impl<'a, 'db> CallArguments<'a, 'db> {
/// Create a [`CallArguments`] with no arguments.
pub(crate) fn none() -> Self {
Self(Vec::new())
impl<'a> CallArguments<'a> {
/// Invoke a function with an optional extra synthetic argument (for a `self` or `cls`
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
/// function is invoked with the unmodified argument list.)
pub(crate) fn with_self<F, R>(&mut self, bound_self: Option<Type<'_>>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
if bound_self.is_some() {
self.0.push_front(Argument::Synthetic);
}
let result = f(self);
if bound_self.is_some() {
self.0.pop_front();
}
result
}
/// Create a [`CallArguments`] from an iterator over non-variadic positional argument types.
pub(crate) fn positional(positional_tys: impl IntoIterator<Item = Type<'db>>) -> Self {
positional_tys
.into_iter()
.map(Argument::Positional)
.collect()
pub(crate) fn len(&self) -> usize {
self.0.len()
}
/// Prepend an extra positional argument.
pub(crate) fn with_self(&self, self_ty: Type<'db>) -> Self {
let mut arguments = Vec::with_capacity(self.0.len() + 1);
arguments.push(Argument::Synthetic(self_ty));
arguments.extend_from_slice(&self.0);
Self(arguments)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &Argument<'a, 'db>> {
self.0.iter()
}
// TODO this should be eliminated in favor of [`bind_call`]
pub(crate) fn first_argument(&self) -> Option<Type<'db>> {
self.0.first().map(Argument::ty)
}
// TODO this should be eliminated in favor of [`bind_call`]
pub(crate) fn second_argument(&self) -> Option<Type<'db>> {
self.0.get(1).map(Argument::ty)
}
// TODO this should be eliminated in favor of [`bind_call`]
pub(crate) fn third_argument(&self) -> Option<Type<'db>> {
self.0.get(2).map(Argument::ty)
pub(crate) fn iter(&self) -> impl Iterator<Item = Argument<'a>> + '_ {
self.0.iter().copied()
}
}
impl<'db, 'a, 'b> IntoIterator for &'b CallArguments<'a, 'db> {
type Item = &'b Argument<'a, 'db>;
type IntoIter = std::slice::Iter<'b, Argument<'a, 'db>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a, 'db> FromIterator<Argument<'a, 'db>> for CallArguments<'a, 'db> {
fn from_iter<T: IntoIterator<Item = Argument<'a, 'db>>>(iter: T) -> Self {
impl<'a> FromIterator<Argument<'a>> for CallArguments<'a> {
fn from_iter<T: IntoIterator<Item = Argument<'a>>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
#[derive(Clone, Debug)]
pub(crate) enum Argument<'a, 'db> {
#[derive(Clone, Copy, Debug)]
pub(crate) enum Argument<'a> {
/// The synthetic `self` or `cls` argument, which doesn't appear explicitly at the call site.
Synthetic(Type<'db>),
Synthetic,
/// A positional argument.
Positional(Type<'db>),
Positional,
/// A starred positional argument (e.g. `*args`).
Variadic(Type<'db>),
Variadic,
/// A keyword argument (e.g. `a=1`).
Keyword { name: &'a str, ty: Type<'db> },
Keyword(&'a str),
/// The double-starred keywords argument (e.g. `**kwargs`).
Keywords(Type<'db>),
Keywords,
}
impl<'db> Argument<'_, 'db> {
fn ty(&self) -> Type<'db> {
match self {
Self::Synthetic(ty) => *ty,
Self::Positional(ty) => *ty,
Self::Variadic(ty) => *ty,
Self::Keyword { name: _, ty } => *ty,
Self::Keywords(ty) => *ty,
/// Arguments for a single call, in source order, along with inferred types for each argument.
pub(crate) struct CallArgumentTypes<'a, 'db> {
arguments: CallArguments<'a>,
types: VecDeque<Type<'db>>,
}
impl<'a, 'db> CallArgumentTypes<'a, 'db> {
/// Create a [`CallArgumentTypes`] with no arguments.
pub(crate) fn none() -> Self {
let arguments = CallArguments::default();
let types = VecDeque::default();
Self { arguments, types }
}
/// Create a [`CallArgumentTypes`] from an iterator over non-variadic positional argument
/// types.
pub(crate) fn positional(positional_tys: impl IntoIterator<Item = Type<'db>>) -> Self {
let types: VecDeque<_> = positional_tys.into_iter().collect();
let arguments = CallArguments(vec![Argument::Positional; types.len()].into());
Self { arguments, types }
}
/// Create a new [`CallArgumentTypes`] to store the inferred types of the arguments in a
/// [`CallArguments`]. Uses the provided callback to infer each argument type.
pub(crate) fn new<F>(arguments: CallArguments<'a>, mut f: F) -> Self
where
F: FnMut(usize, Argument<'a>) -> Type<'db>,
{
let types = arguments
.iter()
.enumerate()
.map(|(idx, argument)| f(idx, argument))
.collect();
Self { arguments, types }
}
/// Invoke a function with an optional extra synthetic argument (for a `self` or `cls`
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
/// function is invoked with the unmodified argument list.)
pub(crate) fn with_self<F, R>(&mut self, bound_self: Option<Type<'db>>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
if let Some(bound_self) = bound_self {
self.arguments.0.push_front(Argument::Synthetic);
self.types.push_front(bound_self);
}
let result = f(self);
if bound_self.is_some() {
self.arguments.0.pop_front();
self.types.pop_front();
}
result
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + '_ {
self.arguments.iter().zip(self.types.iter().copied())
}
}
impl<'a> Deref for CallArgumentTypes<'a, '_> {
type Target = CallArguments<'a>;
fn deref(&self) -> &CallArguments<'a> {
&self.arguments
}
}
impl<'a> DerefMut for CallArgumentTypes<'a, '_> {
fn deref_mut(&mut self) -> &mut CallArguments<'a> {
&mut self.arguments
}
}

View file

@ -3,21 +3,24 @@
//! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a
//! union of types, each of which might contain multiple overloads.
use std::borrow::Cow;
use smallvec::SmallVec;
use super::{
Argument, CallArguments, CallError, CallErrorKind, CallableSignature, InferContext, Signature,
Signatures, Type,
Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, CallableSignature,
InferContext, Signature, Signatures, Type,
};
use crate::db::Db;
use crate::symbol::{Boundness, Symbol};
use crate::types::diagnostic::{
CALL_NON_CALLABLE, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD,
PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT,
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
UNKNOWN_ARGUMENT,
};
use crate::types::signatures::{Parameter, ParameterForm};
use crate::types::{
todo_type, BoundMethodType, CallableType, ClassLiteralType, KnownClass, KnownFunction,
KnownInstanceType, UnionType,
};
use crate::types::signatures::Parameter;
use crate::types::{CallableType, UnionType};
use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
@ -26,29 +29,75 @@ use ruff_text_size::Ranged;
/// compatible with _all_ of the types in the union for the call to be valid.
///
/// It's guaranteed that the wrapped bindings have no errors.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug)]
pub(crate) struct Bindings<'db> {
pub(crate) callable_type: Type<'db>,
signatures: Signatures<'db>,
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union
/// type.
elements: SmallVec<[CallableBinding<'db>; 1]>,
/// Whether each argument will be used as a value and/or a type form in this call.
pub(crate) argument_forms: Box<[Option<ParameterForm>]>,
conflicting_forms: Box<[bool]>,
}
impl<'db> Bindings<'db> {
/// Binds the arguments of a call site against a signature.
/// Match the arguments of a call site against the parameters of a collection of possibly
/// unioned, possibly overloaded signatures.
///
/// The returned bindings provide the return type of the call, the bound types for all
/// The returned bindings tell you which parameter (in each signature) each argument was
/// matched against. You can then perform type inference on each argument with extra context
/// about the expected parameter types. (You do this by creating a [`CallArgumentTypes`] object
/// from the `arguments` that you match against.)
///
/// Once you have argument types available, you can call [`check_types`][Self::check_types] to
/// verify that each argument type is assignable to the corresponding parameter type.
pub(crate) fn match_parameters(
signatures: Signatures<'db>,
arguments: &mut CallArguments<'_>,
) -> Self {
let mut argument_forms = vec![None; arguments.len()];
let mut conflicting_forms = vec![false; arguments.len()];
let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures
.iter()
.map(|signature| {
CallableBinding::match_parameters(
signature,
arguments,
&mut argument_forms,
&mut conflicting_forms,
)
})
.collect();
Bindings {
signatures,
elements,
argument_forms: argument_forms.into(),
conflicting_forms: conflicting_forms.into(),
}
}
/// Verify that the type of each argument is assignable to type of the parameter that it was
/// matched to.
///
/// You must provide an `argument_types` that was created from the same `arguments` that you
/// provided to [`match_parameters`][Self::match_parameters].
///
/// We update the bindings to include the return type of the call, the bound types for all
/// parameters, and any errors resulting from binding the call, all for each union element and
/// overload (if any).
pub(crate) fn bind(
pub(crate) fn check_types(
mut self,
db: &'db dyn Db,
signatures: &Signatures<'db>,
arguments: &CallArguments<'_, 'db>,
argument_types: &mut CallArgumentTypes<'_, 'db>,
) -> Result<Self, CallError<'db>> {
let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures
.into_iter()
.map(|signature| CallableBinding::bind(db, signature, arguments))
.collect();
for (signature, element) in self.signatures.iter().zip(&mut self.elements) {
element.check_types(db, signature, argument_types);
}
self.evaluate_known_cases(db);
// In order of precedence:
//
@ -68,28 +117,28 @@ impl<'db> Bindings<'db> {
let mut all_ok = true;
let mut any_binding_error = false;
let mut all_not_callable = true;
for binding in &elements {
if self.conflicting_forms.contains(&true) {
all_ok = false;
any_binding_error = true;
all_not_callable = false;
}
for binding in &self.elements {
let result = binding.as_result();
all_ok &= result.is_ok();
any_binding_error |= matches!(result, Err(CallErrorKind::BindingError));
all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable));
}
let bindings = Bindings {
callable_type: signatures.callable_type,
elements,
};
if all_ok {
Ok(bindings)
Ok(self)
} else if any_binding_error {
Err(CallError(CallErrorKind::BindingError, Box::new(bindings)))
Err(CallError(CallErrorKind::BindingError, Box::new(self)))
} else if all_not_callable {
Err(CallError(CallErrorKind::NotCallable, Box::new(bindings)))
Err(CallError(CallErrorKind::NotCallable, Box::new(self)))
} else {
Err(CallError(
CallErrorKind::PossiblyNotCallable,
Box::new(bindings),
Box::new(self),
))
}
}
@ -98,6 +147,10 @@ impl<'db> Bindings<'db> {
self.elements.len() == 1
}
pub(crate) fn callable_type(&self) -> Type<'db> {
self.signatures.callable_type
}
/// Returns the return type of the call. For successful calls, this is the actual return type.
/// For calls with binding errors, this is a type that best approximates the return type. For
/// types that are not callable, returns `Type::Unknown`.
@ -122,12 +175,22 @@ impl<'db> Bindings<'db> {
node,
format_args!(
"Object of type `{}` is not callable",
self.callable_type.display(context.db())
self.callable_type().display(context.db())
),
);
return;
}
for (index, conflicting_form) in self.conflicting_forms.iter().enumerate() {
if *conflicting_form {
context.report_lint(
&CONFLICTING_ARGUMENT_FORMS,
BindingError::get_node(node, Some(index)),
format_args!("Argument is used as both a value and a type form in call"),
);
}
}
// TODO: We currently only report errors for the first union element. Ideally, we'd report
// an error saying that the union type can't be called, followed by subdiagnostics
// explaining why.
@ -135,6 +198,286 @@ impl<'db> Bindings<'db> {
first.report_diagnostics(context, node);
}
}
/// Evaluates the return type of certain known callables, where we have special-case logic to
/// determine the return type in a way that isn't directly expressible in the type system.
fn evaluate_known_cases(&mut self, db: &'db dyn Db) {
// Each special case listed here should have a corresponding clause in `Type::signatures`.
for binding in &mut self.elements {
let binding_type = binding.callable_type;
let Some((overload_index, overload)) = binding.matching_overload_mut() else {
continue;
};
match binding_type {
Type::Callable(CallableType::MethodWrapperDunderGet(function)) => {
if function.has_known_class_decorator(db, KnownClass::Classmethod)
&& function.decorators(db).len() == 1
{
match overload.parameter_types() {
[_, Some(owner)] => {
overload.set_return_type(Type::Callable(
CallableType::BoundMethod(BoundMethodType::new(
db, function, *owner,
)),
));
}
[Some(instance), None] => {
overload.set_return_type(Type::Callable(
CallableType::BoundMethod(BoundMethodType::new(
db,
function,
instance.to_meta_type(db),
)),
));
}
_ => {}
}
} else if let [Some(first), _] = overload.parameter_types() {
if first.is_none(db) {
overload.set_return_type(Type::FunctionLiteral(function));
} else {
overload.set_return_type(Type::Callable(CallableType::BoundMethod(
BoundMethodType::new(db, function, *first),
)));
}
}
}
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] =
overload.parameter_types()
{
if function.has_known_class_decorator(db, KnownClass::Classmethod)
&& function.decorators(db).len() == 1
{
match overload.parameter_types() {
[_, _, Some(owner)] => {
overload.set_return_type(Type::Callable(
CallableType::BoundMethod(BoundMethodType::new(
db, *function, *owner,
)),
));
}
[_, Some(instance), None] => {
overload.set_return_type(Type::Callable(
CallableType::BoundMethod(BoundMethodType::new(
db,
*function,
instance.to_meta_type(db),
)),
));
}
_ => {}
}
} else {
match overload.parameter_types() {
[_, Some(instance), _] if instance.is_none(db) => {
overload.set_return_type(*function_ty);
}
[_, Some(Type::KnownInstance(KnownInstanceType::TypeAliasType(
type_alias,
))), Some(Type::ClassLiteral(ClassLiteralType { class }))]
if class.is_known(db, KnownClass::TypeAliasType)
&& function.name(db) == "__name__" =>
{
overload.set_return_type(Type::string_literal(
db,
type_alias.name(db),
));
}
[_, Some(Type::KnownInstance(KnownInstanceType::TypeVar(typevar))), Some(Type::ClassLiteral(ClassLiteralType { class }))]
if class.is_known(db, KnownClass::TypeVar)
&& function.name(db) == "__name__" =>
{
overload.set_return_type(Type::string_literal(
db,
typevar.name(db),
));
}
[_, Some(_), _]
if function
.has_known_class_decorator(db, KnownClass::Property) =>
{
overload.set_return_type(todo_type!("@property"));
}
[_, Some(instance), _] => {
overload.set_return_type(Type::Callable(
CallableType::BoundMethod(BoundMethodType::new(
db, *function, *instance,
)),
));
}
_ => {}
}
}
}
}
Type::FunctionLiteral(function_type) => match function_type.known(db) {
Some(KnownFunction::IsEquivalentTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(
ty_a.is_equivalent_to(db, *ty_b),
));
}
}
Some(KnownFunction::IsSubtypeOf) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(
ty_a.is_subtype_of(db, *ty_b),
));
}
}
Some(KnownFunction::IsAssignableTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(
ty_a.is_assignable_to(db, *ty_b),
));
}
}
Some(KnownFunction::IsDisjointFrom) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(
ty_a.is_disjoint_from(db, *ty_b),
));
}
}
Some(KnownFunction::IsGradualEquivalentTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(
ty_a.is_gradual_equivalent_to(db, *ty_b),
));
}
}
Some(KnownFunction::IsFullyStatic) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db)));
}
}
Some(KnownFunction::IsSingleton) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db)));
}
}
Some(KnownFunction::IsSingleValued) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db)));
}
}
Some(KnownFunction::Len) => {
if let [Some(first_arg)] = overload.parameter_types() {
if let Some(len_ty) = first_arg.len(db) {
overload.set_return_type(len_ty);
}
};
}
Some(KnownFunction::Repr) => {
if let [Some(first_arg)] = overload.parameter_types() {
overload.set_return_type(first_arg.repr(db));
};
}
Some(KnownFunction::Cast) => {
if let [Some(casted_ty), Some(_)] = overload.parameter_types() {
overload.set_return_type(*casted_ty);
}
}
Some(KnownFunction::Overload) => {
overload.set_return_type(todo_type!("overload(..) return type"));
}
Some(KnownFunction::GetattrStatic) => {
let [Some(instance_ty), Some(attr_name), default] =
overload.parameter_types()
else {
continue;
};
let Some(attr_name) = attr_name.into_string_literal() else {
continue;
};
let default = if let Some(default) = default {
*default
} else {
Type::Never
};
let union_with_default = |ty| UnionType::from_elements(db, [ty, default]);
// TODO: we could emit a diagnostic here (if default is not set)
overload.set_return_type(
match instance_ty.static_member(db, attr_name.value(db)) {
Symbol::Type(ty, Boundness::Bound) => {
if instance_ty.is_fully_static(db) {
ty
} else {
// Here, we attempt to model the fact that an attribute lookup on
// a non-fully static type could fail. This is an approximation,
// as there are gradual types like `tuple[Any]`, on which a lookup
// of (e.g. of the `index` method) would always succeed.
union_with_default(ty)
}
}
Symbol::Type(ty, Boundness::PossiblyUnbound) => {
union_with_default(ty)
}
Symbol::Unbound => default,
},
);
}
_ => {}
},
Type::ClassLiteral(ClassLiteralType { class }) => match class.known(db) {
Some(KnownClass::Bool) => match overload.parameter_types() {
[Some(arg)] => overload.set_return_type(arg.bool(db).into_type(db)),
[None] => overload.set_return_type(Type::BooleanLiteral(false)),
_ => {}
},
Some(KnownClass::Str) if overload_index == 0 => {
match overload.parameter_types() {
[Some(arg)] => overload.set_return_type(arg.str(db)),
[None] => overload.set_return_type(Type::string_literal(db, "")),
_ => {}
}
}
Some(KnownClass::Type) if overload_index == 0 => {
if let [Some(arg)] = overload.parameter_types() {
overload.set_return_type(arg.to_meta_type(db));
}
}
_ => {}
},
// Not a special case
_ => {}
}
}
}
}
impl<'a, 'db> IntoIterator for &'a Bindings<'db> {
@ -170,7 +513,7 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> {
/// overloads, we store this error information for each overload.
///
/// [overloads]: https://github.com/python/typing/pull/1839
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug)]
pub(crate) struct CallableBinding<'db> {
pub(crate) callable_type: Type<'db>,
pub(crate) signature_type: Type<'db>,
@ -184,39 +527,55 @@ pub(crate) struct CallableBinding<'db> {
}
impl<'db> CallableBinding<'db> {
/// Bind a [`CallArguments`] against a [`CallableSignature`].
///
/// The returned [`CallableBinding`] provides the return type of the call, the bound types for
/// all parameters, and any errors resulting from binding the call.
fn bind(
db: &'db dyn Db,
fn match_parameters(
signature: &CallableSignature<'db>,
arguments: &CallArguments<'_, 'db>,
arguments: &mut CallArguments<'_>,
argument_forms: &mut [Option<ParameterForm>],
conflicting_forms: &mut [bool],
) -> Self {
// If this callable is a bound method, prepend the self instance onto the arguments list
// before checking.
let arguments = if let Some(bound_type) = signature.bound_type {
Cow::Owned(arguments.with_self(bound_type))
} else {
Cow::Borrowed(arguments)
};
arguments.with_self(signature.bound_type, |arguments| {
// TODO: This checks every overload. In the proposed more detailed call checking spec [1],
// arguments are checked for arity first, and are only checked for type assignability against
// the matching overloads. Make sure to implement that as part of separating call binding into
// two phases.
//
// [1] https://github.com/python/typing/pull/1839
let overloads = signature
.into_iter()
.map(|signature| {
Binding::match_parameters(
signature,
arguments,
argument_forms,
conflicting_forms,
)
})
.collect();
// TODO: This checks every overload. In the proposed more detailed call checking spec [1],
// arguments are checked for arity first, and are only checked for type assignability against
// the matching overloads. Make sure to implement that as part of separating call binding into
// two phases.
//
// [1] https://github.com/python/typing/pull/1839
let overloads = signature
.into_iter()
.map(|signature| Binding::bind(db, signature, arguments.as_ref()))
.collect();
CallableBinding {
callable_type: signature.callable_type,
signature_type: signature.signature_type,
dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound,
overloads,
}
CallableBinding {
callable_type: signature.callable_type,
signature_type: signature.signature_type,
dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound,
overloads,
}
})
}
fn check_types(
&mut self,
db: &'db dyn Db,
signature: &CallableSignature<'db>,
argument_types: &mut CallArgumentTypes<'_, 'db>,
) {
// If this callable is a bound method, prepend the self instance onto the arguments list
// before checking.
argument_types.with_self(signature.bound_type, |argument_types| {
for (signature, overload) in signature.iter().zip(&mut self.overloads) {
overload.check_types(db, signature, argument_types);
}
});
}
fn as_result(&self) -> Result<(), CallErrorKind> {
@ -333,27 +692,35 @@ impl<'db> CallableBinding<'db> {
}
/// Binding information for one of the overloads of a callable.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug)]
pub(crate) struct Binding<'db> {
/// Return type of the call.
return_ty: Type<'db>,
/// Bound types for parameters, in parameter source order.
parameter_tys: Box<[Type<'db>]>,
/// The formal parameter that each argument is matched with, in argument source order, or
/// `None` if the argument was not matched to any parameter.
argument_parameters: Box<[Option<usize>]>,
/// Bound types for parameters, in parameter source order, or `None` if no argument was matched
/// to that parameter.
parameter_tys: Box<[Option<Type<'db>>]>,
/// Call binding errors, if any.
errors: Vec<BindingError<'db>>,
}
impl<'db> Binding<'db> {
fn bind(
db: &'db dyn Db,
fn match_parameters(
signature: &Signature<'db>,
arguments: &CallArguments<'_, 'db>,
arguments: &CallArguments<'_>,
argument_forms: &mut [Option<ParameterForm>],
conflicting_forms: &mut [bool],
) -> Self {
let parameters = signature.parameters();
// The type assigned to each parameter at this call site.
let mut parameter_tys = vec![None; parameters.len()];
// The parameter that each argument is matched with.
let mut argument_parameters = vec![None; arguments.len()];
// Whether each parameter has been matched with an argument.
let mut parameter_matched = vec![false; parameters.len()];
let mut errors = vec![];
let mut next_positional = 0;
let mut first_excess_positional = None;
@ -370,9 +737,9 @@ impl<'db> Binding<'db> {
}
};
for (argument_index, argument) in arguments.iter().enumerate() {
let (index, parameter, argument_ty, positional) = match argument {
Argument::Positional(ty) | Argument::Synthetic(ty) => {
if matches!(argument, Argument::Synthetic(_)) {
let (index, parameter, positional) = match argument {
Argument::Positional | Argument::Synthetic => {
if matches!(argument, Argument::Synthetic) {
num_synthetic_args += 1;
}
let Some((index, parameter)) = parameters
@ -385,9 +752,9 @@ impl<'db> Binding<'db> {
continue;
};
next_positional += 1;
(index, parameter, ty, !parameter.is_variadic())
(index, parameter, !parameter.is_variadic())
}
Argument::Keyword { name, ty } => {
Argument::Keyword(name) => {
let Some((index, parameter)) = parameters
.keyword_by_name(name)
.or_else(|| parameters.keyword_variadic())
@ -398,35 +765,33 @@ impl<'db> Binding<'db> {
});
continue;
};
(index, parameter, ty, false)
(index, parameter, false)
}
Argument::Variadic(_) | Argument::Keywords(_) => {
Argument::Variadic | Argument::Keywords => {
// TODO
continue;
}
};
if let Some(expected_ty) = parameter.annotated_type() {
if !argument_ty.is_assignable_to(db, expected_ty) {
errors.push(BindingError::InvalidArgumentType {
parameter: ParameterContext::new(parameter, index, positional),
argument_index: get_argument_index(argument_index, num_synthetic_args),
expected_ty,
provided_ty: *argument_ty,
});
if !matches!(argument, Argument::Synthetic) {
if let Some(existing) =
argument_forms[argument_index - num_synthetic_args].replace(parameter.form)
{
if existing != parameter.form {
conflicting_forms[argument_index - num_synthetic_args] = true;
}
}
}
if let Some(existing) = parameter_tys[index].replace(*argument_ty) {
if parameter.is_variadic() || parameter.is_keyword_variadic() {
let union = UnionType::from_elements(db, [existing, *argument_ty]);
parameter_tys[index].replace(union);
} else {
if parameter_matched[index] {
if !parameter.is_variadic() && !parameter.is_keyword_variadic() {
errors.push(BindingError::ParameterAlreadyAssigned {
argument_index: get_argument_index(argument_index, num_synthetic_args),
parameter: ParameterContext::new(parameter, index, positional),
});
}
}
argument_parameters[argument_index] = Some(index);
parameter_matched[index] = true;
}
if let Some(first_excess_argument_index) = first_excess_positional {
errors.push(BindingError::TooManyPositionalArguments {
@ -439,8 +804,8 @@ impl<'db> Binding<'db> {
});
}
let mut missing = vec![];
for (index, bound_ty) in parameter_tys.iter().enumerate() {
if bound_ty.is_none() {
for (index, matched) in parameter_matched.iter().copied().enumerate() {
if !matched {
let param = &parameters[index];
if param.is_variadic()
|| param.is_keyword_variadic()
@ -461,14 +826,65 @@ impl<'db> Binding<'db> {
Self {
return_ty: signature.return_ty.unwrap_or(Type::unknown()),
parameter_tys: parameter_tys
.into_iter()
.map(|opt_ty| opt_ty.unwrap_or(Type::unknown()))
.collect(),
argument_parameters: argument_parameters.into_boxed_slice(),
parameter_tys: vec![None; parameters.len()].into_boxed_slice(),
errors,
}
}
fn check_types(
&mut self,
db: &'db dyn Db,
signature: &Signature<'db>,
argument_types: &CallArgumentTypes<'_, 'db>,
) {
let parameters = signature.parameters();
let mut num_synthetic_args = 0;
let get_argument_index = |argument_index: usize, num_synthetic_args: usize| {
if argument_index >= num_synthetic_args {
// Adjust the argument index to skip synthetic args, which don't appear at the call
// site and thus won't be in the Call node arguments list.
Some(argument_index - num_synthetic_args)
} else {
// we are erroring on a synthetic argument, we'll just emit the diagnostic on the
// entire Call node, since there's no argument node for this argument at the call site
None
}
};
for (argument_index, (argument, argument_type)) in argument_types.iter().enumerate() {
if matches!(argument, Argument::Synthetic) {
num_synthetic_args += 1;
}
let Some(parameter_index) = self.argument_parameters[argument_index] else {
// There was an error with argument when matching parameters, so don't bother
// type-checking it.
continue;
};
let parameter = &parameters[parameter_index];
if let Some(expected_ty) = parameter.annotated_type() {
if !argument_type.is_assignable_to(db, expected_ty) {
let positional = matches!(argument, Argument::Positional | Argument::Synthetic)
&& !parameter.is_variadic();
self.errors.push(BindingError::InvalidArgumentType {
parameter: ParameterContext::new(parameter, parameter_index, positional),
argument_index: get_argument_index(argument_index, num_synthetic_args),
expected_ty,
provided_ty: argument_type,
});
}
}
// We still update the actual type of the parameter in this binding to match the
// argument, even if the argument type is not assignable to the expected parameter
// type.
if let Some(existing) = self.parameter_tys[parameter_index].replace(argument_type) {
// We already verified in `match_parameters` that we only match multiple arguments
// with variadic parameters.
let union = UnionType::from_elements(db, [existing, argument_type]);
self.parameter_tys[parameter_index] = Some(union);
}
}
}
pub(crate) fn set_return_type(&mut self, return_ty: Type<'db>) {
self.return_ty = return_ty;
}
@ -477,7 +893,7 @@ impl<'db> Binding<'db> {
self.return_ty
}
pub(crate) fn parameter_types(&self) -> &[Type<'db>] {
pub(crate) fn parameter_types(&self) -> &[Option<Type<'db>>] {
&self.parameter_tys
}

View file

@ -11,7 +11,7 @@ use crate::{
Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers,
},
types::{
definition_expression_type, CallArguments, CallError, CallErrorKind, DynamicType,
definition_expression_type, CallArgumentTypes, CallError, CallErrorKind, DynamicType,
MetaclassCandidate, TupleType, UnionBuilder, UnionType,
},
Db, KnownModule, Program,
@ -279,13 +279,13 @@ impl<'db> Class<'db> {
let namespace = KnownClass::Dict.to_instance(db);
// TODO: Other keyword arguments?
let arguments = CallArguments::positional([name, bases, namespace]);
let arguments = CallArgumentTypes::positional([name, bases, namespace]);
let return_ty_result = match metaclass.try_call(db, &arguments) {
let return_ty_result = match metaclass.try_call(db, arguments) {
Ok(bindings) => Ok(bindings.return_type(db)),
Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError {
kind: MetaclassErrorKind::NotCallable(bindings.callable_type),
kind: MetaclassErrorKind::NotCallable(bindings.callable_type()),
}),
// TODO we should also check for binding errors that would indicate the metaclass

View file

@ -24,6 +24,7 @@ use std::sync::Arc;
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&CALL_NON_CALLABLE);
registry.register_lint(&CALL_POSSIBLY_UNBOUND_METHOD);
registry.register_lint(&CONFLICTING_ARGUMENT_FORMS);
registry.register_lint(&CONFLICTING_DECLARATIONS);
registry.register_lint(&CONFLICTING_METACLASS);
registry.register_lint(&CYCLIC_CLASS_DEFINITION);
@ -106,6 +107,16 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks whether an argument is used as both a value and a type form in a call
pub(crate) static CONFLICTING_ARGUMENT_FORMS = {
summary: "detects when an argument is used as both a value and a type form in a call",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// TODO #14889
pub(crate) static CONFLICTING_DECLARATIONS = {

View file

@ -476,8 +476,7 @@ mod tests {
use crate::db::tests::setup_db;
use crate::types::{
KnownClass, Parameter, ParameterKind, Parameters, Signature, SliceLiteralType,
StringLiteralType, Type,
KnownClass, Parameter, Parameters, Signature, SliceLiteralType, StringLiteralType, Type,
};
use crate::Db;
@ -574,13 +573,7 @@ mod tests {
assert_eq!(
display_signature(
&db,
[Parameter::new(
Some(Type::none(&db)),
ParameterKind::PositionalOnly {
name: None,
default_ty: None
}
)],
[Parameter::positional_only(None).with_annotated_type(Type::none(&db))],
Some(Type::none(&db))
),
"(None, /) -> None"
@ -591,20 +584,11 @@ mod tests {
display_signature(
&db,
[
Parameter::new(
None,
ParameterKind::PositionalOrKeyword {
name: Name::new_static("x"),
default_ty: Some(KnownClass::Int.to_instance(&db))
}
),
Parameter::new(
Some(KnownClass::Str.to_instance(&db)),
ParameterKind::PositionalOrKeyword {
name: Name::new_static("y"),
default_ty: Some(KnownClass::Str.to_instance(&db))
}
)
Parameter::positional_or_keyword(Name::new_static("x"))
.with_default_type(KnownClass::Int.to_instance(&db)),
Parameter::positional_or_keyword(Name::new_static("y"))
.with_annotated_type(KnownClass::Str.to_instance(&db))
.with_default_type(KnownClass::Str.to_instance(&db)),
],
Some(Type::none(&db))
),
@ -616,20 +600,8 @@ mod tests {
display_signature(
&db,
[
Parameter::new(
None,
ParameterKind::PositionalOnly {
name: Some(Name::new_static("x")),
default_ty: None
}
),
Parameter::new(
None,
ParameterKind::PositionalOnly {
name: Some(Name::new_static("y")),
default_ty: None
}
)
Parameter::positional_only(Some(Name::new_static("x"))),
Parameter::positional_only(Some(Name::new_static("y"))),
],
Some(Type::none(&db))
),
@ -641,20 +613,8 @@ mod tests {
display_signature(
&db,
[
Parameter::new(
None,
ParameterKind::PositionalOnly {
name: Some(Name::new_static("x")),
default_ty: None
}
),
Parameter::new(
None,
ParameterKind::PositionalOrKeyword {
name: Name::new_static("y"),
default_ty: None
}
)
Parameter::positional_only(Some(Name::new_static("x"))),
Parameter::positional_or_keyword(Name::new_static("y")),
],
Some(Type::none(&db))
),
@ -666,20 +626,8 @@ mod tests {
display_signature(
&db,
[
Parameter::new(
None,
ParameterKind::KeywordOnly {
name: Name::new_static("x"),
default_ty: None
}
),
Parameter::new(
None,
ParameterKind::KeywordOnly {
name: Name::new_static("y"),
default_ty: None
}
)
Parameter::keyword_only(Name::new_static("x")),
Parameter::keyword_only(Name::new_static("y")),
],
Some(Type::none(&db))
),
@ -691,20 +639,8 @@ mod tests {
display_signature(
&db,
[
Parameter::new(
None,
ParameterKind::PositionalOrKeyword {
name: Name::new_static("x"),
default_ty: None
}
),
Parameter::new(
None,
ParameterKind::KeywordOnly {
name: Name::new_static("y"),
default_ty: None
}
)
Parameter::positional_or_keyword(Name::new_static("x")),
Parameter::keyword_only(Name::new_static("y")),
],
Some(Type::none(&db))
),
@ -716,74 +652,28 @@ mod tests {
display_signature(
&db,
[
Parameter::new(
None,
ParameterKind::PositionalOnly {
name: Some(Name::new_static("a")),
default_ty: None
},
),
Parameter::new(
Some(KnownClass::Int.to_instance(&db)),
ParameterKind::PositionalOnly {
name: Some(Name::new_static("b")),
default_ty: None
},
),
Parameter::new(
None,
ParameterKind::PositionalOnly {
name: Some(Name::new_static("c")),
default_ty: Some(Type::IntLiteral(1)),
},
),
Parameter::new(
Some(KnownClass::Int.to_instance(&db)),
ParameterKind::PositionalOnly {
name: Some(Name::new_static("d")),
default_ty: Some(Type::IntLiteral(2)),
},
),
Parameter::new(
None,
ParameterKind::PositionalOrKeyword {
name: Name::new_static("e"),
default_ty: Some(Type::IntLiteral(3)),
},
),
Parameter::new(
Some(KnownClass::Int.to_instance(&db)),
ParameterKind::PositionalOrKeyword {
name: Name::new_static("f"),
default_ty: Some(Type::IntLiteral(4)),
},
),
Parameter::new(
Some(Type::object(&db)),
ParameterKind::Variadic {
name: Name::new_static("args")
},
),
Parameter::new(
None,
ParameterKind::KeywordOnly {
name: Name::new_static("g"),
default_ty: Some(Type::IntLiteral(5)),
},
),
Parameter::new(
Some(KnownClass::Int.to_instance(&db)),
ParameterKind::KeywordOnly {
name: Name::new_static("h"),
default_ty: Some(Type::IntLiteral(6)),
},
),
Parameter::new(
Some(KnownClass::Str.to_instance(&db)),
ParameterKind::KeywordVariadic {
name: Name::new_static("kwargs")
},
),
Parameter::positional_only(Some(Name::new_static("a"))),
Parameter::positional_only(Some(Name::new_static("b")))
.with_annotated_type(KnownClass::Int.to_instance(&db)),
Parameter::positional_only(Some(Name::new_static("c")))
.with_default_type(Type::IntLiteral(1)),
Parameter::positional_only(Some(Name::new_static("d")))
.with_annotated_type(KnownClass::Int.to_instance(&db))
.with_default_type(Type::IntLiteral(2)),
Parameter::positional_or_keyword(Name::new_static("e"))
.with_default_type(Type::IntLiteral(3)),
Parameter::positional_or_keyword(Name::new_static("f"))
.with_annotated_type(KnownClass::Int.to_instance(&db))
.with_default_type(Type::IntLiteral(4)),
Parameter::variadic(Name::new_static("args"))
.with_annotated_type(Type::object(&db)),
Parameter::keyword_only(Name::new_static("g"))
.with_default_type(Type::IntLiteral(5)),
Parameter::keyword_only(Name::new_static("h"))
.with_annotated_type(KnownClass::Int.to_instance(&db))
.with_default_type(Type::IntLiteral(6)),
Parameter::keyword_variadic(Name::new_static("kwargs"))
.with_annotated_type(KnownClass::Str.to_instance(&db)),
],
Some(KnownClass::Bytes.to_instance(&db))
),

View file

@ -61,7 +61,7 @@ use crate::symbol::{
module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations,
typing_extensions_symbol, Boundness, LookupError,
};
use crate::types::call::{Argument, CallArguments, CallError};
use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError};
use crate::types::diagnostic::{
report_implicit_return_type, report_invalid_arguments_to_annotated,
report_invalid_arguments_to_callable, report_invalid_assignment,
@ -79,12 +79,12 @@ use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
class::MetaclassErrorKind, todo_type, Class, DynamicType, FunctionType, InstanceType,
IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType,
MetaclassCandidate, Parameter, Parameters, SliceLiteralType, SubclassOfType, Symbol,
SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
MetaclassCandidate, Parameter, ParameterForm, Parameters, SliceLiteralType, SubclassOfType,
Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder,
UnionType,
};
use crate::types::{CallableType, GeneralCallableType, ParameterKind, Signature};
use crate::types::{CallableType, GeneralCallableType, Signature};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
use crate::Db;
@ -102,7 +102,7 @@ use super::slots::check_class_slots;
use super::string_annotation::{
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION,
};
use super::{CallDunderError, ParameterExpectation, ParameterExpectations};
use super::CallDunderError;
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
@ -1141,7 +1141,9 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_type_parameters(type_params);
if let Some(arguments) = class.arguments.as_deref() {
self.infer_arguments(arguments, ParameterExpectations::default());
let call_arguments = Self::parse_arguments(arguments);
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
self.infer_argument_types(arguments, call_arguments, &argument_forms);
}
}
@ -1517,7 +1519,7 @@ impl<'db> TypeInferenceBuilder<'db> {
) {
if let Some(annotation) = parameter.annotation() {
let _annotated_ty = self.file_expression_type(annotation);
// TODO `tuple[annotated_ty, ...]`
// TODO `tuple[annotated_type, ...]`
let ty = KnownClass::Tuple.to_instance(self.db());
self.add_declaration_with_binding(
parameter.into(),
@ -1548,7 +1550,7 @@ impl<'db> TypeInferenceBuilder<'db> {
) {
if let Some(annotation) = parameter.annotation() {
let _annotated_ty = self.file_expression_type(annotation);
// TODO `dict[str, annotated_ty]`
// TODO `dict[str, annotated_type]`
let ty = KnownClass::Dict.to_instance(self.db());
self.add_declaration_with_binding(
parameter.into(),
@ -2276,7 +2278,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let successful_call = meta_dunder_set
.try_call(
db,
&CallArguments::positional([meta_attr_ty, object_ty, value_ty]),
CallArgumentTypes::positional([meta_attr_ty, object_ty, value_ty]),
)
.is_ok();
@ -2375,7 +2377,11 @@ impl<'db> TypeInferenceBuilder<'db> {
let successful_call = meta_dunder_set
.try_call(
db,
&CallArguments::positional([meta_attr_ty, object_ty, value_ty]),
CallArgumentTypes::positional([
meta_attr_ty,
object_ty,
value_ty,
]),
)
.is_ok();
@ -2783,7 +2789,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let call = target_type.try_call_dunder(
db,
op.in_place_dunder(),
&CallArguments::positional([value_type]),
CallArgumentTypes::positional([value_type]),
);
match call {
@ -3232,45 +3238,22 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_expression(expression)
}
fn infer_arguments<'a>(
&mut self,
arguments: &'a ast::Arguments,
parameter_expectations: ParameterExpectations,
) -> CallArguments<'a, 'db> {
fn parse_arguments(arguments: &ast::Arguments) -> CallArguments<'_> {
arguments
.arguments_source_order()
.enumerate()
.map(|(index, arg_or_keyword)| {
let infer_argument_type = match parameter_expectations.expectation_at_index(index) {
ParameterExpectation::TypeExpression => Self::infer_type_expression,
ParameterExpectation::ValueExpression => Self::infer_expression,
};
.map(|arg_or_keyword| {
match arg_or_keyword {
ast::ArgOrKeyword::Arg(arg) => match arg {
ast::Expr::Starred(ast::ExprStarred {
value,
range: _,
ctx: _,
}) => {
let ty = infer_argument_type(self, value);
self.store_expression_type(arg, ty);
Argument::Variadic(ty)
}
ast::Expr::Starred(ast::ExprStarred { .. }) => Argument::Variadic,
// TODO diagnostic if after a keyword argument
_ => Argument::Positional(infer_argument_type(self, arg)),
_ => Argument::Positional,
},
ast::ArgOrKeyword::Keyword(ast::Keyword {
arg,
value,
range: _,
}) => {
let ty = infer_argument_type(self, value);
ast::ArgOrKeyword::Keyword(ast::Keyword { arg, .. }) => {
if let Some(arg) = arg {
Argument::Keyword { name: &arg.id, ty }
Argument::Keyword(&arg.id)
} else {
// TODO diagnostic if not last
Argument::Keywords(ty)
Argument::Keywords
}
}
}
@ -3278,6 +3261,44 @@ impl<'db> TypeInferenceBuilder<'db> {
.collect()
}
fn infer_argument_types<'a>(
&mut self,
ast_arguments: &ast::Arguments,
arguments: CallArguments<'a>,
argument_forms: &[Option<ParameterForm>],
) -> CallArgumentTypes<'a, 'db> {
let mut ast_arguments = ast_arguments.arguments_source_order();
CallArgumentTypes::new(arguments, |index, _| {
let arg_or_keyword = ast_arguments
.next()
.expect("argument lists should have consistent lengths");
match arg_or_keyword {
ast::ArgOrKeyword::Arg(arg) => match arg {
ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
let ty = self.infer_argument_type(value, argument_forms[index]);
self.store_expression_type(arg, ty);
ty
}
_ => self.infer_argument_type(arg, argument_forms[index]),
},
ast::ArgOrKeyword::Keyword(ast::Keyword { value, .. }) => {
self.infer_argument_type(value, argument_forms[index])
}
}
})
}
fn infer_argument_type(
&mut self,
ast_argument: &ast::Expr,
form: Option<ParameterForm>,
) -> Type<'db> {
match form {
None | Some(ParameterForm::Value) => self.infer_expression(ast_argument),
Some(ParameterForm::Type) => self.infer_type_expression(ast_argument),
}
}
fn infer_optional_expression(&mut self, expression: Option<&ast::Expr>) -> Option<Type<'db>> {
expression.map(|expr| self.infer_expression(expr))
}
@ -3769,64 +3790,44 @@ impl<'db> TypeInferenceBuilder<'db> {
let positional_only = parameters
.posonlyargs
.iter()
.map(|parameter| {
Parameter::new(
None,
ParameterKind::PositionalOnly {
name: Some(parameter.name().id.clone()),
default_ty: parameter
.default()
.map(|default| self.infer_expression(default)),
},
)
.map(|param| {
let mut parameter = Parameter::positional_only(Some(param.name().id.clone()));
if let Some(default) = param.default() {
parameter = parameter.with_default_type(self.infer_expression(default));
}
parameter
})
.collect::<Vec<_>>();
let positional_or_keyword = parameters
.args
.iter()
.map(|parameter| {
Parameter::new(
None,
ParameterKind::PositionalOrKeyword {
name: parameter.name().id.clone(),
default_ty: parameter
.default()
.map(|default| self.infer_expression(default)),
},
)
.map(|param| {
let mut parameter = Parameter::positional_or_keyword(param.name().id.clone());
if let Some(default) = param.default() {
parameter = parameter.with_default_type(self.infer_expression(default));
}
parameter
})
.collect::<Vec<_>>();
let variadic = parameters.vararg.as_ref().map(|parameter| {
Parameter::new(
None,
ParameterKind::Variadic {
name: parameter.name.id.clone(),
},
)
});
let variadic = parameters
.vararg
.as_ref()
.map(|param| Parameter::variadic(param.name().id.clone()));
let keyword_only = parameters
.kwonlyargs
.iter()
.map(|parameter| {
Parameter::new(
None,
ParameterKind::KeywordOnly {
name: parameter.name().id.clone(),
default_ty: parameter
.default()
.map(|default| self.infer_expression(default)),
},
)
.map(|param| {
let mut parameter = Parameter::keyword_only(param.name().id.clone());
if let Some(default) = param.default() {
parameter = parameter.with_default_type(self.infer_expression(default));
}
parameter
})
.collect::<Vec<_>>();
let keyword_variadic = parameters.kwarg.as_ref().map(|parameter| {
Parameter::new(
None,
ParameterKind::KeywordVariadic {
name: parameter.name.id.clone(),
},
)
});
let keyword_variadic = parameters
.kwarg
.as_ref()
.map(|param| Parameter::keyword_variadic(param.name().id.clone()));
Parameters::new(
positional_only
@ -3856,16 +3857,17 @@ impl<'db> TypeInferenceBuilder<'db> {
arguments,
} = call_expression;
// We don't call `Type::try_call`, because we want to perform type inference on the
// arguments after matching them to parameters, but before checking that the argument types
// are assignable to any parameter annotations.
let mut call_arguments = Self::parse_arguments(arguments);
let function_type = self.infer_expression(func);
let signatures = function_type.signatures(self.db());
let bindings = Bindings::match_parameters(signatures, &mut call_arguments);
let mut call_argument_types =
self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms);
let parameter_expectations = function_type
.into_function_literal()
.and_then(|f| f.known(self.db()))
.map(KnownFunction::parameter_expectations)
.unwrap_or_default();
let call_arguments = self.infer_arguments(arguments, parameter_expectations);
match function_type.try_call(self.db(), &call_arguments) {
match bindings.check_types(self.db(), &mut call_argument_types) {
Ok(bindings) => {
for binding in &bindings {
let Some(known_function) = binding
@ -3882,7 +3884,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match known_function {
KnownFunction::RevealType => {
if let [revealed_type] = overload.parameter_types() {
if let [Some(revealed_type)] = overload.parameter_types() {
self.context.report_diagnostic(
call_expression,
DiagnosticId::RevealedType,
@ -3896,7 +3898,8 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
KnownFunction::AssertType => {
if let [actual_ty, asserted_ty] = overload.parameter_types() {
if let [Some(actual_ty), Some(asserted_ty)] = overload.parameter_types()
{
if !actual_ty.is_gradual_equivalent_to(self.db(), *asserted_ty) {
self.context.report_lint(
&TYPE_ASSERTION_FAILURE,
@ -3911,7 +3914,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
KnownFunction::StaticAssert => {
if let [parameter_ty, message] = overload.parameter_types() {
if let [Some(parameter_ty), message] = overload.parameter_types() {
let truthiness = match parameter_ty.try_bool(self.db()) {
Ok(truthiness) => truthiness,
Err(err) => {
@ -3934,8 +3937,9 @@ impl<'db> TypeInferenceBuilder<'db> {
};
if !truthiness.is_always_true() {
if let Some(message) =
message.into_string_literal().map(|s| &**s.value(self.db()))
if let Some(message) = message
.and_then(Type::into_string_literal)
.map(|s| &**s.value(self.db()))
{
self.context.report_lint(
&STATIC_ASSERT_ERROR,
@ -4352,7 +4356,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match operand_type.try_call_dunder(
self.db(),
unary_dunder_method,
&CallArguments::none(),
CallArgumentTypes::none(),
) {
Ok(outcome) => outcome.return_type(self.db()),
Err(e) => {
@ -4634,7 +4638,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.try_call_dunder(
self.db(),
reflected_dunder,
&CallArguments::positional([left_ty]),
CallArgumentTypes::positional([left_ty]),
)
.map(|outcome| outcome.return_type(self.db()))
.or_else(|_| {
@ -4642,7 +4646,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.try_call_dunder(
self.db(),
op.dunder(),
&CallArguments::positional([right_ty]),
CallArgumentTypes::positional([right_ty]),
)
.map(|outcome| outcome.return_type(self.db()))
})
@ -4654,7 +4658,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.try_call_dunder(
self.db(),
op.dunder(),
&CallArguments::positional([right_ty]),
CallArgumentTypes::positional([right_ty]),
)
.map(|outcome| outcome.return_type(self.db()))
.ok();
@ -4667,7 +4671,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.try_call_dunder(
self.db(),
op.reflected_dunder(),
&CallArguments::positional([left_ty]),
CallArgumentTypes::positional([left_ty]),
)
.map(|outcome| outcome.return_type(self.db()))
.ok()
@ -5318,7 +5322,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.try_call_dunder(
db,
op.dunder(),
&CallArguments::positional([Type::Instance(right)]),
CallArgumentTypes::positional([Type::Instance(right)]),
)
.map(|outcome| outcome.return_type(db))
.ok()
@ -5367,7 +5371,10 @@ impl<'db> TypeInferenceBuilder<'db> {
contains_dunder
.try_call(
db,
&CallArguments::positional([Type::Instance(right), Type::Instance(left)]),
CallArgumentTypes::positional([
Type::Instance(right),
Type::Instance(left),
]),
)
.map(|bindings| bindings.return_type(db))
.ok()
@ -5643,7 +5650,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match value_ty.try_call_dunder(
self.db(),
"__getitem__",
&CallArguments::positional([slice_ty]),
CallArgumentTypes::positional([slice_ty]),
) {
Ok(outcome) => return outcome.return_type(self.db()),
Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
@ -5664,7 +5671,7 @@ impl<'db> TypeInferenceBuilder<'db> {
value_node,
format_args!(
"Method `__getitem__` of type `{}` is not callable on object of type `{}`",
bindings.callable_type.display(self.db()),
bindings.callable_type().display(self.db()),
value_ty.display(self.db()),
),
);
@ -5705,7 +5712,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match ty.try_call(
self.db(),
&CallArguments::positional([value_ty, slice_ty]),
CallArgumentTypes::positional([value_ty, slice_ty]),
) {
Ok(bindings) => return bindings.return_type(self.db()),
Err(CallError(_, bindings)) => {
@ -5714,7 +5721,7 @@ impl<'db> TypeInferenceBuilder<'db> {
value_node,
format_args!(
"Method `__class_getitem__` of type `{}` is not callable on object of type `{}`",
bindings.callable_type.display(self.db()),
bindings.callable_type().display(self.db()),
value_ty.display(self.db()),
),
);
@ -6974,13 +6981,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Parameters::todo()
} else {
Parameters::new(parameter_types.iter().map(|param_type| {
Parameter::new(
Some(*param_type),
ParameterKind::PositionalOnly {
name: None,
default_ty: None,
},
)
Parameter::positional_only(None).with_annotated_type(*param_type)
}))
}
}

View file

@ -66,6 +66,10 @@ impl<'db> Signatures<'db> {
}
}
pub(crate) fn iter(&self) -> std::slice::Iter<'_, CallableSignature<'db>> {
self.elements.iter()
}
pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) {
if self.callable_type == before {
self.callable_type = after;
@ -87,7 +91,7 @@ impl<'a, 'db> IntoIterator for &'a Signatures<'db> {
type IntoIter = std::slice::Iter<'a, CallableSignature<'db>>;
fn into_iter(self) -> Self::IntoIter {
self.elements.iter()
self.iter()
}
}
@ -179,6 +183,10 @@ impl<'db> CallableSignature<'db> {
self
}
pub(crate) fn iter(&self) -> std::slice::Iter<'_, Signature<'db>> {
self.overloads.iter()
}
fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) {
if self.callable_type == before {
self.callable_type = after;
@ -191,7 +199,7 @@ impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> {
type IntoIter = std::slice::Iter<'a, Signature<'db>>;
fn into_iter(self) -> Self::IntoIter {
self.overloads.iter()
self.iter()
}
}
@ -306,18 +314,10 @@ impl<'db> Parameters<'db> {
pub(crate) fn todo() -> Self {
Self {
value: vec![
Parameter {
annotated_ty: Some(todo_type!("todo signature *args")),
kind: ParameterKind::Variadic {
name: Name::new_static("args"),
},
},
Parameter {
annotated_ty: Some(todo_type!("todo signature **kwargs")),
kind: ParameterKind::KeywordVariadic {
name: Name::new_static("kwargs"),
},
},
Parameter::variadic(Name::new_static("args"))
.with_annotated_type(todo_type!("todo signature *args")),
Parameter::keyword_variadic(Name::new_static("kwargs"))
.with_annotated_type(todo_type!("todo signature **kwargs")),
],
is_gradual: false,
}
@ -331,18 +331,10 @@ impl<'db> Parameters<'db> {
pub(crate) fn gradual_form() -> Self {
Self {
value: vec![
Parameter {
annotated_ty: Some(Type::Dynamic(DynamicType::Any)),
kind: ParameterKind::Variadic {
name: Name::new_static("args"),
},
},
Parameter {
annotated_ty: Some(Type::Dynamic(DynamicType::Any)),
kind: ParameterKind::KeywordVariadic {
name: Name::new_static("kwargs"),
},
},
Parameter::variadic(Name::new_static("args"))
.with_annotated_type(Type::Dynamic(DynamicType::Any)),
Parameter::keyword_variadic(Name::new_static("kwargs"))
.with_annotated_type(Type::Dynamic(DynamicType::Any)),
],
is_gradual: true,
}
@ -357,18 +349,10 @@ impl<'db> Parameters<'db> {
pub(crate) fn unknown() -> Self {
Self {
value: vec![
Parameter {
annotated_ty: Some(Type::Dynamic(DynamicType::Unknown)),
kind: ParameterKind::Variadic {
name: Name::new_static("args"),
},
},
Parameter {
annotated_ty: Some(Type::Dynamic(DynamicType::Unknown)),
kind: ParameterKind::KeywordVariadic {
name: Name::new_static("kwargs"),
},
},
Parameter::variadic(Name::new_static("args"))
.with_annotated_type(Type::Dynamic(DynamicType::Unknown)),
Parameter::keyword_variadic(Name::new_static("kwargs"))
.with_annotated_type(Type::Dynamic(DynamicType::Unknown)),
],
is_gradual: true,
}
@ -387,7 +371,7 @@ impl<'db> Parameters<'db> {
kwarg,
range: _,
} = parameters;
let default_ty = |param: &ast::ParameterWithDefault| {
let default_type = |param: &ast::ParameterWithDefault| {
param
.default()
.map(|default| definition_expression_type(db, definition, default))
@ -399,7 +383,7 @@ impl<'db> Parameters<'db> {
&arg.parameter,
ParameterKind::PositionalOnly {
name: Some(arg.parameter.name.id.clone()),
default_ty: default_ty(arg),
default_type: default_type(arg),
},
)
});
@ -410,7 +394,7 @@ impl<'db> Parameters<'db> {
&arg.parameter,
ParameterKind::PositionalOrKeyword {
name: arg.parameter.name.id.clone(),
default_ty: default_ty(arg),
default_type: default_type(arg),
},
)
});
@ -431,7 +415,7 @@ impl<'db> Parameters<'db> {
&arg.parameter,
ParameterKind::KeywordOnly {
name: arg.parameter.name.id.clone(),
default_ty: default_ty(arg),
default_type: default_type(arg),
},
)
});
@ -531,14 +515,82 @@ impl<'db> std::ops::Index<usize> for Parameters<'db> {
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
pub(crate) struct Parameter<'db> {
/// Annotated type of the parameter.
annotated_ty: Option<Type<'db>>,
annotated_type: Option<Type<'db>>,
kind: ParameterKind<'db>,
pub(crate) form: ParameterForm,
}
impl<'db> Parameter<'db> {
pub(crate) fn new(annotated_ty: Option<Type<'db>>, kind: ParameterKind<'db>) -> Self {
Self { annotated_ty, kind }
pub(crate) fn positional_only(name: Option<Name>) -> Self {
Self {
annotated_type: None,
kind: ParameterKind::PositionalOnly {
name,
default_type: None,
},
form: ParameterForm::Value,
}
}
pub(crate) fn positional_or_keyword(name: Name) -> Self {
Self {
annotated_type: None,
kind: ParameterKind::PositionalOrKeyword {
name,
default_type: None,
},
form: ParameterForm::Value,
}
}
pub(crate) fn variadic(name: Name) -> Self {
Self {
annotated_type: None,
kind: ParameterKind::Variadic { name },
form: ParameterForm::Value,
}
}
pub(crate) fn keyword_only(name: Name) -> Self {
Self {
annotated_type: None,
kind: ParameterKind::KeywordOnly {
name,
default_type: None,
},
form: ParameterForm::Value,
}
}
pub(crate) fn keyword_variadic(name: Name) -> Self {
Self {
annotated_type: None,
kind: ParameterKind::KeywordVariadic { name },
form: ParameterForm::Value,
}
}
pub(crate) fn with_annotated_type(mut self, annotated_type: Type<'db>) -> Self {
self.annotated_type = Some(annotated_type);
self
}
pub(crate) fn with_default_type(mut self, default: Type<'db>) -> Self {
match &mut self.kind {
ParameterKind::PositionalOnly { default_type, .. }
| ParameterKind::PositionalOrKeyword { default_type, .. }
| ParameterKind::KeywordOnly { default_type, .. } => *default_type = Some(default),
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
panic!("cannot set default value for variadic parameter")
}
}
self
}
pub(crate) fn type_form(mut self) -> Self {
self.form = ParameterForm::Type;
self
}
fn from_node_and_kind(
@ -548,10 +600,11 @@ impl<'db> Parameter<'db> {
kind: ParameterKind<'db>,
) -> Self {
Self {
annotated_ty: parameter
annotated_type: parameter
.annotation()
.map(|annotation| definition_expression_type(db, definition, annotation)),
kind,
form: ParameterForm::Value,
}
}
@ -598,7 +651,7 @@ impl<'db> Parameter<'db> {
/// Annotated type of the parameter, if annotated.
pub(crate) fn annotated_type(&self) -> Option<Type<'db>> {
self.annotated_ty
self.annotated_type
}
/// Kind of the parameter.
@ -629,11 +682,10 @@ impl<'db> Parameter<'db> {
/// Default-value type of the parameter, if any.
pub(crate) fn default_type(&self) -> Option<Type<'db>> {
match self.kind {
ParameterKind::PositionalOnly { default_ty, .. } => default_ty,
ParameterKind::PositionalOrKeyword { default_ty, .. } => default_ty,
ParameterKind::Variadic { .. } => None,
ParameterKind::KeywordOnly { default_ty, .. } => default_ty,
ParameterKind::KeywordVariadic { .. } => None,
ParameterKind::PositionalOnly { default_type, .. }
| ParameterKind::PositionalOrKeyword { default_type, .. }
| ParameterKind::KeywordOnly { default_type, .. } => default_type,
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => None,
}
}
}
@ -647,14 +699,14 @@ pub(crate) enum ParameterKind<'db> {
/// It is possible for signatures to be defined in ways that leave positional-only parameters
/// nameless (e.g. via `Callable` annotations).
name: Option<Name>,
default_ty: Option<Type<'db>>,
default_type: Option<Type<'db>>,
},
/// Positional-or-keyword parameter, e.g. `def f(x): ...`
PositionalOrKeyword {
/// Parameter name.
name: Name,
default_ty: Option<Type<'db>>,
default_type: Option<Type<'db>>,
},
/// Variadic parameter, e.g. `def f(*args): ...`
@ -667,7 +719,7 @@ pub(crate) enum ParameterKind<'db> {
KeywordOnly {
/// Parameter name.
name: Name,
default_ty: Option<Type<'db>>,
default_type: Option<Type<'db>>,
},
/// Variadic keywords parameter, e.g. `def f(**kwargs): ...`
@ -677,6 +729,13 @@ pub(crate) enum ParameterKind<'db> {
},
}
/// Whether a parameter is used as a value or a type form.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum ParameterForm {
Value,
Type,
}
#[cfg(test)]
mod tests {
use super::*;
@ -734,74 +793,28 @@ mod tests {
assert_params(
&sig,
&[
Parameter {
annotated_ty: None,
kind: ParameterKind::PositionalOnly {
name: Some(Name::new_static("a")),
default_ty: None,
},
},
Parameter {
annotated_ty: Some(KnownClass::Int.to_instance(&db)),
kind: ParameterKind::PositionalOnly {
name: Some(Name::new_static("b")),
default_ty: None,
},
},
Parameter {
annotated_ty: None,
kind: ParameterKind::PositionalOnly {
name: Some(Name::new_static("c")),
default_ty: Some(Type::IntLiteral(1)),
},
},
Parameter {
annotated_ty: Some(KnownClass::Int.to_instance(&db)),
kind: ParameterKind::PositionalOnly {
name: Some(Name::new_static("d")),
default_ty: Some(Type::IntLiteral(2)),
},
},
Parameter {
annotated_ty: None,
kind: ParameterKind::PositionalOrKeyword {
name: Name::new_static("e"),
default_ty: Some(Type::IntLiteral(3)),
},
},
Parameter {
annotated_ty: Some(Type::IntLiteral(4)),
kind: ParameterKind::PositionalOrKeyword {
name: Name::new_static("f"),
default_ty: Some(Type::IntLiteral(4)),
},
},
Parameter {
annotated_ty: Some(Type::object(&db)),
kind: ParameterKind::Variadic {
name: Name::new_static("args"),
},
},
Parameter {
annotated_ty: None,
kind: ParameterKind::KeywordOnly {
name: Name::new_static("g"),
default_ty: Some(Type::IntLiteral(5)),
},
},
Parameter {
annotated_ty: Some(Type::IntLiteral(6)),
kind: ParameterKind::KeywordOnly {
name: Name::new_static("h"),
default_ty: Some(Type::IntLiteral(6)),
},
},
Parameter {
annotated_ty: Some(KnownClass::Str.to_instance(&db)),
kind: ParameterKind::KeywordVariadic {
name: Name::new_static("kwargs"),
},
},
Parameter::positional_only(Some(Name::new_static("a"))),
Parameter::positional_only(Some(Name::new_static("b")))
.with_annotated_type(KnownClass::Int.to_instance(&db)),
Parameter::positional_only(Some(Name::new_static("c")))
.with_default_type(Type::IntLiteral(1)),
Parameter::positional_only(Some(Name::new_static("d")))
.with_annotated_type(KnownClass::Int.to_instance(&db))
.with_default_type(Type::IntLiteral(2)),
Parameter::positional_or_keyword(Name::new_static("e"))
.with_default_type(Type::IntLiteral(3)),
Parameter::positional_or_keyword(Name::new_static("f"))
.with_annotated_type(Type::IntLiteral(4))
.with_default_type(Type::IntLiteral(4)),
Parameter::variadic(Name::new_static("args"))
.with_annotated_type(Type::object(&db)),
Parameter::keyword_only(Name::new_static("g"))
.with_default_type(Type::IntLiteral(5)),
Parameter::keyword_only(Name::new_static("h"))
.with_annotated_type(Type::IntLiteral(6))
.with_default_type(Type::IntLiteral(6)),
Parameter::keyword_variadic(Name::new_static("kwargs"))
.with_annotated_type(KnownClass::Str.to_instance(&db)),
],
);
}
@ -828,15 +841,16 @@ mod tests {
let sig = func.internal_signature(&db);
let [Parameter {
annotated_ty,
annotated_type,
kind: ParameterKind::PositionalOrKeyword { name, .. },
..
}] = &sig.parameters.value[..]
else {
panic!("expected one positional-or-keyword parameter");
};
assert_eq!(name, "a");
// Parameter resolution not deferred; we should see A not B
assert_eq!(annotated_ty.unwrap().display(&db).to_string(), "A");
assert_eq!(annotated_type.unwrap().display(&db).to_string(), "A");
}
#[test]
@ -861,15 +875,16 @@ mod tests {
let sig = func.internal_signature(&db);
let [Parameter {
annotated_ty,
annotated_type,
kind: ParameterKind::PositionalOrKeyword { name, .. },
..
}] = &sig.parameters.value[..]
else {
panic!("expected one positional-or-keyword parameter");
};
assert_eq!(name, "a");
// Parameter resolution deferred; we should see B
assert_eq!(annotated_ty.unwrap().display(&db).to_string(), "B");
assert_eq!(annotated_type.unwrap().display(&db).to_string(), "B");
}
#[test]
@ -894,11 +909,13 @@ mod tests {
let sig = func.internal_signature(&db);
let [Parameter {
annotated_ty: a_annotated_ty,
annotated_type: a_annotated_ty,
kind: ParameterKind::PositionalOrKeyword { name: a_name, .. },
..
}, Parameter {
annotated_ty: b_annotated_ty,
annotated_type: b_annotated_ty,
kind: ParameterKind::PositionalOrKeyword { name: b_name, .. },
..
}] = &sig.parameters.value[..]
else {
panic!("expected two positional-or-keyword parameters");
@ -935,11 +952,13 @@ mod tests {
let sig = func.internal_signature(&db);
let [Parameter {
annotated_ty: a_annotated_ty,
annotated_type: a_annotated_ty,
kind: ParameterKind::PositionalOrKeyword { name: a_name, .. },
..
}, Parameter {
annotated_ty: b_annotated_ty,
annotated_type: b_annotated_ty,
kind: ParameterKind::PositionalOrKeyword { name: b_name, .. },
..
}] = &sig.parameters.value[..]
else {
panic!("expected two positional-or-keyword parameters");