diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index 45ec204484..31849ff193 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -31,3 +31,31 @@ jobs: . venv2\scripts\activate echo "import distutils.util # pylint: disable=unused-import" > test.py pylint test.py + + additional-dependencies-linux-tests: + name: Regression tests w/ additional dependencies (Linux) + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.0.0 + - name: Set up Python + id: python + uses: actions/setup-python@v3.1.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + - name: Install Qt + run: | + sudo apt-get install build-essential libgl1-mesa-dev + - name: Create Python virtual environment + run: | + python -m venv venv + . venv/bin/activate + python -m pip install -U pip setuptools wheel + pip install -U -r requirements_test.txt -r requirements_test_brain.txt + pip install -e . + - name: Run brain_qt tests + # Regression test added in https://github.com/PyCQA/astroid/pull/1505 + run: | + . venv/bin/activate + pytest tests/unittest_brain_qt.py diff --git a/ChangeLog b/ChangeLog index b0ad29b96d..10a7e90857 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,9 @@ What's New in astroid 2.11.3? ============================= Release date: TBA +* Fixed an error in the Qt brain when building ``instance_attrs``. + + Closes PyCQA/pylint#6221 What's New in astroid 2.11.2? diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py index a9be8f2760..c02508478f 100644 --- a/astroid/brain/brain_qt.py +++ b/astroid/brain/brain_qt.py @@ -21,7 +21,7 @@ def _looks_like_signal(node, signal_name="pyqtSignal"): return False -def transform_pyqt_signal(node): +def transform_pyqt_signal(node: nodes.FunctionDef) -> None: module = parse( """ class pyqtSignal(object): @@ -33,13 +33,13 @@ def emit(self, *args): pass """ ) - signal_cls = module["pyqtSignal"] - node.instance_attrs["emit"] = signal_cls["emit"] - node.instance_attrs["disconnect"] = signal_cls["disconnect"] - node.instance_attrs["connect"] = signal_cls["connect"] + signal_cls: nodes.ClassDef = module["pyqtSignal"] + node.instance_attrs["emit"] = [signal_cls["emit"]] + node.instance_attrs["disconnect"] = [signal_cls["disconnect"]] + node.instance_attrs["connect"] = [signal_cls["connect"]] -def transform_pyside_signal(node): +def transform_pyside_signal(node: nodes.FunctionDef) -> None: module = parse( """ class NotPySideSignal(object): @@ -51,10 +51,10 @@ def emit(self, *args): pass """ ) - signal_cls = module["NotPySideSignal"] - node.instance_attrs["connect"] = signal_cls["connect"] - node.instance_attrs["disconnect"] = signal_cls["disconnect"] - node.instance_attrs["emit"] = signal_cls["emit"] + signal_cls: nodes.ClassDef = module["NotPySideSignal"] + node.instance_attrs["connect"] = [signal_cls["connect"]] + node.instance_attrs["disconnect"] = [signal_cls["disconnect"]] + node.instance_attrs["emit"] = [signal_cls["emit"]] def pyqt4_qtcore_transform(): diff --git a/astroid/nodes/scoped_nodes/mixin.py b/astroid/nodes/scoped_nodes/mixin.py index a6c23b5c07..bb1e76f14f 100644 --- a/astroid/nodes/scoped_nodes/mixin.py +++ b/astroid/nodes/scoped_nodes/mixin.py @@ -108,11 +108,10 @@ def add_local_node(self, child_node, name=None): self._append_node(child_node) self.set_local(name or child_node.name, child_node) - def __getitem__(self, item): + def __getitem__(self, item: str) -> "nodes.NodeNG": """The first node the defines the given local. :param item: The name of the locally defined object. - :type item: str :raises KeyError: If the name is not defined. """ diff --git a/requirements_test_brain.txt b/requirements_test_brain.txt index a45e3bf2d0..9f7e13411f 100644 --- a/requirements_test_brain.txt +++ b/requirements_test_brain.txt @@ -3,6 +3,7 @@ types-attrs nose numpy>=1.17.0 python-dateutil +PyQt6 types-python-dateutil six types-six diff --git a/tests/unittest_brain_qt.py b/tests/unittest_brain_qt.py new file mode 100644 index 0000000000..18e0d6d3e8 --- /dev/null +++ b/tests/unittest_brain_qt.py @@ -0,0 +1,38 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt + +from importlib.util import find_spec + +import pytest + +from astroid import Uninferable, extract_node +from astroid.bases import UnboundMethod +from astroid.manager import AstroidManager +from astroid.nodes import FunctionDef + +HAS_PYQT6 = find_spec("PyQt6") + + +@pytest.mark.skipif(HAS_PYQT6 is None, reason="This test requires the PyQt6 library.") +class TestBrainQt: + @staticmethod + def test_value_of_lambda_instance_attrs_is_list(): + """Regression test for https://github.com/PyCQA/pylint/issues/6221 + + A crash occurred in pylint when a nodes.FunctionDef was iterated directly, + giving items like "self" instead of iterating a one-element list containing + the wanted nodes.FunctionDef. + """ + src = """ + from PyQt6 import QtPrintSupport as printsupport + printsupport.QPrintPreviewDialog.paintRequested #@ + """ + AstroidManager.brain["extension_package_whitelist"] = {"PyQt6.QtPrintSupport"} + node = extract_node(src) + attribute_node = node.inferred()[0] + if attribute_node is Uninferable: + pytest.skip("PyQt6 C bindings may not be installed?") + assert isinstance(attribute_node, UnboundMethod) + # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]] + assert isinstance(attribute_node.instance_attrs["connect"][0], FunctionDef)