Skip to content

Commit 2802cbd

Browse files
authored
Don't special-case class instances in unary expression inference (#15045)
We have a handy `to_meta_type` that does the right thing for class instances, and also works for all of the other types that are “instances of” something. Unless I'm missing something, this should let us get rid of the catch-all clause in one fell swoop. cf #14548
1 parent ed2bce6 commit 2802cbd

File tree

2 files changed

+193
-13
lines changed

2 files changed

+193
-13
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Custom unary operations
2+
3+
## Class instances
4+
5+
```py
6+
class Yes:
7+
def __pos__(self) -> bool:
8+
return False
9+
10+
def __neg__(self) -> str:
11+
return "negative"
12+
13+
def __invert__(self) -> int:
14+
return 17
15+
16+
class Sub(Yes): ...
17+
class No: ...
18+
19+
reveal_type(+Yes()) # revealed: bool
20+
reveal_type(-Yes()) # revealed: str
21+
reveal_type(~Yes()) # revealed: int
22+
23+
reveal_type(+Sub()) # revealed: bool
24+
reveal_type(-Sub()) # revealed: str
25+
reveal_type(~Sub()) # revealed: int
26+
27+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `No`"
28+
reveal_type(+No()) # revealed: Unknown
29+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `No`"
30+
reveal_type(-No()) # revealed: Unknown
31+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `No`"
32+
reveal_type(~No()) # revealed: Unknown
33+
```
34+
35+
## Classes
36+
37+
```py
38+
class Yes:
39+
def __pos__(self) -> bool:
40+
return False
41+
42+
def __neg__(self) -> str:
43+
return "negative"
44+
45+
def __invert__(self) -> int:
46+
return 17
47+
48+
class Sub(Yes): ...
49+
class No: ...
50+
51+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Yes]`"
52+
reveal_type(+Yes) # revealed: Unknown
53+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Yes]`"
54+
reveal_type(-Yes) # revealed: Unknown
55+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Yes]`"
56+
reveal_type(~Yes) # revealed: Unknown
57+
58+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Sub]`"
59+
reveal_type(+Sub) # revealed: Unknown
60+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Sub]`"
61+
reveal_type(-Sub) # revealed: Unknown
62+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Sub]`"
63+
reveal_type(~Sub) # revealed: Unknown
64+
65+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`"
66+
reveal_type(+No) # revealed: Unknown
67+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`"
68+
reveal_type(-No) # revealed: Unknown
69+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`"
70+
reveal_type(~No) # revealed: Unknown
71+
```
72+
73+
## Function literals
74+
75+
```py
76+
def f():
77+
pass
78+
79+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[f]`"
80+
reveal_type(+f) # revealed: Unknown
81+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[f]`"
82+
reveal_type(-f) # revealed: Unknown
83+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[f]`"
84+
reveal_type(~f) # revealed: Unknown
85+
```
86+
87+
## Subclass
88+
89+
```py
90+
class Yes:
91+
def __pos__(self) -> bool:
92+
return False
93+
94+
def __neg__(self) -> str:
95+
return "negative"
96+
97+
def __invert__(self) -> int:
98+
return 17
99+
100+
class Sub(Yes): ...
101+
class No: ...
102+
103+
def yes() -> type[Yes]:
104+
return Yes
105+
106+
def sub() -> type[Sub]:
107+
return Sub
108+
109+
def no() -> type[No]:
110+
return No
111+
112+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[Yes]`"
113+
reveal_type(+yes()) # revealed: Unknown
114+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[Yes]`"
115+
reveal_type(-yes()) # revealed: Unknown
116+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[Yes]`"
117+
reveal_type(~yes()) # revealed: Unknown
118+
119+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[Sub]`"
120+
reveal_type(+sub()) # revealed: Unknown
121+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[Sub]`"
122+
reveal_type(-sub()) # revealed: Unknown
123+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[Sub]`"
124+
reveal_type(~sub()) # revealed: Unknown
125+
126+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[No]`"
127+
reveal_type(+no()) # revealed: Unknown
128+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[No]`"
129+
reveal_type(-no()) # revealed: Unknown
130+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[No]`"
131+
reveal_type(~no()) # revealed: Unknown
132+
```
133+
134+
## Metaclass
135+
136+
```py
137+
class Meta(type):
138+
def __pos__(self) -> bool:
139+
return False
140+
141+
def __neg__(self) -> str:
142+
return "negative"
143+
144+
def __invert__(self) -> int:
145+
return 17
146+
147+
class Yes(metaclass=Meta): ...
148+
class Sub(Yes): ...
149+
class No: ...
150+
151+
reveal_type(+Yes) # revealed: bool
152+
reveal_type(-Yes) # revealed: str
153+
reveal_type(~Yes) # revealed: int
154+
155+
reveal_type(+Sub) # revealed: bool
156+
reveal_type(-Sub) # revealed: str
157+
reveal_type(~Sub) # revealed: int
158+
159+
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`"
160+
reveal_type(+No) # revealed: Unknown
161+
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`"
162+
reveal_type(-No) # revealed: Unknown
163+
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`"
164+
reveal_type(~No) # revealed: Unknown
165+
```

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ use crate::types::mro::MroErrorKind;
6262
use crate::types::unpacker::{UnpackResult, Unpacker};
6363
use crate::types::{
6464
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type,
65-
typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType,
66-
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
67-
KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol,
68-
Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, TypeVarBoundOrConstraints,
69-
TypeVarInstance, UnionBuilder, UnionType,
65+
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, FunctionType,
66+
InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass,
67+
KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType,
68+
Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay,
69+
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
7070
};
7171
use crate::unpack::Unpack;
7272
use crate::util::subscript::{PyIndex, PySlice};
@@ -3201,6 +3201,11 @@ impl<'db> TypeInferenceBuilder<'db> {
32013201
let operand_type = self.infer_expression(operand);
32023202

32033203
match (op, operand_type) {
3204+
(_, Type::Any) => Type::Any,
3205+
(_, Type::Todo(_)) => operand_type,
3206+
(_, Type::Never) => Type::Never,
3207+
(_, Type::Unknown) => Type::Unknown,
3208+
32043209
(UnaryOp::UAdd, Type::IntLiteral(value)) => Type::IntLiteral(value),
32053210
(UnaryOp::USub, Type::IntLiteral(value)) => Type::IntLiteral(-value),
32063211
(UnaryOp::Invert, Type::IntLiteral(value)) => Type::IntLiteral(!value),
@@ -3210,11 +3215,23 @@ impl<'db> TypeInferenceBuilder<'db> {
32103215
(UnaryOp::Invert, Type::BooleanLiteral(bool)) => Type::IntLiteral(!i64::from(bool)),
32113216

32123217
(UnaryOp::Not, ty) => ty.bool(self.db()).negate().into_type(self.db()),
3213-
(_, Type::Any) => Type::Any,
3214-
(_, Type::Unknown) => Type::Unknown,
32153218
(
32163219
op @ (UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert),
3217-
Type::Instance(InstanceType { class }),
3220+
Type::FunctionLiteral(_)
3221+
| Type::ModuleLiteral(_)
3222+
| Type::ClassLiteral(_)
3223+
| Type::SubclassOf(_)
3224+
| Type::Instance(_)
3225+
| Type::KnownInstance(_)
3226+
| Type::Union(_)
3227+
| Type::Intersection(_)
3228+
| Type::AlwaysTruthy
3229+
| Type::AlwaysFalsy
3230+
| Type::StringLiteral(_)
3231+
| Type::LiteralString
3232+
| Type::BytesLiteral(_)
3233+
| Type::SliceLiteral(_)
3234+
| Type::Tuple(_),
32183235
) => {
32193236
let unary_dunder_method = match op {
32203237
UnaryOp::Invert => "__invert__",
@@ -3225,11 +3242,10 @@ impl<'db> TypeInferenceBuilder<'db> {
32253242
}
32263243
};
32273244

3228-
if let Symbol::Type(class_member, _) =
3229-
class.class_member(self.db(), unary_dunder_method)
3245+
if let CallDunderResult::CallOutcome(call)
3246+
| CallDunderResult::PossiblyUnbound(call) =
3247+
operand_type.call_dunder(self.db(), unary_dunder_method, &[operand_type])
32303248
{
3231-
let call = class_member.call(self.db(), &[operand_type]);
3232-
32333249
match call.return_ty_result(&self.context, AnyNodeRef::ExprUnaryOp(unary)) {
32343250
Ok(t) => t,
32353251
Err(e) => {
@@ -3257,7 +3273,6 @@ impl<'db> TypeInferenceBuilder<'db> {
32573273
Type::Unknown
32583274
}
32593275
}
3260-
_ => todo_type!(), // TODO other unary op types
32613276
}
32623277
}
32633278

0 commit comments

Comments
 (0)