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:
Douglas Creager
2025-12-15 11:06:49 -05:00
51 changed files with 2057 additions and 301 deletions

View File

@@ -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],
): ...
```

View File

@@ -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

View File

@@ -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

View File

@@ -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)
```

View File

@@ -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 -->

View File

@@ -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
```

View File

@@ -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

View File

@@ -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>]>>,

View File

@@ -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>]> {

View File

@@ -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,

View File

@@ -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>) {

View File

@@ -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 \

View File

@@ -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,