Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: Fluent formatting of method chains (#21369) [ty] Avoid stack overflow when calculating inferable typevars (#21971) [ty] Add "qualify ..." code fix for undefined references (#21968) [ty] Use jemalloc on linux (#21975) Update MSRV to 1.90 (#21987) [ty] Improve check enforcing that an overloaded function must have an implementation (#21978) Update actions/checkout digest to 8e8c483 (#21982) [ty] Use `ParamSpec` without the attr for inferable check (#21934) [ty] Emit diagnostic when a type variable with a default is followed by one without a default (#21787)
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
# Invalid Order of Legacy Type Parameters
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic, Protocol
|
||||
|
||||
T1 = TypeVar("T1", default=int)
|
||||
|
||||
T2 = TypeVar("T2")
|
||||
T3 = TypeVar("T3")
|
||||
|
||||
DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
|
||||
class SubclassMe(Generic[T1, DefaultStrT]):
|
||||
x: DefaultStrT
|
||||
|
||||
class Baz(SubclassMe[int, DefaultStrT]):
|
||||
pass
|
||||
|
||||
# error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
class Foo(Generic[T1, T2]):
|
||||
pass
|
||||
|
||||
class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
class VeryBad(
|
||||
Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
Generic[T1, T2, DefaultStrT, T3],
|
||||
): ...
|
||||
```
|
||||
@@ -424,9 +424,8 @@ p3 = ParamSpecWithDefault4[[int], [str]]()
|
||||
reveal_type(p3.attr1) # revealed: (int, /) -> None
|
||||
reveal_type(p3.attr2) # revealed: (str, /) -> None
|
||||
|
||||
# TODO: error
|
||||
# Un-ordered type variables as the default of `PAnother` is `P`
|
||||
class ParamSpecWithDefault5(Generic[PAnother, P]):
|
||||
class ParamSpecWithDefault5(Generic[PAnother, P]): # error: [invalid-generic-class]
|
||||
attr: Callable[PAnother, None]
|
||||
|
||||
# TODO: error
|
||||
|
||||
@@ -800,6 +800,29 @@ def func(x: D): ...
|
||||
func(G()) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Self-referential protocol with different specialization
|
||||
|
||||
This is a minimal reproduction for [ty#1874](https://github.com/astral-sh/ty/issues/1874).
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
from typing import Protocol
|
||||
from ty_extensions import generic_context
|
||||
|
||||
class A[S, R](Protocol):
|
||||
def get(self, s: S) -> R: ...
|
||||
def set(self, s: S, r: R) -> S: ...
|
||||
def merge[R2](self, other: A[S, R2]) -> A[S, tuple[R, R2]]: ...
|
||||
|
||||
class Impl[S, R](A[S, R]):
|
||||
def foo(self, s: S) -> S:
|
||||
return self.set(s, self.get(s))
|
||||
|
||||
reveal_type(generic_context(A.get)) # revealed: ty_extensions.GenericContext[Self@get]
|
||||
reveal_type(generic_context(A.merge)) # revealed: ty_extensions.GenericContext[Self@merge, R2@merge]
|
||||
reveal_type(generic_context(Impl.foo)) # revealed: ty_extensions.GenericContext[Self@foo]
|
||||
```
|
||||
|
||||
## Tuple as a PEP-695 generic class
|
||||
|
||||
Our special handling for `tuple` does not break if `tuple` is defined as a PEP-695 generic class in
|
||||
|
||||
@@ -687,3 +687,59 @@ reveal_type(with_parameters(int_int, 1)) # revealed: Overload[(x: int) -> str,
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(with_parameters(int_int, "a")) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
||||
```
|
||||
|
||||
## ParamSpec attribute assignability
|
||||
|
||||
When comparing signatures with `ParamSpec` attributes (`P.args` and `P.kwargs`), two different
|
||||
inferable `ParamSpec` attributes with the same kind are assignable to each other. This enables
|
||||
method overrides where both methods have their own `ParamSpec`.
|
||||
|
||||
### Same attribute kind, both inferable
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class Parent:
|
||||
def method[**P](self, callback: Callable[P, None]) -> Callable[P, None]:
|
||||
return callback
|
||||
|
||||
class Child1(Parent):
|
||||
# This is a valid override: Q.args matches P.args, Q.kwargs matches P.kwargs
|
||||
def method[**Q](self, callback: Callable[Q, None]) -> Callable[Q, None]:
|
||||
return callback
|
||||
|
||||
# Both signatures use ParamSpec, so they should be compatible
|
||||
def outer[**P](f: Callable[P, int]) -> Callable[P, int]:
|
||||
def inner[**Q](g: Callable[Q, int]) -> Callable[Q, int]:
|
||||
return g
|
||||
return inner(f)
|
||||
```
|
||||
|
||||
We can explicitly mark it as an override using the `@override` decorator.
|
||||
|
||||
```py
|
||||
from typing import override
|
||||
|
||||
class Child2(Parent):
|
||||
@override
|
||||
def method[**Q](self, callback: Callable[Q, None]) -> Callable[Q, None]:
|
||||
return callback
|
||||
```
|
||||
|
||||
### One `ParamSpec` not inferable
|
||||
|
||||
Here, `P` is in a non-inferable position while `Q` is inferable. So, they are not considered
|
||||
assignable.
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class Container[**P]:
|
||||
def method(self, f: Callable[P, None]) -> Callable[P, None]:
|
||||
return f
|
||||
|
||||
def try_assign[**Q](self, f: Callable[Q, None]) -> Callable[Q, None]:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `(**Q@try_assign) -> None`, found `(**P@Container) -> None`"
|
||||
# error: [invalid-argument-type] "Argument to bound method `method` is incorrect: Expected `(**P@Container) -> None`, found `(**Q@try_assign) -> None`"
|
||||
return self.method(f)
|
||||
```
|
||||
|
||||
@@ -418,6 +418,18 @@ Using the `@abstractmethod` decorator requires that the class's metaclass is `AB
|
||||
from it.
|
||||
|
||||
```py
|
||||
from abc import ABCMeta
|
||||
|
||||
class CustomAbstractMetaclass(ABCMeta): ...
|
||||
|
||||
class Fine(metaclass=CustomAbstractMetaclass):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: str) -> str: ...
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
@abstractmethod
|
||||
@@ -448,6 +460,52 @@ class PartialFoo(ABC):
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### `TYPE_CHECKING` blocks
|
||||
|
||||
As in other areas of ty, we treat `TYPE_CHECKING` blocks the same as "inline stub files", so we
|
||||
permit overloaded functions to exist without an implementation if all overloads are defined inside
|
||||
an `if TYPE_CHECKING` block:
|
||||
|
||||
```py
|
||||
from typing import overload, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def a() -> str: ...
|
||||
@overload
|
||||
def a(x: int) -> int: ...
|
||||
|
||||
class F:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
|
||||
class G:
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def b() -> str: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def b(x: int) -> int: ...
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@overload
|
||||
def c() -> None: ...
|
||||
|
||||
# not all overloads are in a `TYPE_CHECKING` block, so this is an error
|
||||
@overload
|
||||
# error: [invalid-overload]
|
||||
def c(x: int) -> int: ...
|
||||
```
|
||||
|
||||
### `@overload`-decorated functions with non-stub bodies
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_type_parameter_order.md - Invalid Order of Legacy Type Parameters
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_type_parameter_order.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
6 | T3 = TypeVar("T3")
|
||||
7 |
|
||||
8 | DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
9 |
|
||||
10 | class SubclassMe(Generic[T1, DefaultStrT]):
|
||||
11 | x: DefaultStrT
|
||||
12 |
|
||||
13 | class Baz(SubclassMe[int, DefaultStrT]):
|
||||
14 | pass
|
||||
15 |
|
||||
16 | # error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
17 | class Foo(Generic[T1, T2]):
|
||||
18 | pass
|
||||
19 |
|
||||
20 | class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
21 | pass
|
||||
22 |
|
||||
23 | class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
24 | pass
|
||||
25 |
|
||||
26 | class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
27 | pass
|
||||
28 |
|
||||
29 | class VeryBad(
|
||||
30 | Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
31 | Generic[T1, T2, DefaultStrT, T3],
|
||||
32 | ): ...
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:17:19
|
||||
|
|
||||
16 | # error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
17 | class Foo(Generic[T1, T2]):
|
||||
| ^^^^^^
|
||||
| |
|
||||
| Type variable `T2` does not have a default
|
||||
| Earlier TypeVar `T1` does
|
||||
18 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:20:19
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^
|
||||
| |
|
||||
| Type variable `T3` does not have a default
|
||||
| Earlier TypeVar `T1` does
|
||||
21 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
6 | T3 = TypeVar("T3")
|
||||
| ------------------ `T3` defined here
|
||||
7 |
|
||||
8 | DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:23:20
|
||||
|
|
||||
21 | pass
|
||||
22 |
|
||||
23 | class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| Type variables `T2` and `T3` do not have defaults
|
||||
| Earlier TypeVar `T1` does
|
||||
24 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:26:20
|
||||
|
|
||||
24 | pass
|
||||
25 |
|
||||
26 | class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| Type variables `T2` and `T3` do not have defaults
|
||||
| Earlier TypeVar `T1` does
|
||||
27 | pass
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-generic-class]: Type parameters without defaults cannot follow type parameters with defaults
|
||||
--> src/mdtest_snippet.py:30:14
|
||||
|
|
||||
29 | class VeryBad(
|
||||
30 | Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| Type variables `T2` and `T3` do not have defaults
|
||||
| Earlier TypeVar `T1` does
|
||||
31 | Generic[T1, T2, DefaultStrT, T3],
|
||||
32 | ): ...
|
||||
|
|
||||
::: src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic, Protocol
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
| ------------------------------- `T1` defined here
|
||||
4 |
|
||||
5 | T2 = TypeVar("T2")
|
||||
| ------------------ `T2` defined here
|
||||
6 | T3 = TypeVar("T3")
|
||||
|
|
||||
info: rule `invalid-generic-class` is enabled by default
|
||||
|
||||
```
|
||||
@@ -42,7 +42,11 @@ error[invalid-overload]: Overloads for function `func` must be followed by a non
|
||||
9 | class Foo:
|
||||
|
|
||||
info: Attempting to call `func` will raise `TypeError` at runtime
|
||||
info: Overloaded functions without implementations are only permitted in stub files, on protocols, or for abstract methods
|
||||
info: Overloaded functions without implementations are only permitted:
|
||||
info: - in stub files
|
||||
info: - in `if TYPE_CHECKING` blocks
|
||||
info: - as methods on protocol classes
|
||||
info: - or as `@abstractmethod`-decorated methods on abstract classes
|
||||
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
|
||||
info: rule `invalid-overload` is enabled by default
|
||||
|
||||
@@ -58,7 +62,11 @@ error[invalid-overload]: Overloads for function `method` must be followed by a n
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Attempting to call `method` will raise `TypeError` at runtime
|
||||
info: Overloaded functions without implementations are only permitted in stub files, on protocols, or for abstract methods
|
||||
info: Overloaded functions without implementations are only permitted:
|
||||
info: - in stub files
|
||||
info: - in `if TYPE_CHECKING` blocks
|
||||
info: - as methods on protocol classes
|
||||
info: - or as `@abstractmethod`-decorated methods on abstract classes
|
||||
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
|
||||
info: rule `invalid-overload` is enabled by default
|
||||
|
||||
|
||||
@@ -10869,7 +10869,6 @@ pub struct UnionTypeInstance<'db> {
|
||||
/// `<class 'str'>`. For `Union[int, str]`, this field is `None`, as we infer
|
||||
/// the elements as type expressions. Use `value_expression_types` to get the
|
||||
/// corresponding value expression types.
|
||||
#[expect(clippy::ref_option)]
|
||||
#[returns(ref)]
|
||||
_value_expr_types: Option<Box<[Type<'db>]>>,
|
||||
|
||||
|
||||
@@ -1830,13 +1830,6 @@ impl<'db> ClassLiteral<'db> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Determine if this is an abstract class.
|
||||
pub(super) fn is_abstract(self, db: &'db dyn Db) -> bool {
|
||||
self.metaclass(db)
|
||||
.as_class_literal()
|
||||
.is_some_and(|metaclass| metaclass.is_known(db, KnownClass::ABCMeta))
|
||||
}
|
||||
|
||||
/// Return the types of the decorators on this class
|
||||
#[salsa::tracked(returns(deref), cycle_initial=decorators_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn decorators(self, db: &'db dyn Db) -> Box<[Type<'db>]> {
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::types::{
|
||||
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
|
||||
protocol_class::ProtocolClass,
|
||||
};
|
||||
use crate::types::{DataclassFlags, KnownInstanceType, MemberLookupPolicy};
|
||||
use crate::types::{DataclassFlags, KnownInstanceType, MemberLookupPolicy, TypeVarInstance};
|
||||
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
|
||||
use itertools::Itertools;
|
||||
use ruff_db::{
|
||||
@@ -894,15 +894,20 @@ declare_lint! {
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// There are several requirements that you must follow when defining a generic class.
|
||||
/// Many of these result in `TypeError` being raised at runtime if they are violated.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// from typing import Generic, TypeVar
|
||||
/// from typing_extensions import Generic, TypeVar
|
||||
///
|
||||
/// T = TypeVar("T") # okay
|
||||
/// T = TypeVar("T")
|
||||
/// U = TypeVar("U", default=int)
|
||||
///
|
||||
/// # error: class uses both PEP-695 syntax and legacy syntax
|
||||
/// class C[U](Generic[T]): ...
|
||||
///
|
||||
/// # error: type parameter with default comes before type parameter without default
|
||||
/// class D(Generic[U, T]): ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
@@ -3695,6 +3700,90 @@ pub(crate) fn report_cannot_pop_required_field_on_typed_dict<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_invalid_type_param_order<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
node: &ast::StmtClassDef,
|
||||
typevar_with_default: TypeVarInstance<'db>,
|
||||
invalid_later_typevars: &[TypeVarInstance<'db>],
|
||||
) {
|
||||
let db = context.db();
|
||||
|
||||
let base_index = class
|
||||
.explicit_bases(db)
|
||||
.iter()
|
||||
.position(|base| {
|
||||
matches!(
|
||||
base,
|
||||
Type::KnownInstance(
|
||||
KnownInstanceType::SubscriptedProtocol(_)
|
||||
| KnownInstanceType::SubscriptedGeneric(_)
|
||||
)
|
||||
)
|
||||
})
|
||||
.expect(
|
||||
"It should not be possible for a class to have a legacy generic context \
|
||||
if it does not inherit from `Protocol[]` or `Generic[]`",
|
||||
);
|
||||
|
||||
let base_node = &node.bases()[base_index];
|
||||
|
||||
let primary_diagnostic_range = base_node
|
||||
.as_subscript_expr()
|
||||
.map(|subscript| &*subscript.slice)
|
||||
.unwrap_or(base_node)
|
||||
.range();
|
||||
|
||||
let Some(builder) = context.report_lint(&INVALID_GENERIC_CLASS, primary_diagnostic_range)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = builder.into_diagnostic(
|
||||
"Type parameters without defaults cannot follow type parameters with defaults",
|
||||
);
|
||||
|
||||
diagnostic.set_concise_message(format_args!(
|
||||
"Type parameter `{}` without a default cannot follow earlier parameter `{}` with a default",
|
||||
invalid_later_typevars[0].name(db),
|
||||
typevar_with_default.name(db),
|
||||
));
|
||||
|
||||
if let [single_typevar] = invalid_later_typevars {
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Type variable `{}` does not have a default",
|
||||
single_typevar.name(db),
|
||||
));
|
||||
} else {
|
||||
let later_typevars =
|
||||
format_enumeration(invalid_later_typevars.iter().map(|tv| tv.name(db)));
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Type variables {later_typevars} do not have defaults",
|
||||
));
|
||||
}
|
||||
|
||||
diagnostic.annotate(
|
||||
Annotation::primary(Span::from(context.file()).with_range(primary_diagnostic_range))
|
||||
.message(format_args!(
|
||||
"Earlier TypeVar `{}` does",
|
||||
typevar_with_default.name(db)
|
||||
)),
|
||||
);
|
||||
|
||||
for tvar in [typevar_with_default, invalid_later_typevars[0]] {
|
||||
let Some(definition) = tvar.definition(db) else {
|
||||
continue;
|
||||
};
|
||||
let file = definition.file(db);
|
||||
diagnostic.annotate(
|
||||
Annotation::secondary(Span::from(
|
||||
definition.full_range(db, &parsed_module(db, file).load(db)),
|
||||
))
|
||||
.message(format_args!("`{}` defined here", tvar.name(db))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_rebound_typevar<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
typevar_name: &ast::name::Name,
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::types::{
|
||||
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
|
||||
OnlyReorder, Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
||||
walk_bound_type_var_type,
|
||||
walk_type_var_bounds,
|
||||
};
|
||||
use crate::{Db, FxOrderMap, FxOrderSet};
|
||||
|
||||
@@ -290,6 +290,18 @@ impl<'db> GenericContext<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the typevars that are inferable in this generic context. This set might include
|
||||
/// more typevars than the ones directly bound by the generic context. For instance, consider a
|
||||
/// method of a generic class:
|
||||
///
|
||||
/// ```py
|
||||
/// class C[A]:
|
||||
/// def method[T](self, t: T):
|
||||
/// ```
|
||||
///
|
||||
/// In this example, `method`'s generic context binds `Self` and `T`, but its inferable set
|
||||
/// also includes `A@C`. This is needed because at each call site, we need to infer the
|
||||
/// specialized class instance type whose method is being invoked.
|
||||
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||
#[derive(Default)]
|
||||
struct CollectTypeVars<'db> {
|
||||
@@ -299,7 +311,7 @@ impl<'db> GenericContext<'db> {
|
||||
|
||||
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
|
||||
fn should_visit_lazy_type_attributes(&self) -> bool {
|
||||
true
|
||||
false
|
||||
}
|
||||
|
||||
fn visit_bound_type_var_type(
|
||||
@@ -310,7 +322,10 @@ impl<'db> GenericContext<'db> {
|
||||
self.typevars
|
||||
.borrow_mut()
|
||||
.insert(bound_typevar.identity(db));
|
||||
walk_bound_type_var_type(db, bound_typevar, self);
|
||||
let typevar = bound_typevar.typevar(db);
|
||||
if let Some(bound_or_constraints) = typevar.bound_or_constraints(db) {
|
||||
walk_type_var_bounds(db, bound_or_constraints, self);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
|
||||
@@ -78,7 +78,7 @@ use crate::types::diagnostic::{
|
||||
report_invalid_exception_tuple_caught, report_invalid_generator_function_return_type,
|
||||
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
||||
report_invalid_return_type, report_invalid_type_checking_constant,
|
||||
report_named_tuple_field_with_leading_underscore,
|
||||
report_invalid_type_param_order, report_named_tuple_field_with_leading_underscore,
|
||||
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
||||
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
||||
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
|
||||
@@ -949,23 +949,62 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
let scope = class.body_scope(self.db()).scope(self.db());
|
||||
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS)
|
||||
&& let Some(parent) = scope.parent()
|
||||
{
|
||||
for self_typevar in class.typevars_referenced_in_definition(self.db()) {
|
||||
let self_typevar_name = self_typevar.typevar(self.db()).name(self.db());
|
||||
for enclosing in enclosing_generic_contexts(self.db(), self.index, parent) {
|
||||
if let Some(other_typevar) =
|
||||
enclosing.binds_named_typevar(self.db(), self_typevar_name)
|
||||
{
|
||||
report_rebound_typevar(
|
||||
&self.context,
|
||||
self_typevar_name,
|
||||
class,
|
||||
class_node,
|
||||
other_typevar,
|
||||
);
|
||||
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS) {
|
||||
if !class.has_pep_695_type_params(self.db())
|
||||
&& let Some(generic_context) = class.legacy_generic_context(self.db())
|
||||
{
|
||||
struct State<'db> {
|
||||
typevar_with_default: TypeVarInstance<'db>,
|
||||
invalid_later_tvars: Vec<TypeVarInstance<'db>>,
|
||||
}
|
||||
|
||||
let mut state: Option<State<'db>> = None;
|
||||
|
||||
for bound_typevar in generic_context.variables(self.db()) {
|
||||
let typevar = bound_typevar.typevar(self.db());
|
||||
let has_default = typevar.default_type(self.db()).is_some();
|
||||
|
||||
if let Some(state) = state.as_mut() {
|
||||
if !has_default {
|
||||
state.invalid_later_tvars.push(typevar);
|
||||
}
|
||||
} else if has_default {
|
||||
state = Some(State {
|
||||
typevar_with_default: typevar,
|
||||
invalid_later_tvars: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(state) = state
|
||||
&& !state.invalid_later_tvars.is_empty()
|
||||
{
|
||||
report_invalid_type_param_order(
|
||||
&self.context,
|
||||
class,
|
||||
class_node,
|
||||
state.typevar_with_default,
|
||||
&state.invalid_later_tvars,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let scope = class.body_scope(self.db()).scope(self.db());
|
||||
if let Some(parent) = scope.parent() {
|
||||
for self_typevar in class.typevars_referenced_in_definition(self.db()) {
|
||||
let self_typevar_name = self_typevar.typevar(self.db()).name(self.db());
|
||||
for enclosing in enclosing_generic_contexts(self.db(), self.index, parent) {
|
||||
if let Some(other_typevar) =
|
||||
enclosing.binds_named_typevar(self.db(), self_typevar_name)
|
||||
{
|
||||
report_rebound_typevar(
|
||||
&self.context,
|
||||
self_typevar_name,
|
||||
class,
|
||||
class_node,
|
||||
other_typevar,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1104,7 +1143,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if implementation.is_none() && !self.in_stub() {
|
||||
let mut implementation_required = true;
|
||||
|
||||
if let NodeWithScopeKind::Class(class_node_ref) = scope {
|
||||
if function
|
||||
.iter_overloads_and_implementation(self.db())
|
||||
.all(|f| {
|
||||
f.body_scope(self.db())
|
||||
.scope(self.db())
|
||||
.in_type_checking_block()
|
||||
})
|
||||
{
|
||||
implementation_required = false;
|
||||
} else if let NodeWithScopeKind::Class(class_node_ref) = scope {
|
||||
let class = binding_type(
|
||||
self.db(),
|
||||
self.index
|
||||
@@ -1113,7 +1161,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.expect_class_literal();
|
||||
|
||||
if class.is_protocol(self.db())
|
||||
|| (class.is_abstract(self.db())
|
||||
|| (Type::ClassLiteral(class)
|
||||
.is_subtype_of(self.db(), KnownClass::ABCMeta.to_instance(self.db()))
|
||||
&& overloads.iter().all(|overload| {
|
||||
overload.has_known_decorator(
|
||||
self.db(),
|
||||
@@ -1140,8 +1189,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
&function_node.name
|
||||
));
|
||||
diagnostic.info(
|
||||
"Overloaded functions without implementations are only permitted \
|
||||
in stub files, on protocols, or for abstract methods",
|
||||
"Overloaded functions without implementations are only permitted:",
|
||||
);
|
||||
diagnostic.info(" - in stub files");
|
||||
diagnostic.info(" - in `if TYPE_CHECKING` blocks");
|
||||
diagnostic.info(" - as methods on protocol classes");
|
||||
diagnostic.info(
|
||||
" - or as `@abstractmethod`-decorated methods on abstract classes",
|
||||
);
|
||||
diagnostic.info(
|
||||
"See https://docs.python.org/3/library/typing.html#typing.overload \
|
||||
|
||||
@@ -1209,6 +1209,29 @@ impl<'db> Signature<'db> {
|
||||
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
||||
let type1 = type1.unwrap_or(Type::unknown());
|
||||
let type2 = type2.unwrap_or(Type::unknown());
|
||||
|
||||
match (type1, type2) {
|
||||
// This is a special case where the _same_ components of two different `ParamSpec`
|
||||
// type variables are assignable to each other when they're both in an inferable
|
||||
// position.
|
||||
//
|
||||
// `ParamSpec` type variables can only occur in parameter lists so this special case
|
||||
// is present here instead of in `Type::has_relation_to_impl`.
|
||||
(Type::TypeVar(typevar1), Type::TypeVar(typevar2))
|
||||
if typevar1.paramspec_attr(db).is_some()
|
||||
&& typevar1.paramspec_attr(db) == typevar2.paramspec_attr(db)
|
||||
&& typevar1
|
||||
.without_paramspec_attr(db)
|
||||
.is_inferable(db, inferable)
|
||||
&& typevar2
|
||||
.without_paramspec_attr(db)
|
||||
.is_inferable(db, inferable) =>
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
!result
|
||||
.intersect(
|
||||
db,
|
||||
|
||||
Reference in New Issue
Block a user