[ty] Optimize Type::negate() (#22402)

This commit is contained in:
Alex Waygood
2026-01-06 19:17:59 +00:00
committed by GitHub
parent ab1ac254d9
commit 2ec29b7418
2 changed files with 61 additions and 3 deletions

View File

@@ -1455,7 +1455,58 @@ impl<'db> Type<'db> {
#[must_use]
pub(crate) fn negate(&self, db: &'db dyn Db) -> Type<'db> {
IntersectionBuilder::new(db).add_negative(*self).build()
// Avoid invoking the `IntersectionBuilder` for negations that are trivial.
//
// We verify that this always produces the same result as
// `IntersectionBuilder::new(db).add_negative(*self).build()` via the
// property test `all_negated_types_identical_to_intersection_with_single_negated_element`
match self {
Type::Never => Type::object(),
Type::Dynamic(_) => *self,
Type::NominalInstance(instance) if instance.is_object() => Type::Never,
Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::BooleanLiteral(_)
| Type::KnownBoundMethod(_)
| Type::KnownInstance(_)
| Type::SpecialForm(_)
| Type::BoundSuper(_)
| Type::FunctionLiteral(_)
| Type::TypeIs(_)
| Type::TypeGuard(_)
| Type::TypeVar(_)
| Type::TypedDict(_)
| Type::NewTypeInstance(_)
| Type::NominalInstance(_)
| Type::ProtocolInstance(_)
| Type::ModuleLiteral(_)
| Type::ClassLiteral(_)
| Type::GenericAlias(_)
| Type::SubclassOf(_)
| Type::PropertyInstance(_)
| Type::IntLiteral(_)
| Type::StringLiteral(_)
| Type::BytesLiteral(_)
| Type::LiteralString
| Type::DataclassDecorator(_)
| Type::DataclassTransformer(_)
| Type::Callable(_)
| Type::WrapperDescriptor(_)
| Type::EnumLiteral(_)
| Type::TypeAlias(_)
| Type::BoundMethod(_) => Type::Intersection(IntersectionType::new(
db,
FxOrderSet::default(),
NegativeIntersectionElements::Single(*self),
)),
Type::Union(_) | Type::Intersection(_) => {
IntersectionBuilder::new(db).add_negative(*self).build()
}
}
}
#[must_use]

View File

@@ -81,7 +81,7 @@ macro_rules! type_property_test {
mod stable {
use super::union;
use crate::types::{CallableType, KnownClass, Type};
use crate::types::{CallableType, IntersectionBuilder, KnownClass, Type};
// Reflexivity: `T` is equivalent to itself.
type_property_test!(
@@ -234,9 +234,16 @@ mod stable {
// the Liskov violation). All you need to do is to create a class that subclasses
// `Iterable` but assigns `__iter__ = None` in the class body (or similar).
type_property_test!(
all_type_assignable_to_iterable_are_iterable, db,
all_types_assignable_to_iterable_are_iterable, db,
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object()])) => t.try_iterate(db).is_ok()
);
// Our optimized `Type::negate()` function should always produce the exact same type
// as going "the long way" via the `IntersectionBuilder`.
type_property_test!(
all_negated_types_identical_to_intersection_with_single_negated_element, db,
forall types t. t.negate(db) == IntersectionBuilder::new(db).add_negative(t).build()
);
}
/// This module contains property tests that currently lead to many false positives.