mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-26 18:06:43 +00:00
[ty] Don't track inferability via different Type variants (#20677)
We have to track whether a typevar appears in a position where it's inferable or not. In a non-inferable position (in the body of the generic class or function that binds it), assignability must hold for every possible specialization of the typevar. In an inferable position, it only needs to hold for _some_ specialization. https://github.com/astral-sh/ruff/pull/20093 is working on using constraint sets to model assignability of typevars, and the constraint sets that we produce will be the same for inferable vs non-inferable typevars; what changes is what we _compare_ that constraint set to. (For a non-inferable typevar, the constraint set must equal the set of valid specializations; for an inferable typevar, it must not be `never`.) When I first added support for tracking inferable vs non-inferable typevars, it seemed like it would be easiest to have separate `Type` variants for each. The alternative (which lines up with the Δ set in [POPL15](https://doi.org/10.1145/2676726.2676991)) would be to explicitly plumb through a list of inferable typevars through our type property methods. That seemed cumbersome. In retrospect, that was the wrong decision. We've had to jump through hoops to translate types between the inferable and non-inferable variants, which has been quite brittle. Combined with the original point above, that much of the assignability logic will become more identical between inferable and non-inferable, there is less justification for the two `Type` variants. And plumbing an extra `inferable` parameter through all of these methods turns out to not be as bad as I anticipated. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
25023cc0ea
commit
b0e10a9777
17 changed files with 312 additions and 373 deletions
|
|
@ -800,15 +800,9 @@ pub enum Type<'db> {
|
|||
LiteralString,
|
||||
/// A bytes literal
|
||||
BytesLiteral(BytesLiteralType<'db>),
|
||||
/// 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.
|
||||
/// An instance of a typevar. 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`.
|
||||
|
|
@ -1374,9 +1368,6 @@ 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))
|
||||
}),
|
||||
|
|
@ -1453,7 +1444,6 @@ impl<'db> Type<'db> {
|
|||
| Type::Union(_)
|
||||
| Type::Intersection(_)
|
||||
| Type::Callable(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
|
|
@ -1544,7 +1534,6 @@ impl<'db> Type<'db> {
|
|||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::Intersection(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_) => None,
|
||||
}
|
||||
|
|
@ -1729,18 +1718,21 @@ 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::NonInferableTypeVar(_), Type::Union(union))
|
||||
if union.elements(db).contains(&self) =>
|
||||
(Type::TypeVar(bound_typevar), Type::Union(union))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& union.elements(db).contains(&self) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), Type::NonInferableTypeVar(_))
|
||||
if intersection.positive(db).contains(&target) =>
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), Type::NonInferableTypeVar(_))
|
||||
if intersection.negative(db).contains(&target) =>
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
|
@ -1750,18 +1742,19 @@ 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::NonInferableTypeVar(lhs_bound_typevar),
|
||||
Type::NonInferableTypeVar(rhs_bound_typevar),
|
||||
) if lhs_bound_typevar.identity(db) == rhs_bound_typevar.identity(db) => {
|
||||
(Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar))
|
||||
if !lhs_bound_typevar.is_inferable(db, inferable)
|
||||
&& lhs_bound_typevar.identity(db) == rhs_bound_typevar.identity(db) =>
|
||||
{
|
||||
ConstraintSet::from(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::NonInferableTypeVar(bound_typevar), _)
|
||||
if bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
|
||||
(Type::TypeVar(bound_typevar), _)
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
|
||||
{
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => unreachable!(),
|
||||
|
|
@ -1792,23 +1785,24 @@ 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::NonInferableTypeVar(bound_typevar))
|
||||
if !bound_typevar
|
||||
.typevar(db)
|
||||
.constraints(db)
|
||||
.when_some_and(|constraints| {
|
||||
constraints.iter().when_all(db, |constraint| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
*constraint,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
(_, Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& !bound_typevar
|
||||
.typevar(db)
|
||||
.constraints(db)
|
||||
.when_some_and(|constraints| {
|
||||
constraints.iter().when_all(db, |constraint| {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
*constraint,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.is_never_satisfied() =>
|
||||
.is_never_satisfied() =>
|
||||
{
|
||||
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
|
||||
// where this arm only engages if it returns true (or in the world of constraints,
|
||||
|
|
@ -1831,7 +1825,9 @@ impl<'db> Type<'db> {
|
|||
})
|
||||
}
|
||||
|
||||
(Type::TypeVar(_), _) if relation.is_assignability() => {
|
||||
(Type::TypeVar(bound_typevar), _)
|
||||
if bound_typevar.is_inferable(db, inferable) && relation.is_assignability() =>
|
||||
{
|
||||
// The implicit lower bound of a typevar is `Never`, which means
|
||||
// that it is always assignable to any other type.
|
||||
|
||||
|
|
@ -1932,10 +1928,13 @@ 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::NonInferableTypeVar(_)) => ConstraintSet::from(false),
|
||||
(_, Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(_, Type::TypeVar(typevar))
|
||||
if relation.is_assignability()
|
||||
if typevar.is_inferable(db, inferable)
|
||||
&& relation.is_assignability()
|
||||
&& typevar.typevar(db).upper_bound(db).is_none_or(|bound| {
|
||||
!self
|
||||
.has_relation_to_impl(
|
||||
|
|
@ -1964,7 +1963,11 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
// TODO: Infer specializations here
|
||||
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false),
|
||||
(Type::TypeVar(bound_typevar), _) | (_, Type::TypeVar(bound_typevar))
|
||||
if bound_typevar.is_inferable(db, inferable) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
|
||||
// TODO: Implement assignability and subtyping for TypedDict
|
||||
|
|
@ -2399,9 +2402,12 @@ 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::NonInferableTypeVar(_), _) => {
|
||||
(Type::TypeVar(bound_typevar), _) => {
|
||||
// All inferable cases should have been handled above
|
||||
assert!(!bound_typevar.is_inferable(db, inferable));
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
(Type::NominalInstance(_), _) => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2633,16 +2639,17 @@ 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::NonInferableTypeVar(self_bound_typevar),
|
||||
Type::NonInferableTypeVar(other_bound_typevar),
|
||||
) if self_bound_typevar.identity(db) == other_bound_typevar.identity(db) => {
|
||||
(Type::TypeVar(self_bound_typevar), Type::TypeVar(other_bound_typevar))
|
||||
if !self_bound_typevar.is_inferable(db, inferable)
|
||||
&& self_bound_typevar.identity(db) == other_bound_typevar.identity(db) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
(tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection))
|
||||
| (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_))
|
||||
if intersection.negative(db).contains(&tvar) =>
|
||||
(tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection))
|
||||
| (Type::Intersection(intersection), tvar @ Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&tvar) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
|
@ -2651,8 +2658,9 @@ 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::NonInferableTypeVar(bound_typevar), other)
|
||||
| (other, Type::NonInferableTypeVar(bound_typevar)) => {
|
||||
(Type::TypeVar(bound_typevar), other) | (other, Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable) =>
|
||||
{
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => ConstraintSet::from(false),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound
|
||||
|
|
@ -3300,7 +3308,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::NonInferableTypeVar(bound_typevar) => {
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => false,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
|
||||
|
|
@ -3311,8 +3319,6 @@ 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,
|
||||
|
|
@ -3411,7 +3417,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::NonInferableTypeVar(bound_typevar) => {
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => false,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
|
||||
|
|
@ -3422,8 +3428,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Type::TypeVar(_) => false,
|
||||
|
||||
Type::SubclassOf(..) => {
|
||||
// TODO: Same comment as above for `is_singleton`
|
||||
false
|
||||
|
|
@ -3588,7 +3592,6 @@ impl<'db> Type<'db> {
|
|||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
|
|
@ -3699,7 +3702,7 @@ impl<'db> Type<'db> {
|
|||
Type::object().instance_member(db, name)
|
||||
}
|
||||
|
||||
Type::NonInferableTypeVar(bound_typevar) => {
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => Type::object().instance_member(db, name),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
|
|
@ -3712,16 +3715,6 @@ 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::Undefined.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)
|
||||
|
|
@ -4298,7 +4291,6 @@ impl<'db> Type<'db> {
|
|||
| Type::BytesLiteral(..)
|
||||
| Type::EnumLiteral(..)
|
||||
| Type::LiteralString
|
||||
| Type::NonInferableTypeVar(..)
|
||||
| Type::TypeVar(..)
|
||||
| Type::SpecialForm(..)
|
||||
| Type::KnownInstance(..)
|
||||
|
|
@ -4647,7 +4639,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
},
|
||||
|
||||
Type::NonInferableTypeVar(bound_typevar) => {
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => Truthiness::Ambiguous,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
|
|
@ -4658,7 +4650,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Type::TypeVar(_) => Truthiness::Ambiguous,
|
||||
|
||||
Type::NominalInstance(instance) => instance
|
||||
.known_class(db)
|
||||
|
|
@ -4757,7 +4748,7 @@ impl<'db> Type<'db> {
|
|||
.into()
|
||||
}
|
||||
|
||||
Type::NonInferableTypeVar(bound_typevar) => {
|
||||
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),
|
||||
|
|
@ -4770,15 +4761,6 @@ 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())
|
||||
|
|
@ -5635,16 +5617,12 @@ impl<'db> Type<'db> {
|
|||
Type::TypeAlias(alias) => {
|
||||
non_async_special_case(db, alias.value_type(db))
|
||||
}
|
||||
Type::NonInferableTypeVar(tvar) => match tvar.typevar(db).bound_or_constraints(db)? {
|
||||
Type::TypeVar(tvar) => match tvar.typevar(db).bound_or_constraints(db)? {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
||||
non_async_special_case(db, bound)
|
||||
}
|
||||
TypeVarBoundOrConstraints::Constraints(union) => non_async_special_case(db, Type::Union(union)),
|
||||
},
|
||||
Type::TypeVar(_) => unreachable!(
|
||||
"should not be able to iterate over type variable {} in inferable position",
|
||||
ty.display(db)
|
||||
),
|
||||
Type::Union(union) => {
|
||||
let elements = union.elements(db);
|
||||
if elements.len() < MAX_TUPLE_LENGTH {
|
||||
|
|
@ -6040,7 +6018,7 @@ impl<'db> Type<'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.
|
||||
Type::from(class.identity_specialization(db, &Type::TypeVar)),
|
||||
Type::from(class.identity_specialization(db)),
|
||||
),
|
||||
_ => (None, None, self),
|
||||
},
|
||||
|
|
@ -6193,9 +6171,6 @@ 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::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")),
|
||||
|
|
@ -6279,7 +6254,6 @@ impl<'db> Type<'db> {
|
|||
| Type::LiteralString
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::Callable(_)
|
||||
| Type::BoundMethod(_)
|
||||
|
|
@ -6311,7 +6285,7 @@ impl<'db> Type<'db> {
|
|||
typevar_binding_context,
|
||||
*typevar,
|
||||
)
|
||||
.map(Type::NonInferableTypeVar)
|
||||
.map(Type::TypeVar)
|
||||
.unwrap_or(*self))
|
||||
}
|
||||
KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError {
|
||||
|
|
@ -6388,14 +6362,7 @@ impl<'db> Type<'db> {
|
|||
});
|
||||
};
|
||||
|
||||
Ok(typing_self(
|
||||
db,
|
||||
scope_id,
|
||||
typevar_binding_context,
|
||||
class,
|
||||
&Type::NonInferableTypeVar,
|
||||
)
|
||||
.unwrap_or(*self))
|
||||
Ok(typing_self(db, scope_id, typevar_binding_context, class).unwrap_or(*self))
|
||||
}
|
||||
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
|
||||
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
|
||||
|
|
@ -6565,7 +6532,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||
Type::NonInferableTypeVar(bound_typevar) => {
|
||||
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),
|
||||
|
|
@ -6576,7 +6543,6 @@ 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),
|
||||
|
|
@ -6710,36 +6676,12 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
TypeMapping::PromoteLiterals
|
||||
| TypeMapping::BindLegacyTypevars(_)
|
||||
| TypeMapping::MarkTypeVarsInferable(_) => self,
|
||||
| TypeMapping::BindLegacyTypevars(_) => self,
|
||||
TypeMapping::Materialize(materialization_kind) => {
|
||||
Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
|
||||
}
|
||||
}
|
||||
|
||||
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 binding_context.is_none_or(|context| context == bound_typevar.binding_context(db)) {
|
||||
Type::TypeVar(bound_typevar.mark_typevars_inferable(db, visitor))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
TypeMapping::PromoteLiterals
|
||||
| TypeMapping::BindLegacyTypevars(_)
|
||||
| TypeMapping::BindSelf(_)
|
||||
| TypeMapping::ReplaceSelf { .. }
|
||||
=> self,
|
||||
TypeMapping::Materialize(materialization_kind) => Type::NonInferableTypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
|
||||
|
||||
}
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
|
||||
TypeMapping::BindLegacyTypevars(binding_context) => {
|
||||
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
|
||||
|
|
@ -6749,7 +6691,6 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::PromoteLiterals |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::MarkTypeVarsInferable(_) |
|
||||
TypeMapping::Materialize(_) => self,
|
||||
}
|
||||
|
||||
|
|
@ -6864,7 +6805,6 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::BindLegacyTypevars(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::MarkTypeVarsInferable(_) |
|
||||
TypeMapping::Materialize(_) => self,
|
||||
TypeMapping::PromoteLiterals => self.promote_literals_impl(db, tcx)
|
||||
}
|
||||
|
|
@ -6875,7 +6815,6 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::BindLegacyTypevars(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::MarkTypeVarsInferable(_) |
|
||||
TypeMapping::PromoteLiterals => self,
|
||||
TypeMapping::Materialize(materialization_kind) => match materialization_kind {
|
||||
MaterializationKind::Top => Type::object(),
|
||||
|
|
@ -6925,7 +6864,7 @@ impl<'db> Type<'db> {
|
|||
visitor: &FindLegacyTypeVarsVisitor<'db>,
|
||||
) {
|
||||
match self {
|
||||
Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => {
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
if matches!(
|
||||
bound_typevar.typevar(db).kind(db),
|
||||
TypeVarKind::Legacy | TypeVarKind::TypingSelf
|
||||
|
|
@ -7157,7 +7096,6 @@ 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 {
|
||||
|
|
@ -7299,9 +7237,7 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
|
|||
Type::GenericAlias(generic_alias) => generic_alias.variance_of(db, typevar),
|
||||
Type::Callable(callable_type) => callable_type.signatures(db).variance_of(db, typevar),
|
||||
// A type variable is always covariant in itself.
|
||||
Type::TypeVar(other_typevar) | Type::NonInferableTypeVar(other_typevar)
|
||||
if other_typevar == typevar =>
|
||||
{
|
||||
Type::TypeVar(other_typevar) if other_typevar == typevar => {
|
||||
// type variables are covariant in themselves
|
||||
TypeVarVariance::Covariant
|
||||
}
|
||||
|
|
@ -7357,7 +7293,6 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
|
|||
| Type::AlwaysTruthy
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypedDict(_)
|
||||
| Type::TypeAlias(_) => TypeVarVariance::Bivariant,
|
||||
};
|
||||
|
|
@ -7430,17 +7365,6 @@ pub enum TypeMapping<'a, 'db> {
|
|||
BindSelf(Type<'db>),
|
||||
/// Replaces occurrences of `typing.Self` with a new `Self` type variable with the given upper bound.
|
||||
ReplaceSelf { new_upper_bound: Type<'db> },
|
||||
/// Marks type variables as inferable.
|
||||
///
|
||||
/// When we create the signature for a generic function, we mark its type variables as inferable. Since
|
||||
/// the generic function might reference type variables from enclosing generic scopes, we include the
|
||||
/// function's binding context in order to only mark those type variables as inferable that are actually
|
||||
/// bound by that function.
|
||||
///
|
||||
/// When the parameter is set to `None`, *all* type variables will be marked as inferable. We use this
|
||||
/// variant when descending into the bounds and/or constraints, and the default value of a type variable,
|
||||
/// which may include nested type variables (`Self` has a bound of `C[T]` for a generic class `C[T]`).
|
||||
MarkTypeVarsInferable(Option<BindingContext<'db>>),
|
||||
/// Create the top or bottom materialization of a type.
|
||||
Materialize(MaterializationKind),
|
||||
}
|
||||
|
|
@ -7457,7 +7381,6 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
| TypeMapping::PartialSpecialization(_)
|
||||
| TypeMapping::PromoteLiterals
|
||||
| TypeMapping::BindLegacyTypevars(_)
|
||||
| TypeMapping::MarkTypeVarsInferable(_)
|
||||
| TypeMapping::Materialize(_) => context,
|
||||
TypeMapping::BindSelf(_) => GenericContext::from_typevar_instances(
|
||||
db,
|
||||
|
|
@ -8344,48 +8267,6 @@ impl<'db> TypeVarInstance<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
fn mark_typevars_inferable(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
// Type variables can have nested type variables in their bounds, constraints, or default value.
|
||||
// When we mark a type variable as inferable, we also mark all of these nested type variables as
|
||||
// inferable, so we set the parameter to `None` here.
|
||||
let type_mapping = &TypeMapping::MarkTypeVarsInferable(None);
|
||||
|
||||
let new_bound_or_constraints =
|
||||
self._bound_or_constraints(db)
|
||||
.map(|bound_or_constraints| match bound_or_constraints {
|
||||
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => {
|
||||
bound_or_constraints
|
||||
.mark_typevars_inferable(db, visitor)
|
||||
.into()
|
||||
}
|
||||
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound
|
||||
| TypeVarBoundOrConstraintsEvaluation::LazyConstraints => bound_or_constraints,
|
||||
});
|
||||
|
||||
let new_default = self._default(db).and_then(|default| match default {
|
||||
TypeVarDefaultEvaluation::Eager(ty) => Some(
|
||||
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
|
||||
.into(),
|
||||
),
|
||||
TypeVarDefaultEvaluation::Lazy => self.lazy_default(db).map(|ty| {
|
||||
ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor)
|
||||
.into()
|
||||
}),
|
||||
});
|
||||
|
||||
Self::new(
|
||||
db,
|
||||
self.identity(db),
|
||||
new_bound_or_constraints,
|
||||
self.explicit_variance(db),
|
||||
new_default,
|
||||
)
|
||||
}
|
||||
|
||||
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
|
||||
let bound_or_constraints = match self.bound_or_constraints(db)? {
|
||||
TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
|
||||
|
|
@ -8543,7 +8424,7 @@ impl<'db> BindingContext<'db> {
|
|||
/// independent of the typevar's bounds or constraints. Two bound typevars have the same identity
|
||||
/// if they represent the same logical typevar bound in the same context, even if their bounds
|
||||
/// have been materialized differently.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize, salsa::Update)]
|
||||
pub struct BoundTypeVarIdentity<'db> {
|
||||
pub(crate) identity: TypeVarIdentity<'db>,
|
||||
pub(crate) binding_context: BindingContext<'db>,
|
||||
|
|
@ -8708,18 +8589,6 @@ impl<'db> BoundTypeVarInstance<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
fn mark_typevars_inferable(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.typevar(db).mark_typevars_inferable(db, visitor),
|
||||
self.binding_context(db),
|
||||
)
|
||||
}
|
||||
|
||||
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
|
||||
Some(Self::new(
|
||||
db,
|
||||
|
|
@ -8825,38 +8694,6 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_typevars_inferable(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
let type_mapping = &TypeMapping::MarkTypeVarsInferable(None);
|
||||
|
||||
match self {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => TypeVarBoundOrConstraints::UpperBound(
|
||||
bound.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor),
|
||||
),
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
||||
db,
|
||||
constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
ty.apply_type_mapping_impl(
|
||||
db,
|
||||
type_mapping,
|
||||
TypeContext::default(),
|
||||
visitor,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned if a type is not awaitable.
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ impl<'db> BoundSuperType<'db> {
|
|||
Type::TypeAlias(alias) => {
|
||||
return delegate_with_error_mapped(alias.value_type(db), None);
|
||||
}
|
||||
Type::TypeVar(type_var) | Type::NonInferableTypeVar(type_var) => {
|
||||
Type::TypeVar(type_var) => {
|
||||
let type_var = type_var.typevar(db);
|
||||
return match type_var.bound_or_constraints(db) {
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
|
|
|
|||
|
|
@ -1062,8 +1062,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
let mut positive_to_remove = SmallVec::<[usize; 1]>::new();
|
||||
|
||||
for (typevar_index, ty) in self.positive.iter().enumerate() {
|
||||
let (Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar)) = ty
|
||||
else {
|
||||
let Type::TypeVar(bound_typevar) = ty else {
|
||||
continue;
|
||||
};
|
||||
let Some(TypeVarBoundOrConstraints::Constraints(constraints)) =
|
||||
|
|
|
|||
|
|
@ -2589,7 +2589,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
|||
return;
|
||||
};
|
||||
|
||||
// TODO: Use the list of inferable typevars from the generic context of the callable.
|
||||
self.inferable_typevars = generic_context.inferable_typevars(self.db);
|
||||
let mut builder = SpecializationBuilder::new(self.db, self.inferable_typevars);
|
||||
|
||||
let parameters = self.signature.parameters();
|
||||
|
|
|
|||
|
|
@ -1627,15 +1627,10 @@ impl<'db> ClassLiteral<'db> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns a specialization of this class where each typevar is mapped to itself. The second
|
||||
/// parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on the use case.
|
||||
pub(crate) fn identity_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>,
|
||||
) -> ClassType<'db> {
|
||||
/// Returns a specialization of this class where each typevar is mapped to itself.
|
||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
self.apply_specialization(db, |generic_context| {
|
||||
generic_context.identity_specialization(db, typevar_to_type)
|
||||
generic_context.identity_specialization(db)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,6 @@ impl<'db> ClassBase<'db> {
|
|||
| Type::StringLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
|
|
|
|||
|
|
@ -560,9 +560,7 @@ impl Display for DisplayRepresentation<'_> {
|
|||
.display_with(self.db, self.settings.clone()),
|
||||
literal_name = enum_literal.name(self.db)
|
||||
),
|
||||
Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => {
|
||||
bound_typevar.identity(self.db).display(self.db).fmt(f)
|
||||
}
|
||||
Type::TypeVar(bound_typevar) => bound_typevar.identity(self.db).display(self.db).fmt(f),
|
||||
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
|
||||
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
|
||||
Type::BoundSuper(bound_super) => {
|
||||
|
|
|
|||
|
|
@ -365,28 +365,12 @@ impl<'db> OverloadLiteral<'db> {
|
|||
if function_node.is_async && !is_generator {
|
||||
signature = signature.wrap_coroutine_return_type(db);
|
||||
}
|
||||
signature = signature.mark_typevars_inferable(db);
|
||||
|
||||
let pep695_ctx = function_node.type_params.as_ref().map(|type_params| {
|
||||
GenericContext::from_type_params(db, index, self.definition(db), type_params)
|
||||
});
|
||||
let legacy_ctx = GenericContext::from_function_params(
|
||||
db,
|
||||
self.definition(db),
|
||||
signature.parameters(),
|
||||
signature.return_ty,
|
||||
);
|
||||
// We need to update `signature.generic_context` here,
|
||||
// because type variables in `GenericContext::variables` are still non-inferable.
|
||||
signature.generic_context =
|
||||
GenericContext::merge_pep695_and_legacy(db, pep695_ctx, legacy_ctx);
|
||||
|
||||
signature
|
||||
}
|
||||
|
||||
/// Typed internally-visible "raw" signature for this function.
|
||||
/// That is, type variables in parameter types and the return type remain non-inferable,
|
||||
/// and the return types of async functions are not wrapped in `CoroutineType[...]`.
|
||||
/// That is, the return types of async functions are not wrapped in `CoroutineType[...]`.
|
||||
///
|
||||
/// ## Warning
|
||||
///
|
||||
|
|
@ -1133,7 +1117,6 @@ fn is_instance_truthiness<'db>(
|
|||
| Type::PropertyInstance(..)
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy
|
||||
| Type::NonInferableTypeVar(..)
|
||||
| Type::TypeVar(..)
|
||||
| Type::BoundSuper(..)
|
||||
| Type::TypeIs(..)
|
||||
|
|
@ -1729,11 +1712,7 @@ impl KnownFunction {
|
|||
}
|
||||
|
||||
KnownFunction::RangeConstraint => {
|
||||
let [
|
||||
Some(lower),
|
||||
Some(Type::NonInferableTypeVar(typevar)),
|
||||
Some(upper),
|
||||
] = parameter_types
|
||||
let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] = parameter_types
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -1746,11 +1725,7 @@ impl KnownFunction {
|
|||
}
|
||||
|
||||
KnownFunction::NegatedRangeConstraint => {
|
||||
let [
|
||||
Some(lower),
|
||||
Some(Type::NonInferableTypeVar(typevar)),
|
||||
Some(upper),
|
||||
] = parameter_types
|
||||
let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] = parameter_types
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::marker::PhantomData;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Display;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_python_ast as ast;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind, ScopeId};
|
||||
|
|
@ -14,14 +15,16 @@ use crate::types::infer::infer_definition_types;
|
|||
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_type};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarIdentity, BoundTypeVarInstance, ClassLiteral,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
||||
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext,
|
||||
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance,
|
||||
TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type,
|
||||
walk_bound_type_var_type,
|
||||
};
|
||||
use crate::{Db, FxOrderMap, FxOrderSet};
|
||||
use crate::{Db, FxIndexSet, FxOrderMap, FxOrderSet};
|
||||
|
||||
/// Returns an iterator of any generic context introduced by the given scope or any enclosing
|
||||
/// scope.
|
||||
|
|
@ -106,7 +109,6 @@ pub(crate) fn typing_self<'db>(
|
|||
scope_id: ScopeId,
|
||||
typevar_binding_context: Option<Definition<'db>>,
|
||||
class: ClassLiteral<'db>,
|
||||
typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let index = semantic_index(db, scope_id.file(db));
|
||||
|
||||
|
|
@ -118,7 +120,7 @@ pub(crate) fn typing_self<'db>(
|
|||
);
|
||||
let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance(
|
||||
db,
|
||||
class.identity_specialization(db, typevar_to_type),
|
||||
class.identity_specialization(db),
|
||||
));
|
||||
let typevar = TypeVarInstance::new(
|
||||
db,
|
||||
|
|
@ -138,17 +140,70 @@ pub(crate) fn typing_self<'db>(
|
|||
typevar_binding_context,
|
||||
typevar,
|
||||
)
|
||||
.map(typevar_to_type)
|
||||
.map(Type::TypeVar)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum InferableTypeVars<'a, 'db> {
|
||||
None,
|
||||
// TODO: This variant isn't used, and only exists so that we can include the 'a and 'db in the
|
||||
// type definition. They will be used soon when we start creating real InferableTypeVars
|
||||
// instances.
|
||||
#[expect(unused)]
|
||||
Unused(PhantomData<&'a &'db ()>),
|
||||
One(&'a FxHashSet<BoundTypeVarIdentity<'db>>),
|
||||
Two(
|
||||
&'a InferableTypeVars<'a, 'db>,
|
||||
&'a InferableTypeVars<'a, 'db>,
|
||||
),
|
||||
}
|
||||
|
||||
impl<'db> BoundTypeVarInstance<'db> {
|
||||
pub(crate) fn is_inferable(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> bool {
|
||||
match inferable {
|
||||
InferableTypeVars::None => false,
|
||||
InferableTypeVars::One(typevars) => typevars.contains(&self.identity(db)),
|
||||
InferableTypeVars::Two(left, right) => {
|
||||
self.is_inferable(db, *left) || self.is_inferable(db, *right)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'db> InferableTypeVars<'a, 'db> {
|
||||
pub(crate) fn merge(&'a self, other: Option<&'a InferableTypeVars<'a, 'db>>) -> Self {
|
||||
match other {
|
||||
Some(other) => InferableTypeVars::Two(self, other),
|
||||
None => *self,
|
||||
}
|
||||
}
|
||||
|
||||
// Keep this around for debugging purposes
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn display(&self, db: &'db dyn Db) -> impl Display {
|
||||
fn find_typevars<'db>(
|
||||
result: &mut FxHashSet<BoundTypeVarIdentity<'db>>,
|
||||
inferable: &InferableTypeVars<'_, 'db>,
|
||||
) {
|
||||
match inferable {
|
||||
InferableTypeVars::None => {}
|
||||
InferableTypeVars::One(typevars) => result.extend(typevars.iter().copied()),
|
||||
InferableTypeVars::Two(left, right) => {
|
||||
find_typevars(result, left);
|
||||
find_typevars(result, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut typevars = FxHashSet::default();
|
||||
find_typevars(&mut typevars, self);
|
||||
format!(
|
||||
"[{}]",
|
||||
typevars
|
||||
.into_iter()
|
||||
.map(|identity| identity.display(db))
|
||||
.format(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
|
||||
|
|
@ -255,6 +310,66 @@ impl<'db> GenericContext<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||
#[derive(Default)]
|
||||
struct CollectTypeVars<'db> {
|
||||
typevars: RefCell<FxHashSet<BoundTypeVarIdentity<'db>>>,
|
||||
seen_types: RefCell<FxIndexSet<NonAtomicType<'db>>>,
|
||||
}
|
||||
|
||||
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
|
||||
fn should_visit_lazy_type_attributes(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn visit_bound_type_var_type(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
bound_typevar: BoundTypeVarInstance<'db>,
|
||||
) {
|
||||
self.typevars
|
||||
.borrow_mut()
|
||||
.insert(bound_typevar.identity(db));
|
||||
walk_bound_type_var_type(db, bound_typevar, self);
|
||||
}
|
||||
|
||||
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
match TypeKind::from(ty) {
|
||||
TypeKind::Atomic => {}
|
||||
TypeKind::NonAtomic(non_atomic_type) => {
|
||||
if !self.seen_types.borrow_mut().insert(non_atomic_type) {
|
||||
// If we have already seen this type, we can skip it.
|
||||
return;
|
||||
}
|
||||
walk_non_atomic_type(db, non_atomic_type, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked(
|
||||
returns(ref),
|
||||
cycle_fn=inferable_typevars_cycle_recover,
|
||||
cycle_initial=inferable_typevars_cycle_initial,
|
||||
heap_size=ruff_memory_usage::heap_size,
|
||||
)]
|
||||
fn inferable_typevars_inner<'db>(
|
||||
db: &'db dyn Db,
|
||||
generic_context: GenericContext<'db>,
|
||||
) -> FxHashSet<BoundTypeVarIdentity<'db>> {
|
||||
let visitor = CollectTypeVars::default();
|
||||
for bound_typevar in generic_context.variables(db) {
|
||||
visitor.visit_bound_type_var_type(db, bound_typevar);
|
||||
}
|
||||
visitor.typevars.into_inner()
|
||||
}
|
||||
|
||||
// This ensures that salsa caches the FxHashSet, not the InferableTypeVars that wraps it.
|
||||
// (That way InferableTypeVars can contain references, and doesn't need to impl
|
||||
// salsa::Update.)
|
||||
InferableTypeVars::One(inferable_typevars_inner(db, self))
|
||||
}
|
||||
|
||||
pub(crate) fn variables(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
|
@ -410,14 +525,8 @@ impl<'db> GenericContext<'db> {
|
|||
}
|
||||
|
||||
/// Returns a specialization of this generic context where each typevar is mapped to itself.
|
||||
/// The second parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on
|
||||
/// the use case.
|
||||
pub(crate) fn identity_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>,
|
||||
) -> Specialization<'db> {
|
||||
let types = self.variables(db).map(typevar_to_type).collect();
|
||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||
let types = self.variables(db).map(Type::TypeVar).collect();
|
||||
self.specialize(db, types)
|
||||
}
|
||||
|
||||
|
|
@ -543,6 +652,22 @@ impl<'db> GenericContext<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn inferable_typevars_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &FxHashSet<BoundTypeVarIdentity<'db>>,
|
||||
_count: u32,
|
||||
_self: GenericContext<'db>,
|
||||
) -> salsa::CycleRecoveryAction<FxHashSet<BoundTypeVarIdentity<'db>>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
|
||||
fn inferable_typevars_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_self: GenericContext<'db>,
|
||||
) -> FxHashSet<BoundTypeVarIdentity<'db>> {
|
||||
FxHashSet::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) enum LegacyGenericBase {
|
||||
Generic,
|
||||
|
|
@ -1357,7 +1482,9 @@ impl<'db> SpecializationBuilder<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
(Type::TypeVar(bound_typevar), ty) | (ty, Type::TypeVar(bound_typevar)) => {
|
||||
(Type::TypeVar(bound_typevar), ty) | (ty, Type::TypeVar(bound_typevar))
|
||||
if bound_typevar.is_inferable(self.db, self.inferable) =>
|
||||
{
|
||||
match bound_typevar.typevar(self.db).bound_or_constraints(self.db) {
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
if !ty
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ impl<'db> AllMembers<'db> {
|
|||
| Type::ProtocolInstance(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_) => match ty.to_meta_type(db) {
|
||||
|
|
|
|||
|
|
@ -3617,7 +3617,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::WrapperDescriptor(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::NonInferableTypeVar(..)
|
||||
| Type::TypeVar(..)
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy
|
||||
|
|
@ -5513,6 +5512,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
// Retrieve the parameter type for the current argument in a given overload and its binding.
|
||||
let db = self.db();
|
||||
let parameter_type = |overload: &Binding<'db>, binding: &CallableBinding<'db>| {
|
||||
let argument_index = if binding.bound_type.is_some() {
|
||||
argument_index + 1
|
||||
|
|
@ -5525,7 +5525,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
return None;
|
||||
};
|
||||
|
||||
overload.signature.parameters()[*parameter_index].annotated_type()
|
||||
let parameter_type =
|
||||
overload.signature.parameters()[*parameter_index].annotated_type()?;
|
||||
|
||||
// TODO: For now, skip any parameter annotations that mention any typevars. There
|
||||
// are two issues:
|
||||
//
|
||||
// First, if we include those typevars in the type context that we use to infer the
|
||||
// corresponding argument type, the typevars might end up appearing in the inferred
|
||||
// argument type as well. As part of analyzing this call, we're going to (try to)
|
||||
// infer a specialization of those typevars, and would need to substitute those
|
||||
// typevars in the inferred argument type. We can't do that easily at the moment,
|
||||
// since specialization inference occurs _after_ we've inferred argument types, and
|
||||
// we can't _update_ an expression's inferred type after the fact.
|
||||
//
|
||||
// Second, certain kinds of arguments themselves have typevars that we need to
|
||||
// infer specializations for. (For instance, passing the result of _another_ call
|
||||
// to the argument of _this_ call, where both are calls to generic functions.) In
|
||||
// that case, we want to "tie together" the typevars of the two calls so that we
|
||||
// can infer their specializations at the same time — or at least, for the
|
||||
// specialization of one to influence the specialization of the other. It's not yet
|
||||
// clear how we're going to do that. (We might have to start inferring constraint
|
||||
// sets for each expression, instead of simple types?)
|
||||
//
|
||||
// Regardless, for now, the expedient "solution" is to not perform bidi type
|
||||
// checking for these kinds of parameters.
|
||||
if parameter_type.has_typevar(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(parameter_type)
|
||||
};
|
||||
|
||||
// If there is only a single binding and overload, we can infer the argument directly with
|
||||
|
|
@ -5910,11 +5939,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
parenthesized: _,
|
||||
} = tuple;
|
||||
|
||||
// TODO: Use the list of inferable typevars from the generic context of tuple.
|
||||
let inferable = InferableTypeVars::None;
|
||||
|
||||
// Remove any union elements of that are unrelated to the tuple type.
|
||||
let tcx = tcx.map(|annotation| {
|
||||
let inferable = KnownClass::Tuple
|
||||
.try_to_class_literal(self.db())
|
||||
.and_then(|class| class.generic_context(self.db()))
|
||||
.map(|generic_context| generic_context.inferable_typevars(self.db()))
|
||||
.unwrap_or(InferableTypeVars::None);
|
||||
annotation.filter_disjoint_elements(
|
||||
self.db(),
|
||||
KnownClass::Tuple.to_instance(self.db()),
|
||||
|
|
@ -6057,10 +6088,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let elt_tys = |collection_class: KnownClass| {
|
||||
let class_literal = collection_class.try_to_class_literal(self.db())?;
|
||||
let generic_context = class_literal.generic_context(self.db())?;
|
||||
Some((class_literal, generic_context.variables(self.db())))
|
||||
Some((
|
||||
class_literal,
|
||||
generic_context,
|
||||
generic_context.variables(self.db()),
|
||||
))
|
||||
};
|
||||
|
||||
let Some((class_literal, elt_tys)) = elt_tys(collection_class) else {
|
||||
let Some((class_literal, generic_context, elt_tys)) = elt_tys(collection_class) else {
|
||||
// Infer the element types without type context, and fallback to unknown for
|
||||
// custom typesheds.
|
||||
for elt in elts.flatten().flatten() {
|
||||
|
|
@ -6070,9 +6105,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
return None;
|
||||
};
|
||||
|
||||
// TODO: Use the list of inferable typevars from the generic context of the collection
|
||||
// class.
|
||||
let inferable = InferableTypeVars::None;
|
||||
let inferable = generic_context.inferable_typevars(self.db());
|
||||
|
||||
// Remove any union elements of that are unrelated to the collection type.
|
||||
//
|
||||
|
|
@ -6154,7 +6187,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
|
||||
let class_type = class_literal.apply_specialization(self.db(), |generic_context| {
|
||||
let class_type = class_literal.apply_specialization(self.db(), |_| {
|
||||
builder.build(generic_context, TypeContext::default())
|
||||
});
|
||||
|
||||
|
|
@ -7732,7 +7765,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypedDict(_),
|
||||
|
|
@ -8119,7 +8151,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypedDict(_),
|
||||
|
|
@ -8149,7 +8180,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::NonInferableTypeVar(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypedDict(_),
|
||||
|
|
|
|||
|
|
@ -207,15 +207,16 @@ impl ClassInfoConstraintFunction {
|
|||
Type::Union(union) => {
|
||||
union.try_map(db, |element| self.generate_constraint(db, *element))
|
||||
}
|
||||
Type::NonInferableTypeVar(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))
|
||||
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))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// It's not valid to use a generic alias as the second argument to `isinstance()` or `issubclass()`,
|
||||
// e.g. `isinstance(x, list[int])` fails at runtime.
|
||||
|
|
@ -251,7 +252,6 @@ impl ClassInfoConstraintFunction {
|
|||
| Type::IntLiteral(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::TypedDict(_) => None,
|
||||
|
|
|
|||
|
|
@ -28,10 +28,9 @@ use crate::types::generics::{
|
|||
};
|
||||
use crate::types::infer::nearest_enclosing_class;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassLiteral,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
||||
KnownClass, MaterializationKind, NormalizedVisitor, TypeContext, TypeMapping, TypeRelation,
|
||||
VarianceInferable, todo_type,
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind,
|
||||
NormalizedVisitor, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
|
@ -446,19 +445,6 @@ impl<'db> Signature<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_typevars_inferable(self, db: &'db dyn Db) -> Self {
|
||||
if let Some(definition) = self.definition {
|
||||
self.apply_type_mapping_impl(
|
||||
db,
|
||||
&TypeMapping::MarkTypeVarsInferable(Some(BindingContext::Definition(definition))),
|
||||
TypeContext::default(),
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn wrap_coroutine_return_type(self, db: &'db dyn Db) -> Self {
|
||||
let return_ty = self.return_ty.map(|return_ty| {
|
||||
KnownClass::CoroutineType
|
||||
|
|
@ -617,6 +603,15 @@ impl<'db> Signature<'db> {
|
|||
inferable: InferableTypeVars<'_, 'db>,
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
// The typevars in self and other should also be considered inferable when checking whether
|
||||
// two signatures are equivalent.
|
||||
let self_inferable =
|
||||
(self.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let other_inferable =
|
||||
(other.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let inferable = inferable.merge(self_inferable.as_ref());
|
||||
let inferable = inferable.merge(other_inferable.as_ref());
|
||||
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
|
||||
let self_type = self_type.unwrap_or(Type::unknown());
|
||||
|
|
@ -767,6 +762,15 @@ impl<'db> Signature<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
// The typevars in self and other should also be considered inferable when checking whether
|
||||
// two signatures are equivalent.
|
||||
let self_inferable =
|
||||
(self.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let other_inferable =
|
||||
(other.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let inferable = inferable.merge(self_inferable.as_ref());
|
||||
let inferable = inferable.merge(other_inferable.as_ref());
|
||||
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
||||
let type1 = type1.unwrap_or(Type::unknown());
|
||||
|
|
@ -1301,19 +1305,8 @@ impl<'db> Parameters<'db> {
|
|||
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
|
||||
|
||||
Some(
|
||||
// It looks like unnecessary work here that we create the implicit Self
|
||||
// annotation using non-inferable typevars and then immediately apply
|
||||
// `MarkTypeVarsInferable` to it. However, this is currently necessary to
|
||||
// ensure that implicit-Self and explicit Self annotations are both treated
|
||||
// the same. Marking type vars inferable will cause reification of lazy
|
||||
// typevar defaults/bounds/constraints; this needs to happen for both
|
||||
// implicit and explicit Self so they remain the "same" typevar.
|
||||
typing_self(db, scope_id, typevar_binding_context, class, &Type::NonInferableTypeVar)
|
||||
.expect("We should always find the surrounding class for an implicit self: Self annotation").apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::MarkTypeVarsInferable(None),
|
||||
TypeContext::default()
|
||||
)
|
||||
typing_self(db, scope_id, typevar_binding_context, class)
|
||||
.expect("We should always find the surrounding class for an implicit self: Self annotation"),
|
||||
)
|
||||
} else {
|
||||
// For methods of non-generic classes that are not otherwise generic (e.g. return `Self` or
|
||||
|
|
|
|||
|
|
@ -137,10 +137,6 @@ 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,
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ pub(super) enum NonAtomicType<'db> {
|
|||
NominalInstance(NominalInstanceType<'db>),
|
||||
PropertyInstance(PropertyInstanceType<'db>),
|
||||
TypeIs(TypeIsType<'db>),
|
||||
NonInferableTypeVar(BoundTypeVarInstance<'db>),
|
||||
TypeVar(BoundTypeVarInstance<'db>),
|
||||
ProtocolInstance(ProtocolInstanceType<'db>),
|
||||
TypedDict(TypedDictType<'db>),
|
||||
|
|
@ -186,9 +185,6 @@ 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))
|
||||
}
|
||||
|
|
@ -228,9 +224,6 @@ pub(super) 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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue