{"id":13473921,"url":"https://github.com/johnthagen/sealed-typing-pep","last_synced_at":"2025-05-05T21:14:34.295Z","repository":{"id":40435460,"uuid":"482352946","full_name":"johnthagen/sealed-typing-pep","owner":"johnthagen","description":"A PEP to add a `@sealed` typing decorator to Python","archived":false,"fork":false,"pushed_at":"2024-03-23T17:46:23.000Z","size":81,"stargazers_count":25,"open_issues_count":0,"forks_count":0,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-05T21:14:26.888Z","etag":null,"topics":["algebraic-data-types","pattern-matching","pep","python","sealed-class","type-annotations","type-safety"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnthagen.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-16T20:20:22.000Z","updated_at":"2025-04-24T18:57:32.000Z","dependencies_parsed_at":"2024-10-23T06:49:27.232Z","dependency_job_id":"959c87cd-2190-4ebc-993f-c89f583a0d09","html_url":"https://github.com/johnthagen/sealed-typing-pep","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fsealed-typing-pep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fsealed-typing-pep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fsealed-typing-pep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnthagen%2Fsealed-typing-pep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnthagen","download_url":"https://codeload.github.com/johnthagen/sealed-typing-pep/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252577026,"owners_count":21770721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algebraic-data-types","pattern-matching","pep","python","sealed-class","type-annotations","type-safety"],"created_at":"2024-07-31T16:01:08.039Z","updated_at":"2025-05-05T21:14:34.257Z","avatar_url":"https://github.com/johnthagen.png","language":null,"funding_links":[],"categories":["Others"],"sub_categories":[],"readme":"PEP: \u003cREQUIRED: pep number\u003e\nTitle: Sealed Decorator for Static Typing\nAuthor: John Hagen \u003cjohnthagen@gmail.com\u003e, David Hagen \u003cdavid@drhagen.com\u003e\nSponsor:\nPEP-Delegate: \u003cPEP delegate's real name\u003e\nDiscussions-To: https://discuss.python.org/t/draft-pep-sealed-decorator-for-static-typing/49206\nStatus: Draft\nType: Standards Track\nContent-Type: text/x-rst\nCreated: 22-Mar-2024\nPython-Version: 3.13\nPost-History:\nResolution: \u003curl\u003e\n\n\nAbstract\n========\n\nThis PEP proposes a ``@sealed`` decorator be added to the ``typing`` module to\nsupport creating versatile algebraic data types (ADTs) which type checkers can\nexhaustively pattern match against.\n\n\nMotivation\n==========\n\nQuite often it is desirable to apply exhaustiveness to a set of classes without\ndefining ad-hoc union types, which is itself fragile if a class is missing in\nthe union definition. A design pattern where a group of record-like classes is\ncombined into a union is popular in other languages that support pattern\nmatching [1]_ and is known as a nominal sum type, a key instantiation of\nalgebraic data types [2]_.\n\nWe propose adding a special decorator class ``@sealed`` to the ``typing``\nmodule [3]_, that will have no effect at runtime, but will indicate to static\ntype checkers that all direct subclasses of this class should be defined in the\nsame module as the base class.\n\nThe idea is that, since all subclasses are known, the type checker can treat\nthe sealed base class as a union of all its subclasses. Together with\ndataclasses this allows a clean and safe support of algebraic data types\nin Python. Consider this example,\n\n.. code-block:: python\n\n    from dataclasses import dataclass\n    from typing import sealed\n\n    @sealed\n    class Node:\n        ...\n\n    @sealed\n    class Expression(Node):\n        ...\n\n    @sealed\n    class Statement(Node):\n        ...\n\n    @dataclass\n    class Name(Expression):\n        name: str\n\n    @dataclass\n    class Operation(Expression):\n        left: Expression\n        op: str\n        right: Expression\n\n    @dataclass\n    class Assignment(Statement):\n        target: str\n        value: Expression\n\n    @dataclass\n    class Print(Statement):\n        value: Expression\n\nWith such a definition, a type checker can safely treat ``Node`` as\n``Union[Expression, Statement]``, and also safely treat ``Expression`` as\n``Union[Name, Operation]`` and ``Statement`` as ``Union[Assignment, Print]``.\nWith these declarations, a type checking error will occur in the below snippet,\nbecause ``Name`` is not handled (and the type checker can give a useful error\nmessage).\n\n.. code-block:: python\n\n    def dump(node: Node) -\u003e str:\n        match node:\n            case Assignment(target, value):\n                return f\"{target} = {dump(value)}\"\n            case Print(value):\n                return f\"print({dump(value)})\"\n            case Operation(left, op, right):\n                return f\"({dump(left)} {op} {dump(right)})\"\n\nNote: This section was largely derived from PEP 622 [4]_.\n\n\nRationale\n=========\n\nKotlin [5]_, Scala 2 [6]_, and Java 17 [7]_ all support a ``sealed`` keyword\nthat is used to declare algebraic data types. By using the same terminology,\nthe ``@sealed`` decorator will be familiar to developers familiar with those\nlanguages.\n\n\nSpecification\n=============\n\nThe ``typing.sealed`` decorator can be applied to the declaration of any class.\nThis decoration indicates to type checkers that all immediate subclasses of the\ndecorated class are defined in the current file.\n\nThe exhaustiveness checking features of type checkers should assume that there\nare no subclasses outside the current file, treating the decorated class as a\n``Union`` of all its same-file subclasses.\n\nType checkers should raise an error if a sealed class is inherited in a file\ndifferent from where the sealed class is declared.\n\nA sealed class is automatically declared to be abstract. Whatever actions a\ntype checker normally takes with abstract classes should be taken with sealed\nclasses as well. What exactly these behaviors are (e.g. disallowing\ninstantiation) is outside the scope of this PEP.\n\nSimilar to the ``typing.final`` decorator [8]_, the only runtime behavior of\nthis decorator is to set the ``__sealed__`` attribute of class to ``True`` so\nthat the sealed property of the class can be introspected. There is no runtime\nenforcement of sealed class inheritance.\n\n\nReference Implementation\n========================\n\n[Link to any existing implementation and details about its state, e.g.\nproof-of-concept.]\n\n\nRejected Ideas\n==============\n\n``Union`` of independent variants\n---------------------------------\n\nSome of the behavior of ``sealed`` can be emulated with ``Union`` today.\n\n.. code-block:: python\n\n    class Leaf: ...\n    class Branch: ...\n\n    Node = Leaf | Branch\n\nThe main problem with this is that the ADT loses all the features of\ninheritance, which is rather featureful in Python, to put it mildly. There can\nbe no abstract methods, private methods to be reused by the subclasses, public\nmethods to be exposed on all subclasses, class methods of any kind,\n``__init_subclass__``, etc. Even if a specific method is implemented on each\nsubclass, then rename, jump-to-definition, find-usage, and other IDE features\nare difficult to make work reliably.\n\nAdding a base class in addition to the union type alleviates some of these\nissues:\n\n.. code-block:: python\n\n    class BaseNode: ...\n\n    class Leaf(BaseNode): ...\n    class Branch(BaseNode): ...\n\n    Node = Leaf | Branch\n\nDespite being possible today, this is quite unergonomic. The base class and the\nunion type are conceptually the same thing, but have to be defined as two\nseparate objects. If this became standard, it seems Python would be first\nlanguage to separate the definition of an ADT into two different objects.\n\nThis duplication causes a serious don't-repeat-yourself problem. A new subclass\nmust be added to both the base class and the union type. Failure to do so will\nnot result in an immediate error but in inconsistent behavior between the two\nrepresentations.\n\nThe base class is not merely passive, either. There are a number of operations\nthat will only work when using the base class instead of the union type and\nvice verse. For example, matching only works on the base class, not the union\ntype:\n\n.. code-block:: python\n\n    maybe_node: Node | None = ...  # must be Node to enforce exhaustiveness\n\n    match maybe_node:\n        case Node():  # TypeError: called match pattern must be a type\n            ...\n        case None:\n            ...\n\n    match maybe_node:\n        case BaseNode():  # no error\n            ...\n        case None:\n            ...\n\nHaving to remember whether to use the base class or the union type in each\nsituation is particularly unfriendly to the user of a sealed class.\n\nGeneralize ``Enum``\n-------------------\n\nRust [9]_, Scala 3 [10]_, and Swift [11]_ support algebraic data types using a\ngeneralized ``enum`` mechanism.\n\n.. code-block:: rust\n\n    enum Message {\n        Quit,\n        Move { x: i32, y: i32 },\n        Write(String),\n        ChangeColor(i32, i32, i32),\n    }\n\nOne could imagine a generalization of the Python ``Enum`` [12]_ to support\nvariants of different shapes. Valueless variants could use ``enum.auto`` to\nkeep themselves terse.\n\n.. code-block:: python\n\n    from dataclasses import dataclass\n    from enum import auto, Enum\n\n    class Message(Enum):\n        Quit = auto()\n\n        @dataclass\n        class Move:\n            x: int\n            y: int\n\n        @dataclass\n        class Write:\n            message: str\n\n        @dataclass\n        class ChangeColor:\n            r: int\n            g: int\n            b: int\n\nThis solution allows attaching methods directly to the base ADT type,\nsomething a ``Union`` type lacks, but does not support the full\npower of inheritance that ``@sealed`` would provide.\n\nThis would be a substantial addition to the implementation and\nsemantics of ``Enum``.\n\nExplicitly list subclasses\n--------------------------\n\nJava requires that subclasses be explicitly listed with the base class.\n\n.. code-block:: java\n\n    public sealed interface Node\n        permits Leaf, Branch {}\n\n    public final class Leaf {}\n    public final class Branch {}\n\nThe advantage of this requirement is that subclasses can be defined anywhere,\nnot just in the same file, eliminating the somewhat weird file dependence of\nthis feature. The disadvantage is that it requires all subclasses to be\nwritten twice: once when defined and once in the enumerated list on the base\nclass.\n\nThere is also an inherent circular reference when explicitly enumerating the\nsubclasses. The subclass refers to the base class in order to inherit from it,\nand the base class refers to the subclasses in order to enumerate them. In\nstatically typed languages, these kinds of circular references in the types can\nbe managed, but in Python, it is much harder.\n\nFor example, this ``Sealed`` base class that behaves like ``Generic``:\n\n.. code-block:: python\n\n    from typing import Sealed\n\n    class Node(Sealed[Leaf, Branch]): ...\n\n    class Leaf(Node): ...\n    class Branch(Node): ...\n\nThis cannot work because ``Leaf`` must be defined before ``Node`` and ``Node``\nmust be defined before ``Leaf``. This is a not an annotation, so lazy\nannotations cannot save it. Perhaps, the subclasses in the enumerated list could\nbe strings, but that severely hurts the ergonomics of this feature.\n\nIf the enumerated list was in an annotation, it could be made to work, but there\nis no natural place for the annotation to live. Here is one possibility:\n\n.. code-block:: python\n\n    class Node:\n        __sealed__: Leaf | Branch\n\n    class Leaf(Node): ...\n    class Branch(Node): ...\n\nAdding syntax could overcome this limitation, but that is too big of a change to\nthe language to support just this feature:\n\n.. code-block:: python\n\n    class Node of Leaf | Branch:\n        ...\n\n    class Leaf(Node): ...\n    class Branch(Node): ...\n\nFootnotes\n=========\n\n.. [1]\n   https://en.wikipedia.org/wiki/Pattern_matching\n\n.. [2]\n   https://en.wikipedia.org/wiki/Algebraic_data_type\n\n.. [3]\n   https://docs.python.org/3/library/typing.html\n\n.. [4]\n   https://peps.python.org/pep-0622/#sealed-classes-as-algebraic-data-types\n\n.. [5]\n   https://kotlinlang.org/docs/sealed-classes.html\n\n.. [6]\n   https://docs.scala-lang.org/tour/pattern-matching.html\n\n.. [7]\n   https://openjdk.java.net/jeps/409\n\n.. [8]\n   https://peps.python.org/pep-0591/\n\n.. [9]\n   https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html\n\n.. [10]\n   https://docs.scala-lang.org/scala3/reference/enums/adts.html\n\n.. [11]\n   https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html\n\n.. [12]\n   https://docs.python.org/3/library/enum.html\n\n\n\nCopyright\n=========\n\nThis document is placed in the public domain.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnthagen%2Fsealed-typing-pep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnthagen%2Fsealed-typing-pep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnthagen%2Fsealed-typing-pep/lists"}