[ty] Use separate Rust types for bound and unbound type variables (#19796)

This PR creates separate Rust types for bound and unbound type
variables, as proposed in https://github.com/astral-sh/ty/issues/926.

Closes https://github.com/astral-sh/ty/issues/926

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Douglas Creager 2025-08-11 15:29:58 -04:00 committed by GitHub
parent f3f4db7104
commit dc84645c36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 846 additions and 560 deletions

View file

@ -47,8 +47,8 @@ use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
};
use crate::types::generics::{
GenericContext, PartialSpecialization, Specialization, bind_legacy_typevar,
walk_generic_context, walk_partial_specialization, walk_specialization,
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context,
walk_partial_specialization, walk_specialization,
};
pub use crate::types::ide_support::{
CallSignatureDetails, Member, all_members, call_signature_details, definition_kind_for_name,
@ -430,13 +430,14 @@ impl<'db> PropertyInstanceType<'db> {
fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
if let Some(ty) = self.getter(db) {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
if let Some(ty) = self.setter(db) {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -600,7 +601,7 @@ pub enum Type<'db> {
Tuple(TupleType<'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.
TypeVar(TypeVarInstance<'db>),
TypeVar(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`.
@ -699,15 +700,18 @@ impl<'db> Type<'db> {
// existential type representing "all lists, containing any type." We currently
// represent this by replacing `Any` in invariant position with an unresolved type
// variable.
TypeVarVariance::Invariant => Type::TypeVar(TypeVarInstance::new(
TypeVarVariance::Invariant => Type::TypeVar(BoundTypeVarInstance::new(
db,
Name::new_static("T_all"),
None,
None,
None,
variance,
None,
TypeVarKind::Pep695,
TypeVarInstance::new(
db,
Name::new_static("T_all"),
None,
None,
variance,
None,
TypeVarKind::Pep695,
),
BindingContext::Synthetic,
)),
TypeVarVariance::Covariant => Type::object(db),
TypeVarVariance::Contravariant => Type::Never,
@ -779,7 +783,7 @@ impl<'db> Type<'db> {
)
.build(),
Type::Tuple(tuple_type) => Type::tuple(tuple_type.materialize(db, variance)),
Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)),
Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)),
Type::TypeIs(type_is) => {
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
}
@ -1082,9 +1086,9 @@ impl<'db> Type<'db> {
Type::SubclassOf(subclass_of) => visitor.visit(self, |v| {
Type::SubclassOf(subclass_of.normalized_impl(db, v))
}),
Type::TypeVar(typevar) => {
visitor.visit(self, |v| Type::TypeVar(typevar.normalized_impl(db, v)))
}
Type::TypeVar(bound_typevar) => visitor.visit(self, |v| {
Type::TypeVar(bound_typevar.normalized_impl(db, v))
}),
Type::KnownInstance(known_instance) => visitor.visit(self, |v| {
Type::KnownInstance(known_instance.normalized_impl(db, v))
}),
@ -1354,8 +1358,8 @@ 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_typevar), Type::TypeVar(rhs_typevar))
if lhs_typevar == rhs_typevar =>
(Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar))
if lhs_bound_typevar == rhs_bound_typevar =>
{
true
}
@ -1363,8 +1367,10 @@ impl<'db> Type<'db> {
// 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(typevar), _) if typevar.bound_or_constraints(db).is_some() => {
match typevar.bound_or_constraints(db) {
(Type::TypeVar(bound_typevar), _)
if bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
{
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => unreachable!(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.has_relation_to(db, target, relation)
@ -1379,12 +1385,15 @@ 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(typevar))
if typevar.constraints(db).is_some_and(|constraints| {
constraints
.iter()
.all(|constraint| self.has_relation_to(db, *constraint, relation))
}) =>
(_, Type::TypeVar(bound_typevar))
if bound_typevar
.typevar(db)
.constraints(db)
.is_some_and(|constraints| {
constraints
.iter()
.all(|constraint| self.has_relation_to(db, *constraint, relation))
}) =>
{
true
}
@ -1802,8 +1811,8 @@ 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_typevar), Type::TypeVar(other_typevar))
if self_typevar == other_typevar =>
(Type::TypeVar(self_bound_typevar), Type::TypeVar(other_bound_typevar))
if self_bound_typevar == other_bound_typevar =>
{
false
}
@ -1819,8 +1828,8 @@ 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(typevar), other) | (other, Type::TypeVar(typevar)) => {
match typevar.bound_or_constraints(db) {
(Type::TypeVar(bound_typevar), other) | (other, Type::TypeVar(bound_typevar)) => {
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => false,
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.is_disjoint_from_impl(db, other, visitor)
@ -2331,14 +2340,16 @@ 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(typevar) => match typevar.bound_or_constraints(db) {
None => false,
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_singleton(db)),
},
Type::TypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => false,
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_singleton(db)),
}
}
// We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton.
Type::SubclassOf(..) => false,
@ -2466,14 +2477,16 @@ 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(typevar) => match typevar.bound_or_constraints(db) {
None => false,
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_single_valued(db)),
},
Type::TypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => false,
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.elements(db)
.iter()
.all(|constraint| constraint.is_single_valued(db)),
}
}
Type::SubclassOf(..) => {
// TODO: Same comment as above for `is_singleton`
@ -2735,16 +2748,18 @@ impl<'db> Type<'db> {
KnownClass::Object.to_instance(db).instance_member(db, name)
}
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
None => KnownClass::Object.to_instance(db).instance_member(db, name),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.instance_member(db, name)
Type::TypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => KnownClass::Object.to_instance(db).instance_member(db, name),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.instance_member(db, name)
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.map_with_boundness_and_qualifiers(db, |constraint| {
constraint.instance_member(db, name)
}),
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
.map_with_boundness_and_qualifiers(db, |constraint| {
constraint.instance_member(db, name)
}),
},
}
Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name),
Type::BooleanLiteral(_) | Type::TypeIs(_) => {
@ -3606,15 +3621,17 @@ impl<'db> Type<'db> {
}
},
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
None => Truthiness::Ambiguous,
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.try_bool_impl(db, allow_short_circuit)?
Type::TypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => Truthiness::Ambiguous,
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.try_bool_impl(db, allow_short_circuit)?
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
try_union(constraints)?
}
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
try_union(constraints)?
}
},
}
Type::NominalInstance(instance) => match instance.class.known(db) {
Some(known_class) => known_class.bool(),
@ -3710,14 +3727,18 @@ impl<'db> Type<'db> {
.into()
}
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
None => CallableBinding::not_callable(self).into(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db),
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => Bindings::from_union(
self,
constraints.elements(db).iter().map(|ty| ty.bindings(db)),
),
},
Type::TypeVar(bound_typevar) => {
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => CallableBinding::not_callable(self).into(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db),
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
Bindings::from_union(
self,
constraints.elements(db).iter().map(|ty| ty.bindings(db)),
)
}
}
}
Type::BoundMethod(bound_method) => {
let signature = bound_method.function(db).signature(db);
@ -5210,7 +5231,8 @@ 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(typevar) => {
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)?)
@ -5221,15 +5243,18 @@ impl<'db> Type<'db> {
)
}
};
Some(Type::TypeVar(TypeVarInstance::new(
Some(Type::TypeVar(BoundTypeVarInstance::new(
db,
Name::new(format!("{}'instance", typevar.name(db))),
None,
None,
Some(bound_or_constraints),
typevar.variance(db),
None,
typevar.kind(db),
TypeVarInstance::new(
db,
Name::new(format!("{}'instance", typevar.name(db))),
None,
Some(bound_or_constraints),
typevar.variance(db),
None,
typevar.kind(db),
),
bound_typevar.binding_context(db),
)))
}
Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")),
@ -5268,13 +5293,13 @@ impl<'db> Type<'db> {
/// expression, it names the type `Type::NominalInstance(builtins.int)`, that is, all objects whose
/// `__class__` is `int`.
///
/// The `scope_id` and `legacy_typevar_binding_context` arguments must always come from the file we are currently inferring, so
/// The `scope_id` and `typevar_binding_context` arguments must always come from the file we are currently inferring, so
/// as to avoid cross-module AST dependency.
pub(crate) fn in_type_expression(
&self,
db: &'db dyn Db,
scope_id: ScopeId<'db>,
legacy_typevar_binding_context: Option<Definition<'db>>,
typevar_binding_context: Option<Definition<'db>>,
) -> Result<Type<'db>, InvalidTypeExpressionError<'db>> {
match self {
// Special cases for `float` and `complex`
@ -5342,7 +5367,20 @@ impl<'db> Type<'db> {
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)),
KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)),
KnownInstanceType::TypeVar(typevar) => {
let module = parsed_module(db, scope_id.file(db)).load(db);
let index = semantic_index(db, scope_id.file(db));
Ok(bind_typevar(
db,
&module,
index,
scope_id.file_scope_id(db),
typevar_binding_context,
*typevar,
)
.map(Type::TypeVar)
.unwrap_or(*self))
}
KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Deprecated],
fallback_type: Type::unknown(),
@ -5407,22 +5445,21 @@ impl<'db> Type<'db> {
db,
ast::name::Name::new_static("Self"),
Some(class_definition),
None,
Some(TypeVarBoundOrConstraints::UpperBound(instance)),
TypeVarVariance::Invariant,
None,
TypeVarKind::Implicit,
);
let typevar = bind_legacy_typevar(
Ok(bind_typevar(
db,
&module,
index,
scope_id.file_scope_id(db),
legacy_typevar_binding_context,
typevar_binding_context,
typevar,
)
.unwrap_or(typevar);
Ok(Type::TypeVar(typevar))
.map(Type::TypeVar)
.unwrap_or(*self))
}
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
@ -5497,7 +5534,7 @@ impl<'db> Type<'db> {
let mut builder = UnionBuilder::new(db);
let mut invalid_expressions = smallvec::SmallVec::default();
for element in union.elements(db) {
match element.in_type_expression(db, scope_id, legacy_typevar_binding_context) {
match element.in_type_expression(db, scope_id, typevar_binding_context) {
Ok(type_expr) => builder = builder.add(type_expr),
Err(InvalidTypeExpressionError {
fallback_type,
@ -5621,15 +5658,17 @@ impl<'db> Type<'db> {
Type::Tuple(tuple) => tuple
.to_subclass_of(db)
.unwrap_or_else(SubclassOfType::subclass_of_unknown),
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
None => KnownClass::Type.to_instance(db),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db),
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
// TODO: If we add a proper `OneOf` connector, we should use that here instead
// of union. (Using a union here doesn't break anything, but it is imprecise.)
constraints.map(db, |constraint| constraint.to_meta_type(db))
Type::TypeVar(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),
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
// TODO: If we add a proper `OneOf` connector, we should use that here instead
// of union. (Using a union here doesn't break anything, but it is imprecise.)
constraints.map(db, |constraint| constraint.to_meta_type(db))
}
}
},
}
Type::ClassLiteral(class) => class.metaclass(db),
Type::GenericAlias(alias) => ClassType::from(*alias).metaclass(db),
@ -5709,16 +5748,22 @@ impl<'db> Type<'db> {
type_mapping: &TypeMapping<'a, 'db>,
) -> Type<'db> {
match self {
Type::TypeVar(typevar) => match type_mapping {
Type::TypeVar(bound_typevar) => match type_mapping {
TypeMapping::Specialization(specialization) => {
specialization.get(db, typevar).unwrap_or(self)
specialization.get(db, bound_typevar).unwrap_or(self)
}
TypeMapping::PartialSpecialization(partial) => {
partial.get(db, typevar).unwrap_or(self)
partial.get(db, bound_typevar).unwrap_or(self)
}
TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) => self,
}
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) |
TypeMapping::PromoteLiterals => self,
TypeMapping::BindLegacyTypevars(binding_context) => {
Type::TypeVar(typevar.with_binding_context(db, *binding_context))
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
}
}
@ -5841,80 +5886,94 @@ impl<'db> Type<'db> {
pub(crate) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
match self {
Type::TypeVar(typevar) => {
if typevar.is_legacy(db) || typevar.is_implicit(db) {
typevars.insert(typevar);
Type::TypeVar(bound_typevar) => {
if matches!(
bound_typevar.typevar(db).kind(db),
TypeVarKind::Legacy | TypeVarKind::Implicit
) && binding_context.is_none_or(|binding_context| {
bound_typevar.binding_context(db) == BindingContext::Definition(binding_context)
}) {
typevars.insert(bound_typevar);
}
}
Type::FunctionLiteral(function) => function.find_legacy_typevars(db, typevars),
Type::FunctionLiteral(function) => {
function.find_legacy_typevars(db, binding_context, typevars);
}
Type::BoundMethod(method) => {
method.self_instance(db).find_legacy_typevars(db, typevars);
method.function(db).find_legacy_typevars(db, typevars);
method
.self_instance(db)
.find_legacy_typevars(db, binding_context, typevars);
method
.function(db)
.find_legacy_typevars(db, binding_context, typevars);
}
Type::MethodWrapper(
MethodWrapperKind::FunctionTypeDunderGet(function)
| MethodWrapperKind::FunctionTypeDunderCall(function),
) => {
function.find_legacy_typevars(db, typevars);
function.find_legacy_typevars(db, binding_context, typevars);
}
Type::MethodWrapper(
MethodWrapperKind::PropertyDunderGet(property)
| MethodWrapperKind::PropertyDunderSet(property),
) => {
property.find_legacy_typevars(db, typevars);
property.find_legacy_typevars(db, binding_context, typevars);
}
Type::Callable(callable) => {
callable.find_legacy_typevars(db, typevars);
callable.find_legacy_typevars(db, binding_context, typevars);
}
Type::PropertyInstance(property) => {
property.find_legacy_typevars(db, typevars);
property.find_legacy_typevars(db, binding_context, typevars);
}
Type::Union(union) => {
for element in union.iter(db) {
element.find_legacy_typevars(db, typevars);
element.find_legacy_typevars(db, binding_context, typevars);
}
}
Type::Intersection(intersection) => {
for positive in intersection.positive(db) {
positive.find_legacy_typevars(db, typevars);
positive.find_legacy_typevars(db, binding_context, typevars);
}
for negative in intersection.negative(db) {
negative.find_legacy_typevars(db, typevars);
negative.find_legacy_typevars(db, binding_context, typevars);
}
}
Type::Tuple(tuple) => {
tuple.find_legacy_typevars(db, typevars);
tuple.find_legacy_typevars(db, binding_context, typevars);
}
Type::GenericAlias(alias) => {
alias.find_legacy_typevars(db, typevars);
alias.find_legacy_typevars(db, binding_context, typevars);
}
Type::NominalInstance(instance) => {
instance.find_legacy_typevars(db, typevars);
instance.find_legacy_typevars(db, binding_context, typevars);
}
Type::ProtocolInstance(instance) => {
instance.find_legacy_typevars(db, typevars);
instance.find_legacy_typevars(db, binding_context, typevars);
}
Type::SubclassOf(subclass_of) => {
subclass_of.find_legacy_typevars(db, typevars);
subclass_of.find_legacy_typevars(db, binding_context, typevars);
}
Type::TypeIs(type_is) => {
type_is.return_type(db).find_legacy_typevars(db, typevars);
type_is
.return_type(db)
.find_legacy_typevars(db, binding_context, typevars);
}
Type::Dynamic(_)
@ -6044,7 +6103,7 @@ impl<'db> Type<'db> {
| Self::BoundSuper(_)
| Self::Tuple(_) => self.to_meta_type(db).definition(db),
Self::TypeVar(var) => Some(TypeDefinition::TypeVar(var.definition(db)?)),
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
Self::ProtocolInstance(protocol) => match protocol.inner {
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
@ -6168,7 +6227,7 @@ pub enum TypeMapping<'a, 'db> {
PromoteLiterals,
/// Binds a legacy typevar with the generic context (class, function, type alias) that it is
/// being used in.
BindLegacyTypevars(Definition<'db>),
BindLegacyTypevars(BindingContext<'db>),
}
fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -6359,7 +6418,9 @@ impl<'db> KnownInstanceType<'db> {
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"),
KnownInstanceType::TypeVar(typevar) => {
write!(f, "typing.TypeVar({})", typevar.display(self.db))
}
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),
KnownInstanceType::Field(field) => {
f.write_str("dataclasses.Field[")?;
@ -6738,13 +6799,38 @@ pub enum TypeVarKind {
Implicit,
}
/// Data regarding a single type variable.
/// A type variable that has not been bound to a generic context yet.
///
/// This is referenced by `KnownInstanceType::TypeVar` (to represent the singleton type of the
/// runtime `typing.TypeVar` object itself), and by `Type::TypeVar` to represent the type that this
/// typevar represents as an annotation: that is, an unknown set of objects, constrained by the
/// upper-bound/constraints on this type var, defaulting to the default type of this type var when
/// not otherwise bound to a type.
/// This is usually not the type that you want; if you are working with a typevar, in a generic
/// context, which might be specialized to a concrete type, you want [`BoundTypeVarInstance`]. This
/// type holds information that does not depend on which generic context the typevar is used in.
///
/// For a legacy typevar:
///
/// ```py
/// T = TypeVar("T") # [1]
/// def generic_function(t: T) -> T: ... # [2]
/// ```
///
/// we will create a `TypeVarInstance` for the typevar `T` when it is instantiated. The type of `T`
/// at `[1]` will be a `KnownInstanceType::TypeVar` wrapping this `TypeVarInstance`. The typevar is
/// not yet bound to any generic context at this point.
///
/// The typevar is used in `generic_function`, which binds it to a new generic context. We will
/// create a [`BoundTypeVarInstance`] for this new binding of the typevar. The type of `T` at `[2]`
/// will be a `Type::TypeVar` wrapping this `BoundTypeVarInstance`.
///
/// For a PEP 695 typevar:
///
/// ```py
/// def generic_function[T](t: T) -> T: ...
/// # ╰─────╰─────────── [2]
/// # ╰─────────────────────── [1]
/// ```
///
/// the typevar is defined and immediately bound to a single generic context. Just like in the
/// legacy case, we will create a `TypeVarInstance` and [`BoundTypeVarInstance`], and the type of
/// `T` at `[1]` and `[2]` will be that `TypeVarInstance` and `BoundTypeVarInstance`, respectively.
///
/// # Ordering
/// Ordering is based on the type var instance's salsa-assigned id and not on its values.
@ -6759,35 +6845,6 @@ pub struct TypeVarInstance<'db> {
/// The type var's definition (None if synthesized)
pub definition: Option<Definition<'db>>,
/// The definition of the generic class, function, or type alias that binds this typevar. This
/// is `None` for a legacy typevar outside of a context that can bind it.
///
/// For a legacy typevar, the binding context might be missing:
///
/// ```py
/// T = TypeVar("T") # [1]
/// def generic_function(t: T) -> T: ... # [2]
/// ```
///
/// Here, we will create two `TypeVarInstance`s for the typevar `T`. Both will have `[1]` as
/// their [`definition`][Self::definition]. The first represents the variable when it is first
/// created, and not yet used, so it's `binding_context` will be `None`. The second represents
/// when the typevar is used in `generic_function`, and its `binding_context` will be `[2]`
/// (that is, the definition of `generic_function`).
///
/// For a PEP 695 typevar, there will always be a binding context, since you can only define
/// one as part of creating the generic context that uses it:
///
/// ```py
/// def generic_function[T](t: T) -> T: ...
/// ```
///
/// Here, we will create a single `TypeVarInstance`. Its [`definition`][Self::definition] will
/// be the `T` in `[T]` (i.e., the definition of the typevar in the syntactic construct that
/// creates the generic context that uses it). Its `binding_context` will be the definition of
/// `generic_function`.
binding_context: Option<Definition<'db>>,
/// The upper bound or constraint on the type of this TypeVar
bound_or_constraints: Option<TypeVarBoundOrConstraints<'db>>,
@ -6805,13 +6862,13 @@ impl get_size2::GetSize for TypeVarInstance<'_> {}
fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
type_var: TypeVarInstance<'db>,
typevar: TypeVarInstance<'db>,
visitor: &mut V,
) {
if let Some(bounds) = type_var.bound_or_constraints(db) {
if let Some(bounds) = typevar.bound_or_constraints(db) {
walk_type_var_bounds(db, bounds, visitor);
}
if let Some(default_type) = type_var.default_ty(db) {
if let Some(default_type) = typevar.default_ty(db) {
visitor.visit_type(db, default_type);
}
}
@ -6821,23 +6878,8 @@ impl<'db> TypeVarInstance<'db> {
self,
db: &'db dyn Db,
binding_context: Definition<'db>,
) -> Self {
Self::new(
db,
self.name(db),
self.definition(db),
Some(binding_context),
self.bound_or_constraints(db),
self.variance(db),
self.default_ty(db).map(|ty| {
ty.apply_type_mapping(db, &TypeMapping::BindLegacyTypevars(binding_context))
}),
self.kind(db),
)
}
pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool {
matches!(self.kind(db), TypeVarKind::Legacy)
) -> BoundTypeVarInstance<'db> {
BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context))
}
pub(crate) fn is_implicit(self, db: &'db dyn Db) -> bool {
@ -6869,7 +6911,6 @@ impl<'db> TypeVarInstance<'db> {
db,
self.name(db),
self.definition(db),
self.binding_context(db),
self.bound_or_constraints(db)
.map(|b| b.normalized_impl(db, visitor)),
self.variance(db),
@ -6883,7 +6924,6 @@ impl<'db> TypeVarInstance<'db> {
db,
self.name(db),
self.definition(db),
self.binding_context(db),
self.bound_or_constraints(db)
.map(|b| b.materialize(db, variance)),
self.variance(db),
@ -6893,6 +6933,96 @@ impl<'db> TypeVarInstance<'db> {
}
}
/// Where a type variable is bound and usable.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
pub enum BindingContext<'db> {
/// The definition of the generic class, function, or type alias that binds this typevar.
Definition(Definition<'db>),
/// The typevar is synthesized internally, and is not associated with a particular definition
/// in the source, but is still bound and eligible for specialization inference.
Synthetic,
}
impl<'db> BindingContext<'db> {
fn name(self, db: &'db dyn Db) -> Option<String> {
match self {
BindingContext::Definition(definition) => definition.name(db),
BindingContext::Synthetic => None,
}
}
}
/// A type variable that has been bound to a generic context, and which can be specialized to a
/// concrete type.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct BoundTypeVarInstance<'db> {
pub typevar: TypeVarInstance<'db>,
binding_context: BindingContext<'db>,
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for BoundTypeVarInstance<'_> {}
fn walk_bound_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
visitor: &mut V,
) {
visitor.visit_type_var_type(db, bound_typevar.typevar(db));
}
impl<'db> BoundTypeVarInstance<'db> {
/// Returns the default value of this typevar, recursively applying its binding context to any
/// other typevars that appear in the default.
///
/// For instance, in
///
/// ```py
/// T = TypeVar("T")
/// U = TypeVar("U", default=T)
///
/// # revealed: typing.TypeVar[U = typing.TypeVar[T]]
/// reveal_type(U)
///
/// # revealed: typing.Generic[T, U = T@C]
/// class C(reveal_type(Generic[T, U])): ...
/// ```
///
/// In the first case, the use of `U` is unbound, and so we have a [`TypeVarInstance`], and its
/// default value (`T`) is also unbound.
///
/// By using `U` in the generic class, it becomes bound, and so we have a
/// `BoundTypeVarInstance`. As part of binding `U` we must also bind its default value
/// (resulting in `T@C`).
pub(crate) fn default_ty(self, db: &'db dyn Db) -> Option<Type<'db>> {
let binding_context = self.binding_context(db);
self.typevar(db)
.default_ty(db)
.map(|ty| ty.apply_type_mapping(db, &TypeMapping::BindLegacyTypevars(binding_context)))
}
pub(crate) fn normalized_impl(
self,
db: &'db dyn Db,
visitor: &mut TypeTransformer<'db>,
) -> Self {
Self::new(
db,
self.typevar(db).normalized_impl(db, visitor),
self.binding_context(db),
)
}
fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
Self::new(
db,
self.typevar(db).materialize(db, variance),
self.binding_context(db),
)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
pub enum TypeVarVariance {
Invariant,
@ -8109,9 +8239,11 @@ impl<'db> CallableType<'db> {
fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
self.signatures(db).find_legacy_typevars(db, typevars);
self.signatures(db)
.find_legacy_typevars(db, binding_context, typevars);
}
/// Check whether this callable type has the given relation to another callable type.

View file

@ -973,11 +973,11 @@ 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(typevar) = ty else {
let Type::TypeVar(bound_typevar) = ty else {
continue;
};
let Some(TypeVarBoundOrConstraints::Constraints(constraints)) =
typevar.bound_or_constraints(db)
bound_typevar.typevar(db).bound_or_constraints(db)
else {
continue;
};

View file

@ -2857,7 +2857,7 @@ impl<'db> BindingError<'db> {
return;
};
let typevar = error.typevar();
let typevar = error.bound_typevar().typevar(context.db());
let argument_type = error.argument_type();
let argument_ty_display = argument_type.display(context.db());

View file

@ -2,8 +2,8 @@ use std::sync::{LazyLock, Mutex};
use super::TypeVarVariance;
use super::{
IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType,
SubclassOfType, Truthiness, Type, TypeQualifiers,
BoundTypeVarInstance, IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator,
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers,
class_base::ClassBase,
function::{FunctionDecorators, FunctionType},
infer_expression_type, infer_unpack_types,
@ -270,11 +270,13 @@ impl<'db> GenericAlias<'db> {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
// A tuple's specialization will include all of its element types, so we don't need to also
// look in `self.tuple`.
self.specialization(db).find_legacy_typevars(db, typevars);
self.specialization(db)
.find_legacy_typevars(db, binding_context, typevars);
}
pub(super) fn is_typed_dict(self, db: &'db dyn Db) -> bool {
@ -415,11 +417,12 @@ impl<'db> ClassType<'db> {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
match self {
Self::NonGeneric(_) => {}
Self::Generic(generic) => generic.find_legacy_typevars(db, typevars),
Self::Generic(generic) => generic.find_legacy_typevars(db, binding_context, typevars),
}
}
@ -1220,11 +1223,13 @@ impl<'db> ClassLiteral<'db> {
#[salsa::tracked(cycle_fn=pep695_generic_context_cycle_recover, cycle_initial=pep695_generic_context_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn pep695_generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
let scope = self.body_scope(db);
let parsed = parsed_module(db, scope.file(db)).load(db);
let file = scope.file(db);
let parsed = parsed_module(db, file).load(db);
let class_def_node = scope.node(db).expect_class(&parsed);
class_def_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, type_params)
let index = semantic_index(db, file);
let definition = index.expect_single_definition(class_def_node);
GenericContext::from_type_params(db, index, definition, type_params)
})
}
@ -1339,21 +1344,7 @@ impl<'db> ClassLiteral<'db> {
class_stmt
.bases()
.iter()
.map(
|base_node| match definition_expression_type(db, class_definition, base_node) {
Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(generic_context)) => {
Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(
generic_context.with_binding_context(db, class_definition),
))
}
Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(
generic_context,
)) => Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(
generic_context.with_binding_context(db, class_definition),
)),
ty => ty,
},
)
.map(|base_node| definition_expression_type(db, class_definition, base_node))
.collect()
}
@ -4454,15 +4445,11 @@ impl KnownClass {
};
let containing_assignment = index.expect_single_definition(target);
// A freshly created legacy TypeVar does not have a binding context until it is
// used in a base class list, function parameter list, or type alias.
let binding_context = None;
overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar(
TypeVarInstance::new(
db,
&target.id,
Some(containing_assignment),
binding_context,
bound_or_constraint,
variance,
*default,

View file

@ -7,17 +7,17 @@ use ruff_python_ast::str::{Quote, TripleQuotes};
use ruff_python_literal::escape::AsciiEscape;
use ruff_text_size::{TextRange, TextSize};
use crate::Db;
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
use crate::types::function::{FunctionType, OverloadLiteral};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::tuple::TupleSpec;
use crate::types::{
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
WrapperDescriptorKind,
BoundTypeVarInstance, CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol,
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
UnionType, WrapperDescriptorKind,
};
use crate::{Db, FxOrderSet};
impl<'db> Type<'db> {
pub fn display(&self, db: &'db dyn Db) -> DisplayType<'_> {
@ -208,11 +208,9 @@ impl Display for DisplayRepresentation<'_> {
)
}
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
Type::TypeVar(typevar) => {
f.write_str(typevar.name(self.db))?;
if let Some(binding_context) = typevar
.binding_context(self.db)
.and_then(|def| def.name(self.db))
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)
{
write!(f, "@{binding_context}")?;
}
@ -413,6 +411,83 @@ impl Display for DisplayGenericAlias<'_> {
}
}
impl<'db> TypeVarInstance<'db> {
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayTypeVarInstance<'db> {
DisplayTypeVarInstance { typevar: self, db }
}
}
pub(crate) struct DisplayTypeVarInstance<'db> {
typevar: TypeVarInstance<'db>,
db: &'db dyn Db,
}
impl Display for DisplayTypeVarInstance<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
display_quoted_string(self.typevar.name(self.db)).fmt(f)?;
match self.typevar.bound_or_constraints(self.db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
write!(f, ", bound={}", bound.display(self.db))?;
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
for constraint in constraints.iter(self.db) {
write!(f, ", {}", constraint.display(self.db))?;
}
}
None => {}
}
if let Some(default_type) = self.typevar.default_ty(self.db) {
write!(f, ", default={}", default_type.display(self.db))?;
}
Ok(())
}
}
impl<'db> BoundTypeVarInstance<'db> {
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayBoundTypeVarInstance<'db> {
DisplayBoundTypeVarInstance {
bound_typevar: self,
db,
}
}
}
pub(crate) struct DisplayBoundTypeVarInstance<'db> {
bound_typevar: BoundTypeVarInstance<'db>,
db: &'db dyn Db,
}
impl Display for DisplayBoundTypeVarInstance<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// This looks very much like DisplayTypeVarInstance::fmt, but note that we have typevar
// default values in a subtly different way: if the default value contains other typevars,
// here those must be bound as well, whereas in DisplayTypeVarInstance they should not. See
// BoundTypeVarInstance::default_ty for more details.
let typevar = self.bound_typevar.typevar(self.db);
f.write_str(typevar.name(self.db))?;
match typevar.bound_or_constraints(self.db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
write!(f, ": {}", bound.display(self.db))?;
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
f.write_str(": (")?;
for (idx, constraint) in constraints.iter(self.db).enumerate() {
if idx > 0 {
f.write_str(", ")?;
}
constraint.display(self.db).fmt(f)?;
}
f.write_char(')')?;
}
None => {}
}
if let Some(default_type) = self.bound_typevar.default_ty(self.db) {
write!(f, " = {}", default_type.display(self.db))?;
}
Ok(())
}
}
impl<'db> GenericContext<'db> {
pub fn display(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> {
DisplayGenericContext {
@ -452,7 +527,7 @@ impl Display for DisplayGenericContext<'_> {
let non_implicit_variables: Vec<_> = variables
.iter()
.filter(|var| !var.is_implicit(self.db))
.filter(|bound_typevar| !bound_typevar.typevar(self.db).is_implicit(self.db))
.collect();
if non_implicit_variables.is_empty() {
@ -460,30 +535,11 @@ impl Display for DisplayGenericContext<'_> {
}
f.write_char('[')?;
for (idx, var) in non_implicit_variables.iter().enumerate() {
for (idx, bound_typevar) in non_implicit_variables.iter().enumerate() {
if idx > 0 {
f.write_str(", ")?;
}
f.write_str(var.name(self.db))?;
match var.bound_or_constraints(self.db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
write!(f, ": {}", bound.display(self.db))?;
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
f.write_str(": (")?;
for (idx, constraint) in constraints.iter(self.db).enumerate() {
if idx > 0 {
f.write_str(", ")?;
}
constraint.display(self.db).fmt(f)?;
}
f.write_char(')')?;
}
None => {}
}
if let Some(default_type) = var.default_ty(self.db) {
write!(f, " = {}", default_type.display(self.db))?;
}
bound_typevar.display(self.db).fmt(f)?;
}
f.write_char(']')
}
@ -497,7 +553,6 @@ impl<'db> Specialization<'db> {
tuple_specialization: TupleSpecialization,
) -> DisplaySpecialization<'db> {
DisplaySpecialization {
typevars: self.generic_context(db).variables(db),
types: self.types(db),
db,
tuple_specialization,
@ -506,7 +561,6 @@ impl<'db> Specialization<'db> {
}
pub struct DisplaySpecialization<'db> {
typevars: &'db FxOrderSet<TypeVarInstance<'db>>,
types: &'db [Type<'db>],
db: &'db dyn Db,
tuple_specialization: TupleSpecialization,
@ -515,7 +569,7 @@ pub struct DisplaySpecialization<'db> {
impl Display for DisplaySpecialization<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_char('[')?;
for (idx, (_, ty)) in self.typevars.iter().zip(self.types).enumerate() {
for (idx, ty) in self.types.iter().enumerate() {
if idx > 0 {
f.write_str(", ")?;
}
@ -1016,20 +1070,22 @@ impl Display for DisplayTypeArray<'_, '_> {
impl<'db> StringLiteralType<'db> {
fn display(&'db self, db: &'db dyn Db) -> DisplayStringLiteralType<'db> {
DisplayStringLiteralType { db, ty: self }
display_quoted_string(self.value(db))
}
}
fn display_quoted_string(string: &str) -> DisplayStringLiteralType<'_> {
DisplayStringLiteralType { string }
}
struct DisplayStringLiteralType<'db> {
ty: &'db StringLiteralType<'db>,
db: &'db dyn Db,
string: &'db str,
}
impl Display for DisplayStringLiteralType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let value = self.ty.value(self.db);
f.write_char('"')?;
for ch in value.chars() {
for ch in self.string.chars() {
match ch {
// `escape_debug` will escape even single quotes, which is not necessary for our
// use case as we are already using double quotes to wrap the string.

View file

@ -76,9 +76,9 @@ use crate::types::narrow::ClassInfoConstraintFunction;
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::visitor::any_over_type;
use crate::types::{
BoundMethodType, CallableType, ClassBase, ClassLiteral, ClassType, DeprecatedInstance,
DynamicType, KnownClass, Truthiness, Type, TypeMapping, TypeRelation, TypeTransformer,
TypeVarInstance, UnionBuilder, all_members, walk_type_mapping,
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
DeprecatedInstance, DynamicType, KnownClass, Truthiness, Type, TypeMapping, TypeRelation,
TypeTransformer, UnionBuilder, all_members, walk_type_mapping,
};
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
@ -338,7 +338,7 @@ impl<'db> OverloadLiteral<'db> {
let definition = self.definition(db);
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, type_params)
GenericContext::from_type_params(db, index, definition, type_params)
});
let index = semantic_index(db, scope.file(db));
@ -575,6 +575,30 @@ impl<'db> FunctionLiteral<'db> {
}))
}
/// Typed externally-visible signature of the last overload or implementation of this function.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
fn last_definition_signature<'a>(
self,
db: &'db dyn Db,
type_mappings: &'a [TypeMapping<'a, 'db>],
) -> Signature<'db>
where
'db: 'a,
{
let inherited_generic_context = self.inherited_generic_context(db);
type_mappings.iter().fold(
self.last_definition(db)
.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
)
}
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
let context = self
.inherited_generic_context(db)
@ -795,6 +819,26 @@ impl<'db> FunctionType<'db> {
self.literal(db).signature(db, self.type_mappings(db))
}
/// Typed externally-visible signature of the last overload or implementation of this function.
///
/// ## Why is this a salsa query?
///
/// This is a salsa query to short-circuit the invalidation
/// when the function's AST node changes.
///
/// Were this not a salsa query, then the calling query
/// would depend on the function's AST and rerun for every change in that file.
#[salsa::tracked(
returns(ref),
cycle_fn=last_definition_signature_cycle_recover,
cycle_initial=last_definition_signature_cycle_initial,
heap_size=ruff_memory_usage::heap_size,
)]
pub(crate) fn last_definition_signature(self, db: &'db dyn Db) -> Signature<'db> {
self.literal(db)
.last_definition_signature(db, self.type_mappings(db))
}
/// Convert the `FunctionType` into a [`CallableType`].
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> CallableType<'db> {
CallableType::new(db, self.signature(db), false)
@ -861,11 +905,12 @@ impl<'db> FunctionType<'db> {
pub(crate) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
let signatures = self.signature(db);
for signature in &signatures.overloads {
signature.find_legacy_typevars(db, typevars);
signature.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -1000,6 +1045,22 @@ fn signature_cycle_initial<'db>(
CallableSignature::single(Signature::bottom(db))
}
fn last_definition_signature_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &Signature<'db>,
_count: u32,
_function: FunctionType<'db>,
) -> salsa::CycleRecoveryAction<Signature<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn last_definition_signature_cycle_initial<'db>(
db: &'db dyn Db,
_function: FunctionType<'db>,
) -> Signature<'db> {
Signature::bottom(db)
}
/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might
/// have special behavior.
#[derive(

View file

@ -1,12 +1,12 @@
use std::borrow::Cow;
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_db::parsed::ParsedModuleRef;
use ruff_python_ast as ast;
use rustc_hash::FxHashMap;
use crate::semantic_index::SemanticIndex;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind};
use crate::semantic_index::{SemanticIndex, semantic_index};
use crate::types::class::ClassType;
use crate::types::class_base::ClassBase;
use crate::types::infer::infer_definition_types;
@ -14,8 +14,9 @@ use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints,
TypeVarInstance, TypeVarVariance, UnionType, binding_type, declaration_type,
BoundTypeVarInstance, KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer,
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
declaration_type,
};
use crate::{Db, FxOrderSet};
@ -31,64 +32,51 @@ fn enclosing_generic_contexts<'db>(
.ancestor_scopes(scope)
.filter_map(|(_, ancestor_scope)| match ancestor_scope.node() {
NodeWithScopeKind::Class(class) => {
binding_type(db, index.expect_single_definition(class.node(module)))
let definition = index.expect_single_definition(class.node(module));
binding_type(db, definition)
.into_class_literal()?
.generic_context(db)
}
NodeWithScopeKind::Function(function) => {
infer_definition_types(db, index.expect_single_definition(function.node(module)))
let definition = index.expect_single_definition(function.node(module));
infer_definition_types(db, definition)
.undecorated_type()
.expect("function should have undecorated type")
.into_function_literal()?
.signature(db)
.iter()
.last()
.expect("function should have at least one overload")
.last_definition_signature(db)
.generic_context
}
_ => None,
})
}
/// Returns the legacy typevars that have been bound in the given scope or any enclosing scope.
fn bound_legacy_typevars<'db>(
db: &'db dyn Db,
module: &ParsedModuleRef,
index: &'db SemanticIndex<'db>,
scope: FileScopeId,
) -> impl Iterator<Item = TypeVarInstance<'db>> {
enclosing_generic_contexts(db, module, index, scope)
.flat_map(|generic_context| generic_context.variables(db).iter().copied())
.filter(|typevar| typevar.is_legacy(db))
}
/// Binds an unbound legacy typevar.
/// Binds an unbound typevar.
///
/// When a legacy typevar is first created, we will have a [`TypeVarInstance`] which does not have
/// an associated binding context. When the typevar is used in a generic class or function, we
/// "bind" it, adding the [`Definition`] of the generic class or function as its "binding context".
/// When a typevar is first created, we will have a [`TypeVarInstance`] which does not have an
/// associated binding context. When the typevar is used in a generic class or function, we "bind"
/// it, adding the [`Definition`] of the generic class or function as its "binding context".
///
/// When an expression resolves to a legacy typevar, our inferred type will refer to the unbound
/// When an expression resolves to a typevar, our inferred type will refer to the unbound
/// [`TypeVarInstance`] from when the typevar was first created. This function walks the scopes
/// that enclosing the expression, looking for the innermost binding context that binds the
/// typevar.
///
/// If no enclosing scope has already bound the typevar, we might be in a syntactic position that
/// is about to bind it (indicated by a non-`None` `legacy_typevar_binding_context`), in which case
/// we bind the typevar with that new binding context.
pub(crate) fn bind_legacy_typevar<'db>(
/// is about to bind it (indicated by a non-`None` `typevar_binding_context`), in which case we
/// bind the typevar with that new binding context.
pub(crate) fn bind_typevar<'db>(
db: &'db dyn Db,
module: &ParsedModuleRef,
index: &SemanticIndex<'db>,
containing_scope: FileScopeId,
legacy_typevar_binding_context: Option<Definition<'db>>,
typevar_binding_context: Option<Definition<'db>>,
typevar: TypeVarInstance<'db>,
) -> Option<TypeVarInstance<'db>> {
) -> Option<BoundTypeVarInstance<'db>> {
enclosing_generic_contexts(db, module, index, containing_scope)
.find_map(|enclosing_context| enclosing_context.binds_legacy_typevar(db, typevar))
.find_map(|enclosing_context| enclosing_context.binds_typevar(db, typevar))
.or_else(|| {
legacy_typevar_binding_context.map(|legacy_typevar_binding_context| {
typevar.with_binding_context(db, legacy_typevar_binding_context)
typevar_binding_context.map(|typevar_binding_context| {
typevar.with_binding_context(db, typevar_binding_context)
})
})
}
@ -105,7 +93,7 @@ pub(crate) fn bind_legacy_typevar<'db>(
#[derive(PartialOrd, Ord)]
pub struct GenericContext<'db> {
#[returns(ref)]
pub(crate) variables: FxOrderSet<TypeVarInstance<'db>>,
pub(crate) variables: FxOrderSet<BoundTypeVarInstance<'db>>,
}
pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
@ -113,8 +101,8 @@ pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?S
context: GenericContext<'db>,
visitor: &mut V,
) {
for typevar in context.variables(db) {
visitor.visit_type_var_type(db, *typevar);
for bound_typevar in context.variables(db) {
visitor.visit_bound_type_var_type(db, *bound_typevar);
}
}
@ -126,11 +114,14 @@ impl<'db> GenericContext<'db> {
pub(crate) fn from_type_params(
db: &'db dyn Db,
index: &'db SemanticIndex<'db>,
binding_context: Definition<'db>,
type_params_node: &ast::TypeParams,
) -> Self {
let variables: FxOrderSet<_> = type_params_node
.iter()
.filter_map(|type_param| Self::variable_from_type_param(db, index, type_param))
.filter_map(|type_param| {
Self::variable_from_type_param(db, index, binding_context, type_param)
})
.collect();
Self::new(db, variables)
}
@ -138,8 +129,9 @@ impl<'db> GenericContext<'db> {
fn variable_from_type_param(
db: &'db dyn Db,
index: &'db SemanticIndex<'db>,
binding_context: Definition<'db>,
type_param_node: &ast::TypeParam,
) -> Option<TypeVarInstance<'db>> {
) -> Option<BoundTypeVarInstance<'db>> {
match type_param_node {
ast::TypeParam::TypeVar(node) => {
let definition = index.expect_single_definition(node);
@ -148,7 +140,7 @@ impl<'db> GenericContext<'db> {
else {
return None;
};
Some(typevar)
Some(typevar.with_binding_context(db, binding_context))
}
// TODO: Support these!
ast::TypeParam::ParamSpec(_) => None,
@ -168,23 +160,14 @@ impl<'db> GenericContext<'db> {
let mut variables = FxOrderSet::default();
for param in parameters {
if let Some(ty) = param.annotated_type() {
ty.find_legacy_typevars(db, &mut variables);
ty.find_legacy_typevars(db, Some(definition), &mut variables);
}
if let Some(ty) = param.default_type() {
ty.find_legacy_typevars(db, &mut variables);
ty.find_legacy_typevars(db, Some(definition), &mut variables);
}
}
if let Some(ty) = return_type {
ty.find_legacy_typevars(db, &mut variables);
}
// Then remove any that were bound in enclosing scopes.
let file = definition.file(db);
let module = parsed_module(db, file).load(db);
let index = semantic_index(db, file);
let containing_scope = definition.file_scope(db);
for typevar in bound_legacy_typevars(db, &module, index, containing_scope) {
variables.remove(&typevar);
ty.find_legacy_typevars(db, Some(definition), &mut variables);
}
if variables.is_empty() {
@ -201,7 +184,7 @@ impl<'db> GenericContext<'db> {
) -> Option<Self> {
let mut variables = FxOrderSet::default();
for base in bases {
base.find_legacy_typevars(db, &mut variables);
base.find_legacy_typevars(db, None, &mut variables);
}
if variables.is_empty() {
return None;
@ -209,19 +192,6 @@ impl<'db> GenericContext<'db> {
Some(Self::new(db, variables))
}
pub(crate) fn with_binding_context(
self,
db: &'db dyn Db,
binding_context: Definition<'db>,
) -> Self {
let variables: FxOrderSet<_> = self
.variables(db)
.iter()
.map(|typevar| typevar.with_binding_context(db, binding_context))
.collect();
Self::new(db, variables)
}
pub(crate) fn len(self, db: &'db dyn Db) -> usize {
self.variables(db).len()
}
@ -235,7 +205,11 @@ impl<'db> GenericContext<'db> {
Signature::new(parameters, None)
}
fn parameter_from_typevar(db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Parameter<'db> {
fn parameter_from_typevar(
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
) -> Parameter<'db> {
let typevar = bound_typevar.typevar(db);
let mut parameter = Parameter::positional_only(Some(typevar.name(db).clone()));
match typevar.bound_or_constraints(db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
@ -250,7 +224,7 @@ impl<'db> GenericContext<'db> {
}
None => {}
}
if let Some(default_ty) = typevar.default_ty(db) {
if let Some(default_ty) = bound_typevar.default_ty(db) {
parameter = parameter.with_default_type(default_ty);
}
parameter
@ -288,19 +262,14 @@ impl<'db> GenericContext<'db> {
self.variables(db).is_subset(other.variables(db))
}
pub(crate) fn binds_legacy_typevar(
pub(crate) fn binds_typevar(
self,
db: &'db dyn Db,
typevar: TypeVarInstance<'db>,
) -> Option<TypeVarInstance<'db>> {
assert!(typevar.is_legacy(db) || typevar.is_implicit(db));
let typevar_def = typevar.definition(db);
) -> Option<BoundTypeVarInstance<'db>> {
self.variables(db)
.iter()
.find(|self_typevar| {
(self_typevar.is_legacy(db) || self_typevar.is_implicit(db))
&& self_typevar.definition(db) == typevar_def
})
.find(|self_bound_typevar| self_bound_typevar.typevar(db) == typevar)
.copied()
}
@ -380,7 +349,7 @@ impl<'db> GenericContext<'db> {
let variables: FxOrderSet<_> = self
.variables(db)
.iter()
.map(|ty| ty.normalized_impl(db, visitor))
.map(|bound_typevar| bound_typevar.normalized_impl(db, visitor))
.collect();
Self::new(db, variables)
}
@ -454,11 +423,15 @@ impl<'db> Specialization<'db> {
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
/// mapping.
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
pub(crate) fn get(
self,
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
) -> Option<Type<'db>> {
let index = self
.generic_context(db)
.variables(db)
.get_index_of(&typevar)?;
.get_index_of(&bound_typevar)?;
self.types(db).get(index).copied()
}
@ -556,8 +529,8 @@ impl<'db> Specialization<'db> {
.variables(db)
.into_iter()
.zip(self.types(db))
.map(|(typevar, vartype)| {
let variance = match typevar.variance(db) {
.map(|(bound_typevar, vartype)| {
let variance = match bound_typevar.typevar(db).variance(db) {
TypeVarVariance::Invariant => TypeVarVariance::Invariant,
TypeVarVariance::Covariant => variance,
TypeVarVariance::Contravariant => variance.flip(),
@ -589,7 +562,7 @@ impl<'db> Specialization<'db> {
return self_tuple.has_relation_to(db, other_tuple, relation);
}
for ((typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
.zip(self.types(db))
.zip(other.types(db))
{
@ -606,7 +579,7 @@ impl<'db> Specialization<'db> {
// - contravariant: verify that other_type <: self_type
// - invariant: verify that self_type <: other_type AND other_type <: self_type
// - bivariant: skip, can't make subtyping/assignability false
let compatible = match typevar.variance(db) {
let compatible = match bound_typevar.typevar(db).variance(db) {
TypeVarVariance::Invariant => match relation {
TypeRelation::Subtyping => self_type.is_equivalent_to(db, *other_type),
TypeRelation::Assignability => {
@ -634,7 +607,7 @@ impl<'db> Specialization<'db> {
return false;
}
for ((typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
.zip(self.types(db))
.zip(other.types(db))
{
@ -644,7 +617,7 @@ impl<'db> Specialization<'db> {
// - contravariant: verify that other_type == self_type
// - invariant: verify that self_type == other_type
// - bivariant: skip, can't make equivalence false
let compatible = match typevar.variance(db) {
let compatible = match bound_typevar.typevar(db).variance(db) {
TypeVarVariance::Invariant
| TypeVarVariance::Covariant
| TypeVarVariance::Contravariant => self_type.is_equivalent_to(db, *other_type),
@ -661,10 +634,11 @@ impl<'db> Specialization<'db> {
pub(crate) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
for ty in self.types(db) {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
}
}
@ -693,8 +667,15 @@ pub(super) fn walk_partial_specialization<'db, V: super::visitor::TypeVisitor<'d
impl<'db> PartialSpecialization<'_, 'db> {
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
/// mapping.
pub(crate) fn get(&self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
let index = self.generic_context.variables(db).get_index_of(&typevar)?;
pub(crate) fn get(
&self,
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
) -> Option<Type<'db>> {
let index = self
.generic_context
.variables(db)
.get_index_of(&bound_typevar)?;
self.types.get(index).copied()
}
@ -728,7 +709,7 @@ impl<'db> PartialSpecialization<'_, 'db> {
/// specialization of a generic function.
pub(crate) struct SpecializationBuilder<'db> {
db: &'db dyn Db,
types: FxHashMap<TypeVarInstance<'db>, Type<'db>>,
types: FxHashMap<BoundTypeVarInstance<'db>, Type<'db>>,
}
impl<'db> SpecializationBuilder<'db> {
@ -754,9 +735,9 @@ impl<'db> SpecializationBuilder<'db> {
Specialization::new(self.db, generic_context, types, None)
}
fn add_type_mapping(&mut self, typevar: TypeVarInstance<'db>, ty: Type<'db>) {
fn add_type_mapping(&mut self, bound_typevar: BoundTypeVarInstance<'db>, ty: Type<'db>) {
self.types
.entry(typevar)
.entry(bound_typevar)
.and_modify(|existing| {
*existing = UnionType::from_elements(self.db, [*existing, ty]);
})
@ -803,14 +784,14 @@ impl<'db> SpecializationBuilder<'db> {
// and add a mapping between that typevar and the actual type. (Note that we've
// already handled above the case where the actual is assignable to a _non-typevar_
// union element.)
let mut typevars = formal.iter(self.db).filter_map(|ty| match ty {
Type::TypeVar(typevar) => Some(*typevar),
let mut bound_typevars = formal.iter(self.db).filter_map(|ty| match ty {
Type::TypeVar(bound_typevar) => Some(*bound_typevar),
_ => None,
});
let typevar = typevars.next();
let additional_typevars = typevars.next();
if let (Some(typevar), None) = (typevar, additional_typevars) {
self.add_type_mapping(typevar, actual);
let bound_typevar = bound_typevars.next();
let additional_bound_typevars = bound_typevars.next();
if let (Some(bound_typevar), None) = (bound_typevar, additional_bound_typevars) {
self.add_type_mapping(bound_typevar, actual);
}
}
@ -824,31 +805,31 @@ impl<'db> SpecializationBuilder<'db> {
}
}
(Type::TypeVar(typevar), ty) | (ty, Type::TypeVar(typevar)) => {
match typevar.bound_or_constraints(self.db) {
(Type::TypeVar(bound_typevar), ty) | (ty, Type::TypeVar(bound_typevar)) => {
match bound_typevar.typevar(self.db).bound_or_constraints(self.db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
if !ty.is_assignable_to(self.db, bound) {
return Err(SpecializationError::MismatchedBound {
typevar,
bound_typevar,
argument: ty,
});
}
self.add_type_mapping(typevar, ty);
self.add_type_mapping(bound_typevar, ty);
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
for constraint in constraints.iter(self.db) {
if ty.is_assignable_to(self.db, *constraint) {
self.add_type_mapping(typevar, *constraint);
self.add_type_mapping(bound_typevar, *constraint);
return Ok(());
}
}
return Err(SpecializationError::MismatchedConstraint {
typevar,
bound_typevar,
argument: ty,
});
}
_ => {
self.add_type_mapping(typevar, ty);
self.add_type_mapping(bound_typevar, ty);
}
}
}
@ -920,20 +901,20 @@ impl<'db> SpecializationBuilder<'db> {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SpecializationError<'db> {
MismatchedBound {
typevar: TypeVarInstance<'db>,
bound_typevar: BoundTypeVarInstance<'db>,
argument: Type<'db>,
},
MismatchedConstraint {
typevar: TypeVarInstance<'db>,
bound_typevar: BoundTypeVarInstance<'db>,
argument: Type<'db>,
},
}
impl<'db> SpecializationError<'db> {
pub(crate) fn typevar(&self) -> TypeVarInstance<'db> {
pub(crate) fn bound_typevar(&self) -> BoundTypeVarInstance<'db> {
match self {
Self::MismatchedBound { typevar, .. } => *typevar,
Self::MismatchedConstraint { typevar, .. } => *typevar,
Self::MismatchedBound { bound_typevar, .. } => *bound_typevar,
Self::MismatchedConstraint { bound_typevar, .. } => *bound_typevar,
}
}

View file

@ -110,7 +110,7 @@ use crate::types::enums::is_enum_class;
use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
};
use crate::types::generics::{GenericContext, bind_legacy_typevar};
use crate::types::generics::{GenericContext, bind_typevar};
use crate::types::mro::MroErrorKind;
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::tuple::{TupleSpec, TupleType};
@ -816,8 +816,8 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
/// [`check_overloaded_functions`]: TypeInferenceBuilder::check_overloaded_functions
called_functions: FxHashSet<FunctionType<'db>>,
/// Whether we are in a context that binds unbound legacy typevars.
legacy_typevar_binding_context: Option<Definition<'db>>,
/// Whether we are in a context that binds unbound typevars.
typevar_binding_context: Option<Definition<'db>>,
/// The deferred state of inferring types of certain expressions within the region.
///
@ -866,7 +866,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
expressions: FxHashMap::default(),
bindings: VecMap::default(),
declarations: VecMap::default(),
legacy_typevar_binding_context: None,
typevar_binding_context: None,
deferred: VecSet::default(),
undecorated_type: None,
cycle_fallback: false,
@ -2242,12 +2242,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.as_deref()
.expect("class type params scope without type params");
let binding_context = self.index.expect_single_definition(class);
let previous_typevar_binding_context =
self.typevar_binding_context.replace(binding_context);
self.infer_type_parameters(type_params);
if let Some(arguments) = class.arguments.as_deref() {
// Note: We do not install a new `legacy_typevar_binding_context`; since this class has
// PEP 695 typevars, it should not also bind any legacy typevars via inheriting from
// `typing.Generic` or `typing.Protocol`.
let mut call_arguments =
CallArguments::from_arguments(self.db(), arguments, |argument, splatted_value| {
let ty = self.infer_expression(splatted_value);
@ -2257,6 +2258,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()];
self.infer_argument_types(arguments, &mut call_arguments, &argument_forms);
}
self.typevar_binding_context = previous_typevar_binding_context;
}
fn infer_class_body(&mut self, class: &ast::StmtClassDef) {
@ -2269,15 +2272,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.as_deref()
.expect("function type params scope without type params");
// Note: We do not install a new `legacy_typevar_binding_context`; since this function has
// PEP 695 typevars, it should not also bind any legacy typevars by referencing them in its
// parameter or return type annotations.
let binding_context = self.index.expect_single_definition(function);
let previous_typevar_binding_context =
self.typevar_binding_context.replace(binding_context);
self.infer_return_type_annotation(
function.returns.as_deref(),
DeferredExpressionState::None,
);
self.infer_type_parameters(type_params);
self.infer_parameters(&function.parameters);
self.typevar_binding_context = previous_typevar_binding_context;
}
fn infer_type_alias_type_params(&mut self, type_alias: &ast::StmtTypeAlias) {
@ -2286,7 +2290,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.as_ref()
.expect("type alias type params scope without type params");
let binding_context = self.index.expect_single_definition(type_alias);
let previous_typevar_binding_context =
self.typevar_binding_context.replace(binding_context);
self.infer_type_parameters(type_params);
self.typevar_binding_context = previous_typevar_binding_context;
}
fn infer_type_alias(&mut self, type_alias: &ast::StmtTypeAlias) {
@ -2634,14 +2642,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if self.defer_annotations() {
self.deferred.insert(definition);
} else {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
let previous_typevar_binding_context =
self.typevar_binding_context.replace(definition);
self.infer_return_type_annotation(
returns.as_deref(),
DeferredExpressionState::None,
);
self.infer_parameters(parameters);
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
self.typevar_binding_context = previous_typevar_binding_context;
}
}
@ -3054,12 +3062,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if self.in_stub() || class_node.bases().iter().any(contains_string_literal) {
self.deferred.insert(definition);
} else {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
let previous_typevar_binding_context =
self.typevar_binding_context.replace(definition);
for base in class_node.bases() {
self.infer_expression(base);
}
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
self.typevar_binding_context = previous_typevar_binding_context;
}
}
}
@ -3069,23 +3077,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
definition: Definition<'db>,
function: &ast::StmtFunctionDef,
) {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
self.infer_return_type_annotation(
function.returns.as_deref(),
DeferredExpressionState::Deferred,
);
self.infer_parameters(function.parameters.as_ref());
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
self.typevar_binding_context = previous_typevar_binding_context;
}
fn infer_class_deferred(&mut self, definition: Definition<'db>, class: &ast::StmtClassDef) {
let previous_legacy_typevar_binding_context =
self.legacy_typevar_binding_context.replace(definition);
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
for base in class.bases() {
self.infer_expression(base);
}
self.legacy_typevar_binding_context = previous_legacy_typevar_binding_context;
self.typevar_binding_context = previous_typevar_binding_context;
}
fn infer_type_alias_definition(
@ -3394,27 +3400,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
default,
} = node;
// Find the binding context for the PEP 695 typevars defined in this scope. The typevar
// scope should have a child containing the class, function, or type alias definition. Find
// that scope and use its definition as the binding context.
let typevar_scope = definition.file_scope(self.db());
let child_scopes = self.index.child_scopes(typevar_scope);
let binding_context = child_scopes
.filter_map(|(_, binding_scope)| match binding_scope.node() {
NodeWithScopeKind::Class(class) => {
Some(DefinitionNodeKey::from(class.node(self.context.module())))
}
NodeWithScopeKind::Function(function) => Some(DefinitionNodeKey::from(
function.node(self.context.module()),
)),
NodeWithScopeKind::TypeAlias(alias) => {
Some(DefinitionNodeKey::from(alias.node(self.context.module())))
}
_ => None,
})
.map(|key| self.index.expect_single_definition(key))
.next();
let bound_or_constraint = match bound.as_deref() {
Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => {
if elts.len() < 2 {
@ -3459,7 +3444,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.db(),
&name.id,
Some(definition),
binding_context,
bound_or_constraint,
TypeVarVariance::Invariant, // TODO: infer this
default_ty,
@ -6520,43 +6504,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));
resolved
.map_type(|ty| match ty {
// If the expression resolves to a legacy typevar, we will have the TypeVarInstance
// that was created when the typevar was created, which will not have an associated
// binding context. If this expression appears inside of a generic context that
// binds that typevar, we need to update the TypeVarInstance to include that
// binding context. To do that, we walk the enclosing scopes, looking for the
// nearest generic context that binds the typevar.
//
// If the legacy typevar is still unbound after that search, and we are in a
// context that binds unbound legacy typevars (i.e., the signature of a generic
// function), bind it with that context.
Type::TypeVar(typevar) if typevar.is_legacy(self.db()) => bind_legacy_typevar(
self.db(),
self.context.module(),
self.index,
self.scope().file_scope_id(self.db()),
self.legacy_typevar_binding_context,
typevar,
)
.map(Type::TypeVar)
.unwrap_or(ty),
Type::KnownInstance(KnownInstanceType::TypeVar(typevar))
if typevar.is_legacy(self.db()) =>
{
bind_legacy_typevar(
self.db(),
self.context.module(),
self.index,
self.scope().file_scope_id(self.db()),
self.legacy_typevar_binding_context,
typevar,
)
.map(|typevar| Type::KnownInstance(KnownInstanceType::TypeVar(typevar)))
.unwrap_or(ty)
}
_ => ty,
})
// Not found in the module's explicitly declared global symbols?
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
// These are looked up as attributes on `types.ModuleType`.
@ -9069,7 +9016,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let typevars: Result<FxOrderSet<_>, GenericContextError> = typevars
.iter()
.map(|typevar| match typevar {
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => Ok(*typevar),
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => bind_typevar(
self.db(),
self.module(),
self.index,
self.scope().file_scope_id(self.db()),
self.typevar_binding_context,
*typevar,
)
.ok_or(GenericContextError::InvalidArgument),
Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported),
Type::NominalInstance(NominalInstanceType { class, .. })
if matches!(
@ -9167,7 +9122,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
undecorated_type: _,
// builder only state
legacy_typevar_binding_context: _,
typevar_binding_context: _,
deferred_state: _,
called_functions: _,
index: _,
@ -9228,7 +9183,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
undecorated_type,
// builder only state
legacy_typevar_binding_context: _,
typevar_binding_context: _,
deferred_state: _,
called_functions: _,
index: _,
@ -9298,7 +9253,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
undecorated_type: _,
// Builder only state
legacy_typevar_binding_context: _,
typevar_binding_context: _,
deferred_state: _,
called_functions: _,
index: _,
@ -9407,7 +9362,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
.in_type_expression(
self.db(),
self.scope(),
self.legacy_typevar_binding_context,
self.typevar_binding_context,
)
.unwrap_or_else(|error| {
error.into_fallback_type(
@ -9627,11 +9582,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
ast::Expr::Name(name) => match name.ctx {
ast::ExprContext::Load => self
.infer_name_expression(name)
.in_type_expression(
self.db(),
self.scope(),
self.legacy_typevar_binding_context,
)
.in_type_expression(self.db(), self.scope(), self.typevar_binding_context)
.unwrap_or_else(|error| {
error.into_fallback_type(
&self.context,
@ -9648,11 +9599,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx {
ast::ExprContext::Load => self
.infer_attribute_expression(attribute_expression)
.in_type_expression(
self.db(),
self.scope(),
self.legacy_typevar_binding_context,
)
.in_type_expression(self.db(), self.scope(), self.typevar_binding_context)
.unwrap_or_else(|error| {
error.into_fallback_type(
&self.context,
@ -10341,7 +10288,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
.in_type_expression(
self.db(),
self.scope(),
self.legacy_typevar_binding_context,
self.typevar_binding_context,
)
.unwrap_or(Type::unknown())
}
@ -11555,11 +11502,12 @@ mod tests {
.unwrap();
let check_typevar = |var: &'static str,
display: &'static str,
upper_bound: Option<&'static str>,
constraints: Option<&[&'static str]>,
default: Option<&'static str>| {
let var_ty = get_symbol(&db, "src/a.py", &["f"], var).expect_type();
assert_eq!(var_ty.display(&db).to_string(), "typing.TypeVar");
assert_eq!(var_ty.display(&db).to_string(), display);
let expected_name_ty = format!(r#"Literal["{var}"]"#);
let name_ty = var_ty.member(&db, "__name__").place.expect_type();
@ -11593,14 +11541,32 @@ mod tests {
);
};
check_typevar("T", None, None, None);
check_typevar("U", Some("A"), None, None);
check_typevar("V", None, Some(&["A", "B"]), None);
check_typevar("W", None, None, Some("A"));
check_typevar("X", Some("A"), None, Some("A1"));
check_typevar("T", "typing.TypeVar(\"T\")", None, None, None);
check_typevar("U", "typing.TypeVar(\"U\", bound=A)", Some("A"), None, None);
check_typevar(
"V",
"typing.TypeVar(\"V\", A, B)",
None,
Some(&["A", "B"]),
None,
);
check_typevar(
"W",
"typing.TypeVar(\"W\", default=A)",
None,
None,
Some("A"),
);
check_typevar(
"X",
"typing.TypeVar(\"X\", bound=A, default=A1)",
Some("A"),
None,
Some("A1"),
);
// a typevar with less than two constraints is treated as unconstrained
check_typevar("Y", None, None, None);
check_typevar("Y", "typing.TypeVar(\"Y\")", None, None, None);
}
/// Test that a symbol known to be unbound in a scope does not still trigger cycle-causing

View file

@ -3,15 +3,14 @@
use std::marker::PhantomData;
use super::protocol_class::ProtocolInterface;
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
use super::{BoundTypeVarInstance, ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
use crate::place::PlaceAndQualifiers;
use crate::semantic_index::definition::Definition;
use crate::types::cyclic::PairVisitor;
use crate::types::enums::is_single_member_enum;
use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::TupleType;
use crate::types::{
DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypedDictType,
};
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypedDictType};
use crate::{Db, FxOrderSet};
pub(super) use synthesized_protocol::SynthesizedProtocolType;
@ -161,9 +160,11 @@ impl<'db> NominalInstanceType<'db> {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
self.class.find_legacy_typevars(db, typevars);
self.class
.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -342,14 +343,15 @@ impl<'db> ProtocolInstanceType<'db> {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
match self.inner {
Protocol::FromClass(class) => {
class.find_legacy_typevars(db, typevars);
class.find_legacy_typevars(db, binding_context, typevars);
}
Protocol::Synthesized(synthesized) => {
synthesized.find_legacy_typevars(db, typevars);
synthesized.find_legacy_typevars(db, binding_context, typevars);
}
}
}
@ -385,8 +387,9 @@ impl<'db> Protocol<'db> {
}
mod synthesized_protocol {
use crate::semantic_index::definition::Definition;
use crate::types::protocol_class::ProtocolInterface;
use crate::types::{TypeMapping, TypeTransformer, TypeVarInstance, TypeVarVariance};
use crate::types::{BoundTypeVarInstance, TypeMapping, TypeTransformer, TypeVarVariance};
use crate::{Db, FxOrderSet};
/// A "synthesized" protocol type that is dissociated from a class definition in source code.
@ -427,9 +430,10 @@ mod synthesized_protocol {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
self.0.find_legacy_typevars(db, typevars);
self.0.find_legacy_typevars(db, binding_context, typevars);
}
pub(in crate::types) fn interface(self) -> ProtocolInterface<'db> {

View file

@ -222,7 +222,10 @@ impl ClassInfoConstraintFunction {
Type::Union(union) => {
union.try_map(db, |element| self.generate_constraint(db, *element))
}
Type::TypeVar(type_var) => match type_var.bound_or_constraints(db)? {
Type::TypeVar(bound_typevar) => match bound_typevar
.typevar(db)
.bound_or_constraints(db)?
{
TypeVarBoundOrConstraints::UpperBound(bound) => self.generate_constraint(db, bound),
TypeVarBoundOrConstraints::Constraints(constraints) => {
self.generate_constraint(db, Type::Union(constraints))

View file

@ -10,10 +10,11 @@ use crate::semantic_index::place_table;
use crate::{
Db, FxOrderSet,
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
semantic_index::use_def_map,
semantic_index::{definition::Definition, use_def_map},
types::{
CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature,
Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance,
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, KnownFunction,
PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, TypeRelation,
TypeTransformer,
cyclic::PairVisitor,
signatures::{Parameter, Parameters},
},
@ -210,10 +211,11 @@ impl<'db> ProtocolInterface<'db> {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
for data in self.inner(db).values() {
data.find_legacy_typevars(db, typevars);
data.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -271,9 +273,11 @@ impl<'db> ProtocolMemberData<'db> {
fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
self.kind.find_legacy_typevars(db, typevars);
self.kind
.find_legacy_typevars(db, binding_context, typevars);
}
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
@ -358,12 +362,19 @@ impl<'db> ProtocolMemberKind<'db> {
fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
match self {
ProtocolMemberKind::Method(callable) => callable.find_legacy_typevars(db, typevars),
ProtocolMemberKind::Property(property) => property.find_legacy_typevars(db, typevars),
ProtocolMemberKind::Other(ty) => ty.find_legacy_typevars(db, typevars),
ProtocolMemberKind::Method(callable) => {
callable.find_legacy_typevars(db, binding_context, typevars);
}
ProtocolMemberKind::Property(property) => {
property.find_legacy_typevars(db, binding_context, typevars);
}
ProtocolMemberKind::Other(ty) => {
ty.find_legacy_typevars(db, binding_context, typevars);
}
}
}

View file

@ -18,7 +18,7 @@ use smallvec::{SmallVec, smallvec_inline};
use super::{DynamicType, Type, TypeTransformer, TypeVarVariance, definition_expression_type};
use crate::semantic_index::definition::Definition;
use crate::types::generics::{GenericContext, walk_generic_context};
use crate::types::{KnownClass, TypeMapping, TypeRelation, TypeVarInstance, todo_type};
use crate::types::{BoundTypeVarInstance, KnownClass, TypeMapping, TypeRelation, todo_type};
use crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name};
@ -88,10 +88,11 @@ impl<'db> CallableSignature<'db> {
pub(crate) fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
for signature in &self.overloads {
signature.find_legacy_typevars(db, typevars);
signature.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -428,18 +429,19 @@ impl<'db> Signature<'db> {
pub(crate) fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
for param in &self.parameters {
if let Some(ty) = param.annotated_type() {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
if let Some(ty) = param.default_type() {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
}
if let Some(ty) = self.return_ty {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
}

View file

@ -1,9 +1,10 @@
use ruff_python_ast::name::Name;
use crate::place::PlaceAndQualifiers;
use crate::semantic_index::definition::Definition;
use crate::types::{
ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation,
TypeTransformer, TypeVarInstance,
BindingContext, BoundTypeVarInstance, ClassType, DynamicType, KnownClass, MemberLookupPolicy,
Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance,
};
use crate::{Db, FxOrderSet};
@ -89,17 +90,20 @@ 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(TypeVarInstance::new(
Type::TypeVar(BoundTypeVarInstance::new(
db,
Name::new_static("T_all"),
None,
None,
Some(TypeVarBoundOrConstraints::UpperBound(
KnownClass::Type.to_instance(db),
)),
variance,
None,
TypeVarKind::Pep695,
TypeVarInstance::new(
db,
Name::new_static("T_all"),
None,
Some(TypeVarBoundOrConstraints::UpperBound(
KnownClass::Type.to_instance(db),
)),
variance,
None,
TypeVarKind::Pep695,
),
BindingContext::Synthetic,
))
}
TypeVarVariance::Bivariant => unreachable!(),
@ -124,11 +128,12 @@ impl<'db> SubclassOfType<'db> {
pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
match self.subclass_of {
SubclassOfInner::Class(class) => {
class.find_legacy_typevars(db, typevars);
class.find_legacy_typevars(db, binding_context, typevars);
}
SubclassOfInner::Dynamic(_) => {}
}

View file

@ -22,12 +22,13 @@ use std::hash::Hash;
use itertools::{Either, EitherOrBoth, Itertools};
use crate::semantic_index::definition::Definition;
use crate::types::class::{ClassType, KnownClass};
use crate::types::{SubclassOfType, Truthiness};
use crate::types::{
Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
BoundTypeVarInstance, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarVariance,
UnionBuilder, UnionType, cyclic::PairVisitor,
};
use crate::types::{SubclassOfType, Truthiness};
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
use crate::{Db, FxOrderSet};
@ -272,9 +273,11 @@ impl<'db> TupleType<'db> {
pub(crate) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
self.tuple(db).find_legacy_typevars(db, typevars);
self.tuple(db)
.find_legacy_typevars(db, binding_context, typevars);
}
pub(crate) fn has_relation_to(
@ -435,10 +438,11 @@ impl<'db> FixedLengthTuple<Type<'db>> {
fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
for ty in &self.0 {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -773,14 +777,16 @@ impl<'db> VariableLengthTuple<Type<'db>> {
fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
for ty in &self.prefix {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
self.variable.find_legacy_typevars(db, typevars);
self.variable
.find_legacy_typevars(db, binding_context, typevars);
for ty in &self.suffix {
ty.find_legacy_typevars(db, typevars);
ty.find_legacy_typevars(db, binding_context, typevars);
}
}
@ -1120,11 +1126,12 @@ impl<'db> Tuple<Type<'db>> {
fn find_legacy_typevars(
&self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
binding_context: Option<Definition<'db>>,
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
) {
match self {
Tuple::Fixed(tuple) => tuple.find_legacy_typevars(db, typevars),
Tuple::Variable(tuple) => tuple.find_legacy_typevars(db, typevars),
Tuple::Fixed(tuple) => tuple.find_legacy_typevars(db, binding_context, typevars),
Tuple::Variable(tuple) => tuple.find_legacy_typevars(db, binding_context, typevars),
}
}

View file

@ -1,19 +1,19 @@
use crate::{
Db, FxIndexSet,
types::{
BoundMethodType, BoundSuperType, CallableType, GenericAlias, IntersectionType,
KnownInstanceType, MethodWrapperKind, NominalInstanceType, PropertyInstanceType,
ProtocolInstanceType, SubclassOfType, Type, TypeAliasType, TypeIsType, TypeVarInstance,
TypedDictType, UnionType,
BoundMethodType, BoundSuperType, BoundTypeVarInstance, CallableType, GenericAlias,
IntersectionType, KnownInstanceType, MethodWrapperKind, NominalInstanceType,
PropertyInstanceType, ProtocolInstanceType, SubclassOfType, Type, TypeAliasType,
TypeIsType, TypeVarInstance, TypedDictType, UnionType,
class::walk_generic_alias,
function::{FunctionType, walk_function_type},
instance::{walk_nominal_instance_type, walk_protocol_instance_type},
subclass_of::walk_subclass_of_type,
tuple::{TupleType, walk_tuple_type},
walk_bound_method_type, walk_bound_super_type, walk_callable_type, walk_intersection_type,
walk_known_instance_type, walk_method_wrapper_type, walk_property_instance_type,
walk_type_alias_type, walk_type_var_type, walk_typed_dict_type, walk_typeis_type,
walk_union,
walk_bound_method_type, walk_bound_super_type, walk_bound_type_var_type,
walk_callable_type, walk_intersection_type, walk_known_instance_type,
walk_method_wrapper_type, walk_property_instance_type, walk_type_alias_type,
walk_type_var_type, walk_typed_dict_type, walk_typeis_type, walk_union,
},
};
@ -77,8 +77,16 @@ pub(crate) trait TypeVisitor<'db> {
walk_nominal_instance_type(db, nominal, self);
}
fn visit_type_var_type(&mut self, db: &'db dyn Db, type_var: TypeVarInstance<'db>) {
walk_type_var_type(db, type_var, self);
fn visit_bound_type_var_type(
&mut self,
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
) {
walk_bound_type_var_type(db, bound_typevar, self);
}
fn visit_type_var_type(&mut self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) {
walk_type_var_type(db, typevar, self);
}
fn visit_protocol_instance_type(
@ -131,7 +139,7 @@ enum NonAtomicType<'db> {
NominalInstance(NominalInstanceType<'db>),
PropertyInstance(PropertyInstanceType<'db>),
TypeIs(TypeIsType<'db>),
TypeVar(TypeVarInstance<'db>),
TypeVar(BoundTypeVarInstance<'db>),
ProtocolInstance(ProtocolInstanceType<'db>),
TypedDict(TypedDictType<'db>),
}
@ -194,7 +202,9 @@ impl<'db> From<Type<'db>> for TypeKind<'db> {
Type::PropertyInstance(property) => {
TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property))
}
Type::TypeVar(type_var) => TypeKind::NonAtomic(NonAtomicType::TypeVar(type_var)),
Type::TypeVar(bound_typevar) => {
TypeKind::NonAtomic(NonAtomicType::TypeVar(bound_typevar))
}
Type::TypeIs(type_is) => TypeKind::NonAtomic(NonAtomicType::TypeIs(type_is)),
Type::TypedDict(typed_dict) => {
TypeKind::NonAtomic(NonAtomicType::TypedDict(typed_dict))
@ -231,7 +241,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::TypeVar(type_var) => visitor.visit_type_var_type(db, type_var),
NonAtomicType::TypeVar(bound_typevar) => {
visitor.visit_bound_type_var_type(db, bound_typevar);
}
NonAtomicType::ProtocolInstance(protocol) => {
visitor.visit_protocol_instance_type(db, protocol);
}