[ty] Track when type variables are inferable or not (#19786)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

`Type::TypeVar` now distinguishes whether the typevar in question is
inferable or not.

A typevar is _not inferable_ inside the body of the generic class or
function that binds it:

```py
def f[T](t: T) -> T:
    return t
```

The infered type of `t` in the function body is `TypeVar(T,
NotInferable)`. This represents how e.g. assignability checks need to be
valid for all possible specializations of the typevar. Most of the
existing assignability/etc logic only applies to non-inferable typevars.

Outside of the function body, the typevar is _inferable_:

```py
f(4)
```

Here, the parameter type of `f` is `TypeVar(T, Inferable)`. This
represents how e.g. assignability doesn't need to hold for _all_
specializations; instead, we need to find the constraints under which
this specific assignability check holds.

This is in support of starting to perform specialization inference _as
part of_ performing the assignability check at the call site.

In the [[POPL2015][]] paper, this concept is called _monomorphic_ /
_polymorphic_, but I thought _non-inferable_ / _inferable_ would be
clearer for us.

Depends on #19784 

[POPL2015]: https://doi.org/10.1145/2676726.2676991

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Douglas Creager 2025-08-16 18:25:03 -04:00 committed by GitHub
parent 9ac39cee98
commit b892e4548e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 383 additions and 158 deletions

View file

@ -1247,7 +1247,7 @@ quux.<CURSOR>
__init_subclass__ :: bound method Quux.__init_subclass__() -> None __init_subclass__ :: bound method Quux.__init_subclass__() -> None
__module__ :: str __module__ :: str
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool __ne__ :: bound method Quux.__ne__(value: object, /) -> bool
__new__ :: bound method Quux.__new__() -> Self@__new__ __new__ :: bound method Quux.__new__() -> Quux
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...] __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: bound method Quux.__repr__() -> str __repr__ :: bound method Quux.__repr__() -> str
@ -1292,7 +1292,7 @@ quux.b<CURSOR>
__init_subclass__ :: bound method Quux.__init_subclass__() -> None __init_subclass__ :: bound method Quux.__init_subclass__() -> None
__module__ :: str __module__ :: str
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool __ne__ :: bound method Quux.__ne__(value: object, /) -> bool
__new__ :: bound method Quux.__new__() -> Self@__new__ __new__ :: bound method Quux.__new__() -> Quux
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...] __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: bound method Quux.__repr__() -> str __repr__ :: bound method Quux.__repr__() -> str

View file

@ -337,7 +337,9 @@ impl<'db> SemanticTokenVisitor<'db> {
match ty { match ty {
Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers), Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers),
Type::TypeVar(_) => (SemanticTokenType::TypeParameter, modifiers), Type::NonInferableTypeVar(_) | Type::TypeVar(_) => {
(SemanticTokenType::TypeParameter, modifiers)
}
Type::FunctionLiteral(_) => { Type::FunctionLiteral(_) => {
// Check if this is a method based on current scope // Check if this is a method based on current scope
if self.in_class_scope { if self.in_class_scope {

View file

@ -1,16 +1,16 @@
# Self # Self
```toml
[environment]
python-version = "3.11"
```
`Self` is treated as if it were a `TypeVar` bound to the class it's being used on. `Self` is treated as if it were a `TypeVar` bound to the class it's being used on.
`typing.Self` is only available in Python 3.11 and later. `typing.Self` is only available in Python 3.11 and later.
## Methods ## Methods
```toml
[environment]
python-version = "3.11"
```
```py ```py
from typing import Self from typing import Self
@ -74,11 +74,6 @@ reveal_type(C().method()) # revealed: C
## Class Methods ## Class Methods
```toml
[environment]
python-version = "3.11"
```
```py ```py
from typing import Self, TypeVar from typing import Self, TypeVar
@ -101,11 +96,6 @@ reveal_type(Shape.bar()) # revealed: Unknown
## Attributes ## Attributes
```toml
[environment]
python-version = "3.11"
```
TODO: The use of `Self` to annotate the `next_node` attribute should be TODO: The use of `Self` to annotate the `next_node` attribute should be
[modeled as a property][self attribute], using `Self` in its parameter and return type. [modeled as a property][self attribute], using `Self` in its parameter and return type.
@ -127,11 +117,6 @@ reveal_type(LinkedList().next()) # revealed: LinkedList
## Generic Classes ## Generic Classes
```toml
[environment]
python-version = "3.11"
```
```py ```py
from typing import Self, Generic, TypeVar from typing import Self, Generic, TypeVar
@ -153,11 +138,6 @@ TODO: <https://typing.python.org/en/latest/spec/generics.html#use-in-protocols>
## Annotations ## Annotations
```toml
[environment]
python-version = "3.11"
```
```py ```py
from typing import Self from typing import Self
@ -171,11 +151,6 @@ class Shape:
`Self` cannot be used in the signature of a function or variable. `Self` cannot be used in the signature of a function or variable.
```toml
[environment]
python-version = "3.11"
```
```py ```py
from typing import Self, Generic, TypeVar from typing import Self, Generic, TypeVar
@ -218,4 +193,33 @@ class MyMetaclass(type):
return super().__new__(cls) return super().__new__(cls)
``` ```
## Binding a method fixes `Self`
When a method is bound, any instances of `Self` in its signature are "fixed", since we now know the
specific type of the bound parameter.
```py
from typing import Self
class C:
def instance_method(self, other: Self) -> Self:
return self
@classmethod
def class_method(cls) -> Self:
return cls()
# revealed: bound method C.instance_method(other: C) -> C
reveal_type(C().instance_method)
# revealed: bound method <class 'C'>.class_method() -> C
reveal_type(C.class_method)
class D(C): ...
# revealed: bound method D.instance_method(other: D) -> D
reveal_type(D().instance_method)
# revealed: bound method <class 'D'>.class_method() -> D
reveal_type(D.class_method)
```
[self attribute]: https://typing.python.org/en/latest/spec/generics.html#use-in-attribute-annotations [self attribute]: https://typing.python.org/en/latest/spec/generics.html#use-in-attribute-annotations

View file

@ -491,6 +491,24 @@ class A(Generic[T]):
reveal_type(A(x=1)) # revealed: A[int] reveal_type(A(x=1)) # revealed: A[int]
``` ```
### Class typevar has another typevar as a default
```py
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U", default=T)
class C(Generic[T, U]): ...
reveal_type(C()) # revealed: C[Unknown, Unknown]
class D(Generic[T, U]):
def __init__(self) -> None: ...
reveal_type(D()) # revealed: D[Unknown, Unknown]
```
## Generic subclass ## Generic subclass
When a generic subclass fills its superclass's type parameter with one of its own, the actual types When a generic subclass fills its superclass's type parameter with one of its own, the actual types

View file

@ -438,6 +438,19 @@ class A[T]:
reveal_type(A(x=1)) # revealed: A[int] reveal_type(A(x=1)) # revealed: A[int]
``` ```
### Class typevar has another typevar as a default
```py
class C[T, U = T]: ...
reveal_type(C()) # revealed: C[Unknown, Unknown]
class D[T, U = T]:
def __init__(self) -> None: ...
reveal_type(D()) # revealed: D[Unknown, Unknown]
```
## Generic subclass ## Generic subclass
When a generic subclass fills its superclass's type parameter with one of its own, the actual types When a generic subclass fills its superclass's type parameter with one of its own, the actual types

View file

@ -243,7 +243,7 @@ class Person(NamedTuple):
reveal_type(Person._field_defaults) # revealed: dict[str, Any] reveal_type(Person._field_defaults) # revealed: dict[str, Any]
reveal_type(Person._fields) # revealed: tuple[str, ...] reveal_type(Person._fields) # revealed: tuple[str, ...]
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Self@_make reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Person
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
@ -304,8 +304,8 @@ satisfy:
```py ```py
def expects_named_tuple(x: typing.NamedTuple): def expects_named_tuple(x: typing.NamedTuple):
reveal_type(x) # revealed: tuple[object, ...] & NamedTupleLike reveal_type(x) # revealed: tuple[object, ...] & NamedTupleLike
reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> Self@_make reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> NamedTupleLike
reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> Self@_replace reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> NamedTupleLike
# revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], (value: tuple[_T@__add__, ...], /) -> tuple[object, ...]] # revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], (value: tuple[_T@__add__, ...], /) -> tuple[object, ...]]
reveal_type(x.__add__) reveal_type(x.__add__)
reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object] reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object]
@ -328,7 +328,7 @@ class Point(NamedTuple):
x: int x: int
y: int y: int
reveal_type(Point._make) # revealed: bound method <class 'Point'>._make(iterable: Iterable[Any]) -> Self@_make reveal_type(Point._make) # revealed: bound method <class 'Point'>._make(iterable: Iterable[Any]) -> Point
reveal_type(Point._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Point._asdict) # revealed: def _asdict(self) -> dict[str, Any]
reveal_type(Point._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace reveal_type(Point._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace

View file

@ -232,7 +232,7 @@ impl<'db> Completion<'db> {
| Type::BytesLiteral(_) => CompletionKind::Value, | Type::BytesLiteral(_) => CompletionKind::Value,
Type::EnumLiteral(_) => CompletionKind::Enum, Type::EnumLiteral(_) => CompletionKind::Enum,
Type::ProtocolInstance(_) => CompletionKind::Interface, Type::ProtocolInstance(_) => CompletionKind::Interface,
Type::TypeVar(_) => CompletionKind::TypeParameter, Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter,
Type::Union(union) => union.elements(db).iter().find_map(|&ty| imp(db, ty))?, Type::Union(union) => union.elements(db).iter().find_map(|&ty| imp(db, ty))?,
Type::Intersection(intersection) => { Type::Intersection(intersection) => {
intersection.iter_positive(db).find_map(|ty| imp(db, ty))? intersection.iter_positive(db).find_map(|ty| imp(db, ty))?

View file

@ -44,7 +44,7 @@ use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
use crate::types::enums::{enum_metadata, is_single_member_enum}; use crate::types::enums::{enum_metadata, is_single_member_enum};
use crate::types::function::{ use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
}; };
use crate::types::generics::{ use crate::types::generics::{
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context, GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context,
@ -612,9 +612,15 @@ pub enum Type<'db> {
LiteralString, LiteralString,
/// A bytes literal /// A bytes literal
BytesLiteral(BytesLiteralType<'db>), BytesLiteral(BytesLiteralType<'db>),
/// An instance of a typevar in a generic class or function. When the generic class or function /// An instance of a typevar in a context where we can infer a specialization for it. (This is
/// is specialized, we will replace this typevar with its specialization. /// typically the signature of a generic function, or of a constructor of a generic class.)
/// When the generic class or function binding this typevar is specialized, we will replace the
/// typevar with its specialization.
TypeVar(BoundTypeVarInstance<'db>), TypeVar(BoundTypeVarInstance<'db>),
/// An instance of a typevar where we cannot infer a specialization for it. (This is typically
/// the body of the generic function or class that binds the typevar.) In these positions,
/// properties like assignability must hold for all possible specializations.
NonInferableTypeVar(BoundTypeVarInstance<'db>),
/// A bound super object like `super()` or `super(A, A())` /// A bound super object like `super()` or `super(A, A())`
/// This type doesn't handle an unbound super object like `super(A)`; for that we just use /// This type doesn't handle an unbound super object like `super(A)`; for that we just use
/// a `Type::NominalInstance` of `builtins.super`. /// a `Type::NominalInstance` of `builtins.super`.
@ -823,6 +829,9 @@ impl<'db> Type<'db> {
) )
.build(), .build(),
Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)), Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)),
Type::NonInferableTypeVar(bound_typevar) => {
Type::NonInferableTypeVar(bound_typevar.materialize(db, variance))
}
Type::TypeIs(type_is) => { Type::TypeIs(type_is) => {
type_is.with_type(db, type_is.return_type(db).materialize(db, variance)) type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
} }
@ -1127,6 +1136,9 @@ impl<'db> Type<'db> {
Type::TypeVar(bound_typevar) => visitor.visit(self, || { Type::TypeVar(bound_typevar) => visitor.visit(self, || {
Type::TypeVar(bound_typevar.normalized_impl(db, visitor)) Type::TypeVar(bound_typevar.normalized_impl(db, visitor))
}), }),
Type::NonInferableTypeVar(bound_typevar) => visitor.visit(self, || {
Type::NonInferableTypeVar(bound_typevar.normalized_impl(db, visitor))
}),
Type::KnownInstance(known_instance) => visitor.visit(self, || { Type::KnownInstance(known_instance) => visitor.visit(self, || {
Type::KnownInstance(known_instance.normalized_impl(db, visitor)) Type::KnownInstance(known_instance.normalized_impl(db, visitor))
}), }),
@ -1203,6 +1215,7 @@ impl<'db> Type<'db> {
| Type::Union(_) | Type::Union(_)
| Type::Intersection(_) | Type::Intersection(_)
| Type::Callable(_) | Type::Callable(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::BoundSuper(_) | Type::BoundSuper(_)
| Type::TypeIs(_) | Type::TypeIs(_)
@ -1283,6 +1296,7 @@ impl<'db> Type<'db> {
| Type::KnownInstance(_) | Type::KnownInstance(_)
| Type::PropertyInstance(_) | Type::PropertyInstance(_)
| Type::Intersection(_) | Type::Intersection(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::BoundSuper(_) => None, | Type::BoundSuper(_) => None,
} }
@ -1399,13 +1413,17 @@ impl<'db> Type<'db> {
// However, there is one exception to this general rule: for any given typevar `T`, // However, there is one exception to this general rule: for any given typevar `T`,
// `T` will always be a subtype of any union containing `T`. // `T` will always be a subtype of any union containing `T`.
// A similar rule applies in reverse to intersection types. // A similar rule applies in reverse to intersection types.
(Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true, (Type::NonInferableTypeVar(_), Type::Union(union))
(Type::Intersection(intersection), Type::TypeVar(_)) if union.elements(db).contains(&self) =>
{
true
}
(Type::Intersection(intersection), Type::NonInferableTypeVar(_))
if intersection.positive(db).contains(&target) => if intersection.positive(db).contains(&target) =>
{ {
true true
} }
(Type::Intersection(intersection), Type::TypeVar(_)) (Type::Intersection(intersection), Type::NonInferableTypeVar(_))
if intersection.negative(db).contains(&target) => if intersection.negative(db).contains(&target) =>
{ {
false false
@ -1416,16 +1434,15 @@ impl<'db> Type<'db> {
// //
// Note that this is not handled by the early return at the beginning of this method, // Note that this is not handled by the early return at the beginning of this method,
// since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive. // since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive.
(Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar)) (
if lhs_bound_typevar == rhs_bound_typevar => Type::NonInferableTypeVar(lhs_bound_typevar),
{ Type::NonInferableTypeVar(rhs_bound_typevar),
true ) if lhs_bound_typevar == rhs_bound_typevar => true,
}
// A fully static typevar is a subtype of its upper bound, and to something similar to // A fully static typevar is a subtype of its upper bound, and to something similar to
// the union of its constraints. An unbound, unconstrained, fully static typevar has an // the union of its constraints. An unbound, unconstrained, fully static typevar has an
// implicit upper bound of `object` (which is handled above). // implicit upper bound of `object` (which is handled above).
(Type::TypeVar(bound_typevar), _) (Type::NonInferableTypeVar(bound_typevar), _)
if bound_typevar.typevar(db).bound_or_constraints(db).is_some() => if bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
{ {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
@ -1444,7 +1461,7 @@ impl<'db> Type<'db> {
// If the typevar is constrained, there must be multiple constraints, and the typevar // If the typevar is constrained, there must be multiple constraints, and the typevar
// might be specialized to any one of them. However, the constraints do not have to be // might be specialized to any one of them. However, the constraints do not have to be
// disjoint, which means an lhs type might be a subtype of all of the constraints. // disjoint, which means an lhs type might be a subtype of all of the constraints.
(_, Type::TypeVar(bound_typevar)) (_, Type::NonInferableTypeVar(bound_typevar))
if bound_typevar if bound_typevar
.typevar(db) .typevar(db)
.constraints(db) .constraints(db)
@ -1496,7 +1513,10 @@ impl<'db> Type<'db> {
// (If the typevar is bounded, it might be specialized to a smaller type than the // (If the typevar is bounded, it might be specialized to a smaller type than the
// bound. This is true even if the bound is a final class, since the typevar can still // bound. This is true even if the bound is a final class, since the typevar can still
// be specialized to `Never`.) // be specialized to `Never`.)
(_, Type::TypeVar(_)) => false, (_, Type::NonInferableTypeVar(_)) => false,
// TODO: Infer specializations here
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => false,
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`. // Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively. // If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
@ -1756,7 +1776,7 @@ impl<'db> Type<'db> {
// Other than the special cases enumerated above, `Instance` types and typevars are // Other than the special cases enumerated above, `Instance` types and typevars are
// never subtypes of any other variants // never subtypes of any other variants
(Type::NominalInstance(_) | Type::TypeVar(_), _) => false, (Type::NominalInstance(_) | Type::NonInferableTypeVar(_), _) => false,
} }
} }
@ -1913,14 +1933,14 @@ impl<'db> Type<'db> {
// be specialized to the same type. (This is an important difference between typevars // be specialized to the same type. (This is an important difference between typevars
// and `Any`!) Different typevars might be disjoint, depending on their bounds and // and `Any`!) Different typevars might be disjoint, depending on their bounds and
// constraints, which are handled below. // constraints, which are handled below.
(Type::TypeVar(self_bound_typevar), Type::TypeVar(other_bound_typevar)) (Type::NonInferableTypeVar(self_bound_typevar), Type::NonInferableTypeVar(other_bound_typevar))
if self_bound_typevar == other_bound_typevar => if self_bound_typevar == other_bound_typevar =>
{ {
false false
} }
(tvar @ Type::TypeVar(_), Type::Intersection(intersection)) (tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection))
| (Type::Intersection(intersection), tvar @ Type::TypeVar(_)) | (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_))
if intersection.negative(db).contains(&tvar) => if intersection.negative(db).contains(&tvar) =>
{ {
true true
@ -1930,7 +1950,7 @@ impl<'db> Type<'db> {
// specialized to any type. A bounded typevar is not disjoint from its bound, and is // specialized to any type. A bounded typevar is not disjoint from its bound, and is
// only disjoint from other types if its bound is. A constrained typevar is disjoint // only disjoint from other types if its bound is. A constrained typevar is disjoint
// from a type if all of its constraints are. // from a type if all of its constraints are.
(Type::TypeVar(bound_typevar), other) | (other, Type::TypeVar(bound_typevar)) => { (Type::NonInferableTypeVar(bound_typevar), other) | (other, Type::NonInferableTypeVar(bound_typevar)) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => false, None => false,
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
@ -1943,6 +1963,10 @@ impl<'db> Type<'db> {
} }
} }
// TODO: Infer specializations here
(Type::TypeVar(_), _)
| (_, Type::TypeVar(_)) => false,
(Type::Union(union), other) | (other, Type::Union(union)) => union (Type::Union(union), other) | (other, Type::Union(union)) => union
.elements(db) .elements(db)
.iter() .iter()
@ -2383,7 +2407,7 @@ impl<'db> Type<'db> {
// the bound is a final singleton class, since it can still be specialized to `Never`. // the bound is a final singleton class, since it can still be specialized to `Never`.
// A constrained typevar is a singleton if all of its constraints are singletons. (Note // A constrained typevar is a singleton if all of its constraints are singletons. (Note
// that you cannot specialize a constrained typevar to a subtype of a constraint.) // that you cannot specialize a constrained typevar to a subtype of a constraint.)
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => false, None => false,
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false, Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
@ -2394,6 +2418,8 @@ impl<'db> Type<'db> {
} }
} }
Type::TypeVar(_) => false,
// We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton. // We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton.
Type::SubclassOf(..) => false, Type::SubclassOf(..) => false,
Type::BoundSuper(..) => false, Type::BoundSuper(..) => false,
@ -2513,7 +2539,7 @@ impl<'db> Type<'db> {
// `Never`. A constrained typevar is single-valued if all of its constraints are // `Never`. A constrained typevar is single-valued if all of its constraints are
// single-valued. (Note that you cannot specialize a constrained typevar to a subtype // single-valued. (Note that you cannot specialize a constrained typevar to a subtype
// of a constraint.) // of a constraint.)
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => false, None => false,
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false, Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
@ -2524,6 +2550,8 @@ impl<'db> Type<'db> {
} }
} }
Type::TypeVar(_) => false,
Type::SubclassOf(..) => { Type::SubclassOf(..) => {
// TODO: Same comment as above for `is_singleton` // TODO: Same comment as above for `is_singleton`
false false
@ -2680,6 +2708,7 @@ impl<'db> Type<'db> {
| Type::LiteralString | Type::LiteralString
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::EnumLiteral(_) | Type::EnumLiteral(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::NominalInstance(_) | Type::NominalInstance(_)
| Type::ProtocolInstance(_) | Type::ProtocolInstance(_)
@ -2790,7 +2819,7 @@ impl<'db> Type<'db> {
Type::object(db).instance_member(db, name) Type::object(db).instance_member(db, name)
} }
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => Type::object(db).instance_member(db, name), None => Type::object(db).instance_member(db, name),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
@ -2803,6 +2832,16 @@ impl<'db> Type<'db> {
} }
} }
Type::TypeVar(_) => {
debug_assert!(
false,
"should not be able to access instance member `{name}` \
of type variable {} in inferable position",
self.display(db)
);
Place::Unbound.into()
}
Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name), Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name),
Type::BooleanLiteral(_) | Type::TypeIs(_) => { Type::BooleanLiteral(_) | Type::TypeIs(_) => {
KnownClass::Bool.to_instance(db).instance_member(db, name) KnownClass::Bool.to_instance(db).instance_member(db, name)
@ -3329,6 +3368,7 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::EnumLiteral(..) | Type::EnumLiteral(..)
| Type::LiteralString | Type::LiteralString
| Type::NonInferableTypeVar(..)
| Type::TypeVar(..) | Type::TypeVar(..)
| Type::SpecialForm(..) | Type::SpecialForm(..)
| Type::KnownInstance(..) | Type::KnownInstance(..)
@ -3664,7 +3704,7 @@ impl<'db> Type<'db> {
} }
}, },
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => Truthiness::Ambiguous, None => Truthiness::Ambiguous,
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
@ -3675,6 +3715,7 @@ impl<'db> Type<'db> {
} }
} }
} }
Type::TypeVar(_) => Truthiness::Ambiguous,
Type::NominalInstance(instance) => instance Type::NominalInstance(instance) => instance
.class(db) .class(db)
@ -3767,7 +3808,7 @@ impl<'db> Type<'db> {
.into() .into()
} }
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => CallableBinding::not_callable(self).into(), None => CallableBinding::not_callable(self).into(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db),
@ -3780,6 +3821,15 @@ impl<'db> Type<'db> {
} }
} }
Type::TypeVar(_) => {
debug_assert!(
false,
"should not be able to call type variable {} in inferable position",
self.display(db)
);
CallableBinding::not_callable(self).into()
}
Type::BoundMethod(bound_method) => { Type::BoundMethod(bound_method) => {
let signature = bound_method.function(db).signature(db); let signature = bound_method.function(db).signature(db);
CallableBinding::from_overloads(self, signature.overloads.iter().cloned()) CallableBinding::from_overloads(self, signature.overloads.iter().cloned())
@ -5111,20 +5161,22 @@ impl<'db> Type<'db> {
// have the class's typevars still in the method signature when we attempt to call it. To // have the class's typevars still in the method signature when we attempt to call it. To
// do this, we instead use the _identity_ specialization, which maps each of the class's // do this, we instead use the _identity_ specialization, which maps each of the class's
// generic typevars to itself. // generic typevars to itself.
let (generic_origin, generic_context, self_type) = let (generic_origin, generic_context, self_type) = match self {
match self { Type::ClassLiteral(class) => match class.generic_context(db) {
Type::ClassLiteral(class) => match class.generic_context(db) { Some(generic_context) => (
Some(generic_context) => ( Some(class),
Some(class), Some(generic_context),
Some(generic_context), Type::from(class.apply_specialization(db, |_| {
Type::from(class.apply_specialization(db, |_| { // It is important that identity_specialization specializes the class with
generic_context.identity_specialization(db) // _inferable_ typevars, so that our specialization inference logic will
})), // try to find a specialization for them.
), generic_context.identity_specialization(db)
_ => (None, None, self), })),
}, ),
_ => (None, None, self), _ => (None, None, self),
}; },
_ => (None, None, self),
};
// As of now we do not model custom `__call__` on meta-classes, so the code below // As of now we do not model custom `__call__` on meta-classes, so the code below
// only deals with interplay between `__new__` and `__init__` methods. // only deals with interplay between `__new__` and `__init__` methods.
@ -5281,32 +5333,10 @@ impl<'db> Type<'db> {
// If there is no bound or constraints on a typevar `T`, `T: object` implicitly, which // If there is no bound or constraints on a typevar `T`, `T: object` implicitly, which
// has no instance type. Otherwise, synthesize a typevar with bound or constraints // has no instance type. Otherwise, synthesize a typevar with bound or constraints
// mapped through `to_instance`. // mapped through `to_instance`.
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
let typevar = bound_typevar.typevar(db); Some(Type::NonInferableTypeVar(bound_typevar.to_instance(db)?))
let bound_or_constraints = match typevar.bound_or_constraints(db)? {
TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
TypeVarBoundOrConstraints::UpperBound(upper_bound.to_instance(db)?)
}
TypeVarBoundOrConstraints::Constraints(constraints) => {
TypeVarBoundOrConstraints::Constraints(
constraints.to_instance(db)?.into_union()?,
)
}
};
Some(Type::TypeVar(BoundTypeVarInstance::new(
db,
TypeVarInstance::new(
db,
Name::new(format!("{}'instance", typevar.name(db))),
None,
Some(bound_or_constraints.into()),
typevar.variance(db),
None,
typevar.kind(db),
),
bound_typevar.binding_context(db),
)))
} }
Type::TypeVar(bound_typevar) => Some(Type::TypeVar(bound_typevar.to_instance(db)?)),
Type::TypeAlias(alias) => alias.value_type(db).to_instance(db), Type::TypeAlias(alias) => alias.value_type(db).to_instance(db),
Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")), Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")),
Type::BooleanLiteral(_) Type::BooleanLiteral(_)
@ -5390,6 +5420,7 @@ impl<'db> Type<'db> {
| Type::LiteralString | Type::LiteralString
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::StringLiteral(_) | Type::StringLiteral(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::Callable(_) | Type::Callable(_)
| Type::BoundMethod(_) | Type::BoundMethod(_)
@ -5423,7 +5454,7 @@ impl<'db> Type<'db> {
typevar_binding_context, typevar_binding_context,
*typevar, *typevar,
) )
.map(Type::TypeVar) .map(Type::NonInferableTypeVar)
.unwrap_or(*self)) .unwrap_or(*self))
} }
KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError { KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError {
@ -5506,7 +5537,7 @@ impl<'db> Type<'db> {
Some(TypeVarBoundOrConstraints::UpperBound(instance).into()), Some(TypeVarBoundOrConstraints::UpperBound(instance).into()),
TypeVarVariance::Invariant, TypeVarVariance::Invariant,
None, None,
TypeVarKind::Implicit, TypeVarKind::TypingSelf,
); );
Ok(bind_typevar( Ok(bind_typevar(
db, db,
@ -5516,7 +5547,7 @@ impl<'db> Type<'db> {
typevar_binding_context, typevar_binding_context,
typevar, typevar,
) )
.map(Type::TypeVar) .map(Type::NonInferableTypeVar)
.unwrap_or(*self)) .unwrap_or(*self))
} }
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)), SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
@ -5685,7 +5716,7 @@ impl<'db> Type<'db> {
} }
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db), Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => KnownClass::Type.to_instance(db), None => KnownClass::Type.to_instance(db),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db),
@ -5696,6 +5727,7 @@ impl<'db> Type<'db> {
} }
} }
} }
Type::TypeVar(_) => KnownClass::Type.to_instance(db),
Type::ClassLiteral(class) => class.metaclass(db), Type::ClassLiteral(class) => class.metaclass(db),
Type::GenericAlias(alias) => ClassType::from(*alias).metaclass(db), Type::GenericAlias(alias) => ClassType::from(*alias).metaclass(db),
@ -5792,16 +5824,46 @@ impl<'db> Type<'db> {
TypeMapping::PartialSpecialization(partial) => { TypeMapping::PartialSpecialization(partial) => {
partial.get(db, bound_typevar).unwrap_or(self) partial.get(db, bound_typevar).unwrap_or(self)
} }
TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) => self, TypeMapping::BindSelf(self_type) => {
if bound_typevar.typevar(db).is_self(db) {
*self_type
} else {
self
}
}
TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) |
TypeMapping::MarkTypeVarsInferable(_) => self,
}
Type::NonInferableTypeVar(bound_typevar) => match type_mapping {
TypeMapping::Specialization(specialization) => {
specialization.get(db, bound_typevar).unwrap_or(self)
}
TypeMapping::PartialSpecialization(partial) => {
partial.get(db, bound_typevar).unwrap_or(self)
}
TypeMapping::MarkTypeVarsInferable(binding_context) => {
if bound_typevar.binding_context(db) == *binding_context {
Type::TypeVar(bound_typevar)
} else {
self
}
}
TypeMapping::PromoteLiterals |
TypeMapping::BindLegacyTypevars(_) |
TypeMapping::BindSelf(_)
=> self,
} }
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping { Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) |
TypeMapping::PromoteLiterals => self,
TypeMapping::BindLegacyTypevars(binding_context) => { TypeMapping::BindLegacyTypevars(binding_context) => {
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context)) Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
} }
TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) |
TypeMapping::PromoteLiterals |
TypeMapping::BindSelf(_) |
TypeMapping::MarkTypeVarsInferable(_) => self,
} }
Type::FunctionLiteral(function) => { Type::FunctionLiteral(function) => {
@ -5896,7 +5958,9 @@ impl<'db> Type<'db> {
| Type::EnumLiteral(_) => match type_mapping { | Type::EnumLiteral(_) => match type_mapping {
TypeMapping::Specialization(_) | TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) | TypeMapping::PartialSpecialization(_) |
TypeMapping::BindLegacyTypevars(_) => self, TypeMapping::BindLegacyTypevars(_) |
TypeMapping::BindSelf(_) |
TypeMapping::MarkTypeVarsInferable(_) => self,
TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) TypeMapping::PromoteLiterals => self.literal_fallback_instance(db)
.expect("literal type should have fallback instance type"), .expect("literal type should have fallback instance type"),
} }
@ -5929,10 +5993,10 @@ impl<'db> Type<'db> {
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>, typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) { ) {
match self { match self {
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => {
if matches!( if matches!(
bound_typevar.typevar(db).kind(db), bound_typevar.typevar(db).kind(db),
TypeVarKind::Legacy | TypeVarKind::Implicit TypeVarKind::Legacy | TypeVarKind::TypingSelf
) && binding_context.is_none_or(|binding_context| { ) && binding_context.is_none_or(|binding_context| {
bound_typevar.binding_context(db) == BindingContext::Definition(binding_context) bound_typevar.binding_context(db) == BindingContext::Definition(binding_context)
}) { }) {
@ -6145,6 +6209,7 @@ impl<'db> Type<'db> {
| Self::PropertyInstance(_) | Self::PropertyInstance(_)
| Self::BoundSuper(_) => self.to_meta_type(db).definition(db), | Self::BoundSuper(_) => self.to_meta_type(db).definition(db),
Self::NonInferableTypeVar(bound_typevar) |
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)), Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
Self::ProtocolInstance(protocol) => match protocol.inner { Self::ProtocolInstance(protocol) => match protocol.inner {
@ -6270,6 +6335,10 @@ pub enum TypeMapping<'a, 'db> {
/// Binds a legacy typevar with the generic context (class, function, type alias) that it is /// Binds a legacy typevar with the generic context (class, function, type alias) that it is
/// being used in. /// being used in.
BindLegacyTypevars(BindingContext<'db>), BindLegacyTypevars(BindingContext<'db>),
/// Binds any `typing.Self` typevar with a particular `self` class.
BindSelf(Type<'db>),
/// Marks the typevars that are bound by a generic class or function as inferable.
MarkTypeVarsInferable(BindingContext<'db>),
} }
fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -6284,7 +6353,12 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
TypeMapping::PartialSpecialization(specialization) => { TypeMapping::PartialSpecialization(specialization) => {
walk_partial_specialization(db, specialization, visitor); walk_partial_specialization(db, specialization, visitor);
} }
TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) => {} TypeMapping::BindSelf(self_type) => {
visitor.visit_type(db, *self_type);
}
TypeMapping::PromoteLiterals
| TypeMapping::BindLegacyTypevars(_)
| TypeMapping::MarkTypeVarsInferable(_) => {}
} }
} }
@ -6301,6 +6375,10 @@ impl<'db> TypeMapping<'_, 'db> {
TypeMapping::BindLegacyTypevars(binding_context) => { TypeMapping::BindLegacyTypevars(binding_context) => {
TypeMapping::BindLegacyTypevars(*binding_context) TypeMapping::BindLegacyTypevars(*binding_context)
} }
TypeMapping::BindSelf(self_type) => TypeMapping::BindSelf(*self_type),
TypeMapping::MarkTypeVarsInferable(binding_context) => {
TypeMapping::MarkTypeVarsInferable(*binding_context)
}
} }
} }
@ -6316,6 +6394,12 @@ impl<'db> TypeMapping<'_, 'db> {
TypeMapping::BindLegacyTypevars(binding_context) => { TypeMapping::BindLegacyTypevars(binding_context) => {
TypeMapping::BindLegacyTypevars(*binding_context) TypeMapping::BindLegacyTypevars(*binding_context)
} }
TypeMapping::BindSelf(self_type) => {
TypeMapping::BindSelf(self_type.normalized_impl(db, visitor))
}
TypeMapping::MarkTypeVarsInferable(binding_context) => {
TypeMapping::MarkTypeVarsInferable(*binding_context)
}
} }
} }
} }
@ -6836,7 +6920,7 @@ pub enum TypeVarKind {
/// `def foo[T](x: T) -> T: ...` /// `def foo[T](x: T) -> T: ...`
Pep695, Pep695,
/// `typing.Self` /// `typing.Self`
Implicit, TypingSelf,
} }
/// A type variable that has not been bound to a generic context yet. /// A type variable that has not been bound to a generic context yet.
@ -6926,8 +7010,8 @@ impl<'db> TypeVarInstance<'db> {
BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context)) BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context))
} }
pub(crate) fn is_implicit(self, db: &'db dyn Db) -> bool { pub(crate) fn is_self(self, db: &'db dyn Db) -> bool {
matches!(self.kind(db), TypeVarKind::Implicit) matches!(self.kind(db), TypeVarKind::TypingSelf)
} }
pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option<Type<'db>> { pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option<Type<'db>> {
@ -7022,6 +7106,26 @@ impl<'db> TypeVarInstance<'db> {
) )
} }
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
let bound_or_constraints = match self.bound_or_constraints(db)? {
TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
TypeVarBoundOrConstraints::UpperBound(upper_bound.to_instance(db)?)
}
TypeVarBoundOrConstraints::Constraints(constraints) => {
TypeVarBoundOrConstraints::Constraints(constraints.to_instance(db)?.into_union()?)
}
};
Some(Self::new(
db,
Name::new(format!("{}'instance", self.name(db))),
None,
Some(bound_or_constraints.into()),
self.variance(db),
None,
self.kind(db),
))
}
#[salsa::tracked] #[salsa::tracked]
fn lazy_bound(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> { fn lazy_bound(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
let definition = self.definition(db)?; let definition = self.definition(db)?;
@ -7138,6 +7242,14 @@ impl<'db> BoundTypeVarInstance<'db> {
self.binding_context(db), self.binding_context(db),
) )
} }
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
Some(Self::new(
db,
self.typevar(db).to_instance(db)?,
self.binding_context(db),
))
}
} }
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
@ -8328,16 +8440,35 @@ fn walk_bound_method_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
visitor.visit_type(db, method.self_instance(db)); visitor.visit_type(db, method.self_instance(db));
} }
#[salsa::tracked]
impl<'db> BoundMethodType<'db> { impl<'db> BoundMethodType<'db> {
/// Returns the type that replaces any `typing.Self` annotations in the bound method signature.
/// This is normally the bound-instance type (the type of `self` or `cls`), but if the bound method is
/// a `@classmethod`, then it should be an instance of that bound-instance type.
pub(crate) fn typing_self_type(self, db: &'db dyn Db) -> Type<'db> {
let mut self_instance = self.self_instance(db);
if self
.function(db)
.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
{
self_instance = self_instance.to_instance(db).unwrap_or_else(Type::unknown);
}
self_instance
}
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> CallableType<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> CallableType<'db> {
let function = self.function(db);
let self_instance = self.typing_self_type(db);
CallableType::new( CallableType::new(
db, db,
CallableSignature::from_overloads( CallableSignature::from_overloads(
self.function(db) function
.signature(db) .signature(db)
.overloads .overloads
.iter() .iter()
.map(signatures::Signature::bind_self), .map(|signature| signature.bind_self(db, Some(self_instance))),
), ),
false, false,
) )
@ -8435,7 +8566,7 @@ impl<'db> CallableType<'db> {
pub(crate) fn bind_self(self, db: &'db dyn Db) -> Type<'db> { pub(crate) fn bind_self(self, db: &'db dyn Db) -> Type<'db> {
Type::Callable(CallableType::new( Type::Callable(CallableType::new(
db, db,
self.signatures(db).bind_self(), self.signatures(db).bind_self(db, None),
false, false,
)) ))
} }

View file

@ -983,7 +983,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
let mut positive_to_remove = SmallVec::<[usize; 1]>::new(); let mut positive_to_remove = SmallVec::<[usize; 1]>::new();
for (typevar_index, ty) in self.positive.iter().enumerate() { for (typevar_index, ty) in self.positive.iter().enumerate() {
let Type::TypeVar(bound_typevar) = ty else { let (Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar)) = ty
else {
continue; continue;
}; };
let Some(TypeVarBoundOrConstraints::Constraints(constraints)) = let Some(TypeVarBoundOrConstraints::Constraints(constraints)) =

View file

@ -966,6 +966,7 @@ impl<'db> ClassType<'db> {
/// Return a callable type (or union of callable types) that represents the callable /// Return a callable type (or union of callable types) that represents the callable
/// constructor signature of this class. /// constructor signature of this class.
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
pub(super) fn into_callable(self, db: &'db dyn Db) -> Type<'db> { pub(super) fn into_callable(self, db: &'db dyn Db) -> Type<'db> {
let self_ty = Type::from(self); let self_ty = Type::from(self);
let metaclass_dunder_call_function_symbol = self_ty let metaclass_dunder_call_function_symbol = self_ty
@ -1017,9 +1018,10 @@ impl<'db> ClassType<'db> {
}) })
}); });
let instance_ty = Type::instance(db, self);
let dunder_new_bound_method = Type::Callable(CallableType::new( let dunder_new_bound_method = Type::Callable(CallableType::new(
db, db,
dunder_new_signature.bind_self(), dunder_new_signature.bind_self(db, Some(instance_ty)),
true, true,
)); ));
@ -1057,9 +1059,10 @@ impl<'db> ClassType<'db> {
if let Some(signature) = signature { if let Some(signature) = signature {
let synthesized_signature = |signature: &Signature<'db>| { let synthesized_signature = |signature: &Signature<'db>| {
let instance_ty = Type::instance(db, self);
Signature::new(signature.parameters().clone(), Some(correct_return_type)) Signature::new(signature.parameters().clone(), Some(correct_return_type))
.with_definition(signature.definition()) .with_definition(signature.definition())
.bind_self() .bind_self(db, Some(instance_ty))
}; };
let synthesized_dunder_init_signature = CallableSignature::from_overloads( let synthesized_dunder_init_signature = CallableSignature::from_overloads(

View file

@ -155,6 +155,7 @@ impl<'db> ClassBase<'db> {
| Type::StringLiteral(_) | Type::StringLiteral(_)
| Type::LiteralString | Type::LiteralString
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::BoundSuper(_) | Type::BoundSuper(_)
| Type::ProtocolInstance(_) | Type::ProtocolInstance(_)

View file

@ -130,6 +130,8 @@ impl Display for DisplayRepresentation<'_> {
Type::Callable(callable) => callable.display(self.db).fmt(f), Type::Callable(callable) => callable.display(self.db).fmt(f),
Type::BoundMethod(bound_method) => { Type::BoundMethod(bound_method) => {
let function = bound_method.function(self.db); let function = bound_method.function(self.db);
let self_ty = bound_method.self_instance(self.db);
let typing_self_ty = bound_method.typing_self_type(self.db);
match function.signature(self.db).overloads.as_slice() { match function.signature(self.db).overloads.as_slice() {
[signature] => { [signature] => {
@ -142,9 +144,11 @@ impl Display for DisplayRepresentation<'_> {
f, f,
"bound method {instance}.{method}{type_parameters}{signature}", "bound method {instance}.{method}{type_parameters}{signature}",
method = function.name(self.db), method = function.name(self.db),
instance = bound_method.self_instance(self.db).display(self.db), instance = self_ty.display(self.db),
type_parameters = type_parameters, type_parameters = type_parameters,
signature = signature.bind_self().display(self.db) signature = signature
.bind_self(self.db, Some(typing_self_ty))
.display(self.db)
) )
} }
signatures => { signatures => {
@ -152,7 +156,11 @@ impl Display for DisplayRepresentation<'_> {
f.write_str("Overload[")?; f.write_str("Overload[")?;
let mut join = f.join(", "); let mut join = f.join(", ");
for signature in signatures { for signature in signatures {
join.entry(&signature.bind_self().display(self.db)); join.entry(
&signature
.bind_self(self.db, Some(typing_self_ty))
.display(self.db),
);
} }
f.write_str("]") f.write_str("]")
} }
@ -214,7 +222,7 @@ impl Display for DisplayRepresentation<'_> {
name = enum_literal.name(self.db), name = enum_literal.name(self.db),
) )
} }
Type::TypeVar(bound_typevar) => { Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => {
f.write_str(bound_typevar.typevar(self.db).name(self.db))?; f.write_str(bound_typevar.typevar(self.db).name(self.db))?;
if let Some(binding_context) = bound_typevar.binding_context(self.db).name(self.db) if let Some(binding_context) = bound_typevar.binding_context(self.db).name(self.db)
{ {
@ -455,7 +463,7 @@ impl Display for DisplayGenericContext<'_> {
let non_implicit_variables: Vec<_> = variables let non_implicit_variables: Vec<_> = variables
.iter() .iter()
.filter(|bound_typevar| !bound_typevar.typevar(self.db).is_implicit(self.db)) .filter(|bound_typevar| !bound_typevar.typevar(self.db).is_self(self.db))
.collect(); .collect();
if non_implicit_variables.is_empty() { if non_implicit_variables.is_empty() {

View file

@ -1014,6 +1014,7 @@ fn is_instance_truthiness<'db>(
| Type::PropertyInstance(..) | Type::PropertyInstance(..)
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::AlwaysFalsy | Type::AlwaysFalsy
| Type::NonInferableTypeVar(..)
| Type::TypeVar(..) | Type::TypeVar(..)
| Type::BoundSuper(..) | Type::BoundSuper(..)
| Type::TypeIs(..) | Type::TypeIs(..)

View file

@ -236,7 +236,8 @@ impl<'db> GenericContext<'db> {
db: &'db dyn Db, db: &'db dyn Db,
known_class: Option<KnownClass>, known_class: Option<KnownClass>,
) -> Specialization<'db> { ) -> Specialization<'db> {
let partial = self.specialize_partial(db, &vec![None; self.variables(db).len()]); let partial =
self.specialize_partial(db, std::iter::repeat_n(None, self.variables(db).len()));
if known_class == Some(KnownClass::Tuple) { if known_class == Some(KnownClass::Tuple) {
Specialization::new( Specialization::new(
db, db,
@ -249,6 +250,10 @@ impl<'db> GenericContext<'db> {
} }
} }
/// Returns a specialization of this generic context where each typevar is mapped to itself.
/// (And in particular, to an _inferable_ version of itself, since this will be used in our
/// class constructor invocation machinery to infer a specialization for the class from the
/// arguments passed to its constructor.)
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> { pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
let types = self let types = self
.variables(db) .variables(db)
@ -314,11 +319,12 @@ impl<'db> GenericContext<'db> {
/// Creates a specialization of this generic context. Panics if the length of `types` does not /// Creates a specialization of this generic context. Panics if the length of `types` does not
/// match the number of typevars in the generic context. If any provided type is `None`, we /// match the number of typevars in the generic context. If any provided type is `None`, we
/// will use the corresponding typevar's default type. /// will use the corresponding typevar's default type.
pub(crate) fn specialize_partial( pub(crate) fn specialize_partial<I>(self, db: &'db dyn Db, types: I) -> Specialization<'db>
self, where
db: &'db dyn Db, I: IntoIterator<Item = Option<Type<'db>>>,
types: &[Option<Type<'db>>], I::IntoIter: ExactSizeIterator,
) -> Specialization<'db> { {
let types = types.into_iter();
let variables = self.variables(db); let variables = self.variables(db);
assert!(variables.len() == types.len()); assert!(variables.len() == types.len());
@ -331,9 +337,9 @@ impl<'db> GenericContext<'db> {
// If there is a mapping for `T`, we want to map `U` to that type, not to `T`. To handle // If there is a mapping for `T`, we want to map `U` to that type, not to `T`. To handle
// this, we repeatedly apply the specialization to itself, until we reach a fixed point. // this, we repeatedly apply the specialization to itself, until we reach a fixed point.
let mut expanded = vec![Type::unknown(); types.len()]; let mut expanded = vec![Type::unknown(); types.len()];
for (idx, (ty, typevar)) in types.iter().zip(variables).enumerate() { for (idx, (ty, typevar)) in types.zip(variables).enumerate() {
if let Some(ty) = ty { if let Some(ty) = ty {
expanded[idx] = *ty; expanded[idx] = ty;
continue; continue;
} }
@ -749,18 +755,12 @@ impl<'db> SpecializationBuilder<'db> {
} }
pub(crate) fn build(&mut self, generic_context: GenericContext<'db>) -> Specialization<'db> { pub(crate) fn build(&mut self, generic_context: GenericContext<'db>) -> Specialization<'db> {
let types: Box<[_]> = generic_context let types = generic_context
.variables(self.db) .variables(self.db)
.iter() .iter()
.map(|variable| { .map(|variable| self.types.get(variable).copied());
self.types
.get(variable)
.copied()
.unwrap_or(variable.default_type(self.db).unwrap_or(Type::unknown()))
})
.collect();
// TODO Infer the tuple spec for a tuple type // TODO Infer the tuple spec for a tuple type
Specialization::new(self.db, generic_context, types, None) generic_context.specialize_partial(self.db, types)
} }
fn add_type_mapping(&mut self, bound_typevar: BoundTypeVarInstance<'db>, ty: Type<'db>) { fn add_type_mapping(&mut self, bound_typevar: BoundTypeVarInstance<'db>, ty: Type<'db>) {
@ -777,6 +777,10 @@ impl<'db> SpecializationBuilder<'db> {
formal: Type<'db>, formal: Type<'db>,
actual: Type<'db>, actual: Type<'db>,
) -> Result<(), SpecializationError<'db>> { ) -> Result<(), SpecializationError<'db>> {
if formal == actual {
return Ok(());
}
// If the actual type is a subtype of the formal type, then return without adding any new // If the actual type is a subtype of the formal type, then return without adding any new
// type mappings. (Note that if the formal type contains any typevars, this check will // type mappings. (Note that if the formal type contains any typevars, this check will
// fail, since no non-typevar types are assignable to a typevar. Also note that we are // fail, since no non-typevar types are assignable to a typevar. Also note that we are

View file

@ -151,6 +151,7 @@ impl<'db> AllMembers<'db> {
| Type::ProtocolInstance(_) | Type::ProtocolInstance(_)
| Type::SpecialForm(_) | Type::SpecialForm(_)
| Type::KnownInstance(_) | Type::KnownInstance(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::BoundSuper(_) | Type::BoundSuper(_)
| Type::TypeIs(_) => match ty.to_meta_type(db) { | Type::TypeIs(_) => match ty.to_meta_type(db) {

View file

@ -4025,6 +4025,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::WrapperDescriptor(_) | Type::WrapperDescriptor(_)
| Type::DataclassDecorator(_) | Type::DataclassDecorator(_)
| Type::DataclassTransformer(_) | Type::DataclassTransformer(_)
| Type::NonInferableTypeVar(..)
| Type::TypeVar(..) | Type::TypeVar(..)
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::AlwaysFalsy | Type::AlwaysFalsy
@ -7231,6 +7232,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::EnumLiteral(_) | Type::EnumLiteral(_)
| Type::BoundSuper(_) | Type::BoundSuper(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::TypeIs(_) | Type::TypeIs(_)
| Type::TypedDict(_), | Type::TypedDict(_),
@ -7572,6 +7574,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::EnumLiteral(_) | Type::EnumLiteral(_)
| Type::BoundSuper(_) | Type::BoundSuper(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::TypeIs(_) | Type::TypeIs(_)
| Type::TypedDict(_), | Type::TypedDict(_),
@ -7601,6 +7604,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::EnumLiteral(_) | Type::EnumLiteral(_)
| Type::BoundSuper(_) | Type::BoundSuper(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_) | Type::TypeVar(_)
| Type::TypeIs(_) | Type::TypeIs(_)
| Type::TypedDict(_), | Type::TypedDict(_),
@ -8652,7 +8656,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.next() .next()
.expect("valid bindings should have matching overload"); .expect("valid bindings should have matching overload");
Type::from(generic_class.apply_specialization(self.db(), |_| { Type::from(generic_class.apply_specialization(self.db(), |_| {
generic_context.specialize_partial(self.db(), overload.parameter_types()) generic_context
.specialize_partial(self.db(), overload.parameter_types().iter().copied())
})) }))
} }
@ -10604,7 +10609,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let mut signature_iter = callable_binding.into_iter().map(|binding| { let mut signature_iter = callable_binding.into_iter().map(|binding| {
if argument_type.is_bound_method() { if argument_type.is_bound_method() {
binding.signature.bind_self() binding.signature.bind_self(self.db(), Some(argument_type))
} else { } else {
binding.signature.clone() binding.signature.clone()
} }

View file

@ -215,7 +215,7 @@ impl ClassInfoConstraintFunction {
Type::Union(union) => { Type::Union(union) => {
union.try_map(db, |element| self.generate_constraint(db, *element)) union.try_map(db, |element| self.generate_constraint(db, *element))
} }
Type::TypeVar(bound_typevar) => match bound_typevar Type::NonInferableTypeVar(bound_typevar) => match bound_typevar
.typevar(db) .typevar(db)
.bound_or_constraints(db)? .bound_or_constraints(db)?
{ {
@ -259,6 +259,7 @@ impl ClassInfoConstraintFunction {
| Type::IntLiteral(_) | Type::IntLiteral(_)
| Type::KnownInstance(_) | Type::KnownInstance(_)
| Type::TypeIs(_) | Type::TypeIs(_)
| Type::TypeVar(_)
| Type::WrapperDescriptor(_) | Type::WrapperDescriptor(_)
| Type::DataclassTransformer(_) | Type::DataclassTransformer(_)
| Type::TypedDict(_) => None, | Type::TypedDict(_) => None,

View file

@ -19,7 +19,8 @@ use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::generics::{GenericContext, walk_generic_context};
use crate::types::{ use crate::types::{
BoundTypeVarInstance, KnownClass, NormalizedVisitor, TypeMapping, TypeRelation, todo_type, BindingContext, BoundTypeVarInstance, KnownClass, NormalizedVisitor, TypeMapping, TypeRelation,
todo_type,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name}; use ruff_python_ast::{self as ast, name::Name};
@ -98,9 +99,16 @@ impl<'db> CallableSignature<'db> {
} }
} }
pub(crate) fn bind_self(&self) -> Self { /// Binds the first (presumably `self`) parameter of this signature. If a `self_type` is
/// provided, we will replace any occurrences of `typing.Self` in the parameter and return
/// annotations with that type.
pub(crate) fn bind_self(&self, db: &'db dyn Db, self_type: Option<Type<'db>>) -> Self {
Self { Self {
overloads: self.overloads.iter().map(Signature::bind_self).collect(), overloads: self
.overloads
.iter()
.map(|signature| signature.bind_self(db, self_type))
.collect(),
} }
} }
@ -328,8 +336,11 @@ impl<'db> Signature<'db> {
let parameters = let parameters =
Parameters::from_parameters(db, definition, function_node.parameters.as_ref()); Parameters::from_parameters(db, definition, function_node.parameters.as_ref());
let return_ty = function_node.returns.as_ref().map(|returns| { let return_ty = function_node.returns.as_ref().map(|returns| {
let plain_return_ty = definition_expression_type(db, definition, returns.as_ref()); let plain_return_ty = definition_expression_type(db, definition, returns.as_ref())
.apply_type_mapping(
db,
&TypeMapping::MarkTypeVarsInferable(BindingContext::Definition(definition)),
);
if function_node.is_async && !is_generator { if function_node.is_async && !is_generator {
KnownClass::CoroutineType KnownClass::CoroutineType
.to_specialized_instance(db, [Type::any(), Type::any(), plain_return_ty]) .to_specialized_instance(db, [Type::any(), Type::any(), plain_return_ty])
@ -457,13 +468,20 @@ impl<'db> Signature<'db> {
self.definition self.definition
} }
pub(crate) fn bind_self(&self) -> Self { pub(crate) fn bind_self(&self, db: &'db dyn Db, self_type: Option<Type<'db>>) -> Self {
let mut parameters = Parameters::new(self.parameters().iter().skip(1).cloned());
let mut return_ty = self.return_ty;
if let Some(self_type) = self_type {
parameters = parameters.apply_type_mapping(db, &TypeMapping::BindSelf(self_type));
return_ty =
return_ty.map(|ty| ty.apply_type_mapping(db, &TypeMapping::BindSelf(self_type)));
}
Self { Self {
generic_context: self.generic_context, generic_context: self.generic_context,
inherited_generic_context: self.inherited_generic_context, inherited_generic_context: self.inherited_generic_context,
definition: self.definition, definition: self.definition,
parameters: Parameters::new(self.parameters().iter().skip(1).cloned()), parameters,
return_ty: self.return_ty, return_ty,
} }
} }
@ -1432,9 +1450,12 @@ impl<'db> Parameter<'db> {
kind: ParameterKind<'db>, kind: ParameterKind<'db>,
) -> Self { ) -> Self {
Self { Self {
annotated_type: parameter annotated_type: parameter.annotation().map(|annotation| {
.annotation() definition_expression_type(db, definition, annotation).apply_type_mapping(
.map(|annotation| definition_expression_type(db, definition, annotation)), db,
&TypeMapping::MarkTypeVarsInferable(BindingContext::Definition(definition)),
)
}),
kind, kind,
form: ParameterForm::Value, form: ParameterForm::Value,
} }

View file

@ -91,7 +91,7 @@ impl<'db> SubclassOfType<'db> {
TypeVarVariance::Invariant => { TypeVarVariance::Invariant => {
// We need to materialize this to `type[T]` but that isn't representable so // We need to materialize this to `type[T]` but that isn't representable so
// we instead use a type variable with an upper bound of `type`. // we instead use a type variable with an upper bound of `type`.
Type::TypeVar(BoundTypeVarInstance::new( Type::NonInferableTypeVar(BoundTypeVarInstance::new(
db, db,
TypeVarInstance::new( TypeVarInstance::new(
db, db,

View file

@ -142,6 +142,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::ProtocolInstance(_), _) => Ordering::Less, (Type::ProtocolInstance(_), _) => Ordering::Less,
(_, Type::ProtocolInstance(_)) => Ordering::Greater, (_, Type::ProtocolInstance(_)) => Ordering::Greater,
(Type::NonInferableTypeVar(left), Type::NonInferableTypeVar(right)) => left.cmp(right),
(Type::NonInferableTypeVar(_), _) => Ordering::Less,
(_, Type::NonInferableTypeVar(_)) => Ordering::Greater,
(Type::TypeVar(left), Type::TypeVar(right)) => left.cmp(right), (Type::TypeVar(left), Type::TypeVar(right)) => left.cmp(right),
(Type::TypeVar(_), _) => Ordering::Less, (Type::TypeVar(_), _) => Ordering::Less,
(_, Type::TypeVar(_)) => Ordering::Greater, (_, Type::TypeVar(_)) => Ordering::Greater,

View file

@ -114,6 +114,7 @@ enum NonAtomicType<'db> {
NominalInstance(NominalInstanceType<'db>), NominalInstance(NominalInstanceType<'db>),
PropertyInstance(PropertyInstanceType<'db>), PropertyInstance(PropertyInstanceType<'db>),
TypeIs(TypeIsType<'db>), TypeIs(TypeIsType<'db>),
NonInferableTypeVar(BoundTypeVarInstance<'db>),
TypeVar(BoundTypeVarInstance<'db>), TypeVar(BoundTypeVarInstance<'db>),
ProtocolInstance(ProtocolInstanceType<'db>), ProtocolInstance(ProtocolInstanceType<'db>),
TypedDict(TypedDictType<'db>), TypedDict(TypedDictType<'db>),
@ -177,6 +178,9 @@ impl<'db> From<Type<'db>> for TypeKind<'db> {
Type::PropertyInstance(property) => { Type::PropertyInstance(property) => {
TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property)) TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property))
} }
Type::NonInferableTypeVar(bound_typevar) => {
TypeKind::NonAtomic(NonAtomicType::NonInferableTypeVar(bound_typevar))
}
Type::TypeVar(bound_typevar) => { Type::TypeVar(bound_typevar) => {
TypeKind::NonAtomic(NonAtomicType::TypeVar(bound_typevar)) TypeKind::NonAtomic(NonAtomicType::TypeVar(bound_typevar))
} }
@ -216,6 +220,9 @@ fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>(
visitor.visit_property_instance_type(db, property); visitor.visit_property_instance_type(db, property);
} }
NonAtomicType::TypeIs(type_is) => visitor.visit_typeis_type(db, type_is), NonAtomicType::TypeIs(type_is) => visitor.visit_typeis_type(db, type_is),
NonAtomicType::NonInferableTypeVar(bound_typevar) => {
visitor.visit_bound_type_var_type(db, bound_typevar);
}
NonAtomicType::TypeVar(bound_typevar) => { NonAtomicType::TypeVar(bound_typevar) => {
visitor.visit_bound_type_var_type(db, bound_typevar); visitor.visit_bound_type_var_type(db, bound_typevar);
} }