Specify what happens for binary operators (e.g. `__add__`)
There's a subtle dance around __add__, __radd__, and NotImplemented. While most of the rules are in the language spec, I think we haven't specified carefully what the signature should be of e.g. the __add__ method, especially when annotations are inline. (At least, I didn't find any mention of NotImplemented in the spec.)
In typeshed we seem to ignore the "overload" __add__: (Any) -> Literal[NotImplemented] (to be loose with notation), but what do typecheckers do when they see a concrete __add__ or __eq__ implementation that can return NotImplemented?
I think type checkers can implement the semantics of NotImplemented without actually requiring it in the types. Specifically, given a + b,
- if
type(a).__add__(b)type checks, i.e.type(a)has an overload expectingb, -- use that, - otherwise fall back
type(b).__radd__(a).
Note also that
-
Literal[NotImplemented]is not permitted becauseNotImplementedis not an enum nor a literal syntactically; -
NotImplementedTypecould be used to describe instances ofNotImplemented, but it is not currently available from pure python AFAICT; - Alternatively, we could handle
NotImplementedsimilarly toNonein thatNotImplementedin a type annotation meansNotImplementedType.
Those are not the exact rules. Sometimes __radd__ is called first (the rule is so complicated I don't recall offhand if that can be determined statically or not). And when both return NotImplemented, the interpreter issues the exception.
Anyway, we might still consider this, given that the convention in typeshed appears to be not to show NotImplemented. (This would only be a problem for direct callers of these overloads, which must be very rare.)
I am aware of the issues around Literal[NotImplemented], I just used as a shorthand (if it did work, it would be ideal :-).
NotImplemented is generally just ignored by the type system; in typeshed we treat it as being a subclass of Any
https://github.com/python/typeshed/blob/d4daff337fc8ac122a10cff21ea2e7c9f3a30a6d/stdlib/builtins.pyi#L1261
I think to move forward here we'd need:
- A survey of what type checkers currently do
- Some thinking about how we could make the behavior more useful. Are there plausible user mistakes that type checkers currently can't catch, but that could be caught if we came up with a better set of rules? Conversely, are there patterns that work at runtime that type checkers currently do not allow?