mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +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(repr("string")) # revealed: Literal["'string'"]
|
||||||
reveal_type(f("string")) # revealed: Literal["string", "'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(int | str, 1)) # revealed: int | str
|
||||||
|
|
||||||
|
reveal_type(cast(val="foo", typ=int)) # revealed: int
|
||||||
|
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
reveal_type(cast(Literal, True)) # revealed: Unknown
|
reveal_type(cast(Literal, True)) # revealed: Unknown
|
||||||
|
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
reveal_type(cast(1, True)) # revealed: Unknown
|
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)
|
cast(str)
|
||||||
|
# error: [too-many-positional-arguments] "Too many positional arguments to function `cast`: expected 2, got 3"
|
||||||
cast(str, b"ar", "foo")
|
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 arguments;
|
||||||
mod bind;
|
mod bind;
|
||||||
pub(super) use arguments::{Argument, CallArguments};
|
pub(super) use arguments::{Argument, CallArgumentTypes, CallArguments};
|
||||||
pub(super) use bind::Bindings;
|
pub(super) use bind::Bindings;
|
||||||
|
|
||||||
/// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was
|
/// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was
|
||||||
/// unsuccessful.
|
/// unsuccessful.
|
||||||
///
|
///
|
||||||
/// The bindings are boxed so that we do not pass around large `Err` variants on the stack.
|
/// 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>>);
|
pub(crate) struct CallError<'db>(pub(crate) CallErrorKind, pub(crate) Box<Bindings<'db>>);
|
||||||
|
|
||||||
/// The reason why calling a type failed.
|
/// The reason why calling a type failed.
|
||||||
|
@ -32,7 +32,7 @@ pub(crate) enum CallErrorKind {
|
||||||
PossiblyNotCallable,
|
PossiblyNotCallable,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
pub(super) enum CallDunderError<'db> {
|
pub(super) enum CallDunderError<'db> {
|
||||||
/// The dunder attribute exists but it can't be called with the given arguments.
|
/// 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;
|
use super::Type;
|
||||||
|
|
||||||
/// Typed arguments for a single call, in source order.
|
/// Arguments for a single call, in source order.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[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> {
|
impl<'a> CallArguments<'a> {
|
||||||
/// Create a [`CallArguments`] with no arguments.
|
/// Invoke a function with an optional extra synthetic argument (for a `self` or `cls`
|
||||||
pub(crate) fn none() -> Self {
|
/// parameter) prepended to the front of this argument list. (If `bound_self` is none, the
|
||||||
Self(Vec::new())
|
/// 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 len(&self) -> usize {
|
||||||
pub(crate) fn positional(positional_tys: impl IntoIterator<Item = Type<'db>>) -> Self {
|
self.0.len()
|
||||||
positional_tys
|
|
||||||
.into_iter()
|
|
||||||
.map(Argument::Positional)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepend an extra positional argument.
|
pub(crate) fn iter(&self) -> impl Iterator<Item = Argument<'a>> + '_ {
|
||||||
pub(crate) fn with_self(&self, self_ty: Type<'db>) -> Self {
|
self.0.iter().copied()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db, 'a, 'b> IntoIterator for &'b CallArguments<'a, 'db> {
|
impl<'a> FromIterator<Argument<'a>> for CallArguments<'a> {
|
||||||
type Item = &'b Argument<'a, 'db>;
|
fn from_iter<T: IntoIterator<Item = Argument<'a>>>(iter: T) -> Self {
|
||||||
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 {
|
|
||||||
Self(iter.into_iter().collect())
|
Self(iter.into_iter().collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) enum Argument<'a, 'db> {
|
pub(crate) enum Argument<'a> {
|
||||||
/// The synthetic `self` or `cls` argument, which doesn't appear explicitly at the call site.
|
/// The synthetic `self` or `cls` argument, which doesn't appear explicitly at the call site.
|
||||||
Synthetic(Type<'db>),
|
Synthetic,
|
||||||
/// A positional argument.
|
/// A positional argument.
|
||||||
Positional(Type<'db>),
|
Positional,
|
||||||
/// A starred positional argument (e.g. `*args`).
|
/// A starred positional argument (e.g. `*args`).
|
||||||
Variadic(Type<'db>),
|
Variadic,
|
||||||
/// A keyword argument (e.g. `a=1`).
|
/// 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`).
|
/// The double-starred keywords argument (e.g. `**kwargs`).
|
||||||
Keywords(Type<'db>),
|
Keywords,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> Argument<'_, 'db> {
|
/// Arguments for a single call, in source order, along with inferred types for each argument.
|
||||||
fn ty(&self) -> Type<'db> {
|
pub(crate) struct CallArgumentTypes<'a, 'db> {
|
||||||
match self {
|
arguments: CallArguments<'a>,
|
||||||
Self::Synthetic(ty) => *ty,
|
types: VecDeque<Type<'db>>,
|
||||||
Self::Positional(ty) => *ty,
|
}
|
||||||
Self::Variadic(ty) => *ty,
|
|
||||||
Self::Keyword { name: _, ty } => *ty,
|
impl<'a, 'db> CallArgumentTypes<'a, 'db> {
|
||||||
Self::Keywords(ty) => *ty,
|
/// 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
|
//! [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.
|
//! union of types, each of which might contain multiple overloads.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Argument, CallArguments, CallError, CallErrorKind, CallableSignature, InferContext, Signature,
|
Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, CallableSignature,
|
||||||
Signatures, Type,
|
InferContext, Signature, Signatures, Type,
|
||||||
};
|
};
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
|
use crate::symbol::{Boundness, Symbol};
|
||||||
use crate::types::diagnostic::{
|
use crate::types::diagnostic::{
|
||||||
CALL_NON_CALLABLE, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD,
|
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
|
||||||
PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_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_db::diagnostic::{OldSecondaryDiagnosticMessage, Span};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_text_size::Ranged;
|
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.
|
/// 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.
|
/// It's guaranteed that the wrapped bindings have no errors.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Bindings<'db> {
|
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
|
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union
|
||||||
/// type.
|
/// type.
|
||||||
elements: SmallVec<[CallableBinding<'db>; 1]>,
|
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> {
|
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
|
/// parameters, and any errors resulting from binding the call, all for each union element and
|
||||||
/// overload (if any).
|
/// overload (if any).
|
||||||
pub(crate) fn bind(
|
pub(crate) fn check_types(
|
||||||
|
mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
signatures: &Signatures<'db>,
|
argument_types: &mut CallArgumentTypes<'_, 'db>,
|
||||||
arguments: &CallArguments<'_, 'db>,
|
|
||||||
) -> Result<Self, CallError<'db>> {
|
) -> Result<Self, CallError<'db>> {
|
||||||
let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures
|
for (signature, element) in self.signatures.iter().zip(&mut self.elements) {
|
||||||
.into_iter()
|
element.check_types(db, signature, argument_types);
|
||||||
.map(|signature| CallableBinding::bind(db, signature, arguments))
|
}
|
||||||
.collect();
|
|
||||||
|
self.evaluate_known_cases(db);
|
||||||
|
|
||||||
// In order of precedence:
|
// In order of precedence:
|
||||||
//
|
//
|
||||||
|
@ -68,28 +117,28 @@ impl<'db> Bindings<'db> {
|
||||||
let mut all_ok = true;
|
let mut all_ok = true;
|
||||||
let mut any_binding_error = false;
|
let mut any_binding_error = false;
|
||||||
let mut all_not_callable = true;
|
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();
|
let result = binding.as_result();
|
||||||
all_ok &= result.is_ok();
|
all_ok &= result.is_ok();
|
||||||
any_binding_error |= matches!(result, Err(CallErrorKind::BindingError));
|
any_binding_error |= matches!(result, Err(CallErrorKind::BindingError));
|
||||||
all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable));
|
all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable));
|
||||||
}
|
}
|
||||||
|
|
||||||
let bindings = Bindings {
|
|
||||||
callable_type: signatures.callable_type,
|
|
||||||
elements,
|
|
||||||
};
|
|
||||||
|
|
||||||
if all_ok {
|
if all_ok {
|
||||||
Ok(bindings)
|
Ok(self)
|
||||||
} else if any_binding_error {
|
} else if any_binding_error {
|
||||||
Err(CallError(CallErrorKind::BindingError, Box::new(bindings)))
|
Err(CallError(CallErrorKind::BindingError, Box::new(self)))
|
||||||
} else if all_not_callable {
|
} else if all_not_callable {
|
||||||
Err(CallError(CallErrorKind::NotCallable, Box::new(bindings)))
|
Err(CallError(CallErrorKind::NotCallable, Box::new(self)))
|
||||||
} else {
|
} else {
|
||||||
Err(CallError(
|
Err(CallError(
|
||||||
CallErrorKind::PossiblyNotCallable,
|
CallErrorKind::PossiblyNotCallable,
|
||||||
Box::new(bindings),
|
Box::new(self),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,6 +147,10 @@ impl<'db> Bindings<'db> {
|
||||||
self.elements.len() == 1
|
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.
|
/// 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
|
/// For calls with binding errors, this is a type that best approximates the return type. For
|
||||||
/// types that are not callable, returns `Type::Unknown`.
|
/// types that are not callable, returns `Type::Unknown`.
|
||||||
|
@ -122,12 +175,22 @@ impl<'db> Bindings<'db> {
|
||||||
node,
|
node,
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{}` is not callable",
|
"Object of type `{}` is not callable",
|
||||||
self.callable_type.display(context.db())
|
self.callable_type().display(context.db())
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
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
|
// 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
|
// an error saying that the union type can't be called, followed by subdiagnostics
|
||||||
// explaining why.
|
// explaining why.
|
||||||
|
@ -135,6 +198,286 @@ impl<'db> Bindings<'db> {
|
||||||
first.report_diagnostics(context, node);
|
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> {
|
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, we store this error information for each overload.
|
||||||
///
|
///
|
||||||
/// [overloads]: https://github.com/python/typing/pull/1839
|
/// [overloads]: https://github.com/python/typing/pull/1839
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct CallableBinding<'db> {
|
pub(crate) struct CallableBinding<'db> {
|
||||||
pub(crate) callable_type: Type<'db>,
|
pub(crate) callable_type: Type<'db>,
|
||||||
pub(crate) signature_type: Type<'db>,
|
pub(crate) signature_type: Type<'db>,
|
||||||
|
@ -184,23 +527,15 @@ pub(crate) struct CallableBinding<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> CallableBinding<'db> {
|
impl<'db> CallableBinding<'db> {
|
||||||
/// Bind a [`CallArguments`] against a [`CallableSignature`].
|
fn match_parameters(
|
||||||
///
|
|
||||||
/// 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,
|
|
||||||
signature: &CallableSignature<'db>,
|
signature: &CallableSignature<'db>,
|
||||||
arguments: &CallArguments<'_, 'db>,
|
arguments: &mut CallArguments<'_>,
|
||||||
|
argument_forms: &mut [Option<ParameterForm>],
|
||||||
|
conflicting_forms: &mut [bool],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// If this callable is a bound method, prepend the self instance onto the arguments list
|
// If this callable is a bound method, prepend the self instance onto the arguments list
|
||||||
// before checking.
|
// before checking.
|
||||||
let arguments = if let Some(bound_type) = signature.bound_type {
|
arguments.with_self(signature.bound_type, |arguments| {
|
||||||
Cow::Owned(arguments.with_self(bound_type))
|
|
||||||
} else {
|
|
||||||
Cow::Borrowed(arguments)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: This checks every overload. In the proposed more detailed call checking spec [1],
|
// 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
|
// 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
|
// the matching overloads. Make sure to implement that as part of separating call binding into
|
||||||
|
@ -209,14 +544,38 @@ impl<'db> CallableBinding<'db> {
|
||||||
// [1] https://github.com/python/typing/pull/1839
|
// [1] https://github.com/python/typing/pull/1839
|
||||||
let overloads = signature
|
let overloads = signature
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|signature| Binding::bind(db, signature, arguments.as_ref()))
|
.map(|signature| {
|
||||||
|
Binding::match_parameters(
|
||||||
|
signature,
|
||||||
|
arguments,
|
||||||
|
argument_forms,
|
||||||
|
conflicting_forms,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
CallableBinding {
|
CallableBinding {
|
||||||
callable_type: signature.callable_type,
|
callable_type: signature.callable_type,
|
||||||
signature_type: signature.signature_type,
|
signature_type: signature.signature_type,
|
||||||
dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound,
|
dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound,
|
||||||
overloads,
|
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> {
|
fn as_result(&self) -> Result<(), CallErrorKind> {
|
||||||
|
@ -333,27 +692,35 @@ impl<'db> CallableBinding<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Binding information for one of the overloads of a callable.
|
/// Binding information for one of the overloads of a callable.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Binding<'db> {
|
pub(crate) struct Binding<'db> {
|
||||||
/// Return type of the call.
|
/// Return type of the call.
|
||||||
return_ty: Type<'db>,
|
return_ty: Type<'db>,
|
||||||
|
|
||||||
/// Bound types for parameters, in parameter source order.
|
/// The formal parameter that each argument is matched with, in argument source order, or
|
||||||
parameter_tys: Box<[Type<'db>]>,
|
/// `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.
|
/// Call binding errors, if any.
|
||||||
errors: Vec<BindingError<'db>>,
|
errors: Vec<BindingError<'db>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> Binding<'db> {
|
impl<'db> Binding<'db> {
|
||||||
fn bind(
|
fn match_parameters(
|
||||||
db: &'db dyn Db,
|
|
||||||
signature: &Signature<'db>,
|
signature: &Signature<'db>,
|
||||||
arguments: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'_>,
|
||||||
|
argument_forms: &mut [Option<ParameterForm>],
|
||||||
|
conflicting_forms: &mut [bool],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let parameters = signature.parameters();
|
let parameters = signature.parameters();
|
||||||
// The type assigned to each parameter at this call site.
|
// The parameter that each argument is matched with.
|
||||||
let mut parameter_tys = vec![None; parameters.len()];
|
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 errors = vec![];
|
||||||
let mut next_positional = 0;
|
let mut next_positional = 0;
|
||||||
let mut first_excess_positional = None;
|
let mut first_excess_positional = None;
|
||||||
|
@ -370,9 +737,9 @@ impl<'db> Binding<'db> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (argument_index, argument) in arguments.iter().enumerate() {
|
for (argument_index, argument) in arguments.iter().enumerate() {
|
||||||
let (index, parameter, argument_ty, positional) = match argument {
|
let (index, parameter, positional) = match argument {
|
||||||
Argument::Positional(ty) | Argument::Synthetic(ty) => {
|
Argument::Positional | Argument::Synthetic => {
|
||||||
if matches!(argument, Argument::Synthetic(_)) {
|
if matches!(argument, Argument::Synthetic) {
|
||||||
num_synthetic_args += 1;
|
num_synthetic_args += 1;
|
||||||
}
|
}
|
||||||
let Some((index, parameter)) = parameters
|
let Some((index, parameter)) = parameters
|
||||||
|
@ -385,9 +752,9 @@ impl<'db> Binding<'db> {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
next_positional += 1;
|
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
|
let Some((index, parameter)) = parameters
|
||||||
.keyword_by_name(name)
|
.keyword_by_name(name)
|
||||||
.or_else(|| parameters.keyword_variadic())
|
.or_else(|| parameters.keyword_variadic())
|
||||||
|
@ -398,35 +765,33 @@ impl<'db> Binding<'db> {
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
(index, parameter, ty, false)
|
(index, parameter, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
Argument::Variadic(_) | Argument::Keywords(_) => {
|
Argument::Variadic | Argument::Keywords => {
|
||||||
// TODO
|
// TODO
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(expected_ty) = parameter.annotated_type() {
|
if !matches!(argument, Argument::Synthetic) {
|
||||||
if !argument_ty.is_assignable_to(db, expected_ty) {
|
if let Some(existing) =
|
||||||
errors.push(BindingError::InvalidArgumentType {
|
argument_forms[argument_index - num_synthetic_args].replace(parameter.form)
|
||||||
parameter: ParameterContext::new(parameter, index, positional),
|
{
|
||||||
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
if existing != parameter.form {
|
||||||
expected_ty,
|
conflicting_forms[argument_index - num_synthetic_args] = true;
|
||||||
provided_ty: *argument_ty,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(existing) = parameter_tys[index].replace(*argument_ty) {
|
}
|
||||||
if parameter.is_variadic() || parameter.is_keyword_variadic() {
|
if parameter_matched[index] {
|
||||||
let union = UnionType::from_elements(db, [existing, *argument_ty]);
|
if !parameter.is_variadic() && !parameter.is_keyword_variadic() {
|
||||||
parameter_tys[index].replace(union);
|
|
||||||
} else {
|
|
||||||
errors.push(BindingError::ParameterAlreadyAssigned {
|
errors.push(BindingError::ParameterAlreadyAssigned {
|
||||||
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||||
parameter: ParameterContext::new(parameter, index, positional),
|
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 {
|
if let Some(first_excess_argument_index) = first_excess_positional {
|
||||||
errors.push(BindingError::TooManyPositionalArguments {
|
errors.push(BindingError::TooManyPositionalArguments {
|
||||||
|
@ -439,8 +804,8 @@ impl<'db> Binding<'db> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut missing = vec![];
|
let mut missing = vec![];
|
||||||
for (index, bound_ty) in parameter_tys.iter().enumerate() {
|
for (index, matched) in parameter_matched.iter().copied().enumerate() {
|
||||||
if bound_ty.is_none() {
|
if !matched {
|
||||||
let param = ¶meters[index];
|
let param = ¶meters[index];
|
||||||
if param.is_variadic()
|
if param.is_variadic()
|
||||||
|| param.is_keyword_variadic()
|
|| param.is_keyword_variadic()
|
||||||
|
@ -461,14 +826,65 @@ impl<'db> Binding<'db> {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
return_ty: signature.return_ty.unwrap_or(Type::unknown()),
|
return_ty: signature.return_ty.unwrap_or(Type::unknown()),
|
||||||
parameter_tys: parameter_tys
|
argument_parameters: argument_parameters.into_boxed_slice(),
|
||||||
.into_iter()
|
parameter_tys: vec![None; parameters.len()].into_boxed_slice(),
|
||||||
.map(|opt_ty| opt_ty.unwrap_or(Type::unknown()))
|
|
||||||
.collect(),
|
|
||||||
errors,
|
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>) {
|
pub(crate) fn set_return_type(&mut self, return_ty: Type<'db>) {
|
||||||
self.return_ty = return_ty;
|
self.return_ty = return_ty;
|
||||||
}
|
}
|
||||||
|
@ -477,7 +893,7 @@ impl<'db> Binding<'db> {
|
||||||
self.return_ty
|
self.return_ty
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parameter_types(&self) -> &[Type<'db>] {
|
pub(crate) fn parameter_types(&self) -> &[Option<Type<'db>>] {
|
||||||
&self.parameter_tys
|
&self.parameter_tys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers,
|
Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers,
|
||||||
},
|
},
|
||||||
types::{
|
types::{
|
||||||
definition_expression_type, CallArguments, CallError, CallErrorKind, DynamicType,
|
definition_expression_type, CallArgumentTypes, CallError, CallErrorKind, DynamicType,
|
||||||
MetaclassCandidate, TupleType, UnionBuilder, UnionType,
|
MetaclassCandidate, TupleType, UnionBuilder, UnionType,
|
||||||
},
|
},
|
||||||
Db, KnownModule, Program,
|
Db, KnownModule, Program,
|
||||||
|
@ -279,13 +279,13 @@ impl<'db> Class<'db> {
|
||||||
let namespace = KnownClass::Dict.to_instance(db);
|
let namespace = KnownClass::Dict.to_instance(db);
|
||||||
|
|
||||||
// TODO: Other keyword arguments?
|
// 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)),
|
Ok(bindings) => Ok(bindings.return_type(db)),
|
||||||
|
|
||||||
Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError {
|
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
|
// 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) {
|
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||||
registry.register_lint(&CALL_NON_CALLABLE);
|
registry.register_lint(&CALL_NON_CALLABLE);
|
||||||
registry.register_lint(&CALL_POSSIBLY_UNBOUND_METHOD);
|
registry.register_lint(&CALL_POSSIBLY_UNBOUND_METHOD);
|
||||||
|
registry.register_lint(&CONFLICTING_ARGUMENT_FORMS);
|
||||||
registry.register_lint(&CONFLICTING_DECLARATIONS);
|
registry.register_lint(&CONFLICTING_DECLARATIONS);
|
||||||
registry.register_lint(&CONFLICTING_METACLASS);
|
registry.register_lint(&CONFLICTING_METACLASS);
|
||||||
registry.register_lint(&CYCLIC_CLASS_DEFINITION);
|
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! {
|
declare_lint! {
|
||||||
/// TODO #14889
|
/// TODO #14889
|
||||||
pub(crate) static CONFLICTING_DECLARATIONS = {
|
pub(crate) static CONFLICTING_DECLARATIONS = {
|
||||||
|
|
|
@ -476,8 +476,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::db::tests::setup_db;
|
use crate::db::tests::setup_db;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
KnownClass, Parameter, ParameterKind, Parameters, Signature, SliceLiteralType,
|
KnownClass, Parameter, Parameters, Signature, SliceLiteralType, StringLiteralType, Type,
|
||||||
StringLiteralType, Type,
|
|
||||||
};
|
};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
|
@ -574,13 +573,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[Parameter::new(
|
[Parameter::positional_only(None).with_annotated_type(Type::none(&db))],
|
||||||
Some(Type::none(&db)),
|
|
||||||
ParameterKind::PositionalOnly {
|
|
||||||
name: None,
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
)],
|
|
||||||
Some(Type::none(&db))
|
Some(Type::none(&db))
|
||||||
),
|
),
|
||||||
"(None, /) -> None"
|
"(None, /) -> None"
|
||||||
|
@ -591,20 +584,11 @@ mod tests {
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[
|
[
|
||||||
Parameter::new(
|
Parameter::positional_or_keyword(Name::new_static("x"))
|
||||||
None,
|
.with_default_type(KnownClass::Int.to_instance(&db)),
|
||||||
ParameterKind::PositionalOrKeyword {
|
Parameter::positional_or_keyword(Name::new_static("y"))
|
||||||
name: Name::new_static("x"),
|
.with_annotated_type(KnownClass::Str.to_instance(&db))
|
||||||
default_ty: Some(KnownClass::Int.to_instance(&db))
|
.with_default_type(KnownClass::Str.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))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
Some(Type::none(&db))
|
Some(Type::none(&db))
|
||||||
),
|
),
|
||||||
|
@ -616,20 +600,8 @@ mod tests {
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[
|
[
|
||||||
Parameter::new(
|
Parameter::positional_only(Some(Name::new_static("x"))),
|
||||||
None,
|
Parameter::positional_only(Some(Name::new_static("y"))),
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
Some(Type::none(&db))
|
Some(Type::none(&db))
|
||||||
),
|
),
|
||||||
|
@ -641,20 +613,8 @@ mod tests {
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[
|
[
|
||||||
Parameter::new(
|
Parameter::positional_only(Some(Name::new_static("x"))),
|
||||||
None,
|
Parameter::positional_or_keyword(Name::new_static("y")),
|
||||||
ParameterKind::PositionalOnly {
|
|
||||||
name: Some(Name::new_static("x")),
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Parameter::new(
|
|
||||||
None,
|
|
||||||
ParameterKind::PositionalOrKeyword {
|
|
||||||
name: Name::new_static("y"),
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
Some(Type::none(&db))
|
Some(Type::none(&db))
|
||||||
),
|
),
|
||||||
|
@ -666,20 +626,8 @@ mod tests {
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[
|
[
|
||||||
Parameter::new(
|
Parameter::keyword_only(Name::new_static("x")),
|
||||||
None,
|
Parameter::keyword_only(Name::new_static("y")),
|
||||||
ParameterKind::KeywordOnly {
|
|
||||||
name: Name::new_static("x"),
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Parameter::new(
|
|
||||||
None,
|
|
||||||
ParameterKind::KeywordOnly {
|
|
||||||
name: Name::new_static("y"),
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
Some(Type::none(&db))
|
Some(Type::none(&db))
|
||||||
),
|
),
|
||||||
|
@ -691,20 +639,8 @@ mod tests {
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[
|
[
|
||||||
Parameter::new(
|
Parameter::positional_or_keyword(Name::new_static("x")),
|
||||||
None,
|
Parameter::keyword_only(Name::new_static("y")),
|
||||||
ParameterKind::PositionalOrKeyword {
|
|
||||||
name: Name::new_static("x"),
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Parameter::new(
|
|
||||||
None,
|
|
||||||
ParameterKind::KeywordOnly {
|
|
||||||
name: Name::new_static("y"),
|
|
||||||
default_ty: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
Some(Type::none(&db))
|
Some(Type::none(&db))
|
||||||
),
|
),
|
||||||
|
@ -716,74 +652,28 @@ mod tests {
|
||||||
display_signature(
|
display_signature(
|
||||||
&db,
|
&db,
|
||||||
[
|
[
|
||||||
Parameter::new(
|
Parameter::positional_only(Some(Name::new_static("a"))),
|
||||||
None,
|
Parameter::positional_only(Some(Name::new_static("b")))
|
||||||
ParameterKind::PositionalOnly {
|
.with_annotated_type(KnownClass::Int.to_instance(&db)),
|
||||||
name: Some(Name::new_static("a")),
|
Parameter::positional_only(Some(Name::new_static("c")))
|
||||||
default_ty: None
|
.with_default_type(Type::IntLiteral(1)),
|
||||||
},
|
Parameter::positional_only(Some(Name::new_static("d")))
|
||||||
),
|
.with_annotated_type(KnownClass::Int.to_instance(&db))
|
||||||
Parameter::new(
|
.with_default_type(Type::IntLiteral(2)),
|
||||||
Some(KnownClass::Int.to_instance(&db)),
|
Parameter::positional_or_keyword(Name::new_static("e"))
|
||||||
ParameterKind::PositionalOnly {
|
.with_default_type(Type::IntLiteral(3)),
|
||||||
name: Some(Name::new_static("b")),
|
Parameter::positional_or_keyword(Name::new_static("f"))
|
||||||
default_ty: None
|
.with_annotated_type(KnownClass::Int.to_instance(&db))
|
||||||
},
|
.with_default_type(Type::IntLiteral(4)),
|
||||||
),
|
Parameter::variadic(Name::new_static("args"))
|
||||||
Parameter::new(
|
.with_annotated_type(Type::object(&db)),
|
||||||
None,
|
Parameter::keyword_only(Name::new_static("g"))
|
||||||
ParameterKind::PositionalOnly {
|
.with_default_type(Type::IntLiteral(5)),
|
||||||
name: Some(Name::new_static("c")),
|
Parameter::keyword_only(Name::new_static("h"))
|
||||||
default_ty: Some(Type::IntLiteral(1)),
|
.with_annotated_type(KnownClass::Int.to_instance(&db))
|
||||||
},
|
.with_default_type(Type::IntLiteral(6)),
|
||||||
),
|
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||||
Parameter::new(
|
.with_annotated_type(KnownClass::Str.to_instance(&db)),
|
||||||
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")
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
Some(KnownClass::Bytes.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,
|
module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations,
|
||||||
typing_extensions_symbol, Boundness, LookupError,
|
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::{
|
use crate::types::diagnostic::{
|
||||||
report_implicit_return_type, report_invalid_arguments_to_annotated,
|
report_implicit_return_type, report_invalid_arguments_to_annotated,
|
||||||
report_invalid_arguments_to_callable, report_invalid_assignment,
|
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||||
|
@ -79,12 +79,12 @@ use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
class::MetaclassErrorKind, todo_type, Class, DynamicType, FunctionType, InstanceType,
|
class::MetaclassErrorKind, todo_type, Class, DynamicType, FunctionType, InstanceType,
|
||||||
IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType,
|
IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType,
|
||||||
MetaclassCandidate, Parameter, Parameters, SliceLiteralType, SubclassOfType, Symbol,
|
MetaclassCandidate, Parameter, ParameterForm, Parameters, SliceLiteralType, SubclassOfType,
|
||||||
SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
||||||
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder,
|
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder,
|
||||||
UnionType,
|
UnionType,
|
||||||
};
|
};
|
||||||
use crate::types::{CallableType, GeneralCallableType, ParameterKind, Signature};
|
use crate::types::{CallableType, GeneralCallableType, Signature};
|
||||||
use crate::unpack::Unpack;
|
use crate::unpack::Unpack;
|
||||||
use crate::util::subscript::{PyIndex, PySlice};
|
use crate::util::subscript::{PyIndex, PySlice};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
@ -102,7 +102,7 @@ use super::slots::check_class_slots;
|
||||||
use super::string_annotation::{
|
use super::string_annotation::{
|
||||||
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_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.
|
/// 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
|
/// 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);
|
self.infer_type_parameters(type_params);
|
||||||
|
|
||||||
if let Some(arguments) = class.arguments.as_deref() {
|
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() {
|
if let Some(annotation) = parameter.annotation() {
|
||||||
let _annotated_ty = self.file_expression_type(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());
|
let ty = KnownClass::Tuple.to_instance(self.db());
|
||||||
self.add_declaration_with_binding(
|
self.add_declaration_with_binding(
|
||||||
parameter.into(),
|
parameter.into(),
|
||||||
|
@ -1548,7 +1550,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
) {
|
) {
|
||||||
if let Some(annotation) = parameter.annotation() {
|
if let Some(annotation) = parameter.annotation() {
|
||||||
let _annotated_ty = self.file_expression_type(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());
|
let ty = KnownClass::Dict.to_instance(self.db());
|
||||||
self.add_declaration_with_binding(
|
self.add_declaration_with_binding(
|
||||||
parameter.into(),
|
parameter.into(),
|
||||||
|
@ -2276,7 +2278,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let successful_call = meta_dunder_set
|
let successful_call = meta_dunder_set
|
||||||
.try_call(
|
.try_call(
|
||||||
db,
|
db,
|
||||||
&CallArguments::positional([meta_attr_ty, object_ty, value_ty]),
|
CallArgumentTypes::positional([meta_attr_ty, object_ty, value_ty]),
|
||||||
)
|
)
|
||||||
.is_ok();
|
.is_ok();
|
||||||
|
|
||||||
|
@ -2375,7 +2377,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let successful_call = meta_dunder_set
|
let successful_call = meta_dunder_set
|
||||||
.try_call(
|
.try_call(
|
||||||
db,
|
db,
|
||||||
&CallArguments::positional([meta_attr_ty, object_ty, value_ty]),
|
CallArgumentTypes::positional([
|
||||||
|
meta_attr_ty,
|
||||||
|
object_ty,
|
||||||
|
value_ty,
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
.is_ok();
|
.is_ok();
|
||||||
|
|
||||||
|
@ -2783,7 +2789,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let call = target_type.try_call_dunder(
|
let call = target_type.try_call_dunder(
|
||||||
db,
|
db,
|
||||||
op.in_place_dunder(),
|
op.in_place_dunder(),
|
||||||
&CallArguments::positional([value_type]),
|
CallArgumentTypes::positional([value_type]),
|
||||||
);
|
);
|
||||||
|
|
||||||
match call {
|
match call {
|
||||||
|
@ -3232,45 +3238,22 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
self.infer_expression(expression)
|
self.infer_expression(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_arguments<'a>(
|
fn parse_arguments(arguments: &ast::Arguments) -> CallArguments<'_> {
|
||||||
&mut self,
|
|
||||||
arguments: &'a ast::Arguments,
|
|
||||||
parameter_expectations: ParameterExpectations,
|
|
||||||
) -> CallArguments<'a, 'db> {
|
|
||||||
arguments
|
arguments
|
||||||
.arguments_source_order()
|
.arguments_source_order()
|
||||||
.enumerate()
|
.map(|arg_or_keyword| {
|
||||||
.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,
|
|
||||||
};
|
|
||||||
|
|
||||||
match arg_or_keyword {
|
match arg_or_keyword {
|
||||||
ast::ArgOrKeyword::Arg(arg) => match arg {
|
ast::ArgOrKeyword::Arg(arg) => match arg {
|
||||||
ast::Expr::Starred(ast::ExprStarred {
|
ast::Expr::Starred(ast::ExprStarred { .. }) => Argument::Variadic,
|
||||||
value,
|
|
||||||
range: _,
|
|
||||||
ctx: _,
|
|
||||||
}) => {
|
|
||||||
let ty = infer_argument_type(self, value);
|
|
||||||
self.store_expression_type(arg, ty);
|
|
||||||
Argument::Variadic(ty)
|
|
||||||
}
|
|
||||||
// TODO diagnostic if after a keyword argument
|
// TODO diagnostic if after a keyword argument
|
||||||
_ => Argument::Positional(infer_argument_type(self, arg)),
|
_ => Argument::Positional,
|
||||||
},
|
},
|
||||||
ast::ArgOrKeyword::Keyword(ast::Keyword {
|
ast::ArgOrKeyword::Keyword(ast::Keyword { arg, .. }) => {
|
||||||
arg,
|
|
||||||
value,
|
|
||||||
range: _,
|
|
||||||
}) => {
|
|
||||||
let ty = infer_argument_type(self, value);
|
|
||||||
if let Some(arg) = arg {
|
if let Some(arg) = arg {
|
||||||
Argument::Keyword { name: &arg.id, ty }
|
Argument::Keyword(&arg.id)
|
||||||
} else {
|
} else {
|
||||||
// TODO diagnostic if not last
|
// TODO diagnostic if not last
|
||||||
Argument::Keywords(ty)
|
Argument::Keywords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3278,6 +3261,44 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.collect()
|
.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>> {
|
fn infer_optional_expression(&mut self, expression: Option<&ast::Expr>) -> Option<Type<'db>> {
|
||||||
expression.map(|expr| self.infer_expression(expr))
|
expression.map(|expr| self.infer_expression(expr))
|
||||||
}
|
}
|
||||||
|
@ -3769,64 +3790,44 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let positional_only = parameters
|
let positional_only = parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|parameter| {
|
.map(|param| {
|
||||||
Parameter::new(
|
let mut parameter = Parameter::positional_only(Some(param.name().id.clone()));
|
||||||
None,
|
if let Some(default) = param.default() {
|
||||||
ParameterKind::PositionalOnly {
|
parameter = parameter.with_default_type(self.infer_expression(default));
|
||||||
name: Some(parameter.name().id.clone()),
|
}
|
||||||
default_ty: parameter
|
parameter
|
||||||
.default()
|
|
||||||
.map(|default| self.infer_expression(default)),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let positional_or_keyword = parameters
|
let positional_or_keyword = parameters
|
||||||
.args
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|parameter| {
|
.map(|param| {
|
||||||
Parameter::new(
|
let mut parameter = Parameter::positional_or_keyword(param.name().id.clone());
|
||||||
None,
|
if let Some(default) = param.default() {
|
||||||
ParameterKind::PositionalOrKeyword {
|
parameter = parameter.with_default_type(self.infer_expression(default));
|
||||||
name: parameter.name().id.clone(),
|
}
|
||||||
default_ty: parameter
|
parameter
|
||||||
.default()
|
|
||||||
.map(|default| self.infer_expression(default)),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let variadic = parameters.vararg.as_ref().map(|parameter| {
|
let variadic = parameters
|
||||||
Parameter::new(
|
.vararg
|
||||||
None,
|
.as_ref()
|
||||||
ParameterKind::Variadic {
|
.map(|param| Parameter::variadic(param.name().id.clone()));
|
||||||
name: parameter.name.id.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let keyword_only = parameters
|
let keyword_only = parameters
|
||||||
.kwonlyargs
|
.kwonlyargs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|parameter| {
|
.map(|param| {
|
||||||
Parameter::new(
|
let mut parameter = Parameter::keyword_only(param.name().id.clone());
|
||||||
None,
|
if let Some(default) = param.default() {
|
||||||
ParameterKind::KeywordOnly {
|
parameter = parameter.with_default_type(self.infer_expression(default));
|
||||||
name: parameter.name().id.clone(),
|
}
|
||||||
default_ty: parameter
|
parameter
|
||||||
.default()
|
|
||||||
.map(|default| self.infer_expression(default)),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let keyword_variadic = parameters.kwarg.as_ref().map(|parameter| {
|
let keyword_variadic = parameters
|
||||||
Parameter::new(
|
.kwarg
|
||||||
None,
|
.as_ref()
|
||||||
ParameterKind::KeywordVariadic {
|
.map(|param| Parameter::keyword_variadic(param.name().id.clone()));
|
||||||
name: parameter.name.id.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
Parameters::new(
|
Parameters::new(
|
||||||
positional_only
|
positional_only
|
||||||
|
@ -3856,16 +3857,17 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
arguments,
|
arguments,
|
||||||
} = call_expression;
|
} = 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 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
|
match bindings.check_types(self.db(), &mut call_argument_types) {
|
||||||
.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) {
|
|
||||||
Ok(bindings) => {
|
Ok(bindings) => {
|
||||||
for binding in &bindings {
|
for binding in &bindings {
|
||||||
let Some(known_function) = binding
|
let Some(known_function) = binding
|
||||||
|
@ -3882,7 +3884,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
match known_function {
|
match known_function {
|
||||||
KnownFunction::RevealType => {
|
KnownFunction::RevealType => {
|
||||||
if let [revealed_type] = overload.parameter_types() {
|
if let [Some(revealed_type)] = overload.parameter_types() {
|
||||||
self.context.report_diagnostic(
|
self.context.report_diagnostic(
|
||||||
call_expression,
|
call_expression,
|
||||||
DiagnosticId::RevealedType,
|
DiagnosticId::RevealedType,
|
||||||
|
@ -3896,7 +3898,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KnownFunction::AssertType => {
|
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) {
|
if !actual_ty.is_gradual_equivalent_to(self.db(), *asserted_ty) {
|
||||||
self.context.report_lint(
|
self.context.report_lint(
|
||||||
&TYPE_ASSERTION_FAILURE,
|
&TYPE_ASSERTION_FAILURE,
|
||||||
|
@ -3911,7 +3914,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KnownFunction::StaticAssert => {
|
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()) {
|
let truthiness = match parameter_ty.try_bool(self.db()) {
|
||||||
Ok(truthiness) => truthiness,
|
Ok(truthiness) => truthiness,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -3934,8 +3937,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !truthiness.is_always_true() {
|
if !truthiness.is_always_true() {
|
||||||
if let Some(message) =
|
if let Some(message) = message
|
||||||
message.into_string_literal().map(|s| &**s.value(self.db()))
|
.and_then(Type::into_string_literal)
|
||||||
|
.map(|s| &**s.value(self.db()))
|
||||||
{
|
{
|
||||||
self.context.report_lint(
|
self.context.report_lint(
|
||||||
&STATIC_ASSERT_ERROR,
|
&STATIC_ASSERT_ERROR,
|
||||||
|
@ -4352,7 +4356,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
match operand_type.try_call_dunder(
|
match operand_type.try_call_dunder(
|
||||||
self.db(),
|
self.db(),
|
||||||
unary_dunder_method,
|
unary_dunder_method,
|
||||||
&CallArguments::none(),
|
CallArgumentTypes::none(),
|
||||||
) {
|
) {
|
||||||
Ok(outcome) => outcome.return_type(self.db()),
|
Ok(outcome) => outcome.return_type(self.db()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -4634,7 +4638,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.try_call_dunder(
|
.try_call_dunder(
|
||||||
self.db(),
|
self.db(),
|
||||||
reflected_dunder,
|
reflected_dunder,
|
||||||
&CallArguments::positional([left_ty]),
|
CallArgumentTypes::positional([left_ty]),
|
||||||
)
|
)
|
||||||
.map(|outcome| outcome.return_type(self.db()))
|
.map(|outcome| outcome.return_type(self.db()))
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
|
@ -4642,7 +4646,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.try_call_dunder(
|
.try_call_dunder(
|
||||||
self.db(),
|
self.db(),
|
||||||
op.dunder(),
|
op.dunder(),
|
||||||
&CallArguments::positional([right_ty]),
|
CallArgumentTypes::positional([right_ty]),
|
||||||
)
|
)
|
||||||
.map(|outcome| outcome.return_type(self.db()))
|
.map(|outcome| outcome.return_type(self.db()))
|
||||||
})
|
})
|
||||||
|
@ -4654,7 +4658,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.try_call_dunder(
|
.try_call_dunder(
|
||||||
self.db(),
|
self.db(),
|
||||||
op.dunder(),
|
op.dunder(),
|
||||||
&CallArguments::positional([right_ty]),
|
CallArgumentTypes::positional([right_ty]),
|
||||||
)
|
)
|
||||||
.map(|outcome| outcome.return_type(self.db()))
|
.map(|outcome| outcome.return_type(self.db()))
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -4667,7 +4671,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.try_call_dunder(
|
.try_call_dunder(
|
||||||
self.db(),
|
self.db(),
|
||||||
op.reflected_dunder(),
|
op.reflected_dunder(),
|
||||||
&CallArguments::positional([left_ty]),
|
CallArgumentTypes::positional([left_ty]),
|
||||||
)
|
)
|
||||||
.map(|outcome| outcome.return_type(self.db()))
|
.map(|outcome| outcome.return_type(self.db()))
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -5318,7 +5322,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.try_call_dunder(
|
.try_call_dunder(
|
||||||
db,
|
db,
|
||||||
op.dunder(),
|
op.dunder(),
|
||||||
&CallArguments::positional([Type::Instance(right)]),
|
CallArgumentTypes::positional([Type::Instance(right)]),
|
||||||
)
|
)
|
||||||
.map(|outcome| outcome.return_type(db))
|
.map(|outcome| outcome.return_type(db))
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -5367,7 +5371,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
contains_dunder
|
contains_dunder
|
||||||
.try_call(
|
.try_call(
|
||||||
db,
|
db,
|
||||||
&CallArguments::positional([Type::Instance(right), Type::Instance(left)]),
|
CallArgumentTypes::positional([
|
||||||
|
Type::Instance(right),
|
||||||
|
Type::Instance(left),
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
.map(|bindings| bindings.return_type(db))
|
.map(|bindings| bindings.return_type(db))
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -5643,7 +5650,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
match value_ty.try_call_dunder(
|
match value_ty.try_call_dunder(
|
||||||
self.db(),
|
self.db(),
|
||||||
"__getitem__",
|
"__getitem__",
|
||||||
&CallArguments::positional([slice_ty]),
|
CallArgumentTypes::positional([slice_ty]),
|
||||||
) {
|
) {
|
||||||
Ok(outcome) => return outcome.return_type(self.db()),
|
Ok(outcome) => return outcome.return_type(self.db()),
|
||||||
Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
|
Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
|
||||||
|
@ -5664,7 +5671,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
value_node,
|
value_node,
|
||||||
format_args!(
|
format_args!(
|
||||||
"Method `__getitem__` of type `{}` is not callable on object of type `{}`",
|
"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()),
|
value_ty.display(self.db()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -5705,7 +5712,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
match ty.try_call(
|
match ty.try_call(
|
||||||
self.db(),
|
self.db(),
|
||||||
&CallArguments::positional([value_ty, slice_ty]),
|
CallArgumentTypes::positional([value_ty, slice_ty]),
|
||||||
) {
|
) {
|
||||||
Ok(bindings) => return bindings.return_type(self.db()),
|
Ok(bindings) => return bindings.return_type(self.db()),
|
||||||
Err(CallError(_, bindings)) => {
|
Err(CallError(_, bindings)) => {
|
||||||
|
@ -5714,7 +5721,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
value_node,
|
value_node,
|
||||||
format_args!(
|
format_args!(
|
||||||
"Method `__class_getitem__` of type `{}` is not callable on object of type `{}`",
|
"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()),
|
value_ty.display(self.db()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -6974,13 +6981,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Parameters::todo()
|
Parameters::todo()
|
||||||
} else {
|
} else {
|
||||||
Parameters::new(parameter_types.iter().map(|param_type| {
|
Parameters::new(parameter_types.iter().map(|param_type| {
|
||||||
Parameter::new(
|
Parameter::positional_only(None).with_annotated_type(*param_type)
|
||||||
Some(*param_type),
|
|
||||||
ParameterKind::PositionalOnly {
|
|
||||||
name: None,
|
|
||||||
default_ty: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>) {
|
pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) {
|
||||||
if self.callable_type == before {
|
if self.callable_type == before {
|
||||||
self.callable_type = after;
|
self.callable_type = after;
|
||||||
|
@ -87,7 +91,7 @@ impl<'a, 'db> IntoIterator for &'a Signatures<'db> {
|
||||||
type IntoIter = std::slice::Iter<'a, CallableSignature<'db>>;
|
type IntoIter = std::slice::Iter<'a, CallableSignature<'db>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.elements.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +183,10 @@ impl<'db> CallableSignature<'db> {
|
||||||
self
|
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>) {
|
fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) {
|
||||||
if self.callable_type == before {
|
if self.callable_type == before {
|
||||||
self.callable_type = after;
|
self.callable_type = after;
|
||||||
|
@ -191,7 +199,7 @@ impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> {
|
||||||
type IntoIter = std::slice::Iter<'a, Signature<'db>>;
|
type IntoIter = std::slice::Iter<'a, Signature<'db>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.overloads.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,18 +314,10 @@ impl<'db> Parameters<'db> {
|
||||||
pub(crate) fn todo() -> Self {
|
pub(crate) fn todo() -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: vec![
|
value: vec![
|
||||||
Parameter {
|
Parameter::variadic(Name::new_static("args"))
|
||||||
annotated_ty: Some(todo_type!("todo signature *args")),
|
.with_annotated_type(todo_type!("todo signature *args")),
|
||||||
kind: ParameterKind::Variadic {
|
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||||
name: Name::new_static("args"),
|
.with_annotated_type(todo_type!("todo signature **kwargs")),
|
||||||
},
|
|
||||||
},
|
|
||||||
Parameter {
|
|
||||||
annotated_ty: Some(todo_type!("todo signature **kwargs")),
|
|
||||||
kind: ParameterKind::KeywordVariadic {
|
|
||||||
name: Name::new_static("kwargs"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
is_gradual: false,
|
is_gradual: false,
|
||||||
}
|
}
|
||||||
|
@ -331,18 +331,10 @@ impl<'db> Parameters<'db> {
|
||||||
pub(crate) fn gradual_form() -> Self {
|
pub(crate) fn gradual_form() -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: vec![
|
value: vec![
|
||||||
Parameter {
|
Parameter::variadic(Name::new_static("args"))
|
||||||
annotated_ty: Some(Type::Dynamic(DynamicType::Any)),
|
.with_annotated_type(Type::Dynamic(DynamicType::Any)),
|
||||||
kind: ParameterKind::Variadic {
|
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||||
name: Name::new_static("args"),
|
.with_annotated_type(Type::Dynamic(DynamicType::Any)),
|
||||||
},
|
|
||||||
},
|
|
||||||
Parameter {
|
|
||||||
annotated_ty: Some(Type::Dynamic(DynamicType::Any)),
|
|
||||||
kind: ParameterKind::KeywordVariadic {
|
|
||||||
name: Name::new_static("kwargs"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
is_gradual: true,
|
is_gradual: true,
|
||||||
}
|
}
|
||||||
|
@ -357,18 +349,10 @@ impl<'db> Parameters<'db> {
|
||||||
pub(crate) fn unknown() -> Self {
|
pub(crate) fn unknown() -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: vec![
|
value: vec![
|
||||||
Parameter {
|
Parameter::variadic(Name::new_static("args"))
|
||||||
annotated_ty: Some(Type::Dynamic(DynamicType::Unknown)),
|
.with_annotated_type(Type::Dynamic(DynamicType::Unknown)),
|
||||||
kind: ParameterKind::Variadic {
|
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||||
name: Name::new_static("args"),
|
.with_annotated_type(Type::Dynamic(DynamicType::Unknown)),
|
||||||
},
|
|
||||||
},
|
|
||||||
Parameter {
|
|
||||||
annotated_ty: Some(Type::Dynamic(DynamicType::Unknown)),
|
|
||||||
kind: ParameterKind::KeywordVariadic {
|
|
||||||
name: Name::new_static("kwargs"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
is_gradual: true,
|
is_gradual: true,
|
||||||
}
|
}
|
||||||
|
@ -387,7 +371,7 @@ impl<'db> Parameters<'db> {
|
||||||
kwarg,
|
kwarg,
|
||||||
range: _,
|
range: _,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
let default_ty = |param: &ast::ParameterWithDefault| {
|
let default_type = |param: &ast::ParameterWithDefault| {
|
||||||
param
|
param
|
||||||
.default()
|
.default()
|
||||||
.map(|default| definition_expression_type(db, definition, default))
|
.map(|default| definition_expression_type(db, definition, default))
|
||||||
|
@ -399,7 +383,7 @@ impl<'db> Parameters<'db> {
|
||||||
&arg.parameter,
|
&arg.parameter,
|
||||||
ParameterKind::PositionalOnly {
|
ParameterKind::PositionalOnly {
|
||||||
name: Some(arg.parameter.name.id.clone()),
|
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,
|
&arg.parameter,
|
||||||
ParameterKind::PositionalOrKeyword {
|
ParameterKind::PositionalOrKeyword {
|
||||||
name: arg.parameter.name.id.clone(),
|
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,
|
&arg.parameter,
|
||||||
ParameterKind::KeywordOnly {
|
ParameterKind::KeywordOnly {
|
||||||
name: arg.parameter.name.id.clone(),
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||||
pub(crate) struct Parameter<'db> {
|
pub(crate) struct Parameter<'db> {
|
||||||
/// Annotated type of the parameter.
|
/// Annotated type of the parameter.
|
||||||
annotated_ty: Option<Type<'db>>,
|
annotated_type: Option<Type<'db>>,
|
||||||
|
|
||||||
kind: ParameterKind<'db>,
|
kind: ParameterKind<'db>,
|
||||||
|
pub(crate) form: ParameterForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> Parameter<'db> {
|
impl<'db> Parameter<'db> {
|
||||||
pub(crate) fn new(annotated_ty: Option<Type<'db>>, kind: ParameterKind<'db>) -> Self {
|
pub(crate) fn positional_only(name: Option<Name>) -> Self {
|
||||||
Self { annotated_ty, kind }
|
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(
|
fn from_node_and_kind(
|
||||||
|
@ -548,10 +600,11 @@ impl<'db> Parameter<'db> {
|
||||||
kind: ParameterKind<'db>,
|
kind: ParameterKind<'db>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
annotated_ty: parameter
|
annotated_type: parameter
|
||||||
.annotation()
|
.annotation()
|
||||||
.map(|annotation| definition_expression_type(db, definition, annotation)),
|
.map(|annotation| definition_expression_type(db, definition, annotation)),
|
||||||
kind,
|
kind,
|
||||||
|
form: ParameterForm::Value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,7 +651,7 @@ impl<'db> Parameter<'db> {
|
||||||
|
|
||||||
/// Annotated type of the parameter, if annotated.
|
/// Annotated type of the parameter, if annotated.
|
||||||
pub(crate) fn annotated_type(&self) -> Option<Type<'db>> {
|
pub(crate) fn annotated_type(&self) -> Option<Type<'db>> {
|
||||||
self.annotated_ty
|
self.annotated_type
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kind of the parameter.
|
/// Kind of the parameter.
|
||||||
|
@ -629,11 +682,10 @@ impl<'db> Parameter<'db> {
|
||||||
/// Default-value type of the parameter, if any.
|
/// Default-value type of the parameter, if any.
|
||||||
pub(crate) fn default_type(&self) -> Option<Type<'db>> {
|
pub(crate) fn default_type(&self) -> Option<Type<'db>> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
ParameterKind::PositionalOnly { default_ty, .. } => default_ty,
|
ParameterKind::PositionalOnly { default_type, .. }
|
||||||
ParameterKind::PositionalOrKeyword { default_ty, .. } => default_ty,
|
| ParameterKind::PositionalOrKeyword { default_type, .. }
|
||||||
ParameterKind::Variadic { .. } => None,
|
| ParameterKind::KeywordOnly { default_type, .. } => default_type,
|
||||||
ParameterKind::KeywordOnly { default_ty, .. } => default_ty,
|
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => None,
|
||||||
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
|
/// It is possible for signatures to be defined in ways that leave positional-only parameters
|
||||||
/// nameless (e.g. via `Callable` annotations).
|
/// nameless (e.g. via `Callable` annotations).
|
||||||
name: Option<Name>,
|
name: Option<Name>,
|
||||||
default_ty: Option<Type<'db>>,
|
default_type: Option<Type<'db>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Positional-or-keyword parameter, e.g. `def f(x): ...`
|
/// Positional-or-keyword parameter, e.g. `def f(x): ...`
|
||||||
PositionalOrKeyword {
|
PositionalOrKeyword {
|
||||||
/// Parameter name.
|
/// Parameter name.
|
||||||
name: Name,
|
name: Name,
|
||||||
default_ty: Option<Type<'db>>,
|
default_type: Option<Type<'db>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Variadic parameter, e.g. `def f(*args): ...`
|
/// Variadic parameter, e.g. `def f(*args): ...`
|
||||||
|
@ -667,7 +719,7 @@ pub(crate) enum ParameterKind<'db> {
|
||||||
KeywordOnly {
|
KeywordOnly {
|
||||||
/// Parameter name.
|
/// Parameter name.
|
||||||
name: Name,
|
name: Name,
|
||||||
default_ty: Option<Type<'db>>,
|
default_type: Option<Type<'db>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Variadic keywords parameter, e.g. `def f(**kwargs): ...`
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -734,74 +793,28 @@ mod tests {
|
||||||
assert_params(
|
assert_params(
|
||||||
&sig,
|
&sig,
|
||||||
&[
|
&[
|
||||||
Parameter {
|
Parameter::positional_only(Some(Name::new_static("a"))),
|
||||||
annotated_ty: None,
|
Parameter::positional_only(Some(Name::new_static("b")))
|
||||||
kind: ParameterKind::PositionalOnly {
|
.with_annotated_type(KnownClass::Int.to_instance(&db)),
|
||||||
name: Some(Name::new_static("a")),
|
Parameter::positional_only(Some(Name::new_static("c")))
|
||||||
default_ty: None,
|
.with_default_type(Type::IntLiteral(1)),
|
||||||
},
|
Parameter::positional_only(Some(Name::new_static("d")))
|
||||||
},
|
.with_annotated_type(KnownClass::Int.to_instance(&db))
|
||||||
Parameter {
|
.with_default_type(Type::IntLiteral(2)),
|
||||||
annotated_ty: Some(KnownClass::Int.to_instance(&db)),
|
Parameter::positional_or_keyword(Name::new_static("e"))
|
||||||
kind: ParameterKind::PositionalOnly {
|
.with_default_type(Type::IntLiteral(3)),
|
||||||
name: Some(Name::new_static("b")),
|
Parameter::positional_or_keyword(Name::new_static("f"))
|
||||||
default_ty: None,
|
.with_annotated_type(Type::IntLiteral(4))
|
||||||
},
|
.with_default_type(Type::IntLiteral(4)),
|
||||||
},
|
Parameter::variadic(Name::new_static("args"))
|
||||||
Parameter {
|
.with_annotated_type(Type::object(&db)),
|
||||||
annotated_ty: None,
|
Parameter::keyword_only(Name::new_static("g"))
|
||||||
kind: ParameterKind::PositionalOnly {
|
.with_default_type(Type::IntLiteral(5)),
|
||||||
name: Some(Name::new_static("c")),
|
Parameter::keyword_only(Name::new_static("h"))
|
||||||
default_ty: Some(Type::IntLiteral(1)),
|
.with_annotated_type(Type::IntLiteral(6))
|
||||||
},
|
.with_default_type(Type::IntLiteral(6)),
|
||||||
},
|
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||||
Parameter {
|
.with_annotated_type(KnownClass::Str.to_instance(&db)),
|
||||||
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"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -828,15 +841,16 @@ mod tests {
|
||||||
let sig = func.internal_signature(&db);
|
let sig = func.internal_signature(&db);
|
||||||
|
|
||||||
let [Parameter {
|
let [Parameter {
|
||||||
annotated_ty,
|
annotated_type,
|
||||||
kind: ParameterKind::PositionalOrKeyword { name, .. },
|
kind: ParameterKind::PositionalOrKeyword { name, .. },
|
||||||
|
..
|
||||||
}] = &sig.parameters.value[..]
|
}] = &sig.parameters.value[..]
|
||||||
else {
|
else {
|
||||||
panic!("expected one positional-or-keyword parameter");
|
panic!("expected one positional-or-keyword parameter");
|
||||||
};
|
};
|
||||||
assert_eq!(name, "a");
|
assert_eq!(name, "a");
|
||||||
// Parameter resolution not deferred; we should see A not B
|
// 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]
|
#[test]
|
||||||
|
@ -861,15 +875,16 @@ mod tests {
|
||||||
let sig = func.internal_signature(&db);
|
let sig = func.internal_signature(&db);
|
||||||
|
|
||||||
let [Parameter {
|
let [Parameter {
|
||||||
annotated_ty,
|
annotated_type,
|
||||||
kind: ParameterKind::PositionalOrKeyword { name, .. },
|
kind: ParameterKind::PositionalOrKeyword { name, .. },
|
||||||
|
..
|
||||||
}] = &sig.parameters.value[..]
|
}] = &sig.parameters.value[..]
|
||||||
else {
|
else {
|
||||||
panic!("expected one positional-or-keyword parameter");
|
panic!("expected one positional-or-keyword parameter");
|
||||||
};
|
};
|
||||||
assert_eq!(name, "a");
|
assert_eq!(name, "a");
|
||||||
// Parameter resolution deferred; we should see B
|
// 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]
|
#[test]
|
||||||
|
@ -894,11 +909,13 @@ mod tests {
|
||||||
let sig = func.internal_signature(&db);
|
let sig = func.internal_signature(&db);
|
||||||
|
|
||||||
let [Parameter {
|
let [Parameter {
|
||||||
annotated_ty: a_annotated_ty,
|
annotated_type: a_annotated_ty,
|
||||||
kind: ParameterKind::PositionalOrKeyword { name: a_name, .. },
|
kind: ParameterKind::PositionalOrKeyword { name: a_name, .. },
|
||||||
|
..
|
||||||
}, Parameter {
|
}, Parameter {
|
||||||
annotated_ty: b_annotated_ty,
|
annotated_type: b_annotated_ty,
|
||||||
kind: ParameterKind::PositionalOrKeyword { name: b_name, .. },
|
kind: ParameterKind::PositionalOrKeyword { name: b_name, .. },
|
||||||
|
..
|
||||||
}] = &sig.parameters.value[..]
|
}] = &sig.parameters.value[..]
|
||||||
else {
|
else {
|
||||||
panic!("expected two positional-or-keyword parameters");
|
panic!("expected two positional-or-keyword parameters");
|
||||||
|
@ -935,11 +952,13 @@ mod tests {
|
||||||
let sig = func.internal_signature(&db);
|
let sig = func.internal_signature(&db);
|
||||||
|
|
||||||
let [Parameter {
|
let [Parameter {
|
||||||
annotated_ty: a_annotated_ty,
|
annotated_type: a_annotated_ty,
|
||||||
kind: ParameterKind::PositionalOrKeyword { name: a_name, .. },
|
kind: ParameterKind::PositionalOrKeyword { name: a_name, .. },
|
||||||
|
..
|
||||||
}, Parameter {
|
}, Parameter {
|
||||||
annotated_ty: b_annotated_ty,
|
annotated_type: b_annotated_ty,
|
||||||
kind: ParameterKind::PositionalOrKeyword { name: b_name, .. },
|
kind: ParameterKind::PositionalOrKeyword { name: b_name, .. },
|
||||||
|
..
|
||||||
}] = &sig.parameters.value[..]
|
}] = &sig.parameters.value[..]
|
||||||
else {
|
else {
|
||||||
panic!("expected two positional-or-keyword parameters");
|
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": {
|
"conflicting-declarations": {
|
||||||
"title": "detects conflicting declarations",
|
"title": "detects conflicting declarations",
|
||||||
"description": "TODO #14889",
|
"description": "TODO #14889",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue