diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index a447601574..7d327ffece 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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] diff --git a/crates/ty_python_semantic/src/types/property_tests.rs b/crates/ty_python_semantic/src/types/property_tests.rs index ba65ad72d3..4ac29c25c2 100644 --- a/crates/ty_python_semantic/src/types/property_tests.rs +++ b/crates/ty_python_semantic/src/types/property_tests.rs @@ -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.