diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index c452ff75ff..72d4a29756 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1247,7 +1247,7 @@ quux. __init_subclass__ :: bound method Quux.__init_subclass__() -> None __module__ :: str __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_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method Quux.__repr__() -> str @@ -1292,7 +1292,7 @@ quux.b __init_subclass__ :: bound method Quux.__init_subclass__() -> None __module__ :: str __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_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method Quux.__repr__() -> str diff --git a/crates/ty_ide/src/semantic_tokens.rs b/crates/ty_ide/src/semantic_tokens.rs index f8f6955bc5..749671ad18 100644 --- a/crates/ty_ide/src/semantic_tokens.rs +++ b/crates/ty_ide/src/semantic_tokens.rs @@ -337,7 +337,9 @@ impl<'db> SemanticTokenVisitor<'db> { match ty { Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers), - Type::TypeVar(_) => (SemanticTokenType::TypeParameter, modifiers), + Type::NonInferableTypeVar(_) | Type::TypeVar(_) => { + (SemanticTokenType::TypeParameter, modifiers) + } Type::FunctionLiteral(_) => { // Check if this is a method based on current scope if self.in_class_scope { diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index d03dd21c02..e476e03a75 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -1,16 +1,16 @@ # 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. `typing.Self` is only available in Python 3.11 and later. ## Methods -```toml -[environment] -python-version = "3.11" -``` - ```py from typing import Self @@ -74,11 +74,6 @@ reveal_type(C().method()) # revealed: C ## Class Methods -```toml -[environment] -python-version = "3.11" -``` - ```py from typing import Self, TypeVar @@ -101,11 +96,6 @@ reveal_type(Shape.bar()) # revealed: Unknown ## Attributes -```toml -[environment] -python-version = "3.11" -``` - 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. @@ -127,11 +117,6 @@ reveal_type(LinkedList().next()) # revealed: LinkedList ## Generic Classes -```toml -[environment] -python-version = "3.11" -``` - ```py from typing import Self, Generic, TypeVar @@ -153,11 +138,6 @@ TODO: ## Annotations -```toml -[environment] -python-version = "3.11" -``` - ```py from typing import Self @@ -171,11 +151,6 @@ class Shape: `Self` cannot be used in the signature of a function or variable. -```toml -[environment] -python-version = "3.11" -``` - ```py from typing import Self, Generic, TypeVar @@ -218,4 +193,33 @@ class MyMetaclass(type): 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_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_method() -> D +reveal_type(D.class_method) +``` + [self attribute]: https://typing.python.org/en/latest/spec/generics.html#use-in-attribute-annotations diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 0a60e08d85..234de56a8c 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -491,6 +491,24 @@ class A(Generic[T]): 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 When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 261c3d370d..56cdbc426b 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -438,6 +438,19 @@ class A[T]: 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 When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 3d20c98492..b89d0a457e 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -243,7 +243,7 @@ class Person(NamedTuple): reveal_type(Person._field_defaults) # revealed: dict[str, Any] reveal_type(Person._fields) # revealed: tuple[str, ...] -reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self@_make +reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Person reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace @@ -304,8 +304,8 @@ satisfy: ```py def expects_named_tuple(x: typing.NamedTuple): 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._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> Self@_replace + reveal_type(x._make) # revealed: bound method type[NamedTupleLike]._make(iterable: Iterable[Any]) -> NamedTupleLike + reveal_type(x._replace) # revealed: bound method NamedTupleLike._replace(**kwargs) -> NamedTupleLike # revealed: Overload[(value: tuple[object, ...], /) -> tuple[object, ...], (value: tuple[_T@__add__, ...], /) -> tuple[object, ...]] reveal_type(x.__add__) reveal_type(x.__iter__) # revealed: bound method tuple[object, ...].__iter__() -> Iterator[object] @@ -328,7 +328,7 @@ class Point(NamedTuple): x: int y: int -reveal_type(Point._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self@_make +reveal_type(Point._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Point reveal_type(Point._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Point._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 52587fe60f..1aeeb36db7 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -232,7 +232,7 @@ impl<'db> Completion<'db> { | Type::BytesLiteral(_) => CompletionKind::Value, Type::EnumLiteral(_) => CompletionKind::Enum, 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::Intersection(intersection) => { intersection.iter_positive(db).find_map(|ty| imp(db, ty))? diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ad7a4c0ade..2636acfd89 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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::enums::{enum_metadata, is_single_member_enum}; use crate::types::function::{ - DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, + DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction, }; use crate::types::generics::{ GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context, @@ -612,9 +612,15 @@ pub enum Type<'db> { LiteralString, /// A bytes literal BytesLiteral(BytesLiteralType<'db>), - /// An instance of a typevar in a generic class or function. When the generic class or function - /// is specialized, we will replace this typevar with its specialization. + /// An instance of a typevar in a context where we can infer a specialization for it. (This is + /// 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>), + /// 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())` /// This type doesn't handle an unbound super object like `super(A)`; for that we just use /// a `Type::NominalInstance` of `builtins.super`. @@ -823,6 +829,9 @@ impl<'db> Type<'db> { ) .build(), 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_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.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.normalized_impl(db, visitor)) }), @@ -1203,6 +1215,7 @@ impl<'db> Type<'db> { | Type::Union(_) | Type::Intersection(_) | Type::Callable(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::TypeIs(_) @@ -1283,6 +1296,7 @@ impl<'db> Type<'db> { | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | 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`, // `T` will always be a subtype of any union containing `T`. // A similar rule applies in reverse to intersection types. - (Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true, - (Type::Intersection(intersection), Type::TypeVar(_)) + (Type::NonInferableTypeVar(_), Type::Union(union)) + if union.elements(db).contains(&self) => + { + true + } + (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) if intersection.positive(db).contains(&target) => { true } - (Type::Intersection(intersection), Type::TypeVar(_)) + (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) if intersection.negative(db).contains(&target) => { 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, // 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 => - { - true - } + ( + Type::NonInferableTypeVar(lhs_bound_typevar), + Type::NonInferableTypeVar(rhs_bound_typevar), + ) if lhs_bound_typevar == rhs_bound_typevar => true, // 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 // 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() => { 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 // 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. - (_, Type::TypeVar(bound_typevar)) + (_, Type::NonInferableTypeVar(bound_typevar)) if bound_typevar .typevar(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 // bound. This is true even if the bound is a final class, since the typevar can still // 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__`. // 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 // 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 // and `Any`!) Different typevars might be disjoint, depending on their bounds and // 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 => { false } - (tvar @ Type::TypeVar(_), Type::Intersection(intersection)) - | (Type::Intersection(intersection), tvar @ Type::TypeVar(_)) + (tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection)) + | (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_)) if intersection.negative(db).contains(&tvar) => { true @@ -1930,7 +1950,7 @@ impl<'db> Type<'db> { // 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 // 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) { None => false, 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 .elements(db) .iter() @@ -2383,7 +2407,7 @@ impl<'db> Type<'db> { // 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 // 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) { None => 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. Type::SubclassOf(..) => 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 // single-valued. (Note 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) { None => false, Some(TypeVarBoundOrConstraints::UpperBound(_)) => false, @@ -2524,6 +2550,8 @@ impl<'db> Type<'db> { } } + Type::TypeVar(_) => false, + Type::SubclassOf(..) => { // TODO: Same comment as above for `is_singleton` false @@ -2680,6 +2708,7 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::BytesLiteral(_) | Type::EnumLiteral(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) @@ -2790,7 +2819,7 @@ impl<'db> Type<'db> { Type::object(db).instance_member(db, name) } - Type::TypeVar(bound_typevar) => { + Type::NonInferableTypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => Type::object(db).instance_member(db, name), 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::BooleanLiteral(_) | Type::TypeIs(_) => { KnownClass::Bool.to_instance(db).instance_member(db, name) @@ -3329,6 +3368,7 @@ impl<'db> Type<'db> { | Type::BytesLiteral(..) | Type::EnumLiteral(..) | Type::LiteralString + | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::SpecialForm(..) | 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) { None => Truthiness::Ambiguous, Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { @@ -3675,6 +3715,7 @@ impl<'db> Type<'db> { } } } + Type::TypeVar(_) => Truthiness::Ambiguous, Type::NominalInstance(instance) => instance .class(db) @@ -3767,7 +3808,7 @@ impl<'db> Type<'db> { .into() } - Type::TypeVar(bound_typevar) => { + Type::NonInferableTypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => CallableBinding::not_callable(self).into(), 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) => { let signature = bound_method.function(db).signature(db); 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 // do this, we instead use the _identity_ specialization, which maps each of the class's // generic typevars to itself. - let (generic_origin, generic_context, self_type) = - match self { - Type::ClassLiteral(class) => match class.generic_context(db) { - Some(generic_context) => ( - Some(class), - Some(generic_context), - Type::from(class.apply_specialization(db, |_| { - generic_context.identity_specialization(db) - })), - ), - _ => (None, None, self), - }, + let (generic_origin, generic_context, self_type) = match self { + Type::ClassLiteral(class) => match class.generic_context(db) { + Some(generic_context) => ( + Some(class), + Some(generic_context), + Type::from(class.apply_specialization(db, |_| { + // It is important that identity_specialization specializes the class with + // _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), + }; // 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. @@ -5281,32 +5333,10 @@ impl<'db> Type<'db> { // 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 // mapped through `to_instance`. - Type::TypeVar(bound_typevar) => { - let typevar = bound_typevar.typevar(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::NonInferableTypeVar(bound_typevar) => { + Some(Type::NonInferableTypeVar(bound_typevar.to_instance(db)?)) } + Type::TypeVar(bound_typevar) => Some(Type::TypeVar(bound_typevar.to_instance(db)?)), Type::TypeAlias(alias) => alias.value_type(db).to_instance(db), Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")), Type::BooleanLiteral(_) @@ -5390,6 +5420,7 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::ModuleLiteral(_) | Type::StringLiteral(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::Callable(_) | Type::BoundMethod(_) @@ -5423,7 +5454,7 @@ impl<'db> Type<'db> { typevar_binding_context, *typevar, ) - .map(Type::TypeVar) + .map(Type::NonInferableTypeVar) .unwrap_or(*self)) } KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError { @@ -5506,7 +5537,7 @@ impl<'db> Type<'db> { Some(TypeVarBoundOrConstraints::UpperBound(instance).into()), TypeVarVariance::Invariant, None, - TypeVarKind::Implicit, + TypeVarKind::TypingSelf, ); Ok(bind_typevar( db, @@ -5516,7 +5547,7 @@ impl<'db> Type<'db> { typevar_binding_context, typevar, ) - .map(Type::TypeVar) + .map(Type::NonInferableTypeVar) .unwrap_or(*self)) } 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::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), - Type::TypeVar(bound_typevar) => { + Type::NonInferableTypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => KnownClass::Type.to_instance(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::GenericAlias(alias) => ClassType::from(*alias).metaclass(db), @@ -5792,16 +5824,46 @@ impl<'db> Type<'db> { TypeMapping::PartialSpecialization(partial) => { 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 { - TypeMapping::Specialization(_) | - TypeMapping::PartialSpecialization(_) | - TypeMapping::PromoteLiterals => self, TypeMapping::BindLegacyTypevars(binding_context) => { Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context)) } + TypeMapping::Specialization(_) | + TypeMapping::PartialSpecialization(_) | + TypeMapping::PromoteLiterals | + TypeMapping::BindSelf(_) | + TypeMapping::MarkTypeVarsInferable(_) => self, } Type::FunctionLiteral(function) => { @@ -5896,7 +5958,9 @@ impl<'db> Type<'db> { | Type::EnumLiteral(_) => match type_mapping { TypeMapping::Specialization(_) | TypeMapping::PartialSpecialization(_) | - TypeMapping::BindLegacyTypevars(_) => self, + TypeMapping::BindLegacyTypevars(_) | + TypeMapping::BindSelf(_) | + TypeMapping::MarkTypeVarsInferable(_) => self, TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) .expect("literal type should have fallback instance type"), } @@ -5929,10 +5993,10 @@ impl<'db> Type<'db> { typevars: &mut FxOrderSet>, ) { match self { - Type::TypeVar(bound_typevar) => { + Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => { if matches!( bound_typevar.typevar(db).kind(db), - TypeVarKind::Legacy | TypeVarKind::Implicit + TypeVarKind::Legacy | TypeVarKind::TypingSelf ) && binding_context.is_none_or(|binding_context| { bound_typevar.binding_context(db) == BindingContext::Definition(binding_context) }) { @@ -6145,6 +6209,7 @@ impl<'db> Type<'db> { | Self::PropertyInstance(_) | 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::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 /// being used in. 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>( @@ -6284,7 +6353,12 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>( TypeMapping::PartialSpecialization(specialization) => { 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::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::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: ...` Pep695, /// `typing.Self` - Implicit, + TypingSelf, } /// 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)) } - pub(crate) fn is_implicit(self, db: &'db dyn Db) -> bool { - matches!(self.kind(db), TypeVarKind::Implicit) + pub(crate) fn is_self(self, db: &'db dyn Db) -> bool { + matches!(self.kind(db), TypeVarKind::TypingSelf) } pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option> { @@ -7022,6 +7106,26 @@ impl<'db> TypeVarInstance<'db> { ) } + fn to_instance(self, db: &'db dyn Db) -> Option { + 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] fn lazy_bound(self, db: &'db dyn Db) -> Option> { let definition = self.definition(db)?; @@ -7138,6 +7242,14 @@ impl<'db> BoundTypeVarInstance<'db> { self.binding_context(db), ) } + + fn to_instance(self, db: &'db dyn Db) -> Option { + 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)] @@ -8328,16 +8440,35 @@ fn walk_bound_method_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( visitor.visit_type(db, method.self_instance(db)); } +#[salsa::tracked] 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> { + let function = self.function(db); + let self_instance = self.typing_self_type(db); + CallableType::new( db, CallableSignature::from_overloads( - self.function(db) + function .signature(db) .overloads .iter() - .map(signatures::Signature::bind_self), + .map(|signature| signature.bind_self(db, Some(self_instance))), ), false, ) @@ -8435,7 +8566,7 @@ impl<'db> CallableType<'db> { pub(crate) fn bind_self(self, db: &'db dyn Db) -> Type<'db> { Type::Callable(CallableType::new( db, - self.signatures(db).bind_self(), + self.signatures(db).bind_self(db, None), false, )) } diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 8e529e4fca..0f6ba1782d 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -983,7 +983,8 @@ impl<'db> InnerIntersectionBuilder<'db> { let mut positive_to_remove = SmallVec::<[usize; 1]>::new(); 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; }; let Some(TypeVarBoundOrConstraints::Constraints(constraints)) = diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index cd749f8444..beac62e361 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -966,6 +966,7 @@ impl<'db> ClassType<'db> { /// Return a callable type (or union of callable types) that represents the callable /// 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> { let self_ty = Type::from(self); 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( db, - dunder_new_signature.bind_self(), + dunder_new_signature.bind_self(db, Some(instance_ty)), true, )); @@ -1057,9 +1059,10 @@ impl<'db> ClassType<'db> { if let Some(signature) = signature { let synthesized_signature = |signature: &Signature<'db>| { + let instance_ty = Type::instance(db, self); Signature::new(signature.parameters().clone(), Some(correct_return_type)) .with_definition(signature.definition()) - .bind_self() + .bind_self(db, Some(instance_ty)) }; let synthesized_dunder_init_signature = CallableSignature::from_overloads( diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index aff7e833ce..e8a5fd1e41 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -155,6 +155,7 @@ impl<'db> ClassBase<'db> { | Type::StringLiteral(_) | Type::LiteralString | Type::ModuleLiteral(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::ProtocolInstance(_) diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index b50ca3d996..3d23a8393d 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -130,6 +130,8 @@ impl Display for DisplayRepresentation<'_> { Type::Callable(callable) => callable.display(self.db).fmt(f), Type::BoundMethod(bound_method) => { 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() { [signature] => { @@ -142,9 +144,11 @@ impl Display for DisplayRepresentation<'_> { f, "bound method {instance}.{method}{type_parameters}{signature}", 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, - signature = signature.bind_self().display(self.db) + signature = signature + .bind_self(self.db, Some(typing_self_ty)) + .display(self.db) ) } signatures => { @@ -152,7 +156,11 @@ impl Display for DisplayRepresentation<'_> { f.write_str("Overload[")?; let mut join = f.join(", "); 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("]") } @@ -214,7 +222,7 @@ impl Display for DisplayRepresentation<'_> { 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))?; 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 .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(); if non_implicit_variables.is_empty() { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 632425f16c..b482e23e74 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1014,6 +1014,7 @@ fn is_instance_truthiness<'db>( | Type::PropertyInstance(..) | Type::AlwaysTruthy | Type::AlwaysFalsy + | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::BoundSuper(..) | Type::TypeIs(..) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 00089be337..8b628fd375 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -236,7 +236,8 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, known_class: Option, ) -> 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) { Specialization::new( 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> { let types = self .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 /// match the number of typevars in the generic context. If any provided type is `None`, we /// will use the corresponding typevar's default type. - pub(crate) fn specialize_partial( - self, - db: &'db dyn Db, - types: &[Option>], - ) -> Specialization<'db> { + pub(crate) fn specialize_partial(self, db: &'db dyn Db, types: I) -> Specialization<'db> + where + I: IntoIterator>>, + I::IntoIter: ExactSizeIterator, + { + let types = types.into_iter(); let variables = self.variables(db); 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 // this, we repeatedly apply the specialization to itself, until we reach a fixed point. 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 { - expanded[idx] = *ty; + expanded[idx] = ty; continue; } @@ -749,18 +755,12 @@ impl<'db> SpecializationBuilder<'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) .iter() - .map(|variable| { - self.types - .get(variable) - .copied() - .unwrap_or(variable.default_type(self.db).unwrap_or(Type::unknown())) - }) - .collect(); + .map(|variable| self.types.get(variable).copied()); // 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>) { @@ -777,6 +777,10 @@ impl<'db> SpecializationBuilder<'db> { formal: Type<'db>, actual: Type<'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 // 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 diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 12f3a07bd0..36a139145c 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -151,6 +151,7 @@ impl<'db> AllMembers<'db> { | Type::ProtocolInstance(_) | Type::SpecialForm(_) | Type::KnownInstance(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::TypeIs(_) => match ty.to_meta_type(db) { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 2a6ebc63ae..c819d1d348 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -4025,6 +4025,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::WrapperDescriptor(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) + | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::AlwaysTruthy | Type::AlwaysFalsy @@ -7231,6 +7232,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -7572,6 +7574,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -7601,6 +7604,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) + | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -8652,7 +8656,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .next() .expect("valid bindings should have matching overload"); 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| { if argument_type.is_bound_method() { - binding.signature.bind_self() + binding.signature.bind_self(self.db(), Some(argument_type)) } else { binding.signature.clone() } diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 8569c961d5..d1df76dee6 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -215,7 +215,7 @@ impl ClassInfoConstraintFunction { Type::Union(union) => { 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) .bound_or_constraints(db)? { @@ -259,6 +259,7 @@ impl ClassInfoConstraintFunction { | Type::IntLiteral(_) | Type::KnownInstance(_) | Type::TypeIs(_) + | Type::TypeVar(_) | Type::WrapperDescriptor(_) | Type::DataclassTransformer(_) | Type::TypedDict(_) => None, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 11ca44bb8f..8d1492adc6 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -19,7 +19,8 @@ use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; use crate::semantic_index::definition::Definition; use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::{ - BoundTypeVarInstance, KnownClass, NormalizedVisitor, TypeMapping, TypeRelation, todo_type, + BindingContext, BoundTypeVarInstance, KnownClass, NormalizedVisitor, TypeMapping, TypeRelation, + todo_type, }; use crate::{Db, FxOrderSet}; 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>) -> 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 = Parameters::from_parameters(db, definition, function_node.parameters.as_ref()); 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 { KnownClass::CoroutineType .to_specialized_instance(db, [Type::any(), Type::any(), plain_return_ty]) @@ -457,13 +468,20 @@ impl<'db> Signature<'db> { self.definition } - pub(crate) fn bind_self(&self) -> Self { + pub(crate) fn bind_self(&self, db: &'db dyn Db, self_type: Option>) -> 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 { generic_context: self.generic_context, inherited_generic_context: self.inherited_generic_context, definition: self.definition, - parameters: Parameters::new(self.parameters().iter().skip(1).cloned()), - return_ty: self.return_ty, + parameters, + return_ty, } } @@ -1432,9 +1450,12 @@ impl<'db> Parameter<'db> { kind: ParameterKind<'db>, ) -> Self { Self { - annotated_type: parameter - .annotation() - .map(|annotation| definition_expression_type(db, definition, annotation)), + annotated_type: parameter.annotation().map(|annotation| { + definition_expression_type(db, definition, annotation).apply_type_mapping( + db, + &TypeMapping::MarkTypeVarsInferable(BindingContext::Definition(definition)), + ) + }), kind, form: ParameterForm::Value, } diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 1a67759432..c27e92018e 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -91,7 +91,7 @@ impl<'db> SubclassOfType<'db> { TypeVarVariance::Invariant => { // 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`. - Type::TypeVar(BoundTypeVarInstance::new( + Type::NonInferableTypeVar(BoundTypeVarInstance::new( db, TypeVarInstance::new( db, diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 0747fc8d17..21612c3614 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -142,6 +142,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::ProtocolInstance(_), _) => Ordering::Less, (_, 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(_), _) => Ordering::Less, (_, Type::TypeVar(_)) => Ordering::Greater, diff --git a/crates/ty_python_semantic/src/types/visitor.rs b/crates/ty_python_semantic/src/types/visitor.rs index deb68eab74..4b58f20bf5 100644 --- a/crates/ty_python_semantic/src/types/visitor.rs +++ b/crates/ty_python_semantic/src/types/visitor.rs @@ -114,6 +114,7 @@ enum NonAtomicType<'db> { NominalInstance(NominalInstanceType<'db>), PropertyInstance(PropertyInstanceType<'db>), TypeIs(TypeIsType<'db>), + NonInferableTypeVar(BoundTypeVarInstance<'db>), TypeVar(BoundTypeVarInstance<'db>), ProtocolInstance(ProtocolInstanceType<'db>), TypedDict(TypedDictType<'db>), @@ -177,6 +178,9 @@ impl<'db> From> for TypeKind<'db> { Type::PropertyInstance(property) => { TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property)) } + Type::NonInferableTypeVar(bound_typevar) => { + TypeKind::NonAtomic(NonAtomicType::NonInferableTypeVar(bound_typevar)) + } Type::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); } 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) => { visitor.visit_bound_type_var_type(db, bound_typevar); }