[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) 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>> { pub const fn into_class_literal(self) -> Option<ClassLiteralType<'db>> {
match self { match self {
Type::ClassLiteral(class_type) => Some(class_type), Type::ClassLiteral(class_type) => Some(class_type),
@ -399,20 +403,6 @@ impl<'db> Type<'db> {
.expect("Expected a Type::ModuleLiteral variant") .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>> { pub const fn into_union(self) -> Option<UnionType<'db>> {
match self { match self {
Type::Union(union_type) => Some(union_type), Type::Union(union_type) => Some(union_type),
@ -480,6 +470,28 @@ impl<'db> Type<'db> {
matches!(self, Type::LiteralString) 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`. /// 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 /// [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::Todo => Type::Todo,
Type::Unknown => Type::Unknown, Type::Unknown => Type::Unknown,
Type::Never => Type::Never, Type::Never => Type::Never,
Type::ClassLiteral(ClassLiteralType { class }) => { Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
Type::Instance(InstanceType { class: *class }) Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class),
}
Type::SubclassOf(SubclassOfType { class }) => {
Type::Instance(InstanceType { class: *class })
}
Type::Union(union) => union.map(db, |element| element.to_instance(db)), Type::Union(union) => union.map(db, |element| element.to_instance(db)),
// TODO: we can probably do better here: --Alex // TODO: we can probably do better here: --Alex
Type::Intersection(_) => Type::Todo, Type::Intersection(_) => Type::Todo,
@ -1420,13 +1428,13 @@ impl<'db> Type<'db> {
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db),
Type::Tuple(_) => KnownClass::Tuple.to_class(db), Type::Tuple(_) => KnownClass::Tuple.to_class(db),
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db), Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
Type::SubclassOf(SubclassOfType { class }) => Type::SubclassOf( Type::SubclassOf(SubclassOfType { class }) => Type::subclass_of(
class class
.try_metaclass(db) .try_metaclass(db)
.ok() .ok()
.and_then(Type::into_class_literal) .and_then(Type::into_class_literal)
.unwrap_or(KnownClass::Type.to_class(db).expect_class_literal()) .unwrap_or_else(|| KnownClass::Type.to_class(db).expect_class_literal())
.to_subclass_of_type(), .class,
), ),
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class(db), Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class(db),
// TODO: `type[Any]`? // TODO: `type[Any]`?
@ -2528,9 +2536,7 @@ impl<'db> Class<'db> {
}); });
} }
Ok(Type::ClassLiteral(ClassLiteralType { Ok(Type::class_literal(candidate.metaclass))
class: candidate.metaclass,
}))
} }
/// Returns the class member of this class named `name`. /// 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> { fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
self.class.class_member(db, name) 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> { impl<'db> From<ClassLiteralType<'db>> for Type<'db> {
@ -3053,13 +3055,11 @@ mod tests {
assert!(literal_derived.is_class_literal()); assert!(literal_derived.is_class_literal());
// `subclass_of_base` represents `Type[Base]`. // `subclass_of_base` represents `Type[Base]`.
let subclass_of_base = let subclass_of_base = Type::subclass_of(literal_base.expect_class_literal().class);
Type::SubclassOf(literal_base.expect_class_literal().to_subclass_of_type());
assert!(literal_base.is_subtype_of(&db, subclass_of_base)); assert!(literal_base.is_subtype_of(&db, subclass_of_base));
assert!(literal_derived.is_subtype_of(&db, subclass_of_base)); assert!(literal_derived.is_subtype_of(&db, subclass_of_base));
let subclass_of_derived = let subclass_of_derived = Type::subclass_of(literal_derived.expect_class_literal().class);
Type::SubclassOf(literal_derived.expect_class_literal().to_subclass_of_type());
assert!(literal_derived.is_subtype_of(&db, subclass_of_derived)); assert!(literal_derived.is_subtype_of(&db, subclass_of_derived));
assert!(!literal_base.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_a = super::global_symbol(&db, module, "A").expect_type();
let literal_b = super::global_symbol(&db, module, "B").expect_type(); let literal_b = super::global_symbol(&db, module, "B").expect_type();
let subclass_of_a = let subclass_of_a = Type::subclass_of(literal_a.expect_class_literal().class);
Type::SubclassOf(literal_a.expect_class_literal().to_subclass_of_type()); let subclass_of_b = Type::subclass_of(literal_b.expect_class_literal().class);
let subclass_of_b =
Type::SubclassOf(literal_b.expect_class_literal().to_subclass_of_type());
// Class literals are always disjoint. They are singleton types // Class literals are always disjoint. They are singleton types
assert!(literal_a.is_disjoint_from(&db, literal_b)); 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())); .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 = 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); 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 // anything else is invalid and should lead to a diagnostic being reported --Alex
match node_ty { match node_ty {
Type::Any | Type::Unknown => node_ty, Type::Any | Type::Unknown => node_ty,
Type::ClassLiteral(ClassLiteralType { class }) => { Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(class),
Type::Instance(InstanceType { class })
}
Type::Tuple(tuple) => UnionType::from_elements( Type::Tuple(tuple) => UnionType::from_elements(
self.db, self.db,
tuple.elements(self.db).iter().map(|ty| { tuple.elements(self.db).iter().map(|ty| {
ty.into_class_literal() ty.into_class_literal()
.map_or(Type::Todo, |ClassLiteralType { class }| { .map_or(Type::Todo, |ClassLiteralType { class }| {
Type::Instance(InstanceType { class }) Type::instance(class)
}) })
}), }),
), ),
@ -4283,8 +4281,8 @@ impl<'db> TypeInferenceBuilder<'db> {
match slice { match slice {
ast::Expr::Name(name) => { ast::Expr::Name(name) => {
let name_ty = self.infer_name_expression(name); let name_ty = self.infer_name_expression(name);
if let Some(class_literal) = name_ty.into_class_literal() { if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
Type::SubclassOf(class_literal.to_subclass_of_type()) Type::subclass_of(class)
} else { } else {
Type::Todo Type::Todo
} }

View file

@ -406,7 +406,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
ClassBase::Any => Type::Any, ClassBase::Any => Type::Any,
ClassBase::Todo => Type::Todo, ClassBase::Todo => Type::Todo,
ClassBase::Unknown => Type::Unknown, 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::{ScopeId, ScopedSymbolId, SymbolTable};
use crate::semantic_index::symbol_table; use crate::semantic_index::symbol_table;
use crate::types::{ use crate::types::{
infer_expression_types, ClassLiteralType, InstanceType, IntersectionBuilder, KnownClass, infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass,
KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder, KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder,
}; };
use crate::Db; use crate::Db;
@ -353,14 +353,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
let to_constraint = match function { let to_constraint = match function {
KnownConstraintFunction::IsInstance => { KnownConstraintFunction::IsInstance => {
|class_literal: ClassLiteralType<'db>| { |class_literal: ClassLiteralType<'db>| {
Type::Instance(InstanceType { Type::instance(class_literal.class)
class: class_literal.class,
})
} }
} }
KnownConstraintFunction::IsSubclass => { KnownConstraintFunction::IsSubclass => {
|class_literal: ClassLiteralType<'db>| { |class_literal: ClassLiteralType<'db>| {
Type::SubclassOf(class_literal.to_subclass_of_type()) Type::subclass_of(class_literal.class)
} }
} }
}; };