python / python/mypy

User defined type-level operators works, are they legal?

Open
#13,894 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

documentation
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

While looking for a workaround about the lack of extensible typed records, found some approaches that works, but then I realized, I'm using quite a few type level operators for that, are they legal?

My use case is too cumbersome and irrelevant to explain, but a simpler self-explanatory example:

Retrieving inner type of Awaitable:
from typing import Awaitable, Type, TypeVar

A = TypeVar("A")


class typeofMeta(type):
    def __getitem__(cls, value: A) -> Type[A]: raise NotImplementedError
class AwaitedMeta(type):
    def __getitem__(cls, value: Type[Awaitable[A]]) -> Type[A]: raise NotImplementedError

class typeof(metaclass=typeofMeta): ...
class Awaited(metaclass=AwaitedMeta): ...


async def add(a: int, b: int) -> int:
    return a + b


coro = add(1, 2)

reveal_type(Awaited[typeof[coro]])
# Revealed type is "Type[builtins.int]"

By defining a __getitem__ at class level I can do whatever with the types and make native-looking type operators. After that I noticed the use of @_SpecialForm in typing internals, but no mention that I could find in docs if this use can be replicated by developers. So I'm wondering if it's safe to rely on this kind of stuff, it enables a lot of nice composition patterns and zero-cost abstractions structures, like is common is TS.

Here are a few more typescript util clones, full implementation in gist:

Type-Level if-else
reveal_type(InstanceType[If[True, int, str]])
# Revealed type is 'builtins.int'

reveal_type(InstanceType[If[False, int, str]])
# Revealed type is 'builtins.str'
Get precise type of an object
def add(a: int, b: int) -> int: return a + b

reveal_type(typeof[add])
# Revealed type is 'def (builtins.int, builtins.int) -> builtins.int'

reveal_type(Type[add])  # with builtin `Type` get down to object
# Revealed type is builtins.object'
Get instance type from a class
class Box(Generic[A]):
    value: A

reveal_type(InstanceType[Box[int]])
# Revealed type is 'Box[builtins.int]'
Get return type of a callable
def add(a: int, b: int) -> int: return a + b

reveal_type(ReturnType[add])
# Revealed type is 'builtins.int'
Get parameter typle of a callable
def add(a: int, b: int) -> int: return a + b

reveal_type(Parameters[add])
# Revealed type is 'Tuple[builtins.int, builtins.int]'
Get inner type of an Awaitable
async def add(a: int, b: int) -> int: return a + b
coro = add(1, 2)

reveal_type(Awaited[typeof[coro]])
# Revealed type is "Type[builtins.int]"
Get item type of an iterable
reveal_type(ItemOf[list[int]])
# Revealed type is 'Type[builtins.int]'
Get key type of a Mapping
reveal_type(keyof[dict[str, int]])
# Revealed type is 'builtins.str'

So, are those legal?

Searched but couldn't find docs on it. I do remember seeing @gvanrossum commenting on some issue (couldn't find it now) that mypy is being conservative about allowing user-defined type operators, so I'm a bit afraid of using those.

In pyright it's even crazier, a lot of tricks with tuples are possible with Head[Unpack[Vars]] and Tail[Unpack[Vars]], plus some typevar bounding behaviour that made me notice even higher kinded types implementation is possible using the Lightweight higher-kinded polymorphism encoding, or maybe even a small type-level implementation of lisp

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the user-defined operators and reveal_type examples in the issue, then compare them with mypy/typeshed/stdlib/typing.pyi and the linked pyright behavior. Determine whether the documented typing rules address these constructs and what specific documentation gap remains; the issue is complete only when the legality and supported usage are documented or clearly resolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.