Enums: Teach mypy that descriptors are not converted to become enum members
Fixes #12494.
Description
At runtime, most objects in an enum class statement are automatically converted by the enum module into members of the enumeration. However, if an object has a __get__, __set__ or __delete__ method, special-casing by the enum module means that the object is not converted into an enum member. Mypy is currently aware of this special-casing, leading it to falsely assume that certain enum classes are unsubclassable, when, in fact, they are.
This PR teaches mypy about descriptor special-casing in enums.
Test Plan
- Two test cases have been added.
- One test case has been extended.
- One test case has been altered slightly -- it was in fact buggy. (It shouldn't have been passing.)
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
Ah, this patch fixes some false positives where mypy complains about enums-with-no-members being unsubclassable, but there are still some issues:
from enum import Enum
class Foo(Enum):
x = classmethod(lambda cls: 42)
reveal_type(Foo.x) # note: Revealed type is "Literal[test.Foo.x]?"
b = Foo.x() # error: "Foo" not callable
Diff from mypy_primer, showing the effect of this PR on open source code:
psycopg (https://github.com/psycopg/psycopg)
+ psycopg/psycopg/pq/_enums.py:202: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
+ https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
+ Please report a bug at https://github.com/python/mypy/issues
+ version: 0.960+dev.07988fccbcb97b054cde780b2463387193d8a0ec
+ psycopg/psycopg/pq/_enums.py:202: : note: use --pdb to drop into pdb
+ Traceback (most recent call last):
+ File "", line 8, in <module>
+ sys.exit(console_entry())
+ File "/__main__.py", line 12, in console_entry
+ main(None, sys.stdout, sys.stderr)
+ File "/main.py", line 96, in main
+ res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
+ File "/main.py", line 173, in run_build
+ res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
+ File "/build.py", line 180, in build
+ result = _build(
+ File "/build.py", line 256, in _build
+ graph = dispatch(sources, manager, stdout)
+ File "/build.py", line 2733, in dispatch
+ process_graph(graph, manager)
+ File "/build.py", line 3081, in process_graph
+ process_stale_scc(graph, scc, manager)
+ File "/build.py", line 3173, in process_stale_scc
+ mypy.semanal_main.semantic_analysis_for_scc(graph, scc, manager.errors)
+ File "/semanal_main.py", line 78, in semantic_analysis_for_scc
+ process_top_levels(graph, scc, patches)
+ File "/semanal_main.py", line 199, in process_top_levels
+ deferred, incomplete, progress = semantic_analyze_target(next_id, state,
+ File "/semanal_main.py", line 326, in semantic_analyze_target
+ analyzer.refresh_partial(refresh_node,
+ File "/semanal.py", line 414, in refresh_partial
+ self.refresh_top_level(node)
+ File "/semanal.py", line 425, in refresh_top_level
+ self.accept(d)
+ File "/semanal.py", line 5352, in accept
+ node.accept(self)
+ File "/nodes.py", line 1028, in accept
+ return visitor.visit_class_def(self)
+ File "/semanal.py", line 1121, in visit_class_def
+ self.analyze_class(defn)
+ File "/semanal.py", line 1201, in analyze_class
+ self.analyze_class_body_common(defn)
+ File "/semanal.py", line 1209, in analyze_class_body_common
+ defn.defs.accept(self)
+ File "/nodes.py", line 1099, in accept
+ return visitor.visit_block(self)
+ File "/semanal.py", line 3630, in visit_block
+ self.accept(s)
+ File "/semanal.py", line 5352, in accept
+ node.accept(self)
+ File "/nodes.py", line 1165, in accept
+ return visitor.visit_assignment_stmt(self)
+ File "/semanal.py", line 2100, in visit_assignment_stmt
+ self.store_final_status(s)
+ File "/semanal.py", line 2495, in store_final_status
+ info = self.named_type(s.rvalue.callee.fullname).type
+ File "/semanal.py", line 4725, in named_type
+ assert isinstance(node, TypeInfo)
+ AssertionError:
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
I doubt I'll get to this any time soon