mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Add dedicated variant for NominalInstance(object)
(#20340)
Previously, `Type::object` would find the definition of the `object` class in typeshed, load that in (to produce a `ClassLiteral` and `ClassType`), and then create a `NominalInstance` of that class. It's possible that we are using a typeshed that doesn't define `object`. We will not be able to do much useful work with that kind of typeshed, but it's still a possibility that we have to support at least without panicking. Previously, we would handle this situation by falling back on `Unknown`. In most cases, that's a perfectly fine fallback! But `object` is also our top type — the type of all values. `Unknown` is _not_ an acceptable stand-in for the top type. This PR adds a new `NominalInstance` variant for "instances of `object`". Unlike other nominal instances, we do not need to load in `object`'s `ClassType` to instantiate this variant. We will use this new variant even when the current typeshed does not define an `object` class, ensuring that we have a fully static representation of our top type at all times. There are several operations that need access to a nominal instance's class, and for this new `object` variant we load it lazily only when it's needed. That means this operation is now fallible, since this is where the "typeshed doesn't define `object`" failure shows up. This new approach also has the benefit of avoiding some salsa cycles that were cropping up while I was debugging #20093, since the new constraint set representation was trying to instantiate `Type::object` while in the middle of processing its definition in typeshed. Cycle handling was kicking in correctly and returning the `Unknown` fallback mentioned above. But the constraint set implementation depends on `Type::object` being a distinct and fully static type, highlighting that this is a correctness fix, not just an optimization fix. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
0e3697a643
commit
abb705aa4e
18 changed files with 196 additions and 155 deletions
|
@ -344,7 +344,7 @@ fn pattern_kind_to_type<'db>(db: &'db dyn Db, kind: &PatternPredicateKind<'db>)
|
|||
PatternPredicateKind::As(pattern, _) => pattern
|
||||
.as_deref()
|
||||
.map(|p| pattern_kind_to_type(db, p))
|
||||
.unwrap_or_else(|| Type::object(db)),
|
||||
.unwrap_or_else(Type::object),
|
||||
PatternPredicateKind::Unsupported => Type::Never,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -766,10 +766,6 @@ impl<'db> Type<'db> {
|
|||
Self::Dynamic(DynamicType::Divergent(DivergentType { scope }))
|
||||
}
|
||||
|
||||
pub(crate) fn object(db: &'db dyn Db) -> Self {
|
||||
KnownClass::Object.to_instance(db)
|
||||
}
|
||||
|
||||
pub const fn is_unknown(&self) -> bool {
|
||||
matches!(self, Type::Dynamic(DynamicType::Unknown))
|
||||
}
|
||||
|
@ -785,18 +781,18 @@ impl<'db> Type<'db> {
|
|||
|
||||
fn is_none(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::NoneType))
|
||||
.is_some_and(|instance| instance.has_known_class(db, KnownClass::NoneType))
|
||||
}
|
||||
|
||||
fn is_bool(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::Bool))
|
||||
.is_some_and(|instance| instance.has_known_class(db, KnownClass::Bool))
|
||||
}
|
||||
|
||||
fn is_enum(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance().is_some_and(|instance| {
|
||||
crate::types::enums::enum_metadata(db, instance.class(db).class_literal(db).0).is_some()
|
||||
})
|
||||
self.into_nominal_instance()
|
||||
.and_then(|instance| crate::types::enums::enum_metadata(db, instance.class_literal(db)))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Return true if this type overrides __eq__ or __ne__ methods
|
||||
|
@ -823,16 +819,8 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
pub(crate) fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance().is_some_and(|instance| {
|
||||
instance
|
||||
.class(db)
|
||||
.is_known(db, KnownClass::NotImplementedType)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_object(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.is_object(db))
|
||||
.is_some_and(|instance| instance.has_known_class(db, KnownClass::NotImplementedType))
|
||||
}
|
||||
|
||||
pub(crate) const fn is_todo(&self) -> bool {
|
||||
|
@ -1455,7 +1443,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
match (self, target) {
|
||||
// Everything is a subtype of `object`.
|
||||
(_, Type::NominalInstance(instance)) if instance.is_object(db) => {
|
||||
(_, Type::NominalInstance(instance)) if instance.is_object() => {
|
||||
C::always_satisfiable(db)
|
||||
}
|
||||
(_, Type::ProtocolInstance(target)) if target.is_equivalent_to_object(db) => {
|
||||
|
@ -1627,7 +1615,7 @@ impl<'db> Type<'db> {
|
|||
(left, Type::AlwaysTruthy) => C::from_bool(db, left.bool(db).is_always_true()),
|
||||
// Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
|
||||
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
|
||||
target.when_equivalent_to(db, Type::object(db))
|
||||
target.when_equivalent_to(db, Type::object())
|
||||
}
|
||||
|
||||
// These clauses handle type variants that include function literals. A function
|
||||
|
@ -1979,7 +1967,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
C::from_bool(db, n.is_object(db) && protocol.normalized(db) == nominal)
|
||||
C::from_bool(db, n.is_object() && protocol.normalized(db) == nominal)
|
||||
}
|
||||
// An instance of an enum class is equivalent to an enum literal of that class,
|
||||
// if that enum has only has one member.
|
||||
|
@ -1988,9 +1976,7 @@ impl<'db> Type<'db> {
|
|||
if literal.enum_class_instance(db) != Type::NominalInstance(instance) {
|
||||
return C::unsatisfiable(db);
|
||||
}
|
||||
|
||||
let class_literal = instance.class(db).class_literal(db).0;
|
||||
C::from_bool(db, is_single_member_enum(db, class_literal))
|
||||
C::from_bool(db, is_single_member_enum(db, instance.class_literal(db)))
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(left), Type::PropertyInstance(right)) => {
|
||||
|
@ -2840,9 +2826,7 @@ impl<'db> Type<'db> {
|
|||
// i.e. Type::NominalInstance(type). So looking up a name in the MRO of
|
||||
// `Type::NominalInstance(type)` is equivalent to looking up the name in the
|
||||
// MRO of the class `object`.
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::Type) =>
|
||||
{
|
||||
Type::NominalInstance(instance) if instance.has_known_class(db, KnownClass::Type) => {
|
||||
if policy.mro_no_object_fallback() {
|
||||
Some(Place::Unbound.into())
|
||||
} else {
|
||||
|
@ -2982,12 +2966,12 @@ impl<'db> Type<'db> {
|
|||
.to_instance(db)
|
||||
.instance_member(db, name),
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => {
|
||||
Type::object(db).instance_member(db, name)
|
||||
Type::object().instance_member(db, name)
|
||||
}
|
||||
|
||||
Type::NonInferableTypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => Type::object(db).instance_member(db, name),
|
||||
None => Type::object().instance_member(db, name),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
bound.instance_member(db, name)
|
||||
}
|
||||
|
@ -3020,7 +3004,7 @@ impl<'db> Type<'db> {
|
|||
.enum_class_instance(db)
|
||||
.instance_member(db, name),
|
||||
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object().instance_member(db, name),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType
|
||||
.to_instance(db)
|
||||
.instance_member(db, name),
|
||||
|
@ -3477,12 +3461,12 @@ impl<'db> Type<'db> {
|
|||
.member_lookup_with_policy(db, name, policy),
|
||||
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => {
|
||||
Type::object(db).member_lookup_with_policy(db, name, policy)
|
||||
Type::object().member_lookup_with_policy(db, name, policy)
|
||||
}
|
||||
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(name.as_str(), "major" | "minor")
|
||||
&& instance.class(db).is_known(db, KnownClass::VersionInfo) =>
|
||||
&& instance.has_known_class(db, KnownClass::VersionInfo) =>
|
||||
{
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let segment = if name == "major" {
|
||||
|
@ -3577,7 +3561,7 @@ impl<'db> Type<'db> {
|
|||
// resolve the attribute.
|
||||
if matches!(
|
||||
self.into_nominal_instance()
|
||||
.and_then(|instance| instance.class(db).known(db)),
|
||||
.and_then(|instance| instance.known_class(db)),
|
||||
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
|
||||
) {
|
||||
return Place::Unbound.into();
|
||||
|
@ -3906,8 +3890,7 @@ impl<'db> Type<'db> {
|
|||
Type::TypeVar(_) => Truthiness::Ambiguous,
|
||||
|
||||
Type::NominalInstance(instance) => instance
|
||||
.class(db)
|
||||
.known(db)
|
||||
.known_class(db)
|
||||
.and_then(KnownClass::bool)
|
||||
.map(Ok)
|
||||
.unwrap_or_else(try_dunder_bool)?,
|
||||
|
@ -4042,7 +4025,7 @@ impl<'db> Type<'db> {
|
|||
self,
|
||||
Signature::new(
|
||||
Parameters::new([Parameter::positional_only(Some(Name::new_static("func")))
|
||||
.with_annotated_type(Type::object(db))]),
|
||||
.with_annotated_type(Type::object())]),
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
@ -4307,7 +4290,7 @@ impl<'db> Type<'db> {
|
|||
Parameters::new([Parameter::positional_or_keyword(
|
||||
Name::new_static("object"),
|
||||
)
|
||||
.with_annotated_type(Type::object(db))
|
||||
.with_annotated_type(Type::object())
|
||||
.with_default_type(Type::string_literal(db, ""))]),
|
||||
Some(KnownClass::Str.to_instance(db)),
|
||||
),
|
||||
|
@ -4390,7 +4373,7 @@ impl<'db> Type<'db> {
|
|||
// ```
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new(Parameters::empty(), Some(Type::object(db))),
|
||||
Signature::new(Parameters::empty(), Some(Type::object())),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
@ -4631,7 +4614,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Some(KnownClass::Tuple) => {
|
||||
let object = Type::object(db);
|
||||
let object = Type::object();
|
||||
|
||||
// ```py
|
||||
// class tuple:
|
||||
|
@ -5648,7 +5631,7 @@ impl<'db> Type<'db> {
|
|||
// See conversation in https://github.com/astral-sh/ruff/pull/19915.
|
||||
SpecialFormType::NamedTuple => Ok(IntersectionBuilder::new(db)
|
||||
.positive_elements([
|
||||
Type::homogeneous_tuple(db, Type::object(db)),
|
||||
Type::homogeneous_tuple(db, Type::object()),
|
||||
KnownClass::NamedTupleLike.to_instance(db),
|
||||
])
|
||||
.build()),
|
||||
|
@ -5787,7 +5770,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
Type::Dynamic(_) => Ok(*self),
|
||||
|
||||
Type::NominalInstance(instance) => match instance.class(db).known(db) {
|
||||
Type::NominalInstance(instance) => match instance.known_class(db) {
|
||||
Some(KnownClass::TypeVar) => Ok(todo_type!(
|
||||
"Support for `typing.TypeVar` instances in type expressions"
|
||||
)),
|
||||
|
@ -5908,7 +5891,7 @@ impl<'db> Type<'db> {
|
|||
pub(crate) fn dunder_class(self, db: &'db dyn Db) -> Type<'db> {
|
||||
if self.is_typed_dict() {
|
||||
return KnownClass::Dict
|
||||
.to_specialized_class_type(db, [KnownClass::Str.to_instance(db), Type::object(db)])
|
||||
.to_specialized_class_type(db, [KnownClass::Str.to_instance(db), Type::object()])
|
||||
.map(Type::from)
|
||||
// Guard against user-customized typesheds with a broken `dict` class
|
||||
.unwrap_or_else(Type::unknown);
|
||||
|
@ -6142,7 +6125,7 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::MarkTypeVarsInferable(_) |
|
||||
TypeMapping::PromoteLiterals => self,
|
||||
TypeMapping::Materialize(materialization_kind) => match materialization_kind {
|
||||
MaterializationKind::Top => Type::object(db),
|
||||
MaterializationKind::Top => Type::object(),
|
||||
MaterializationKind::Bottom => Type::Never,
|
||||
}
|
||||
}
|
||||
|
@ -9096,7 +9079,7 @@ impl<'db> CallableType<'db> {
|
|||
/// `(*args: object, **kwargs: object) -> Never`.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn bottom(db: &'db dyn Db) -> Type<'db> {
|
||||
Self::single(db, Signature::bottom(db))
|
||||
Self::single(db, Signature::bottom())
|
||||
}
|
||||
|
||||
/// Return a "normalized" version of this `Callable` type.
|
||||
|
@ -9380,7 +9363,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
|||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
|
@ -9400,9 +9383,9 @@ impl<'db> KnownBoundMethodType<'db> {
|
|||
Either::Right(std::iter::once(Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
Parameter::positional_only(Some(Name::new_static("value")))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
]),
|
||||
None,
|
||||
)))
|
||||
|
@ -9482,7 +9465,7 @@ impl WrapperDescriptorKind {
|
|||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(descriptor),
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
|
@ -9503,7 +9486,7 @@ impl WrapperDescriptorKind {
|
|||
Either::Left(dunder_get_signatures(db, KnownClass::Property).into_iter())
|
||||
}
|
||||
WrapperDescriptorKind::PropertyDunderSet => {
|
||||
let object = Type::object(db);
|
||||
let object = Type::object();
|
||||
Either::Right(std::iter::once(Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
|
@ -10257,7 +10240,7 @@ impl<'db> IntersectionType<'db> {
|
|||
/// there are no positive elements, returns a single `object` type.
|
||||
fn positive_elements_or_object(self, db: &'db dyn Db) -> impl Iterator<Item = Type<'db>> {
|
||||
if self.positive(db).is_empty() {
|
||||
Either::Left(std::iter::once(Type::object(db)))
|
||||
Either::Left(std::iter::once(Type::object()))
|
||||
} else {
|
||||
Either::Right(self.positive(db).iter().copied())
|
||||
}
|
||||
|
|
|
@ -242,8 +242,7 @@ impl<'db> UnionBuilder<'db> {
|
|||
/// Collapse the union to a single type: `object`.
|
||||
fn collapse_to_object(&mut self) {
|
||||
self.elements.clear();
|
||||
self.elements
|
||||
.push(UnionElement::Type(Type::object(self.db)));
|
||||
self.elements.push(UnionElement::Type(Type::object()));
|
||||
}
|
||||
|
||||
/// Adds a type to this union.
|
||||
|
@ -448,7 +447,7 @@ impl<'db> UnionBuilder<'db> {
|
|||
}
|
||||
}
|
||||
// Adding `object` to a union results in `object`.
|
||||
ty if ty.is_object(self.db) => {
|
||||
ty if ty.is_object() => {
|
||||
self.collapse_to_object();
|
||||
}
|
||||
_ => {
|
||||
|
@ -648,8 +647,7 @@ impl<'db> IntersectionBuilder<'db> {
|
|||
self
|
||||
}
|
||||
Type::NominalInstance(instance)
|
||||
if enum_metadata(self.db, instance.class(self.db).class_literal(self.db).0)
|
||||
.is_some() =>
|
||||
if enum_metadata(self.db, instance.class_literal(self.db)).is_some() =>
|
||||
{
|
||||
let mut contains_enum_literal_as_negative_element = false;
|
||||
for intersection in &self.intersections {
|
||||
|
@ -674,7 +672,7 @@ impl<'db> IntersectionBuilder<'db> {
|
|||
self.add_positive_impl(
|
||||
Type::Union(UnionType::new(
|
||||
db,
|
||||
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
|
||||
enum_member_literals(db, instance.class_literal(db), None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.collect::<Box<[_]>>(),
|
||||
)),
|
||||
|
@ -860,7 +858,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
_ => {
|
||||
let known_instance = new_positive
|
||||
.into_nominal_instance()
|
||||
.and_then(|instance| instance.class(db).known(db));
|
||||
.and_then(|instance| instance.known_class(db));
|
||||
|
||||
if known_instance == Some(KnownClass::Object) {
|
||||
// `object & T` -> `T`; it is always redundant to add `object` to an intersection
|
||||
|
@ -880,7 +878,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
new_positive = Type::BooleanLiteral(false);
|
||||
}
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::Bool) =>
|
||||
if instance.has_known_class(db, KnownClass::Bool) =>
|
||||
{
|
||||
match new_positive {
|
||||
// `bool & AlwaysTruthy` -> `Literal[True]`
|
||||
|
@ -974,7 +972,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
self.positive
|
||||
.iter()
|
||||
.filter_map(|ty| ty.into_nominal_instance())
|
||||
.filter_map(|instance| instance.class(db).known(db))
|
||||
.filter_map(|instance| instance.known_class(db))
|
||||
.any(KnownClass::is_bool)
|
||||
};
|
||||
|
||||
|
@ -990,7 +988,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
Type::Never => {
|
||||
// Adding ~Never to an intersection is a no-op.
|
||||
}
|
||||
Type::NominalInstance(instance) if instance.is_object(db) => {
|
||||
Type::NominalInstance(instance) if instance.is_object() => {
|
||||
// Adding ~object to an intersection results in Never.
|
||||
*self = Self::default();
|
||||
self.positive.insert(Type::Never);
|
||||
|
@ -1152,7 +1150,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
fn build(mut self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.simplify_constrained_typevars(db);
|
||||
match (self.positive.len(), self.negative.len()) {
|
||||
(0, 0) => Type::object(db),
|
||||
(0, 0) => Type::object(),
|
||||
(1, 0) => self.positive[0],
|
||||
_ => {
|
||||
self.positive.shrink_to_fit();
|
||||
|
@ -1208,7 +1206,7 @@ mod tests {
|
|||
let db = setup_db();
|
||||
|
||||
let intersection = IntersectionBuilder::new(&db).build();
|
||||
assert_eq!(intersection, Type::object(&db));
|
||||
assert_eq!(intersection, Type::object());
|
||||
}
|
||||
|
||||
#[test_case(Type::BooleanLiteral(true))]
|
||||
|
@ -1222,7 +1220,7 @@ mod tests {
|
|||
// We add t_object in various orders (in first or second position) in
|
||||
// the tests below to ensure that the boolean simplification eliminates
|
||||
// everything from the intersection, not just `bool`.
|
||||
let t_object = Type::object(&db);
|
||||
let t_object = Type::object();
|
||||
let t_bool = KnownClass::Bool.to_instance(&db);
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
|
|
|
@ -1313,7 +1313,7 @@ impl<'db> Field<'db> {
|
|||
pub(crate) fn is_kw_only_sentinel(&self, db: &'db dyn Db) -> bool {
|
||||
self.declared_ty
|
||||
.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::KwOnly))
|
||||
.is_some_and(|instance| instance.has_known_class(db, KnownClass::KwOnly))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ impl<'db> ClassBase<'db> {
|
|||
Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))),
|
||||
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::GenericAlias) =>
|
||||
if instance.has_known_class(db, KnownClass::GenericAlias) =>
|
||||
{
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"), subclass)
|
||||
}
|
||||
|
|
|
@ -1042,7 +1042,7 @@ impl<'db> Constraint<'db> {
|
|||
|
||||
// If the requested constraint is `Never ≤ T ≤ object`, then the typevar can be specialized
|
||||
// to _any_ type, and the constraint does nothing.
|
||||
if lower.is_never() && upper.is_object(db) {
|
||||
if lower.is_never() && upper.is_object() {
|
||||
return Satisfiable::Always;
|
||||
}
|
||||
|
||||
|
@ -1189,7 +1189,7 @@ impl<'db> RangeConstraint<'db> {
|
|||
ConstraintClause::from_constraints(
|
||||
db,
|
||||
[
|
||||
Constraint::range(db, self.upper, Type::object(db)).constrain(typevar),
|
||||
Constraint::range(db, self.upper, Type::object()).constrain(typevar),
|
||||
Constraint::not_equivalent(db, self.upper).constrain(typevar),
|
||||
],
|
||||
),
|
||||
|
@ -1214,7 +1214,7 @@ impl<'db> RangeConstraint<'db> {
|
|||
write!(f, "{} ≤ ", self.constraint.lower.display(self.db))?;
|
||||
}
|
||||
self.typevar.fmt(f)?;
|
||||
if !self.constraint.upper.is_object(self.db) {
|
||||
if !self.constraint.upper.is_object() {
|
||||
write!(f, " ≤ {}", self.constraint.upper.display(self.db))?;
|
||||
}
|
||||
f.write_str(")")
|
||||
|
@ -1356,7 +1356,7 @@ impl<'db> Constraint<'db> {
|
|||
debug_assert_eq!(ty, ty.top_materialization(db));
|
||||
|
||||
// Every type is comparable to Never and to object.
|
||||
if ty.is_never() || ty.is_object(db) {
|
||||
if ty.is_never() || ty.is_object() {
|
||||
return Satisfiable::Never;
|
||||
}
|
||||
|
||||
|
@ -1407,7 +1407,7 @@ impl<'db> IncomparableConstraint<'db> {
|
|||
);
|
||||
set.union_constraint(
|
||||
db,
|
||||
Constraint::range(db, self.ty, Type::object(db)).constrain(typevar),
|
||||
Constraint::range(db, self.ty, Type::object()).constrain(typevar),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -90,9 +90,7 @@ impl DisplaySettings {
|
|||
fn type_to_class_literal<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<ClassLiteral<'db>> {
|
||||
match ty {
|
||||
Type::ClassLiteral(class) => Some(class),
|
||||
Type::NominalInstance(instance) => {
|
||||
type_to_class_literal(db, Type::from(instance.class(db)))
|
||||
}
|
||||
Type::NominalInstance(instance) => Some(instance.class_literal(db)),
|
||||
Type::EnumLiteral(enum_literal) => Some(enum_literal.enum_class(db)),
|
||||
Type::GenericAlias(alias) => Some(alias.origin(db)),
|
||||
Type::ProtocolInstance(ProtocolInstanceType {
|
||||
|
@ -1648,7 +1646,7 @@ mod tests {
|
|||
.with_annotated_type(KnownClass::Int.to_instance(&db))
|
||||
.with_default_type(Type::IntLiteral(4)),
|
||||
Parameter::variadic(Name::new_static("args"))
|
||||
.with_annotated_type(Type::object(&db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
Parameter::keyword_only(Name::new_static("g"))
|
||||
.with_default_type(Type::IntLiteral(5)),
|
||||
Parameter::keyword_only(Name::new_static("h"))
|
||||
|
@ -1804,7 +1802,7 @@ mod tests {
|
|||
.with_annotated_type(KnownClass::Int.to_instance(&db))
|
||||
.with_default_type(Type::IntLiteral(4)),
|
||||
Parameter::variadic(Name::new_static("args"))
|
||||
.with_annotated_type(Type::object(&db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
Parameter::keyword_only(Name::new_static("g"))
|
||||
.with_default_type(Type::IntLiteral(5)),
|
||||
Parameter::keyword_only(Name::new_static("h"))
|
||||
|
|
|
@ -130,7 +130,7 @@ pub(crate) fn enum_metadata<'db>(
|
|||
// Some types are specifically disallowed for enum members.
|
||||
return None;
|
||||
}
|
||||
Type::NominalInstance(instance) => match instance.class(db).known(db) {
|
||||
Type::NominalInstance(instance) => match instance.known_class(db) {
|
||||
// enum.nonmember
|
||||
Some(KnownClass::Nonmember) => return None,
|
||||
|
||||
|
@ -208,7 +208,7 @@ pub(crate) fn enum_metadata<'db>(
|
|||
PlaceAndQualifiers {
|
||||
place: Place::Type(Type::NominalInstance(instance), _),
|
||||
..
|
||||
} if instance.class(db).is_known(db, KnownClass::Member) => {
|
||||
} if instance.has_known_class(db, KnownClass::Member) => {
|
||||
// If the attribute is specifically declared with `enum.member`, it is considered a member
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -1133,10 +1133,10 @@ fn signature_cycle_recover<'db>(
|
|||
}
|
||||
|
||||
fn signature_cycle_initial<'db>(
|
||||
db: &'db dyn Db,
|
||||
_db: &'db dyn Db,
|
||||
_function: FunctionType<'db>,
|
||||
) -> CallableSignature<'db> {
|
||||
CallableSignature::single(Signature::bottom(db))
|
||||
CallableSignature::single(Signature::bottom())
|
||||
}
|
||||
|
||||
fn last_definition_signature_cycle_recover<'db>(
|
||||
|
@ -1149,10 +1149,10 @@ fn last_definition_signature_cycle_recover<'db>(
|
|||
}
|
||||
|
||||
fn last_definition_signature_cycle_initial<'db>(
|
||||
db: &'db dyn Db,
|
||||
_db: &'db dyn Db,
|
||||
_function: FunctionType<'db>,
|
||||
) -> Signature<'db> {
|
||||
Signature::bottom(db)
|
||||
Signature::bottom()
|
||||
}
|
||||
|
||||
/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might
|
||||
|
|
|
@ -95,8 +95,7 @@ impl<'db> AllMembers<'db> {
|
|||
),
|
||||
|
||||
Type::NominalInstance(instance) => {
|
||||
let (class_literal, _specialization) = instance.class(db).class_literal(db);
|
||||
self.extend_with_instance_members(db, ty, class_literal);
|
||||
self.extend_with_instance_members(db, ty, instance.class_literal(db));
|
||||
}
|
||||
|
||||
Type::ClassLiteral(class_literal) if class_literal.is_typed_dict(db) => {
|
||||
|
@ -211,7 +210,7 @@ impl<'db> AllMembers<'db> {
|
|||
match ty {
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(
|
||||
instance.class(db).known(db),
|
||||
instance.known_class(db),
|
||||
Some(
|
||||
KnownClass::TypeVar
|
||||
| KnownClass::TypeVarTuple
|
||||
|
|
|
@ -1245,7 +1245,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(
|
||||
instance.class(self.db()).known(self.db()),
|
||||
instance.known_class(self.db()),
|
||||
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
|
||||
) => {}
|
||||
_ => return false,
|
||||
|
@ -3411,9 +3411,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
),
|
||||
|
||||
// Super instances do not allow attribute assignment
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::Super) =>
|
||||
{
|
||||
Type::NominalInstance(instance) if instance.has_known_class(db, KnownClass::Super) => {
|
||||
if emit_diagnostics {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
|
@ -7559,10 +7557,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// We didn't see any positive elements, check if the operation is supported on `object`:
|
||||
match intersection_on {
|
||||
IntersectionOn::Left => {
|
||||
self.infer_binary_type_comparison(Type::object(self.db()), op, other, range)
|
||||
self.infer_binary_type_comparison(Type::object(), op, other, range)
|
||||
}
|
||||
IntersectionOn::Right => {
|
||||
self.infer_binary_type_comparison(other, op, Type::object(self.db()), range)
|
||||
self.infer_binary_type_comparison(other, op, Type::object(), range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8733,7 +8731,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
} else if any_over_type(self.db(), *typevar, &|ty| match ty {
|
||||
Type::Dynamic(DynamicType::TodoUnpack) => true,
|
||||
Type::NominalInstance(nominal) => matches!(
|
||||
nominal.class(self.db()).known(self.db()),
|
||||
nominal.known_class(self.db()),
|
||||
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
|
||||
),
|
||||
_ => false,
|
||||
|
@ -8776,9 +8774,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let type_to_slice_argument = |ty: Option<Type<'db>>| match ty {
|
||||
Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty),
|
||||
Some(ty @ Type::NominalInstance(instance))
|
||||
if instance
|
||||
.class(self.db())
|
||||
.is_known(self.db(), KnownClass::NoneType) =>
|
||||
if instance.has_known_class(self.db(), KnownClass::NoneType) =>
|
||||
{
|
||||
SliceArg::Arg(ty)
|
||||
}
|
||||
|
|
|
@ -1521,9 +1521,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
if any_over_type(self.db(), self.infer_name_load(name), &|ty| match ty {
|
||||
Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => true,
|
||||
Type::NominalInstance(nominal) => nominal
|
||||
.class(self.db())
|
||||
.is_known(self.db(), KnownClass::ParamSpec),
|
||||
Type::NominalInstance(nominal) => {
|
||||
nominal.has_known_class(self.db(), KnownClass::ParamSpec)
|
||||
}
|
||||
_ => false,
|
||||
}) {
|
||||
return Some(Parameters::todo());
|
||||
|
|
|
@ -12,32 +12,44 @@ use crate::types::enums::is_single_member_enum;
|
|||
use crate::types::protocol_class::walk_protocol_interface;
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, ClassBase, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation,
|
||||
VarianceInferable,
|
||||
ApplyTypeMappingVisitor, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeMapping,
|
||||
TypeRelation, VarianceInferable,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) const fn object() -> Self {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::Object))
|
||||
}
|
||||
|
||||
pub(crate) const fn is_object(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::Object))
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
|
||||
if class_literal.is_known(db, KnownClass::Tuple) {
|
||||
Type::tuple(TupleType::new(
|
||||
match class_literal.known(db) {
|
||||
Some(KnownClass::Tuple) => Type::tuple(TupleType::new(
|
||||
db,
|
||||
specialization
|
||||
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
|
||||
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown())))
|
||||
.as_ref(),
|
||||
))
|
||||
} else if class_literal.is_protocol(db) {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
||||
} else if class_literal.is_typed_dict(db) {
|
||||
Type::typed_dict(class)
|
||||
} else {
|
||||
Type::non_tuple_instance(class)
|
||||
)),
|
||||
Some(KnownClass::Object) => Type::object(),
|
||||
_ if class_literal.is_protocol(db) => {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
||||
}
|
||||
_ if class_literal.is_typed_dict(db) => Type::typed_dict(class),
|
||||
// We don't call non_tuple_instance here because we've already checked that the class
|
||||
// is not `object`
|
||||
_ => Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,8 +86,12 @@ impl<'db> Type<'db> {
|
|||
|
||||
/// **Private** helper function to create a `Type::NominalInstance` from a class that
|
||||
/// is known not to be `Any`, a protocol class, or a typed dict class.
|
||||
fn non_tuple_instance(class: ClassType<'db>) -> Self {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||
fn non_tuple_instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
if class.is_known(db, KnownClass::Object) {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::Object))
|
||||
} else {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
|
||||
|
@ -132,7 +148,12 @@ impl<'db> Type<'db> {
|
|||
// recognise `str` as a subtype of `Container[str]`.
|
||||
structurally_satisfied.or(db, || {
|
||||
if let Protocol::FromClass(class) = protocol.inner {
|
||||
self.has_relation_to_impl(db, Type::non_tuple_instance(class), relation, visitor)
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
Type::non_tuple_instance(db, class),
|
||||
relation,
|
||||
visitor,
|
||||
)
|
||||
} else {
|
||||
C::unsatisfiable(db)
|
||||
}
|
||||
|
@ -161,9 +182,42 @@ impl<'db> NominalInstanceType<'db> {
|
|||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
NominalInstanceInner::Object => KnownClass::Object
|
||||
.try_to_class_literal(db)
|
||||
.expect("Typeshed should always have a `object` class in `builtins.pyi`")
|
||||
.default_specialization(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn class_literal(&self, db: &'db dyn Db) -> ClassLiteral<'db> {
|
||||
let class = match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
NominalInstanceInner::Object => {
|
||||
return KnownClass::Object
|
||||
.try_to_class_literal(db)
|
||||
.expect("Typeshed should always have a `object` class in `builtins.pyi`");
|
||||
}
|
||||
};
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
class_literal
|
||||
}
|
||||
|
||||
/// Returns the [`KnownClass`] that this is a nominal instance of, or `None` if it is not an
|
||||
/// instance of a known class.
|
||||
pub(super) fn known_class(&self, db: &'db dyn Db) -> Option<KnownClass> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(_) => Some(KnownClass::Tuple),
|
||||
NominalInstanceInner::NonTuple(class) => class.known(db),
|
||||
NominalInstanceInner::Object => Some(KnownClass::Object),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this is a nominal instance of a particular [`KnownClass`].
|
||||
pub(super) fn has_known_class(&self, db: &'db dyn Db, known_class: KnownClass) -> bool {
|
||||
self.known_class(db) == Some(known_class)
|
||||
}
|
||||
|
||||
/// If this is an instance type where the class has a tuple spec, returns the tuple spec.
|
||||
///
|
||||
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
|
||||
|
@ -203,15 +257,13 @@ impl<'db> NominalInstanceType<'db> {
|
|||
_ => None,
|
||||
})
|
||||
}
|
||||
NominalInstanceInner::Object => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if this type represents instances of the class `builtins.object`.
|
||||
pub(super) fn is_object(self, db: &'db dyn Db) -> bool {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(_) => false,
|
||||
NominalInstanceInner::NonTuple(class) => class.is_object(db),
|
||||
}
|
||||
pub(super) const fn is_object(self) -> bool {
|
||||
matches!(self.0, NominalInstanceInner::Object)
|
||||
}
|
||||
|
||||
/// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
|
||||
|
@ -227,7 +279,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
pub(super) fn own_tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
|
||||
NominalInstanceInner::NonTuple(_) => None,
|
||||
NominalInstanceInner::NonTuple(_) | NominalInstanceInner::Object => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,7 +290,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
/// integers or `None`.
|
||||
pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> {
|
||||
let class = match self.0 {
|
||||
NominalInstanceInner::ExactTuple(_) => return None,
|
||||
NominalInstanceInner::ExactTuple(_) | NominalInstanceInner::Object => return None,
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
};
|
||||
let (class, Some(specialization)) = class.class_literal(db) else {
|
||||
|
@ -255,7 +307,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
|
||||
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::NoneType) =>
|
||||
if instance.has_known_class(db, KnownClass::NoneType) =>
|
||||
{
|
||||
Some(None)
|
||||
}
|
||||
|
@ -278,8 +330,9 @@ impl<'db> NominalInstanceType<'db> {
|
|||
Type::tuple(tuple.normalized_impl(db, visitor))
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
Type::non_tuple_instance(class.normalized_impl(db, visitor))
|
||||
Type::non_tuple_instance(db, class.normalized_impl(db, visitor))
|
||||
}
|
||||
NominalInstanceInner::Object => Type::object(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,6 +344,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
visitor: &HasRelationToVisitor<'db, C>,
|
||||
) -> C {
|
||||
match (self.0, other.0) {
|
||||
(_, NominalInstanceInner::Object) => C::always_satisfiable(db),
|
||||
(
|
||||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
|
@ -312,6 +366,9 @@ impl<'db> NominalInstanceType<'db> {
|
|||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
) => tuple1.is_equivalent_to_impl(db, tuple2, visitor),
|
||||
(NominalInstanceInner::Object, NominalInstanceInner::Object) => {
|
||||
C::always_satisfiable(db)
|
||||
}
|
||||
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
|
||||
class1.is_equivalent_to_impl(db, class2, visitor)
|
||||
}
|
||||
|
@ -325,6 +382,9 @@ impl<'db> NominalInstanceType<'db> {
|
|||
other: Self,
|
||||
visitor: &IsDisjointVisitor<'db, C>,
|
||||
) -> C {
|
||||
if self.is_object() || other.is_object() {
|
||||
return C::unsatisfiable(db);
|
||||
}
|
||||
let mut result = C::unsatisfiable(db);
|
||||
if let Some(self_spec) = self.tuple_spec(db) {
|
||||
if let Some(other_spec) = other.tuple_spec(db) {
|
||||
|
@ -349,7 +409,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
// should not be relied on for type narrowing, so we do not treat it as one.
|
||||
// See:
|
||||
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
|
||||
NominalInstanceInner::ExactTuple(_) => false,
|
||||
NominalInstanceInner::ExactTuple(_) | NominalInstanceInner::Object => false,
|
||||
NominalInstanceInner::NonTuple(class) => class
|
||||
.known(db)
|
||||
.map(KnownClass::is_singleton)
|
||||
|
@ -360,6 +420,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db),
|
||||
NominalInstanceInner::Object => false,
|
||||
NominalInstanceInner::NonTuple(class) => class
|
||||
.known(db)
|
||||
.and_then(KnownClass::is_single_valued)
|
||||
|
@ -382,9 +443,11 @@ impl<'db> NominalInstanceType<'db> {
|
|||
NominalInstanceInner::ExactTuple(tuple) => {
|
||||
Type::tuple(tuple.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
Type::non_tuple_instance(class.apply_type_mapping_impl(db, type_mapping, visitor))
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => Type::non_tuple_instance(
|
||||
db,
|
||||
class.apply_type_mapping_impl(db, type_mapping, visitor),
|
||||
),
|
||||
NominalInstanceInner::Object => Type::object(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,6 +465,7 @@ impl<'db> NominalInstanceType<'db> {
|
|||
NominalInstanceInner::NonTuple(class) => {
|
||||
class.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
|
||||
}
|
||||
NominalInstanceInner::Object => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -417,6 +481,12 @@ impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
|
|||
/// instances where it would be unnecessary (this is somewhat expensive!).
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)]
|
||||
enum NominalInstanceInner<'db> {
|
||||
/// An instance of `object`.
|
||||
///
|
||||
/// We model it with a dedicated enum variant since its use as "the type of all values" is so
|
||||
/// prevalent and foundational, and it's useful to be able to instantiate this without having
|
||||
/// to load the definition of `object` from the typeshed.
|
||||
Object,
|
||||
/// A tuple type, e.g. `tuple[int, str]`.
|
||||
///
|
||||
/// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`,
|
||||
|
@ -514,7 +584,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
pub(super) fn is_equivalent_to_object(self, db: &'db dyn Db) -> bool {
|
||||
#[salsa::tracked(cycle_fn=recover, cycle_initial=initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
fn inner<'db>(db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, _: ()) -> bool {
|
||||
Type::object(db)
|
||||
Type::object()
|
||||
.satisfies_protocol(
|
||||
db,
|
||||
protocol,
|
||||
|
@ -558,7 +628,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
visitor: &NormalizedVisitor<'db>,
|
||||
) -> Type<'db> {
|
||||
if self.is_equivalent_to_object(db) {
|
||||
return Type::object(db);
|
||||
return Type::object();
|
||||
}
|
||||
match self.inner {
|
||||
Protocol::FromClass(_) => Type::ProtocolInstance(Self::synthesized(
|
||||
|
|
|
@ -292,13 +292,13 @@ fn merge_constraints_or<'db>(
|
|||
*entry.get_mut() = UnionBuilder::new(db).add(*entry.get()).add(*value).build();
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(Type::object(db));
|
||||
entry.insert(Type::object());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (key, value) in into.iter_mut() {
|
||||
if !from.contains_key(key) {
|
||||
*value = Type::object(db);
|
||||
*value = Type::object();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -554,7 +554,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
}
|
||||
// Treat `bool` as `Literal[True, False]`.
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::Bool) =>
|
||||
if instance.has_known_class(db, KnownClass::Bool) =>
|
||||
{
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
|
@ -565,11 +565,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
}
|
||||
// Treat enums as a union of their members.
|
||||
Type::NominalInstance(instance)
|
||||
if enum_metadata(db, instance.class(db).class_literal(db).0).is_some() =>
|
||||
if enum_metadata(db, instance.class_literal(db)).is_some() =>
|
||||
{
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
|
||||
enum_member_literals(db, instance.class_literal(db), None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
|
||||
)
|
||||
|
@ -596,7 +596,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
|
||||
match (lhs_ty, rhs_ty) {
|
||||
(Type::NominalInstance(instance), Type::IntLiteral(i))
|
||||
if instance.class(self.db).is_known(self.db, KnownClass::Bool) =>
|
||||
if instance.has_known_class(self.db, KnownClass::Bool) =>
|
||||
{
|
||||
if i == 0 {
|
||||
Some(Type::BooleanLiteral(false).negate(self.db))
|
||||
|
@ -912,10 +912,8 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
|
||||
// Since `hasattr` only checks if an attribute is readable,
|
||||
// the type of the protocol member should be a read-only property that returns `object`.
|
||||
let constraint = Type::protocol_with_readonly_members(
|
||||
self.db,
|
||||
[(attr, Type::object(self.db))],
|
||||
);
|
||||
let constraint =
|
||||
Type::protocol_with_readonly_members(self.db, [(attr, Type::object())]);
|
||||
|
||||
return Some(NarrowingConstraints::from_iter([(
|
||||
place,
|
||||
|
|
|
@ -133,13 +133,13 @@ mod stable {
|
|||
// All types should be assignable to `object`
|
||||
type_property_test!(
|
||||
all_types_assignable_to_object, db,
|
||||
forall types t. t.is_assignable_to(db, Type::object(db))
|
||||
forall types t. t.is_assignable_to(db, Type::object())
|
||||
);
|
||||
|
||||
// And all types should be subtypes of `object`
|
||||
type_property_test!(
|
||||
all_types_subtype_of_object, db,
|
||||
forall types t. t.is_subtype_of(db, Type::object(db))
|
||||
forall types t. t.is_subtype_of(db, Type::object())
|
||||
);
|
||||
|
||||
// Never should be assignable to every type
|
||||
|
@ -182,7 +182,7 @@ mod stable {
|
|||
// Only `object` is a supertype of `Any`.
|
||||
type_property_test!(
|
||||
only_object_is_supertype_of_any, db,
|
||||
forall types t. !t.is_equivalent_to(db, Type::object(db)) => !Type::any().is_subtype_of(db, t)
|
||||
forall types t. !t.is_equivalent_to(db, Type::object()) => !Type::any().is_subtype_of(db, t)
|
||||
);
|
||||
|
||||
// Equivalence is commutative.
|
||||
|
@ -332,6 +332,6 @@ mod flaky {
|
|||
// Currently flaky due to <https://github.com/astral-sh/ty/issues/889>
|
||||
type_property_test!(
|
||||
all_type_assignable_to_iterable_are_iterable, db,
|
||||
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object(db)])) => t.try_iterate(db).is_ok()
|
||||
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object()])) => t.try_iterate(db).is_ok()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ impl Ty {
|
|||
.place
|
||||
.expect_type();
|
||||
debug_assert!(
|
||||
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class(db).class_literal(db).0))
|
||||
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class_literal(db)))
|
||||
);
|
||||
ty
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ impl<'db> ProtocolInterface<'db> {
|
|||
place: Place::bound(member.ty()),
|
||||
qualifiers: member.qualifiers(),
|
||||
})
|
||||
.unwrap_or_else(|| Type::object(db).member(db, name))
|
||||
.unwrap_or_else(|| Type::object().member(db, name))
|
||||
}
|
||||
|
||||
/// Return `true` if `self` extends the interface of `other`, i.e.,
|
||||
|
|
|
@ -408,8 +408,8 @@ impl<'db> Signature<'db> {
|
|||
}
|
||||
|
||||
/// Return the "bottom" signature, subtype of all other fully-static signatures.
|
||||
pub(crate) fn bottom(db: &'db dyn Db) -> Self {
|
||||
Self::new(Parameters::object(db), Some(Type::Never))
|
||||
pub(crate) fn bottom() -> Self {
|
||||
Self::new(Parameters::object(), Some(Type::Never))
|
||||
}
|
||||
|
||||
pub(crate) fn with_inherited_generic_context(
|
||||
|
@ -704,11 +704,11 @@ impl<'db> Signature<'db> {
|
|||
&& self
|
||||
.parameters
|
||||
.variadic()
|
||||
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object(db)))
|
||||
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object()))
|
||||
&& self
|
||||
.parameters
|
||||
.keyword_variadic()
|
||||
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object(db)))
|
||||
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object()))
|
||||
{
|
||||
return C::always_satisfiable(db);
|
||||
}
|
||||
|
@ -1142,12 +1142,12 @@ impl<'db> Parameters<'db> {
|
|||
}
|
||||
|
||||
/// Return parameters that represents `(*args: object, **kwargs: object)`.
|
||||
pub(crate) fn object(db: &'db dyn Db) -> Self {
|
||||
pub(crate) fn object() -> Self {
|
||||
Self {
|
||||
value: vec![
|
||||
Parameter::variadic(Name::new_static("args")).with_annotated_type(Type::object(db)),
|
||||
Parameter::variadic(Name::new_static("args")).with_annotated_type(Type::object()),
|
||||
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(Type::object()),
|
||||
],
|
||||
is_gradual: false,
|
||||
}
|
||||
|
@ -1274,7 +1274,7 @@ impl<'db> Parameters<'db> {
|
|||
// so the "top" materialization here is the bottom materialization of the whole Signature.
|
||||
// It might make sense to flip the materialization here instead.
|
||||
TypeMapping::Materialize(MaterializationKind::Top) if self.is_gradual => {
|
||||
Parameters::object(db)
|
||||
Parameters::object()
|
||||
}
|
||||
// TODO: This is wrong, the empty Parameters is not a subtype of all materializations.
|
||||
// The bottom materialization is not currently representable and implementing it
|
||||
|
@ -1779,8 +1779,7 @@ mod tests {
|
|||
Parameter::positional_or_keyword(Name::new_static("f"))
|
||||
.with_annotated_type(Type::IntLiteral(4))
|
||||
.with_default_type(Type::IntLiteral(4)),
|
||||
Parameter::variadic(Name::new_static("args"))
|
||||
.with_annotated_type(Type::object(&db)),
|
||||
Parameter::variadic(Name::new_static("args")).with_annotated_type(Type::object()),
|
||||
Parameter::keyword_only(Name::new_static("g"))
|
||||
.with_default_type(Type::IntLiteral(5)),
|
||||
Parameter::keyword_only(Name::new_static("h"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue