summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Byrne <31762852+mbyrnepr2@users.noreply.github.com>2023-01-15 18:26:47 +0100
committerGitHub <noreply@github.com>2023-01-15 18:26:47 +0100
commitc267397eda848544bcbea04e889815ac4faa6ba8 (patch)
treec1508f72cee5d2d92b58beb57f55811fb734c3ee
parent8581d9d26ae76ea3d5719bdd9a0e103bf79e1528 (diff)
downloadastroid-git-c267397eda848544bcbea04e889815ac4faa6ba8.tar.gz
Fix a false positive with user-defined `Enum` class (#1967)
Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>
-rw-r--r--.github/workflows/ci.yaml1
-rw-r--r--ChangeLog5
-rw-r--r--astroid/brain/brain_namedtuple_enum.py22
-rw-r--r--tests/unittest_brain.py44
4 files changed, 71 insertions, 1 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 01c0be83..a4be3e87 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -93,6 +93,7 @@ jobs:
- name: Install Qt
if: ${{ matrix.python-version == '3.10' }}
run: |
+ sudo apt-get update
sudo apt-get install build-essential libgl1-mesa-dev
- name: Generate partial Python venv restore key
id: generate-python-key
diff --git a/ChangeLog b/ChangeLog
index 5eb6c5e9..fc4d5643 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,11 @@ Release date: TBA
Closes #1958
+* Fix a false positive when an attribute named ``Enum`` was confused with ``enum.Enum``.
+ Calls to ``Enum`` are now inferred & the qualified name is checked.
+
+ Refs PyCQA/pylint#5719
+
* Remove unnecessary typing_extensions dependency on Python 3.11 and newer
What's New in astroid 2.13.2?
diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py
index ed80e783..c7e847f8 100644
--- a/astroid/brain/brain_namedtuple_enum.py
+++ b/astroid/brain/brain_namedtuple_enum.py
@@ -8,6 +8,7 @@ from __future__ import annotations
import functools
import keyword
+import sys
from collections.abc import Iterator
from textwrap import dedent
@@ -24,7 +25,12 @@ from astroid.exceptions import (
)
from astroid.manager import AstroidManager
-TYPING_NAMEDTUPLE_BASENAMES = {"NamedTuple", "typing.NamedTuple"}
+if sys.version_info >= (3, 8):
+ from typing import Final
+else:
+ from typing_extensions import Final
+
+
ENUM_BASE_NAMES = {
"Enum",
"IntEnum",
@@ -33,6 +39,8 @@ ENUM_BASE_NAMES = {
"IntFlag",
"enum.IntFlag",
}
+ENUM_QNAME: Final[str] = "enum.Enum"
+TYPING_NAMEDTUPLE_BASENAMES: Final[set[str]] = {"NamedTuple", "typing.NamedTuple"}
def _infer_first(node, context):
@@ -298,6 +306,18 @@ def infer_enum(
node: nodes.Call, context: InferenceContext | None = None
) -> Iterator[bases.Instance]:
"""Specific inference function for enum Call node."""
+ # Raise `UseInferenceDefault` if `node` is a call to a a user-defined Enum.
+ try:
+ inferred = node.func.infer(context)
+ except (InferenceError, StopIteration) as exc:
+ raise UseInferenceDefault from exc
+
+ if not any(
+ isinstance(item, nodes.ClassDef) and item.qname() == ENUM_QNAME
+ for item in inferred
+ ):
+ raise UseInferenceDefault
+
enum_meta = _extract_single_node(
"""
class EnumMeta(object):
diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py
index 11dd1139..9ee0f98f 100644
--- a/tests/unittest_brain.py
+++ b/tests/unittest_brain.py
@@ -1230,6 +1230,50 @@ class EnumBrainTest(unittest.TestCase):
assert isinstance(inferred, bases.Instance)
assert inferred._proxied.name == "ENUM_KEY"
+ def test_class_named_enum(self) -> None:
+ """Test that the user-defined class named `Enum` is not inferred as `enum.Enum`"""
+ astroid.extract_node(
+ """
+ class Enum:
+ def __init__(self, one, two):
+ self.one = one
+ self.two = two
+ def pear(self):
+ ...
+ """,
+ "module_with_class_named_enum",
+ )
+
+ attribute_nodes = astroid.extract_node(
+ """
+ import module_with_class_named_enum
+ module_with_class_named_enum.Enum("apple", "orange") #@
+ typo_module_with_class_named_enum.Enum("apple", "orange") #@
+ """
+ )
+
+ name_nodes = astroid.extract_node(
+ """
+ from module_with_class_named_enum import Enum
+ Enum("apple", "orange") #@
+ TypoEnum("apple", "orange") #@
+ """
+ )
+
+ # Test that both of the successfully inferred `Name` & `Attribute`
+ # nodes refer to the user-defined Enum class.
+ for inferred in (attribute_nodes[0].inferred()[0], name_nodes[0].inferred()[0]):
+ assert isinstance(inferred, astroid.Instance)
+ assert inferred.name == "Enum"
+ assert inferred.qname() == "module_with_class_named_enum.Enum"
+ assert "pear" in inferred.locals
+
+ # Test that an `InferenceError` is raised when an attempt is made to
+ # infer a `Name` or `Attribute` node & they cannot be found.
+ for node in (attribute_nodes[1], name_nodes[1]):
+ with pytest.raises(InferenceError):
+ node.inferred()
+
@unittest.skipUnless(HAS_DATEUTIL, "This test requires the dateutil library.")
class DateutilBrainTest(unittest.TestCase):