mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +00:00
[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:
parent
4773878ee7
commit
c03c28d199
12 changed files with 1164 additions and 1132 deletions
|
@ -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]
|
||||
```
|
||||
|
|
|
@ -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
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = ¶meters[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 = ¶meters[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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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))
|
||||
),
|
||||
|
|
|
@ -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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -240,6 +240,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"conflicting-argument-forms": {
|
||||
"title": "detects when an argument is used as both a value and a type form in a call",
|
||||
"description": "## What it does\nChecks whether an argument is used as both a value and a type form in a call",
|
||||
"default": "error",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Level"
|
||||
}
|
||||
]
|
||||
},
|
||||
"conflicting-declarations": {
|
||||
"title": "detects conflicting declarations",
|
||||
"description": "TODO #14889",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue