diff --git a/crates/ty_python_semantic/resources/mdtest/deprecated.md b/crates/ty_python_semantic/resources/mdtest/deprecated.md index 55e9d41237e882..4279abaf32ea1d 100644 --- a/crates/ty_python_semantic/resources/mdtest/deprecated.md +++ b/crates/ty_python_semantic/resources/mdtest/deprecated.md @@ -412,6 +412,31 @@ y = MyInt(2) z = x + y # TODO error: [deprecated] "MyInt `+` support is broken" ``` +Unlike binary operators, unary operators like `~` already fire a diagnostic when their dunder is +deprecated. + +```py +from typing_extensions import deprecated + +class MyBits: + @deprecated("MyBits `~` support is broken") + def __invert__(self): + return self + +x = MyBits() +~x # error: [deprecated] "MyBits `~` support is broken" +``` + +`bool.__invert__` is one such case in typeshed: `~` on a `bool` is deprecated and will be removed in +Python 3.16. This applies both to `bool` literals and to arbitrary values of type `bool`. + +```py +~True # error: [deprecated] + +def f(x: bool): + ~x # error: [deprecated] +``` + ## Overloads Overloads can be deprecated, but only trigger warnings when invoked. diff --git a/crates/ty_python_semantic/resources/mdtest/unary/integers.md b/crates/ty_python_semantic/resources/mdtest/unary/integers.md index ec439977edea13..c8ee19b278102d 100644 --- a/crates/ty_python_semantic/resources/mdtest/unary/integers.md +++ b/crates/ty_python_semantic/resources/mdtest/unary/integers.md @@ -21,5 +21,9 @@ reveal_type(-True) # revealed: Literal[-1] ```py reveal_type(~0) # revealed: Literal[-1] reveal_type(~1) # revealed: Literal[-2] -reveal_type(~True) # revealed: Literal[-2] + +# `~` on a `bool` is deprecated and will be removed in Python 3.16. +# error: [deprecated] +# revealed: Literal[-2] +reveal_type(~True) ``` diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index d0f2bd0184f73b..38b3e7acf42d38 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -10702,7 +10702,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { CallArguments::none(), TypeContext::default(), ) { - Ok(outcome) => outcome.return_type(db, env), + Ok(outcome) => { + for callable in outcome.iter_flat() { + self.check_deprecated(unary, callable.callable_type); + } + outcome.return_type(db, env) + } Err(e) => { self.report_unsupported_unary_operator( unary, @@ -10743,7 +10748,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { (ast::UnaryOp::Invert, Type::LiteralValue(literal)) => match literal.kind() { LiteralValueTypeKind::Int(value) => Type::int_literal(!value.as_i64()), - LiteralValueTypeKind::Bool(value) => Type::int_literal(!i64::from(value)), + LiteralValueTypeKind::Bool(value) => { + // `~bool` is deprecated and will be removed in Python 3.16. + // Then it will become unavailable and the fallback will report it + // as `unsupported-operator`. + if let Some(dunder) = operand_type + .member_lookup_with_policy( + db, + env, + "__invert__", + MemberLookupPolicy::NO_INSTANCE_FALLBACK, + ) + .place + .ignore_possibly_undefined() + { + self.check_deprecated(unary, dunder); + Type::int_literal(!i64::from(value)) + } else { + fallback_unary_expression_type() + } + } _ => fallback_unary_expression_type(), },