mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
wip
This commit is contained in:
parent
9903104328
commit
c7d44d0d3d
10 changed files with 521 additions and 118 deletions
|
@ -0,0 +1,92 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
assertion_line: 421
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: functions.md - Generic functions: Legacy syntax - Inferring a bound typevar
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing import TypeVar
|
||||||
|
2 | from typing_extensions import reveal_type
|
||||||
|
3 |
|
||||||
|
4 | T = TypeVar("T", bound=int)
|
||||||
|
5 |
|
||||||
|
6 | def f(x: T) -> T:
|
||||||
|
7 | return x
|
||||||
|
8 |
|
||||||
|
9 | reveal_type(f(1)) # revealed: Literal[1]
|
||||||
|
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
11 | # error: [invalid-argument-type]
|
||||||
|
12 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
info[revealed-type]: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:9:13
|
||||||
|
|
|
||||||
|
7 | return x
|
||||||
|
8 |
|
||||||
|
9 | reveal_type(f(1)) # revealed: Literal[1]
|
||||||
|
| ^^^^ `_T@reveal_type | Literal[1]`
|
||||||
|
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
11 | # error: [invalid-argument-type]
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
info[revealed-type]: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:10:13
|
||||||
|
|
|
||||||
|
9 | reveal_type(f(1)) # revealed: Literal[1]
|
||||||
|
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
| ^^^^^^^ `_T@reveal_type | Literal[True]`
|
||||||
|
11 | # error: [invalid-argument-type]
|
||||||
|
12 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
info[revealed-type]: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:12:13
|
||||||
|
|
|
||||||
|
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
11 | # error: [invalid-argument-type]
|
||||||
|
12 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
| ^^^^^^^^^^^ `_T@reveal_type`
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||||
|
--> src/mdtest_snippet.py:12:15
|
||||||
|
|
|
||||||
|
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
11 | # error: [invalid-argument-type]
|
||||||
|
12 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound `int` of type variable `T`
|
||||||
|
|
|
||||||
|
info: Type variable defined here
|
||||||
|
--> src/mdtest_snippet.py:4:1
|
||||||
|
|
|
||||||
|
2 | from typing_extensions import reveal_type
|
||||||
|
3 |
|
||||||
|
4 | T = TypeVar("T", bound=int)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
5 |
|
||||||
|
6 | def f(x: T) -> T:
|
||||||
|
|
|
||||||
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
|
```
|
|
@ -0,0 +1,88 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
assertion_line: 421
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: functions.md - Generic functions: PEP 695 syntax - Inferring a bound typevar
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing_extensions import reveal_type
|
||||||
|
2 |
|
||||||
|
3 | def f[T: int](x: T) -> T:
|
||||||
|
4 | return x
|
||||||
|
5 |
|
||||||
|
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||||
|
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
8 | # error: [invalid-argument-type]
|
||||||
|
9 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
info[revealed-type]: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:6:13
|
||||||
|
|
|
||||||
|
4 | return x
|
||||||
|
5 |
|
||||||
|
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||||
|
| ^^^^ `_T@reveal_type | Literal[1]`
|
||||||
|
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
8 | # error: [invalid-argument-type]
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
info[revealed-type]: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:7:13
|
||||||
|
|
|
||||||
|
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||||
|
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
| ^^^^^^^ `_T@reveal_type | Literal[True]`
|
||||||
|
8 | # error: [invalid-argument-type]
|
||||||
|
9 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
info[revealed-type]: Revealed type
|
||||||
|
--> src/mdtest_snippet.py:9:13
|
||||||
|
|
|
||||||
|
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
8 | # error: [invalid-argument-type]
|
||||||
|
9 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
| ^^^^^^^^^^^ `_T@reveal_type`
|
||||||
|
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||||
|
--> src/mdtest_snippet.py:9:15
|
||||||
|
|
|
||||||
|
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||||
|
8 | # error: [invalid-argument-type]
|
||||||
|
9 | reveal_type(f("string")) # revealed: Unknown
|
||||||
|
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound `int` of type variable `T`
|
||||||
|
|
|
||||||
|
info: Type variable defined here
|
||||||
|
--> src/mdtest_snippet.py:3:7
|
||||||
|
|
|
||||||
|
1 | from typing_extensions import reveal_type
|
||||||
|
2 |
|
||||||
|
3 | def f[T: int](x: T) -> T:
|
||||||
|
| ^^^^^^
|
||||||
|
4 | return x
|
||||||
|
|
|
||||||
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
|
```
|
|
@ -4853,11 +4853,10 @@ impl<'db> Type<'db> {
|
||||||
fn try_call(
|
fn try_call(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
argument_types: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'_, 'db>,
|
||||||
) -> Result<Bindings<'db>, CallError<'db>> {
|
) -> Result<Bindings<'db>, CallError<'db>> {
|
||||||
self.bindings(db)
|
let (bindings, argument_types) = self.bindings(db).match_parameters(db, arguments);
|
||||||
.match_parameters(db, argument_types)
|
bindings.check_types(db, arguments, &argument_types, &TypeContext::default())
|
||||||
.check_types(db, argument_types, &TypeContext::default())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a dunder method on the meta-type of `self` and call it.
|
/// Look up a dunder method on the meta-type of `self` and call it.
|
||||||
|
@ -4889,7 +4888,7 @@ impl<'db> Type<'db> {
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
name: &str,
|
name: &str,
|
||||||
argument_types: &mut CallArguments<'_, 'db>,
|
arguments: &mut CallArguments<'_, 'db>,
|
||||||
policy: MemberLookupPolicy,
|
policy: MemberLookupPolicy,
|
||||||
) -> Result<Bindings<'db>, CallDunderError<'db>> {
|
) -> Result<Bindings<'db>, CallDunderError<'db>> {
|
||||||
// Implicit calls to dunder methods never access instance members, so we pass
|
// Implicit calls to dunder methods never access instance members, so we pass
|
||||||
|
@ -4903,10 +4902,14 @@ impl<'db> Type<'db> {
|
||||||
.place
|
.place
|
||||||
{
|
{
|
||||||
Place::Type(dunder_callable, boundness) => {
|
Place::Type(dunder_callable, boundness) => {
|
||||||
let bindings = dunder_callable
|
let (bindings, argument_types) =
|
||||||
.bindings(db)
|
dunder_callable.bindings(db).match_parameters(db, arguments);
|
||||||
.match_parameters(db, argument_types)
|
let bindings = bindings.check_types(
|
||||||
.check_types(db, argument_types, &TypeContext::default())?;
|
db,
|
||||||
|
arguments,
|
||||||
|
&argument_types,
|
||||||
|
&TypeContext::default(),
|
||||||
|
)?;
|
||||||
if boundness == Boundness::PossiblyUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
return Err(CallDunderError::PossiblyUnbound(Box::new(bindings)));
|
return Err(CallDunderError::PossiblyUnbound(Box::new(bindings)));
|
||||||
}
|
}
|
||||||
|
@ -5389,8 +5392,7 @@ impl<'db> Type<'db> {
|
||||||
let new_call_outcome = new_method.and_then(|new_method| {
|
let new_call_outcome = new_method.and_then(|new_method| {
|
||||||
match new_method.place.try_call_dunder_get(db, self_type) {
|
match new_method.place.try_call_dunder_get(db, self_type) {
|
||||||
Place::Type(new_method, boundness) => {
|
Place::Type(new_method, boundness) => {
|
||||||
let result =
|
let result = new_method.try_call(db, &argument_types.with_self(self_type));
|
||||||
new_method.try_call(db, argument_types.with_self(Some(self_type)).as_ref());
|
|
||||||
if boundness == Boundness::PossiblyUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
Some(Err(DunderNewCallError::PossiblyUnbound(result.err())))
|
Some(Err(DunderNewCallError::PossiblyUnbound(result.err())))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::types::call::bind::BindingError;
|
||||||
|
|
||||||
mod arguments;
|
mod arguments;
|
||||||
pub(crate) mod bind;
|
pub(crate) mod bind;
|
||||||
pub(super) use arguments::{Argument, CallArguments};
|
pub(super) use arguments::{Argument, CallArgumentTypes, CallArguments};
|
||||||
pub(super) use bind::{Binding, Bindings, CallableBinding, MatchedArgument};
|
pub(super) use bind::{Binding, Bindings, CallableBinding, MatchedArgument};
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use itertools::{Either, Itertools};
|
use itertools::{Either, Itertools};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
|
||||||
|
@ -90,21 +88,16 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
self.types.iter().map(|ty| ty.unwrap_or_else(Type::unknown))
|
self.types.iter().map(|ty| ty.unwrap_or_else(Type::unknown))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepend an optional extra synthetic argument (for a `self` or `cls` parameter) to the front
|
/// Prepend a extra synthetic argument (for a `self` or `cls` parameter) to the front
|
||||||
/// of this argument list. (If `bound_self` is none, we return the argument list
|
/// of this argument list.)
|
||||||
/// unmodified.)
|
pub(crate) fn with_self(&self, bound_self: Type<'db>) -> Self {
|
||||||
pub(crate) fn with_self(&self, bound_self: Option<Type<'db>>) -> Cow<'_, Self> {
|
|
||||||
if bound_self.is_some() {
|
|
||||||
let arguments = std::iter::once(Argument::Synthetic)
|
let arguments = std::iter::once(Argument::Synthetic)
|
||||||
.chain(self.arguments.iter().copied())
|
.chain(self.arguments.iter().copied())
|
||||||
.collect();
|
.collect();
|
||||||
let types = std::iter::once(bound_self)
|
let types = std::iter::once(Some(bound_self))
|
||||||
.chain(self.types.iter().copied())
|
.chain(self.types.iter().copied())
|
||||||
.collect();
|
.collect();
|
||||||
Cow::Owned(CallArguments { arguments, types })
|
CallArguments { arguments, types }
|
||||||
} else {
|
|
||||||
Cow::Borrowed(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Option<Type<'db>>)> + '_ {
|
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Option<Type<'db>>)> + '_ {
|
||||||
|
@ -117,32 +110,89 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
(self.arguments.iter().copied()).zip(self.types.iter_mut())
|
(self.arguments.iter().copied()).zip(self.types.iter_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn iter_with_types<'iter>(
|
||||||
|
&'iter self,
|
||||||
|
argument_types: &'iter CallArgumentTypes<'db>,
|
||||||
|
) -> impl Iterator<Item = (Argument<'a>, Option<Type<'db>>)> + 'iter {
|
||||||
|
(self.arguments.iter().copied()).zip(argument_types.iter().copied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'db> FromIterator<(Argument<'a>, Option<Type<'db>>)> for CallArguments<'a, 'db> {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = (Argument<'a>, Option<Type<'db>>)>,
|
||||||
|
{
|
||||||
|
let (arguments, types) = iter.into_iter().unzip();
|
||||||
|
Self { arguments, types }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider removing this, and go back to just storing nested `CallArguments`.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct CallArgumentTypes<'db> {
|
||||||
|
types: Vec<Option<Type<'db>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> CallArgumentTypes<'db> {
|
||||||
|
pub(crate) fn new(types: Vec<Option<Type<'db>>>) -> Self {
|
||||||
|
Self { types }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn types(&self) -> &[Option<Type<'db>>] {
|
||||||
|
&self.types
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.types.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn iter(&self) -> impl Iterator<Item = &Option<Type<'db>>> {
|
||||||
|
self.types.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Option<Type<'db>>> {
|
||||||
|
self.types.iter_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_self(&self, bound_self: Type<'db>) -> Self {
|
||||||
|
let types = std::iter::once(Some(bound_self))
|
||||||
|
.chain(self.types.iter().copied())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
CallArgumentTypes { types }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> CallArgumentTypes<'db> {
|
||||||
/// Returns an iterator on performing [argument type expansion].
|
/// Returns an iterator on performing [argument type expansion].
|
||||||
///
|
///
|
||||||
/// Each element of the iterator represents a set of argument lists, where each argument list
|
/// Each element of the iterator represents a set of argument lists, where each argument list
|
||||||
/// contains the same arguments, but with one or more of the argument types expanded.
|
/// contains the same arguments, but with one or more of the argument types expanded.
|
||||||
///
|
///
|
||||||
|
/// The iterator will return an element for every argument, even if it could not be expanded.
|
||||||
|
///
|
||||||
/// [argument type expansion]: https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
|
/// [argument type expansion]: https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
|
||||||
pub(super) fn expand(&self, db: &'db dyn Db) -> impl Iterator<Item = Expansion<'a, 'db>> + '_ {
|
pub(super) fn expand(&self, db: &'db dyn Db) -> impl Iterator<Item = Expansion<'db>> + '_ {
|
||||||
/// Maximum number of argument lists that can be generated in a single expansion step.
|
/// Maximum number of argument lists that can be generated in a single expansion step.
|
||||||
static MAX_EXPANSIONS: usize = 512;
|
static MAX_EXPANSIONS: usize = 512;
|
||||||
|
|
||||||
/// Represents the state of the expansion process.
|
/// Represents the state of the expansion process.
|
||||||
enum State<'a, 'b, 'db> {
|
enum State<'a, 'db> {
|
||||||
LimitReached(usize),
|
LimitReached(usize),
|
||||||
Expanding(ExpandingState<'a, 'b, 'db>),
|
Expanding(ExpandingState<'a, 'db>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the expanding state with either the initial types or the expanded types.
|
/// Represents the expanding state with either the initial types or the expanded types.
|
||||||
///
|
///
|
||||||
/// This is useful to avoid cloning the initial types vector if none of the types can be
|
/// This is useful to avoid cloning the initial types vector if none of the types can be
|
||||||
/// expanded.
|
/// expanded.
|
||||||
enum ExpandingState<'a, 'b, 'db> {
|
enum ExpandingState<'a, 'db> {
|
||||||
Initial(&'b Vec<Option<Type<'db>>>),
|
Initial(&'a [Option<Type<'db>>]),
|
||||||
Expanded(Vec<CallArguments<'a, 'db>>),
|
Expanded(Vec<CallArgumentTypes<'db>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> ExpandingState<'_, '_, 'db> {
|
impl<'db> ExpandingState<'_, 'db> {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
ExpandingState::Initial(_) => 1,
|
ExpandingState::Initial(_) => 1,
|
||||||
|
@ -152,11 +202,9 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
|
|
||||||
fn iter(&self) -> impl Iterator<Item = &[Option<Type<'db>>]> + '_ {
|
fn iter(&self) -> impl Iterator<Item = &[Option<Type<'db>>]> + '_ {
|
||||||
match self {
|
match self {
|
||||||
ExpandingState::Initial(types) => {
|
ExpandingState::Initial(types) => Either::Left(std::iter::once(*types)),
|
||||||
Either::Left(std::iter::once(types.as_slice()))
|
|
||||||
}
|
|
||||||
ExpandingState::Expanded(expanded) => {
|
ExpandingState::Expanded(expanded) => {
|
||||||
Either::Right(expanded.iter().map(CallArguments::types))
|
Either::Right(expanded.iter().map(CallArgumentTypes::types))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +213,7 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
|
||||||
std::iter::successors(
|
std::iter::successors(
|
||||||
Some(State::Expanding(ExpandingState::Initial(&self.types))),
|
Some(State::Expanding(ExpandingState::Initial(self.types()))),
|
||||||
move |previous| {
|
move |previous| {
|
||||||
let state = match previous {
|
let state = match previous {
|
||||||
State::LimitReached(index) => return Some(State::LimitReached(*index)),
|
State::LimitReached(index) => return Some(State::LimitReached(*index)),
|
||||||
|
@ -174,7 +222,7 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
|
|
||||||
// Find the next type that can be expanded.
|
// Find the next type that can be expanded.
|
||||||
let expanded_types = loop {
|
let expanded_types = loop {
|
||||||
let arg_type = self.types.get(index)?;
|
let arg_type = self.types().get(index)?;
|
||||||
if let Some(arg_type) = arg_type {
|
if let Some(arg_type) = arg_type {
|
||||||
if let Some(expanded_types) = expand_type(db, *arg_type) {
|
if let Some(expanded_types) = expand_type(db, *arg_type) {
|
||||||
break expanded_types;
|
break expanded_types;
|
||||||
|
@ -198,10 +246,7 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
for subtype in &expanded_types {
|
for subtype in &expanded_types {
|
||||||
let mut new_expanded_types = pre_expanded_types.to_vec();
|
let mut new_expanded_types = pre_expanded_types.to_vec();
|
||||||
new_expanded_types[index] = Some(*subtype);
|
new_expanded_types[index] = Some(*subtype);
|
||||||
expanded_arguments.push(CallArguments::new(
|
expanded_arguments.push(CallArgumentTypes::new(new_expanded_types));
|
||||||
self.arguments.clone(),
|
|
||||||
new_expanded_types,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,8 +271,8 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
||||||
|
|
||||||
/// Represents a single element of the expansion process for argument types for [`expand`].
|
/// Represents a single element of the expansion process for argument types for [`expand`].
|
||||||
///
|
///
|
||||||
/// [`expand`]: CallArguments::expand
|
/// [`expand`]: CallArgumentTypes::expand
|
||||||
pub(super) enum Expansion<'a, 'db> {
|
pub(super) enum Expansion<'db> {
|
||||||
/// Indicates that the expansion process has reached the maximum number of argument lists
|
/// Indicates that the expansion process has reached the maximum number of argument lists
|
||||||
/// that can be generated in a single step.
|
/// that can be generated in a single step.
|
||||||
///
|
///
|
||||||
|
@ -237,17 +282,7 @@ pub(super) enum Expansion<'a, 'db> {
|
||||||
|
|
||||||
/// Contains the expanded argument lists, where each list contains the same arguments, but with
|
/// Contains the expanded argument lists, where each list contains the same arguments, but with
|
||||||
/// one or more of the argument types expanded.
|
/// one or more of the argument types expanded.
|
||||||
Expanded(Vec<CallArguments<'a, 'db>>),
|
Expanded(Vec<CallArgumentTypes<'db>>),
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'db> FromIterator<(Argument<'a>, Option<Type<'db>>)> for CallArguments<'a, 'db> {
|
|
||||||
fn from_iter<T>(iter: T) -> Self
|
|
||||||
where
|
|
||||||
T: IntoIterator<Item = (Argument<'a>, Option<Type<'db>>)>,
|
|
||||||
{
|
|
||||||
let (arguments, types) = iter.into_iter().unzip();
|
|
||||||
Self { arguments, types }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the type can be expanded into its subtypes.
|
/// Returns `true` if the type can be expanded into its subtypes.
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
//! [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 std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::{fmt, iter};
|
||||||
|
|
||||||
use itertools::{Either, Itertools};
|
use itertools::{Either, Itertools};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
|
@ -16,7 +17,7 @@ use crate::Program;
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::dunder_all::dunder_all_names;
|
use crate::dunder_all::dunder_all_names;
|
||||||
use crate::place::{Boundness, Place};
|
use crate::place::{Boundness, Place};
|
||||||
use crate::types::call::arguments::{Expansion, is_expandable_type};
|
use crate::types::call::arguments::{CallArgumentTypes, is_expandable_type};
|
||||||
use crate::types::diagnostic::{
|
use crate::types::diagnostic::{
|
||||||
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
|
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
|
||||||
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, POSITIONAL_ONLY_PARAMETER_AS_KWARG,
|
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, POSITIONAL_ONLY_PARAMETER_AS_KWARG,
|
||||||
|
@ -38,6 +39,42 @@ use crate::types::{
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||||
use ruff_python_ast::{self as ast, PythonVersion};
|
use ruff_python_ast::{self as ast, PythonVersion};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct MatchedCallArguments<'db> {
|
||||||
|
bindings: SmallVec<[BindingCallArguments<'db>; 1]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> MatchedCallArguments<'db> {
|
||||||
|
pub(crate) fn bindings(&self) -> impl Iterator<Item = &BindingCallArguments<'db>> {
|
||||||
|
self.bindings.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bindings_mut(&mut self) -> impl Iterator<Item = &mut BindingCallArguments<'db>> {
|
||||||
|
self.bindings.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct BindingCallArguments<'db> {
|
||||||
|
overloads: SmallVec<[CallArgumentTypes<'db>; 1]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'db> From<&CallArguments<'a, 'db>> for CallArgumentTypes<'db> {
|
||||||
|
fn from(arguments: &CallArguments<'a, 'db>) -> Self {
|
||||||
|
CallArgumentTypes::new(arguments.types().to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> BindingCallArguments<'db> {
|
||||||
|
pub(crate) fn overloads(&self) -> impl Iterator<Item = &CallArgumentTypes<'db>> {
|
||||||
|
self.overloads.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn overloads_mut(&mut self) -> impl Iterator<Item = &mut CallArgumentTypes<'db>> {
|
||||||
|
self.overloads.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Binding information for a possible union of callables. At a call site, the arguments must be
|
/// Binding information for a possible union of callables. At a call site, the arguments must be
|
||||||
/// 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.
|
||||||
///
|
///
|
||||||
|
@ -102,18 +139,33 @@ impl<'db> Bindings<'db> {
|
||||||
///
|
///
|
||||||
/// Once you have argument types available, you can call [`check_types`][Self::check_types] to
|
/// 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.
|
/// verify that each argument type is assignable to the corresponding parameter type.
|
||||||
pub(crate) fn match_parameters(
|
pub(crate) fn match_parameters<'a>(
|
||||||
mut self,
|
mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
arguments: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'a, 'db>,
|
||||||
) -> Self {
|
) -> (Self, MatchedCallArguments<'db>) {
|
||||||
let mut argument_forms = ArgumentForms::new(arguments.len());
|
let mut argument_forms = ArgumentForms::new(arguments.len());
|
||||||
for binding in &mut self.elements {
|
for binding in &mut self.elements {
|
||||||
binding.match_parameters(db, arguments, &mut argument_forms);
|
binding.match_parameters(db, arguments, &mut argument_forms);
|
||||||
}
|
}
|
||||||
argument_forms.shrink_to_fit();
|
argument_forms.shrink_to_fit();
|
||||||
self.argument_forms = argument_forms;
|
self.argument_forms = argument_forms;
|
||||||
self
|
|
||||||
|
let binding_arguments = self
|
||||||
|
.elements
|
||||||
|
.iter()
|
||||||
|
.map(|element| BindingCallArguments {
|
||||||
|
overloads: (0..element.overloads.len())
|
||||||
|
.map(|_| CallArgumentTypes::from(arguments))
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let arguments = MatchedCallArguments {
|
||||||
|
bindings: binding_arguments,
|
||||||
|
};
|
||||||
|
|
||||||
|
(self, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that the type of each argument is assignable to type of the parameter that it was
|
/// Verify that the type of each argument is assignable to type of the parameter that it was
|
||||||
|
@ -131,12 +183,13 @@ impl<'db> Bindings<'db> {
|
||||||
pub(crate) fn check_types(
|
pub(crate) fn check_types(
|
||||||
mut self,
|
mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
argument_types: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'_, 'db>,
|
||||||
|
argument_types: &MatchedCallArguments<'db>,
|
||||||
call_expression_tcx: &TypeContext<'db>,
|
call_expression_tcx: &TypeContext<'db>,
|
||||||
) -> Result<Self, CallError<'db>> {
|
) -> Result<Self, CallError<'db>> {
|
||||||
for element in &mut self.elements {
|
for (element, argument_types) in iter::zip(&mut self.elements, argument_types.bindings()) {
|
||||||
if let Some(mut updated_argument_forms) =
|
if let Some(mut updated_argument_forms) =
|
||||||
element.check_types(db, argument_types, call_expression_tcx)
|
element.check_types(db, arguments, argument_types, call_expression_tcx)
|
||||||
{
|
{
|
||||||
// If this element returned a new set of argument forms (indicating successful
|
// If this element returned a new set of argument forms (indicating successful
|
||||||
// argument type expansion), update the `Bindings` with these forms.
|
// argument type expansion), update the `Bindings` with these forms.
|
||||||
|
@ -1276,7 +1329,10 @@ impl<'db> CallableBinding<'db> {
|
||||||
) {
|
) {
|
||||||
// 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 = arguments.with_self(self.bound_type);
|
let arguments = match self.bound_type {
|
||||||
|
None => Cow::Borrowed(arguments),
|
||||||
|
Some(bound_self) => Cow::Owned(arguments.with_self(bound_self)),
|
||||||
|
};
|
||||||
|
|
||||||
for overload in &mut self.overloads {
|
for overload in &mut self.overloads {
|
||||||
overload.match_parameters(db, arguments.as_ref(), argument_forms);
|
overload.match_parameters(db, arguments.as_ref(), argument_forms);
|
||||||
|
@ -1286,12 +1342,27 @@ impl<'db> CallableBinding<'db> {
|
||||||
fn check_types(
|
fn check_types(
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
argument_types: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'_, 'db>,
|
||||||
|
argument_types: &BindingCallArguments<'db>,
|
||||||
call_expression_tcx: &TypeContext<'db>,
|
call_expression_tcx: &TypeContext<'db>,
|
||||||
) -> Option<ArgumentForms> {
|
) -> Option<ArgumentForms> {
|
||||||
// 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 argument_types = argument_types.with_self(self.bound_type);
|
let (arguments, argument_types) = match self.bound_type {
|
||||||
|
None => (Cow::Borrowed(arguments), Cow::Borrowed(argument_types)),
|
||||||
|
Some(bound_self) => {
|
||||||
|
let arguments = arguments.with_self(bound_self);
|
||||||
|
let overloads = argument_types
|
||||||
|
.overloads()
|
||||||
|
.map(|argument_types| argument_types.with_self(bound_self))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(
|
||||||
|
Cow::Owned(arguments),
|
||||||
|
Cow::Owned(BindingCallArguments { overloads }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Step 1: Check the result of the arity check which is done by `match_parameters`
|
// Step 1: Check the result of the arity check which is done by `match_parameters`
|
||||||
let matching_overload_indexes = match self.matching_overload_index() {
|
let matching_overload_indexes = match self.matching_overload_index() {
|
||||||
|
@ -1300,15 +1371,26 @@ impl<'db> CallableBinding<'db> {
|
||||||
// still perform type checking for non-overloaded function to provide better user
|
// still perform type checking for non-overloaded function to provide better user
|
||||||
// experience.
|
// experience.
|
||||||
if let [overload] = self.overloads.as_mut_slice() {
|
if let [overload] = self.overloads.as_mut_slice() {
|
||||||
overload.check_types(db, argument_types.as_ref(), call_expression_tcx);
|
overload.check_types(
|
||||||
|
db,
|
||||||
|
arguments.as_ref(),
|
||||||
|
&argument_types.overloads[0],
|
||||||
|
call_expression_tcx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
MatchingOverloadIndex::Single(index) => {
|
MatchingOverloadIndex::Single(index) => {
|
||||||
// If only one candidate overload remains, it is the winning match. Evaluate it as
|
// If only one candidate overload remains, it is the winning match. Evaluate it as
|
||||||
// a regular (non-overloaded) call.
|
// a regular (non-overloaded) call.
|
||||||
self.matching_overload_index = Some(index);
|
self.matching_overload_index = Some(index);
|
||||||
self.overloads[index].check_types(db, argument_types.as_ref(), call_expression_tcx);
|
self.overloads[index].check_types(
|
||||||
|
db,
|
||||||
|
arguments.as_ref(),
|
||||||
|
&argument_types.overloads[index],
|
||||||
|
call_expression_tcx,
|
||||||
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
MatchingOverloadIndex::Multiple(indexes) => {
|
MatchingOverloadIndex::Multiple(indexes) => {
|
||||||
|
@ -1319,8 +1401,13 @@ impl<'db> CallableBinding<'db> {
|
||||||
|
|
||||||
// Step 2: Evaluate each remaining overload as a regular (non-overloaded) call to determine
|
// Step 2: Evaluate each remaining overload as a regular (non-overloaded) call to determine
|
||||||
// whether it is compatible with the supplied argument list.
|
// whether it is compatible with the supplied argument list.
|
||||||
for (_, overload) in self.matching_overloads_mut() {
|
for (overload_index, overload) in self.matching_overloads_mut() {
|
||||||
overload.check_types(db, argument_types.as_ref(), call_expression_tcx);
|
overload.check_types(
|
||||||
|
db,
|
||||||
|
arguments.as_ref(),
|
||||||
|
&argument_types.overloads[overload_index],
|
||||||
|
call_expression_tcx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.matching_overload_index() {
|
match self.matching_overload_index() {
|
||||||
|
@ -1352,7 +1439,8 @@ impl<'db> CallableBinding<'db> {
|
||||||
// If two or more candidate overloads remain, proceed to step 5.
|
// If two or more candidate overloads remain, proceed to step 5.
|
||||||
self.filter_overloads_using_any_or_unknown(
|
self.filter_overloads_using_any_or_unknown(
|
||||||
db,
|
db,
|
||||||
argument_types.as_ref(),
|
&arguments,
|
||||||
|
&argument_types,
|
||||||
&indexes,
|
&indexes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1365,29 +1453,45 @@ impl<'db> CallableBinding<'db> {
|
||||||
|
|
||||||
// Step 3: Perform "argument type expansion". Reference:
|
// Step 3: Perform "argument type expansion". Reference:
|
||||||
// https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
|
// https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
|
||||||
let mut expansions = argument_types.expand(db).peekable();
|
let overload_expansions = argument_types
|
||||||
|
.overloads()
|
||||||
|
.filter_map(|argument_types| {
|
||||||
|
let mut expanded = argument_types.expand(db).peekable();
|
||||||
|
if expanded.peek().is_some() {
|
||||||
|
Some(expanded)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Return early if there are no argument types to expand.
|
// Return early if there are no argument types to expand for any overload.
|
||||||
expansions.peek()?;
|
if overload_expansions.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
// At this point, there's at least one argument that can be expanded.
|
// At this point, there's at least one argument that can be expanded.
|
||||||
//
|
//
|
||||||
// This heuristic tries to detect if there's any need to perform argument type expansion or
|
// This heuristic tries to detect if there's any need to perform argument type expansion or
|
||||||
// not by checking whether there are any non-expandable argument type that cannot be
|
// not by checking whether there are any non-expandable argument type that cannot be
|
||||||
// assigned to any of the overloads.
|
// assigned to any of the overloads.
|
||||||
for (argument_index, (argument, argument_type)) in argument_types.iter().enumerate() {
|
for (argument_index, (argument, _)) in arguments.iter().enumerate() {
|
||||||
// TODO: Remove `Keywords` once `**kwargs` support is added
|
// TODO: Remove `Keywords` once `**kwargs` support is added
|
||||||
if matches!(argument, Argument::Synthetic | Argument::Keywords) {
|
if matches!(argument, Argument::Synthetic | Argument::Keywords) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let mut is_argument_assignable_to_any_overload = false;
|
||||||
|
'overload: for (overload_index, overload) in self.overloads.iter().enumerate() {
|
||||||
|
let argument_type =
|
||||||
|
argument_types.overloads[overload_index].types()[argument_index];
|
||||||
|
|
||||||
let Some(argument_type) = argument_type else {
|
let Some(argument_type) = argument_type else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if is_expandable_type(db, argument_type) {
|
if is_expandable_type(db, argument_type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut is_argument_assignable_to_any_overload = false;
|
|
||||||
'overload: for overload in &self.overloads {
|
|
||||||
for parameter_index in &overload.argument_matches[argument_index].parameters {
|
for parameter_index in &overload.argument_matches[argument_index].parameters {
|
||||||
let parameter_type = overload.signature.parameters()[*parameter_index]
|
let parameter_type = overload.signature.parameters()[*parameter_index]
|
||||||
.annotated_type()
|
.annotated_type()
|
||||||
|
@ -1400,9 +1504,8 @@ impl<'db> CallableBinding<'db> {
|
||||||
}
|
}
|
||||||
if !is_argument_assignable_to_any_overload {
|
if !is_argument_assignable_to_any_overload {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Argument at {argument_index} (`{}`) is not assignable to any of the \
|
"Argument at {argument_index} is not assignable to any of the \
|
||||||
remaining overloads, skipping argument type expansion",
|
remaining overloads, skipping argument type expansion"
|
||||||
argument_type.display(db)
|
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -1414,7 +1517,7 @@ impl<'db> CallableBinding<'db> {
|
||||||
// the non-expanded argument types.
|
// the non-expanded argument types.
|
||||||
let post_evaluation_snapshot = snapshotter.take(self);
|
let post_evaluation_snapshot = snapshotter.take(self);
|
||||||
|
|
||||||
for expansion in expansions {
|
for expansion in overload_expansions {
|
||||||
let expanded_argument_lists = match expansion {
|
let expanded_argument_lists = match expansion {
|
||||||
Expansion::LimitReached(index) => {
|
Expansion::LimitReached(index) => {
|
||||||
snapshotter.restore(self, post_evaluation_snapshot);
|
snapshotter.restore(self, post_evaluation_snapshot);
|
||||||
|
@ -1586,6 +1689,7 @@ impl<'db> CallableBinding<'db> {
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
arguments: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'_, 'db>,
|
||||||
|
_argument_types: &BindingCallArguments<'db>,
|
||||||
matching_overload_indexes: &[usize],
|
matching_overload_indexes: &[usize],
|
||||||
) {
|
) {
|
||||||
// These are the parameter indexes that matches the arguments that participate in the
|
// These are the parameter indexes that matches the arguments that participate in the
|
||||||
|
@ -1599,7 +1703,7 @@ impl<'db> CallableBinding<'db> {
|
||||||
// participating parameter indexes.
|
// participating parameter indexes.
|
||||||
let mut top_materialized_argument_types = vec![];
|
let mut top_materialized_argument_types = vec![];
|
||||||
|
|
||||||
for (argument_index, argument_type) in arguments.iter_types().enumerate() {
|
for (argument_index, (_, argument_type)) in arguments.iter().enumerate() {
|
||||||
let mut first_parameter_type: Option<Type<'db>> = None;
|
let mut first_parameter_type: Option<Type<'db>> = None;
|
||||||
let mut participating_parameter_index = None;
|
let mut participating_parameter_index = None;
|
||||||
|
|
||||||
|
@ -1624,7 +1728,11 @@ impl<'db> CallableBinding<'db> {
|
||||||
|
|
||||||
if let Some(parameter_index) = participating_parameter_index {
|
if let Some(parameter_index) = participating_parameter_index {
|
||||||
participating_parameter_indexes.insert(parameter_index);
|
participating_parameter_indexes.insert(parameter_index);
|
||||||
top_materialized_argument_types.push(argument_type.top_materialization(db));
|
top_materialized_argument_types.push(
|
||||||
|
argument_type
|
||||||
|
.unwrap_or_else(Type::unknown)
|
||||||
|
.top_materialization(db),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1750,6 +1858,12 @@ impl<'db> CallableBinding<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the overloads for this call binding, including
|
||||||
|
/// those that did not match.
|
||||||
|
pub(crate) fn overloads(&self) -> impl Iterator<Item = &Binding<'db>> {
|
||||||
|
self.overloads.iter()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all the overloads that matched for this call binding.
|
/// Returns an iterator over all the overloads that matched for this call binding.
|
||||||
pub(crate) fn matching_overloads(&self) -> impl Iterator<Item = (usize, &Binding<'db>)> {
|
pub(crate) fn matching_overloads(&self) -> impl Iterator<Item = (usize, &Binding<'db>)> {
|
||||||
self.overloads
|
self.overloads
|
||||||
|
@ -2378,6 +2492,7 @@ struct ArgumentTypeChecker<'a, 'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
signature: &'a Signature<'db>,
|
signature: &'a Signature<'db>,
|
||||||
arguments: &'a CallArguments<'a, 'db>,
|
arguments: &'a CallArguments<'a, 'db>,
|
||||||
|
argument_types: &'a CallArgumentTypes<'db>,
|
||||||
argument_matches: &'a [MatchedArgument<'db>],
|
argument_matches: &'a [MatchedArgument<'db>],
|
||||||
parameter_tys: &'a mut [Option<Type<'db>>],
|
parameter_tys: &'a mut [Option<Type<'db>>],
|
||||||
call_expression_tcx: &'a TypeContext<'db>,
|
call_expression_tcx: &'a TypeContext<'db>,
|
||||||
|
@ -2392,6 +2507,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
signature: &'a Signature<'db>,
|
signature: &'a Signature<'db>,
|
||||||
arguments: &'a CallArguments<'a, 'db>,
|
arguments: &'a CallArguments<'a, 'db>,
|
||||||
|
argument_types: &'a CallArgumentTypes<'db>,
|
||||||
argument_matches: &'a [MatchedArgument<'db>],
|
argument_matches: &'a [MatchedArgument<'db>],
|
||||||
parameter_tys: &'a mut [Option<Type<'db>>],
|
parameter_tys: &'a mut [Option<Type<'db>>],
|
||||||
call_expression_tcx: &'a TypeContext<'db>,
|
call_expression_tcx: &'a TypeContext<'db>,
|
||||||
|
@ -2401,6 +2517,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||||
db,
|
db,
|
||||||
signature,
|
signature,
|
||||||
arguments,
|
arguments,
|
||||||
|
argument_types,
|
||||||
argument_matches,
|
argument_matches,
|
||||||
parameter_tys,
|
parameter_tys,
|
||||||
call_expression_tcx,
|
call_expression_tcx,
|
||||||
|
@ -2413,7 +2530,10 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||||
fn enumerate_argument_types(
|
fn enumerate_argument_types(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = (usize, Option<usize>, Argument<'a>, Type<'db>)> + 'a {
|
) -> impl Iterator<Item = (usize, Option<usize>, Argument<'a>, Type<'db>)> + 'a {
|
||||||
let mut iter = self.arguments.iter().enumerate();
|
let mut iter = self
|
||||||
|
.arguments
|
||||||
|
.iter_with_types(self.argument_types)
|
||||||
|
.enumerate();
|
||||||
let mut num_synthetic_args = 0;
|
let mut num_synthetic_args = 0;
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
let (argument_index, (argument, argument_type)) = iter.next()?;
|
let (argument_index, (argument, argument_type)) = iter.next()?;
|
||||||
|
@ -2829,12 +2949,14 @@ impl<'db> Binding<'db> {
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
arguments: &CallArguments<'_, 'db>,
|
arguments: &CallArguments<'_, 'db>,
|
||||||
|
argument_types: &CallArgumentTypes<'db>,
|
||||||
call_expression_tcx: &TypeContext<'db>,
|
call_expression_tcx: &TypeContext<'db>,
|
||||||
) {
|
) {
|
||||||
let mut checker = ArgumentTypeChecker::new(
|
let mut checker = ArgumentTypeChecker::new(
|
||||||
db,
|
db,
|
||||||
&self.signature,
|
&self.signature,
|
||||||
arguments,
|
arguments,
|
||||||
|
argument_types,
|
||||||
&self.argument_matches,
|
&self.argument_matches,
|
||||||
&mut self.parameter_tys,
|
&mut self.parameter_tys,
|
||||||
call_expression_tcx,
|
call_expression_tcx,
|
||||||
|
@ -2877,11 +2999,12 @@ impl<'db> Binding<'db> {
|
||||||
|
|
||||||
pub(crate) fn arguments_for_parameter<'a>(
|
pub(crate) fn arguments_for_parameter<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
argument_types: &'a CallArguments<'a, 'db>,
|
arguments: &'a CallArguments<'a, 'db>,
|
||||||
|
argument_types: &'a CallArgumentTypes<'db>,
|
||||||
parameter_index: usize,
|
parameter_index: usize,
|
||||||
) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + 'a {
|
) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + 'a {
|
||||||
argument_types
|
arguments
|
||||||
.iter()
|
.iter_with_types(argument_types)
|
||||||
.zip(&self.argument_matches)
|
.zip(&self.argument_matches)
|
||||||
.filter(move |(_, argument_matches)| {
|
.filter(move |(_, argument_matches)| {
|
||||||
argument_matches.parameters.contains(¶meter_index)
|
argument_matches.parameters.contains(¶meter_index)
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::semantic_index::symbol::Symbol;
|
||||||
use crate::semantic_index::{
|
use crate::semantic_index::{
|
||||||
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
|
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
|
||||||
};
|
};
|
||||||
|
use crate::types::call::CallArgumentTypes;
|
||||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||||
|
@ -4909,6 +4910,7 @@ impl KnownClass {
|
||||||
index: &SemanticIndex<'db>,
|
index: &SemanticIndex<'db>,
|
||||||
overload: &mut Binding<'db>,
|
overload: &mut Binding<'db>,
|
||||||
call_arguments: &CallArguments<'_, 'db>,
|
call_arguments: &CallArguments<'_, 'db>,
|
||||||
|
call_argument_types: &CallArgumentTypes<'db>,
|
||||||
call_expression: &ast::ExprCall,
|
call_expression: &ast::ExprCall,
|
||||||
) {
|
) {
|
||||||
let db = context.db();
|
let db = context.db();
|
||||||
|
@ -5127,7 +5129,7 @@ impl KnownClass {
|
||||||
let elements = UnionType::new(
|
let elements = UnionType::new(
|
||||||
db,
|
db,
|
||||||
overload
|
overload
|
||||||
.arguments_for_parameter(call_arguments, 1)
|
.arguments_for_parameter(call_arguments, call_argument_types, 1)
|
||||||
.map(|(_, ty)| ty)
|
.map(|(_, ty)| ty)
|
||||||
.collect::<Box<_>>(),
|
.collect::<Box<_>>(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -64,7 +64,7 @@ use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::scope::ScopeId;
|
use crate::semantic_index::scope::ScopeId;
|
||||||
use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index};
|
use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index};
|
||||||
use crate::types::call::{Binding, CallArguments};
|
use crate::types::call::{Binding, CallArgumentTypes, CallArguments};
|
||||||
use crate::types::constraints::ConstraintSet;
|
use crate::types::constraints::ConstraintSet;
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
use crate::types::diagnostic::{
|
use crate::types::diagnostic::{
|
||||||
|
@ -1416,6 +1416,7 @@ impl KnownFunction {
|
||||||
context: &InferContext<'db, '_>,
|
context: &InferContext<'db, '_>,
|
||||||
overload: &mut Binding<'db>,
|
overload: &mut Binding<'db>,
|
||||||
call_arguments: &CallArguments<'_, 'db>,
|
call_arguments: &CallArguments<'_, 'db>,
|
||||||
|
call_argument_types: &CallArgumentTypes<'db>,
|
||||||
call_expression: &ast::ExprCall,
|
call_expression: &ast::ExprCall,
|
||||||
file: File,
|
file: File,
|
||||||
) {
|
) {
|
||||||
|
@ -1425,7 +1426,7 @@ impl KnownFunction {
|
||||||
match self {
|
match self {
|
||||||
KnownFunction::RevealType => {
|
KnownFunction::RevealType => {
|
||||||
let revealed_type = overload
|
let revealed_type = overload
|
||||||
.arguments_for_parameter(call_arguments, 0)
|
.arguments_for_parameter(call_arguments, call_argument_types, 0)
|
||||||
.fold(UnionBuilder::new(db), |builder, (_, ty)| builder.add(ty))
|
.fold(UnionBuilder::new(db), |builder, (_, ty)| builder.add(ty))
|
||||||
.build();
|
.build();
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
|
|
|
@ -877,7 +877,7 @@ pub fn call_signature_details<'db>(
|
||||||
CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| {
|
CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| {
|
||||||
splatted_value.inferred_type(model)
|
splatted_value.inferred_type(model)
|
||||||
});
|
});
|
||||||
let bindings = callable_type
|
let (bindings, _) = callable_type
|
||||||
.bindings(db)
|
.bindings(db)
|
||||||
.match_parameters(db, &call_arguments);
|
.match_parameters(db, &call_arguments);
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ use crate::semantic_index::symbol::{ScopedSymbolId, Symbol};
|
||||||
use crate::semantic_index::{
|
use crate::semantic_index::{
|
||||||
ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table,
|
ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table,
|
||||||
};
|
};
|
||||||
|
use crate::types::call::bind::MatchedCallArguments;
|
||||||
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
|
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
|
||||||
use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
|
use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
|
||||||
use crate::types::context::{InNoTypeCheck, InferContext};
|
use crate::types::context::{InNoTypeCheck, InferContext};
|
||||||
|
@ -4917,6 +4918,51 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.infer_expression(expression, TypeContext::default())
|
self.infer_expression(expression, TypeContext::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_matched_argument_types<'a>(
|
||||||
|
&mut self,
|
||||||
|
ast_arguments: &ast::Arguments,
|
||||||
|
argument_types: &mut MatchedCallArguments<'db>,
|
||||||
|
bindings: &Bindings<'db>,
|
||||||
|
) {
|
||||||
|
for (binding, argument_types) in iter::zip(bindings, argument_types.bindings_mut()) {
|
||||||
|
for (overload, argument_types) in
|
||||||
|
iter::zip(binding.overloads(), argument_types.overloads_mut())
|
||||||
|
{
|
||||||
|
debug_assert_eq!(ast_arguments.len(), argument_types.len());
|
||||||
|
debug_assert_eq!(argument_types.len(), bindings.argument_forms().len());
|
||||||
|
|
||||||
|
for (argument_index, argument_type, argument_form, ast_argument) in itertools::izip!(
|
||||||
|
0..,
|
||||||
|
argument_types.iter_mut(),
|
||||||
|
bindings.argument_forms(),
|
||||||
|
ast_arguments.arguments_source_order()
|
||||||
|
) {
|
||||||
|
let ast_argument = match ast_argument {
|
||||||
|
// We already inferred the type of splatted arguments.
|
||||||
|
ast::ArgOrKeyword::Arg(ast::Expr::Starred(_))
|
||||||
|
| ast::ArgOrKeyword::Keyword(ast::Keyword { arg: None, .. }) => continue,
|
||||||
|
ast::ArgOrKeyword::Arg(arg) => arg,
|
||||||
|
ast::ArgOrKeyword::Keyword(ast::Keyword { value, .. }) => value,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (parameter_index, variadic_argument_type) in
|
||||||
|
overload.argument_matches()[argument_index].iter()
|
||||||
|
{
|
||||||
|
if variadic_argument_type.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parameter = &overload.signature.parameters()[parameter_index];
|
||||||
|
let tcx = TypeContext::new(parameter.annotated_type());
|
||||||
|
|
||||||
|
*argument_type =
|
||||||
|
Some(self.infer_argument_type(ast_argument, *argument_form, tcx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn infer_argument_types<'a>(
|
fn infer_argument_types<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ast_arguments: &ast::Arguments,
|
ast_arguments: &ast::Arguments,
|
||||||
|
@ -5082,7 +5128,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let previous = self.expressions.insert(expression.into(), ty);
|
let previous = self.expressions.insert(expression.into(), ty);
|
||||||
assert_eq!(previous, None);
|
assert!(previous == None || previous == Some(ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_number_literal_expression(&mut self, literal: &ast::ExprNumberLiteral) -> Type<'db> {
|
fn infer_number_literal_expression(&mut self, literal: &ast::ExprNumberLiteral) -> Type<'db> {
|
||||||
|
@ -5977,10 +6023,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bindings = callable_type
|
let (bindings, mut call_argument_types) = callable_type
|
||||||
.bindings(self.db())
|
.bindings(self.db())
|
||||||
.match_parameters(self.db(), &call_arguments);
|
.match_parameters(self.db(), &call_arguments);
|
||||||
self.infer_argument_types(arguments, &mut call_arguments, bindings.argument_forms());
|
|
||||||
|
self.infer_matched_argument_types(arguments, &mut call_argument_types, &bindings);
|
||||||
|
|
||||||
// Validate `TypedDict` constructor calls after argument type inference
|
// Validate `TypedDict` constructor calls after argument type inference
|
||||||
if let Some(class_literal) = callable_type.into_class_literal() {
|
if let Some(class_literal) = callable_type.into_class_literal() {
|
||||||
|
@ -5998,7 +6045,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bindings = match bindings.check_types(self.db(), &call_arguments, &tcx) {
|
let mut bindings =
|
||||||
|
match bindings.check_types(self.db(), &call_arguments, &call_argument_types, &tcx) {
|
||||||
Ok(bindings) => bindings,
|
Ok(bindings) => bindings,
|
||||||
Err(CallError(_, bindings)) => {
|
Err(CallError(_, bindings)) => {
|
||||||
bindings.report_diagnostics(&self.context, call_expression.into());
|
bindings.report_diagnostics(&self.context, call_expression.into());
|
||||||
|
@ -6006,9 +6054,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for binding in &mut bindings {
|
for (binding, call_argument_types) in
|
||||||
|
iter::zip(&mut bindings, call_argument_types.bindings())
|
||||||
|
{
|
||||||
let binding_type = binding.callable_type;
|
let binding_type = binding.callable_type;
|
||||||
for (_, overload) in binding.matching_overloads_mut() {
|
for ((_, overload), call_argument_types) in binding
|
||||||
|
.matching_overloads_mut()
|
||||||
|
.zip(call_argument_types.overloads())
|
||||||
|
{
|
||||||
match binding_type {
|
match binding_type {
|
||||||
Type::FunctionLiteral(function_literal) => {
|
Type::FunctionLiteral(function_literal) => {
|
||||||
if let Some(known_function) = function_literal.known(self.db()) {
|
if let Some(known_function) = function_literal.known(self.db()) {
|
||||||
|
@ -6016,6 +6069,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
&self.context,
|
&self.context,
|
||||||
overload,
|
overload,
|
||||||
&call_arguments,
|
&call_arguments,
|
||||||
|
&call_argument_types,
|
||||||
call_expression,
|
call_expression,
|
||||||
self.file(),
|
self.file(),
|
||||||
);
|
);
|
||||||
|
@ -6028,6 +6082,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.index,
|
self.index,
|
||||||
overload,
|
overload,
|
||||||
&call_arguments,
|
&call_arguments,
|
||||||
|
&call_argument_types,
|
||||||
call_expression,
|
call_expression,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8549,7 +8604,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
|
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
let slice_node = subscript.slice.as_ref();
|
let slice_node = subscript.slice.as_ref();
|
||||||
let call_argument_types = match slice_node {
|
let call_arguments = match slice_node {
|
||||||
ast::Expr::Tuple(tuple) => {
|
ast::Expr::Tuple(tuple) => {
|
||||||
let arguments = CallArguments::positional(
|
let arguments = CallArguments::positional(
|
||||||
tuple.elts.iter().map(|elt| self.infer_type_expression(elt)),
|
tuple.elts.iter().map(|elt| self.infer_type_expression(elt)),
|
||||||
|
@ -8563,10 +8618,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
_ => CallArguments::positional([self.infer_type_expression(slice_node)]),
|
_ => CallArguments::positional([self.infer_type_expression(slice_node)]),
|
||||||
};
|
};
|
||||||
let binding = Binding::single(value_ty, generic_context.signature(self.db()));
|
let binding = Binding::single(value_ty, generic_context.signature(self.db()));
|
||||||
let bindings = match Bindings::from(binding)
|
// TODO: Call directly on `Binding`, not `Bindings`, to avoid the indirection.
|
||||||
.match_parameters(self.db(), &call_argument_types)
|
let (bindings, call_argument_types) =
|
||||||
.check_types(self.db(), &call_argument_types, &TypeContext::default())
|
Bindings::from(binding).match_parameters(self.db(), &call_arguments);
|
||||||
{
|
let bindings = match bindings.check_types(
|
||||||
|
self.db(),
|
||||||
|
&call_arguments,
|
||||||
|
&call_argument_types,
|
||||||
|
&TypeContext::default(),
|
||||||
|
) {
|
||||||
Ok(bindings) => bindings,
|
Ok(bindings) => bindings,
|
||||||
Err(CallError(_, bindings)) => {
|
Err(CallError(_, bindings)) => {
|
||||||
bindings.report_diagnostics(&self.context, subscript.into());
|
bindings.report_diagnostics(&self.context, subscript.into());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue