# Custom binary operations

## Class instances

```py
from typing import Literal

class Yes:
    def __add__(self, other) -> Literal["+"]:
        return "+"

    def __sub__(self, other) -> Literal["-"]:
        return "-"

    def __mul__(self, other) -> Literal["*"]:
        return "*"

    def __matmul__(self, other) -> Literal["@"]:
        return "@"

    def __truediv__(self, other) -> Literal["/"]:
        return "/"

    def __mod__(self, other) -> Literal["%"]:
        return "%"

    def __pow__(self, other) -> Literal["**"]:
        return "**"

    def __lshift__(self, other) -> Literal["<<"]:
        return "<<"

    def __rshift__(self, other) -> Literal[">>"]:
        return ">>"

    def __or__(self, other) -> Literal["|"]:
        return "|"

    def __xor__(self, other) -> Literal["^"]:
        return "^"

    def __and__(self, other) -> Literal["&"]:
        return "&"

    def __floordiv__(self, other) -> Literal["//"]:
        return "//"

class Sub(Yes): ...
class No: ...

# Yes implements all of the dunder methods.
reveal_type(Yes() + Yes())  # revealed: Literal["+"]
reveal_type(Yes() - Yes())  # revealed: Literal["-"]
reveal_type(Yes() * Yes())  # revealed: Literal["*"]
reveal_type(Yes() @ Yes())  # revealed: Literal["@"]
reveal_type(Yes() / Yes())  # revealed: Literal["/"]
reveal_type(Yes() % Yes())  # revealed: Literal["%"]
reveal_type(Yes() ** Yes())  # revealed: Literal["**"]
reveal_type(Yes() << Yes())  # revealed: Literal["<<"]
reveal_type(Yes() >> Yes())  # revealed: Literal[">>"]
reveal_type(Yes() | Yes())  # revealed: Literal["|"]
reveal_type(Yes() ^ Yes())  # revealed: Literal["^"]
reveal_type(Yes() & Yes())  # revealed: Literal["&"]
reveal_type(Yes() // Yes())  # revealed: Literal["//"]

# Sub inherits Yes's implementation of the dunder methods.
reveal_type(Sub() + Sub())  # revealed: Literal["+"]
reveal_type(Sub() - Sub())  # revealed: Literal["-"]
reveal_type(Sub() * Sub())  # revealed: Literal["*"]
reveal_type(Sub() @ Sub())  # revealed: Literal["@"]
reveal_type(Sub() / Sub())  # revealed: Literal["/"]
reveal_type(Sub() % Sub())  # revealed: Literal["%"]
reveal_type(Sub() ** Sub())  # revealed: Literal["**"]
reveal_type(Sub() << Sub())  # revealed: Literal["<<"]
reveal_type(Sub() >> Sub())  # revealed: Literal[">>"]
reveal_type(Sub() | Sub())  # revealed: Literal["|"]
reveal_type(Sub() ^ Sub())  # revealed: Literal["^"]
reveal_type(Sub() & Sub())  # revealed: Literal["&"]
reveal_type(Sub() // Sub())  # revealed: Literal["//"]

# No does not implement any of the dunder methods.
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `No`"
reveal_type(No() + No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `-` is not supported between two objects of type `No`"
reveal_type(No() - No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `*` is not supported between two objects of type `No`"
reveal_type(No() * No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `@` is not supported between two objects of type `No`"
reveal_type(No() @ No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `/` is not supported between two objects of type `No`"
reveal_type(No() / No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `%` is not supported between two objects of type `No`"
reveal_type(No() % No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `**` is not supported between two objects of type `No`"
reveal_type(No() ** No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `<<` is not supported between two objects of type `No`"
reveal_type(No() << No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `>>` is not supported between two objects of type `No`"
reveal_type(No() >> No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `|` is not supported between two objects of type `No`"
reveal_type(No() | No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `^` is not supported between two objects of type `No`"
reveal_type(No() ^ No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `&` is not supported between two objects of type `No`"
reveal_type(No() & No())  # revealed: Unknown
# error: [unsupported-operator] "Operator `//` is not supported between two objects of type `No`"
reveal_type(No() // No())  # revealed: Unknown

# Yes does not implement any of the reflected dunder methods.
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `No` and `Yes`"
reveal_type(No() + Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `-` is not supported between objects of type `No` and `Yes`"
reveal_type(No() - Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `*` is not supported between objects of type `No` and `Yes`"
reveal_type(No() * Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `@` is not supported between objects of type `No` and `Yes`"
reveal_type(No() @ Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `/` is not supported between objects of type `No` and `Yes`"
reveal_type(No() / Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `%` is not supported between objects of type `No` and `Yes`"
reveal_type(No() % Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `**` is not supported between objects of type `No` and `Yes`"
reveal_type(No() ** Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `<<` is not supported between objects of type `No` and `Yes`"
reveal_type(No() << Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `>>` is not supported between objects of type `No` and `Yes`"
reveal_type(No() >> Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `|` is not supported between objects of type `No` and `Yes`"
reveal_type(No() | Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `^` is not supported between objects of type `No` and `Yes`"
reveal_type(No() ^ Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `&` is not supported between objects of type `No` and `Yes`"
reveal_type(No() & Yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `//` is not supported between objects of type `No` and `Yes`"
reveal_type(No() // Yes())  # revealed: Unknown
```

## Subclass reflections override superclass dunders

```py
from typing import Literal

class Yes:
    def __add__(self, other) -> Literal["+"]:
        return "+"

    def __sub__(self, other) -> Literal["-"]:
        return "-"

    def __mul__(self, other) -> Literal["*"]:
        return "*"

    def __matmul__(self, other) -> Literal["@"]:
        return "@"

    def __truediv__(self, other) -> Literal["/"]:
        return "/"

    def __mod__(self, other) -> Literal["%"]:
        return "%"

    def __pow__(self, other) -> Literal["**"]:
        return "**"

    def __lshift__(self, other) -> Literal["<<"]:
        return "<<"

    def __rshift__(self, other) -> Literal[">>"]:
        return ">>"

    def __or__(self, other) -> Literal["|"]:
        return "|"

    def __xor__(self, other) -> Literal["^"]:
        return "^"

    def __and__(self, other) -> Literal["&"]:
        return "&"

    def __floordiv__(self, other) -> Literal["//"]:
        return "//"

class Sub(Yes):
    def __radd__(self, other) -> Literal["r+"]:
        return "r+"

    def __rsub__(self, other) -> Literal["r-"]:
        return "r-"

    def __rmul__(self, other) -> Literal["r*"]:
        return "r*"

    def __rmatmul__(self, other) -> Literal["r@"]:
        return "r@"

    def __rtruediv__(self, other) -> Literal["r/"]:
        return "r/"

    def __rmod__(self, other) -> Literal["r%"]:
        return "r%"

    def __rpow__(self, other) -> Literal["r**"]:
        return "r**"

    def __rlshift__(self, other) -> Literal["r<<"]:
        return "r<<"

    def __rrshift__(self, other) -> Literal["r>>"]:
        return "r>>"

    def __ror__(self, other) -> Literal["r|"]:
        return "r|"

    def __rxor__(self, other) -> Literal["r^"]:
        return "r^"

    def __rand__(self, other) -> Literal["r&"]:
        return "r&"

    def __rfloordiv__(self, other) -> Literal["r//"]:
        return "r//"

class No:
    def __radd__(self, other) -> Literal["r+"]:
        return "r+"

    def __rsub__(self, other) -> Literal["r-"]:
        return "r-"

    def __rmul__(self, other) -> Literal["r*"]:
        return "r*"

    def __rmatmul__(self, other) -> Literal["r@"]:
        return "r@"

    def __rtruediv__(self, other) -> Literal["r/"]:
        return "r/"

    def __rmod__(self, other) -> Literal["r%"]:
        return "r%"

    def __rpow__(self, other) -> Literal["r**"]:
        return "r**"

    def __rlshift__(self, other) -> Literal["r<<"]:
        return "r<<"

    def __rrshift__(self, other) -> Literal["r>>"]:
        return "r>>"

    def __ror__(self, other) -> Literal["r|"]:
        return "r|"

    def __rxor__(self, other) -> Literal["r^"]:
        return "r^"

    def __rand__(self, other) -> Literal["r&"]:
        return "r&"

    def __rfloordiv__(self, other) -> Literal["r//"]:
        return "r//"

# Subclass reflected dunder methods may take precedence over the superclass's regular dunders,
# depending on the operands' runtime classes.
reveal_type(Yes() + Sub())  # revealed: Literal["r+", "+"]
reveal_type(Yes() - Sub())  # revealed: Literal["r-", "-"]
reveal_type(Yes() * Sub())  # revealed: Literal["r*", "*"]
reveal_type(Yes() @ Sub())  # revealed: Literal["r@", "@"]
reveal_type(Yes() / Sub())  # revealed: Literal["r/", "/"]
reveal_type(Yes() % Sub())  # revealed: Literal["r%", "%"]
reveal_type(Yes() ** Sub())  # revealed: Literal["r**", "**"]
reveal_type(Yes() << Sub())  # revealed: Literal["r<<", "<<"]
reveal_type(Yes() >> Sub())  # revealed: Literal["r>>", ">>"]
reveal_type(Yes() | Sub())  # revealed: Literal["r|", "|"]
reveal_type(Yes() ^ Sub())  # revealed: Literal["r^", "^"]
reveal_type(Yes() & Sub())  # revealed: Literal["r&", "&"]
reveal_type(Yes() // Sub())  # revealed: Literal["r//", "//"]

# But for an unrelated class, the superclass regular dunders are used.
reveal_type(Yes() + No())  # revealed: Literal["+"]
reveal_type(Yes() - No())  # revealed: Literal["-"]
reveal_type(Yes() * No())  # revealed: Literal["*"]
reveal_type(Yes() @ No())  # revealed: Literal["@"]
reveal_type(Yes() / No())  # revealed: Literal["/"]
reveal_type(Yes() % No())  # revealed: Literal["%"]
reveal_type(Yes() ** No())  # revealed: Literal["**"]
reveal_type(Yes() << No())  # revealed: Literal["<<"]
reveal_type(Yes() >> No())  # revealed: Literal[">>"]
reveal_type(Yes() | No())  # revealed: Literal["|"]
reveal_type(Yes() ^ No())  # revealed: Literal["^"]
reveal_type(Yes() & No())  # revealed: Literal["&"]
reveal_type(Yes() // No())  # revealed: Literal["//"]
```

## Classes

Dunder methods defined in a class are available to instances of that class, but not to the class
itself. (For these operators to work on the class itself, they would have to be defined on the
class's type, i.e. `type`.)

```py
from typing import Literal

class Yes:
    def __add__(self, other) -> Literal["+"]:
        return "+"

class Sub(Yes): ...
class No: ...

# snapshot: unsupported-operator
reveal_type(Yes + Yes)  # revealed: Unknown
```

```snapshot
error[unsupported-operator]: Unsupported `+` operation
  --> src/mdtest_snippet.py:11:13
   |
11 | reveal_type(Yes + Yes)  # revealed: Unknown
   |             ---^^^---
   |             |
   |             Both operands have type `<class 'Yes'>`
   |
```

```py
# snapshot: unsupported-operator
reveal_type(Sub + Sub)  # revealed: Unknown
```

```snapshot
error[unsupported-operator]: Unsupported `+` operation
  --> src/mdtest_snippet.py:13:13
   |
13 | reveal_type(Sub + Sub)  # revealed: Unknown
   |             ---^^^---
   |             |
   |             Both operands have type `<class 'Sub'>`
   |
```

```py
# snapshot: unsupported-operator
reveal_type(No + No)  # revealed: Unknown
```

```snapshot
error[unsupported-operator]: Unsupported `+` operation
  --> src/mdtest_snippet.py:15:13
   |
15 | reveal_type(No + No)  # revealed: Unknown
   |             --^^^--
   |             |
   |             Both operands have type `<class 'No'>`
   |
```

## Subclass

```py
from typing import Literal

class Yes:
    def __add__(self, other) -> Literal["+"]:
        return "+"

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] "Operator `+` is not supported between two objects of type `type[Yes]`"
reveal_type(yes() + yes())  # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Sub]`"
reveal_type(sub() + sub())  # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[No]`"
reveal_type(no() + no())  # revealed: Unknown
```

## Function literals

```py
def f():
    pass

# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f + f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `-` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f - f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `*` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f * f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `@` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f @ f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `/` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f / f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `%` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f % f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `**` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f**f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `<<` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f << f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `>>` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f >> f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `|` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f | f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `^` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f ^ f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `&` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f & f)  # revealed: Unknown
# error: [unsupported-operator] "Operator `//` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f // f)  # revealed: Unknown
```

## Classes from different modules with the same name

We use the fully qualified names in diagnostics if the two classes have the same unqualified name,
but are nonetheless different.

`mod1.py`:

```py
class A: ...
```

`mod2.py`:

```py
import mod1

class A: ...

# snapshot: unsupported-operator
A() + mod1.A()
```

```snapshot
error[unsupported-operator]: Unsupported `+` operation
 --> src/mod2.py:6:1
  |
6 | A() + mod1.A()
  | ---^^^--------
  | |     |
  | |     Has type `mod1.A`
  | Has type `mod2.A`
  |
```
