[red-knot] Add Type constructors for Instance, ClassLiteral and SubclassOf variants (#14215)

## Summary

Reduces some repetetiveness and verbosity at callsites. Addresses
@carljm's review comments at
https://github.com/astral-sh/ruff/pull/14155/files#r1833252458

## Test Plan

`cargo test -p red_knot_python_semantic`
This commit is contained in:
Alex Waygood 2024-11-09 09:10:00 +00:00 committed by GitHub
parent eea6b31980
commit d3f1c8e536
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 51 deletions

View file

@ -369,6 +369,10 @@ impl<'db> Type<'db> {
matches!(self, Type::Todo)
}
pub const fn class_literal(class: Class<'db>) -> Self {
Self::ClassLiteral(ClassLiteralType { class })
}
pub const fn into_class_literal(self) -> Option<ClassLiteralType<'db>> {
match self {
Type::ClassLiteral(class_type) => Some(class_type),
@ -399,20 +403,6 @@ impl<'db> Type<'db> {
.expect("Expected a Type::ModuleLiteral variant")
}
#[must_use]
pub fn negate(&self, db: &'db dyn Db) -> Type<'db> {
IntersectionBuilder::new(db).add_negative(*self).build()
}
#[must_use]
pub fn negate_if(&self, db: &'db dyn Db, yes: bool) -> Type<'db> {
if yes {
self.negate(db)
} else {
*self
}
}
pub const fn into_union(self) -> Option<UnionType<'db>> {
match self {
Type::Union(union_type) => Some(union_type),
@ -480,6 +470,28 @@ impl<'db> Type<'db> {
matches!(self, Type::LiteralString)
}
pub const fn instance(class: Class<'db>) -> Self {
Self::Instance(InstanceType { class })
}
pub const fn subclass_of(class: Class<'db>) -> Self {
Self::SubclassOf(SubclassOfType { class })
}
#[must_use]
pub fn negate(&self, db: &'db dyn Db) -> Type<'db> {
IntersectionBuilder::new(db).add_negative(*self).build()
}
#[must_use]
pub fn negate_if(&self, db: &'db dyn Db, yes: bool) -> Type<'db> {
if yes {
self.negate(db)
} else {
*self
}
}
/// Return true if this type is a [subtype of] type `target`.
///
/// [subtype of]: https://typing.readthedocs.io/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
@ -1371,12 +1383,8 @@ impl<'db> Type<'db> {
Type::Todo => Type::Todo,
Type::Unknown => Type::Unknown,
Type::Never => Type::Never,
Type::ClassLiteral(ClassLiteralType { class }) => {
Type::Instance(InstanceType { class: *class })
}
Type::SubclassOf(SubclassOfType { class }) => {
Type::Instance(InstanceType { class: *class })
}
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class),
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
// TODO: we can probably do better here: --Alex
Type::Intersection(_) => Type::Todo,
@ -1420,13 +1428,13 @@ impl<'db> Type<'db> {
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db),
Type::Tuple(_) => KnownClass::Tuple.to_class(db),
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
Type::SubclassOf(SubclassOfType { class }) => Type::SubclassOf(
Type::SubclassOf(SubclassOfType { class }) => Type::subclass_of(
class
.try_metaclass(db)
.ok()
.and_then(Type::into_class_literal)
.unwrap_or(KnownClass::Type.to_class(db).expect_class_literal())
.to_subclass_of_type(),
.unwrap_or_else(|| KnownClass::Type.to_class(db).expect_class_literal())
.class,
),
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class(db),
// TODO: `type[Any]`?
@ -2528,9 +2536,7 @@ impl<'db> Class<'db> {
});
}
Ok(Type::ClassLiteral(ClassLiteralType {
class: candidate.metaclass,
}))
Ok(Type::class_literal(candidate.metaclass))
}
/// Returns the class member of this class named `name`.
@ -2629,10 +2635,6 @@ impl<'db> ClassLiteralType<'db> {
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
self.class.class_member(db, name)
}
fn to_subclass_of_type(self) -> SubclassOfType<'db> {
SubclassOfType { class: self.class }
}
}
impl<'db> From<ClassLiteralType<'db>> for Type<'db> {
@ -3053,13 +3055,11 @@ mod tests {
assert!(literal_derived.is_class_literal());
// `subclass_of_base` represents `Type[Base]`.
let subclass_of_base =
Type::SubclassOf(literal_base.expect_class_literal().to_subclass_of_type());
let subclass_of_base = Type::subclass_of(literal_base.expect_class_literal().class);
assert!(literal_base.is_subtype_of(&db, subclass_of_base));
assert!(literal_derived.is_subtype_of(&db, subclass_of_base));
let subclass_of_derived =
Type::SubclassOf(literal_derived.expect_class_literal().to_subclass_of_type());
let subclass_of_derived = Type::subclass_of(literal_derived.expect_class_literal().class);
assert!(literal_derived.is_subtype_of(&db, subclass_of_derived));
assert!(!literal_base.is_subtype_of(&db, subclass_of_derived));
@ -3213,10 +3213,8 @@ mod tests {
let literal_a = super::global_symbol(&db, module, "A").expect_type();
let literal_b = super::global_symbol(&db, module, "B").expect_type();
let subclass_of_a =
Type::SubclassOf(literal_a.expect_class_literal().to_subclass_of_type());
let subclass_of_b =
Type::SubclassOf(literal_b.expect_class_literal().to_subclass_of_type());
let subclass_of_a = Type::subclass_of(literal_a.expect_class_literal().class);
let subclass_of_b = Type::subclass_of(literal_b.expect_class_literal().class);
// Class literals are always disjoint. They are singleton types
assert!(literal_a.is_disjoint_from(&db, literal_b));

View file

@ -1042,7 +1042,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.and_then(|module| KnownClass::maybe_from_module(module, name.as_str()));
let class = Class::new(self.db, &*name.id, body_scope, maybe_known_class);
let class_ty = Type::ClassLiteral(ClassLiteralType { class });
let class_ty = Type::class_literal(class);
self.add_declaration_with_binding(class_node.into(), definition, class_ty, class_ty);
@ -1349,15 +1349,13 @@ impl<'db> TypeInferenceBuilder<'db> {
// anything else is invalid and should lead to a diagnostic being reported --Alex
match node_ty {
Type::Any | Type::Unknown => node_ty,
Type::ClassLiteral(ClassLiteralType { class }) => {
Type::Instance(InstanceType { class })
}
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(class),
Type::Tuple(tuple) => UnionType::from_elements(
self.db,
tuple.elements(self.db).iter().map(|ty| {
ty.into_class_literal()
.map_or(Type::Todo, |ClassLiteralType { class }| {
Type::Instance(InstanceType { class })
Type::instance(class)
})
}),
),
@ -4283,8 +4281,8 @@ impl<'db> TypeInferenceBuilder<'db> {
match slice {
ast::Expr::Name(name) => {
let name_ty = self.infer_name_expression(name);
if let Some(class_literal) = name_ty.into_class_literal() {
Type::SubclassOf(class_literal.to_subclass_of_type())
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
Type::subclass_of(class)
} else {
Type::Todo
}

View file

@ -406,7 +406,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
ClassBase::Any => Type::Any,
ClassBase::Todo => Type::Todo,
ClassBase::Unknown => Type::Unknown,
ClassBase::Class(class) => Type::ClassLiteral(ClassLiteralType { class }),
ClassBase::Class(class) => Type::class_literal(class),
}
}
}

View file

@ -5,7 +5,7 @@ use crate::semantic_index::expression::Expression;
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
use crate::semantic_index::symbol_table;
use crate::types::{
infer_expression_types, ClassLiteralType, InstanceType, IntersectionBuilder, KnownClass,
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass,
KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder,
};
use crate::Db;
@ -353,14 +353,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
let to_constraint = match function {
KnownConstraintFunction::IsInstance => {
|class_literal: ClassLiteralType<'db>| {
Type::Instance(InstanceType {
class: class_literal.class,
})
Type::instance(class_literal.class)
}
}
KnownConstraintFunction::IsSubclass => {
|class_literal: ClassLiteralType<'db>| {
Type::SubclassOf(class_literal.to_subclass_of_type())
Type::subclass_of(class_literal.class)
}
}
};