[ty] Fix further issues in super() inference logic (#20843)

This commit is contained in:
Alex Waygood 2025-10-14 13:48:47 +01:00 committed by GitHub
parent 441ba20876
commit 9090aead0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 212 additions and 142 deletions

View file

@ -25,6 +25,8 @@ python-version = "3.12"
``` ```
```py ```py
from __future__ import annotations
class A: class A:
def a(self): ... def a(self): ...
aa: int = 1 aa: int = 1
@ -116,6 +118,26 @@ def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]> # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
reveal_type(super(object, y)) reveal_type(super(object, y))
# The first argument to `super()` must be an actual class object;
# instances of `GenericAlias` are not accepted at runtime:
#
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super(list[int], []))
```
`super(pivot_class, owner)` can be called from inside methods, just like single-argument `super()`:
```py
class Super:
def method(self) -> int:
return 42
class Sub(Super):
def method(self: Sub) -> int:
# revealed: <super: <class 'Sub'>, Sub>
return reveal_type(super(self.__class__, self)).method()
``` ```
### Implicit Super Object ### Implicit Super Object

View file

@ -12,104 +12,121 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
## mdtest_snippet.py ## mdtest_snippet.py
``` ```
1 | class A: 1 | from __future__ import annotations
2 | def a(self): ... 2 |
3 | aa: int = 1 3 | class A:
4 | 4 | def a(self): ...
5 | class B(A): 5 | aa: int = 1
6 | def b(self): ... 6 |
7 | bb: int = 2 7 | class B(A):
8 | 8 | def b(self): ...
9 | class C(B): 9 | bb: int = 2
10 | def c(self): ... 10 |
11 | cc: int = 3 11 | class C(B):
12 | 12 | def c(self): ...
13 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>] 13 | cc: int = 3
14 | 14 |
15 | super(C, C()).a 15 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
16 | super(C, C()).b 16 |
17 | super(C, C()).c # error: [unresolved-attribute] 17 | super(C, C()).a
18 | 18 | super(C, C()).b
19 | super(B, C()).a 19 | super(C, C()).c # error: [unresolved-attribute]
20 | super(B, C()).b # error: [unresolved-attribute] 20 |
21 | super(B, C()).c # error: [unresolved-attribute] 21 | super(B, C()).a
22 | 22 | super(B, C()).b # error: [unresolved-attribute]
23 | super(A, C()).a # error: [unresolved-attribute] 23 | super(B, C()).c # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute] 24 |
25 | super(A, C()).c # error: [unresolved-attribute] 25 | super(A, C()).a # error: [unresolved-attribute]
26 | 26 | super(A, C()).b # error: [unresolved-attribute]
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown 27 | super(A, C()).c # error: [unresolved-attribute]
28 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown 28 |
29 | reveal_type(super(C, C()).aa) # revealed: int 29 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
30 | reveal_type(super(C, C()).bb) # revealed: int 30 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
31 | import types 31 | reveal_type(super(C, C()).aa) # revealed: int
32 | from typing_extensions import Callable, TypeIs, Literal, TypedDict 32 | reveal_type(super(C, C()).bb) # revealed: int
33 | 33 | import types
34 | def f(): ... 34 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
35 | 35 |
36 | class Foo[T]: 36 | def f(): ...
37 | def method(self): ... 37 |
38 | @property 38 | class Foo[T]:
39 | def some_property(self): ... 39 | def method(self): ...
40 | 40 | @property
41 | type Alias = int 41 | def some_property(self): ...
42 | 42 |
43 | class SomeTypedDict(TypedDict): 43 | type Alias = int
44 | x: int 44 |
45 | y: bytes 45 | class SomeTypedDict(TypedDict):
46 | 46 | x: int
47 | # revealed: <super: <class 'object'>, FunctionType> 47 | y: bytes
48 | reveal_type(super(object, f)) 48 |
49 | # revealed: <super: <class 'object'>, WrapperDescriptorType> 49 | # revealed: <super: <class 'object'>, FunctionType>
50 | reveal_type(super(object, types.FunctionType.__get__)) 50 | reveal_type(super(object, f))
51 | # revealed: <super: <class 'object'>, GenericAlias> 51 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
52 | reveal_type(super(object, Foo[int])) 52 | reveal_type(super(object, types.FunctionType.__get__))
53 | # revealed: <super: <class 'object'>, _SpecialForm> 53 | # revealed: <super: <class 'object'>, GenericAlias>
54 | reveal_type(super(object, Literal)) 54 | reveal_type(super(object, Foo[int]))
55 | # revealed: <super: <class 'object'>, TypeAliasType> 55 | # revealed: <super: <class 'object'>, _SpecialForm>
56 | reveal_type(super(object, Alias)) 56 | reveal_type(super(object, Literal))
57 | # revealed: <super: <class 'object'>, MethodType> 57 | # revealed: <super: <class 'object'>, TypeAliasType>
58 | reveal_type(super(object, Foo().method)) 58 | reveal_type(super(object, Alias))
59 | # revealed: <super: <class 'object'>, property> 59 | # revealed: <super: <class 'object'>, MethodType>
60 | reveal_type(super(object, Foo.some_property)) 60 | reveal_type(super(object, Foo().method))
61 | 61 | # revealed: <super: <class 'object'>, property>
62 | def g(x: object) -> TypeIs[list[object]]: 62 | reveal_type(super(object, Foo.some_property))
63 | return isinstance(x, list) 63 |
64 | 64 | def g(x: object) -> TypeIs[list[object]]:
65 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]): 65 | return isinstance(x, list)
66 | if hasattr(x, "bar"): 66 |
67 | # revealed: <Protocol with members 'bar'> 67 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
68 | reveal_type(x) 68 | if hasattr(x, "bar"):
69 | # error: [invalid-super-argument] 69 | # revealed: <Protocol with members 'bar'>
70 | # revealed: Unknown 70 | reveal_type(x)
71 | reveal_type(super(object, x)) 71 | # error: [invalid-super-argument]
72 | 72 | # revealed: Unknown
73 | # error: [invalid-super-argument] 73 | reveal_type(super(object, x))
74 | # revealed: Unknown 74 |
75 | reveal_type(super(object, z)) 75 | # error: [invalid-super-argument]
76 | 76 | # revealed: Unknown
77 | is_list = g(x) 77 | reveal_type(super(object, z))
78 | # revealed: TypeIs[list[object] @ x] 78 |
79 | reveal_type(is_list) 79 | is_list = g(x)
80 | # revealed: <super: <class 'object'>, bool> 80 | # revealed: TypeIs[list[object] @ x]
81 | reveal_type(super(object, is_list)) 81 | reveal_type(is_list)
82 | 82 | # revealed: <super: <class 'object'>, bool>
83 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]> 83 | reveal_type(super(object, is_list))
84 | reveal_type(super(object, y)) 84 |
85 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
86 | reveal_type(super(object, y))
87 |
88 | # The first argument to `super()` must be an actual class object;
89 | # instances of `GenericAlias` are not accepted at runtime:
90 | #
91 | # error: [invalid-super-argument]
92 | # revealed: Unknown
93 | reveal_type(super(list[int], []))
94 | class Super:
95 | def method(self) -> int:
96 | return 42
97 |
98 | class Sub(Super):
99 | def method(self: Sub) -> int:
100 | # revealed: <super: <class 'Sub'>, Sub>
101 | return reveal_type(super(self.__class__, self)).method()
``` ```
# Diagnostics # Diagnostics
``` ```
error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c` error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:17:1 --> src/mdtest_snippet.py:19:1
| |
15 | super(C, C()).a 17 | super(C, C()).a
16 | super(C, C()).b 18 | super(C, C()).b
17 | super(C, C()).c # error: [unresolved-attribute] 19 | super(C, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
18 | 20 |
19 | super(B, C()).a 21 | super(B, C()).a
| |
info: rule `unresolved-attribute` is enabled by default info: rule `unresolved-attribute` is enabled by default
@ -117,12 +134,12 @@ info: rule `unresolved-attribute` is enabled by default
``` ```
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b` error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b`
--> src/mdtest_snippet.py:20:1 --> src/mdtest_snippet.py:22:1
| |
19 | super(B, C()).a 21 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute] 22 | super(B, C()).b # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
21 | super(B, C()).c # error: [unresolved-attribute] 23 | super(B, C()).c # error: [unresolved-attribute]
| |
info: rule `unresolved-attribute` is enabled by default info: rule `unresolved-attribute` is enabled by default
@ -130,14 +147,14 @@ info: rule `unresolved-attribute` is enabled by default
``` ```
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c` error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:21:1 --> src/mdtest_snippet.py:23:1
| |
19 | super(B, C()).a 21 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute] 22 | super(B, C()).b # error: [unresolved-attribute]
21 | super(B, C()).c # error: [unresolved-attribute] 23 | super(B, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
22 | 24 |
23 | super(A, C()).a # error: [unresolved-attribute] 25 | super(A, C()).a # error: [unresolved-attribute]
| |
info: rule `unresolved-attribute` is enabled by default info: rule `unresolved-attribute` is enabled by default
@ -145,14 +162,14 @@ info: rule `unresolved-attribute` is enabled by default
``` ```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a` error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a`
--> src/mdtest_snippet.py:23:1 --> src/mdtest_snippet.py:25:1
| |
21 | super(B, C()).c # error: [unresolved-attribute] 23 | super(B, C()).c # error: [unresolved-attribute]
22 | 24 |
23 | super(A, C()).a # error: [unresolved-attribute] 25 | super(A, C()).a # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
24 | super(A, C()).b # error: [unresolved-attribute] 26 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute] 27 | super(A, C()).c # error: [unresolved-attribute]
| |
info: rule `unresolved-attribute` is enabled by default info: rule `unresolved-attribute` is enabled by default
@ -160,12 +177,12 @@ info: rule `unresolved-attribute` is enabled by default
``` ```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b` error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b`
--> src/mdtest_snippet.py:24:1 --> src/mdtest_snippet.py:26:1
| |
23 | super(A, C()).a # error: [unresolved-attribute] 25 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute] 26 | super(A, C()).b # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
25 | super(A, C()).c # error: [unresolved-attribute] 27 | super(A, C()).c # error: [unresolved-attribute]
| |
info: rule `unresolved-attribute` is enabled by default info: rule `unresolved-attribute` is enabled by default
@ -173,14 +190,14 @@ info: rule `unresolved-attribute` is enabled by default
``` ```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c` error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:25:1 --> src/mdtest_snippet.py:27:1
| |
23 | super(A, C()).a # error: [unresolved-attribute] 25 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute] 26 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute] 27 | super(A, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
26 | 28 |
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown 29 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
| |
info: rule `unresolved-attribute` is enabled by default info: rule `unresolved-attribute` is enabled by default
@ -188,14 +205,14 @@ info: rule `unresolved-attribute` is enabled by default
``` ```
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
--> src/mdtest_snippet.py:71:21 --> src/mdtest_snippet.py:73:21
| |
69 | # error: [invalid-super-argument] 71 | # error: [invalid-super-argument]
70 | # revealed: Unknown 72 | # revealed: Unknown
71 | reveal_type(super(object, x)) 73 | reveal_type(super(object, x))
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
72 | 74 |
73 | # error: [invalid-super-argument] 75 | # error: [invalid-super-argument]
| |
info: rule `invalid-super-argument` is enabled by default info: rule `invalid-super-argument` is enabled by default
@ -203,14 +220,29 @@ info: rule `invalid-super-argument` is enabled by default
``` ```
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
--> src/mdtest_snippet.py:75:17 --> src/mdtest_snippet.py:77:17
| |
73 | # error: [invalid-super-argument] 75 | # error: [invalid-super-argument]
74 | # revealed: Unknown 76 | # revealed: Unknown
75 | reveal_type(super(object, z)) 77 | reveal_type(super(object, z))
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
76 | 78 |
77 | is_list = g(x) 79 | is_list = g(x)
|
info: rule `invalid-super-argument` is enabled by default
```
```
error[invalid-super-argument]: `types.GenericAlias` instance `list[int]` is not a valid class
--> src/mdtest_snippet.py:93:13
|
91 | # error: [invalid-super-argument]
92 | # revealed: Unknown
93 | reveal_type(super(list[int], []))
| ^^^^^^^^^^^^^^^^^^^^
94 | class Super:
95 | def method(self) -> int:
| |
info: rule `invalid-super-argument` is enabled by default info: rule `invalid-super-argument` is enabled by default

View file

@ -162,6 +162,7 @@ error[invalid-super-argument]: `S@method7` is not an instance or subclass of `<c
| |
info: Type variable `S` has `object` as its implicit upper bound info: Type variable `S` has `object` as its implicit upper bound
info: `object` is not an instance or subclass of `<class 'Foo'>` info: `object` is not an instance or subclass of `<class 'Foo'>`
help: Consider adding an upper bound to type variable `S`
info: rule `invalid-super-argument` is enabled by default info: rule `invalid-super-argument` is enabled by default
``` ```

View file

@ -7790,6 +7790,12 @@ pub enum TypeVarKind {
TypingSelf, TypingSelf,
} }
impl TypeVarKind {
const fn is_self(self) -> bool {
matches!(self, Self::TypingSelf)
}
}
/// The identity of a type variable. /// The identity of a type variable.
/// ///
/// This represents the core identity of a typevar, independent of its bounds or constraints. Two /// This represents the core identity of a typevar, independent of its bounds or constraints. Two

View file

@ -5,7 +5,7 @@ use ruff_db::diagnostic::Diagnostic;
use ruff_python_ast::AnyNodeRef; use ruff_python_ast::AnyNodeRef;
use crate::{ use crate::{
Db, Db, DisplaySettings,
place::{Place, PlaceAndQualifiers}, place::{Place, PlaceAndQualifiers},
types::{ types::{
ClassBase, ClassType, DynamicType, IntersectionBuilder, KnownClass, MemberLookupPolicy, ClassBase, ClassType, DynamicType, IntersectionBuilder, KnownClass, MemberLookupPolicy,
@ -75,10 +75,16 @@ impl<'db> BoundSuperError<'db> {
} }
BoundSuperError::InvalidPivotClassType { pivot_class } => { BoundSuperError::InvalidPivotClassType { pivot_class } => {
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) { if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
builder.into_diagnostic(format_args!( match pivot_class {
"`{pivot_class}` is not a valid class", Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
pivot_class = pivot_class.display(context.db()), "`types.GenericAlias` instance `{}` is not a valid class",
)); alias.display_with(context.db(), DisplaySettings::default()),
)),
_ => builder.into_diagnostic(format_args!(
"`{pivot_class}` is not a valid class",
pivot_class = pivot_class.display(context.db()),
)),
};
} }
} }
BoundSuperError::FailingConditionCheck { BoundSuperError::FailingConditionCheck {
@ -102,6 +108,14 @@ impl<'db> BoundSuperError<'db> {
bound_or_constraints_union.display(context.db()), bound_or_constraints_union.display(context.db()),
pivot_class = pivot_class.display(context.db()), pivot_class = pivot_class.display(context.db()),
)); ));
if typevar_context.bound_or_constraints(context.db()).is_none()
&& !typevar_context.kind(context.db()).is_self()
{
diagnostic.help(format_args!(
"Consider adding an upper bound to type variable `{}`",
typevar_context.name(context.db())
));
}
} }
} }
} }
@ -412,15 +426,10 @@ impl<'db> BoundSuperType<'db> {
// but are valid as pivot classes, e.g. unsubscripted `typing.Generic` // but are valid as pivot classes, e.g. unsubscripted `typing.Generic`
let pivot_class = match pivot_class_type { let pivot_class = match pivot_class_type {
Type::ClassLiteral(class) => ClassBase::Class(ClassType::NonGeneric(class)), Type::ClassLiteral(class) => ClassBase::Class(ClassType::NonGeneric(class)),
Type::GenericAlias(class) => ClassBase::Class(ClassType::Generic(class)), Type::SubclassOf(subclass_of) => match subclass_of.subclass_of() {
Type::SubclassOf(subclass_of) if subclass_of.subclass_of().is_dynamic() => { SubclassOfInner::Class(class) => ClassBase::Class(class),
ClassBase::Dynamic( SubclassOfInner::Dynamic(dynamic) => ClassBase::Dynamic(dynamic),
subclass_of },
.subclass_of()
.into_dynamic()
.expect("Checked in branch arm"),
)
}
Type::SpecialForm(SpecialFormType::Protocol) => ClassBase::Protocol, Type::SpecialForm(SpecialFormType::Protocol) => ClassBase::Protocol,
Type::SpecialForm(SpecialFormType::Generic) => ClassBase::Generic, Type::SpecialForm(SpecialFormType::Generic) => ClassBase::Generic,
Type::SpecialForm(SpecialFormType::TypedDict) => ClassBase::TypedDict, Type::SpecialForm(SpecialFormType::TypedDict) => ClassBase::TypedDict,