[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

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

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::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<BoundTypeVarInstance<'db>>,
) {
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<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]
fn lazy_bound(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
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<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)]
@ -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,
))
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -236,7 +236,8 @@ impl<'db> GenericContext<'db> {
db: &'db dyn Db,
known_class: Option<KnownClass>,
) -> 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<Type<'db>>],
) -> Specialization<'db> {
pub(crate) fn specialize_partial<I>(self, db: &'db dyn Db, types: I) -> Specialization<'db>
where
I: IntoIterator<Item = Option<Type<'db>>>,
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

View file

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

View file

@ -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()
}

View file

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

View file

@ -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<Type<'db>>) -> 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<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 {
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,
}

View file

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

View file

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

View file

@ -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<Type<'db>> 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);
}