Skip to content

Commit

Permalink
[red-knot] Add Type constructors for Instance, ClassLiteral and…
Browse files Browse the repository at this point in the history
… `SubclassOf` variants (astral-sh#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`
  • Loading branch information
AlexWaygood authored Nov 9, 2024
1 parent eea6b31 commit d3f1c8e
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 51 deletions.
74 changes: 36 additions & 38 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]`?
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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));
Expand Down
12 changes: 5 additions & 7 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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)
})
}),
),
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion crates/red_knot_python_semantic/src/types/mro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions crates/red_knot_python_semantic/src/types/narrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
}
}
};
Expand Down

0 comments on commit d3f1c8e

Please sign in to comment.