@@ -357,106 +357,55 @@ def __mul__(self, other):
357
357
358
358
def infer_enum_class (node : nodes .ClassDef ) -> nodes .ClassDef :
359
359
"""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
369
367
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 ()
378
372
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 ):
379
384
continue
380
385
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 )
433
393
]
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 ]
460
409
return node
461
410
462
411
0 commit comments