Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/unary/custom.md
Original file line number Diff line number Diff line change
@@ -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
```
41 changes: 28 additions & 13 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -3182,6 +3182,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),
Expand All @@ -3191,11 +3196,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__",
Expand All @@ -3206,11 +3223,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) => {
Expand Down Expand Up @@ -3238,7 +3254,6 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::Unknown
}
}
_ => todo_type!(), // TODO other unary op types
}
}

Expand Down
Loading