@@ -641,6 +641,30 @@ reveal_type(ManyAliases.alias3.value) # revealed: Literal["real_member"]
641641reveal_type(ManyAliases.alias3.name) # revealed: Literal["real_member"]
642642```
643643
644+ Functional enums also detect duplicate-value aliases in both dict and list-of-tuples forms:
645+
646+ ``` py
647+ from enum import Enum
648+ from ty_extensions import enum_members
649+
650+ DictAlias = Enum(" DictAlias" , {" A" : 1 , " B" : 1 })
651+
652+ # revealed: tuple[Literal["A"]]
653+ reveal_type(enum_members(DictAlias))
654+
655+ # single-member enum is a singleton, so member access resolves to the instance type
656+ reveal_type(DictAlias.A) # revealed: DictAlias
657+ reveal_type(DictAlias.B) # revealed: DictAlias
658+
659+ PairsAlias = Enum(" PairsAlias" , [(" A" , 1 ), (" B" , 1 )])
660+
661+ # revealed: tuple[Literal["A"]]
662+ reveal_type(enum_members(PairsAlias))
663+
664+ reveal_type(PairsAlias.A) # revealed: PairsAlias
665+ reveal_type(PairsAlias.B) # revealed: PairsAlias
666+ ```
667+
644668### Using ` auto() `
645669
646670``` toml
@@ -683,6 +707,24 @@ reveal_type(Mixed.MANUAL_2.value) # revealed: Literal[-2]
683707reveal_type(Mixed.AUTO_2 .value) # revealed: Literal[2]
684708```
685709
710+ If ` auto() ` follows a non-literal value, the generated value widens to ` int ` since the previous
711+ value isn't known at type-check time:
712+
713+ ``` py
714+ def f (n : int ):
715+ class StaticDynamic (Enum ):
716+ A = n
717+ B = auto()
718+
719+ reveal_type(StaticDynamic.A.value) # revealed: int
720+ reveal_type(StaticDynamic.B.value) # revealed: int
721+
722+ Dynamic = Enum(" Dynamic" , {" A" : n, " B" : auto()})
723+
724+ reveal_type(Dynamic.A.value) # revealed: int
725+ reveal_type(Dynamic.B.value) # revealed: int
726+ ```
727+
686728When using ` auto() ` with ` StrEnum ` , the value is the lowercase name of the member:
687729
688730``` py
@@ -1410,6 +1452,24 @@ reveal_type(Mixed.B.value) # revealed: Literal[11]
14101452reveal_type(Mixed.C.value) # revealed: Literal[12]
14111453```
14121454
1455+ ### ` auto() ` in tuple/list entries
1456+
1457+ ` auto() ` should also expand in tuple/list entry forms of the functional syntax:
1458+
1459+ ``` py
1460+ from enum import Enum, Flag, auto
1461+
1462+ Color = Enum(" Color" , [(" RED" , auto()), (" GREEN" , auto())])
1463+
1464+ reveal_type(Color.RED .value) # revealed: Literal[1]
1465+ reveal_type(Color.GREEN .value) # revealed: Literal[2]
1466+
1467+ Perm = Flag(" Perm" , ((" READ" , auto()), (" WRITE" , auto())))
1468+
1469+ reveal_type(Perm.READ .value) # revealed: Literal[1]
1470+ reveal_type(Perm.WRITE .value) # revealed: Literal[2]
1471+ ```
1472+
14131473### Duplicate member names
14141474
14151475Duplicate member names raise ` TypeError ` at runtime. We degrade to unknown members rather than
@@ -1503,6 +1563,19 @@ from enum import Enum
15031563Color = Enum(" Color" , " RED GREEN BLUE" , bad_kwarg = True )
15041564```
15051565
1566+ ### Keyword argument type validation
1567+
1568+ Functional enum construction should still preserve overload-based argument validation:
1569+
1570+ ``` py
1571+ from enum import Enum
1572+
1573+ # error: [invalid-argument-type]
1574+ Color = Enum(" Color" , " RED" , start = " 0" )
1575+
1576+ reveal_type(Color.RED .value) # revealed: Literal[1]
1577+ ```
1578+
15061579### ` boundary ` keyword (Python 3.11+)
15071580
15081581#### Available on 3.11+
@@ -1576,6 +1649,43 @@ reveal_type(Http.OK.value) # revealed: Literal[1]
15761649reveal_type(Http.NOT_FOUND .value) # revealed: Literal[2]
15771650```
15781651
1652+ Functional enums should still validate ` type= ` arguments eagerly, both for obvious non-types and for
1653+ bases that are structurally invalid to combine with ` Enum ` :
1654+
1655+ ``` py
1656+ from enum import Enum
1657+ from typing import TypedDict
1658+
1659+ # error: [invalid-argument-type]
1660+ BadType = Enum(" BadType" , " RED" , type = 1 )
1661+
1662+ # error: [invalid-argument-type]
1663+ BadStringType = Enum(" BadStringType" , " RED" , type = " Mixin" )
1664+
1665+ TD = TypedDict(" TD" , {" x" : int })
1666+
1667+ # error: [invalid-base]
1668+ BadBase = Enum(" BadBase" , " RED" , type = TD )
1669+ ```
1670+
1671+ Functional enums with a ` type= ` mixin should also have the same MRO as the equivalent static enum
1672+ class:
1673+
1674+ ``` py
1675+ from enum import Enum
1676+ from ty_extensions import reveal_mro
1677+
1678+ Http = Enum(" Http" , " OK NOT_FOUND" , type = int )
1679+
1680+ reveal_mro(Http) # revealed: (<class 'Http'>, <class 'int'>, <class 'Enum'>, <class 'object'>)
1681+
1682+ class StaticHttp (int , Enum ):
1683+ OK = 1
1684+ NOT_FOUND = 2
1685+
1686+ reveal_mro(StaticHttp) # revealed: (<class 'StaticHttp'>, <class 'int'>, <class 'Enum'>, <class 'object'>)
1687+ ```
1688+
15791689### IntEnum function syntax
15801690
15811691``` py
@@ -1638,6 +1748,59 @@ reveal_type(BigFlag.X.value) # revealed: Literal[4611686018427387904]
16381748reveal_type(BigFlag.Y.value) # revealed: int
16391749```
16401750
1751+ ### Accessing members from instances
1752+
1753+ ``` py
1754+ from enum import Enum
1755+
1756+ Answer = Enum(" Answer" , " YES NO" )
1757+
1758+ reveal_type(Answer.YES .NO ) # revealed: Literal[Answer.NO]
1759+
1760+ def _ (answer : Answer) -> None :
1761+ reveal_type(answer.YES ) # revealed: Literal[Answer.YES]
1762+ reveal_type(answer.NO ) # revealed: Literal[Answer.NO]
1763+ ```
1764+
1765+ ### Accessing members from ` type[…] `
1766+
1767+ ``` py
1768+ from enum import Enum
1769+
1770+ Answer = Enum(" Answer" , " YES NO" )
1771+
1772+ def _ (answer : type[Answer]) -> None :
1773+ reveal_type(answer.YES ) # revealed: Literal[Answer.YES]
1774+ reveal_type(answer.NO ) # revealed: Literal[Answer.NO]
1775+ ```
1776+
1777+ ### Implicitly final
1778+
1779+ Functional enums with members should also be implicitly final:
1780+
1781+ ``` py
1782+ from enum import Enum
1783+
1784+ Color = Enum(" Color" , " RED GREEN BLUE" )
1785+
1786+ # error: [subclass-of-final-class]
1787+ class ExtendedColor (Color ):
1788+ YELLOW = 4
1789+ ```
1790+
1791+ ### Meta-type
1792+
1793+ ``` py
1794+ from enum import Enum
1795+
1796+ Answer = Enum(" Answer" , " YES NO" )
1797+
1798+ reveal_type(type (Answer.YES )) # revealed: <class 'Answer'>
1799+
1800+ def _ (answer : Answer):
1801+ reveal_type(type (answer)) # revealed: <class 'Answer'>
1802+ ```
1803+
16411804## Exhaustiveness checking
16421805
16431806## ` if ` statements
@@ -1744,6 +1907,80 @@ def singleton_check(value: Singleton) -> str:
17441907 assert_never(value)
17451908```
17461909
1910+ ## ` if ` statements (function syntax)
1911+
1912+ ``` py
1913+ from enum import Enum
1914+ from typing_extensions import assert_never
1915+
1916+ Color = Enum(" Color" , " RED GREEN BLUE" )
1917+
1918+ def color_name (color : Color) -> str :
1919+ if color is Color.RED :
1920+ return " Red"
1921+ elif color is Color.GREEN :
1922+ return " Green"
1923+ elif color is Color.BLUE :
1924+ return " Blue"
1925+ else :
1926+ assert_never(color)
1927+
1928+ def color_name_without_assertion (color : Color) -> str :
1929+ if color is Color.RED :
1930+ return " Red"
1931+ elif color is Color.GREEN :
1932+ return " Green"
1933+ elif color is Color.BLUE :
1934+ return " Blue"
1935+
1936+ def color_name_misses_one_variant (color : Color) -> str :
1937+ if color is Color.RED :
1938+ return " Red"
1939+ elif color is Color.GREEN :
1940+ return " Green"
1941+ else :
1942+ assert_never(color) # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"
1943+ ```
1944+
1945+ ## ` match ` statements (function syntax)
1946+
1947+ TODO: ` match ` exhaustiveness does not yet work for functional enums. The pattern matching narrowing
1948+ path does not resolve functional enum members the same way ` is ` comparisons do.
1949+
1950+ ``` toml
1951+ [environment ]
1952+ python-version = " 3.10"
1953+ ```
1954+
1955+ ``` py
1956+ from enum import Enum
1957+ from typing_extensions import assert_never
1958+
1959+ Color = Enum(" Color" , " RED GREEN BLUE" )
1960+
1961+ # TODO : `assert_never` should not fire here (exhaustive match).
1962+ def color_name (color : Color) -> str :
1963+ match color:
1964+ case Color.RED :
1965+ return " Red"
1966+ case Color.GREEN :
1967+ return " Green"
1968+ case Color.BLUE :
1969+ return " Blue"
1970+ case _:
1971+ assert_never(color) # error: [type-assertion-failure]
1972+
1973+ # TODO : This should ideally emit `Literal[Color.BLUE]` in the assertion, not `Color`.
1974+ def color_name_misses_one_variant (color : Color) -> str :
1975+ match color:
1976+ case Color.RED :
1977+ return " Red"
1978+ case Color.GREEN :
1979+ return " Green"
1980+ case _:
1981+ assert_never(color) # error: [type-assertion-failure] "Type `Color` is not equivalent to `Never`"
1982+ ```
1983+
17471984## ` __eq__ ` and ` __ne__ `
17481985
17491986### No ` __eq__ ` or ` __ne__ ` overrides
0 commit comments