diff --git a/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md b/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md new file mode 100644 index 0000000000000..931521f75c340 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/unary/custom.md @@ -0,0 +1,165 @@ +# Custom unary operations + +## Class instances + +```py +class Yes: + def __pos__(self) -> bool: + return False + + def __neg__(self) -> str: + return "negative" + + def __invert__(self) -> int: + return 17 + +class Sub(Yes): ... +class No: ... + +reveal_type(+Yes()) # revealed: bool +reveal_type(-Yes()) # revealed: str +reveal_type(~Yes()) # revealed: int + +reveal_type(+Sub()) # revealed: bool +reveal_type(-Sub()) # revealed: str +reveal_type(~Sub()) # revealed: int + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `No`" +reveal_type(+No()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `No`" +reveal_type(-No()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `No`" +reveal_type(~No()) # revealed: Unknown +``` + +## Classes + +```py +class Yes: + def __pos__(self) -> bool: + return False + + def __neg__(self) -> str: + return "negative" + + def __invert__(self) -> int: + return 17 + +class Sub(Yes): ... +class No: ... + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Yes]`" +reveal_type(+Yes) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Yes]`" +reveal_type(-Yes) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Yes]`" +reveal_type(~Yes) # revealed: Unknown + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Sub]`" +reveal_type(+Sub) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Sub]`" +reveal_type(-Sub) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Sub]`" +reveal_type(~Sub) # revealed: Unknown + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`" +reveal_type(+No) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`" +reveal_type(-No) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`" +reveal_type(~No) # revealed: Unknown +``` + +## Function literals + +```py +def f(): + pass + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[f]`" +reveal_type(+f) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[f]`" +reveal_type(-f) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[f]`" +reveal_type(~f) # revealed: Unknown +``` + +## Subclass + +```py +class Yes: + def __pos__(self) -> bool: + return False + + def __neg__(self) -> str: + return "negative" + + def __invert__(self) -> int: + return 17 + +class Sub(Yes): ... +class No: ... + +def yes() -> type[Yes]: + return Yes + +def sub() -> type[Sub]: + return Sub + +def no() -> type[No]: + return No + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[Yes]`" +reveal_type(+yes()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[Yes]`" +reveal_type(-yes()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[Yes]`" +reveal_type(~yes()) # revealed: Unknown + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[Sub]`" +reveal_type(+sub()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[Sub]`" +reveal_type(-sub()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[Sub]`" +reveal_type(~sub()) # revealed: Unknown + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[No]`" +reveal_type(+no()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[No]`" +reveal_type(-no()) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[No]`" +reveal_type(~no()) # revealed: Unknown +``` + +## Metaclass + +```py +class Meta(type): + def __pos__(self) -> bool: + return False + + def __neg__(self) -> str: + return "negative" + + def __invert__(self) -> int: + return 17 + +class Yes(metaclass=Meta): ... +class Sub(Yes): ... +class No: ... + +reveal_type(+Yes) # revealed: bool +reveal_type(-Yes) # revealed: str +reveal_type(~Yes) # revealed: int + +reveal_type(+Sub) # revealed: bool +reveal_type(-Sub) # revealed: str +reveal_type(~Sub) # revealed: int + +# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`" +reveal_type(+No) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`" +reveal_type(-No) # revealed: Unknown +# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`" +reveal_type(~No) # revealed: Unknown +``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 7ff8431f345e9..95e7eca3f1dfd 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -62,11 +62,11 @@ use crate::types::mro::MroErrorKind; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type, - typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType, - IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction, - KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, - Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, TypeVarBoundOrConstraints, - TypeVarInstance, UnionBuilder, UnionType, + typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, FunctionType, + InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, + KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, + Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, + TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, }; use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; @@ -3201,6 +3201,11 @@ impl<'db> TypeInferenceBuilder<'db> { let operand_type = self.infer_expression(operand); match (op, operand_type) { + (_, Type::Any) => Type::Any, + (_, Type::Todo(_)) => operand_type, + (_, Type::Never) => Type::Never, + (_, Type::Unknown) => Type::Unknown, + (UnaryOp::UAdd, Type::IntLiteral(value)) => Type::IntLiteral(value), (UnaryOp::USub, Type::IntLiteral(value)) => Type::IntLiteral(-value), (UnaryOp::Invert, Type::IntLiteral(value)) => Type::IntLiteral(!value), @@ -3210,11 +3215,23 @@ impl<'db> TypeInferenceBuilder<'db> { (UnaryOp::Invert, Type::BooleanLiteral(bool)) => Type::IntLiteral(!i64::from(bool)), (UnaryOp::Not, ty) => ty.bool(self.db()).negate().into_type(self.db()), - (_, Type::Any) => Type::Any, - (_, Type::Unknown) => Type::Unknown, ( op @ (UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert), - Type::Instance(InstanceType { class }), + Type::FunctionLiteral(_) + | Type::ModuleLiteral(_) + | Type::ClassLiteral(_) + | Type::SubclassOf(_) + | Type::Instance(_) + | Type::KnownInstance(_) + | Type::Union(_) + | Type::Intersection(_) + | Type::AlwaysTruthy + | Type::AlwaysFalsy + | Type::StringLiteral(_) + | Type::LiteralString + | Type::BytesLiteral(_) + | Type::SliceLiteral(_) + | Type::Tuple(_), ) => { let unary_dunder_method = match op { UnaryOp::Invert => "__invert__", @@ -3225,11 +3242,10 @@ impl<'db> TypeInferenceBuilder<'db> { } }; - if let Symbol::Type(class_member, _) = - class.class_member(self.db(), unary_dunder_method) + if let CallDunderResult::CallOutcome(call) + | CallDunderResult::PossiblyUnbound(call) = + operand_type.call_dunder(self.db(), unary_dunder_method, &[operand_type]) { - let call = class_member.call(self.db(), &[operand_type]); - match call.return_ty_result(&self.context, AnyNodeRef::ExprUnaryOp(unary)) { Ok(t) => t, Err(e) => { @@ -3257,7 +3273,6 @@ impl<'db> TypeInferenceBuilder<'db> { Type::Unknown } } - _ => todo_type!(), // TODO other unary op types } }