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