From 4fc5d6c2d17fc24819ce4d950e5278bd98b31f46 Mon Sep 17 00:00:00 2001 From: Tobias Alex-Petersen Date: Thu, 30 Jul 2026 13:47:00 +0200 Subject: [PATCH 1/2] Add conformance test for full literal-union equivalency of enums The spec's Enum Literal Expansion section states that a type checker should treat a complete union of all literal members as equivalent to the enum type. Add test to cover cases failing in some checkers. Also refreshes all three quoted spec passages, which had drifted from the section as rewritten in a6eeccf ("new type system concepts section", Reported in python/typing#2323. --- conformance/tests/enums_expansion.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/conformance/tests/enums_expansion.py b/conformance/tests/enums_expansion.py index 57f701f10..ae77565dc 100644 --- a/conformance/tests/enums_expansion.py +++ b/conformance/tests/enums_expansion.py @@ -8,8 +8,11 @@ from typing import Literal, Never, assert_type # > From the perspective of the type system, most enum classes are equivalent -# > to the union of the literal members within that enum. Type checkers may -# > therefore expand an enum type +# > to the union of the literal members within that enum. [...] Because of the +# > equivalency between an enum class and the union of literal members within +# > that enum, the two types may be used interchangeably. Type checkers may +# > therefore expand an enum type (that does not derive from enum.Flag) into a +# > union of literal values during type narrowing and exhaustion detection class Color(Enum): @@ -35,8 +38,8 @@ def print_color2(c: Color): assert_type(c, Never) # E? -# > This rule does not apply to classes that derive from enum. Flag because -# > these enums allow flags to be combined in arbitrary ways. +# > (This rule does not apply to classes that derive from enum.Flag because +# > these enums allow flags to be combined in arbitrary ways.) class CustomFlags(Flag): @@ -64,7 +67,7 @@ def test2(f: CustomFlags): # > A type checker should treat a complete union of all literal members as -# > compatible with the enum type. +# > equivalent to the enum type. class Answer(Enum): @@ -76,3 +79,8 @@ def test3(val: object) -> list[Answer]: assert val is Answer.Yes or val is Answer.No x = [val] return x + + +def test4(a: Answer) -> None: + x: Literal[Answer.Yes, Answer.No] = a + assert_type(a, Literal[Answer.Yes, Answer.No]) From 43b2427aecaec341d2b820d710fc675f0e1c782d Mon Sep 17 00:00:00 2001 From: Tobias Alex-Petersen Date: Thu, 30 Jul 2026 14:09:25 +0200 Subject: [PATCH 2/2] Update conformance results for the new enum expansion test Four checkers accept the full-literal-union equivalency and are unaffected: mypy, pyrefly, pycroscope and ty (mypy and ty remain "Partial" for the pre-existing `Flag` narrowing gap only). Three drop from "Pass" to "Partial": * pyright and zuban reject both the assignment and the `assert_type`. * basilisk rejects only the assignment; it reports the `assert_type` in test1 as redundant, so it does treat the two types as equivalent there. --- .../results/basilisk/enums_expansion.toml | 13 +++++--- conformance/results/mypy/enums_expansion.toml | 6 ++-- .../results/pycroscope/enums_expansion.toml | 2 +- .../results/pyrefly/enums_expansion.toml | 2 +- .../results/pyright/enums_expansion.toml | 16 +++++++-- conformance/results/results.html | 33 ++++++++++++++----- conformance/results/ty/enums_expansion.toml | 9 ++--- .../results/zuban/enums_expansion.toml | 14 ++++++-- 8 files changed, 67 insertions(+), 28 deletions(-) diff --git a/conformance/results/basilisk/enums_expansion.toml b/conformance/results/basilisk/enums_expansion.toml index 8895a74ec..e4a77e027 100644 --- a/conformance/results/basilisk/enums_expansion.toml +++ b/conformance/results/basilisk/enums_expansion.toml @@ -1,8 +1,13 @@ -conformant = "Pass" -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Does not accept an enum-typed value where a complete union of all literal members is declared, although it treats the two as equivalent for `assert_type`. +""" +conformance_automated = "Fail" errors_diff = """ +Line 85: Unexpected errors ['enums_expansion.py:85:5: error: Type mismatch: `x` is annotated `Literal[Answer.Yes, Answer.No]` (answer.yes | answer.no) but assigned answer [assignment_compatibility]'] """ output = """ -enums_expansion.py:25:9: error: Redundant `assert_type` with `Literal[Color.GREEN]` on enum-typed parameter [enums_expansion] -enums_expansion.py:53:9: error: Redundant `assert_type` with `Literal[CustomFlags.FLAG3]` on enum-typed parameter [enums_expansion] +enums_expansion.py:28:9: error: Redundant `assert_type` with `Literal[Color.GREEN]` on enum-typed parameter [enums_expansion] +enums_expansion.py:56:9: error: Redundant `assert_type` with `Literal[CustomFlags.FLAG3]` on enum-typed parameter [enums_expansion] +enums_expansion.py:85:5: error: Type mismatch: `x` is annotated `Literal[Answer.Yes, Answer.No]` (answer.yes | answer.no) but assigned answer [assignment_compatibility] """ diff --git a/conformance/results/mypy/enums_expansion.toml b/conformance/results/mypy/enums_expansion.toml index 8b6a14aa0..b14b1afd2 100644 --- a/conformance/results/mypy/enums_expansion.toml +++ b/conformance/results/mypy/enums_expansion.toml @@ -4,9 +4,9 @@ Improperly applies narrowing to `Flag` subclass. """ conformance_automated = "Fail" errors_diff = """ -Line 53: Expected 1 errors -Line 52: Unexpected errors ['enums_expansion.py:52: error: Expression is of type "Literal[CustomFlags.FLAG3]", not "CustomFlags" [assert-type]'] +Line 56: Expected 1 errors +Line 55: Unexpected errors ['enums_expansion.py:55: error: Expression is of type "Literal[CustomFlags.FLAG3]", not "CustomFlags" [assert-type]'] """ output = """ -enums_expansion.py:52: error: Expression is of type "Literal[CustomFlags.FLAG3]", not "CustomFlags" [assert-type] +enums_expansion.py:55: error: Expression is of type "Literal[CustomFlags.FLAG3]", not "CustomFlags" [assert-type] """ diff --git a/conformance/results/pycroscope/enums_expansion.toml b/conformance/results/pycroscope/enums_expansion.toml index e12a9c12f..529ddb5b1 100644 --- a/conformance/results/pycroscope/enums_expansion.toml +++ b/conformance/results/pycroscope/enums_expansion.toml @@ -2,5 +2,5 @@ conformance_automated = "Pass" errors_diff = """ """ output = """ -./enums_expansion.py:53:20: enums_expansion.CustomFlags is not equivalent to Literal[] +./enums_expansion.py:56:20: enums_expansion.CustomFlags is not equivalent to Literal[] """ diff --git a/conformance/results/pyrefly/enums_expansion.toml b/conformance/results/pyrefly/enums_expansion.toml index bbf9ce7ce..7c27606f9 100644 --- a/conformance/results/pyrefly/enums_expansion.toml +++ b/conformance/results/pyrefly/enums_expansion.toml @@ -3,5 +3,5 @@ conformance_automated = "Pass" errors_diff = """ """ output = """ -ERROR enums_expansion.py:53:20-51: assert_type(CustomFlags, Literal[CustomFlags.FLAG3]) failed [assert-type] +ERROR enums_expansion.py:56:20-51: assert_type(CustomFlags, Literal[CustomFlags.FLAG3]) failed [assert-type] """ diff --git a/conformance/results/pyright/enums_expansion.toml b/conformance/results/pyright/enums_expansion.toml index 2a48dea06..ad3e5573a 100644 --- a/conformance/results/pyright/enums_expansion.toml +++ b/conformance/results/pyright/enums_expansion.toml @@ -1,7 +1,17 @@ -conformant = "Pass" -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Does not treat a complete union of all literal members as equivalent to the enum type. +""" +conformance_automated = "Fail" errors_diff = """ +Line 85: Unexpected errors ['enums_expansion.py:85:41 - error: Type "Answer" is not assignable to declared type "Literal[Answer.Yes, Answer.No]"'] +Line 86: Unexpected errors ['enums_expansion.py:86:17 - error: "assert_type" mismatch: expected "Literal[Answer.Yes, Answer.No]" but received "Answer" (reportAssertTypeFailure)'] """ output = """ -enums_expansion.py:53:21 - error: "assert_type" mismatch: expected "Literal[CustomFlags.FLAG3]" but received "CustomFlags" (reportAssertTypeFailure) +enums_expansion.py:56:21 - error: "assert_type" mismatch: expected "Literal[CustomFlags.FLAG3]" but received "CustomFlags" (reportAssertTypeFailure) +enums_expansion.py:85:41 - error: Type "Answer" is not assignable to declared type "Literal[Answer.Yes, Answer.No]" +  Type "Answer" is not assignable to type "Literal[Answer.Yes, Answer.No]" +    "Answer" is not assignable to type "Literal[Answer.Yes]" +    "Answer" is not assignable to type "Literal[Answer.No]" (reportAssignmentType) +enums_expansion.py:86:17 - error: "assert_type" mismatch: expected "Literal[Answer.Yes, Answer.No]" but received "Answer" (reportAssertTypeFailure) """ diff --git a/conformance/results/results.html b/conformance/results/results.html index a236e7b95..23306cb7d 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -2477,7 +2477,12 @@

Python Type System Conformance Test Results

enums_expansion - Pass + + Partial +
    +
  • Does not accept an enum-typed value where a complete union of all literal members is declared, although it treats the two as equivalent for assert_type.
  • +
+ Partial
    @@ -2486,14 +2491,24 @@

    Python Type System Conformance Test Results

    Pass Pass - Pass + + Partial +
      +
    • Does not treat a complete union of all literal members as equivalent to the enum type.
    • +
    + Partial
    • Does not support enum.Flag.
    - Pass + + Partial +
      +
    • Does not treat a complete union of all literal members as equivalent to the enum type.
    • +
    + enums_member_names @@ -2553,13 +2568,13 @@

    Python Type System Conformance Test Results

    - 6 / 6 • 100.0% + 5.5 / 6 • 91.7% 4.5 / 6 • 75.0% 5.5 / 6 • 91.7% 6 / 6 • 100.0% - 6 / 6 • 100.0% 5.5 / 6 • 91.7% - 6 / 6 • 100.0% + 5.5 / 6 • 91.7% + 5.5 / 6 • 91.7% @@ -2807,13 +2822,13 @@

    Python Type System Conformance Test Results

    - 141 / 141 • 100.0% + 140.5 / 141 • 99.6% 109 / 141 • 77.3% 130 / 141 • 92.2% 138 / 141 • 97.9% - 136.5 / 141 • 96.8% + 136 / 141 • 96.5% 116 / 141 • 82.3% - 140.5 / 141 • 99.6% + 140 / 141 • 99.3% diff --git a/conformance/results/ty/enums_expansion.toml b/conformance/results/ty/enums_expansion.toml index 5e5e1f0de..485e934fe 100644 --- a/conformance/results/ty/enums_expansion.toml +++ b/conformance/results/ty/enums_expansion.toml @@ -1,10 +1,11 @@ conformance_automated = "Fail" conformant = "Partial" -notes = """Does not support `enum.Flag`.""" +notes = """ +Does not support `enum.Flag`.""" errors_diff = """ -Line 53: Expected 1 errors -Line 52: Unexpected errors ['enums_expansion.py:52:9: error[type-assertion-failure] Type `Literal[CustomFlags.FLAG3]` does not match asserted type `CustomFlags`'] +Line 56: Expected 1 errors +Line 55: Unexpected errors ['enums_expansion.py:55:9: error[type-assertion-failure] Type `Literal[CustomFlags.FLAG3]` does not match asserted type `CustomFlags`'] """ output = """ -enums_expansion.py:52:9: error[type-assertion-failure] Type `Literal[CustomFlags.FLAG3]` does not match asserted type `CustomFlags` +enums_expansion.py:55:9: error[type-assertion-failure] Type `Literal[CustomFlags.FLAG3]` does not match asserted type `CustomFlags` """ diff --git a/conformance/results/zuban/enums_expansion.toml b/conformance/results/zuban/enums_expansion.toml index 787ef8ef0..99ac1e7f1 100644 --- a/conformance/results/zuban/enums_expansion.toml +++ b/conformance/results/zuban/enums_expansion.toml @@ -1,7 +1,15 @@ -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Does not treat a complete union of all literal members as equivalent to the enum type. +""" +conformance_automated = "Fail" errors_diff = """ +Line 85: Unexpected errors ['enums_expansion.py:85: error: Incompatible types in assignment (expression has type "Answer", variable has type "Literal[Answer.Yes, Answer.No]") [assignment]'] +Line 86: Unexpected errors ['enums_expansion.py:86: error: Expression is of type "Answer", not "Literal[Answer.Yes, Answer.No]" [misc]'] """ output = """ -enums_expansion.py:35: error: Statement is unreachable [unreachable] -enums_expansion.py:53: error: Expression is of type "CustomFlags", not "Literal[CustomFlags.FLAG3]" [misc] +enums_expansion.py:38: error: Statement is unreachable [unreachable] +enums_expansion.py:56: error: Expression is of type "CustomFlags", not "Literal[CustomFlags.FLAG3]" [misc] +enums_expansion.py:85: error: Incompatible types in assignment (expression has type "Answer", variable has type "Literal[Answer.Yes, Answer.No]") [assignment] +enums_expansion.py:86: error: Expression is of type "Answer", not "Literal[Answer.Yes, Answer.No]" [misc] """