|
64 | 64 | YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SymbolNode, |
65 | 65 | SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, |
66 | 66 | YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, |
67 | | - IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, |
| 67 | + IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, EnumCallExpr, |
68 | 68 | COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, |
69 | 69 | ) |
70 | 70 | from mypy.typevars import has_no_typevars, fill_typevars |
@@ -1195,6 +1195,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: |
1195 | 1195 | self.process_typevar_declaration(s) |
1196 | 1196 | self.process_namedtuple_definition(s) |
1197 | 1197 | self.process_typeddict_definition(s) |
| 1198 | + self.process_enum_call(s) |
1198 | 1199 |
|
1199 | 1200 | if (len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr) and |
1200 | 1201 | s.lvalues[0].name == '__all__' and s.lvalues[0].kind == GDEF and |
@@ -1991,6 +1992,103 @@ def build_typeddict_typeinfo(self, name: str, items: List[str], |
1991 | 1992 |
|
1992 | 1993 | return info |
1993 | 1994 |
|
| 1995 | + def process_enum_call(self, s: AssignmentStmt) -> None: |
| 1996 | + """Check if s defines an Enum; if yes, store the definition in symbol table.""" |
| 1997 | + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): |
| 1998 | + return |
| 1999 | + lvalue = s.lvalues[0] |
| 2000 | + name = lvalue.name |
| 2001 | + enum_call = self.check_enum_call(s.rvalue, name) |
| 2002 | + if enum_call is None: |
| 2003 | + return |
| 2004 | + # Yes, it's a valid Enum definition. Add it to the symbol table. |
| 2005 | + node = self.lookup(name, s) |
| 2006 | + if node: |
| 2007 | + node.kind = GDEF # TODO locally defined Enum |
| 2008 | + node.node = enum_call |
| 2009 | + |
| 2010 | + def check_enum_call(self, node: Expression, var_name: str = None) -> Optional[TypeInfo]: |
| 2011 | + """Check if a call defines an Enum. |
| 2012 | +
|
| 2013 | + Example: |
| 2014 | +
|
| 2015 | + A = enum.Enum('A', 'foo bar') |
| 2016 | +
|
| 2017 | + is equivalent to: |
| 2018 | +
|
| 2019 | + class A(enum.Enum): |
| 2020 | + foo = 1 |
| 2021 | + bar = 2 |
| 2022 | + """ |
| 2023 | + if not isinstance(node, CallExpr): |
| 2024 | + return None |
| 2025 | + call = node |
| 2026 | + callee = call.callee |
| 2027 | + if not isinstance(callee, RefExpr): |
| 2028 | + return None |
| 2029 | + fullname = callee.fullname |
| 2030 | + if fullname not in ('enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag'): |
| 2031 | + return None |
| 2032 | + items, types, ok = self.parse_enum_call_args(call) |
| 2033 | + if not ok: |
| 2034 | + # Error. Construct dummy return value. |
| 2035 | + return self.build_enum_call_typeinfo('Enum', [], [], fullname) |
| 2036 | + name = cast(StrExpr, call.args[0]).value |
| 2037 | + if name != var_name or self.is_func_scope(): |
| 2038 | + # Give it a unique name derived from the line number. |
| 2039 | + name += '@' + str(call.line) |
| 2040 | + info = self.build_enum_call_typeinfo(name, items, types, fullname) |
| 2041 | + # Store it as a global just in case it would remain anonymous. |
| 2042 | + # (Or in the nearest class if there is one.) |
| 2043 | + stnode = SymbolTableNode(GDEF, info, self.cur_mod_id) |
| 2044 | + if self.type: |
| 2045 | + self.type.names[name] = stnode |
| 2046 | + else: |
| 2047 | + self.globals[name] = stnode |
| 2048 | + call.analyzed = EnumCallExpr(info, items) |
| 2049 | + call.analyzed.set_line(call.line, call.column) |
| 2050 | + return info |
| 2051 | + |
| 2052 | + def build_enum_call_typeinfo(self, name: str, items: List[str], |
| 2053 | + types: List[Type], fullname: str) -> TypeInfo: |
| 2054 | + base = self.named_type_or_none(fullname) |
| 2055 | + assert base is not None |
| 2056 | + info = self.basic_new_typeinfo(name, base) |
| 2057 | + info.is_enum = True |
| 2058 | + for item, typ in zip(items, types): |
| 2059 | + var = Var(item, typ) |
| 2060 | + var.info = info |
| 2061 | + var.is_property = True |
| 2062 | + info.names[item] = SymbolTableNode(MDEF, var) |
| 2063 | + return info |
| 2064 | + |
| 2065 | + def parse_enum_call_args(self, call: CallExpr) -> Tuple[List[str], List[Type], bool]: |
| 2066 | + args = call.args |
| 2067 | + if len(args) < 2: |
| 2068 | + return self.fail_enum_call_arg("Too few arguments for Enum()", call) |
| 2069 | + if len(args) > 2: |
| 2070 | + return self.fail_enum_call_arg("Too many arguments for Enum()", call) |
| 2071 | + if call.arg_kinds != [ARG_POS, ARG_POS]: |
| 2072 | + return self.fail_enum_call_arg("Unexpected arguments to Enum()", call) |
| 2073 | + if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): |
| 2074 | + return self.fail_typeddict_arg( |
| 2075 | + "Enum() expects a string literal as the first argument", call) |
| 2076 | + if not isinstance(args[1], (StrExpr, BytesExpr, UnicodeExpr)): |
| 2077 | + return self.fail_typeddict_arg( |
| 2078 | + "Enum() expects a string literal as the second argument", call) |
| 2079 | + fields = cast(StrExpr, args[1]).value |
| 2080 | + items = [] |
| 2081 | + types = [] # type: List[Type] |
| 2082 | + for field in fields.replace(',', ' ').split(): |
| 2083 | + items.append(field) |
| 2084 | + types.append(AnyType()) |
| 2085 | + return items, types, True |
| 2086 | + |
| 2087 | + def fail_enum_call_arg(self, message: str, |
| 2088 | + context: Context) -> Tuple[List[str], List[Type], bool]: |
| 2089 | + self.fail(message, context) |
| 2090 | + return [], [], False |
| 2091 | + |
1994 | 2092 | def visit_decorator(self, dec: Decorator) -> None: |
1995 | 2093 | for d in dec.decorators: |
1996 | 2094 | d.accept(self) |
|
0 commit comments