@@ -357,106 +357,55 @@ def __mul__(self, other):
357357
358358def infer_enum_class (node : nodes .ClassDef ) -> nodes .ClassDef :
359359 """Specific inference for enums."""
360- for basename in (b for cls in node .mro () for b in cls .basenames ):
361- if node .root ().name == "enum" :
362- # Skip if the class is directly from enum module.
363- break
364- dunder_members = {}
365- target_names = set ()
366- for local , values in node .locals .items ():
367- if any (not isinstance (value , nodes .AssignName ) for value in values ):
368- continue
360+ if node .root ().name == "enum" :
361+ # Skip if the class is directly from enum module.
362+ return node
363+ dunder_members : dict [str , bases .Instance ] = {}
364+ for local , values in node .locals .items ():
365+ if any (not isinstance (value , nodes .AssignName ) for value in values ):
366+ continue
369367
370- stmt = values [0 ].statement (future = True )
371- if isinstance (stmt , nodes .Assign ):
372- if isinstance (stmt .targets [0 ], nodes .Tuple ):
373- targets = stmt .targets [0 ].itered ()
374- else :
375- targets = stmt .targets
376- elif isinstance (stmt , nodes .AnnAssign ):
377- targets = [stmt .target ]
368+ stmt = values [0 ].statement (future = True )
369+ if isinstance (stmt , nodes .Assign ):
370+ if isinstance (stmt .targets [0 ], nodes .Tuple ):
371+ targets : list [nodes .NodeNG ] = stmt .targets [0 ].itered ()
378372 else :
373+ targets = stmt .targets
374+ value_node = stmt .value
375+ elif isinstance (stmt , nodes .AnnAssign ):
376+ targets = [stmt .target ] # type: ignore[list-item] # .target shouldn't be None
377+ value_node = stmt .value
378+ else :
379+ continue
380+
381+ new_targets : list [bases .Instance ] = []
382+ for target in targets :
383+ if isinstance (target , nodes .Starred ):
379384 continue
380385
381- inferred_return_value = None
382- if isinstance (stmt , nodes .Assign ):
383- if isinstance (stmt .value , nodes .Const ):
384- if isinstance (stmt .value .value , str ):
385- inferred_return_value = repr (stmt .value .value )
386- else :
387- inferred_return_value = stmt .value .value
388- else :
389- inferred_return_value = stmt .value .as_string ()
390-
391- new_targets = []
392- for target in targets :
393- if isinstance (target , nodes .Starred ):
394- continue
395- target_names .add (target .name )
396- # Replace all the assignments with our mocked class.
397- classdef = dedent (
398- """
399- class {name}({types}):
400- @property
401- def value(self):
402- return {return_value}
403- @property
404- def name(self):
405- return "{name}"
406- """ .format (
407- name = target .name ,
408- types = ", " .join (node .basenames ),
409- return_value = inferred_return_value ,
410- )
411- )
412- if "IntFlag" in basename :
413- # Alright, we need to add some additional methods.
414- # Unfortunately we still can't infer the resulting objects as
415- # Enum members, but once we'll be able to do that, the following
416- # should result in some nice symbolic execution
417- classdef += INT_FLAG_ADDITION_METHODS .format (name = target .name )
418-
419- fake = AstroidBuilder (
420- AstroidManager (), apply_transforms = False
421- ).string_build (classdef )[target .name ]
422- fake .parent = target .parent
423- for method in node .mymethods ():
424- fake .locals [method .name ] = [method ]
425- new_targets .append (fake .instantiate_class ())
426- dunder_members [local ] = fake
427- node .locals [local ] = new_targets
428- members = nodes .Dict (parent = node )
429- members .postinit (
430- [
431- (nodes .Const (k , parent = members ), nodes .Name (v .name , parent = members ))
432- for k , v in dunder_members .items ()
386+ # Instantiate a class of the Enum with the value and name
387+ # attributes set to the values of the assignment
388+ # See: https://docs.python.org/3/library/enum.html#creating-an-enum
389+ target_node = node .instantiate_class ()
390+ target_node ._explicit_instance_attrs ["value" ] = [value_node ]
391+ target_node ._explicit_instance_attrs ["name" ] = [
392+ nodes .const_factory (target .name )
433393 ]
434- )
435- node .locals ["__members__" ] = [members ]
436- # The enum.Enum class itself defines two @DynamicClassAttribute data-descriptors
437- # "name" and "value" (which we override in the mocked class for each enum member
438- # above). When dealing with inference of an arbitrary instance of the enum
439- # class, e.g. in a method defined in the class body like:
440- # class SomeEnum(enum.Enum):
441- # def method(self):
442- # self.name # <- here
443- # In the absence of an enum member called "name" or "value", these attributes
444- # should resolve to the descriptor on that particular instance, i.e. enum member.
445- # For "value", we have no idea what that should be, but for "name", we at least
446- # know that it should be a string, so infer that as a guess.
447- if "name" not in target_names :
448- code = dedent (
449- """
450- @property
451- def name(self):
452- return ''
453- """
454- )
455- name_dynamicclassattr = AstroidBuilder (AstroidManager ()).string_build (code )[
456- "name"
457- ]
458- node .locals ["name" ] = [name_dynamicclassattr ]
459- break
394+
395+ new_targets .append (target_node )
396+ dunder_members [local ] = target_node
397+
398+ node .locals [local ] = new_targets
399+
400+ # Creation of the __members__ attribute of the Enum node
401+ members = nodes .Dict (parent = node )
402+ members .postinit (
403+ [
404+ (nodes .Const (k , parent = members ), nodes .Name (v .name , parent = members ))
405+ for k , v in dunder_members .items ()
406+ ]
407+ )
408+ node .locals ["__members__" ] = [members ]
460409 return node
461410
462411
0 commit comments